@trillboards/edge-sdk 0.2.4 → 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 +55 -13
  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,361 @@
1
+ <#
2
+ Provision the LiteRT-LM on-device VLM runtime for the BYO-CCTV connector.
3
+ This script runs as the signed-in user, is safe to retry, and reports setup
4
+ progress without including camera credentials.
5
+ #>
6
+ [CmdletBinding()]
7
+ param(
8
+ [string]$Port = "9379",
9
+ [string]$ModelId = "gemma-4-E2B-it.litertlm",
10
+ [string]$HfRepo = "litert-community/gemma-4-E2B-it-litert-lm",
11
+ [string]$LitertPython = $(if ($env:LITERT_LM_PYTHON) { $env:LITERT_LM_PYTHON } else { "3.12" }),
12
+ [string]$ModelVersion = $(if ($env:GEMMA_VLM_VERSION) { $env:GEMMA_VLM_VERSION } else { "1.0.0" }),
13
+ [string]$BundledModelPath = "",
14
+ [string]$BundledModelSha256 = "181938105e0eefd105961417e8da75903eacda102c4fce9ce90f50b97139a63c",
15
+ [switch]$RequireBundledModel,
16
+ [switch]$AllowModelDownload,
17
+ [string]$ProgressPath = "",
18
+ [switch]$SmokeTest
19
+ )
20
+
21
+ $ErrorActionPreference = "Continue"
22
+ $Here = Split-Path -Parent $MyInvocation.MyCommand.Path
23
+ $LocalBin = Join-Path $env:USERPROFILE ".local\bin"
24
+ $LogDir = Join-Path $env:LOCALAPPDATA "Trillboards\logs"
25
+ $LitertDir = if ($env:LITERT_LM_DIR) { $env:LITERT_LM_DIR } else { Join-Path $env:USERPROFILE ".litert-lm" }
26
+ $StoreDir = Join-Path (Join-Path $LitertDir "models") $ModelId
27
+ $VersionMarker = Join-Path $StoreDir "trillboards-bundled-version.json"
28
+ $ExpectedModelBytes = [long](2.4 * 1GB)
29
+ New-Item -ItemType Directory -Force -Path $LogDir | Out-Null
30
+ $LogPath = Join-Path $LogDir "litertlm-setup.log"
31
+
32
+ function Log([string]$Message) {
33
+ $line = "[trillboards setup-litertlm] {0} {1}" -f (Get-Date -Format "HH:mm:ss"), $Message
34
+ Add-Content -Path $LogPath -Value $line -ErrorAction SilentlyContinue
35
+ Write-Host $line
36
+ }
37
+
38
+ function Write-SetupProgress {
39
+ param(
40
+ [Parameter(Mandatory = $true)][string]$Stage,
41
+ [Parameter(Mandatory = $true)][string]$Title,
42
+ [Parameter(Mandatory = $true)][string]$Message,
43
+ [string]$Phase = "",
44
+ [object]$Percent = $null,
45
+ [object]$DownloadedBytes = $null,
46
+ [object]$TotalBytes = $null,
47
+ [bool]$Recoverable = $false
48
+ )
49
+ if ([string]::IsNullOrWhiteSpace($ProgressPath)) { return }
50
+ try {
51
+ $state = [ordered]@{
52
+ version = 1
53
+ stage = $Stage
54
+ title = $Title
55
+ message = $Message
56
+ updatedAt = [DateTime]::UtcNow.ToString("o")
57
+ }
58
+ if (Test-Path -LiteralPath $ProgressPath) {
59
+ try {
60
+ $previous = Get-Content -LiteralPath $ProgressPath -Raw | ConvertFrom-Json
61
+ foreach ($name in @("devicesFound", "validatedFeeds", "rejectedFeeds", "cameraCount")) {
62
+ $property = $previous.PSObject.Properties[$name]
63
+ if ($null -ne $property -and $null -ne $property.Value) { $state[$name] = [int]$property.Value }
64
+ }
65
+ } catch { }
66
+ }
67
+ if ($Phase) { $state.localAiPhase = $Phase }
68
+ if ($null -ne $Percent) { $state.localAiProgressPercent = [Math]::Max(0, [Math]::Min(100, [int]$Percent)) }
69
+ if ($null -ne $DownloadedBytes) { $state.localAiDownloadedBytes = [Math]::Max([long]0, [long]$DownloadedBytes) }
70
+ if ($null -ne $TotalBytes) { $state.localAiTotalBytes = [Math]::Max([long]0, [long]$TotalBytes) }
71
+ if ($Recoverable) { $state.recoverable = $true }
72
+
73
+ $parent = Split-Path -Parent $ProgressPath
74
+ New-Item -ItemType Directory -Force -Path $parent | Out-Null
75
+ $temporary = "$ProgressPath.$PID.tmp"
76
+ $json = ($state | ConvertTo-Json -Depth 4) + [Environment]::NewLine
77
+ [System.IO.File]::WriteAllText($temporary, $json, [System.Text.UTF8Encoding]::new($false))
78
+ Move-Item -LiteralPath $temporary -Destination $ProgressPath -Force
79
+ } catch {
80
+ Log "WARN: could not update setup progress: $_"
81
+ }
82
+ }
83
+
84
+ function Get-PartialModelBytes {
85
+ if (-not (Test-Path -LiteralPath $StoreDir)) { return [long]0 }
86
+ try {
87
+ $partial = Get-ChildItem -LiteralPath $StoreDir -Recurse -File -Filter "*.tmp" -ErrorAction SilentlyContinue |
88
+ Sort-Object Length -Descending |
89
+ Select-Object -First 1
90
+ if ($null -ne $partial) { return [long]$partial.Length }
91
+ } catch { }
92
+ return [long]0
93
+ }
94
+
95
+ function Write-DownloadProgress {
96
+ $downloaded = Get-PartialModelBytes
97
+ $percent = [int][Math]::Min(99, [Math]::Floor(($downloaded * 100.0) / $ExpectedModelBytes))
98
+ Write-SetupProgress -Stage "local_ai" -Title "Downloading local AI" -Message "Gemma is downloading to this computer. Keep it online; cloud inference remains off." -Phase "downloading_model" -Percent $percent -DownloadedBytes $downloaded -TotalBytes $ExpectedModelBytes
99
+ }
100
+
101
+ function Test-ServeHealthy {
102
+ try {
103
+ $response = Invoke-WebRequest -Uri "http://127.0.0.1:$Port/v1/models" -UseBasicParsing -TimeoutSec 3
104
+ return ($response.StatusCode -eq 200 -and ([string]$response.Content) -match [regex]::Escape($ModelId))
105
+ } catch {
106
+ return $false
107
+ }
108
+ }
109
+
110
+ function Complete-Setup {
111
+ Log "local AI is healthy on :$Port"
112
+ Write-SetupProgress -Stage "ready" -Title "Camera setup is complete" -Message "Local AI is installed and healthy. Trillboards is running in the background." -Phase "verifying" -Percent 100
113
+ }
114
+
115
+ function Fail-LocalAi([string]$Message) {
116
+ Log "ERROR: $Message"
117
+ Write-SetupProgress -Stage "local_ai_error" -Title "Local AI needs attention" -Message $Message -Recoverable $true
118
+ }
119
+
120
+ function Test-LitertLmModel([string]$ExecutablePath) {
121
+ try { return ((& $ExecutablePath list 2>$null | Out-String) -match [regex]::Escape($ModelId)) }
122
+ catch { return $false }
123
+ }
124
+
125
+ if ($SmokeTest) {
126
+ if ([string]::IsNullOrWhiteSpace($ProgressPath)) { throw "SmokeTest requires ProgressPath" }
127
+ Write-SetupProgress -Stage "local_ai" -Title "Installing local AI" -Message "Smoke test" -Phase "installing_model" -Percent 62 -TotalBytes $ExpectedModelBytes
128
+ Write-SetupProgress -Stage "ready" -Title "Camera setup is complete" -Message "Smoke test complete" -Phase "verifying" -Percent 100
129
+ $smokeState = Get-Content -LiteralPath $ProgressPath -Raw | ConvertFrom-Json
130
+ if ($smokeState.stage -ne "ready" -or $smokeState.localAiProgressPercent -ne 100) {
131
+ throw "Local AI progress smoke test failed"
132
+ }
133
+ Write-Host "LiteRT-LM setup progress smoke test passed."
134
+ exit 0
135
+ }
136
+
137
+ $setupMutex = New-Object System.Threading.Mutex($false, "Local\TrillboardsLiteRtLmSetup")
138
+ $haveSetupMutex = $false
139
+ try {
140
+ $haveSetupMutex = $setupMutex.WaitOne(0)
141
+ } catch [System.Threading.AbandonedMutexException] {
142
+ $haveSetupMutex = $true
143
+ }
144
+
145
+ if (-not $haveSetupMutex) {
146
+ Log "LiteRT-LM provisioning is already running; attaching setup progress"
147
+ if ([string]::IsNullOrWhiteSpace($ProgressPath) -and -not $RequireBundledModel) {
148
+ $setupMutex.Dispose()
149
+ exit 0
150
+ }
151
+ for ($attempt = 0; $attempt -lt 600 -and -not $haveSetupMutex; $attempt++) {
152
+ if (Test-ServeHealthy) {
153
+ Complete-Setup
154
+ $setupMutex.Dispose()
155
+ exit 0
156
+ }
157
+ if ((Get-PartialModelBytes) -gt 0) {
158
+ Write-DownloadProgress
159
+ } else {
160
+ Write-SetupProgress -Stage "local_ai" -Title "Installing local AI" -Message "The local AI setup is already running. Cloud inference remains off." -Phase "installing_runtime" -Percent 0
161
+ }
162
+ Start-Sleep -Seconds 3
163
+ try {
164
+ $haveSetupMutex = $setupMutex.WaitOne(0)
165
+ } catch [System.Threading.AbandonedMutexException] {
166
+ $haveSetupMutex = $true
167
+ }
168
+ }
169
+ if (-not $haveSetupMutex) {
170
+ Fail-LocalAi "The local AI installation did not finish. Reinstall Trillboards CCTV Setup."
171
+ $setupMutex.Dispose()
172
+ exit 1
173
+ }
174
+ }
175
+
176
+ function Stop-SetupLock {
177
+ try { $setupMutex.ReleaseMutex() } catch { }
178
+ $setupMutex.Dispose()
179
+ }
180
+
181
+ Log "starting LiteRT-LM provisioning (Windows x64, CPU backend)"
182
+ Write-SetupProgress -Stage "local_ai" -Title "Installing local AI" -Message "Preparing the private on-device AI runtime. Cloud inference remains off." -Phase "installing_runtime" -Percent 0
183
+
184
+ if (-not [Environment]::Is64BitOperatingSystem) {
185
+ Fail-LocalAi "Local AI requires 64-bit Windows. Camera pairing is saved."
186
+ Stop-SetupLock
187
+ exit 1
188
+ }
189
+
190
+ if (Test-ServeHealthy) {
191
+ Complete-Setup
192
+ Stop-SetupLock
193
+ exit 0
194
+ }
195
+
196
+ $env:Path = "$LocalBin;$env:Path"
197
+ if (-not (Get-Command uv -ErrorAction SilentlyContinue)) {
198
+ Log "uv not found; installing the local package runtime (no admin required)"
199
+ Write-SetupProgress -Stage "local_ai" -Title "Installing local AI" -Message "Installing the local AI runtime. Cloud inference remains off." -Phase "installing_runtime" -Percent 5
200
+ try { Invoke-RestMethod -Uri "https://astral.sh/uv/install.ps1" | Invoke-Expression *>> $LogPath }
201
+ catch { Log "WARN: uv install failed: $_" }
202
+ $env:Path = "$LocalBin;$env:Path"
203
+ }
204
+ $uvCmd = Get-Command uv -ErrorAction SilentlyContinue
205
+ if (-not $uvCmd) {
206
+ Fail-LocalAi "The local AI runtime could not be installed. Check the internet connection, then retry Local AI."
207
+ Stop-SetupLock
208
+ exit 1
209
+ }
210
+
211
+ $litertlmCmd = Get-Command litert-lm -ErrorAction SilentlyContinue
212
+ $litertlmPath = if ($litertlmCmd) { $litertlmCmd.Source } else { Join-Path $LocalBin "litert-lm.exe" }
213
+ function Test-LitertLmCli {
214
+ if (-not (Test-Path -LiteralPath $litertlmPath)) { return $false }
215
+ try {
216
+ & $litertlmPath list *> $null
217
+ return ($LASTEXITCODE -eq 0)
218
+ } catch {
219
+ return $false
220
+ }
221
+ }
222
+
223
+ if (Test-LitertLmCli) {
224
+ Log "litert-lm CLI is healthy; keeping current install"
225
+ } else {
226
+ Log "installing or repairing litert-lm with Python $LitertPython"
227
+ Write-SetupProgress -Stage "local_ai" -Title "Installing local AI" -Message "Installing the local AI engine. Cloud inference remains off." -Phase "installing_runtime" -Percent 10
228
+ & $uvCmd.Source tool install --python $LitertPython --managed-python --upgrade --force litert-lm *>> $LogPath
229
+ $litertlmCmd = Get-Command litert-lm -ErrorAction SilentlyContinue
230
+ $litertlmPath = if ($litertlmCmd) { $litertlmCmd.Source } else { Join-Path $LocalBin "litert-lm.exe" }
231
+ if (-not (Test-LitertLmCli)) {
232
+ Log "litert-lm remains unhealthy after repair"
233
+ Fail-LocalAi "The local AI engine could not be installed. Check the internet connection, then retry Local AI."
234
+ Stop-SetupLock
235
+ exit 1
236
+ }
237
+ Log "litert-lm CLI repaired successfully"
238
+ }
239
+
240
+ $bundledModelVerified = $false
241
+ if (-not [string]::IsNullOrWhiteSpace($BundledModelPath)) {
242
+ if (-not (Test-Path -LiteralPath $BundledModelPath -PathType Leaf)) {
243
+ Fail-LocalAi "The local AI model included with this installer is missing. Run the installer again."
244
+ Stop-SetupLock
245
+ exit 1
246
+ }
247
+ $actualModelSha256 = (Get-FileHash -LiteralPath $BundledModelPath -Algorithm SHA256).Hash.ToLowerInvariant()
248
+ if ($actualModelSha256 -ne $BundledModelSha256.ToLowerInvariant()) {
249
+ Log "bundled model checksum mismatch (expected $BundledModelSha256, got $actualModelSha256)"
250
+ Fail-LocalAi "The included local AI model could not be verified. Download a fresh Trillboards installer."
251
+ Stop-SetupLock
252
+ exit 1
253
+ }
254
+ $bundledModelVerified = $true
255
+ Log "verified bundled model SHA-256"
256
+ }
257
+
258
+ $haveModel = Test-LitertLmModel $litertlmPath
259
+ if ($haveModel) {
260
+ Log "model $ModelId already imported; skipping import"
261
+ } elseif ($bundledModelVerified) {
262
+ Write-SetupProgress -Stage "local_ai" -Title "Installing local AI" -Message "Installing the AI model included with Trillboards. Cloud inference remains off." -Phase "installing_model" -Percent 25 -TotalBytes (Get-Item -LiteralPath $BundledModelPath).Length
263
+ & $litertlmPath import $BundledModelPath $ModelId *>> $LogPath
264
+ $haveModel = (($LASTEXITCODE -eq 0) -and (Test-LitertLmModel $litertlmPath))
265
+ if ($haveModel) {
266
+ Log "model imported from the installer bundle"
267
+ } else {
268
+ Log "bundled model import failed"
269
+ }
270
+ } elseif ($RequireBundledModel -or -not $AllowModelDownload) {
271
+ Fail-LocalAi "The included local AI model is unavailable. Run the Trillboards installer again."
272
+ Stop-SetupLock
273
+ exit 1
274
+ } else {
275
+ $maxAttempts = 3
276
+ for ($attempt = 1; $attempt -le $maxAttempts -and -not $haveModel; $attempt++) {
277
+ Log "importing $ModelId from $HfRepo (about 2.4 GB) [attempt $attempt/$maxAttempts]"
278
+ Write-DownloadProgress
279
+ $stdoutPath = Join-Path $LogDir "litertlm-import-$PID.out.log"
280
+ $stderrPath = Join-Path $LogDir "litertlm-import-$PID.err.log"
281
+ try {
282
+ $process = Start-Process -FilePath $litertlmPath -ArgumentList @("import", "--from-huggingface-repo", $HfRepo, $ModelId) -NoNewWindow -RedirectStandardOutput $stdoutPath -RedirectStandardError $stderrPath -PassThru
283
+ while (-not $process.HasExited) {
284
+ Write-DownloadProgress
285
+ Start-Sleep -Seconds 2
286
+ $process.Refresh()
287
+ }
288
+ $process.WaitForExit()
289
+ Log "model import exited with code $($process.ExitCode)"
290
+ $process.Dispose()
291
+ } catch {
292
+ Log "WARN: model import process failed: $_"
293
+ }
294
+ foreach ($path in @($stdoutPath, $stderrPath)) {
295
+ if (Test-Path -LiteralPath $path) {
296
+ Get-Content -LiteralPath $path -ErrorAction SilentlyContinue | Add-Content -Path $LogPath -ErrorAction SilentlyContinue
297
+ Remove-Item -LiteralPath $path -Force -ErrorAction SilentlyContinue
298
+ }
299
+ }
300
+ $haveModel = Test-LitertLmModel $litertlmPath
301
+ if ($haveModel) {
302
+ Log "model imported"
303
+ } elseif ($attempt -lt $maxAttempts) {
304
+ $backoff = 5 * $attempt
305
+ Log "WARN: model import attempt $attempt failed; retrying in ${backoff}s"
306
+ Start-Sleep -Seconds $backoff
307
+ }
308
+ }
309
+ }
310
+
311
+ if (-not $haveModel) {
312
+ $failureMessage = if ($RequireBundledModel) {
313
+ "The included local AI model could not be installed. Run the Trillboards installer again."
314
+ } else {
315
+ "Gemma did not finish downloading. Check the internet connection, then retry Local AI."
316
+ }
317
+ Fail-LocalAi $failureMessage
318
+ Stop-SetupLock
319
+ exit 1
320
+ }
321
+
322
+ try {
323
+ New-Item -ItemType Directory -Force -Path $StoreDir | Out-Null
324
+ $markerJson = '{"version":"' + $ModelVersion + '","modelType":"gemma-vlm"}'
325
+ [System.IO.File]::WriteAllText($VersionMarker, $markerJson, [System.Text.UTF8Encoding]::new($false))
326
+ Log "recorded installed version $ModelVersion at $VersionMarker"
327
+ } catch {
328
+ Log "WARN: could not write version marker $VersionMarker : $_"
329
+ }
330
+
331
+ Write-SetupProgress -Stage "local_ai" -Title "Starting local AI" -Message "Gemma is installed. Starting the private local AI service." -Phase "starting_service" -Percent 100
332
+ $registration = Join-Path $Here "register-litertlm-task.ps1"
333
+ if (-not (Test-Path -LiteralPath $registration)) {
334
+ Fail-LocalAi "The local AI service installer is missing. Reinstall the connector, then retry Local AI."
335
+ Stop-SetupLock
336
+ exit 1
337
+ }
338
+
339
+ Log "registering litert-lm serve Scheduled Task on :$Port"
340
+ & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $registration -LitertLmPath $litertlmPath -Port $Port *>> $LogPath
341
+ if ($LASTEXITCODE -ne 0) {
342
+ Fail-LocalAi "The local AI service could not be started. Restart Windows, then retry Local AI."
343
+ Stop-SetupLock
344
+ exit 1
345
+ }
346
+
347
+ Write-SetupProgress -Stage "local_ai" -Title "Checking local AI" -Message "Verifying that Gemma is responding on this computer." -Phase "verifying" -Percent 100
348
+ $healthy = $false
349
+ for ($attempt = 0; $attempt -lt 60 -and -not $healthy; $attempt++) {
350
+ $healthy = Test-ServeHealthy
351
+ if (-not $healthy) { Start-Sleep -Seconds 2 }
352
+ }
353
+ if (-not $healthy) {
354
+ Fail-LocalAi "Gemma is installed, but the local AI service did not respond. Restart Windows, then retry Local AI."
355
+ Stop-SetupLock
356
+ exit 1
357
+ }
358
+
359
+ Complete-Setup
360
+ Stop-SetupLock
361
+ exit 0
@@ -0,0 +1,72 @@
1
+ <#
2
+ Stops only processes belonging to this per-user Trillboards Edge install.
3
+ Used by upgrades and uninstall so bundled node.exe is never overwritten while
4
+ running, without killing unrelated Node or PowerShell applications.
5
+ #>
6
+ [CmdletBinding()]
7
+ param(
8
+ [Parameter(Mandatory = $true)][string]$InstallDir
9
+ )
10
+
11
+ $ErrorActionPreference = "Stop"
12
+ $root = [IO.Path]::GetFullPath($InstallDir).TrimEnd('\')
13
+ $rootPrefix = $root + '\'
14
+
15
+ Stop-ScheduledTask -TaskName "TrillboardsEdge" -ErrorAction SilentlyContinue
16
+ Start-Sleep -Milliseconds 750
17
+
18
+ function Get-TrillboardsEdgeProcesses {
19
+ $markers = @(
20
+ 'trillboards-edge-task.vbs',
21
+ 'run-connector.ps1',
22
+ 'trillboards-edge.cmd',
23
+ 'trillboards-setup.ps1',
24
+ 'trillboards-pair.cmd'
25
+ )
26
+
27
+ @(Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object {
28
+ if ($_.ProcessId -eq $PID) { return $false }
29
+ $exe = if ($_.ExecutablePath) { [IO.Path]::GetFullPath($_.ExecutablePath) } else { '' }
30
+ $isInstalledExecutable = (
31
+ $exe.Equals($root, [StringComparison]::OrdinalIgnoreCase) -or
32
+ $exe.StartsWith($rootPrefix, [StringComparison]::OrdinalIgnoreCase)
33
+ )
34
+ if ($isInstalledExecutable) {
35
+ return $true
36
+ }
37
+ $commandLine = [string]$_.CommandLine
38
+ if ($commandLine.IndexOf($root, [StringComparison]::OrdinalIgnoreCase) -lt 0) {
39
+ return $false
40
+ }
41
+ foreach ($marker in $markers) {
42
+ if ($commandLine.IndexOf($marker, [StringComparison]::OrdinalIgnoreCase) -ge 0) {
43
+ return $true
44
+ }
45
+ }
46
+ return $false
47
+ })
48
+ }
49
+
50
+ $processes = Get-TrillboardsEdgeProcesses
51
+ foreach ($process in $processes) {
52
+ Stop-Process -Id $process.ProcessId -ErrorAction SilentlyContinue
53
+ }
54
+
55
+ $deadline = [DateTime]::UtcNow.AddSeconds(5)
56
+ do {
57
+ Start-Sleep -Milliseconds 250
58
+ $remaining = Get-TrillboardsEdgeProcesses
59
+ } while ($remaining.Count -gt 0 -and [DateTime]::UtcNow -lt $deadline)
60
+
61
+ foreach ($process in $remaining) {
62
+ Stop-Process -Id $process.ProcessId -Force -ErrorAction SilentlyContinue
63
+ }
64
+
65
+ Start-Sleep -Milliseconds 250
66
+ $remaining = Get-TrillboardsEdgeProcesses
67
+ if ($remaining.Count -gt 0) {
68
+ $ids = ($remaining | ForEach-Object { $_.ProcessId }) -join ', '
69
+ throw "Could not stop the existing Trillboards connector processes: $ids"
70
+ }
71
+
72
+ Write-Host "Stopped existing Trillboards Edge processes."
@@ -0,0 +1,25 @@
1
+ Option Explicit
2
+
3
+ Dim fso, shell, scriptDir, installDir, supervisor
4
+ Dim mode, command, exitCode
5
+
6
+ Set fso = CreateObject("Scripting.FileSystemObject")
7
+ Set shell = CreateObject("WScript.Shell")
8
+
9
+ scriptDir = fso.GetParentFolderName(WScript.ScriptFullName)
10
+ installDir = fso.GetParentFolderName(scriptDir)
11
+ mode = "auto-sense"
12
+ If WScript.Arguments.Count > 0 Then mode = LCase(Trim(WScript.Arguments(0)))
13
+ If mode <> "auto-sense" And mode <> "local-sense" And mode <> "cloud-sense" Then
14
+ mode = "auto-sense"
15
+ End If
16
+
17
+ supervisor = fso.BuildPath(scriptDir, "run-connector.ps1")
18
+
19
+ ' PowerShell owns the child output stream so it can rotate the log while the
20
+ ' connector remains healthy. Window style 0 keeps the entire process tree hidden.
21
+ command = "powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File " _
22
+ & Chr(34) & supervisor & Chr(34) & " -InstallDir " & Chr(34) & installDir & Chr(34) _
23
+ & " -Mode " & Chr(34) & mode & Chr(34)
24
+ exitCode = shell.Run(command, 0, True)
25
+ WScript.Quit exitCode
@@ -0,0 +1,33 @@
1
+ @echo off
2
+ REM Trillboards Edge connector launcher (bundled runtime — no system Node/ffmpeg).
3
+ REM bin\ goes first on PATH so the connector's default ffmpeg='ffmpeg' resolves to
4
+ REM OUR bundled ffmpeg.exe; NODE_PATH points at the bundled runtime subset.
5
+ setlocal EnableExtensions EnableDelayedExpansion
6
+ set "BIN=%~dp0"
7
+ set "PATH=%BIN%;%PATH%"
8
+ set "NODE_PATH=%BIN%..\lib\node_modules"
9
+
10
+ REM The scheduled task exists only after installation and starts after pairing.
11
+ REM Repair or restart the installer-provisioned local AI service on login. In
12
+ REM required-bundle mode this never downloads a missing model from the network.
13
+ if /I "%~1"=="auto-sense" (
14
+ set "LITERT_ROOT=%LITERT_LM_DIR%"
15
+ if not defined LITERT_ROOT set "LITERT_ROOT=%USERPROFILE%\.litert-lm"
16
+ set "NEEDS_LOCAL_AI=0"
17
+ if not exist "!LITERT_ROOT!\models\gemma-4-E2B-it.litertlm\trillboards-bundled-version.json" set "NEEDS_LOCAL_AI=1"
18
+ schtasks /Query /TN "TrillboardsLiteRtLm" >nul 2>&1
19
+ if errorlevel 1 set "NEEDS_LOCAL_AI=1"
20
+ curl.exe --fail --silent --max-time 2 http://127.0.0.1:9379/v1/models >nul 2>&1
21
+ if errorlevel 1 set "NEEDS_LOCAL_AI=1"
22
+ if "!NEEDS_LOCAL_AI!"=="1" if exist "%BIN%setup-litertlm.ps1" (
23
+ set "SETUP_PROGRESS=%LOCALAPPDATA%\Trillboards\setup-progress.json"
24
+ if exist "!SETUP_PROGRESS!" (
25
+ start "" powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "%BIN%setup-litertlm.ps1" -RequireBundledModel -ProgressPath "!SETUP_PROGRESS!"
26
+ ) else (
27
+ start "" powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "%BIN%setup-litertlm.ps1" -RequireBundledModel
28
+ )
29
+ )
30
+ )
31
+
32
+ "%BIN%node.exe" "%BIN%..\lib\edge-sdk\dist\cli.js" %*
33
+ exit /b %ERRORLEVEL%