@vincenthanxiaodu/pi-web 1.202608.1

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 (376) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +185 -0
  3. package/dist/cli.js +1149 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/client/apple-touch-icon.png +0 -0
  6. package/dist/client/assets/CodeViewer-D_Q6ljLV.js +4 -0
  7. package/dist/client/assets/TerminalPanel-CTGixJIz.js +187 -0
  8. package/dist/client/assets/index-C2phil_t.js +5637 -0
  9. package/dist/client/assets/vendor-editor-core-CXO8gGab.js +12 -0
  10. package/dist/client/assets/vendor-editor-languages-CpW4sJsX.js +46 -0
  11. package/dist/client/assets/vendor-editor-legacy-CYBnW6ZU.js +1 -0
  12. package/dist/client/assets/vendor-terminal-BrP-ENHg.css +1 -0
  13. package/dist/client/assets/vendor-terminal-D8k4UKM2.js +35 -0
  14. package/dist/client/favicon.svg +11 -0
  15. package/dist/client/index.html +124 -0
  16. package/dist/client/manifest.webmanifest +24 -0
  17. package/dist/client/pwa-icon-192.png +0 -0
  18. package/dist/client/pwa-icon-512.png +0 -0
  19. package/dist/config.js +505 -0
  20. package/dist/config.js.map +1 -0
  21. package/dist/docker/piWebDockerCommandPlan.js +212 -0
  22. package/dist/docker/piWebDockerCommandPlan.js.map +1 -0
  23. package/dist/environment.js +4 -0
  24. package/dist/environment.js.map +1 -0
  25. package/dist/nativeServices/installedServiceDefinitions.js +602 -0
  26. package/dist/nativeServices/installedServiceDefinitions.js.map +1 -0
  27. package/dist/nativeServices/serviceAction.js +174 -0
  28. package/dist/nativeServices/serviceAction.js.map +1 -0
  29. package/dist/nativeServices/serviceDoctor.js +783 -0
  30. package/dist/nativeServices/serviceDoctor.js.map +1 -0
  31. package/dist/nativeServices/serviceInstall.js +35 -0
  32. package/dist/nativeServices/serviceInstall.js.map +1 -0
  33. package/dist/nativeServices/servicePlan.js +358 -0
  34. package/dist/nativeServices/servicePlan.js.map +1 -0
  35. package/dist/nativeServices/serviceProbe.js +447 -0
  36. package/dist/nativeServices/serviceProbe.js.map +1 -0
  37. package/dist/nativeServices/serviceRendering.js +132 -0
  38. package/dist/nativeServices/serviceRendering.js.map +1 -0
  39. package/dist/pi-web-plugins/git/browser/git-contract.js +108 -0
  40. package/dist/pi-web-plugins/git/browser/git-panel.js +609 -0
  41. package/dist/pi-web-plugins/git/browser/gitFileList.js +43 -0
  42. package/dist/pi-web-plugins/git/browser/gitFileShared.js +16 -0
  43. package/dist/pi-web-plugins/git/browser/gitFileTree.js +81 -0
  44. package/dist/pi-web-plugins/git/browser/gitFileViewPreference.js +35 -0
  45. package/dist/pi-web-plugins/git/browser/gitRoute.js +42 -0
  46. package/dist/pi-web-plugins/git/browser/pi-web-plugin.js +10 -0
  47. package/dist/pi-web-plugins/git/browser/unifiedDiff.js +301 -0
  48. package/dist/pi-web-plugins/git/git-backend.js +439 -0
  49. package/dist/pi-web-plugins/git/package.json +16 -0
  50. package/dist/pi-web-plugins/git/server-plugin.js +176 -0
  51. package/dist/pi-web-plugins/info/infoInternals.js +222 -0
  52. package/dist/pi-web-plugins/info/package.json +9 -0
  53. package/dist/pi-web-plugins/info/pi-web-plugin.js +48 -0
  54. package/dist/pi-web-plugins/relays/markdownDocument.js +81 -0
  55. package/dist/pi-web-plugins/relays/package.json +9 -0
  56. package/dist/pi-web-plugins/relays/pi-web-plugin.js +44 -0
  57. package/dist/pi-web-plugins/relays/relayDiscovery.js +228 -0
  58. package/dist/pi-web-plugins/relays/relaysPanelElement.js +550 -0
  59. package/dist/pi-web-plugins/relays/vendor/README.md +24 -0
  60. package/dist/pi-web-plugins/relays/vendor/marked.esm.js +76 -0
  61. package/dist/pi-web-plugins/updates/package.json +9 -0
  62. package/dist/pi-web-plugins/updates/pi-web-plugin.js +185 -0
  63. package/dist/pi-web-plugins/updates/updatesLogic.js +97 -0
  64. package/dist/pi-web-plugins/workspace-tasks/config.js +91 -0
  65. package/dist/pi-web-plugins/workspace-tasks/package.json +9 -0
  66. package/dist/pi-web-plugins/workspace-tasks/pi-web-plugin.js +48 -0
  67. package/dist/pi-web-plugins/workspace-tasks/taskRunner.js +12 -0
  68. package/dist/pi-web-plugins/workspace-tasks/tasksPanelElement.js +288 -0
  69. package/dist/pi-web-plugins/workspace-tasks/workspaceTasksClient.js +47 -0
  70. package/dist/piWebVersionReport.js +278 -0
  71. package/dist/piWebVersionReport.js.map +1 -0
  72. package/dist/plugin-api.d.ts +222 -0
  73. package/dist/pluginRecoveryCli.js +172 -0
  74. package/dist/pluginRecoveryCli.js.map +1 -0
  75. package/dist/server/activeAgentProfileProvider.js +25 -0
  76. package/dist/server/activeAgentProfileProvider.js.map +1 -0
  77. package/dist/server/activity/workspaceActivityService.js +106 -0
  78. package/dist/server/activity/workspaceActivityService.js.map +1 -0
  79. package/dist/server/app.js +218 -0
  80. package/dist/server/app.js.map +1 -0
  81. package/dist/server/browserMessageProjection.js +54 -0
  82. package/dist/server/browserMessageProjection.js.map +1 -0
  83. package/dist/server/configRoutes.js +293 -0
  84. package/dist/server/configRoutes.js.map +1 -0
  85. package/dist/server/diagnostics/nodePtyNativeModule.js +36 -0
  86. package/dist/server/diagnostics/nodePtyNativeModule.js.map +1 -0
  87. package/dist/server/diagnostics/nodePtySpawnHelper.js +135 -0
  88. package/dist/server/diagnostics/nodePtySpawnHelper.js.map +1 -0
  89. package/dist/server/git/gitEnv.js +15 -0
  90. package/dist/server/git/gitEnv.js.map +1 -0
  91. package/dist/server/goals/goalArchive.js +184 -0
  92. package/dist/server/goals/goalArchive.js.map +1 -0
  93. package/dist/server/goals/goalFile.js +200 -0
  94. package/dist/server/goals/goalFile.js.map +1 -0
  95. package/dist/server/goals/goalStore.js +65 -0
  96. package/dist/server/goals/goalStore.js.map +1 -0
  97. package/dist/server/index.js +7 -0
  98. package/dist/server/index.js.map +1 -0
  99. package/dist/server/machines/machineClient.js +191 -0
  100. package/dist/server/machines/machineClient.js.map +1 -0
  101. package/dist/server/machines/machinePluginProxyRoutes.js +247 -0
  102. package/dist/server/machines/machinePluginProxyRoutes.js.map +1 -0
  103. package/dist/server/machines/machineProxyRoutes.js +318 -0
  104. package/dist/server/machines/machineProxyRoutes.js.map +1 -0
  105. package/dist/server/machines/machineRoutes.js +56 -0
  106. package/dist/server/machines/machineRoutes.js.map +1 -0
  107. package/dist/server/machines/machineService.js +274 -0
  108. package/dist/server/machines/machineService.js.map +1 -0
  109. package/dist/server/machines/machineStore.js +143 -0
  110. package/dist/server/machines/machineStore.js.map +1 -0
  111. package/dist/server/piPackageRoutes.js +91 -0
  112. package/dist/server/piPackageRoutes.js.map +1 -0
  113. package/dist/server/piPackageService.js +101 -0
  114. package/dist/server/piPackageService.js.map +1 -0
  115. package/dist/server/piWebPluginCatalog.js +584 -0
  116. package/dist/server/piWebPluginCatalog.js.map +1 -0
  117. package/dist/server/piWebPluginLifecycle.js +162 -0
  118. package/dist/server/piWebPluginLifecycle.js.map +1 -0
  119. package/dist/server/piWebPluginService.js +224 -0
  120. package/dist/server/piWebPluginService.js.map +1 -0
  121. package/dist/server/piWebReleaseLookupCache.js +37 -0
  122. package/dist/server/piWebReleaseLookupCache.js.map +1 -0
  123. package/dist/server/piWebStatus.js +624 -0
  124. package/dist/server/piWebStatus.js.map +1 -0
  125. package/dist/server/piWebStatusCache.js +45 -0
  126. package/dist/server/piWebStatusCache.js.map +1 -0
  127. package/dist/server/plugins/pluginBackendProxyRoutes.js +69 -0
  128. package/dist/server/plugins/pluginBackendProxyRoutes.js.map +1 -0
  129. package/dist/server/plugins/serverPluginExec.js +184 -0
  130. package/dist/server/plugins/serverPluginExec.js.map +1 -0
  131. package/dist/server/plugins/serverPluginRuntime.js +480 -0
  132. package/dist/server/plugins/serverPluginRuntime.js.map +1 -0
  133. package/dist/server/projectTrustRoutes.js +80 -0
  134. package/dist/server/projectTrustRoutes.js.map +1 -0
  135. package/dist/server/projects/directorySuggestions.js +207 -0
  136. package/dist/server/projects/directorySuggestions.js.map +1 -0
  137. package/dist/server/projects/projectService.js +33 -0
  138. package/dist/server/projects/projectService.js.map +1 -0
  139. package/dist/server/realtime/sessionEventHub.js +93 -0
  140. package/dist/server/realtime/sessionEventHub.js.map +1 -0
  141. package/dist/server/requestCancellation.js +32 -0
  142. package/dist/server/requestCancellation.js.map +1 -0
  143. package/dist/server/sessiond/agentHttpDispatcher.js +108 -0
  144. package/dist/server/sessiond/agentHttpDispatcher.js.map +1 -0
  145. package/dist/server/sessiond/agentProcessEnvironment.js +61 -0
  146. package/dist/server/sessiond/agentProcessEnvironment.js.map +1 -0
  147. package/dist/server/sessiond/pluginBackendRoutes.js +64 -0
  148. package/dist/server/sessiond/pluginBackendRoutes.js.map +1 -0
  149. package/dist/server/sessiond/sessionDaemonShutdown.js +31 -0
  150. package/dist/server/sessiond/sessionDaemonShutdown.js.map +1 -0
  151. package/dist/server/sessiond/sessionProxyRoutes.js +61 -0
  152. package/dist/server/sessiond/sessionProxyRoutes.js.map +1 -0
  153. package/dist/server/sessiond/sessionServiceDependencies.js +34 -0
  154. package/dist/server/sessiond/sessionServiceDependencies.js.map +1 -0
  155. package/dist/server/sessiond/sessiondStateOwnership.js +235 -0
  156. package/dist/server/sessiond/sessiondStateOwnership.js.map +1 -0
  157. package/dist/server/sessiond/workspaceCatalogRoutes.js +31 -0
  158. package/dist/server/sessiond/workspaceCatalogRoutes.js.map +1 -0
  159. package/dist/server/sessiond/workspaceRemovalRoutes.js +41 -0
  160. package/dist/server/sessiond/workspaceRemovalRoutes.js.map +1 -0
  161. package/dist/server/sessiond.js +387 -0
  162. package/dist/server/sessiond.js.map +1 -0
  163. package/dist/server/sessions/askUserTool.js +102 -0
  164. package/dist/server/sessions/askUserTool.js.map +1 -0
  165. package/dist/server/sessions/attachmentService.js +119 -0
  166. package/dist/server/sessions/attachmentService.js.map +1 -0
  167. package/dist/server/sessions/authProviderOptions.js +50 -0
  168. package/dist/server/sessions/authProviderOptions.js.map +1 -0
  169. package/dist/server/sessions/authRoutes.js +59 -0
  170. package/dist/server/sessions/authRoutes.js.map +1 -0
  171. package/dist/server/sessions/authService.js +122 -0
  172. package/dist/server/sessions/authService.js.map +1 -0
  173. package/dist/server/sessions/builtinCommands.js +27 -0
  174. package/dist/server/sessions/builtinCommands.js.map +1 -0
  175. package/dist/server/sessions/dockerEnvironmentFacts.js +254 -0
  176. package/dist/server/sessions/dockerEnvironmentFacts.js.map +1 -0
  177. package/dist/server/sessions/editPreview.js +196 -0
  178. package/dist/server/sessions/editPreview.js.map +1 -0
  179. package/dist/server/sessions/extensionDialogWaiters.js +76 -0
  180. package/dist/server/sessions/extensionDialogWaiters.js.map +1 -0
  181. package/dist/server/sessions/globalProviderPolicy.js +167 -0
  182. package/dist/server/sessions/globalProviderPolicy.js.map +1 -0
  183. package/dist/server/sessions/interruptedRunStore.js +147 -0
  184. package/dist/server/sessions/interruptedRunStore.js.map +1 -0
  185. package/dist/server/sessions/messagePaging.js +43 -0
  186. package/dist/server/sessions/messagePaging.js.map +1 -0
  187. package/dist/server/sessions/modelCatalogRefresher.js +193 -0
  188. package/dist/server/sessions/modelCatalogRefresher.js.map +1 -0
  189. package/dist/server/sessions/oauthLoginFlowService.js +346 -0
  190. package/dist/server/sessions/oauthLoginFlowService.js.map +1 -0
  191. package/dist/server/sessions/pendingAskStore.js +275 -0
  192. package/dist/server/sessions/pendingAskStore.js.map +1 -0
  193. package/dist/server/sessions/pendingExtensionDialogStore.js +196 -0
  194. package/dist/server/sessions/pendingExtensionDialogStore.js.map +1 -0
  195. package/dist/server/sessions/piSessionManagerGateway.js +246 -0
  196. package/dist/server/sessions/piSessionManagerGateway.js.map +1 -0
  197. package/dist/server/sessions/piSessionService.js +4059 -0
  198. package/dist/server/sessions/piSessionService.js.map +1 -0
  199. package/dist/server/sessions/plainTextTheme.js +112 -0
  200. package/dist/server/sessions/plainTextTheme.js.map +1 -0
  201. package/dist/server/sessions/sessionArchiveStore.js +273 -0
  202. package/dist/server/sessions/sessionArchiveStore.js.map +1 -0
  203. package/dist/server/sessions/sessionArchiveTree.js +35 -0
  204. package/dist/server/sessions/sessionArchiveTree.js.map +1 -0
  205. package/dist/server/sessions/sessionCleanup.js +157 -0
  206. package/dist/server/sessions/sessionCleanup.js.map +1 -0
  207. package/dist/server/sessions/sessionCommandService.js +336 -0
  208. package/dist/server/sessions/sessionCommandService.js.map +1 -0
  209. package/dist/server/sessions/sessionEnvironmentFacts.js +48 -0
  210. package/dist/server/sessions/sessionEnvironmentFacts.js.map +1 -0
  211. package/dist/server/sessions/sessionFileFormat.js +27 -0
  212. package/dist/server/sessions/sessionFileFormat.js.map +1 -0
  213. package/dist/server/sessions/sessionFileHeader.js +104 -0
  214. package/dist/server/sessions/sessionFileHeader.js.map +1 -0
  215. package/dist/server/sessions/sessionModelScope.js +141 -0
  216. package/dist/server/sessions/sessionModelScope.js.map +1 -0
  217. package/dist/server/sessions/sessionNameGenerator.js +90 -0
  218. package/dist/server/sessions/sessionNameGenerator.js.map +1 -0
  219. package/dist/server/sessions/sessionNotificationStore.js +484 -0
  220. package/dist/server/sessions/sessionNotificationStore.js.map +1 -0
  221. package/dist/server/sessions/sessionRoutes.js +744 -0
  222. package/dist/server/sessions/sessionRoutes.js.map +1 -0
  223. package/dist/server/sessions/sessionRuntimeStore.js +2 -0
  224. package/dist/server/sessions/sessionRuntimeStore.js.map +1 -0
  225. package/dist/server/sessions/sessionService.js +2 -0
  226. package/dist/server/sessions/sessionService.js.map +1 -0
  227. package/dist/server/sessions/sessionSummaryScanner.js +491 -0
  228. package/dist/server/sessions/sessionSummaryScanner.js.map +1 -0
  229. package/dist/server/sessions/sessionTreeProjection.js +250 -0
  230. package/dist/server/sessions/sessionTreeProjection.js.map +1 -0
  231. package/dist/server/sessions/sessionUnreadStore.js +520 -0
  232. package/dist/server/sessions/sessionUnreadStore.js.map +1 -0
  233. package/dist/server/sessions/shutdownDrain.js +73 -0
  234. package/dist/server/sessions/shutdownDrain.js.map +1 -0
  235. package/dist/server/sessions/spawnSessionTool.js +48 -0
  236. package/dist/server/sessions/spawnSessionTool.js.map +1 -0
  237. package/dist/server/sessions/spawnSubsessionTool.js +216 -0
  238. package/dist/server/sessions/spawnSubsessionTool.js.map +1 -0
  239. package/dist/server/sessions/spawnTargetResolver.js +23 -0
  240. package/dist/server/sessions/spawnTargetResolver.js.map +1 -0
  241. package/dist/server/sessions/subsessionTranscript.js +235 -0
  242. package/dist/server/sessions/subsessionTranscript.js.map +1 -0
  243. package/dist/server/status/machineStatusRoutes.js +8 -0
  244. package/dist/server/status/machineStatusRoutes.js.map +1 -0
  245. package/dist/server/status/machineStatusService.js +181 -0
  246. package/dist/server/status/machineStatusService.js.map +1 -0
  247. package/dist/server/status/workspaceAttribution.js +87 -0
  248. package/dist/server/status/workspaceAttribution.js.map +1 -0
  249. package/dist/server/storage/projectStore.js +88 -0
  250. package/dist/server/storage/projectStore.js.map +1 -0
  251. package/dist/server/terminalProxyRoutes.js +141 -0
  252. package/dist/server/terminalProxyRoutes.js.map +1 -0
  253. package/dist/server/terminals/terminalRoutes.js +155 -0
  254. package/dist/server/terminals/terminalRoutes.js.map +1 -0
  255. package/dist/server/terminals/terminalService.js +305 -0
  256. package/dist/server/terminals/terminalService.js.map +1 -0
  257. package/dist/server/terminals/terminalSize.js +17 -0
  258. package/dist/server/terminals/terminalSize.js.map +1 -0
  259. package/dist/server/types.js +2 -0
  260. package/dist/server/types.js.map +1 -0
  261. package/dist/server/updates/fleetRoutes.js +111 -0
  262. package/dist/server/updates/fleetRoutes.js.map +1 -0
  263. package/dist/server/updates/restartRoutes.js +48 -0
  264. package/dist/server/updates/restartRoutes.js.map +1 -0
  265. package/dist/server/updates/selfUpdateRoutes.js +145 -0
  266. package/dist/server/updates/selfUpdateRoutes.js.map +1 -0
  267. package/dist/server/webSocketBridge.js +32 -0
  268. package/dist/server/webSocketBridge.js.map +1 -0
  269. package/dist/server/workingDirectory.js +44 -0
  270. package/dist/server/workingDirectory.js.map +1 -0
  271. package/dist/server/workspaceExplorerRoutes.js +147 -0
  272. package/dist/server/workspaceExplorerRoutes.js.map +1 -0
  273. package/dist/server/workspaces/effectivePathAccess.js +8 -0
  274. package/dist/server/workspaces/effectivePathAccess.js.map +1 -0
  275. package/dist/server/workspaces/fileContentService.js +190 -0
  276. package/dist/server/workspaces/fileContentService.js.map +1 -0
  277. package/dist/server/workspaces/filePreviewResponseHeaders.js +18 -0
  278. package/dist/server/workspaces/filePreviewResponseHeaders.js.map +1 -0
  279. package/dist/server/workspaces/filePreviewResponsePolicy.js +55 -0
  280. package/dist/server/workspaces/filePreviewResponsePolicy.js.map +1 -0
  281. package/dist/server/workspaces/filePreviewService.js +81 -0
  282. package/dist/server/workspaces/filePreviewService.js.map +1 -0
  283. package/dist/server/workspaces/fileSuggestions.js +520 -0
  284. package/dist/server/workspaces/fileSuggestions.js.map +1 -0
  285. package/dist/server/workspaces/fileTreeService.js +35 -0
  286. package/dist/server/workspaces/fileTreeService.js.map +1 -0
  287. package/dist/server/workspaces/pathAccessPolicy.js +90 -0
  288. package/dist/server/workspaces/pathAccessPolicy.js.map +1 -0
  289. package/dist/server/workspaces/pathSafety.js +45 -0
  290. package/dist/server/workspaces/pathSafety.js.map +1 -0
  291. package/dist/server/workspaces/projectPiWebConfig.js +61 -0
  292. package/dist/server/workspaces/projectPiWebConfig.js.map +1 -0
  293. package/dist/server/workspaces/projectWorkspaceCwds.js +17 -0
  294. package/dist/server/workspaces/projectWorkspaceCwds.js.map +1 -0
  295. package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js +402 -0
  296. package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js.map +1 -0
  297. package/dist/server/workspaces/workspaceCatalog.js +47 -0
  298. package/dist/server/workspaces/workspaceCatalog.js.map +1 -0
  299. package/dist/server/workspaces/workspaceContext.js +6 -0
  300. package/dist/server/workspaces/workspaceContext.js.map +1 -0
  301. package/dist/server/workspaces/workspaceDeletionRoutes.js +49 -0
  302. package/dist/server/workspaces/workspaceDeletionRoutes.js.map +1 -0
  303. package/dist/server/workspaces/workspaceProviderRegistry.js +651 -0
  304. package/dist/server/workspaces/workspaceProviderRegistry.js.map +1 -0
  305. package/dist/server/workspaces/workspaceRemovalService.js +256 -0
  306. package/dist/server/workspaces/workspaceRemovalService.js.map +1 -0
  307. package/dist/server/workspaces/workspaceRouteErrors.js +7 -0
  308. package/dist/server/workspaces/workspaceRouteErrors.js.map +1 -0
  309. package/dist/server/workspaces/worktreePreRemoveHook.js +39 -0
  310. package/dist/server/workspaces/worktreePreRemoveHook.js.map +1 -0
  311. package/dist/server-plugin-api.d.ts +140 -0
  312. package/dist/server-plugin-api.js +2 -0
  313. package/dist/server-plugin-api.js.map +1 -0
  314. package/dist/serverPluginRecovery.js +138 -0
  315. package/dist/serverPluginRecovery.js.map +1 -0
  316. package/dist/sessiond/activeAgentProfile.js +12 -0
  317. package/dist/sessiond/activeAgentProfile.js.map +1 -0
  318. package/dist/sessiond/config.js +20 -0
  319. package/dist/sessiond/config.js.map +1 -0
  320. package/dist/sessiond/sessionDaemonClient.js +105 -0
  321. package/dist/sessiond/sessionDaemonClient.js.map +1 -0
  322. package/dist/shared/activeAgentProfile.js +37 -0
  323. package/dist/shared/activeAgentProfile.js.map +1 -0
  324. package/dist/shared/activity.js +16 -0
  325. package/dist/shared/activity.js.map +1 -0
  326. package/dist/shared/apiTypes.js +41 -0
  327. package/dist/shared/apiTypes.js.map +1 -0
  328. package/dist/shared/capabilities.js +37 -0
  329. package/dist/shared/capabilities.js.map +1 -0
  330. package/dist/shared/federatedRoutes.js +127 -0
  331. package/dist/shared/federatedRoutes.js.map +1 -0
  332. package/dist/shared/machinePluginIds.js +43 -0
  333. package/dist/shared/machinePluginIds.js.map +1 -0
  334. package/dist/shared/machineStatus.js +94 -0
  335. package/dist/shared/machineStatus.js.map +1 -0
  336. package/dist/shared/piWebStatusParsing.js +141 -0
  337. package/dist/shared/piWebStatusParsing.js.map +1 -0
  338. package/dist/shared/pluginApiTypes.d.ts +150 -0
  339. package/dist/shared/pluginApiTypes.js +3 -0
  340. package/dist/shared/pluginApiTypes.js.map +1 -0
  341. package/dist/shared/pluginBackendProtocol.js +110 -0
  342. package/dist/shared/pluginBackendProtocol.js.map +1 -0
  343. package/dist/shared/pluginIds.js +10 -0
  344. package/dist/shared/pluginIds.js.map +1 -0
  345. package/dist/shared/pluginRecoveryCommands.js +14 -0
  346. package/dist/shared/pluginRecoveryCommands.js.map +1 -0
  347. package/dist/shared/promptAttachments.js +92 -0
  348. package/dist/shared/promptAttachments.js.map +1 -0
  349. package/dist/shared/selfUpdate.js +3 -0
  350. package/dist/shared/selfUpdate.js.map +1 -0
  351. package/dist/shared/thinkingLevels.js +31 -0
  352. package/dist/shared/thinkingLevels.js.map +1 -0
  353. package/dist/shared/workspaceDeletion.js +12 -0
  354. package/dist/shared/workspaceDeletion.js.map +1 -0
  355. package/dist/shared/workspaceFiles.js +42 -0
  356. package/dist/shared/workspaceFiles.js.map +1 -0
  357. package/dist/shared/workspaceRemovalProtocol.js +24 -0
  358. package/dist/shared/workspaceRemovalProtocol.js.map +1 -0
  359. package/docs/assets/favicon.svg +11 -0
  360. package/docs/assets/pi-web-banner.png +0 -0
  361. package/docs/assets/pi-web-desktop.png +0 -0
  362. package/docs/assets/pi-web-mobile.png +0 -0
  363. package/docs/assets/pi-web-tablet.png +0 -0
  364. package/docs/config.md +444 -0
  365. package/docs/plugins.md +1305 -0
  366. package/examples/workspace-provider-plugin/README.md +52 -0
  367. package/examples/workspace-provider-plugin/package.json +25 -0
  368. package/examples/workspace-provider-plugin/src/browser/index.ts +69 -0
  369. package/examples/workspace-provider-plugin/src/server.ts +64 -0
  370. package/examples/workspace-provider-plugin/tsconfig.json +21 -0
  371. package/extensions/README-pi-web-restart-migration.md +45 -0
  372. package/extensions/pi-web.ts +324 -0
  373. package/install.sh +5 -0
  374. package/package.json +173 -0
  375. package/plugin-api.d.ts +1 -0
  376. package/server-plugin-api.d.ts +1 -0
package/dist/cli.js ADDED
@@ -0,0 +1,1149 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+ import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
4
+ import { mkdir, rm, writeFile } from "node:fs/promises";
5
+ import { homedir, userInfo } from "node:os";
6
+ import { basename, dirname, join, resolve } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { defaultPiWebConfigPath, defaultPiWebDataDir, examplePiWebConfig } from "./config.js";
9
+ import { piWebDockerCommand } from "./docker/piWebDockerCommandPlan.js";
10
+ import { ownEnvironmentValue } from "./environment.js";
11
+ import { runPluginRecoveryCli } from "./pluginRecoveryCli.js";
12
+ import { packageVersion, printPiWebVersionReport, probeRunningComponentReady, runningComponentsReady, } from "./piWebVersionReport.js";
13
+ import { checkNodePtyDarwinSpawnHelper, formatNodePtyDarwinSpawnHelperCheck } from "./server/diagnostics/nodePtySpawnHelper.js";
14
+ import { checkNodePtyNativeModule, formatNodePtyNativeModuleCheck } from "./server/diagnostics/nodePtyNativeModule.js";
15
+ import { installNativeServiceCandidate, nativeServiceInstallFailureNeedsPathAdvice, } from "./nativeServices/serviceInstall.js";
16
+ import { launchdBootoutArgs, launchdServiceTarget, orderServices, performServiceAction, serviceStartOrder, serviceStopOrder, settleLaunchdServiceUnload, startLaunchdService, } from "./nativeServices/serviceAction.js";
17
+ import { minimumSupportedNodeVersion, nativeServiceManagerRefs, productionNativeServiceIds, } from "./nativeServices/servicePlan.js";
18
+ import { inspectInstalledNativeServiceDefinitions, } from "./nativeServices/installedServiceDefinitions.js";
19
+ import { formatNativeServiceDoctorResult, inferInstalledNativeServiceMode, inspectInstalledDevelopmentServiceInput, inspectInstalledProductionServiceContext, runNativeServiceDoctor, selectManagedNativeServiceConfig, } from "./nativeServices/serviceDoctor.js";
20
+ import { createNativeServiceAuthoritativeProbe, nativeServicePrerequisiteShellCheck, } from "./nativeServices/serviceProbe.js";
21
+ import { renderLaunchdPlist, renderSystemdUnit } from "./nativeServices/serviceRendering.js";
22
+ const PI_WEB_PACKAGE_NAME = "@vincenthanxiaodu/pi-web";
23
+ const systemdServiceDir = join(homedir(), ".config", "systemd", "user");
24
+ const launchdServiceDir = join(homedir(), "Library", "LaunchAgents");
25
+ const logDir = join(defaultPiWebDataDir(), "logs");
26
+ const serviceRefs = {
27
+ sessiond: { id: "sessiond", ...nativeServiceManagerRefs.sessiond },
28
+ web: { id: "web", ...nativeServiceManagerRefs.web },
29
+ uiDev: { id: "uiDev", ...nativeServiceManagerRefs.uiDev },
30
+ };
31
+ const productionServiceIds = [...productionNativeServiceIds];
32
+ function platformLabel() {
33
+ if (process.platform === "darwin")
34
+ return "macOS";
35
+ if (process.platform === "linux")
36
+ return "Linux";
37
+ if (process.platform === "win32")
38
+ return "Windows";
39
+ return process.platform;
40
+ }
41
+ export function serviceBackendForPlatform(platform) {
42
+ if (platform === "linux")
43
+ return { kind: "systemd", label: "systemd user services" };
44
+ if (platform === "darwin")
45
+ return { kind: "launchd", label: "LaunchAgents" };
46
+ return undefined;
47
+ }
48
+ function currentServiceBackend() {
49
+ return serviceBackendForPlatform(process.platform);
50
+ }
51
+ function requireServiceBackend(command) {
52
+ const backend = currentServiceBackend();
53
+ if (backend !== undefined)
54
+ return backend;
55
+ throw new Error(`\`${command}\` requires a supported per-user service manager (systemd user services or LaunchAgents) and is not supported on ${platformLabel()}.\n\n${manualRunAdvice()}`);
56
+ }
57
+ function manualRunAdvice() {
58
+ return [
59
+ "Run PI WEB manually from a checkout:",
60
+ " npm run start:sessiond",
61
+ " PI_WEB_PORT=8504 npm start",
62
+ "",
63
+ "For development in one terminal:",
64
+ " npm run dev",
65
+ "",
66
+ "For split development, keep sessiond separate and run web/API plus Vite UI separately:",
67
+ " npm run dev:sessiond",
68
+ " npm run dev:web",
69
+ " npm run dev:client",
70
+ ].join("\n");
71
+ }
72
+ function run(command, args, options = {}) {
73
+ const result = spawnSync(command, args, { stdio: "inherit" });
74
+ const status = result.status ?? 1;
75
+ if (options.check === true && status !== 0)
76
+ process.exit(status);
77
+ return status;
78
+ }
79
+ function outputText(value) {
80
+ return typeof value === "string" ? value : "";
81
+ }
82
+ function capture(command, args) {
83
+ const result = spawnSync(command, args, { encoding: "utf8" });
84
+ const errorMessage = result.error instanceof Error ? result.error.message : "";
85
+ const stderr = outputText(result.stderr);
86
+ return { status: result.status ?? 1, stdout: outputText(result.stdout), stderr: stderr === "" ? errorMessage : stderr };
87
+ }
88
+ function captureInstalledServiceDefinitionCommand(command, args) {
89
+ const result = spawnSync(command, args);
90
+ return {
91
+ status: result.status ?? 1,
92
+ stdout: result.stdout,
93
+ stderr: result.stderr,
94
+ spawnError: result.error instanceof Error ? result.error.message : "",
95
+ };
96
+ }
97
+ function runQuiet(command, args) {
98
+ return capture(command, args).status;
99
+ }
100
+ function hasCommand(command) {
101
+ return capture("/usr/bin/env", ["sh", "-c", `command -v ${shellSingleQuote(command)}`]).status === 0;
102
+ }
103
+ function isLingerEnabled() {
104
+ if (!hasCommand("loginctl"))
105
+ return undefined;
106
+ const result = capture("loginctl", ["show-user", userInfo().username, "-p", "Linger"]);
107
+ if (result.status !== 0)
108
+ return undefined;
109
+ const value = result.stdout.trim();
110
+ if (value === "Linger=yes")
111
+ return true;
112
+ if (value === "Linger=no")
113
+ return false;
114
+ return undefined;
115
+ }
116
+ function parseInstallOptions(args) {
117
+ const options = { host: "127.0.0.1", port: "8504", mode: "production" };
118
+ for (let i = 0; i < args.length; i += 1) {
119
+ const arg = args[i];
120
+ if (arg === undefined)
121
+ continue;
122
+ if (arg === "--host") {
123
+ const value = args[i + 1];
124
+ if (value === undefined)
125
+ throw new Error("--host requires a value");
126
+ options.host = value;
127
+ i += 1;
128
+ }
129
+ else if (arg.startsWith("--host=")) {
130
+ options.host = arg.slice("--host=".length);
131
+ }
132
+ else if (arg === "--port") {
133
+ const value = args[i + 1];
134
+ if (value === undefined)
135
+ throw new Error("--port requires a value");
136
+ options.port = value;
137
+ i += 1;
138
+ }
139
+ else if (arg.startsWith("--port=")) {
140
+ options.port = arg.slice("--port=".length);
141
+ }
142
+ else if (arg === "--config") {
143
+ const value = args[i + 1];
144
+ if (value === undefined)
145
+ throw new Error("--config requires a value");
146
+ options.config = value;
147
+ i += 1;
148
+ }
149
+ else if (arg.startsWith("--config=")) {
150
+ options.config = arg.slice("--config=".length);
151
+ }
152
+ else if (arg === "--dev") {
153
+ options.mode = "dev";
154
+ }
155
+ else if (arg === "--user-systemd") {
156
+ // Accepted for backwards-compatible readability; PI WEB chooses the native user service backend automatically.
157
+ }
158
+ else {
159
+ throw new Error(`Unknown install option: ${arg}`);
160
+ }
161
+ }
162
+ return options;
163
+ }
164
+ function shellSingleQuote(value) {
165
+ return `'${value.replaceAll("'", "'\\''")}'`;
166
+ }
167
+ function fishSingleQuote(value) {
168
+ return `'${value.replaceAll("\\", "\\\\").replaceAll("'", "\\'")}'`;
169
+ }
170
+ function packageRootPath() {
171
+ return dirname(dirname(fileURLToPath(import.meta.url)));
172
+ }
173
+ function packageEntrypointPath(name) {
174
+ return join(packageRootPath(), "dist", "server", name === "server" ? "index.js" : "sessiond.js");
175
+ }
176
+ export function regularFileExists(path) {
177
+ return existsSync(path) && statSync(path).isFile();
178
+ }
179
+ function detectServiceShell() {
180
+ const userShell = userInfo().shell ?? undefined;
181
+ const envShell = process.env["SHELL"]?.trim();
182
+ const detected = envShell === undefined || envShell === "" ? userShell : envShell;
183
+ const name = basename(detected ?? "").replace(/^-/, "");
184
+ if (name === "bash" || name === "zsh" || name === "fish") {
185
+ return {
186
+ name,
187
+ executable: detected ?? name,
188
+ source: "detected",
189
+ detectedExecutable: detected ?? name,
190
+ };
191
+ }
192
+ return {
193
+ name: "bash",
194
+ executable: "bash",
195
+ source: "fallback",
196
+ detectedExecutable: detected ?? null,
197
+ };
198
+ }
199
+ function serviceShellCommand(command, cwd) {
200
+ const fullCommand = cwd === undefined ? command : `cd ${serviceShellQuote(cwd)} && ${command}`;
201
+ return ["/usr/bin/env", detectServiceShell().executable, "-lc", fullCommand];
202
+ }
203
+ function serviceShellQuote(value) {
204
+ return detectServiceShell().name === "fish" ? fishSingleQuote(value) : shellSingleQuote(value);
205
+ }
206
+ function describeServiceShell() {
207
+ const shell = detectServiceShell();
208
+ if (shell.source === "fallback") {
209
+ return shell.detectedExecutable === null
210
+ ? "could not detect a supported login shell; using bash"
211
+ : `detected ${shell.detectedExecutable}; using bash because PI WEB currently supports bash, zsh, and fish`;
212
+ }
213
+ return shell.detectedExecutable === null ? shell.name : `${shell.name} (${shell.detectedExecutable})`;
214
+ }
215
+ function configEnvironment(options, configPath) {
216
+ return options.config === undefined ? {} : { PI_WEB_CONFIG: configPath };
217
+ }
218
+ function serviceRefList(ids) {
219
+ return ids.map((id) => serviceRefs[id]);
220
+ }
221
+ function allServiceRefs() {
222
+ return serviceRefList(["sessiond", "web", "uiDev"]);
223
+ }
224
+ function productionServiceRefs() {
225
+ return serviceRefList(productionServiceIds);
226
+ }
227
+ function startOrder(refs) {
228
+ return orderServices(refs, serviceStartOrder);
229
+ }
230
+ function stopOrder(refs) {
231
+ return orderServices(refs, serviceStopOrder);
232
+ }
233
+ function devRootPath() {
234
+ return resolve(process.cwd());
235
+ }
236
+ function validateDevCheckout(root) {
237
+ const packageJsonPath = join(root, "package.json");
238
+ if (!existsSync(packageJsonPath)) {
239
+ throw new Error(`Development mode must be installed from a PI WEB checkout. Missing package.json: ${packageJsonPath}`);
240
+ }
241
+ const parsed = JSON.parse(readFileSync(packageJsonPath, "utf8"));
242
+ if (!isRecord(parsed) || parsed["name"] !== PI_WEB_PACKAGE_NAME) {
243
+ throw new Error(`Development mode must be installed from a PI WEB checkout. ${packageJsonPath} is not ${PI_WEB_PACKAGE_NAME}.`);
244
+ }
245
+ }
246
+ function launchdLogPath(ref) {
247
+ return join(logDir, ref.logName);
248
+ }
249
+ function installConfigPath(options) {
250
+ return options.config === undefined ? defaultPiWebConfigPath() : resolve(options.config);
251
+ }
252
+ async function writeInitialConfig(options, configPath) {
253
+ await mkdir(dirname(configPath), { recursive: true });
254
+ if (!existsSync(configPath)) {
255
+ await writeFile(configPath, examplePiWebConfig({ host: options.host, port: Number(options.port) }));
256
+ }
257
+ }
258
+ function systemdServicePath(ref) {
259
+ return join(systemdServiceDir, ref.systemdName);
260
+ }
261
+ function launchdPlistPath(ref) {
262
+ return join(launchdServiceDir, ref.launchdPlistName);
263
+ }
264
+ function serviceFilePath(backend, ref) {
265
+ return backend.kind === "systemd" ? systemdServicePath(ref) : launchdPlistPath(ref);
266
+ }
267
+ function serviceFileExists(backend, ref) {
268
+ return existsSync(serviceFilePath(backend, ref));
269
+ }
270
+ function installedServiceIds(backend) {
271
+ return new Set(allServiceRefs().filter((ref) => serviceFileExists(backend, ref)).map((ref) => ref.id));
272
+ }
273
+ function installedServiceRefs(backend) {
274
+ const installed = startOrder(allServiceRefs().filter((ref) => serviceFileExists(backend, ref)));
275
+ return installed.length === 0 ? productionServiceRefs() : installed;
276
+ }
277
+ async function installSystemdServices(plan) {
278
+ const selected = new Set(plan.services.map((service) => service.id));
279
+ const obsolete = stopOrder(allServiceRefs().filter((ref) => !selected.has(ref.id)));
280
+ for (const ref of obsolete) {
281
+ runQuiet("systemctl", ["--user", "disable", "--now", ref.systemdName]);
282
+ await rm(systemdServicePath(ref), { force: true });
283
+ }
284
+ await mkdir(systemdServiceDir, { recursive: true });
285
+ for (const service of plan.services) {
286
+ await writeFile(join(systemdServiceDir, service.manager.systemdName), renderSystemdUnit(plan, service));
287
+ }
288
+ const names = plan.services.map((service) => service.manager.systemdName);
289
+ run("systemctl", ["--user", "daemon-reload"], { check: true });
290
+ run("systemctl", ["--user", "enable", ...names], { check: true });
291
+ run("systemctl", ["--user", "restart", ...names], { check: true });
292
+ }
293
+ function launchdDomain() {
294
+ return `gui/${String(userInfo().uid)}`;
295
+ }
296
+ function currentLaunchdServiceTarget(ref) {
297
+ return launchdServiceTarget(launchdDomain(), ref);
298
+ }
299
+ function launchdActionContext() {
300
+ return { domain: launchdDomain(), plistPath: launchdPlistPath };
301
+ }
302
+ function serviceActionDeps(backend, probeEnvironment = process.env) {
303
+ return {
304
+ run: (command, args) => run(command, args),
305
+ runQuiet,
306
+ sleep: (ms) => new Promise((resolve) => {
307
+ setTimeout(resolve, ms);
308
+ }),
309
+ isServiceRunning: (ref) => runtimeStatus(backend, ref).health === "running",
310
+ isComponentReady: (component) => probeRunningComponentReady(component, { configEnv: probeEnvironment }),
311
+ };
312
+ }
313
+ async function installLaunchdServices(plan) {
314
+ const selected = new Set(plan.services.map((service) => service.id));
315
+ await mkdir(launchdServiceDir, { recursive: true });
316
+ await mkdir(logDir, { recursive: true });
317
+ for (const ref of stopOrder(allServiceRefs())) {
318
+ runQuiet("launchctl", launchdBootoutArgs(currentLaunchdServiceTarget(ref)));
319
+ }
320
+ for (const ref of allServiceRefs().filter((candidate) => !selected.has(candidate.id))) {
321
+ await rm(launchdPlistPath(ref), { force: true });
322
+ }
323
+ for (const service of plan.services) {
324
+ const plistPath = join(launchdServiceDir, service.manager.launchdPlistName);
325
+ await writeFile(plistPath, renderLaunchdPlist(plan, service, logDir));
326
+ }
327
+ // Settle each bootout before deciding bootstrap vs kickstart: acting on a
328
+ // label launchd is still asynchronously unloading races the teardown and can
329
+ // lose the service entirely (the same defect the restart path had).
330
+ const context = launchdActionContext();
331
+ const deps = serviceActionDeps(plan.backend);
332
+ for (const service of plan.services) {
333
+ const ref = serviceRefFromPlan(service.id, service.manager);
334
+ await settleLaunchdServiceUnload(currentLaunchdServiceTarget(ref), deps);
335
+ startLaunchdService(ref, context, deps);
336
+ }
337
+ }
338
+ async function installNativeServices(plan) {
339
+ if (plan.backend.kind === "systemd")
340
+ await installSystemdServices(plan);
341
+ else
342
+ await installLaunchdServices(plan);
343
+ }
344
+ function serviceRefFromPlan(id, manager) {
345
+ return { id, ...manager };
346
+ }
347
+ async function uninstallSystemdServices() {
348
+ for (const ref of stopOrder(allServiceRefs())) {
349
+ runQuiet("systemctl", ["--user", "disable", "--now", ref.systemdName]);
350
+ await rm(systemdServicePath(ref), { force: true });
351
+ }
352
+ runQuiet("systemctl", ["--user", "daemon-reload"]);
353
+ }
354
+ async function uninstallLaunchdServices() {
355
+ for (const ref of stopOrder(allServiceRefs())) {
356
+ runQuiet("launchctl", launchdBootoutArgs(currentLaunchdServiceTarget(ref)));
357
+ await rm(launchdPlistPath(ref), { force: true });
358
+ }
359
+ }
360
+ async function uninstallNativeServices(backend) {
361
+ if (backend.kind === "systemd")
362
+ await uninstallSystemdServices();
363
+ else
364
+ await uninstallLaunchdServices();
365
+ }
366
+ function serviceDisplayName(ref) {
367
+ if (ref.id === "sessiond")
368
+ return "session daemon";
369
+ if (ref.id === "uiDev")
370
+ return "UI/API dev server";
371
+ return "web server";
372
+ }
373
+ function statusServiceRefs(backend) {
374
+ const ids = installedServiceIds(backend);
375
+ if (ids.size === 0)
376
+ return [];
377
+ if (ids.has("web") && ids.has("uiDev"))
378
+ return startOrder(allServiceRefs());
379
+ if (ids.has("uiDev"))
380
+ return serviceRefList(["sessiond", "uiDev"]);
381
+ if (ids.has("web"))
382
+ return productionServiceRefs();
383
+ return serviceRefList(["sessiond"]);
384
+ }
385
+ function serviceInstallMode(backend) {
386
+ const ids = installedServiceIds(backend);
387
+ if (ids.size === 0)
388
+ return "not installed";
389
+ const hasSessiond = ids.has("sessiond");
390
+ const hasWeb = ids.has("web");
391
+ const hasUiDev = ids.has("uiDev");
392
+ if (hasWeb && hasUiDev)
393
+ return "mixed";
394
+ if (hasUiDev)
395
+ return hasSessiond ? "development" : "development (incomplete)";
396
+ if (hasWeb)
397
+ return hasSessiond ? "production" : "production (incomplete)";
398
+ return "partial";
399
+ }
400
+ function makeServiceRuntimeStatus(ref, health, detail, target, filePath, pid) {
401
+ return {
402
+ ref,
403
+ health,
404
+ detail,
405
+ target,
406
+ filePath,
407
+ ...(pid === undefined ? {} : { pid }),
408
+ };
409
+ }
410
+ function firstOutputLine(...values) {
411
+ for (const value of values) {
412
+ const line = value.trim().split("\n").find((candidate) => candidate.trim() !== "");
413
+ if (line !== undefined)
414
+ return line.trim();
415
+ }
416
+ return undefined;
417
+ }
418
+ function systemdMainPid(ref) {
419
+ const result = capture("systemctl", ["--user", "--no-pager", "show", ref.systemdName, "--property=MainPID", "--value"]);
420
+ if (result.status !== 0)
421
+ return undefined;
422
+ const value = result.stdout.trim();
423
+ return value === "" || value === "0" ? undefined : value;
424
+ }
425
+ function systemdRuntimeStatus(backend, ref) {
426
+ const target = ref.systemdName;
427
+ const filePath = serviceFilePath(backend, ref);
428
+ if (!serviceFileExists(backend, ref))
429
+ return makeServiceRuntimeStatus(ref, "not-installed", "not installed", target, filePath);
430
+ const result = capture("systemctl", ["--user", "--no-pager", "is-active", target]);
431
+ const state = firstOutputLine(result.stdout, result.stderr) ?? "unknown";
432
+ if (result.status === 0 && state === "active")
433
+ return makeServiceRuntimeStatus(ref, "running", "running", target, filePath, systemdMainPid(ref));
434
+ return makeServiceRuntimeStatus(ref, state === "unknown" ? "unknown" : "stopped", state, target, filePath);
435
+ }
436
+ function parseLaunchdField(output, field) {
437
+ const match = new RegExp(`^\\s*${field}\\s=\\s(.+)$`, "m").exec(output);
438
+ return match?.[1]?.trim();
439
+ }
440
+ export function launchdRuntimeDetails(output) {
441
+ const state = parseLaunchdField(output, "state") ?? "unknown";
442
+ const pid = parseLaunchdField(output, "pid");
443
+ const lastExitCode = parseLaunchdField(output, "last exit code");
444
+ const detail = state === "running"
445
+ ? "running"
446
+ : lastExitCode === undefined ? state : `${state} (last exit code ${lastExitCode})`;
447
+ return { state, detail, pid };
448
+ }
449
+ function launchdRuntimeStatus(backend, ref) {
450
+ const target = currentLaunchdServiceTarget(ref);
451
+ const filePath = serviceFilePath(backend, ref);
452
+ if (!serviceFileExists(backend, ref))
453
+ return makeServiceRuntimeStatus(ref, "not-installed", "not installed", target, filePath);
454
+ const result = capture("launchctl", ["print", target]);
455
+ if (result.status !== 0) {
456
+ return makeServiceRuntimeStatus(ref, "stopped", firstOutputLine(result.stderr, result.stdout) ?? "not loaded", target, filePath);
457
+ }
458
+ const details = launchdRuntimeDetails(result.stdout);
459
+ const health = details.state === "running" ? "running" : details.state === "unknown" ? "unknown" : "stopped";
460
+ return makeServiceRuntimeStatus(ref, health, details.detail, target, filePath, details.pid);
461
+ }
462
+ function runtimeStatus(backend, ref) {
463
+ return backend.kind === "systemd" ? systemdRuntimeStatus(backend, ref) : launchdRuntimeStatus(backend, ref);
464
+ }
465
+ function printServiceStatus(status) {
466
+ const icon = status.health === "running" ? "✓" : "✗";
467
+ const pid = status.pid === undefined ? "" : `, pid ${status.pid}`;
468
+ console.log(`${icon} ${serviceDisplayName(status.ref)}: ${status.detail} (${status.target}${pid})`);
469
+ if (status.health === "not-installed")
470
+ console.log(` missing service file: ${status.filePath}`);
471
+ }
472
+ function printServiceStatusReport(backend) {
473
+ const refs = statusServiceRefs(backend);
474
+ console.log(`PI WEB services: ${serviceInstallMode(backend)} (${backend.label})`);
475
+ if (refs.length === 0) {
476
+ console.log("✗ no PI WEB service files found");
477
+ console.log(" Run `pi-web install` or `pi-web install --dev`.");
478
+ return false;
479
+ }
480
+ const statuses = refs.map((ref) => runtimeStatus(backend, ref));
481
+ for (const status of statuses)
482
+ printServiceStatus(status);
483
+ console.log("\nUse `pi-web logs` for service logs.");
484
+ return statuses.every((status) => status.health === "running");
485
+ }
486
+ function configuredServiceCommand(name) {
487
+ const value = process.env[name];
488
+ return value === undefined || value.trim() === "" ? undefined : value;
489
+ }
490
+ function productionNativeServicePlanInput(backend, shell, environment) {
491
+ return {
492
+ backend,
493
+ shell,
494
+ environment,
495
+ executables: {
496
+ sessiond: {
497
+ configuredCommand: configuredServiceCommand("PI_WEB_SESSIOND_EXEC"),
498
+ namedCommand: "pi-web-sessiond",
499
+ bundledEntrypointPath: packageEntrypointPath("sessiond"),
500
+ },
501
+ web: {
502
+ configuredCommand: configuredServiceCommand("PI_WEB_SERVER_EXEC"),
503
+ namedCommand: "pi-web-server",
504
+ bundledEntrypointPath: packageEntrypointPath("server"),
505
+ },
506
+ },
507
+ };
508
+ }
509
+ function nativeServiceInstallCandidate(options, backend, configPath, devRoot) {
510
+ const shell = detectServiceShell();
511
+ const environment = configEnvironment(options, configPath);
512
+ if (options.mode === "production") {
513
+ return {
514
+ mode: "production",
515
+ input: productionNativeServicePlanInput(backend, shell, environment),
516
+ };
517
+ }
518
+ const root = devRoot ?? devRootPath();
519
+ return {
520
+ mode: "development",
521
+ input: {
522
+ backend,
523
+ shell,
524
+ environment,
525
+ workingDirectory: root,
526
+ packageJsonPath: join(root, "package.json"),
527
+ },
528
+ };
529
+ }
530
+ function printNativeServiceInstallFailure(failure) {
531
+ if (failure.kind === "plan-resolution") {
532
+ for (const item of failure.failures) {
533
+ if (item.kind === "probe-infrastructure") {
534
+ console.log(`✗ Service-manager probe infrastructure failure (${item.reason}): ${item.message}`);
535
+ }
536
+ else if (item.kind === "entrypoint-inspection-failure") {
537
+ console.log(`✗ Could not inspect bundled ${item.serviceId} entrypoint ${item.entrypointPath}: ${item.message}`);
538
+ }
539
+ else {
540
+ console.log(`✗ ${item.namedCommand} is unavailable to the service manager, and bundled entrypoint ${item.bundledEntrypointPath} is missing.`);
541
+ if (item.namedCommandFailure !== null)
542
+ console.log(` ${item.namedCommandFailure}`);
543
+ }
544
+ }
545
+ return;
546
+ }
547
+ for (const item of failure.failures) {
548
+ if (item.kind === "probe-infrastructure") {
549
+ console.log(`✗ Service-manager probe infrastructure failure (${item.reason}): ${item.message}`);
550
+ }
551
+ else {
552
+ console.log(`✗ ${item.prerequisite.description}`);
553
+ if (item.detail !== null && item.detail !== item.prerequisite.description)
554
+ console.log(` ${item.detail}`);
555
+ }
556
+ }
557
+ }
558
+ async function install(args) {
559
+ const backend = requireServiceBackend("pi-web install");
560
+ const options = parseInstallOptions(args);
561
+ const devRoot = options.mode === "dev" ? devRootPath() : undefined;
562
+ if (devRoot !== undefined)
563
+ validateDevCheckout(devRoot);
564
+ const configPath = installConfigPath(options);
565
+ const candidate = nativeServiceInstallCandidate(options, backend, configPath, devRoot);
566
+ console.log(`Running PI WEB ${options.mode} install preflight checks...`);
567
+ console.log(`Service backend: ${backend.label}`);
568
+ console.log(`Service shell: ${describeServiceShell()}`);
569
+ if (!printNodePtyNativeModuleCheck()) {
570
+ throw new Error("Install preflight checks failed without changing config or services. Fix the failure above, then run `pi-web doctor` for more detail.");
571
+ }
572
+ const result = await installNativeServiceCandidate(candidate, {
573
+ probe: createNativeServiceAuthoritativeProbe(),
574
+ fileExists: regularFileExists,
575
+ writeInitialConfig: () => writeInitialConfig(options, configPath),
576
+ replaceServices: installNativeServices,
577
+ });
578
+ if (!result.ok) {
579
+ printNativeServiceInstallFailure(result.failure);
580
+ if (nativeServiceInstallFailureNeedsPathAdvice(result.failure))
581
+ printPathSetupAdvice();
582
+ throw new Error("Install preflight checks failed without changing config or services. Fix the failure above, then run `pi-web doctor` for more detail.");
583
+ }
584
+ for (const service of result.plan.services.filter((item) => item.strategy.kind === "configured-override")) {
585
+ console.log(`! ${service.description} uses a configured command override; preflight did not execute that arbitrary command.`);
586
+ }
587
+ console.log(`\nPI WEB ${options.mode} services are installed and starting.`);
588
+ console.log(`Config: ${configPath}`);
589
+ if (options.mode === "dev") {
590
+ console.log("Open: http://127.0.0.1:8505");
591
+ }
592
+ else {
593
+ console.log(`Open: http://${options.host === "0.0.0.0" ? "127.0.0.1" : options.host}:${options.port}`);
594
+ }
595
+ if (backend.kind === "systemd") {
596
+ const linger = isLingerEnabled();
597
+ if (linger === false) {
598
+ console.log("\nRecommended for server use: keep user services running after logout/reboot:");
599
+ console.log(` sudo loginctl enable-linger ${userInfo().username}`);
600
+ }
601
+ else if (linger === undefined) {
602
+ console.log("\nRecommended for server use: enable systemd user lingering so services survive logout/reboot:");
603
+ console.log(` sudo loginctl enable-linger ${userInfo().username}`);
604
+ }
605
+ }
606
+ console.log("\nUseful commands:");
607
+ console.log(" pi-web status");
608
+ console.log(" pi-web logs");
609
+ console.log(" pi-web restart");
610
+ }
611
+ async function uninstall() {
612
+ const backend = requireServiceBackend("pi-web uninstall");
613
+ await uninstallNativeServices(backend);
614
+ console.log(`PI WEB ${backend.label} removed. Production and development service files were removed; config and data were left in place.`);
615
+ }
616
+ async function serviceAction(action) {
617
+ const backend = requireServiceBackend(`pi-web ${action}`);
618
+ if (action === "status") {
619
+ if (!printServiceStatusReport(backend))
620
+ process.exitCode = 1;
621
+ return;
622
+ }
623
+ await performLifecycleServiceAction(backend, action, process.env);
624
+ }
625
+ async function performLifecycleServiceAction(backend, action, probeEnvironment) {
626
+ const refs = installedServiceRefs(backend);
627
+ const input = { backend, action, refs, launchdContext: launchdActionContext() };
628
+ const result = await performServiceAction(input, serviceActionDeps(backend, probeEnvironment));
629
+ if (result.unreadyServices.length > 0) {
630
+ for (const ref of result.unreadyServices) {
631
+ const target = backend.kind === "systemd" ? ref.systemdName : currentLaunchdServiceTarget(ref);
632
+ console.log(`✗ ${serviceDisplayName(ref)} did not become ready (${target}).`);
633
+ }
634
+ console.log(" Check `pi-web status` and `pi-web logs` for details.");
635
+ process.exitCode = 1;
636
+ }
637
+ }
638
+ /** Offline restart guidance used only after a recovery config mutation. */
639
+ export function sessionDaemonRestartPlan(options = {}) {
640
+ const env = options.env ?? process.env;
641
+ if (options.explicitConfigPath === true && options.configPath !== undefined) {
642
+ return {
643
+ kind: "manual",
644
+ guidance: `Restart the session daemon process configured with PI_WEB_CONFIG=${JSON.stringify(options.configPath)}.`,
645
+ };
646
+ }
647
+ const dockerMode = activeDockerMode(env);
648
+ const runCommand = options.runCommand ?? ((command, args) => { run(command, args, { check: true }); });
649
+ if (dockerMode !== undefined) {
650
+ const command = piWebDockerCommand(dockerMode, "restart-sessiond");
651
+ return {
652
+ kind: "automatic",
653
+ command,
654
+ guidance: "Run the Docker control command from the PI WEB Docker host.",
655
+ perform: () => {
656
+ runCommand("pi-web-docker", [...(dockerMode === "dev" ? ["--dev"] : []), "restart-sessiond"]);
657
+ },
658
+ };
659
+ }
660
+ const backend = serviceBackendForPlatform(options.platform ?? process.platform);
661
+ const fileExists = options.serviceFileExists ?? existsSync;
662
+ if (backend?.kind === "systemd" && fileExists(systemdServicePath(serviceRefs.sessiond))) {
663
+ const args = ["--user", "restart", serviceRefs.sessiond.systemdName];
664
+ return {
665
+ kind: "automatic",
666
+ command: `systemctl ${args.join(" ")}`,
667
+ guidance: "Run:",
668
+ perform: () => { runCommand("systemctl", args); },
669
+ };
670
+ }
671
+ if (backend?.kind === "launchd" && fileExists(launchdPlistPath(serviceRefs.sessiond))) {
672
+ const target = `gui/${String(options.uid ?? userInfo().uid)}/${serviceRefs.sessiond.launchdLabel}`;
673
+ const args = ["kickstart", "-k", target];
674
+ return {
675
+ kind: "automatic",
676
+ command: `launchctl ${args.join(" ")}`,
677
+ guidance: "Run:",
678
+ perform: () => { runCommand("launchctl", args); },
679
+ };
680
+ }
681
+ return {
682
+ kind: "manual",
683
+ guidance: "Stop and rerun the process that owns sessiond (from a checkout: `npm run start:sessiond`).",
684
+ };
685
+ }
686
+ function activeDockerMode(env) {
687
+ if (!truthyEnvValue(ownEnvironmentValue(env, "PI_WEB_DOCKER_RUNTIME")))
688
+ return undefined;
689
+ if (ownEnvironmentValue(env, "PI_WEB_DOCKER_MODE") === "dev")
690
+ return "dev";
691
+ return "runtime";
692
+ }
693
+ function truthyEnvValue(value) {
694
+ return value !== undefined && value !== "" && value !== "0" && value.toLowerCase() !== "false";
695
+ }
696
+ function logs() {
697
+ const backend = requireServiceBackend("pi-web logs");
698
+ const refs = installedServiceRefs(backend);
699
+ if (backend.kind === "systemd") {
700
+ run("journalctl", ["--user", ...refs.flatMap((ref) => ["-u", ref.systemdName]), "-f"]);
701
+ return;
702
+ }
703
+ run("tail", ["-F", ...refs.map((ref) => launchdLogPath(ref))]);
704
+ }
705
+ function serviceShellLabel() {
706
+ return `${detectServiceShell().name} -lc`;
707
+ }
708
+ function commandCheck(command) {
709
+ return `command -v ${serviceShellQuote(command)}`;
710
+ }
711
+ export function commandWithVersionCheck(command) {
712
+ const found = commandCheck(command);
713
+ const commandWord = serviceShellQuote(command);
714
+ if (detectServiceShell().name === "fish") {
715
+ return `${found} && begin; ${commandWord} --version 2>&1 || true; end`;
716
+ }
717
+ return `${found} && (${commandWord} --version 2>&1 || true)`;
718
+ }
719
+ export function nodeVersionCheck() {
720
+ return nativeServicePrerequisiteShellCheck(detectServiceShell().name, {
721
+ id: "caller.node",
722
+ kind: "node-version",
723
+ command: "node",
724
+ minimumVersion: minimumSupportedNodeVersion,
725
+ description: `node >= ${minimumSupportedNodeVersion}`,
726
+ });
727
+ }
728
+ export function generalDoctorChecks() {
729
+ const shell = serviceShellLabel();
730
+ return [
731
+ [`Caller login ${shell} can find node >= ${minimumSupportedNodeVersion}`, serviceShellCommand(nodeVersionCheck())],
732
+ [`Caller login ${shell} can find npm`, serviceShellCommand(commandWithVersionCheck("npm"))],
733
+ // Sessions run on the bundled pi SDK; the only CLI check is a hardcoded
734
+ // `pi` PATH probe — nothing names or locates a configurable command.
735
+ [`Caller login ${shell} can find pi`, serviceShellCommand(commandWithVersionCheck("pi"))],
736
+ ];
737
+ }
738
+ function runChecks(checks) {
739
+ let failed = false;
740
+ for (const [label, command] of checks) {
741
+ const [bin, ...args] = command;
742
+ if (bin === undefined)
743
+ continue;
744
+ const result = capture(bin, args);
745
+ const ok = result.status === 0;
746
+ failed ||= !ok;
747
+ console.log(`${ok ? "✓" : "✗"} ${label}`);
748
+ printCheckOutput(result.stdout || result.stderr);
749
+ }
750
+ return !failed;
751
+ }
752
+ function printCheckOutput(output) {
753
+ const trimmed = output.trim();
754
+ if (trimmed === "")
755
+ return;
756
+ const lines = trimmed.split("\n");
757
+ for (const line of lines.slice(0, 3))
758
+ console.log(` ${line}`);
759
+ if (lines.length > 3)
760
+ console.log(" ...");
761
+ }
762
+ function optionalDoctorChecks() {
763
+ const shell = serviceShellLabel();
764
+ return [[`Caller login ${shell} can find optional ripgrep (rg)`, serviceShellCommand(commandCheck("rg"))]];
765
+ }
766
+ function printOptionalDoctorChecks() {
767
+ let missingOptionalTool = false;
768
+ for (const [label, command] of optionalDoctorChecks()) {
769
+ const [bin, ...args] = command;
770
+ if (bin === undefined)
771
+ continue;
772
+ const result = capture(bin, args);
773
+ const ok = result.status === 0;
774
+ missingOptionalTool ||= !ok;
775
+ console.log(`${ok ? "✓" : "!"} ${label}`);
776
+ printCheckOutput(result.stdout || result.stderr);
777
+ }
778
+ if (missingOptionalTool) {
779
+ console.log(" Install ripgrep, or make rg visible to the service shell, for faster all-file @ suggestions.");
780
+ console.log(" PI WEB falls back to a bounded filesystem scan when rg is unavailable.");
781
+ }
782
+ }
783
+ function installedServiceDefinitions(backend, ids, purpose) {
784
+ const sources = ids.map((id) => ({
785
+ id,
786
+ path: serviceFilePath(backend, serviceRefs[id]),
787
+ systemdName: serviceRefs[id].systemdName,
788
+ launchdTarget: currentLaunchdServiceTarget(serviceRefs[id]),
789
+ }));
790
+ return inspectInstalledNativeServiceDefinitions(backend, sources, {
791
+ readFile: (path) => readFileSync(path),
792
+ realpath: realpathSync,
793
+ capture: captureInstalledServiceDefinitionCommand,
794
+ }, purpose);
795
+ }
796
+ /** Build an isolated probe environment without mutating the caller or process. */
797
+ export function managedServiceProbeEnvironment(backend, definitions, callerEnvironment) {
798
+ const selection = selectManagedNativeServiceConfig(backend, definitions, ownEnvironmentValue(callerEnvironment, "PI_WEB_CONFIG"));
799
+ if (!selection.ok)
800
+ return selection;
801
+ if (selection.value.source === "installed") {
802
+ return {
803
+ ok: true,
804
+ value: { ...callerEnvironment, PI_WEB_CONFIG: selection.value.configPath },
805
+ };
806
+ }
807
+ if (selection.value.source === "default" && definitions.length > 0) {
808
+ // An own empty value preserves default resolution while masking any
809
+ // prototype-inherited PI_WEB_CONFIG from downstream environment lookups.
810
+ return { ok: true, value: { ...callerEnvironment, PI_WEB_CONFIG: "" } };
811
+ }
812
+ return { ok: true, value: callerEnvironment };
813
+ }
814
+ function installedServiceProbeEnvironment(backend, ids, callerEnvironment, purpose, inspectDefinitions) {
815
+ // The explicit caller override wins without requiring installed definitions
816
+ // to be readable or canonical.
817
+ const callerConfigPath = ownEnvironmentValue(callerEnvironment, "PI_WEB_CONFIG");
818
+ if (callerConfigPath !== undefined && callerConfigPath !== "") {
819
+ return { ok: true, value: callerEnvironment };
820
+ }
821
+ if (ids.size === 0)
822
+ return { ok: true, value: callerEnvironment };
823
+ const definitions = inspectDefinitions(backend, [...ids], purpose);
824
+ if (!definitions.ok)
825
+ return definitions;
826
+ return managedServiceProbeEnvironment(backend, definitions.value, callerEnvironment);
827
+ }
828
+ function managedServiceProbeFailure(backend, message) {
829
+ return [
830
+ `Could not select managed PI_WEB_CONFIG from installed ${backend.label}: ${message}`,
831
+ "Repair the reported service-manager override or definition, then re-run `pi-web install` (or `pi-web install --dev`); a nonempty PI_WEB_CONFIG explicitly overrides managed config selection for this command.",
832
+ ].join("\n");
833
+ }
834
+ /**
835
+ * Dispatch commands whose probes may need managed config. The service-manager,
836
+ * definition, lifecycle, doctor, and version boundaries are injected so this
837
+ * exact orchestration can be tested without mutating live services or process
838
+ * environment.
839
+ */
840
+ export async function runReadinessCliCommand(command, dependencies) {
841
+ if (command === "version") {
842
+ await dependencies.printVersion();
843
+ return;
844
+ }
845
+ const environment = dependencies.environment;
846
+ if (command === "start" || command === "restart") {
847
+ const backend = dependencies.requireBackend(`pi-web ${command}`);
848
+ const installedIds = dependencies.installedServiceIds(backend);
849
+ const probeEnvironment = installedServiceProbeEnvironment(backend, installedIds, environment, command, dependencies.inspectDefinitions);
850
+ if (!probeEnvironment.ok)
851
+ throw new Error(managedServiceProbeFailure(backend, probeEnvironment.message));
852
+ await dependencies.runLifecycle(backend, command, probeEnvironment.value);
853
+ return;
854
+ }
855
+ const backend = dependencies.currentBackend();
856
+ const installedIds = backend === undefined
857
+ ? new Set()
858
+ : dependencies.installedServiceIds(backend);
859
+ let versionReportOptions = { configEnv: environment };
860
+ let managedInspectionFailed = false;
861
+ if (backend !== undefined && activeDockerMode(environment) === undefined) {
862
+ const probeEnvironment = installedServiceProbeEnvironment(backend, installedIds, environment, "doctor", dependencies.inspectDefinitions);
863
+ if (probeEnvironment.ok) {
864
+ versionReportOptions = { configEnv: probeEnvironment.value };
865
+ }
866
+ else {
867
+ managedInspectionFailed = true;
868
+ versionReportOptions = { webEndpointError: managedServiceProbeFailure(backend, probeEnvironment.message) };
869
+ }
870
+ }
871
+ await dependencies.runDoctor({
872
+ backend,
873
+ installedIds,
874
+ environment,
875
+ versionReportOptions,
876
+ managedInspectionFailed,
877
+ });
878
+ }
879
+ function readinessCliCommandDependencies() {
880
+ return {
881
+ environment: process.env,
882
+ currentBackend: currentServiceBackend,
883
+ requireBackend: requireServiceBackend,
884
+ installedServiceIds,
885
+ inspectDefinitions: installedServiceDefinitions,
886
+ runLifecycle: performLifecycleServiceAction,
887
+ runDoctor: doctor,
888
+ printVersion: async () => { await printPiWebVersionReport(); },
889
+ };
890
+ }
891
+ function nativeServiceDoctorTarget(backend, ids) {
892
+ const mode = inferInstalledNativeServiceMode(ids);
893
+ if (mode === "ambiguous") {
894
+ return {
895
+ kind: "inspection-failure",
896
+ message: `installed service IDs do not identify one mode (${[...ids].join(", ") || "none"}).`,
897
+ };
898
+ }
899
+ if (mode === "none") {
900
+ return {
901
+ kind: "prospective-production",
902
+ input: productionNativeServicePlanInput(backend, detectServiceShell(), {}),
903
+ reason: "no installed service strategy is available",
904
+ };
905
+ }
906
+ const expectedIds = mode === "production"
907
+ ? productionNativeServiceIds
908
+ : ["sessiond", "uiDev"];
909
+ const missingId = expectedIds.find((id) => !ids.has(id));
910
+ if (missingId !== undefined) {
911
+ return {
912
+ kind: "inspection-failure",
913
+ message: `installed ${mode} service set is incomplete; ${missingId} is missing.`,
914
+ };
915
+ }
916
+ const definitions = installedServiceDefinitions(backend, expectedIds, "doctor");
917
+ if (!definitions.ok)
918
+ return { kind: "inspection-failure", message: definitions.message };
919
+ if (mode === "development") {
920
+ const inspection = inspectInstalledDevelopmentServiceInput(backend, definitions.value);
921
+ return inspection.ok
922
+ ? { kind: "installed-development", input: inspection.value }
923
+ : { kind: "inspection-failure", message: inspection.message };
924
+ }
925
+ const inspection = inspectInstalledProductionServiceContext(backend, definitions.value);
926
+ return inspection.ok
927
+ ? {
928
+ kind: "prospective-production",
929
+ input: productionNativeServicePlanInput(backend, inspection.value.shell, inspection.value.environment),
930
+ reason: "installed executable strategy is not recorded",
931
+ }
932
+ : { kind: "inspection-failure", message: inspection.message };
933
+ }
934
+ async function printNativeServiceDoctorChecks(backend, installedIds) {
935
+ const result = await runNativeServiceDoctor(nativeServiceDoctorTarget(backend, installedIds), {
936
+ probe: createNativeServiceAuthoritativeProbe(),
937
+ fileExists: regularFileExists,
938
+ });
939
+ const report = formatNativeServiceDoctorResult(result);
940
+ for (const line of report.lines)
941
+ console.log(line);
942
+ printCallerContextComparisons(report);
943
+ return report;
944
+ }
945
+ function printCallerContextComparisons(report) {
946
+ if (report.plan === null || report.failedPrerequisites.length === 0)
947
+ return;
948
+ const seen = new Set();
949
+ for (const prerequisite of report.failedPrerequisites) {
950
+ if (seen.has(prerequisite.id))
951
+ continue;
952
+ seen.add(prerequisite.id);
953
+ const service = report.plan.services.find((candidate) => candidate.prerequisites.some((item) => item.id === prerequisite.id));
954
+ const command = nativeServicePrerequisiteShellCheck(report.plan.shell.name, prerequisite);
955
+ const result = captureServiceShell(report.plan.shell, command, service?.workingDirectory ?? null);
956
+ console.log(` Caller-invoked ${report.plan.shell.name} -lc ${result.status === 0 ? "satisfies" : "also does not satisfy"} ${prerequisite.description}; the service-manager result is authoritative.`);
957
+ }
958
+ }
959
+ function captureServiceShell(shell, command, workingDirectory) {
960
+ const fullCommand = workingDirectory === null
961
+ ? command
962
+ : `cd ${shellQuoteFor(shell.name, workingDirectory)} && ${command}`;
963
+ return capture("/usr/bin/env", [shell.executable, "-lc", fullCommand]);
964
+ }
965
+ function shellQuoteFor(shell, value) {
966
+ return shell === "fish" ? fishSingleQuote(value) : shellSingleQuote(value);
967
+ }
968
+ function printPathSetupAdvice(shell = detectServiceShell()) {
969
+ console.log("\nPATH setup advice:");
970
+ if (shell.name === "bash") {
971
+ console.log(" Detected bash. Put PATH setup for node/version managers/tools in ~/.bash_profile or ~/.profile.");
972
+ console.log(" If ~/.bash_profile exists, bash will not read ~/.profile unless you source it from ~/.bash_profile.");
973
+ console.log(" Do not rely only on ~/.bashrc or prompt hooks for tools needed by services or agents.");
974
+ }
975
+ else if (shell.name === "zsh") {
976
+ console.log(" Detected zsh. Put PATH setup for node/version managers/tools in ~/.zprofile, not only ~/.zshrc.");
977
+ console.log(" Avoid relying on prompt hooks; PI WEB services run non-interactive login shells.");
978
+ }
979
+ else {
980
+ console.log(" Detected fish. Prefer universal PATH setup such as `fish_add_path -U ...` for tools needed by services or agents.");
981
+ console.log(" Avoid relying on prompt hooks; PI WEB services run non-interactive login shells.");
982
+ }
983
+ }
984
+ /**
985
+ * Running components an installed deployment must keep ready: the API-serving
986
+ * component whenever the web or UI dev service file is installed (the version
987
+ * endpoint probes the API port in both modes), and the session daemon whenever
988
+ * its service file is installed. Docker deployments have no native service
989
+ * files, so the Docker runtime marker expects both components. A machine with
990
+ * nothing installed expects nothing: doctor's manual-run guidance stays a
991
+ * passing outcome there.
992
+ */
993
+ export function expectedRunningComponents(installedServices, env) {
994
+ if (activeDockerMode(env) !== undefined)
995
+ return ["web", "sessiond"];
996
+ const expected = [];
997
+ if (installedServices.has("web") || installedServices.has("uiDev"))
998
+ expected.push("web");
999
+ if (installedServices.has("sessiond"))
1000
+ expected.push("sessiond");
1001
+ return expected;
1002
+ }
1003
+ export function doctorExitCode(generalReadinessOk, nativeServicePlanOk, nodePtyRuntimeOk, runningComponentsOk) {
1004
+ return generalReadinessOk && nativeServicePlanOk && nodePtyRuntimeOk && runningComponentsOk ? 0 : 1;
1005
+ }
1006
+ async function doctor(context) {
1007
+ const { backend, installedIds, environment, versionReportOptions, managedInspectionFailed } = context;
1008
+ console.log(`Platform: ${platformLabel()}`);
1009
+ console.log(`Service backend: ${backend?.label ?? "manual run only"}`);
1010
+ console.log(`Service shell: ${describeServiceShell()}`);
1011
+ if (backend === undefined) {
1012
+ console.log(`- Native user service plan checks skipped on ${platformLabel()}; no native-service drift is reported.`);
1013
+ }
1014
+ console.log("");
1015
+ const runningVersionInfo = await printPiWebVersionReport(versionReportOptions);
1016
+ const expectedComponents = expectedRunningComponents(installedIds, environment);
1017
+ const runningComponentsOk = runningComponentsReady(runningVersionInfo, expectedComponents);
1018
+ if (!runningComponentsOk) {
1019
+ console.log("\n✗ Installed PI WEB services are unavailable or stale (see \"Running services\" above).");
1020
+ console.log(" Run `pi-web restart`, then check `pi-web status`.");
1021
+ }
1022
+ console.log("\nGeneral login-shell readiness (separate from native-service requirements):");
1023
+ const generalReadinessOk = runChecks(generalDoctorChecks());
1024
+ printOptionalDoctorChecks();
1025
+ console.log("\nNative terminal runtime readiness:");
1026
+ const nodePtyNativeModuleOk = printNodePtyNativeModuleCheck();
1027
+ const nodePtySpawnHelperOk = nodePtyNativeModuleOk ? printNodePtyDarwinSpawnHelperCheck() : true;
1028
+ let nativeServiceReport = null;
1029
+ if (backend !== undefined) {
1030
+ console.log("\nNative service plan checks (service-manager context):");
1031
+ if (managedInspectionFailed) {
1032
+ console.log("✗ Installed native-service plan could not be inspected consistently with the readiness probe (see \"Running services\" above).");
1033
+ console.log(" Repair the reported service-manager definition before relying on doctor results.");
1034
+ }
1035
+ else {
1036
+ nativeServiceReport = await printNativeServiceDoctorChecks(backend, installedIds);
1037
+ }
1038
+ }
1039
+ if (backend?.kind === "systemd") {
1040
+ const linger = isLingerEnabled();
1041
+ if (linger === true) {
1042
+ console.log("✓ systemd user lingering enabled");
1043
+ }
1044
+ else if (linger === false) {
1045
+ console.log("✗ systemd user lingering disabled");
1046
+ console.log(` Recommended on servers: sudo loginctl enable-linger ${userInfo().username}`);
1047
+ }
1048
+ else {
1049
+ console.log("? systemd user lingering unknown");
1050
+ console.log(` Recommended on servers: sudo loginctl enable-linger ${userInfo().username}`);
1051
+ }
1052
+ }
1053
+ else if (backend?.kind === "launchd") {
1054
+ console.log("- user services start at login with LaunchAgents");
1055
+ }
1056
+ else {
1057
+ console.log(`- systemd user lingering skipped on ${platformLabel()}`);
1058
+ }
1059
+ const nativeServicePlanOk = !managedInspectionFailed && (nativeServiceReport?.ok ?? true);
1060
+ const pathFailure = !generalReadinessOk || nativeServiceReport?.pathAdviceRecommended === true;
1061
+ if (pathFailure) {
1062
+ console.log("\nIf a command works in your terminal but fails in the service-manager check, compare the caller and manager contexts above.");
1063
+ const adviceShell = nativeServiceReport?.pathAdviceRecommended === true && nativeServiceReport.adviceShell !== null
1064
+ ? nativeServiceReport.adviceShell
1065
+ : detectServiceShell();
1066
+ printPathSetupAdvice(adviceShell);
1067
+ }
1068
+ if (generalReadinessOk && backend === undefined) {
1069
+ console.log(`\n${manualRunAdvice()}`);
1070
+ }
1071
+ if (doctorExitCode(generalReadinessOk, nativeServicePlanOk, nodePtyNativeModuleOk && nodePtySpawnHelperOk, runningComponentsOk) !== 0)
1072
+ process.exitCode = 1;
1073
+ }
1074
+ function printNodePtyNativeModuleCheck() {
1075
+ const result = formatNodePtyNativeModuleCheck(checkNodePtyNativeModule());
1076
+ for (const line of result.lines)
1077
+ console.log(line);
1078
+ return result.ok;
1079
+ }
1080
+ function printNodePtyDarwinSpawnHelperCheck() {
1081
+ const result = formatNodePtyDarwinSpawnHelperCheck(checkNodePtyDarwinSpawnHelper());
1082
+ for (const line of result.lines)
1083
+ console.log(line);
1084
+ return result.ok;
1085
+ }
1086
+ function isRecord(value) {
1087
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1088
+ }
1089
+ function help() {
1090
+ console.log(`PI WEB
1091
+
1092
+ Usage:
1093
+ pi-web install [--dev] [--host 127.0.0.1] [--port 8504] [--config ~/.config/pi-web/config.json]
1094
+ pi-web uninstall
1095
+ pi-web start|stop|restart|status|logs
1096
+ pi-web plugins disable <plugin-id> [--config <path>] [--restart]
1097
+ pi-web plugins safe-start show|clear|set <bundled-only|none> [--config <path>] [--restart]
1098
+ pi-web doctor
1099
+ pi-web version
1100
+
1101
+ Recommended install:
1102
+ npm install -g @vincenthanxiaodu/pi-web --allow-scripts=node-pty
1103
+ pi-web install
1104
+
1105
+ Development service install from a checkout:
1106
+ pi-web install --dev
1107
+ `);
1108
+ }
1109
+ async function main() {
1110
+ const [command = "help", ...args] = process.argv.slice(2);
1111
+ if (command === "install")
1112
+ await install(args);
1113
+ else if (command === "uninstall")
1114
+ await uninstall();
1115
+ else if (command === "start" || command === "restart" || command === "doctor" || command === "version") {
1116
+ await runReadinessCliCommand(command, readinessCliCommandDependencies());
1117
+ }
1118
+ else if (command === "stop" || command === "status")
1119
+ await serviceAction(command);
1120
+ else if (command === "logs")
1121
+ logs();
1122
+ else if (command === "plugins")
1123
+ runPluginRecoveryCli(args, { restartPlan: (context) => sessionDaemonRestartPlan(context) });
1124
+ else if (command === "--version" || command === "-v")
1125
+ console.log(packageVersion());
1126
+ else if (command === "help" || command === "--help" || command === "-h")
1127
+ help();
1128
+ else
1129
+ throw new Error(`Unknown command: ${command}`);
1130
+ }
1131
+ export function isCliEntrypoint(entrypoint = process.argv[1], modulePath = fileURLToPath(import.meta.url)) {
1132
+ if (entrypoint === undefined)
1133
+ return false;
1134
+ if (entrypoint === modulePath)
1135
+ return true;
1136
+ try {
1137
+ return realpathSync(entrypoint) === realpathSync(modulePath);
1138
+ }
1139
+ catch {
1140
+ return false;
1141
+ }
1142
+ }
1143
+ if (isCliEntrypoint()) {
1144
+ main().catch((error) => {
1145
+ console.error(error instanceof Error ? error.message : String(error));
1146
+ process.exit(1);
1147
+ });
1148
+ }
1149
+ //# sourceMappingURL=cli.js.map