@camstack/addon-terminal 0.1.31 → 0.1.33

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 (3) hide show
  1. package/dist/addon.js +657 -397
  2. package/dist/addon.mjs +657 -397
  3. package/package.json +1 -1
package/dist/addon.mjs CHANGED
@@ -5810,6 +5810,13 @@ var BaseAddon = class {
5810
5810
  _readinessGeneration = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2, 14);
5811
5811
  /** Capability names this addon registered at init — used to emit matching `down` events on shutdown. */
5812
5812
  _registeredCapNames = [];
5813
+ /**
5814
+ * True only after `readAddonStore` actually answered. Constructor
5815
+ * defaults look like stored config when the store is down — a forked
5816
+ * addon that auto-starts from those defaults (cloudflare-tunnel quick
5817
+ * mode, 2026-08-25) is not "the operator chose this".
5818
+ */
5819
+ settingsStoreReady = false;
5813
5820
  /** Default config values. Provided via constructor. */
5814
5821
  defaults;
5815
5822
  constructor(defaults) {
@@ -6210,7 +6217,9 @@ var BaseAddon = class {
6210
6217
  ];
6211
6218
  let lastErr;
6212
6219
  for (let attempt = 0; attempt <= delaysMs.length; attempt++) try {
6213
- return await settings.readAddonStore() ?? {};
6220
+ const stored = await settings.readAddonStore() ?? {};
6221
+ this.settingsStoreReady = true;
6222
+ return stored;
6214
6223
  } catch (err) {
6215
6224
  lastErr = err;
6216
6225
  const msg = err instanceof Error ? err.message : String(err);
@@ -6218,6 +6227,7 @@ var BaseAddon = class {
6218
6227
  if (attempt === delaysMs.length) break;
6219
6228
  await new Promise((r) => setTimeout(r, delaysMs[attempt]));
6220
6229
  }
6230
+ this.settingsStoreReady = false;
6221
6231
  this._ctx?.logger?.warn?.("readAddonStore: settings-store unavailable after retries — using defaults", { meta: { error: lastErr instanceof Error ? lastErr.message : String(lastErr) } });
6222
6232
  return {};
6223
6233
  }
@@ -8096,6 +8106,15 @@ var LabelDefinitionSchema = object({
8096
8106
  description: string().optional(),
8097
8107
  icon: string().optional()
8098
8108
  });
8109
+ var ClassMapDefinitionSchema = object({
8110
+ mapping: record(string(), _enum([
8111
+ "person",
8112
+ "vehicle",
8113
+ "animal",
8114
+ "package"
8115
+ ])),
8116
+ preserveOriginal: boolean()
8117
+ });
8099
8118
  var MODEL_FORMATS = [
8100
8119
  "onnx",
8101
8120
  "coreml",
@@ -8179,6 +8198,12 @@ var ModelVariantGroupSchema = object({
8179
8198
  */
8180
8199
  resolution: number().int().positive().optional()
8181
8200
  });
8201
+ var ModelProviderIdSchema = _enum([
8202
+ "camstack",
8203
+ "frigate",
8204
+ "scrypted",
8205
+ "custom"
8206
+ ]);
8182
8207
  var ModelCatalogEntrySchema = object({
8183
8208
  id: string(),
8184
8209
  name: string(),
@@ -8274,7 +8299,19 @@ var ModelCatalogEntrySchema = object({
8274
8299
  * `id` stays the source of truth for resolution/download/persistence; grouping
8275
8300
  * is a presentation overlay resolved back to an `id`.
8276
8301
  */
8277
- group: ModelVariantGroupSchema.optional()
8302
+ group: ModelVariantGroupSchema.optional(),
8303
+ /**
8304
+ * Catalog source for the pipeline stepper's provider-first picker. Absent on
8305
+ * built-in CamStack entries (treated as `camstack`) and on registry rows
8306
+ * persisted before this field existed (`inferModelProvider` fills those).
8307
+ */
8308
+ provider: ModelProviderIdSchema.optional(),
8309
+ /**
8310
+ * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8311
+ * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8312
+ * labels already ARE the CamStack macros (Scrypted identity map).
8313
+ */
8314
+ classMap: ClassMapDefinitionSchema.optional()
8278
8315
  });
8279
8316
  var ConvertTargetSchema = discriminatedUnion("format", [object({
8280
8317
  format: literal("openvino"),
@@ -8303,7 +8340,8 @@ var ModelConvertMetadataSchema = object({
8303
8340
  "ocr",
8304
8341
  "segmentation"
8305
8342
  ]),
8306
- faceAlignment: boolean().optional()
8343
+ faceAlignment: boolean().optional(),
8344
+ classMap: ClassMapDefinitionSchema.optional()
8307
8345
  });
8308
8346
  var ConvertResultSchema = object({
8309
8347
  entry: ModelCatalogEntrySchema,
@@ -12005,6 +12043,27 @@ var LinkedDeviceSchema = object({
12005
12043
  features: array(string()),
12006
12044
  producesTrackedEvents: boolean().optional()
12007
12045
  });
12046
+ /** One camera's resolved linked set, tagged with the camera it belongs to.
12047
+ * The batch answer needs the tag; the single-device answer already has it
12048
+ * from the input, which is why `getLinkedDevices` keeps the untagged shape. */
12049
+ var LinkedDevicesForDeviceSchema = object({
12050
+ deviceId: number(),
12051
+ mode: LinkedDevicesModeSchema,
12052
+ devices: array(LinkedDeviceSchema)
12053
+ });
12054
+ /** One device's binding map — the shape `getBindings`, `getBindingsBatch` and
12055
+ * `getAllBindings` all answer in. Declared once: three copies of the same
12056
+ * object literal is exactly how the three drift apart. */
12057
+ var DeviceBindingsForDeviceSchema = object({
12058
+ deviceId: number(),
12059
+ entries: array(object({
12060
+ capName: string(),
12061
+ kind: _enum(["native", "wrapped"]),
12062
+ providerAddonId: string(),
12063
+ providerNodeId: string(),
12064
+ nativeAddonId: string()
12065
+ }))
12066
+ });
12008
12067
  var SavedDeviceRowSchema = object({
12009
12068
  /** Numeric id reserved at allocateDeviceId time. */
12010
12069
  id: number(),
@@ -12230,11 +12289,25 @@ method(object({
12230
12289
  projection: _enum(["full", "slim"]).optional(),
12231
12290
  /** Return only camera devices. Filtering server-side instead of
12232
12291
  * shipping 293 rows to find 12. */
12233
- isCamera: boolean().optional()
12292
+ isCamera: boolean().optional(),
12293
+ /**
12294
+ * Return only these device ids. For the caller that already KNOWS the
12295
+ * handful it wants and needs a field the id-bearing answer does not
12296
+ * carry — the viewer's linked-devices panel joins `type` and `online`
12297
+ * onto ~8 linked ids and, unfiltered, dragged the fleet across to do
12298
+ * it: 433 KB slim / 958 KB full for 967 devices, on a query that
12299
+ * refetches on the reconcile interval, on a phone.
12300
+ *
12301
+ * Safe to send at a hub that predates it: Zod STRIPS unknown input
12302
+ * keys rather than rejecting them (verified against the live hub
12303
+ * 2026-08-25 — 967 rows came back), so an old hub answers exactly what
12304
+ * it answers today and the caller filters as it already does.
12305
+ */
12306
+ deviceIds: array(number()).optional()
12234
12307
  }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
12235
12308
  mode: LinkedDevicesModeSchema,
12236
12309
  devices: array(LinkedDeviceSchema)
12237
- })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
12310
+ })), method(object({ deviceIds: array(number()) }), array(LinkedDevicesForDeviceSchema)), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
12238
12311
  deviceId: number(),
12239
12312
  values: record(string(), unknown())
12240
12313
  }), object({ success: literal(true) }), {
@@ -12261,25 +12334,7 @@ method(object({
12261
12334
  }), method(object({ deviceId: number() }), array(StreamProbeResultSchema), {
12262
12335
  kind: "mutation",
12263
12336
  auth: "admin"
12264
- }), method(object({ deviceId: number() }), object({
12265
- deviceId: number(),
12266
- entries: array(object({
12267
- capName: string(),
12268
- kind: _enum(["native", "wrapped"]),
12269
- providerAddonId: string(),
12270
- providerNodeId: string(),
12271
- nativeAddonId: string()
12272
- }))
12273
- })), method(object({}), array(object({
12274
- deviceId: number(),
12275
- entries: array(object({
12276
- capName: string(),
12277
- kind: _enum(["native", "wrapped"]),
12278
- providerAddonId: string(),
12279
- providerNodeId: string(),
12280
- nativeAddonId: string()
12281
- }))
12282
- }))), method(object({
12337
+ }), method(object({ deviceId: number() }), DeviceBindingsForDeviceSchema), method(object({ deviceIds: array(number()) }), array(DeviceBindingsForDeviceSchema)), method(object({}), array(DeviceBindingsForDeviceSchema)), method(object({
12283
12338
  deviceId: number(),
12284
12339
  capName: string(),
12285
12340
  wrapperAddonId: string(),
@@ -14689,12 +14744,15 @@ var NcOccupancyConditionSchema = object({
14689
14744
  * there is no second switch that can disagree with the first and every rule
14690
14745
  * authored before the decision migrates for free (`audioModeOf`):
14691
14746
  *
14692
- * - **LABEL mode — `labels` present.** The rule fires on the FIRST frame the
14693
- * classifier labels with one of them. No window, no percentage:
14694
- * `hitPercent` and `samplingSeconds` are ignored, and the rule's own
14695
- * `throttle` cooldown is the only brake. The per-label confidence floor is
14696
- * the analyzer's (`classificationMinScore`, per device) a label only
14697
- * reaches this condition if the classifier was already confident enough.
14747
+ * - **LABEL mode — `labels` present.** The rule fires when `confirmHits`
14748
+ * labelled frames land inside `confirmWindowSec` (default 2 in 5 s).
14749
+ * `hitPercent` and `samplingSeconds` are still ignored a percentage of
14750
+ * frames is the wrong question for a classifier that labels 1–3 frames
14751
+ * per episode. The count window is the brake that drops a single-frame
14752
+ * false positive; the rule's own `throttle` cooldown is the other. The
14753
+ * per-label confidence floor is the analyzer's (`classificationMinScore`,
14754
+ * per device) — a label only reaches this condition if the classifier was
14755
+ * already confident enough. `confirmHits: 1` restores first-frame fire.
14698
14756
  * - **LEVEL mode — `dbThreshold` present, no labels.** The sampling window IS
14699
14757
  * the condition: at least `hitPercent`% of the samples over
14700
14758
  * `samplingSeconds` must be at or above `dbThreshold` dBFS (see
@@ -14721,14 +14779,22 @@ var NcOccupancyConditionSchema = object({
14721
14779
  * an operator who typed `dog` mean the same thing.
14722
14780
  */
14723
14781
  var NcAudioConditionSchema = object({
14724
- /** LABEL MODE: audio macro labels. Present ⇒ fires on the first labelled frame. */
14782
+ /** LABEL MODE: audio macro labels. Present ⇒ count-in-window confirm. */
14725
14783
  labels: array(string().min(1)).min(1).optional(),
14726
14784
  /** LEVEL MODE: floor in dBFS (negative-going, `0` = full scale). */
14727
14785
  dbThreshold: number().min(-96).max(0).optional(),
14728
14786
  /** LEVEL MODE ONLY: percentage of the window's samples that must be hits (1–100). */
14729
14787
  hitPercent: number().int().min(1).max(100).default(60),
14730
14788
  /** LEVEL MODE ONLY: length of the sampling window in seconds. */
14731
- samplingSeconds: number().int().min(1).max(300).default(10)
14789
+ samplingSeconds: number().int().min(1).max(300).default(10),
14790
+ /**
14791
+ * LABEL MODE: how many labelled frames must land inside
14792
+ * {@link NcAudioConditionSchema.shape.confirmWindowSec} before dispatch.
14793
+ * Absent ⇒ 2 (the matcher default). `1` is first-frame fire.
14794
+ */
14795
+ confirmHits: number().int().min(1).max(20).optional(),
14796
+ /** LABEL MODE: the window those frames must share, in seconds. Absent ⇒ 5. */
14797
+ confirmWindowSec: number().int().min(1).max(60).optional()
14732
14798
  });
14733
14799
  /**
14734
14800
  * Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
@@ -17102,6 +17168,46 @@ var RecentTracksPageSchema = object({
17102
17168
  /** Cursor for the next page, or null when this page is the last. */
17103
17169
  nextCursor: string().nullable()
17104
17170
  });
17171
+ var LIST_GROUPS_DEFAULT_LIMIT = 40;
17172
+ var LIST_GROUPS_MAX_LIMIT = 100;
17173
+ var AnalyticsGroupRecordSchema = object({
17174
+ id: string(),
17175
+ deviceId: number().int(),
17176
+ openedAt: number().int(),
17177
+ closedAt: number().int(),
17178
+ timestamp: number().int(),
17179
+ memberCount: number().int(),
17180
+ memberTrackIds: array(string()).readonly(),
17181
+ className: string(),
17182
+ classes: array(string()).readonly(),
17183
+ /** Relative event-media path, or null when the group has no picture yet. */
17184
+ mediaUrl: string().nullable(),
17185
+ singleton: boolean()
17186
+ });
17187
+ var AnalyticsGroupMemberSchema = object({
17188
+ trackId: string(),
17189
+ deviceId: number().int(),
17190
+ className: string(),
17191
+ firstSeen: number().int(),
17192
+ lastSeen: number().int(),
17193
+ mediaUrl: string().nullable()
17194
+ });
17195
+ var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: array(AnalyticsGroupMemberSchema).readonly() });
17196
+ var ListGroupsQueryInput = object({
17197
+ /** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
17198
+ deviceIds: array(number()),
17199
+ /** Window lower bound on `closedAt` (inclusive). */
17200
+ since: number().optional(),
17201
+ /** Window upper bound on `openedAt` (inclusive). */
17202
+ until: number().optional(),
17203
+ limit: number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
17204
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
17205
+ cursor: string().optional()
17206
+ });
17207
+ var ListGroupsPageSchema = object({
17208
+ groups: array(AnalyticsGroupRecordSchema).readonly(),
17209
+ nextCursor: string().nullable()
17210
+ });
17105
17211
  var KeyEventQueryInput = object({
17106
17212
  deviceId: number(),
17107
17213
  /** Window lower bound (track firstSeen ≥ since). */
@@ -17177,7 +17283,9 @@ var TrackCascadeCountsSchema = object({
17177
17283
  /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
17178
17284
  plates: number().int(),
17179
17285
  /** Per-track CLIP search vectors removed (best-effort). */
17180
- embeddings: number().int()
17286
+ embeddings: number().int(),
17287
+ /** Group membership + group rows removed with their last member (best-effort). */
17288
+ groups: number().int()
17181
17289
  });
17182
17290
  /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
17183
17291
  var DiskReconcileCountsSchema = object({
@@ -17323,7 +17431,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
17323
17431
  * stationary registry). Default false: the timeline lists passages,
17324
17432
  * not parking records (operator decision, 2026-08-15). */
17325
17433
  includeStationary: boolean().optional()
17326
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
17434
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(ListGroupsQueryInput, ListGroupsPageSchema), method(object({
17435
+ deviceId: number(),
17436
+ groupId: string().min(1)
17437
+ }), AnalyticsGroupDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
17327
17438
  kind: "mutation",
17328
17439
  auth: "admin"
17329
17440
  }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(EventKindsForDeviceSchema).readonly()), method(object({
@@ -17541,6 +17652,33 @@ var NativeCropRefSchema = object({
17541
17652
  h: number()
17542
17653
  })
17543
17654
  });
17655
+ object({
17656
+ crop: object({
17657
+ left: number(),
17658
+ top: number(),
17659
+ width: number().positive(),
17660
+ height: number().positive()
17661
+ }).optional(),
17662
+ content: object({
17663
+ width: number().int().positive(),
17664
+ height: number().int().positive()
17665
+ }),
17666
+ fit: _enum(["stretch", "contain"]),
17667
+ format: _enum([
17668
+ "rgb",
17669
+ "gray",
17670
+ "jpeg"
17671
+ ])
17672
+ });
17673
+ var FrameRefSchema = object({
17674
+ registryId: string().min(1),
17675
+ id: string().min(1),
17676
+ width: number().int().positive(),
17677
+ height: number().int().positive(),
17678
+ format: _enum(["rgb", "gray"]),
17679
+ timestamp: number(),
17680
+ capturedAt: number().optional()
17681
+ });
17544
17682
  var ModelFormatSchema$1 = _enum([
17545
17683
  "onnx",
17546
17684
  "coreml",
@@ -17606,7 +17744,8 @@ var PipelineModelOptionSchema = object({
17606
17744
  sizeMB: number()
17607
17745
  })),
17608
17746
  group: ModelVariantGroupSchema.optional(),
17609
- legacy: boolean().optional()
17747
+ legacy: boolean().optional(),
17748
+ provider: ModelProviderIdSchema.optional()
17610
17749
  });
17611
17750
  var ConfigFieldBridge = custom();
17612
17751
  var PipelineAddonSchemaSchema = object({
@@ -17785,6 +17924,12 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
17785
17924
  steps: array(PipelineStepInputSchema).min(1),
17786
17925
  frame: FrameInputSchema.optional(),
17787
17926
  /**
17927
+ * Process-local lazy frame. Valid only when caller and provider resolve
17928
+ * in the same execution-group process; split/cross-node callers use
17929
+ * `frame`/`image` inline compatibility instead.
17930
+ */
17931
+ frameRef: FrameRefSchema.optional(),
17932
+ /**
17788
17933
  * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
17789
17934
  * the decoded pixels live in. One more member of the one-of
17790
17935
  * frame/frameHandle/image/imageBase64/referenceImage group.
@@ -18080,7 +18225,10 @@ var NativeCropResultSchema = object({
18080
18225
  * Which source served this crop, so a quality-sensitive consumer (the native
18081
18226
  * `keyFrame`) can reject a degraded fallback:
18082
18227
  * - `native` — cut from the decode worker's retained NATIVE surface (the
18083
- * quality path).
18228
+ * quality path). A subject-tile serve is also native-resolution and stays
18229
+ * `native` here: the public enum cannot name `tile` without a breaking cap
18230
+ * change. Runner telemetry distinguishes lease vs tile via `source` on the
18231
+ * internal crop result (`nativeHits` vs `tileHits`).
18084
18232
  * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
18085
18233
  * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
18086
18234
  * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
@@ -18571,12 +18719,41 @@ var RunnerLocalLoadSchema = object({
18571
18719
  * legacy `OrchestratorMetricsSchema` shape so existing dashboards keep
18572
18720
  * working unchanged when they switch to reading from the runner cap.
18573
18721
  */
18722
+ var FrameLazyCountersSchema = object({
18723
+ framesDecoded: number(),
18724
+ framesAdmitted: number(),
18725
+ framesDroppedPixelFree: number(),
18726
+ viewsMaterialized: number(),
18727
+ viewsSkipped: number(),
18728
+ workerToRunnerBytes: number(),
18729
+ runnerToPoolRawBytes: number(),
18730
+ runnerToPoolJpegBytes: number(),
18731
+ onDemandFullFrameRequests: number(),
18732
+ onDemandCropRequests: number(),
18733
+ nativeHits: number(),
18734
+ nativeMisses: number(),
18735
+ tileHits: number(),
18736
+ tileMisses: number(),
18737
+ fallbackHits: number(),
18738
+ fallbackMisses: number(),
18739
+ retainedWritesAvoided: number(),
18740
+ residentRefs: number(),
18741
+ residentBytes: number(),
18742
+ releases: number(),
18743
+ evictions: number(),
18744
+ staleMisses: number()
18745
+ });
18746
+ var FrameLazyMetricsSchema = object({
18747
+ node: FrameLazyCountersSchema,
18748
+ cameras: array(FrameLazyCountersSchema.extend({ deviceId: number() }))
18749
+ });
18574
18750
  var RunnerLocalMetricsSchema = object({
18575
18751
  nodeId: string(),
18576
18752
  activeCameras: number(),
18577
18753
  throttledCameras: number(),
18578
18754
  avgInferenceTimeMs: number(),
18579
- queueDepth: number()
18755
+ queueDepth: number(),
18756
+ frameLazy: FrameLazyMetricsSchema.optional()
18580
18757
  });
18581
18758
  method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mutation" }), method(object({ deviceId: number() }), object({ success: literal(true) }), { kind: "mutation" }), method(ReportMotionInputSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), RunnerLocalLoadSchema), method(_void(), RunnerLocalMetricsSchema), method(object({ deviceId: number() }), CameraMetricsSchema.nullable()), method(_void(), array(CameraMetricsWithDeviceIdSchema).readonly()), method(_void(), array(number()).readonly()), method(object({
18582
18759
  handle: FrameHandleSchema,
@@ -24186,6 +24363,17 @@ var NotificationEndpointSchema = object({
24186
24363
  /** What the ranking currently resolves to (null when nothing is reachable). */
24187
24364
  resolved: string().nullable()
24188
24365
  });
24366
+ /**
24367
+ * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
24368
+ * AUTO (every LAN IPv4 + the public tunnel). `resolved` is what that choice
24369
+ * currently expands to, so the UI can show the effective set either way.
24370
+ */
24371
+ var ViewerEndpointsSchema = object({
24372
+ /** The operator's explicit race set, or empty for AUTO. */
24373
+ baseUrls: array(string()).readonly(),
24374
+ /** What the ranking currently resolves to (may be empty if nothing is up). */
24375
+ resolved: array(string()).readonly()
24376
+ });
24189
24377
  var AllowedAddressesSchema = object({
24190
24378
  /**
24191
24379
  * Allowlist of interface addresses operators have explicitly opted
@@ -24217,17 +24405,18 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
24217
24405
  */
24218
24406
  port: number().int().min(1).max(65535).optional(),
24219
24407
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
24220
- * candidate. Default `true`. */
24408
+ * candidate. Default `false` — loopback is not a client route. */
24221
24409
  includeLoopback: boolean().optional(),
24222
- /** Skip IPv6 entries. Some legacy clients can't parse them.
24223
- * Default `false`. */
24410
+ /** Skip IPv6 entries. Default `false` the palette includes stable
24411
+ * LAN IPv6 (ICE/WebRTC uses dual-stack regardless). Pass `true` to
24412
+ * hide them. The viewer HTTP/WS race is `getViewerEndpoints`. */
24224
24413
  ipv4Only: boolean().optional(),
24225
24414
  /** Scheme to emit for LAN/loopback URLs. Default `'http'`.
24226
24415
  * Pass `'https'` when the caller is itself loaded over HTTPS
24227
24416
  * to avoid mixed-content blocks in the browser. The public
24228
24417
  * tunnel always emits `https://` regardless. */
24229
24418
  scheme: _enum(["http", "https"]).optional()
24230
- }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" }), method(_void(), TlsStatusSchema), method(object({ reason: string().optional() }), TlsStatusSchema, {
24419
+ }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), ViewerEndpointsSchema), method(object({ baseUrls: array(string()).readonly() }), ViewerEndpointsSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" }), method(_void(), TlsStatusSchema), method(object({ reason: string().optional() }), TlsStatusSchema, {
24231
24420
  kind: "mutation",
24232
24421
  auth: "admin"
24233
24422
  }), method(object({
@@ -25801,7 +25990,12 @@ var PlateInfoSchema = object({
25801
25990
  plateBbox: BoundingBoxSchema.optional(),
25802
25991
  /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
25803
25992
  keyFrameMediaKey: string().optional(),
25804
- base64: string().optional()
25993
+ base64: string().optional(),
25994
+ /**
25995
+ * Same crop as a data-plane URL. `getPlateByTrack` returns this and
25996
+ * never inlines JPEG; `listPlates` still inlines for the admin-ui.
25997
+ */
25998
+ cropUrl: string().optional()
25805
25999
  });
25806
26000
  var MediaFileLiteSchema = object({
25807
26001
  key: string(),
@@ -31323,6 +31517,12 @@ Object.freeze({
31323
31517
  addonId: null,
31324
31518
  access: "view"
31325
31519
  },
31520
+ "deviceManager.getBindingsBatch": {
31521
+ capName: "device-manager",
31522
+ capScope: "system",
31523
+ addonId: null,
31524
+ access: "view"
31525
+ },
31326
31526
  "deviceManager.getChildren": {
31327
31527
  capName: "device-manager",
31328
31528
  capScope: "system",
@@ -31383,6 +31583,12 @@ Object.freeze({
31383
31583
  addonId: null,
31384
31584
  access: "view"
31385
31585
  },
31586
+ "deviceManager.getLinkedDevicesBatch": {
31587
+ capName: "device-manager",
31588
+ capScope: "system",
31589
+ addonId: null,
31590
+ access: "view"
31591
+ },
31386
31592
  "deviceManager.getRoleDisplayDefaults": {
31387
31593
  capName: "device-manager",
31388
31594
  capScope: "system",
@@ -32301,6 +32507,12 @@ Object.freeze({
32301
32507
  addonId: null,
32302
32508
  access: "view"
32303
32509
  },
32510
+ "localNetwork.getViewerEndpoints": {
32511
+ capName: "local-network",
32512
+ capScope: "system",
32513
+ addonId: null,
32514
+ access: "view"
32515
+ },
32304
32516
  "localNetwork.list": {
32305
32517
  capName: "local-network",
32306
32518
  capScope: "system",
@@ -32337,6 +32549,12 @@ Object.freeze({
32337
32549
  addonId: null,
32338
32550
  access: "create"
32339
32551
  },
32552
+ "localNetwork.setViewerEndpoints": {
32553
+ capName: "local-network",
32554
+ capScope: "system",
32555
+ addonId: null,
32556
+ access: "create"
32557
+ },
32340
32558
  "localNetwork.uploadCertificate": {
32341
32559
  capName: "local-network",
32342
32560
  capScope: "system",
@@ -33141,6 +33359,12 @@ Object.freeze({
33141
33359
  addonId: null,
33142
33360
  access: "view"
33143
33361
  },
33362
+ "pipelineAnalytics.getGroup": {
33363
+ capName: "pipeline-analytics",
33364
+ capScope: "device",
33365
+ addonId: null,
33366
+ access: "view"
33367
+ },
33144
33368
  "pipelineAnalytics.getKeyEvents": {
33145
33369
  capName: "pipeline-analytics",
33146
33370
  capScope: "device",
@@ -33225,6 +33449,12 @@ Object.freeze({
33225
33449
  addonId: null,
33226
33450
  access: "view"
33227
33451
  },
33452
+ "pipelineAnalytics.listGroups": {
33453
+ capName: "pipeline-analytics",
33454
+ capScope: "device",
33455
+ addonId: null,
33456
+ access: "view"
33457
+ },
33228
33458
  "pipelineAnalytics.listOpsLog": {
33229
33459
  capName: "pipeline-analytics",
33230
33460
  capScope: "device",
@@ -36006,6 +36236,11 @@ Object.freeze({
36006
36236
  form: "single",
36007
36237
  optional: false
36008
36238
  }],
36239
+ "deviceManager.getBindingsBatch": [{
36240
+ name: "deviceIds",
36241
+ form: "array",
36242
+ optional: false
36243
+ }],
36009
36244
  "deviceManager.getChildren": [{
36010
36245
  name: "parentDeviceId",
36011
36246
  form: "single",
@@ -36051,6 +36286,11 @@ Object.freeze({
36051
36286
  form: "single",
36052
36287
  optional: false
36053
36288
  }],
36289
+ "deviceManager.getLinkedDevicesBatch": [{
36290
+ name: "deviceIds",
36291
+ form: "array",
36292
+ optional: false
36293
+ }],
36054
36294
  "deviceManager.getSettingsSchema": [{
36055
36295
  name: "deviceId",
36056
36296
  form: "single",
@@ -36071,6 +36311,11 @@ Object.freeze({
36071
36311
  form: "single",
36072
36312
  optional: false
36073
36313
  }],
36314
+ "deviceManager.listAll": [{
36315
+ name: "deviceIds",
36316
+ form: "array",
36317
+ optional: true
36318
+ }],
36074
36319
  "deviceManager.loadConfig": [{
36075
36320
  name: "deviceId",
36076
36321
  form: "single",
@@ -36644,6 +36889,11 @@ Object.freeze({
36644
36889
  form: "single",
36645
36890
  optional: false
36646
36891
  }],
36892
+ "pipelineAnalytics.getGroup": [{
36893
+ name: "deviceId",
36894
+ form: "single",
36895
+ optional: false
36896
+ }],
36647
36897
  "pipelineAnalytics.getKeyEvents": [{
36648
36898
  name: "deviceId",
36649
36899
  form: "single",
@@ -36699,6 +36949,11 @@ Object.freeze({
36699
36949
  form: "array",
36700
36950
  optional: false
36701
36951
  }],
36952
+ "pipelineAnalytics.listGroups": [{
36953
+ name: "deviceIds",
36954
+ form: "array",
36955
+ optional: false
36956
+ }],
36702
36957
  "pipelineAnalytics.listOpsLog": [{
36703
36958
  name: "deviceId",
36704
36959
  form: "single",
@@ -38116,356 +38371,6 @@ async function silenceAnalysisFor(deps, deviceId) {
38116
38371
  if (failures.length > 0) throw new Error(`terminal camera ${deviceId}: could not switch off ${failures.length} analyzer(s) — it will run at full detection cost (${failures.join("; ")})`);
38117
38372
  }
38118
38373
  //#endregion
38119
- //#region src/terminal-camera-declarations.ts
38120
- /**
38121
- * Feed DeclaredDevices every live declaration plus one deterministic orphan
38122
- * batch. The generic sweep intentionally refuses an over-limit set; selecting
38123
- * a batch here drains large historical Terminal orphan sets across convergence
38124
- * passes without weakening that global safety guard.
38125
- */
38126
- function terminalCameraReconciliationIndex(rows, integrationId, declarations, managedStableIds = /* @__PURE__ */ new Set()) {
38127
- if (!integrationId) return [];
38128
- const declared = new Set(declarations.map((camera) => camera.stableId));
38129
- const owned = rows.filter((row) => row.integrationId === integrationId && (declared.has(row.stableId) || managedStableIds.has(row.stableId) || row.stableId.startsWith("terminal-camera-instance-")));
38130
- return [...owned.filter((row) => declared.has(row.stableId)), ...owned.filter((row) => !declared.has(row.stableId)).sort((left, right) => left.id - right.id).slice(0, 32)];
38131
- }
38132
- /** Explicit persisted instances, never the node × profile template matrix. */
38133
- function buildTerminalInstanceCameraDeclarations(instances) {
38134
- return instances.filter((instance) => instance.enabled).map((instance) => ({
38135
- stableId: instance.cameraStableId,
38136
- name: instance.name,
38137
- config: {
38138
- instanceId: instance.id,
38139
- nodeId: instance.nodeId,
38140
- profileId: instance.profileId,
38141
- profileLabel: instance.profileLabel
38142
- }
38143
- }));
38144
- }
38145
- /**
38146
- * `DeviceConfig` materializes schema defaults in memory, so comparing
38147
- * `config.get()` cannot detect a legacy `{ nodeId }` row. Reconciliation must
38148
- * inspect the raw persisted blob to make the profile migration durable.
38149
- */
38150
- function needsTerminalCameraConfigMigration(persistedConfig, declaration) {
38151
- return persistedConfig.instanceId !== declaration.config.instanceId || persistedConfig.nodeId !== declaration.config.nodeId || persistedConfig.profileId !== declaration.config.profileId || persistedConfig.profileLabel !== declaration.config.profileLabel;
38152
- }
38153
- //#endregion
38154
- //#region src/terminal-cell-runs.ts
38155
- var TERMINAL_DEFAULT_FG = "#d7dce2";
38156
- var TERMINAL_DEFAULT_BG = "#0b0d10";
38157
- /**
38158
- * The 16 ANSI colours, in a dark-theme variant chosen to sit with the existing
38159
- * frame (`#0b0d10` ground, `#d7dce2` text) rather than the raw xterm defaults,
38160
- * whose pure `#0000ff` blue and `#008000` green are close to unreadable on a
38161
- * near-black background at 13px. Index 7 IS the default foreground, so plain
38162
- * `CSI 37m` text renders identically to unstyled text.
38163
- */
38164
- var TERMINAL_ANSI_PALETTE = [
38165
- "#282c34",
38166
- "#e06c75",
38167
- "#98c379",
38168
- "#e5c07b",
38169
- "#61afef",
38170
- "#c678dd",
38171
- "#56b6c2",
38172
- TERMINAL_DEFAULT_FG,
38173
- "#5c6370",
38174
- "#ef596f",
38175
- "#89ca78",
38176
- "#f0c674",
38177
- "#6cb6ff",
38178
- "#d55fde",
38179
- "#2bbac5",
38180
- "#ffffff"
38181
- ];
38182
- /** The six levels of the xterm 6×6×6 colour cube (indices 16-231). */
38183
- var TERMINAL_CUBE_LEVELS = [
38184
- 0,
38185
- 95,
38186
- 135,
38187
- 175,
38188
- 215,
38189
- 255
38190
- ];
38191
- var TERMINAL_CUBE_FIRST = 16;
38192
- var TERMINAL_GRAYSCALE_FIRST = 232;
38193
- var TERMINAL_GRAYSCALE_BASE = 8;
38194
- var TERMINAL_GRAYSCALE_STEP = 10;
38195
- /** SGR 2 keeps the foreground legible; it must not become the background. */
38196
- var TERMINAL_DIM_WEIGHT = .6;
38197
- function channel(value) {
38198
- return Math.max(0, Math.min(255, Math.round(value))).toString(16).padStart(2, "0");
38199
- }
38200
- function hex(red, green, blue) {
38201
- return `#${channel(red)}${channel(green)}${channel(blue)}`;
38202
- }
38203
- function parseHex(color) {
38204
- return [
38205
- Number.parseInt(color.slice(1, 3), 16),
38206
- Number.parseInt(color.slice(3, 5), 16),
38207
- Number.parseInt(color.slice(5, 7), 16)
38208
- ];
38209
- }
38210
- /** Resolve an xterm palette index (0-255) to a hex colour. */
38211
- function terminalPaletteColor(index) {
38212
- const ansi = TERMINAL_ANSI_PALETTE[index];
38213
- if (ansi !== void 0) return ansi;
38214
- if (index >= TERMINAL_GRAYSCALE_FIRST) {
38215
- const level = TERMINAL_GRAYSCALE_BASE + (index - TERMINAL_GRAYSCALE_FIRST) * TERMINAL_GRAYSCALE_STEP;
38216
- return hex(level, level, level);
38217
- }
38218
- if (index >= TERMINAL_CUBE_FIRST) {
38219
- const offset = index - TERMINAL_CUBE_FIRST;
38220
- return hex(TERMINAL_CUBE_LEVELS[Math.floor(offset / 36) % 6] ?? 0, TERMINAL_CUBE_LEVELS[Math.floor(offset / 6) % 6] ?? 0, TERMINAL_CUBE_LEVELS[offset % 6] ?? 0);
38221
- }
38222
- return TERMINAL_DEFAULT_FG;
38223
- }
38224
- /** Resolve a 0xRRGGBB truecolor value to a hex colour. */
38225
- function terminalRgbColor(value) {
38226
- return hex(value >> 16 & 255, value >> 8 & 255, value & 255);
38227
- }
38228
- function blend(color, toward, weight) {
38229
- const [red, green, blue] = parseHex(color);
38230
- const [targetRed, targetGreen, targetBlue] = parseHex(toward);
38231
- return hex(red * weight + targetRed * (1 - weight), green * weight + targetGreen * (1 - weight), blue * weight + targetBlue * (1 - weight));
38232
- }
38233
- function resolveForeground(cell) {
38234
- if (cell.isFgRGB()) return terminalRgbColor(cell.getFgColor());
38235
- if (cell.isFgPalette()) return terminalPaletteColor(cell.getFgColor());
38236
- return TERMINAL_DEFAULT_FG;
38237
- }
38238
- function resolveBackground(cell) {
38239
- if (cell.isBgRGB()) return terminalRgbColor(cell.getBgColor());
38240
- if (cell.isBgPalette()) return terminalPaletteColor(cell.getBgColor());
38241
- return TERMINAL_DEFAULT_BG;
38242
- }
38243
- /**
38244
- * Resolve one cell's attributes into concrete colours.
38245
- *
38246
- * Inverse is applied by SWAPPING the already-resolved pair, so inverse over two
38247
- * defaults is still a visible swap rather than a no-op — that is how a selected
38248
- * or highlighted row in Glances reads. Invisible is then conceal-by-equality
38249
- * (foreground painted in its own background): the cell keeps its columns, which
38250
- * a dropped cell would not, and dropping it would shift the whole rest of the
38251
- * row left.
38252
- */
38253
- function resolveCellStyle(cell) {
38254
- const inverse = cell.isInverse() !== 0;
38255
- const plainFg = resolveForeground(cell);
38256
- const plainBg = resolveBackground(cell);
38257
- const background = inverse ? plainFg : plainBg;
38258
- let foreground = inverse ? plainBg : plainFg;
38259
- if (cell.isDim() !== 0) foreground = blend(foreground, background, TERMINAL_DIM_WEIGHT);
38260
- if (cell.isInvisible() !== 0) foreground = background;
38261
- return {
38262
- fg: foreground === "#d7dce2" ? null : foreground,
38263
- bg: background === "#0b0d10" ? null : background,
38264
- bold: cell.isBold() !== 0
38265
- };
38266
- }
38267
- function sameStyle(left, right) {
38268
- return left.fg === right.fg && left.bg === right.bg && left.bold === right.bold;
38269
- }
38270
- /**
38271
- * Merge adjacent same-style cells into runs, then drop the trailing run of
38272
- * default-styled whitespace so a row costs what it draws — the same trim
38273
- * `translateToString(true)` performs. Whitespace carrying a BACKGROUND is kept:
38274
- * a green bar of spaces out to the right margin is a pixel Glances drew.
38275
- */
38276
- function buildCellRuns(cells) {
38277
- const runs = [];
38278
- let text = "";
38279
- let style = null;
38280
- for (const cell of cells) {
38281
- if (style !== null && sameStyle(style, cell.style)) {
38282
- text += cell.text;
38283
- continue;
38284
- }
38285
- if (style !== null) runs.push({
38286
- text,
38287
- ...style
38288
- });
38289
- text = cell.text;
38290
- style = cell.style;
38291
- }
38292
- if (style !== null) runs.push({
38293
- text,
38294
- ...style
38295
- });
38296
- while (runs.length > 0) {
38297
- const last = runs[runs.length - 1];
38298
- if (last === void 0 || last.bg !== null) break;
38299
- const trimmed = last.text.replace(/\s+$/u, "");
38300
- if (trimmed === last.text) break;
38301
- if (trimmed === "") {
38302
- runs.pop();
38303
- continue;
38304
- }
38305
- runs[runs.length - 1] = {
38306
- ...last,
38307
- text: trimmed
38308
- };
38309
- break;
38310
- }
38311
- return runs;
38312
- }
38313
- /**
38314
- * Monospace families to try, in order — NOT one family and a generic.
38315
- *
38316
- * A terminal screen is mostly box-drawing and block characters, and a font
38317
- * without them renders the frame as noise rather than as missing detail.
38318
- * `DejaVu Sans Mono` covers them and ships in the hub image; macOS has neither
38319
- * DejaVu nor fontconfig, so the Mac agent fell through to a generic whose
38320
- * coverage is not, and its Glances camera came out unreadable while the hub's
38321
- * was pixel-perfect (measured 2026-08-11, same Glances, two nodes).
38322
- *
38323
- * `Menlo` is the fix rather than a guess: it is macOS's default terminal face,
38324
- * present on every install, and derived from DejaVu Sans Mono — the same glyph
38325
- * coverage by ancestry. Monaco and Courier New follow as older fallbacks; the
38326
- * generic stays last so a host with none of them still draws something.
38327
- */
38328
- var TERMINAL_FONT_STACK = "DejaVu Sans Mono,Menlo,Monaco,Courier New,monospace";
38329
- var TERMINAL_FONT_SIZE = 13;
38330
- var TERMINAL_TEXT_MARGIN_X = 8;
38331
- var TERMINAL_ROW_HEIGHT = 15;
38332
- var TERMINAL_BASELINE_Y = 18;
38333
- /**
38334
- * Distance from a row's baseline up to the top of its cell box. Chosen so
38335
- * consecutive rows tile exactly: row N's box runs from `baseline - this` for
38336
- * `TERMINAL_ROW_HEIGHT`, and row N+1's box starts where it ends. A background
38337
- * bar that stopped short would draw as stripes across a `CSI 42m` panel.
38338
- */
38339
- var TERMINAL_CELL_ASCENT = 11.5;
38340
- var TERMINAL_CELL_WIDTH = TERMINAL_FONT_SIZE * (1233 / 2048);
38341
- /** Two decimals is under a tenth of a pixel and keeps the SVG small. */
38342
- function coordinate(value) {
38343
- return String(Number(value.toFixed(2)));
38344
- }
38345
- function escapeXml(value) {
38346
- return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&apos;");
38347
- }
38348
- /**
38349
- * Render already-interpreted terminal rows into a compact MJPEG frame.
38350
- *
38351
- * `xml:space="preserve"` is load-bearing, not tidiness. SVG `<text>` collapses
38352
- * runs of whitespace by default, and a terminal's entire column alignment IS
38353
- * runs of whitespace — Glances pads every field with spaces. Without it the
38354
- * frame drew each line at roughly half its true width, crammed into the
38355
- * top-left of a mostly-black image, while the SAME session over `attach`
38356
- * looked perfect — which is exactly how the operator reported it. Measured in
38357
- * the hub's own rasterizer 2026-08-11: `AAA<10 spaces>BBB<3>CCC` drew 92 px
38358
- * collapsed against 178 px preserved.
38359
- *
38360
- * Every run is anchored at its OWN column (`x = margin + startCol * cellW`),
38361
- * never appended to the one before it, so the background rects and the glyphs
38362
- * are placed off the same grid and cannot drift apart. `textLength` is emitted
38363
- * with it because it is the correct declaration and renderers that honour it
38364
- * get an exact grid — but it is not what makes this work: librsvg, which sharp
38365
- * uses, ignores it outright (measured 2026-08-12, a 120-glyph run pinned to
38366
- * 600 px still drew its natural 937 px). The anchoring is the guarantee.
38367
- */
38368
- function renderTerminalSvg(rows) {
38369
- const backgrounds = [];
38370
- const texts = [];
38371
- rows.slice(0, 40).forEach((row, index) => {
38372
- const baseline = TERMINAL_BASELINE_Y + index * TERMINAL_ROW_HEIGHT;
38373
- const top = baseline - TERMINAL_CELL_ASCENT;
38374
- let column = 0;
38375
- for (const run of row) {
38376
- if (column >= 120) break;
38377
- const clipped = clipRun(run, 120 - column);
38378
- const columns = [...clipped].length;
38379
- if (columns === 0) continue;
38380
- const x = TERMINAL_TEXT_MARGIN_X + column * TERMINAL_CELL_WIDTH;
38381
- const width = columns * TERMINAL_CELL_WIDTH;
38382
- if (run.bg !== null) backgrounds.push(`<rect x="${coordinate(x)}" y="${coordinate(top)}" width="${coordinate(width)}" height="${String(TERMINAL_ROW_HEIGHT)}" fill="${run.bg}"/>`);
38383
- if (clipped.trim() !== "") {
38384
- const fill = run.fg === null ? "" : ` fill="${run.fg}"`;
38385
- const weight = run.bold ? " font-weight=\"bold\"" : "";
38386
- texts.push(`<text xml:space="preserve" x="${coordinate(x)}" y="${coordinate(baseline)}" textLength="${coordinate(width)}" lengthAdjust="spacingAndGlyphs"${fill}${weight}>${escapeXml(clipped)}</text>`);
38387
- }
38388
- column += columns;
38389
- }
38390
- });
38391
- return `<svg xmlns="http://www.w3.org/2000/svg" width="${String(960)}" height="${String(640)}"><rect width="100%" height="100%" fill="${TERMINAL_DEFAULT_BG}"/>${backgrounds.join("")}<g fill="${TERMINAL_DEFAULT_FG}" font-family="${TERMINAL_FONT_STACK}" font-size="${String(TERMINAL_FONT_SIZE)}">${texts.join("")}</g></svg>`;
38392
- }
38393
- /** Cut a run to the columns still left in the row, by code point not unit. */
38394
- function clipRun(run, remaining) {
38395
- const points = [...run.text];
38396
- return points.length <= remaining ? run.text : points.slice(0, remaining).join("");
38397
- }
38398
- async function renderTerminalJpeg(rows) {
38399
- return sharp(Buffer.from(renderTerminalSvg(rows))).jpeg({
38400
- quality: 82,
38401
- chromaSubsampling: "4:2:0"
38402
- }).toBuffer();
38403
- }
38404
- //#endregion
38405
- //#region src/terminal-camera-device.ts
38406
- var terminalCameraSchema = object({
38407
- instanceId: string().min(1).optional(),
38408
- nodeId: string().min(1),
38409
- profileId: string().min(1).default("monitor"),
38410
- profileLabel: string().min(1).default("BTM")
38411
- });
38412
- var relay = null;
38413
- function installTerminalCameraRelay(next) {
38414
- relay = next;
38415
- }
38416
- var TerminalCameraDevice = class extends BaseDevice {
38417
- features = [DeviceFeature.NativeSnapshot];
38418
- constructor(ctx) {
38419
- super(ctx, terminalCameraSchema, { type: ctx.deviceMeta.type });
38420
- this.ctx.registerNativeCap(streamCatalogCapability, { getCatalog: async ({ deviceId }) => {
38421
- if (deviceId !== this.id) return [];
38422
- return this.catalog();
38423
- } });
38424
- this.ctx.registerNativeCap(snapshotCapability, {
38425
- getSnapshot: async ({ deviceId }) => {
38426
- if (deviceId !== this.id) throw new Error(`TerminalCameraDevice: deviceId mismatch, expected ${String(this.id)}, got ${String(deviceId)}`);
38427
- const activeRelay = relay;
38428
- if (!activeRelay) throw new Error("terminal camera relay is unavailable");
38429
- return {
38430
- base64: (await activeRelay.snapshot(this.relayInstanceId(), this.config.get("nodeId"), this.config.get("profileId"))).toString("base64"),
38431
- contentType: "image/jpeg"
38432
- };
38433
- },
38434
- invalidateCache: async () => {}
38435
- });
38436
- this.markOnline(true);
38437
- }
38438
- async catalog() {
38439
- const activeRelay = relay;
38440
- if (!activeRelay) throw new Error("terminal camera relay is unavailable");
38441
- const nodeId = this.config.get("nodeId");
38442
- const profileId = this.config.get("profileId");
38443
- const instanceId = this.relayInstanceId();
38444
- return [{
38445
- camStreamId: profileId,
38446
- kind: "pull-http",
38447
- url: activeRelay.streamUrl(instanceId, nodeId, profileId),
38448
- codec: "h264",
38449
- resolution: {
38450
- width: 960,
38451
- height: 640
38452
- },
38453
- fps: 2,
38454
- label: this.config.get("profileLabel")
38455
- }];
38456
- }
38457
- setNodeOnline(online) {
38458
- this.markOnline(online);
38459
- if (!online) relay?.closeInstance(this.relayInstanceId());
38460
- }
38461
- async removeDevice() {
38462
- await relay?.closeInstance(this.relayInstanceId());
38463
- }
38464
- relayInstanceId() {
38465
- return this.config.get("instanceId") ?? `legacy:${this.stableId}`;
38466
- }
38467
- };
38468
- //#endregion
38469
38374
  //#region ../../node_modules/@xterm/addon-serialize/lib/addon-serialize.js
38470
38375
  var require_addon_serialize = /* @__PURE__ */ __commonJSMin(((exports, module) => {
38471
38376
  (function(e, t) {
@@ -43273,11 +43178,176 @@ var require_xterm_headless = /* @__PURE__ */ __commonJSMin(((exports) => {
43273
43178
  })();
43274
43179
  }));
43275
43180
  //#endregion
43276
- //#region src/xterm-screen.ts
43181
+ //#region src/terminal-cell-runs.ts
43277
43182
  var import_addon_serialize = require_addon_serialize();
43278
43183
  var import_xterm_headless = require_xterm_headless();
43184
+ var TERMINAL_DEFAULT_FG = "#d7dce2";
43185
+ var TERMINAL_DEFAULT_BG = "#0b0d10";
43186
+ /**
43187
+ * The 16 ANSI colours, in a dark-theme variant chosen to sit with the existing
43188
+ * frame (`#0b0d10` ground, `#d7dce2` text) rather than the raw xterm defaults,
43189
+ * whose pure `#0000ff` blue and `#008000` green are close to unreadable on a
43190
+ * near-black background at 13px. Index 7 IS the default foreground, so plain
43191
+ * `CSI 37m` text renders identically to unstyled text.
43192
+ */
43193
+ var TERMINAL_ANSI_PALETTE = [
43194
+ "#282c34",
43195
+ "#e06c75",
43196
+ "#98c379",
43197
+ "#e5c07b",
43198
+ "#61afef",
43199
+ "#c678dd",
43200
+ "#56b6c2",
43201
+ TERMINAL_DEFAULT_FG,
43202
+ "#5c6370",
43203
+ "#ef596f",
43204
+ "#89ca78",
43205
+ "#f0c674",
43206
+ "#6cb6ff",
43207
+ "#d55fde",
43208
+ "#2bbac5",
43209
+ "#ffffff"
43210
+ ];
43211
+ /** The six levels of the xterm 6×6×6 colour cube (indices 16-231). */
43212
+ var TERMINAL_CUBE_LEVELS = [
43213
+ 0,
43214
+ 95,
43215
+ 135,
43216
+ 175,
43217
+ 215,
43218
+ 255
43219
+ ];
43220
+ var TERMINAL_CUBE_FIRST = 16;
43221
+ var TERMINAL_GRAYSCALE_FIRST = 232;
43222
+ var TERMINAL_GRAYSCALE_BASE = 8;
43223
+ var TERMINAL_GRAYSCALE_STEP = 10;
43224
+ /** SGR 2 keeps the foreground legible; it must not become the background. */
43225
+ var TERMINAL_DIM_WEIGHT = .6;
43226
+ function channel(value) {
43227
+ return Math.max(0, Math.min(255, Math.round(value))).toString(16).padStart(2, "0");
43228
+ }
43229
+ function hex(red, green, blue) {
43230
+ return `#${channel(red)}${channel(green)}${channel(blue)}`;
43231
+ }
43232
+ function parseHex(color) {
43233
+ return [
43234
+ Number.parseInt(color.slice(1, 3), 16),
43235
+ Number.parseInt(color.slice(3, 5), 16),
43236
+ Number.parseInt(color.slice(5, 7), 16)
43237
+ ];
43238
+ }
43239
+ /** Resolve an xterm palette index (0-255) to a hex colour. */
43240
+ function terminalPaletteColor(index) {
43241
+ const ansi = TERMINAL_ANSI_PALETTE[index];
43242
+ if (ansi !== void 0) return ansi;
43243
+ if (index >= TERMINAL_GRAYSCALE_FIRST) {
43244
+ const level = TERMINAL_GRAYSCALE_BASE + (index - TERMINAL_GRAYSCALE_FIRST) * TERMINAL_GRAYSCALE_STEP;
43245
+ return hex(level, level, level);
43246
+ }
43247
+ if (index >= TERMINAL_CUBE_FIRST) {
43248
+ const offset = index - TERMINAL_CUBE_FIRST;
43249
+ return hex(TERMINAL_CUBE_LEVELS[Math.floor(offset / 36) % 6] ?? 0, TERMINAL_CUBE_LEVELS[Math.floor(offset / 6) % 6] ?? 0, TERMINAL_CUBE_LEVELS[offset % 6] ?? 0);
43250
+ }
43251
+ return TERMINAL_DEFAULT_FG;
43252
+ }
43253
+ /** Resolve a 0xRRGGBB truecolor value to a hex colour. */
43254
+ function terminalRgbColor(value) {
43255
+ return hex(value >> 16 & 255, value >> 8 & 255, value & 255);
43256
+ }
43257
+ function blend(color, toward, weight) {
43258
+ const [red, green, blue] = parseHex(color);
43259
+ const [targetRed, targetGreen, targetBlue] = parseHex(toward);
43260
+ return hex(red * weight + targetRed * (1 - weight), green * weight + targetGreen * (1 - weight), blue * weight + targetBlue * (1 - weight));
43261
+ }
43262
+ function resolveForeground(cell) {
43263
+ if (cell.isFgRGB()) return terminalRgbColor(cell.getFgColor());
43264
+ if (cell.isFgPalette()) return terminalPaletteColor(cell.getFgColor());
43265
+ return TERMINAL_DEFAULT_FG;
43266
+ }
43267
+ function resolveBackground(cell) {
43268
+ if (cell.isBgRGB()) return terminalRgbColor(cell.getBgColor());
43269
+ if (cell.isBgPalette()) return terminalPaletteColor(cell.getBgColor());
43270
+ return TERMINAL_DEFAULT_BG;
43271
+ }
43272
+ /**
43273
+ * Resolve one cell's attributes into concrete colours.
43274
+ *
43275
+ * Inverse is applied by SWAPPING the already-resolved pair, so inverse over two
43276
+ * defaults is still a visible swap rather than a no-op — that is how a selected
43277
+ * or highlighted row in Glances reads. Invisible is then conceal-by-equality
43278
+ * (foreground painted in its own background): the cell keeps its columns, which
43279
+ * a dropped cell would not, and dropping it would shift the whole rest of the
43280
+ * row left.
43281
+ */
43282
+ function resolveCellStyle(cell) {
43283
+ const inverse = cell.isInverse() !== 0;
43284
+ const plainFg = resolveForeground(cell);
43285
+ const plainBg = resolveBackground(cell);
43286
+ const background = inverse ? plainFg : plainBg;
43287
+ let foreground = inverse ? plainBg : plainFg;
43288
+ if (cell.isDim() !== 0) foreground = blend(foreground, background, TERMINAL_DIM_WEIGHT);
43289
+ if (cell.isInvisible() !== 0) foreground = background;
43290
+ return {
43291
+ fg: foreground === "#d7dce2" ? null : foreground,
43292
+ bg: background === "#0b0d10" ? null : background,
43293
+ bold: cell.isBold() !== 0
43294
+ };
43295
+ }
43296
+ function sameStyle(left, right) {
43297
+ return left.fg === right.fg && left.bg === right.bg && left.bold === right.bold;
43298
+ }
43299
+ /**
43300
+ * Merge adjacent same-style cells into runs, then drop the trailing run of
43301
+ * default-styled whitespace so a row costs what it draws — the same trim
43302
+ * `translateToString(true)` performs. Whitespace carrying a BACKGROUND is kept:
43303
+ * a green bar of spaces out to the right margin is a pixel Glances drew.
43304
+ */
43305
+ function buildCellRuns(cells) {
43306
+ const runs = [];
43307
+ let text = "";
43308
+ let style = null;
43309
+ for (const cell of cells) {
43310
+ if (style !== null && sameStyle(style, cell.style)) {
43311
+ text += cell.text;
43312
+ continue;
43313
+ }
43314
+ if (style !== null) runs.push({
43315
+ text,
43316
+ ...style
43317
+ });
43318
+ text = cell.text;
43319
+ style = cell.style;
43320
+ }
43321
+ if (style !== null) runs.push({
43322
+ text,
43323
+ ...style
43324
+ });
43325
+ while (runs.length > 0) {
43326
+ const last = runs[runs.length - 1];
43327
+ if (last === void 0 || last.bg !== null) break;
43328
+ const trimmed = last.text.replace(/\s+$/u, "");
43329
+ if (trimmed === last.text) break;
43330
+ if (trimmed === "") {
43331
+ runs.pop();
43332
+ continue;
43333
+ }
43334
+ runs[runs.length - 1] = {
43335
+ ...last,
43336
+ text: trimmed
43337
+ };
43338
+ break;
43339
+ }
43340
+ return runs;
43341
+ }
43342
+ //#endregion
43343
+ //#region src/xterm-screen.ts
43344
+ /**
43345
+ * Real {@link ScreenBuffer} backed by `@xterm/headless` — the DOM-less xterm
43346
+ * build — plus the serialize addon, which turns the current buffer into a
43347
+ * self-contained repaint escape sequence for reconnecting clients.
43348
+ */
43279
43349
  var SCROLLBACK_LINES = 2e3;
43280
- function createXtermScreen$1(cols, rows) {
43350
+ function createXtermScreen(cols, rows) {
43281
43351
  const term = new import_xterm_headless.Terminal({
43282
43352
  cols,
43283
43353
  rows,
@@ -43342,6 +43412,196 @@ function createXtermScreen$1(cols, rows) {
43342
43412
  };
43343
43413
  }
43344
43414
  //#endregion
43415
+ //#region src/terminal-camera-declarations.ts
43416
+ /**
43417
+ * Feed DeclaredDevices every live declaration plus one deterministic orphan
43418
+ * batch. The generic sweep intentionally refuses an over-limit set; selecting
43419
+ * a batch here drains large historical Terminal orphan sets across convergence
43420
+ * passes without weakening that global safety guard.
43421
+ */
43422
+ function terminalCameraReconciliationIndex(rows, integrationId, declarations, managedStableIds = /* @__PURE__ */ new Set()) {
43423
+ if (!integrationId) return [];
43424
+ const declared = new Set(declarations.map((camera) => camera.stableId));
43425
+ const owned = rows.filter((row) => row.integrationId === integrationId && (declared.has(row.stableId) || managedStableIds.has(row.stableId) || row.stableId.startsWith("terminal-camera-instance-")));
43426
+ return [...owned.filter((row) => declared.has(row.stableId)), ...owned.filter((row) => !declared.has(row.stableId)).sort((left, right) => left.id - right.id).slice(0, 32)];
43427
+ }
43428
+ /** Explicit persisted instances, never the node × profile template matrix. */
43429
+ function buildTerminalInstanceCameraDeclarations(instances) {
43430
+ return instances.filter((instance) => instance.enabled).map((instance) => ({
43431
+ stableId: instance.cameraStableId,
43432
+ name: instance.name,
43433
+ config: {
43434
+ instanceId: instance.id,
43435
+ nodeId: instance.nodeId,
43436
+ profileId: instance.profileId,
43437
+ profileLabel: instance.profileLabel
43438
+ }
43439
+ }));
43440
+ }
43441
+ /**
43442
+ * `DeviceConfig` materializes schema defaults in memory, so comparing
43443
+ * `config.get()` cannot detect a legacy `{ nodeId }` row. Reconciliation must
43444
+ * inspect the raw persisted blob to make the profile migration durable.
43445
+ */
43446
+ function needsTerminalCameraConfigMigration(persistedConfig, declaration) {
43447
+ return persistedConfig.instanceId !== declaration.config.instanceId || persistedConfig.nodeId !== declaration.config.nodeId || persistedConfig.profileId !== declaration.config.profileId || persistedConfig.profileLabel !== declaration.config.profileLabel;
43448
+ }
43449
+ /**
43450
+ * Monospace families to try, in order — NOT one family and a generic.
43451
+ *
43452
+ * A terminal screen is mostly box-drawing and block characters, and a font
43453
+ * without them renders the frame as noise rather than as missing detail.
43454
+ * `DejaVu Sans Mono` covers them and ships in the hub image; macOS has neither
43455
+ * DejaVu nor fontconfig, so the Mac agent fell through to a generic whose
43456
+ * coverage is not, and its Glances camera came out unreadable while the hub's
43457
+ * was pixel-perfect (measured 2026-08-11, same Glances, two nodes).
43458
+ *
43459
+ * `Menlo` is the fix rather than a guess: it is macOS's default terminal face,
43460
+ * present on every install, and derived from DejaVu Sans Mono — the same glyph
43461
+ * coverage by ancestry. Monaco and Courier New follow as older fallbacks; the
43462
+ * generic stays last so a host with none of them still draws something.
43463
+ */
43464
+ var TERMINAL_FONT_STACK = "DejaVu Sans Mono,Menlo,Monaco,Courier New,monospace";
43465
+ var TERMINAL_FONT_SIZE = 13;
43466
+ var TERMINAL_TEXT_MARGIN_X = 8;
43467
+ var TERMINAL_ROW_HEIGHT = 15;
43468
+ var TERMINAL_BASELINE_Y = 18;
43469
+ /**
43470
+ * Distance from a row's baseline up to the top of its cell box. Chosen so
43471
+ * consecutive rows tile exactly: row N's box runs from `baseline - this` for
43472
+ * `TERMINAL_ROW_HEIGHT`, and row N+1's box starts where it ends. A background
43473
+ * bar that stopped short would draw as stripes across a `CSI 42m` panel.
43474
+ */
43475
+ var TERMINAL_CELL_ASCENT = 11.5;
43476
+ var TERMINAL_CELL_WIDTH = TERMINAL_FONT_SIZE * (1233 / 2048);
43477
+ /** Two decimals is under a tenth of a pixel and keeps the SVG small. */
43478
+ function coordinate(value) {
43479
+ return String(Number(value.toFixed(2)));
43480
+ }
43481
+ function escapeXml(value) {
43482
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&apos;");
43483
+ }
43484
+ /**
43485
+ * Render already-interpreted terminal rows into a compact MJPEG frame.
43486
+ *
43487
+ * `xml:space="preserve"` is load-bearing, not tidiness. SVG `<text>` collapses
43488
+ * runs of whitespace by default, and a terminal's entire column alignment IS
43489
+ * runs of whitespace — Glances pads every field with spaces. Without it the
43490
+ * frame drew each line at roughly half its true width, crammed into the
43491
+ * top-left of a mostly-black image, while the SAME session over `attach`
43492
+ * looked perfect — which is exactly how the operator reported it. Measured in
43493
+ * the hub's own rasterizer 2026-08-11: `AAA<10 spaces>BBB<3>CCC` drew 92 px
43494
+ * collapsed against 178 px preserved.
43495
+ *
43496
+ * Every run is anchored at its OWN column (`x = margin + startCol * cellW`),
43497
+ * never appended to the one before it, so the background rects and the glyphs
43498
+ * are placed off the same grid and cannot drift apart. `textLength` is emitted
43499
+ * with it because it is the correct declaration and renderers that honour it
43500
+ * get an exact grid — but it is not what makes this work: librsvg, which sharp
43501
+ * uses, ignores it outright (measured 2026-08-12, a 120-glyph run pinned to
43502
+ * 600 px still drew its natural 937 px). The anchoring is the guarantee.
43503
+ */
43504
+ function renderTerminalSvg(rows) {
43505
+ const backgrounds = [];
43506
+ const texts = [];
43507
+ rows.slice(0, 40).forEach((row, index) => {
43508
+ const baseline = TERMINAL_BASELINE_Y + index * TERMINAL_ROW_HEIGHT;
43509
+ const top = baseline - TERMINAL_CELL_ASCENT;
43510
+ let column = 0;
43511
+ for (const run of row) {
43512
+ if (column >= 120) break;
43513
+ const clipped = clipRun(run, 120 - column);
43514
+ const columns = [...clipped].length;
43515
+ if (columns === 0) continue;
43516
+ const x = TERMINAL_TEXT_MARGIN_X + column * TERMINAL_CELL_WIDTH;
43517
+ const width = columns * TERMINAL_CELL_WIDTH;
43518
+ if (run.bg !== null) backgrounds.push(`<rect x="${coordinate(x)}" y="${coordinate(top)}" width="${coordinate(width)}" height="${String(TERMINAL_ROW_HEIGHT)}" fill="${run.bg}"/>`);
43519
+ if (clipped.trim() !== "") {
43520
+ const fill = run.fg === null ? "" : ` fill="${run.fg}"`;
43521
+ const weight = run.bold ? " font-weight=\"bold\"" : "";
43522
+ texts.push(`<text xml:space="preserve" x="${coordinate(x)}" y="${coordinate(baseline)}" textLength="${coordinate(width)}" lengthAdjust="spacingAndGlyphs"${fill}${weight}>${escapeXml(clipped)}</text>`);
43523
+ }
43524
+ column += columns;
43525
+ }
43526
+ });
43527
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${String(960)}" height="${String(640)}"><rect width="100%" height="100%" fill="${TERMINAL_DEFAULT_BG}"/>${backgrounds.join("")}<g fill="${TERMINAL_DEFAULT_FG}" font-family="${TERMINAL_FONT_STACK}" font-size="${String(TERMINAL_FONT_SIZE)}">${texts.join("")}</g></svg>`;
43528
+ }
43529
+ /** Cut a run to the columns still left in the row, by code point not unit. */
43530
+ function clipRun(run, remaining) {
43531
+ const points = [...run.text];
43532
+ return points.length <= remaining ? run.text : points.slice(0, remaining).join("");
43533
+ }
43534
+ async function renderTerminalJpeg(rows) {
43535
+ return sharp(Buffer.from(renderTerminalSvg(rows))).jpeg({
43536
+ quality: 82,
43537
+ chromaSubsampling: "4:2:0"
43538
+ }).toBuffer();
43539
+ }
43540
+ //#endregion
43541
+ //#region src/terminal-camera-device.ts
43542
+ var terminalCameraSchema = object({
43543
+ instanceId: string().min(1).optional(),
43544
+ nodeId: string().min(1),
43545
+ profileId: string().min(1).default("monitor"),
43546
+ profileLabel: string().min(1).default("BTM")
43547
+ });
43548
+ var relay = null;
43549
+ function installTerminalCameraRelay(next) {
43550
+ relay = next;
43551
+ }
43552
+ var TerminalCameraDevice = class extends BaseDevice {
43553
+ features = [DeviceFeature.NativeSnapshot];
43554
+ constructor(ctx) {
43555
+ super(ctx, terminalCameraSchema, { type: ctx.deviceMeta.type });
43556
+ this.ctx.registerNativeCap(streamCatalogCapability, { getCatalog: async ({ deviceId }) => {
43557
+ if (deviceId !== this.id) return [];
43558
+ return this.catalog();
43559
+ } });
43560
+ this.ctx.registerNativeCap(snapshotCapability, {
43561
+ getSnapshot: async ({ deviceId }) => {
43562
+ if (deviceId !== this.id) throw new Error(`TerminalCameraDevice: deviceId mismatch, expected ${String(this.id)}, got ${String(deviceId)}`);
43563
+ const activeRelay = relay;
43564
+ if (!activeRelay) throw new Error("terminal camera relay is unavailable");
43565
+ return {
43566
+ base64: (await activeRelay.snapshot(this.relayInstanceId(), this.config.get("nodeId"), this.config.get("profileId"))).toString("base64"),
43567
+ contentType: "image/jpeg"
43568
+ };
43569
+ },
43570
+ invalidateCache: async () => {}
43571
+ });
43572
+ this.markOnline(true);
43573
+ }
43574
+ async catalog() {
43575
+ const activeRelay = relay;
43576
+ if (!activeRelay) throw new Error("terminal camera relay is unavailable");
43577
+ const nodeId = this.config.get("nodeId");
43578
+ const profileId = this.config.get("profileId");
43579
+ const instanceId = this.relayInstanceId();
43580
+ return [{
43581
+ camStreamId: profileId,
43582
+ kind: "pull-http",
43583
+ url: activeRelay.streamUrl(instanceId, nodeId, profileId),
43584
+ codec: "h264",
43585
+ resolution: {
43586
+ width: 960,
43587
+ height: 640
43588
+ },
43589
+ fps: 2,
43590
+ label: this.config.get("profileLabel")
43591
+ }];
43592
+ }
43593
+ setNodeOnline(online) {
43594
+ this.markOnline(online);
43595
+ if (!online) relay?.closeInstance(this.relayInstanceId());
43596
+ }
43597
+ async removeDevice() {
43598
+ await relay?.closeInstance(this.relayInstanceId());
43599
+ }
43600
+ relayInstanceId() {
43601
+ return this.config.get("instanceId") ?? `legacy:${this.stableId}`;
43602
+ }
43603
+ };
43604
+ //#endregion
43345
43605
  //#region src/terminal-camera-relay.ts
43346
43606
  var MJPEG_BOUNDARY = "camstack-terminal-frame";
43347
43607
  var SESSION_IDLE_MS = 3e4;
@@ -43421,7 +43681,7 @@ var TerminalCameraRelay = class {
43421
43681
  instanceId,
43422
43682
  nodeId,
43423
43683
  profileId,
43424
- screen: createXtermScreen$1(120, 40),
43684
+ screen: createXtermScreen(120, 40),
43425
43685
  sessionId: null,
43426
43686
  cursor: 0,
43427
43687
  clients: 0,
@@ -43478,7 +43738,7 @@ var TerminalCameraRelay = class {
43478
43738
  applyBatch(state, batch) {
43479
43739
  if (batch.reset) {
43480
43740
  state.screen.dispose();
43481
- state.screen = createXtermScreen$1(120, 40);
43741
+ state.screen = createXtermScreen(120, 40);
43482
43742
  if (batch.snapshot) state.screen.write(batch.snapshot);
43483
43743
  }
43484
43744
  let exited = false;
@@ -45236,4 +45496,4 @@ var TerminalAddon = class extends BaseAddon {
45236
45496
  }
45237
45497
  };
45238
45498
  //#endregion
45239
- export { TerminalAddon, createXtermScreen$1 as a, buildCellRuns as c, terminalRgbColor as d, createNodePtySpawner as f, createTerminalDataPlaneHandler as i, resolveCellStyle as l, buildProfiles as n, TERMINAL_DEFAULT_BG as o, warmNodePty as p, findProfile as r, TERMINAL_DEFAULT_FG as s, TerminalSessionManager as t, terminalPaletteColor as u };
45499
+ export { TerminalAddon, createXtermScreen as a, buildCellRuns as c, terminalRgbColor as d, createNodePtySpawner as f, createTerminalDataPlaneHandler as i, resolveCellStyle as l, buildProfiles as n, TERMINAL_DEFAULT_BG as o, warmNodePty as p, findProfile as r, TERMINAL_DEFAULT_FG as s, TerminalSessionManager as t, terminalPaletteColor as u };