@trillboards/edge-sdk 0.2.3 → 0.2.6

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 (200) hide show
  1. package/README.md +4 -3
  2. package/deploy/docker/Dockerfile.cloud-sense +117 -0
  3. package/deploy/docker/Dockerfile.cpu +30 -21
  4. package/deploy/docker/Dockerfile.cuda +30 -21
  5. package/deploy/docker/Dockerfile.openvino +30 -20
  6. package/deploy/docker/README.md +9 -4
  7. package/deploy/docker/connector-entrypoint.sh +122 -0
  8. package/deploy/helm/README.md +1 -1
  9. package/deploy/k8s/daemonset.yaml +1 -1
  10. package/deploy/macos/README.md +161 -0
  11. package/deploy/macos/TrillboardsSetupApp.m +603 -0
  12. package/deploy/macos/build-macos-pkg.sh +588 -0
  13. package/deploy/macos/com.trillboards.edge.plist +62 -0
  14. package/deploy/macos/com.trillboards.litertlm.plist +45 -0
  15. package/deploy/macos/pair.command +519 -0
  16. package/deploy/macos/scripts/ensure-litertlm.sh +62 -0
  17. package/deploy/macos/scripts/postinstall +153 -0
  18. package/deploy/macos/scripts/setup-litertlm.sh +272 -0
  19. package/deploy/macos/test-install-pair.sh +383 -0
  20. package/deploy/macos/test-setup-litertlm-reinstall.sh +109 -0
  21. package/deploy/systemd/install.sh +25 -15
  22. package/deploy/systemd/trillboards-edge.service +4 -1
  23. package/deploy/windows/README.md +70 -0
  24. package/deploy/windows/build-windows.ps1 +225 -0
  25. package/deploy/windows/bundle-windows-installer.ps1 +153 -0
  26. package/deploy/windows/check-feeds.js +26 -0
  27. package/deploy/windows/e2e/fixtures/person.jpg +0 -0
  28. package/deploy/windows/e2e/test-local-sense.ps1 +185 -0
  29. package/deploy/windows/install-update.ps1 +101 -0
  30. package/deploy/windows/installer.nsi +138 -0
  31. package/deploy/windows/register-litertlm-task.ps1 +42 -0
  32. package/deploy/windows/register-task.ps1 +44 -0
  33. package/deploy/windows/run-connector.ps1 +69 -0
  34. package/deploy/windows/setup-litertlm.ps1 +361 -0
  35. package/deploy/windows/stop-existing.ps1 +72 -0
  36. package/deploy/windows/trillboards-edge-task.vbs +25 -0
  37. package/deploy/windows/trillboards-edge.cmd +33 -0
  38. package/deploy/windows/trillboards-pair.cmd +276 -0
  39. package/deploy/windows/trillboards-setup.ps1 +456 -0
  40. package/deploy/windows/trillboards-setup.vbs +13 -0
  41. package/dist/AudienceAnalyzeClient.d.ts +136 -0
  42. package/dist/AudienceAnalyzeClient.d.ts.map +1 -0
  43. package/dist/AudienceAnalyzeClient.js +246 -0
  44. package/dist/AudienceAnalyzeClient.js.map +1 -0
  45. package/dist/ChannelProber.d.ts +150 -0
  46. package/dist/ChannelProber.d.ts.map +1 -0
  47. package/dist/ChannelProber.js +887 -0
  48. package/dist/ChannelProber.js.map +1 -0
  49. package/dist/CloudEscalationPolicy.d.ts +143 -0
  50. package/dist/CloudEscalationPolicy.d.ts.map +1 -0
  51. package/dist/CloudEscalationPolicy.js +137 -0
  52. package/dist/CloudEscalationPolicy.js.map +1 -0
  53. package/dist/CloudSensingManager.d.ts +533 -0
  54. package/dist/CloudSensingManager.d.ts.map +1 -0
  55. package/dist/CloudSensingManager.js +1254 -0
  56. package/dist/CloudSensingManager.js.map +1 -0
  57. package/dist/DiscoveryReportClient.d.ts +66 -0
  58. package/dist/DiscoveryReportClient.d.ts.map +1 -0
  59. package/dist/DiscoveryReportClient.js +85 -0
  60. package/dist/DiscoveryReportClient.js.map +1 -0
  61. package/dist/EdgeAgent.d.ts +12 -2
  62. package/dist/EdgeAgent.d.ts.map +1 -1
  63. package/dist/EdgeAgent.js +122 -65
  64. package/dist/EdgeAgent.js.map +1 -1
  65. package/dist/LiteRtVlmProvider.d.ts +193 -0
  66. package/dist/LiteRtVlmProvider.d.ts.map +1 -0
  67. package/dist/LiteRtVlmProvider.js +436 -0
  68. package/dist/LiteRtVlmProvider.js.map +1 -0
  69. package/dist/MosaicSource.d.ts +108 -0
  70. package/dist/MosaicSource.d.ts.map +1 -0
  71. package/dist/MosaicSource.js +274 -0
  72. package/dist/MosaicSource.js.map +1 -0
  73. package/dist/OnvifClient.d.ts +83 -0
  74. package/dist/OnvifClient.d.ts.map +1 -0
  75. package/dist/OnvifClient.js +209 -0
  76. package/dist/OnvifClient.js.map +1 -0
  77. package/dist/PairClient.d.ts +106 -0
  78. package/dist/PairClient.d.ts.map +1 -0
  79. package/dist/PairClient.js +186 -0
  80. package/dist/PairClient.js.map +1 -0
  81. package/dist/RecorderAddressRecovery.d.ts +53 -0
  82. package/dist/RecorderAddressRecovery.d.ts.map +1 -0
  83. package/dist/RecorderAddressRecovery.js +359 -0
  84. package/dist/RecorderAddressRecovery.js.map +1 -0
  85. package/dist/SceneChangeDetector.d.ts +291 -0
  86. package/dist/SceneChangeDetector.d.ts.map +1 -0
  87. package/dist/SceneChangeDetector.js +682 -0
  88. package/dist/SceneChangeDetector.js.map +1 -0
  89. package/dist/SensingProfileClient.d.ts +59 -0
  90. package/dist/SensingProfileClient.d.ts.map +1 -0
  91. package/dist/SensingProfileClient.js +78 -0
  92. package/dist/SensingProfileClient.js.map +1 -0
  93. package/dist/SetupSessionClient.d.ts +114 -0
  94. package/dist/SetupSessionClient.d.ts.map +1 -0
  95. package/dist/SetupSessionClient.js +95 -0
  96. package/dist/SetupSessionClient.js.map +1 -0
  97. package/dist/VlmAdmissionGate.d.ts +126 -0
  98. package/dist/VlmAdmissionGate.d.ts.map +1 -0
  99. package/dist/VlmAdmissionGate.js +166 -0
  100. package/dist/VlmAdmissionGate.js.map +1 -0
  101. package/dist/autoSense.d.ts +216 -0
  102. package/dist/autoSense.d.ts.map +1 -0
  103. package/dist/autoSense.js +521 -0
  104. package/dist/autoSense.js.map +1 -0
  105. package/dist/cli.d.ts +27 -1
  106. package/dist/cli.d.ts.map +1 -1
  107. package/dist/cli.js +511 -21
  108. package/dist/cli.js.map +1 -1
  109. package/dist/combinedSense.d.ts +128 -0
  110. package/dist/combinedSense.d.ts.map +1 -0
  111. package/dist/combinedSense.js +261 -0
  112. package/dist/combinedSense.js.map +1 -0
  113. package/dist/config.d.ts +351 -0
  114. package/dist/config.d.ts.map +1 -1
  115. package/dist/config.js +99 -2
  116. package/dist/config.js.map +1 -1
  117. package/dist/connectorCadence.d.ts +135 -0
  118. package/dist/connectorCadence.d.ts.map +1 -0
  119. package/dist/connectorCadence.js +151 -0
  120. package/dist/connectorCadence.js.map +1 -0
  121. package/dist/dataDir.d.ts +33 -0
  122. package/dist/dataDir.d.ts.map +1 -0
  123. package/dist/dataDir.js +76 -0
  124. package/dist/dataDir.js.map +1 -0
  125. package/dist/deployedCadence.d.ts +167 -0
  126. package/dist/deployedCadence.d.ts.map +1 -0
  127. package/dist/deployedCadence.js +209 -0
  128. package/dist/deployedCadence.js.map +1 -0
  129. package/dist/deployedCadenceSource.d.ts +48 -0
  130. package/dist/deployedCadenceSource.d.ts.map +1 -0
  131. package/dist/deployedCadenceSource.js +74 -0
  132. package/dist/deployedCadenceSource.js.map +1 -0
  133. package/dist/diagnose.d.ts +1 -1
  134. package/dist/diagnose.d.ts.map +1 -1
  135. package/dist/diagnose.js +13 -12
  136. package/dist/diagnose.js.map +1 -1
  137. package/dist/discovery.d.ts +131 -0
  138. package/dist/discovery.d.ts.map +1 -0
  139. package/dist/discovery.js +499 -0
  140. package/dist/discovery.js.map +1 -0
  141. package/dist/gemmaModelOta.d.ts +118 -0
  142. package/dist/gemmaModelOta.d.ts.map +1 -0
  143. package/dist/gemmaModelOta.js +291 -0
  144. package/dist/gemmaModelOta.js.map +1 -0
  145. package/dist/index.d.ts +16 -1
  146. package/dist/index.d.ts.map +1 -1
  147. package/dist/index.js +40 -1
  148. package/dist/index.js.map +1 -1
  149. package/dist/init.d.ts.map +1 -1
  150. package/dist/init.js +41 -18
  151. package/dist/init.js.map +1 -1
  152. package/dist/keepAwake.d.ts +33 -0
  153. package/dist/keepAwake.d.ts.map +1 -0
  154. package/dist/keepAwake.js +54 -0
  155. package/dist/keepAwake.js.map +1 -0
  156. package/dist/legacyRecorderProfile.d.ts +20 -0
  157. package/dist/legacyRecorderProfile.d.ts.map +1 -0
  158. package/dist/legacyRecorderProfile.js +27 -0
  159. package/dist/legacyRecorderProfile.js.map +1 -0
  160. package/dist/localInferenceSchema.d.ts +132 -0
  161. package/dist/localInferenceSchema.d.ts.map +1 -0
  162. package/dist/localInferenceSchema.js +274 -0
  163. package/dist/localInferenceSchema.js.map +1 -0
  164. package/dist/localSenseE2eHarness.d.ts +101 -0
  165. package/dist/localSenseE2eHarness.d.ts.map +1 -0
  166. package/dist/localSenseE2eHarness.js +279 -0
  167. package/dist/localSenseE2eHarness.js.map +1 -0
  168. package/dist/localSensing.d.ts +268 -0
  169. package/dist/localSensing.d.ts.map +1 -0
  170. package/dist/localSensing.js +492 -0
  171. package/dist/localSensing.js.map +1 -0
  172. package/dist/modelIntegrity.d.ts +59 -0
  173. package/dist/modelIntegrity.d.ts.map +1 -0
  174. package/dist/modelIntegrity.js +165 -0
  175. package/dist/modelIntegrity.js.map +1 -0
  176. package/dist/pairVerify.d.ts +100 -0
  177. package/dist/pairVerify.d.ts.map +1 -0
  178. package/dist/pairVerify.js +296 -0
  179. package/dist/pairVerify.js.map +1 -0
  180. package/dist/qrScanner.d.ts +33 -0
  181. package/dist/qrScanner.d.ts.map +1 -0
  182. package/dist/qrScanner.js +103 -0
  183. package/dist/qrScanner.js.map +1 -0
  184. package/dist/runtimePaths.d.ts +10 -0
  185. package/dist/runtimePaths.d.ts.map +1 -0
  186. package/dist/runtimePaths.js +79 -0
  187. package/dist/runtimePaths.js.map +1 -0
  188. package/dist/sensingRunMode.d.ts +68 -0
  189. package/dist/sensingRunMode.d.ts.map +1 -0
  190. package/dist/sensingRunMode.js +346 -0
  191. package/dist/sensingRunMode.js.map +1 -0
  192. package/dist/setupMode.d.ts +98 -0
  193. package/dist/setupMode.d.ts.map +1 -0
  194. package/dist/setupMode.js +828 -0
  195. package/dist/setupMode.js.map +1 -0
  196. package/dist/setupProgress.d.ts +31 -0
  197. package/dist/setupProgress.d.ts.map +1 -0
  198. package/dist/setupProgress.js +173 -0
  199. package/dist/setupProgress.js.map +1 -0
  200. package/package.json +13 -4
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+ /**
3
+ * Is this file actually an ONNX model?
4
+ *
5
+ * THE FAILURE THIS PREVENTS
6
+ * -------------------------------------------------------------------------
7
+ * `models.trillboards.com` was never provisioned. The `trillboards.com` zone
8
+ * has no `models` record, so the name is caught by the `*.trillboards.com`
9
+ * wildcard A-alias and answered by the application ALB, which returns the
10
+ * marketing SPA's `index.html` — HTTP **200**, `content-type: text/html`,
11
+ * 2490 bytes — for every path under it.
12
+ *
13
+ * Every consumer of that URL treated 200 as success and, at most, checked a
14
+ * SIZE FLOOR:
15
+ *
16
+ * deploy/docker/Dockerfile.{cpu,cuda,openvino} `if (size < 1000) reject`
17
+ * scripts/download-models.sh `curl -f` (does not fail on 200)
18
+ * src/cli.ts / src/init.ts statusCode !== 200 only
19
+ * src/diagnose.ts `size === 0` only
20
+ *
21
+ * 2490 > 1000, so every one of them accepted a web page as a model, wrote it
22
+ * to `blazeface.onnx`, and reported success — `download-models` printed
23
+ * `[OK] blazeface.onnx (0.0 MB)` and `diagnose` reported `ONNX Models: pass`.
24
+ * At runtime onnxruntime then throws `Protobuf parsing failed`, the camera
25
+ * subsystem reports ERROR, and the operator has been told everything is fine.
26
+ *
27
+ * A size floor cannot distinguish a model from a web page and never will. The
28
+ * check has to be on the CONTENT.
29
+ *
30
+ * WHAT IS CHECKED
31
+ * -------------------------------------------------------------------------
32
+ * An ONNX file is a serialized `ModelProto`. Protobuf encodes field 1
33
+ * (`ir_version`, a varint) as tag byte `0x08`, so every real ONNX model begins
34
+ * with `0x08`. HTML begins with `<` (0x3C). The two are decisive and cheap.
35
+ *
36
+ * Deliberately reports WHAT it found rather than a bare "invalid": an operator
37
+ * who is told "this is an HTML page, the model host is serving a web page" can
38
+ * act, where "invalid model" sends them looking at their disk.
39
+ */
40
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
41
+ if (k2 === undefined) k2 = k;
42
+ var desc = Object.getOwnPropertyDescriptor(m, k);
43
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
44
+ desc = { enumerable: true, get: function() { return m[k]; } };
45
+ }
46
+ Object.defineProperty(o, k2, desc);
47
+ }) : (function(o, m, k, k2) {
48
+ if (k2 === undefined) k2 = k;
49
+ o[k2] = m[k];
50
+ }));
51
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
52
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
53
+ }) : function(o, v) {
54
+ o["default"] = v;
55
+ });
56
+ var __importStar = (this && this.__importStar) || (function () {
57
+ var ownKeys = function(o) {
58
+ ownKeys = Object.getOwnPropertyNames || function (o) {
59
+ var ar = [];
60
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
61
+ return ar;
62
+ };
63
+ return ownKeys(o);
64
+ };
65
+ return function (mod) {
66
+ if (mod && mod.__esModule) return mod;
67
+ var result = {};
68
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
69
+ __setModuleDefault(result, mod);
70
+ return result;
71
+ };
72
+ })();
73
+ Object.defineProperty(exports, "__esModule", { value: true });
74
+ exports.classifyModelBytes = classifyModelBytes;
75
+ exports.inspectModelFile = inspectModelFile;
76
+ exports.assertOnnxModel = assertOnnxModel;
77
+ const fs = __importStar(require("fs"));
78
+ /** Protobuf tag for `ModelProto.ir_version` (field 1, varint) — every ONNX file starts here. */
79
+ const ONNX_FIRST_BYTE = 0x08;
80
+ /**
81
+ * Smallest plausible ONNX model. Not a substitute for the magic-byte check —
82
+ * it only catches a truncated write that happens to start correctly.
83
+ */
84
+ const MIN_MODEL_BYTES = 1024;
85
+ /** Classify a model file's leading bytes. Pure — takes the buffer, touches no disk. */
86
+ function classifyModelBytes(buf, totalBytes) {
87
+ if (totalBytes === 0) {
88
+ return { valid: false, detected: 'empty', bytes: 0, reason: 'file is empty' };
89
+ }
90
+ // Check HTML BEFORE the size floor: an HTML error page is the failure we are
91
+ // actually here for, and saying "too small" about a web page sends the reader
92
+ // to the wrong place.
93
+ const head = buf.subarray(0, Math.min(buf.length, 512)).toString('utf8').trimStart().toLowerCase();
94
+ if (head.startsWith('<!doctype') || head.startsWith('<html') || head.startsWith('<?xml')) {
95
+ return {
96
+ valid: false,
97
+ detected: 'html',
98
+ bytes: totalBytes,
99
+ reason: `served an HTML page (${totalBytes} bytes), not a model — the model host is ` +
100
+ 'answering with a web page. Nothing was downloaded.',
101
+ };
102
+ }
103
+ if (buf.length > 0 && buf[0] !== ONNX_FIRST_BYTE) {
104
+ return {
105
+ valid: false,
106
+ detected: 'not-onnx',
107
+ bytes: totalBytes,
108
+ reason: `does not begin with the ONNX ModelProto tag 0x${ONNX_FIRST_BYTE.toString(16)} ` +
109
+ `(found 0x${buf[0].toString(16).padStart(2, '0')}) — this is not an ONNX model`,
110
+ };
111
+ }
112
+ if (totalBytes < MIN_MODEL_BYTES) {
113
+ return {
114
+ valid: false,
115
+ detected: 'truncated',
116
+ bytes: totalBytes,
117
+ reason: `only ${totalBytes} bytes — truncated or incomplete`,
118
+ };
119
+ }
120
+ return { valid: true, bytes: totalBytes };
121
+ }
122
+ /** Classify a model file on disk. Returns `{valid:false}` rather than throwing for a missing file. */
123
+ function inspectModelFile(filePath) {
124
+ let fd;
125
+ try {
126
+ const size = fs.statSync(filePath).size;
127
+ const buf = Buffer.alloc(Math.min(size, 512));
128
+ if (buf.length > 0) {
129
+ fd = fs.openSync(filePath, 'r');
130
+ fs.readSync(fd, buf, 0, buf.length, 0);
131
+ }
132
+ return classifyModelBytes(buf, size);
133
+ }
134
+ catch (err) {
135
+ return {
136
+ valid: false,
137
+ reason: `unreadable (${err instanceof Error ? err.message : String(err)})`,
138
+ };
139
+ }
140
+ finally {
141
+ if (fd !== undefined)
142
+ fs.closeSync(fd);
143
+ }
144
+ }
145
+ /**
146
+ * Throw unless `filePath` is a real ONNX model, DELETING it if it is not.
147
+ *
148
+ * Deleting is the point. A rejected download that stays on disk is worse than
149
+ * no download at all: the next `download-models` run sees the file, prints
150
+ * `[SKIP] (already exists)`, and the bad bytes become permanent.
151
+ */
152
+ function assertOnnxModel(filePath, label = filePath) {
153
+ const result = inspectModelFile(filePath);
154
+ if (result.valid)
155
+ return;
156
+ try {
157
+ if (fs.existsSync(filePath))
158
+ fs.unlinkSync(filePath);
159
+ }
160
+ catch {
161
+ /* best effort — the throw below is what matters */
162
+ }
163
+ throw new Error(`${label}: ${result.reason ?? 'not a valid ONNX model'}`);
164
+ }
165
+ //# sourceMappingURL=modelIntegrity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modelIntegrity.js","sourceRoot":"","sources":["../src/modelIntegrity.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuBH,gDAyCC;AAGD,4CAkBC;AASD,0CASC;AArGD,uCAAyB;AAEzB,gGAAgG;AAChG,MAAM,eAAe,GAAG,IAAI,CAAC;AAE7B;;;GAGG;AACH,MAAM,eAAe,GAAG,IAAI,CAAC;AAW7B,uFAAuF;AACvF,SAAgB,kBAAkB,CAAC,GAAW,EAAE,UAAkB;IAChE,IAAI,UAAU,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IAChF,CAAC;IAED,6EAA6E;IAC7E,8EAA8E;IAC9E,sBAAsB;IACtB,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,CAAC,WAAW,EAAE,CAAC;IACnG,IAAI,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACzF,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,QAAQ,EAAE,MAAM;YAChB,KAAK,EAAE,UAAU;YACjB,MAAM,EACJ,wBAAwB,UAAU,2CAA2C;gBAC7E,oDAAoD;SACvD,CAAC;IACJ,CAAC;IAED,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,eAAe,EAAE,CAAC;QACjD,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,QAAQ,EAAE,UAAU;YACpB,KAAK,EAAE,UAAU;YACjB,MAAM,EACJ,iDAAiD,eAAe,CAAC,QAAQ,CAAC,EAAE,CAAC,GAAG;gBAChF,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,+BAA+B;SAClF,CAAC;IACJ,CAAC;IAED,IAAI,UAAU,GAAG,eAAe,EAAE,CAAC;QACjC,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,QAAQ,EAAE,WAAW;YACrB,KAAK,EAAE,UAAU;YACjB,MAAM,EAAE,QAAQ,UAAU,kCAAkC;SAC7D,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;AAC5C,CAAC;AAED,sGAAsG;AACtG,SAAgB,gBAAgB,CAAC,QAAgB;IAC/C,IAAI,EAAsB,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC;QACxC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;QAC9C,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACnB,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;YAChC,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QACzC,CAAC;QACD,OAAO,kBAAkB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACvC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,MAAM,EAAE,eAAe,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG;SAC3E,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,IAAI,EAAE,KAAK,SAAS;YAAE,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IACzC,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,eAAe,CAAC,QAAgB,EAAE,KAAK,GAAG,QAAQ;IAChE,MAAM,MAAM,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAC1C,IAAI,MAAM,CAAC,KAAK;QAAE,OAAO;IACzB,IAAI,CAAC;QACH,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,mDAAmD;IACrD,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,KAAK,MAAM,CAAC,MAAM,IAAI,wBAAwB,EAAE,CAAC,CAAC;AAC5E,CAAC"}
@@ -0,0 +1,100 @@
1
+ import { type HttpPoster } from './AudienceAnalyzeClient';
2
+ /**
3
+ * pairVerify — the "honest pairing" helpers behind `trillboards-edge pair` and
4
+ * the new `verify-connection` subcommand.
5
+ *
6
+ * Motivated by two field failures where the pairing surface LIED about success:
7
+ * - Hiten (Windows, 16-ch NVR): pairing "succeeded" while the TrillboardsEdge
8
+ * scheduled task was never registered — 16 screens, zero frames, hours of
9
+ * CloudWatch archaeology.
10
+ * - Rushi (macOS): an EACCES from an admin-owned config dir was misreported
11
+ * as "Double-check the code (codes expire)" (#8200).
12
+ *
13
+ * Three jobs, all pure/injectable so they unit-test without network or shell:
14
+ * 1. classifyPairFailure — map a pair() failure (thrown transport error OR a
15
+ * non-2xx response) to an owner-grade failure class + copy. NEVER tells
16
+ * the operator to re-check a code unless the SERVER rejected the code.
17
+ * 2. verifyApiConnection — one authenticated round-trip after pairing
18
+ * (GET /v2/earner/cameras/sensing-profile with a paired device token,
19
+ * mirroring SensingProfileClient's auth headers) so "connected" is proven,
20
+ * not assumed. Distinguishes network vs auth vs server failures.
21
+ * 3. pairing log — a small rolling transcript under the platform log dir so
22
+ * "send diagnostics" has something to send. Never logs codes or tokens.
23
+ */
24
+ export type PairFailureKind = 'network' | 'rate_limited' | 'bad_code' | 'server';
25
+ export interface PairFailure {
26
+ kind: PairFailureKind;
27
+ /** Owner-grade explanation + next step. */
28
+ message: string;
29
+ }
30
+ /** True when an error thrown by the HTTP transport is a connectivity failure. */
31
+ export declare function isNetworkError(err: unknown): boolean;
32
+ /**
33
+ * Classify a pairing failure. `thrown` is an error from the transport (the
34
+ * request never completed); `statusCode`/`error` come from a completed non-2xx
35
+ * response. The copy rules:
36
+ * - Only a SERVER code rejection (400/404/410) may mention the code.
37
+ * - Connectivity problems say the code is still valid (it was never spent).
38
+ */
39
+ export declare function classifyPairFailure(input: {
40
+ thrown?: unknown;
41
+ statusCode?: number;
42
+ error?: string;
43
+ }): PairFailure;
44
+ export type VerifyKind = 'verified' | 'network' | 'auth' | 'server' | 'no_config';
45
+ export interface VerifyResult {
46
+ ok: boolean;
47
+ kind: VerifyKind;
48
+ /** Owner-grade explanation (empty when ok). */
49
+ message: string;
50
+ /** HTTP status of the probe, when a response was received. */
51
+ statusCode?: number;
52
+ }
53
+ /** The minimal slice of trillboards.config.json that verification needs. */
54
+ export interface VerifiableConfig {
55
+ apiBaseUrl: string;
56
+ /** First available per-feed device token (one feed = one token = one screen). */
57
+ deviceToken: string;
58
+ /** Display name of the feed the token belongs to (for the success line). */
59
+ feedName: string;
60
+ }
61
+ /**
62
+ * Extract the API base + one device token from a written config file. Accepts
63
+ * both shapes (multi-feed `cameras: []` and the single-camera back-compat
64
+ * top-level `deviceToken`), mirroring pair.command's count_feeds_missing_rtsp.
65
+ * Returns null when the file is missing/unreadable or holds no token.
66
+ */
67
+ export declare function readVerifiableConfig(configPath: string): VerifiableConfig | null;
68
+ /**
69
+ * Prove the pairing works end-to-end: an authenticated GET of this feed's
70
+ * sensing profile. Same endpoint + device-auth headers as
71
+ * SensingProfileClient.fetchProfile (deviceRequestAuth validates the token +
72
+ * nonce + timestamp) — but unlike that best-effort fetch, this one DISTINGUISHES
73
+ * failure classes, because the whole point is telling the operator the truth.
74
+ */
75
+ export declare function verifyApiConnection(config: VerifiableConfig, poster?: HttpPoster): Promise<VerifyResult>;
76
+ /**
77
+ * Platform log dir, matching where the installers already put logs:
78
+ * macOS ~/Library/Logs/Trillboards (postinstall creates it)
79
+ * Windows %LOCALAPPDATA%\Trillboards\logs
80
+ * other ~/.trillboards/logs (Docker/systemd installs)
81
+ * Injectable platform/env for tests.
82
+ */
83
+ export declare function defaultLogDir(platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv, homedir?: string): string;
84
+ /**
85
+ * Append timestamped lines to the rolling pairing log. Fully best-effort: a
86
+ * logging failure must never fail pairing. Returns the log file path (for the
87
+ * "details saved to …" hint) or null when the write failed.
88
+ * NEVER pass claim codes or device tokens in `lines`.
89
+ */
90
+ export declare function appendPairLog(logDir: string, lines: string | string[]): string | null;
91
+ /**
92
+ * Exit-code contract (the install wrappers branch on these — keep stable):
93
+ * 0 verified — config present, API reachable, token accepted
94
+ * 2 network — config present but Trillboards is unreachable from here
95
+ * 3 auth — API reachable but the saved pairing was rejected
96
+ * 4 no usable config (missing file / no device token)
97
+ * 5 server — API responded with an unexpected status
98
+ */
99
+ export declare function runVerifyConnection(args: string[]): Promise<void>;
100
+ //# sourceMappingURL=pairVerify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pairVerify.d.ts","sourceRoot":"","sources":["../src/pairVerify.ts"],"names":[],"mappings":"AAIA,OAAO,EAAqB,KAAK,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAE7E;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,cAAc,GAAG,UAAU,GAAG,QAAQ,CAAC;AAEjF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,eAAe,CAAC;IACtB,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;CACjB;AAcD,iFAAiF;AACjF,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAKpD;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IACzC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,WAAW,CA+Cd;AAID,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,WAAW,CAAC;AAElF,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,EAAE,UAAU,CAAC;IACjB,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,4EAA4E;AAC5E,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,WAAW,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CA0BhF;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,gBAAgB,EACxB,MAAM,GAAE,UAA8B,GACrC,OAAO,CAAC,YAAY,CAAC,CAgDvB;AAQD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,OAAO,GAAE,MAAqB,GAC7B,MAAM,CAOR;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAmBrF;AAID;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAgCvE"}
@@ -0,0 +1,296 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.isNetworkError = isNetworkError;
37
+ exports.classifyPairFailure = classifyPairFailure;
38
+ exports.readVerifiableConfig = readVerifiableConfig;
39
+ exports.verifyApiConnection = verifyApiConnection;
40
+ exports.defaultLogDir = defaultLogDir;
41
+ exports.appendPairLog = appendPairLog;
42
+ exports.runVerifyConnection = runVerifyConnection;
43
+ const fs = __importStar(require("fs"));
44
+ const os = __importStar(require("os"));
45
+ const path = __importStar(require("path"));
46
+ const crypto_1 = require("crypto");
47
+ const AudienceAnalyzeClient_1 = require("./AudienceAnalyzeClient");
48
+ const NETWORK_ERRNO = new Set([
49
+ 'ENOTFOUND',
50
+ 'EAI_AGAIN',
51
+ 'ECONNREFUSED',
52
+ 'ECONNRESET',
53
+ 'ETIMEDOUT',
54
+ 'EHOSTUNREACH',
55
+ 'ENETUNREACH',
56
+ 'ENETDOWN',
57
+ 'EPIPE',
58
+ ]);
59
+ /** True when an error thrown by the HTTP transport is a connectivity failure. */
60
+ function isNetworkError(err) {
61
+ const e = err;
62
+ if (!e)
63
+ return false;
64
+ if (e.code && NETWORK_ERRNO.has(e.code))
65
+ return true;
66
+ return /timed out|network|getaddrinfo|socket hang up/i.test(String(e.message ?? ''));
67
+ }
68
+ /**
69
+ * Classify a pairing failure. `thrown` is an error from the transport (the
70
+ * request never completed); `statusCode`/`error` come from a completed non-2xx
71
+ * response. The copy rules:
72
+ * - Only a SERVER code rejection (400/404/410) may mention the code.
73
+ * - Connectivity problems say the code is still valid (it was never spent).
74
+ */
75
+ function classifyPairFailure(input) {
76
+ if (input.thrown !== undefined) {
77
+ if (isNetworkError(input.thrown)) {
78
+ const code = input.thrown?.code;
79
+ return {
80
+ kind: 'network',
81
+ message: `This computer can't reach Trillboards right now` +
82
+ (code ? ` (${code})` : '') +
83
+ `.\nCheck this machine's internet connection (Wi-Fi/ethernet, firewall, captive portal),\n` +
84
+ `then run pairing again. Your code was NOT used — it stays valid until it expires.`,
85
+ };
86
+ }
87
+ const msg = input.thrown instanceof Error ? input.thrown.message : String(input.thrown);
88
+ return {
89
+ kind: 'server',
90
+ message: `Pairing hit an unexpected error: ${msg}\n` +
91
+ `Try again in a minute. Your code was NOT used.`,
92
+ };
93
+ }
94
+ const status = input.statusCode ?? 0;
95
+ if (status === 429) {
96
+ return {
97
+ kind: 'rate_limited',
98
+ message: `Too many pairing attempts from this network — wait a minute, then try again.\n` +
99
+ `Your code stays valid until it expires.`,
100
+ };
101
+ }
102
+ if (status === 400 || status === 404 || status === 410) {
103
+ return {
104
+ kind: 'bad_code',
105
+ message: `That code wasn't recognized — it may have expired (codes last about 30 minutes)\n` +
106
+ `or have a typo. Get a fresh code from the portal and try again.` +
107
+ (input.error ? `\n(Server said: ${input.error})` : ''),
108
+ };
109
+ }
110
+ return {
111
+ kind: 'server',
112
+ message: `Trillboards had a hiccup (HTTP ${status || '?'}). This is not a code problem —\n` +
113
+ `wait a minute and try again. Your code stays valid until it expires.` +
114
+ (input.error ? `\n(Server said: ${input.error})` : ''),
115
+ };
116
+ }
117
+ /**
118
+ * Extract the API base + one device token from a written config file. Accepts
119
+ * both shapes (multi-feed `cameras: []` and the single-camera back-compat
120
+ * top-level `deviceToken`), mirroring pair.command's count_feeds_missing_rtsp.
121
+ * Returns null when the file is missing/unreadable or holds no token.
122
+ */
123
+ function readVerifiableConfig(configPath) {
124
+ let raw;
125
+ try {
126
+ raw = fs.readFileSync(configPath, 'utf8');
127
+ }
128
+ catch {
129
+ return null;
130
+ }
131
+ try {
132
+ const cfg = JSON.parse(raw);
133
+ const apiBaseUrl = String(cfg.apiBaseUrl || 'https://api.trillboards.com');
134
+ const cameras = Array.isArray(cfg.cameras) ? cfg.cameras : [];
135
+ const withToken = cameras.find((c) => c && typeof c.deviceToken === 'string' && c.deviceToken);
136
+ if (withToken) {
137
+ return {
138
+ apiBaseUrl,
139
+ deviceToken: String(withToken.deviceToken),
140
+ feedName: String(withToken.name || withToken.screenId || 'camera'),
141
+ };
142
+ }
143
+ if (typeof cfg.deviceToken === 'string' && cfg.deviceToken) {
144
+ return { apiBaseUrl, deviceToken: cfg.deviceToken, feedName: 'camera' };
145
+ }
146
+ return null;
147
+ }
148
+ catch {
149
+ return null;
150
+ }
151
+ }
152
+ /**
153
+ * Prove the pairing works end-to-end: an authenticated GET of this feed's
154
+ * sensing profile. Same endpoint + device-auth headers as
155
+ * SensingProfileClient.fetchProfile (deviceRequestAuth validates the token +
156
+ * nonce + timestamp) — but unlike that best-effort fetch, this one DISTINGUISHES
157
+ * failure classes, because the whole point is telling the operator the truth.
158
+ */
159
+ async function verifyApiConnection(config, poster = AudienceAnalyzeClient_1.defaultHttpPoster) {
160
+ const base = config.apiBaseUrl.replace(/\/+$/, '');
161
+ const endpoint = `${base}/v2/earner/cameras/sensing-profile`;
162
+ const headers = {
163
+ Accept: 'application/json',
164
+ 'X-Device-Token': config.deviceToken,
165
+ 'X-Trillboard-Device-Token': config.deviceToken,
166
+ 'X-Device-Timestamp': String(Date.now()),
167
+ 'X-Device-Nonce': (0, crypto_1.randomUUID)(),
168
+ };
169
+ let result;
170
+ try {
171
+ result = await poster(endpoint, { method: 'GET', headers, timeoutMs: 15000 }, '');
172
+ }
173
+ catch (err) {
174
+ const code = err?.code;
175
+ return {
176
+ ok: false,
177
+ kind: 'network',
178
+ message: `Paired and saved, but this computer can't reach Trillboards right now` +
179
+ (code ? ` (${code})` : '') +
180
+ `.\nYour pairing is SAVED — fix this machine's internet connection and run the\n` +
181
+ `pairing helper again. You will NOT need a new code.`,
182
+ };
183
+ }
184
+ if (result.statusCode >= 200 && result.statusCode < 300) {
185
+ return { ok: true, kind: 'verified', message: '', statusCode: result.statusCode };
186
+ }
187
+ if (result.statusCode === 401 || result.statusCode === 403) {
188
+ return {
189
+ ok: false,
190
+ kind: 'auth',
191
+ statusCode: result.statusCode,
192
+ message: `Trillboards rejected this machine's saved pairing (HTTP ${result.statusCode}).\n` +
193
+ `The pairing may have been revoked. Generate a fresh code in the portal and pair again.`,
194
+ };
195
+ }
196
+ return {
197
+ ok: false,
198
+ kind: 'server',
199
+ statusCode: result.statusCode,
200
+ message: `Paired and saved, but the connection check got HTTP ${result.statusCode} from Trillboards.\n` +
201
+ `Your pairing is SAVED — wait a minute and run the pairing helper again.`,
202
+ };
203
+ }
204
+ // ─── 3. Rolling pairing log ──────────────────────────────────────────────────
205
+ /** Rotate at 512 KB, keep one previous generation — enough for diagnostics. */
206
+ const LOG_MAX_BYTES = 512 * 1024;
207
+ const LOG_FILE = 'pairing.log';
208
+ /**
209
+ * Platform log dir, matching where the installers already put logs:
210
+ * macOS ~/Library/Logs/Trillboards (postinstall creates it)
211
+ * Windows %LOCALAPPDATA%\Trillboards\logs
212
+ * other ~/.trillboards/logs (Docker/systemd installs)
213
+ * Injectable platform/env for tests.
214
+ */
215
+ function defaultLogDir(platform = process.platform, env = process.env, homedir = os.homedir()) {
216
+ if (platform === 'darwin')
217
+ return path.join(homedir, 'Library', 'Logs', 'Trillboards');
218
+ if (platform === 'win32') {
219
+ const localAppData = env.LOCALAPPDATA || path.join(homedir, 'AppData', 'Local');
220
+ return path.join(localAppData, 'Trillboards', 'logs');
221
+ }
222
+ return path.join(homedir, '.trillboards', 'logs');
223
+ }
224
+ /**
225
+ * Append timestamped lines to the rolling pairing log. Fully best-effort: a
226
+ * logging failure must never fail pairing. Returns the log file path (for the
227
+ * "details saved to …" hint) or null when the write failed.
228
+ * NEVER pass claim codes or device tokens in `lines`.
229
+ */
230
+ function appendPairLog(logDir, lines) {
231
+ try {
232
+ fs.mkdirSync(logDir, { recursive: true });
233
+ const file = path.join(logDir, LOG_FILE);
234
+ try {
235
+ const st = fs.statSync(file);
236
+ if (st.size > LOG_MAX_BYTES)
237
+ fs.renameSync(file, `${file}.1`);
238
+ }
239
+ catch {
240
+ /* first write */
241
+ }
242
+ const stamp = new Date().toISOString();
243
+ const text = (Array.isArray(lines) ? lines : [lines])
244
+ .map((l) => `[${stamp}] ${l}`)
245
+ .join('\n');
246
+ fs.appendFileSync(file, text + '\n');
247
+ return file;
248
+ }
249
+ catch {
250
+ return null;
251
+ }
252
+ }
253
+ // ─── CLI entry: `trillboards-edge verify-connection` ─────────────────────────
254
+ /**
255
+ * Exit-code contract (the install wrappers branch on these — keep stable):
256
+ * 0 verified — config present, API reachable, token accepted
257
+ * 2 network — config present but Trillboards is unreachable from here
258
+ * 3 auth — API reachable but the saved pairing was rejected
259
+ * 4 no usable config (missing file / no device token)
260
+ * 5 server — API responded with an unexpected status
261
+ */
262
+ async function runVerifyConnection(args) {
263
+ const cfgIdx = args.indexOf('--config');
264
+ const configPath = cfgIdx >= 0 && args[cfgIdx + 1] ? args[cfgIdx + 1] : 'trillboards.config.json';
265
+ const asJson = args.includes('--json');
266
+ const resolved = path.resolve(configPath);
267
+ const cfg = readVerifiableConfig(resolved);
268
+ if (!cfg) {
269
+ if (asJson)
270
+ console.log(JSON.stringify({ ok: false, kind: 'no_config' }));
271
+ else
272
+ console.error(`No paired config with a device token at: ${resolved}`);
273
+ process.exit(4);
274
+ return;
275
+ }
276
+ const res = await verifyApiConnection(cfg);
277
+ appendPairLog(defaultLogDir(), `verify-connection: ${res.kind}${res.statusCode ? ` (HTTP ${res.statusCode})` : ''}`);
278
+ if (asJson) {
279
+ console.log(JSON.stringify({ ok: res.ok, kind: res.kind, statusCode: res.statusCode ?? null }));
280
+ }
281
+ else if (res.ok) {
282
+ console.log(`Connected to Trillboards — pairing for "${cfg.feedName}" is valid.`);
283
+ }
284
+ else {
285
+ console.error(res.message);
286
+ }
287
+ const exitByKind = {
288
+ verified: 0,
289
+ network: 2,
290
+ auth: 3,
291
+ server: 5,
292
+ no_config: 4,
293
+ };
294
+ process.exit(exitByKind[res.kind]);
295
+ }
296
+ //# sourceMappingURL=pairVerify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pairVerify.js","sourceRoot":"","sources":["../src/pairVerify.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * qrScanner — decode a Trillboards claim code from a QR image so the connector's
3
+ * `pair` flow can be driven by SCANNING the portal's Install-step QR instead of
4
+ * hand-typing the code. The portal renders that QR via `<QrCodeDisplay
5
+ * value={claimCode} />`, so the QR payload IS the raw claim code — we return it
6
+ * verbatim and let PairClient.normalizeClaimCode canonicalize it.
7
+ *
8
+ * Pure-JS and only reached via cli.ts's lazy `require('./qrScanner')` inside the
9
+ * `pair --qr` branch, so the common `pair --code` entrypoint (the macOS/edge
10
+ * "cloud-sense" installer surface) never pulls in the image-decode deps. Supports
11
+ * PNG (a screenshot / "save image" of the portal QR) and JPEG (a phone photo).
12
+ */
13
+ export interface DecodedImage {
14
+ /** RGBA pixels, 4 bytes per pixel. */
15
+ data: Uint8ClampedArray;
16
+ width: number;
17
+ height: number;
18
+ }
19
+ /**
20
+ * Decode a PNG or JPEG buffer to RGBA pixels, or null for an unsupported format
21
+ * (sniffed by magic bytes, not file extension, so a mislabeled file still works).
22
+ */
23
+ export declare function decodeImageToRgba(buf: Buffer): DecodedImage | null;
24
+ /** Decode the QR payload from an image buffer, or null if unsupported / no QR found. */
25
+ export declare function decodeQrFromImageBuffer(buf: Buffer): string | null;
26
+ /**
27
+ * Read an image file (a PNG/JPEG screenshot or photo of the portal's claim-code
28
+ * QR) and return the embedded claim code. Throws a clear, owner-facing error for
29
+ * a missing file, an unsupported format, or an image with no QR — so the CLI can
30
+ * print actionable guidance instead of a stack trace.
31
+ */
32
+ export declare function decodeClaimCodeFromImageFile(filePath: string): Promise<string>;
33
+ //# sourceMappingURL=qrScanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qrScanner.d.ts","sourceRoot":"","sources":["../src/qrScanner.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,YAAY;IAC3B,sCAAsC;IACtC,IAAI,EAAE,iBAAiB,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAaD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAUlE;AAED,wFAAwF;AACxF,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKlE;AAED;;;;;GAKG;AACH,wBAAsB,4BAA4B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAepF"}