@firstpick/pi-package-webui 0.4.3 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/start-webui.ps1 DELETED
@@ -1,368 +0,0 @@
1
- $ErrorActionPreference = "Stop"
2
-
3
- $PackageName = "@firstpick/pi-package-webui"
4
- $DefaultHost = "127.0.0.1"
5
- $DefaultPort = "31415"
6
- $script:ServerProcess = $null
7
-
8
- function Write-Stderr {
9
- param([string]$Message)
10
- [Console]::Error.WriteLine($Message)
11
- }
12
-
13
- function Get-LaunchCwd {
14
- $cwd = $env:PI_WEBUI_CWD
15
-
16
- if ([string]::IsNullOrWhiteSpace($cwd) -or -not (Test-Path -LiteralPath $cwd -PathType Container)) {
17
- try {
18
- $cwd = (Get-Location).ProviderPath
19
- } catch {
20
- $cwd = $HOME
21
- }
22
- }
23
-
24
- if ([string]::IsNullOrWhiteSpace($cwd) -or -not (Test-Path -LiteralPath $cwd -PathType Container)) {
25
- $cwd = $HOME
26
- }
27
-
28
- if ([string]::IsNullOrWhiteSpace($cwd) -or -not (Test-Path -LiteralPath $cwd -PathType Container)) {
29
- throw "Could not determine a valid working directory."
30
- }
31
-
32
- return $cwd
33
- }
34
-
35
- function Get-PiManagedPiWebui {
36
- $node = Get-Command "node" -ErrorAction SilentlyContinue
37
- if (-not $node) {
38
- return $null
39
- }
40
-
41
- $script = @'
42
- const { homedir } = require("node:os");
43
- const { join } = require("node:path");
44
-
45
- let agentDir = process.env.PI_CODING_AGENT_DIR || join(homedir(), ".pi", "agent");
46
- if (agentDir === "~") {
47
- agentDir = homedir();
48
- } else if (agentDir.startsWith("~/") || (process.platform === "win32" && agentDir.startsWith("~\\"))) {
49
- agentDir = join(homedir(), agentDir.slice(2));
50
- }
51
-
52
- const binName = process.platform === "win32" ? "pi-webui.cmd" : "pi-webui";
53
- for (const candidate of [
54
- join(agentDir, "npm", "node_modules", ".bin", binName),
55
- join(agentDir, "npm", "node_modules", ".bin", "pi-webui"),
56
- ]) {
57
- process.stdout.write(`${candidate}\n`);
58
- }
59
- '@
60
-
61
- $candidates = @(& $node.Source -e $script 2>$null)
62
- if ($LASTEXITCODE -ne 0) {
63
- return $null
64
- }
65
-
66
- foreach ($candidate in $candidates) {
67
- if (-not [string]::IsNullOrWhiteSpace($candidate) -and (Test-Path -LiteralPath $candidate -PathType Leaf)) {
68
- return $candidate
69
- }
70
- }
71
-
72
- return $null
73
- }
74
-
75
- function Ensure-PiWebui {
76
- $managed = Get-PiManagedPiWebui
77
- if ($managed) {
78
- return $managed
79
- }
80
-
81
- $command = Get-Command "pi-webui" -ErrorAction SilentlyContinue
82
- if ($command) {
83
- return $command.Source
84
- }
85
-
86
- Write-Host "pi-webui is not installed or not available on PATH."
87
-
88
- $npm = Get-Command "npm" -ErrorAction SilentlyContinue
89
- if (-not $npm) {
90
- Write-Stderr "npm is required to install it globally. Install Node.js/npm, then run:`n npm install -g $PackageName"
91
- exit 1
92
- }
93
-
94
- if (-not [Environment]::UserInteractive) {
95
- Write-Stderr "Non-interactive shell; refusing to install without confirmation. Run manually:`n npm install -g $PackageName"
96
- exit 1
97
- }
98
-
99
- $answer = Read-Host "Install $PackageName globally now? [y/N]"
100
- if ($answer -match '^(?i:y|yes)$') {
101
- & $npm.Source install -g $PackageName
102
- if ($LASTEXITCODE -ne 0) {
103
- exit $LASTEXITCODE
104
- }
105
- } else {
106
- Write-Stderr "Aborted. Install later with:`n npm install -g $PackageName"
107
- exit 1
108
- }
109
-
110
- $command = Get-Command "pi-webui" -ErrorAction SilentlyContinue
111
- if (-not $command) {
112
- Write-Stderr "Installed, but pi-webui is still not on PATH. Check your npm global bin directory."
113
- exit 1
114
- }
115
-
116
- return $command.Source
117
- }
118
-
119
- function Get-BrowserHostForUrl {
120
- param([string]$HostName)
121
-
122
- if ([string]::IsNullOrWhiteSpace($HostName) -or $HostName -eq "0.0.0.0") {
123
- return "127.0.0.1"
124
- }
125
- if ($HostName -eq "::") {
126
- return "[::1]"
127
- }
128
- if ($HostName.StartsWith("[")) {
129
- return $HostName
130
- }
131
- if ($HostName.Contains(":")) {
132
- return "[$HostName]"
133
- }
134
- return $HostName
135
- }
136
-
137
- function Get-ConnectHostForPort {
138
- param([string]$HostName)
139
-
140
- if ([string]::IsNullOrWhiteSpace($HostName) -or $HostName -eq "0.0.0.0") {
141
- return "127.0.0.1"
142
- }
143
- if ($HostName -eq "::") {
144
- return "::1"
145
- }
146
- if ($HostName.StartsWith("[") -and $HostName.EndsWith("]")) {
147
- return $HostName.Substring(1, $HostName.Length - 2)
148
- }
149
- return $HostName
150
- }
151
-
152
- function Open-WebUrl {
153
- param([string]$Url)
154
-
155
- try {
156
- Start-Process $Url | Out-Null
157
- } catch {
158
- Write-Warning "Could not open the default browser. Open manually: $Url"
159
- }
160
- }
161
-
162
- function Test-HttpOk {
163
- param([string]$Url)
164
-
165
- try {
166
- $response = Invoke-WebRequest -Uri $Url -UseBasicParsing -TimeoutSec 2 -Method Get
167
- return ([int]$response.StatusCode -ge 200 -and [int]$response.StatusCode -lt 400)
168
- } catch {
169
- return $false
170
- }
171
- }
172
-
173
- function Test-WebuiRunning {
174
- param([string]$Url)
175
-
176
- $baseUrl = $Url.TrimEnd("/")
177
- return (Test-HttpOk "${baseUrl}/api/webui-status") -or (Test-HttpOk "${baseUrl}/api/webui-status?detailed=1")
178
- }
179
-
180
- function Normalize-CwdComparable {
181
- param([string]$Path)
182
-
183
- $text = ([string]$Path) -replace '\\', '/'
184
- if ($text -match '^/[A-Za-z]/') {
185
- $text = "$($text[1]):$($text.Substring(2))"
186
- }
187
-
188
- if ($env:OS -eq "Windows_NT") {
189
- return $text.ToLowerInvariant()
190
- }
191
- return $text
192
- }
193
-
194
- function Get-WebuiUrlForCwd {
195
- param(
196
- [string]$Url,
197
- [string]$Cwd
198
- )
199
-
200
- $baseUrl = $Url.TrimEnd("/")
201
- $targetCwd = Normalize-CwdComparable $Cwd
202
-
203
- try {
204
- $tabsResponse = Invoke-RestMethod -Uri "${baseUrl}/api/tabs" -UseBasicParsing -TimeoutSec 5 -Method Get
205
- $tab = @($tabsResponse.data.tabs) | Where-Object { (Normalize-CwdComparable $_.cwd) -eq $targetCwd } | Select-Object -First 1
206
- if ($tab -and $tab.id) {
207
- return "$baseUrl/?tab=$([System.Uri]::EscapeDataString([string]$tab.id))"
208
- }
209
- } catch {
210
- # Fall back to creating a tab below, then to the root URL.
211
- }
212
-
213
- try {
214
- $body = @{ cwd = $Cwd } | ConvertTo-Json -Compress
215
- $created = Invoke-RestMethod -Uri "${baseUrl}/api/tabs" -UseBasicParsing -TimeoutSec 10 -Method Post -ContentType "application/json" -Body $body
216
- $id = $created.data.tab.id
217
- if ($id) {
218
- return "$baseUrl/?tab=$([System.Uri]::EscapeDataString([string]$id))"
219
- }
220
- } catch {
221
- # Fall back to the root URL.
222
- }
223
-
224
- return "$baseUrl/"
225
- }
226
-
227
- function Test-PortInUse {
228
- param(
229
- [string]$HostName,
230
- [string]$Port
231
- )
232
-
233
- $portNumber = 0
234
- if (-not [int]::TryParse($Port, [ref]$portNumber)) {
235
- return $false
236
- }
237
-
238
- $connectHost = Get-ConnectHostForPort $HostName
239
- $client = $null
240
- $async = $null
241
-
242
- try {
243
- $client = [System.Net.Sockets.TcpClient]::new()
244
- $async = $client.BeginConnect($connectHost, $portNumber, $null, $null)
245
- if (-not $async.AsyncWaitHandle.WaitOne(500, $false)) {
246
- return $false
247
- }
248
- $client.EndConnect($async)
249
- return $client.Connected
250
- } catch {
251
- return $false
252
- } finally {
253
- if ($async -and $async.AsyncWaitHandle) {
254
- $async.AsyncWaitHandle.Close()
255
- }
256
- if ($client) {
257
- $client.Close()
258
- }
259
- }
260
- }
261
-
262
- function Wait-UntilReady {
263
- param(
264
- [string]$Url,
265
- [System.Diagnostics.Process]$Process
266
- )
267
-
268
- for ($i = 0; $i -lt 50; $i++) {
269
- $Process.Refresh()
270
- if ($Process.HasExited) {
271
- return 2
272
- }
273
-
274
- if (Test-HttpOk $Url) {
275
- return 0
276
- }
277
-
278
- Start-Sleep -Milliseconds 200
279
- }
280
-
281
- return 1
282
- }
283
-
284
- function Stop-ServerProcess {
285
- if ($script:ServerProcess) {
286
- $script:ServerProcess.Refresh()
287
- if (-not $script:ServerProcess.HasExited) {
288
- try {
289
- Stop-Process -Id $script:ServerProcess.Id -Force -ErrorAction SilentlyContinue
290
- } catch {
291
- # Best-effort cleanup only.
292
- }
293
- }
294
- }
295
- }
296
-
297
- $cwd = Get-LaunchCwd
298
- $hostName = if ([string]::IsNullOrWhiteSpace($env:PI_WEBUI_HOST)) { $DefaultHost } else { $env:PI_WEBUI_HOST }
299
- $port = if ([string]::IsNullOrWhiteSpace($env:PI_WEBUI_PORT)) { $DefaultPort } else { $env:PI_WEBUI_PORT }
300
- $passThroughArgs = @($args)
301
-
302
- for ($i = 0; $i -lt $passThroughArgs.Count; $i++) {
303
- if ($passThroughArgs[$i] -eq "--") {
304
- break
305
- }
306
-
307
- switch ($passThroughArgs[$i]) {
308
- "--cwd" {
309
- if ($i + 1 -lt $passThroughArgs.Count) { $cwd = $passThroughArgs[$i + 1] }
310
- }
311
- "--host" {
312
- if ($i + 1 -lt $passThroughArgs.Count) { $hostName = $passThroughArgs[$i + 1] }
313
- }
314
- "--port" {
315
- if ($i + 1 -lt $passThroughArgs.Count) { $port = $passThroughArgs[$i + 1] }
316
- }
317
- }
318
- }
319
-
320
- $browserHost = Get-BrowserHostForUrl $hostName
321
- $connectHost = Get-ConnectHostForPort $hostName
322
- $url = "http://${browserHost}:${port}/"
323
-
324
- if (Test-WebuiRunning $url) {
325
- $targetUrl = Get-WebuiUrlForCwd $url $cwd
326
- Write-Host "Pi Web UI already appears to be running at: $url"
327
- Write-Host "Opening: $targetUrl"
328
- Open-WebUrl $targetUrl
329
- exit 0
330
- }
331
-
332
- if (Test-PortInUse $hostName $port) {
333
- Write-Stderr "Port $port is already in use on $connectHost; not starting Pi Web UI."
334
- if (Test-HttpOk $url) {
335
- Write-Stderr "An HTTP server responded at $url, but it did not expose Pi Web UI status."
336
- } else {
337
- Write-Stderr "No Pi Web UI status endpoint responded at $url."
338
- }
339
- exit 1
340
- }
341
-
342
- $piWebuiCommand = Ensure-PiWebui
343
- $webuiArgs = @("--cwd", $cwd, "--host", $hostName, "--port", [string]$port) + $passThroughArgs
344
-
345
- Write-Host "Starting Pi Web UI in: $cwd"
346
- Write-Host "Web UI URL: $url"
347
-
348
- try {
349
- $script:ServerProcess = Start-Process -FilePath $piWebuiCommand -ArgumentList $webuiArgs -NoNewWindow -PassThru
350
- $readyStatus = Wait-UntilReady $url $script:ServerProcess
351
-
352
- if ($readyStatus -eq 0) {
353
- Open-WebUrl $url
354
- } elseif ($readyStatus -eq 2) {
355
- Write-Stderr "Pi Web UI exited before it became ready."
356
- $script:ServerProcess.Refresh()
357
- exit $script:ServerProcess.ExitCode
358
- } else {
359
- Write-Warning "Server did not respond yet; opening the URL anyway."
360
- Open-WebUrl $url
361
- }
362
-
363
- Wait-Process -Id $script:ServerProcess.Id
364
- $script:ServerProcess.Refresh()
365
- exit $script:ServerProcess.ExitCode
366
- } finally {
367
- Stop-ServerProcess
368
- }