@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
@@ -0,0 +1,52 @@
1
+ # PI WEB dual-entry workspace-provider example
2
+
3
+ This standalone package is a copyable starting point for a trusted PI WEB plugin with both browser API v2 and server API v1 entries. It claims only projects that opt in with a marker file, publishes non-secret metadata to the browser, and demonstrates a browser request routed to the server provider that owns the workspace.
4
+
5
+ The example imports only the supported package declarations:
6
+
7
+ - `@vincenthanxiaodu/pi-web/plugin-api`
8
+ - `@vincenthanxiaodu/pi-web/server-plugin-api`
9
+
10
+ It requires PI WEB `1.202608.1` or newer, the first release that provides those entrypoints and browser API v2.
11
+
12
+ ## Build and install
13
+
14
+ Copy `examples/workspace-provider-plugin/` out of the PI WEB repository or installed `@vincenthanxiaodu/pi-web` package, then run:
15
+
16
+ ```bash
17
+ npm install
18
+ npm run build
19
+ mkdir -p ~/.pi-web/plugins
20
+ ln -s "$PWD" ~/.pi-web/plugins/example-workspaces
21
+ ```
22
+
23
+ If `PI_WEB_DATA_DIR` is set, link into `$PI_WEB_DATA_DIR/plugins` instead. The link name must be a valid plugin id and must not collide with another package.
24
+
25
+ Opt one registered PI WEB project into this example provider:
26
+
27
+ ```bash
28
+ mkdir -p /absolute/path/to/project/.pi-web
29
+ touch /absolute/path/to/project/.pi-web/example-workspace-provider
30
+ ```
31
+
32
+ Then restart the target session daemon and reload the browser:
33
+
34
+ ```bash
35
+ systemctl --user restart pi-web-sessiond
36
+ ```
37
+
38
+ Restarting `pi-web-sessiond.service` may interrupt active sessions and runtime ownership. A browser or web/API restart alone does not activate a server entry.
39
+
40
+ Open the opted-in project. The **Example Provider** panel displays the public marker metadata and its button calls the owning server provider's `summary` operation. Remove the marker and restart sessiond (or trigger a later workspace resolution) to stop claiming that project.
41
+
42
+ ## Boundary demonstrated by the package
43
+
44
+ `package.json` declares:
45
+
46
+ - browser module `dist/browser/index.js` with `browserRoot: "dist/browser"`;
47
+ - server module `dist/server.js`, outside the browser root;
48
+ - `machineSpecific: true`, required for a dual browser/server entry.
49
+
50
+ Only files under `dist/browser/` can be served through this plugin's browser asset route. Source, package metadata, the server module, and dependencies remain outside that route. Keep secrets out of `publicMetadata`: it is visible to every browser script and API consumer.
51
+
52
+ The server provider intentionally does not advertise workspace removal. See the canonical plugin guide for removal-plan shell and completion semantics, package limits, federation behavior, and the complete API contract: <https://pi-web.dev/plugins>.
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "pi-web-example-workspace-provider",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "build": "tsc -p tsconfig.json"
8
+ },
9
+ "devDependencies": {
10
+ "@vincenthanxiaodu/pi-web": "^1.202608.1",
11
+ "@types/node": "^24.13.3",
12
+ "typescript": "^6.0.3"
13
+ },
14
+ "piWeb": {
15
+ "plugins": [
16
+ {
17
+ "id": "example-workspaces",
18
+ "browserRoot": "dist/browser",
19
+ "module": "dist/browser/index.js",
20
+ "serverModule": "dist/server.js",
21
+ "machineSpecific": true
22
+ }
23
+ ]
24
+ }
25
+ }
@@ -0,0 +1,69 @@
1
+ import type { JsonObject, PiWebPlugin, WorkspacePanelContext } from "@vincenthanxiaodu/pi-web/plugin-api";
2
+
3
+ const summaries = new Map<string, string>();
4
+
5
+ const plugin: PiWebPlugin = {
6
+ apiVersion: 2,
7
+ name: "Example Workspace Provider",
8
+ activate: ({ pluginId, runtimePluginId, html }) => ({
9
+ contributions: {
10
+ actions: [
11
+ {
12
+ id: "workspace.open",
13
+ title: "Open Example Workspace Provider",
14
+ enabled: ({ state }) => state.selectedWorkspace?.provider?.pluginId === pluginId,
15
+ run: ({ selectWorkspaceTool }) => {
16
+ selectWorkspaceTool(`${runtimePluginId}:workspace.example-provider`);
17
+ },
18
+ },
19
+ ],
20
+ workspacePanels: [
21
+ {
22
+ id: "workspace.example-provider",
23
+ title: "Example Provider",
24
+ visible: ({ workspace }) => workspace.provider?.pluginId === pluginId,
25
+ render: (context) => {
26
+ const marker = stringMetadata(context.workspace.provider?.metadata, "marker") ?? "unknown";
27
+ const summary = summaries.get(workspaceKey(context)) ?? "Request a summary from the owning server plugin.";
28
+ return html`
29
+ <section class="toolbar"><strong>Example workspace provider</strong></section>
30
+ <section class="viewer">
31
+ <p>This workspace is owned by <code>${pluginId}</code>.</p>
32
+ <p class="muted">Claim marker: <code>${marker}</code></p>
33
+ <button
34
+ ?disabled=${context.backend === undefined}
35
+ @click=${() => { void refreshSummary(context); }}
36
+ >Request backend summary</button>
37
+ <p aria-live="polite">${summary}</p>
38
+ </section>
39
+ `;
40
+ },
41
+ },
42
+ ],
43
+ },
44
+ }),
45
+ };
46
+
47
+ export default plugin;
48
+
49
+ async function refreshSummary(context: WorkspacePanelContext): Promise<void> {
50
+ const key = workspaceKey(context);
51
+ try {
52
+ if (context.backend === undefined) throw new Error("The paired workspace backend is unavailable");
53
+ const result = await context.backend.request("summary", null);
54
+ if (typeof result !== "string") throw new Error("The workspace backend returned an invalid summary");
55
+ summaries.set(key, result);
56
+ } catch (error) {
57
+ summaries.set(key, error instanceof Error ? error.message : String(error));
58
+ }
59
+ context.host.requestRender();
60
+ }
61
+
62
+ function workspaceKey(context: WorkspacePanelContext): string {
63
+ return `${context.machine.id}:${context.workspace.id}`;
64
+ }
65
+
66
+ function stringMetadata(metadata: JsonObject | undefined, key: string): string | undefined {
67
+ const value = metadata?.[key];
68
+ return typeof value === "string" ? value : undefined;
69
+ }
@@ -0,0 +1,64 @@
1
+ import { access } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import type {
4
+ PiWebServerPlugin,
5
+ ProjectInput,
6
+ ProviderClaim,
7
+ ProviderRequestContext,
8
+ ProviderWorkspace,
9
+ WorkspaceProvider,
10
+ } from "@vincenthanxiaodu/pi-web/server-plugin-api";
11
+
12
+ const markerPath = ".pi-web/example-workspace-provider";
13
+
14
+ const plugin: PiWebServerPlugin = {
15
+ apiVersion: 1,
16
+ name: "Example Workspace Provider",
17
+ activate({ pluginId, logger }) {
18
+ logger.info("Activating example workspace provider", { pluginId });
19
+ return { workspaceProvider: createWorkspaceProvider(pluginId) };
20
+ },
21
+ };
22
+
23
+ export default plugin;
24
+
25
+ function createWorkspaceProvider(pluginId: string): WorkspaceProvider {
26
+ return {
27
+ async probe(project: ProjectInput, signal: AbortSignal): Promise<ProviderClaim> {
28
+ signal.throwIfAborted();
29
+ try {
30
+ await access(join(project.path, markerPath));
31
+ signal.throwIfAborted();
32
+ return "claim";
33
+ } catch (error) {
34
+ if (isMissingFile(error)) return "pass";
35
+ throw error;
36
+ }
37
+ },
38
+ async list(project: ProjectInput, signal: AbortSignal): Promise<ProviderWorkspace[]> {
39
+ signal.throwIfAborted();
40
+ return [{
41
+ key: "main",
42
+ path: project.path,
43
+ label: project.name,
44
+ isMain: true,
45
+ // This object is visible to every browser script and API consumer.
46
+ publicMetadata: { kind: "example", marker: markerPath },
47
+ }];
48
+ },
49
+ request(context: ProviderRequestContext) {
50
+ context.signal.throwIfAborted();
51
+ if (context.operation !== "summary") {
52
+ throw new Error(`Unsupported example workspace operation: ${context.operation}`);
53
+ }
54
+ return Promise.resolve(`${pluginId} owns ${context.workspace.label} at ${context.workspace.path}`);
55
+ },
56
+ };
57
+ }
58
+
59
+ function isMissingFile(error: unknown): boolean {
60
+ return typeof error === "object"
61
+ && error !== null
62
+ && "code" in error
63
+ && Reflect.get(error, "code") === "ENOENT";
64
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "lib": ["ES2022", "DOM"],
7
+ "types": ["node"],
8
+ "rootDir": "src",
9
+ "outDir": "dist",
10
+ "strict": true,
11
+ "exactOptionalPropertyTypes": true,
12
+ "noUncheckedIndexedAccess": true,
13
+ "noImplicitReturns": true,
14
+ "noFallthroughCasesInSwitch": true,
15
+ "forceConsistentCasingInFileNames": true,
16
+ "verbatimModuleSyntax": true,
17
+ "skipLibCheck": false,
18
+ "noEmitOnError": true
19
+ },
20
+ "include": ["src/**/*.ts"]
21
+ }
@@ -0,0 +1,45 @@
1
+ # `/pi-web-restart` is now `/pi-web restart`
2
+
3
+ The standalone `/pi-web-restart` command (a personal extension at
4
+ `~/.pi/agent/extensions/pi-web-restart.ts`) is superseded by subcommands of the
5
+ built-in `/pi-web` command.
6
+
7
+ | Old | New |
8
+ |---|---|
9
+ | `/pi-web-restart` | `/pi-web restart` |
10
+ | `/pi-web-restart all` | `/pi-web restart --all` |
11
+ | `/pi-web-restart --update` | `/pi-web update` |
12
+ | `/pi-web-restart --update all` | `/pi-web update --all` |
13
+ | (new) | `/pi-web machines` — list machines with version and online state |
14
+ | (new) | `/pi-web restart --machine=<name>` — one machine by name or id |
15
+ | (new) | `/pi-web status --all` — every machine's version |
16
+
17
+ ## What changed beyond the name
18
+
19
+ `--all` is now **hub-scoped and stated**. The old command fanned out from the
20
+ machine list of whichever session it ran in, so the covered set depended on
21
+ where you were standing. The new command asks the PI WEB server this session
22
+ runs on to fan out over the machines *it* knows, and every report names that
23
+ server plus each machine's outcome and version. Restart and update also have a
24
+ visible home now: **Settings ▸ Machines ▸ Machines and updates**.
25
+
26
+ ## Removing the old extension
27
+
28
+ Delete `~/.pi/agent/extensions/pi-web-restart.ts` and `/reload`. Nothing depends
29
+ on it; the built-in command covers every case above.
30
+
31
+ ## Making the built-in command available
32
+
33
+ The `/pi-web` command lives in this package's `extensions/` directory, which pi
34
+ loads when the package is registered. Installing PI WEB from a release tarball
35
+ does not register it, so the command is absent until the release directory is
36
+ added as a local package:
37
+
38
+ ```
39
+ # ~/.pi/agent/settings.json, "packages"
40
+ "/home/you/pi-web-release/current"
41
+ ```
42
+
43
+ Point it at the `current` symlink rather than a version directory, so the
44
+ command follows the deployment instead of pinning to whichever release happened
45
+ to be installed the day it was set up. Then `/reload`.
@@ -0,0 +1,324 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
7
+
8
+ const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
9
+ const cliPath = join(packageRoot, "dist", "cli.js");
10
+ const serviceNames = ["pi-web-sessiond.service", "pi-web.service", "pi-web-ui-dev.service"];
11
+ const macLogPaths = ["sessiond.log", "web.log", "ui-dev.log"].map((name) => join(homedir(), ".pi-web", "logs", name));
12
+
13
+ const subcommands = [
14
+ "install",
15
+ "status",
16
+ "logs",
17
+ "restart",
18
+ "update",
19
+ "machines",
20
+ "start",
21
+ "stop",
22
+ "doctor",
23
+ "version",
24
+ "uninstall",
25
+ "open",
26
+ "help",
27
+ ] as const;
28
+
29
+ type Subcommand = (typeof subcommands)[number];
30
+
31
+ /**
32
+ * Subcommands that can address more than this machine.
33
+ *
34
+ * Fan-out is done by the PI WEB server, not here: the scope of "every machine"
35
+ * is then the machine list of one named server, which the report states, rather
36
+ * than whichever machine happened to own the session this command ran in.
37
+ */
38
+ const FLEET_SUBCOMMANDS = new Set<Subcommand>(["restart", "update", "machines"]);
39
+
40
+ interface FleetIdentity { machineId: string; name: string }
41
+ interface FleetTargetReport extends FleetIdentity { kind: string; online: boolean; version?: string; piVersion?: string; error?: string }
42
+ interface FleetReport { hub: FleetIdentity; machines: FleetTargetReport[] }
43
+ interface FleetOutcome extends FleetIdentity { started: boolean; error?: string }
44
+ interface FleetRunResponse { operation: string; hub: FleetIdentity; outcomes: FleetOutcome[] }
45
+
46
+ interface ParsedFlags {
47
+ all: boolean;
48
+ machines: string[];
49
+ rest: string[];
50
+ }
51
+
52
+ function parseFlags(args: string[]): ParsedFlags {
53
+ const machines: string[] = [];
54
+ const rest: string[] = [];
55
+ let all = false;
56
+ for (let index = 0; index < args.length; index++) {
57
+ const arg = args[index] ?? "";
58
+ if (arg === "--all" || arg === "all") { all = true; continue; }
59
+ if (arg.startsWith("--machine=")) { machines.push(arg.slice("--machine=".length)); continue; }
60
+ if (arg === "--machine") { const next = args[index + 1]; if (next !== undefined) { machines.push(next); index++; } continue; }
61
+ rest.push(arg);
62
+ }
63
+ return { all, machines, rest };
64
+ }
65
+
66
+ /**
67
+ * The PI WEB server this agent process can reach. A session runs on the machine
68
+ * that owns it, so this is that machine's server - which the report names, so a
69
+ * fan-out is never mistaken for one started from a different browser's hub.
70
+ */
71
+ function piWebBaseUrl(): string {
72
+ const advertised = process.env["PI_WEB_BASE_URL"];
73
+ if (advertised !== undefined && advertised !== "") return advertised.replace(/\/$/, "");
74
+ const port = process.env["PI_WEB_PORT"];
75
+ return `http://127.0.0.1:${port !== undefined && port !== "" ? port : "8504"}`;
76
+ }
77
+
78
+ async function fleetRequest(path: string, init?: { method: string; body: unknown }): Promise<unknown> {
79
+ const response = await fetch(`${piWebBaseUrl()}${path}`, {
80
+ method: init?.method ?? "GET",
81
+ headers: { accept: "application/json", ...(init === undefined ? {} : { "content-type": "application/json" }) },
82
+ ...(init === undefined ? {} : { body: JSON.stringify(init.body) }),
83
+ });
84
+ if (!response.ok) throw new Error(`${init?.method ?? "GET"} ${path} failed: ${String(response.status)}`);
85
+ return await response.json();
86
+ }
87
+
88
+ /**
89
+ * Read the server's answer defensively.
90
+ *
91
+ * The response crosses a version boundary - an older or newer PI WEB may be
92
+ * answering - so the shape is checked rather than asserted, and a machine that
93
+ * does not parse is reported as such instead of rendering as "undefined".
94
+ */
95
+ function isRecord(value: unknown): value is Record<string, unknown> {
96
+ return typeof value === "object" && value !== null;
97
+ }
98
+
99
+ function readIdentity(value: unknown): FleetIdentity {
100
+ if (!isRecord(value)) return { machineId: "unknown", name: "unknown machine" };
101
+ return {
102
+ machineId: typeof value["machineId"] === "string" ? value["machineId"] : "unknown",
103
+ name: typeof value["name"] === "string" ? value["name"] : "unknown machine",
104
+ };
105
+ }
106
+
107
+ function readString(value: unknown): string | undefined {
108
+ return typeof value === "string" ? value : undefined;
109
+ }
110
+
111
+ function readFleetReport(value: unknown): FleetReport {
112
+ const record = isRecord(value) ? value : {};
113
+ const machines = Array.isArray(record["machines"]) ? record["machines"] : [];
114
+ return {
115
+ hub: readIdentity(record["hub"]),
116
+ machines: machines.map((entry) => {
117
+ const machine = isRecord(entry) ? entry : {};
118
+ const version = readString(machine["version"]);
119
+ const piVersion = readString(machine["piVersion"]);
120
+ const error = readString(machine["error"]);
121
+ return {
122
+ ...readIdentity(entry),
123
+ kind: readString(machine["kind"]) ?? "unknown",
124
+ online: machine["online"] === true,
125
+ ...(version === undefined ? {} : { version }),
126
+ ...(piVersion === undefined ? {} : { piVersion }),
127
+ ...(error === undefined ? {} : { error }),
128
+ };
129
+ }),
130
+ };
131
+ }
132
+
133
+ function readFleetRun(value: unknown): FleetRunResponse {
134
+ const record = isRecord(value) ? value : {};
135
+ const outcomes = Array.isArray(record["outcomes"]) ? record["outcomes"] : [];
136
+ return {
137
+ operation: readString(record["operation"]) ?? "operation",
138
+ hub: readIdentity(record["hub"]),
139
+ outcomes: outcomes.map((entry) => {
140
+ const outcome = isRecord(entry) ? entry : {};
141
+ const error = readString(outcome["error"]);
142
+ return { ...readIdentity(entry), started: outcome["started"] === true, ...(error === undefined ? {} : { error }) };
143
+ }),
144
+ };
145
+ }
146
+
147
+ function formatFleetReport(report: FleetReport): string {
148
+ const rows = report.machines.map((machine) => {
149
+ const state = machine.online ? "online" : `offline${machine.error === undefined ? "" : ` (${machine.error})`}`;
150
+ const version = machine.version === undefined ? "version unknown" : `pi-web ${machine.version}`;
151
+ const pi = machine.piVersion === undefined ? "" : `, pi ${machine.piVersion}`;
152
+ return ` ${machine.name} [${machine.kind}] — ${state}, ${version}${pi}`;
153
+ });
154
+ return [`Machines known to ${report.hub.name}:`, ...rows].join("\n");
155
+ }
156
+
157
+ function formatFleetRun(response: FleetRunResponse, scope: string): string {
158
+ const lines = response.outcomes.map((outcome) => ` ${outcome.started ? "started" : "FAILED "} ${outcome.name}${outcome.error === undefined ? "" : ` — ${outcome.error}`}`);
159
+ const failed = response.outcomes.filter((outcome) => !outcome.started).length;
160
+ const summary = `${response.operation} on ${scope}: ${String(response.outcomes.length - failed)}/${String(response.outcomes.length)} started`;
161
+ // Naming the server that fanned out is the whole point: "all" means the
162
+ // machines *it* knows, and a different machine may know a different set.
163
+ return [`${summary} (fanned out from ${response.hub.name})`, ...lines].join("\n");
164
+ }
165
+
166
+ function parseArgs(args: string): string[] {
167
+ return args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)?.map((part) => {
168
+ if ((part.startsWith('"') && part.endsWith('"')) || (part.startsWith("'") && part.endsWith("'"))) {
169
+ return part.slice(1, -1);
170
+ }
171
+ return part;
172
+ }) ?? [];
173
+ }
174
+
175
+ function truncateOutput(output: string): string {
176
+ const trimmed = output.trim();
177
+ if (trimmed.length <= 3_500) return trimmed;
178
+ return `${trimmed.slice(0, 3_500)}\n… output truncated`;
179
+ }
180
+
181
+ function run(command: string, args: string[], env: NodeJS.ProcessEnv = {}): Promise<{ code: number; output: string }> {
182
+ return new Promise((resolve) => {
183
+ const child = spawn(command, args, {
184
+ env: { ...process.env, ...env },
185
+ stdio: ["ignore", "pipe", "pipe"],
186
+ });
187
+
188
+ let output = "";
189
+ child.stdout.setEncoding("utf8");
190
+ child.stderr.setEncoding("utf8");
191
+ child.stdout.on("data", (chunk: string) => { output += chunk; });
192
+ child.stderr.on("data", (chunk: string) => { output += chunk; });
193
+ child.on("error", (error) => {
194
+ resolve({ code: 1, output: error.message });
195
+ });
196
+ child.on("close", (code) => {
197
+ resolve({ code: code ?? 1, output });
198
+ });
199
+ });
200
+ }
201
+
202
+ async function runPiWeb(args: string[], env: NodeJS.ProcessEnv = {}): Promise<{ code: number; output: string }> {
203
+ if (existsSync(cliPath)) {
204
+ return run(process.execPath, [cliPath, ...args], env);
205
+ }
206
+ return run("pi-web", args, env);
207
+ }
208
+
209
+ function showResult(ctx: { ui: { notify(message: string, type?: "info" | "warning" | "error" | "success"): void } }, title: string, result: { code: number; output: string }): void {
210
+ const body = truncateOutput(result.output) || (result.code === 0 ? "Done." : `Command failed with exit code ${String(result.code)}.`);
211
+ ctx.ui.notify(`${title}\n\n${body}`, result.code === 0 ? "info" : "error");
212
+ }
213
+
214
+ function isSubcommand(value: string): value is Subcommand {
215
+ return subcommands.some((command) => command === value);
216
+ }
217
+
218
+ async function boundedLogs(): Promise<{ code: number; output: string }> {
219
+ if (process.platform === "darwin") {
220
+ const existingLogs = macLogPaths.filter((path) => existsSync(path));
221
+ if (existingLogs.length === 0) return { code: 1, output: "No PI WEB log files found in ~/.pi-web/logs." };
222
+ return run("tail", ["-n", "100", ...existingLogs]);
223
+ }
224
+ return run("journalctl", ["--user", ...serviceNames.flatMap((serviceName) => ["-u", serviceName]), "-n", "100", "--no-pager"]);
225
+ }
226
+
227
+ export default function piWebExtension(pi: ExtensionAPI): void {
228
+ pi.registerCommand("pi-web", {
229
+ // The list is the discovery surface: a subcommand missing from here is one
230
+ // nobody finds. update and machines were added and left out of it.
231
+ description: "Manage PI WEB services and machines: status, machines, restart, update (add --all or --machine=<name>), logs, start, stop, install, doctor, version, open",
232
+ getArgumentCompletions(prefix: string): { value: string; label: string }[] | null {
233
+ const [first = ""] = parseArgs(prefix);
234
+ const items = subcommands
235
+ .filter((command) => command.startsWith(first))
236
+ .map((command) => ({ value: command, label: command }));
237
+ return items.length > 0 ? items : null;
238
+ },
239
+ async handler(args, ctx) {
240
+ const parsedArgs = parseArgs(args);
241
+ const subcommand = parsedArgs[0] ?? "help";
242
+ const rest = parsedArgs.slice(1);
243
+
244
+ if (subcommand === "help") {
245
+ ctx.ui.notify([
246
+ "PI WEB commands:",
247
+ "",
248
+ " /pi-web machines list the machines this server knows",
249
+ " /pi-web restart [--all] restart PI WEB here, or on every known machine",
250
+ " /pi-web update [--all] update PI WEB here, or on every known machine",
251
+ " /pi-web restart --machine=NAME restart one machine by name or id",
252
+ " /pi-web status [--all] service status here, or every machine's version",
253
+ " /pi-web logs last 100 service log lines",
254
+ " /pi-web install | start | stop | doctor | version | uninstall | open",
255
+ "",
256
+ "--all covers the machines known to the PI WEB server this session runs on;",
257
+ "every fan-out report names that server so the scope is never ambiguous.",
258
+ ].join("\n"), "info");
259
+ return;
260
+ }
261
+
262
+ if (subcommand === "open") {
263
+ ctx.ui.notify("PI WEB default URL: http://127.0.0.1:8504", "info");
264
+ return;
265
+ }
266
+
267
+ if (!isSubcommand(subcommand)) {
268
+ ctx.ui.notify(`Unknown pi-web command: ${subcommand}. Try /pi-web help.`, "error");
269
+ return;
270
+ }
271
+
272
+ if (subcommand === "stop" || subcommand === "uninstall") {
273
+ const ok = await ctx.ui.confirm(`pi-web ${subcommand}`, `Run pi-web ${subcommand}?`);
274
+ if (!ok) return;
275
+ }
276
+
277
+ if (subcommand === "logs") {
278
+ showResult(ctx, "pi-web logs", await boundedLogs());
279
+ return;
280
+ }
281
+
282
+ const flags = parseFlags(rest);
283
+ if (FLEET_SUBCOMMANDS.has(subcommand) && (subcommand === "machines" || flags.all || flags.machines.length > 0)) {
284
+ await runFleetCommand(ctx, subcommand, flags);
285
+ return;
286
+ }
287
+ if (subcommand === "status" && flags.all) {
288
+ await runFleetCommand(ctx, "machines", flags);
289
+ return;
290
+ }
291
+ if (subcommand === "machines" || subcommand === "update") {
292
+ // Both only exist as fleet operations; without a target they mean this
293
+ // machine, which the server can still answer for.
294
+ await runFleetCommand(ctx, subcommand, { ...flags, machines: ["local"] });
295
+ return;
296
+ }
297
+
298
+ showResult(ctx, `pi-web ${subcommand}`, await runPiWeb([subcommand, ...flags.rest]));
299
+ },
300
+ });
301
+ }
302
+
303
+ async function runFleetCommand(
304
+ ctx: { ui: { notify(message: string, type?: "info" | "warning" | "error" | "success"): void } },
305
+ subcommand: Subcommand,
306
+ flags: ParsedFlags,
307
+ ): Promise<void> {
308
+ try {
309
+ if (subcommand === "machines") {
310
+ ctx.ui.notify(formatFleetReport(readFleetReport(await fleetRequest("/api/pi-web/fleet"))), "info");
311
+ return;
312
+ }
313
+ const operation = subcommand === "update" ? "update" : "restart";
314
+ const scope = flags.all ? "every known machine" : flags.machines.join(", ");
315
+ const response = readFleetRun(await fleetRequest("/api/pi-web/fleet/run", {
316
+ method: "POST",
317
+ body: { operation, ...(flags.all ? {} : { machineIds: flags.machines }) },
318
+ }));
319
+ const failed = response.outcomes.filter((outcome) => !outcome.started).length;
320
+ ctx.ui.notify(formatFleetRun(response, scope), failed === 0 ? "info" : "error");
321
+ } catch (error) {
322
+ ctx.ui.notify(`pi-web ${subcommand} failed: ${error instanceof Error ? error.message : String(error)}`, "error");
323
+ }
324
+ }
package/install.sh ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env sh
2
+ set -eu
3
+
4
+ npm install -g @vincenthanxiaodu/pi-web --allow-scripts=node-pty
5
+ pi-web install