@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.
- package/LICENSE +21 -0
- package/README.md +185 -0
- package/dist/cli.js +1149 -0
- package/dist/cli.js.map +1 -0
- package/dist/client/apple-touch-icon.png +0 -0
- package/dist/client/assets/CodeViewer-D_Q6ljLV.js +4 -0
- package/dist/client/assets/TerminalPanel-CTGixJIz.js +187 -0
- package/dist/client/assets/index-C2phil_t.js +5637 -0
- package/dist/client/assets/vendor-editor-core-CXO8gGab.js +12 -0
- package/dist/client/assets/vendor-editor-languages-CpW4sJsX.js +46 -0
- package/dist/client/assets/vendor-editor-legacy-CYBnW6ZU.js +1 -0
- package/dist/client/assets/vendor-terminal-BrP-ENHg.css +1 -0
- package/dist/client/assets/vendor-terminal-D8k4UKM2.js +35 -0
- package/dist/client/favicon.svg +11 -0
- package/dist/client/index.html +124 -0
- package/dist/client/manifest.webmanifest +24 -0
- package/dist/client/pwa-icon-192.png +0 -0
- package/dist/client/pwa-icon-512.png +0 -0
- package/dist/config.js +505 -0
- package/dist/config.js.map +1 -0
- package/dist/docker/piWebDockerCommandPlan.js +212 -0
- package/dist/docker/piWebDockerCommandPlan.js.map +1 -0
- package/dist/environment.js +4 -0
- package/dist/environment.js.map +1 -0
- package/dist/nativeServices/installedServiceDefinitions.js +602 -0
- package/dist/nativeServices/installedServiceDefinitions.js.map +1 -0
- package/dist/nativeServices/serviceAction.js +174 -0
- package/dist/nativeServices/serviceAction.js.map +1 -0
- package/dist/nativeServices/serviceDoctor.js +783 -0
- package/dist/nativeServices/serviceDoctor.js.map +1 -0
- package/dist/nativeServices/serviceInstall.js +35 -0
- package/dist/nativeServices/serviceInstall.js.map +1 -0
- package/dist/nativeServices/servicePlan.js +358 -0
- package/dist/nativeServices/servicePlan.js.map +1 -0
- package/dist/nativeServices/serviceProbe.js +447 -0
- package/dist/nativeServices/serviceProbe.js.map +1 -0
- package/dist/nativeServices/serviceRendering.js +132 -0
- package/dist/nativeServices/serviceRendering.js.map +1 -0
- package/dist/pi-web-plugins/git/browser/git-contract.js +108 -0
- package/dist/pi-web-plugins/git/browser/git-panel.js +609 -0
- package/dist/pi-web-plugins/git/browser/gitFileList.js +43 -0
- package/dist/pi-web-plugins/git/browser/gitFileShared.js +16 -0
- package/dist/pi-web-plugins/git/browser/gitFileTree.js +81 -0
- package/dist/pi-web-plugins/git/browser/gitFileViewPreference.js +35 -0
- package/dist/pi-web-plugins/git/browser/gitRoute.js +42 -0
- package/dist/pi-web-plugins/git/browser/pi-web-plugin.js +10 -0
- package/dist/pi-web-plugins/git/browser/unifiedDiff.js +301 -0
- package/dist/pi-web-plugins/git/git-backend.js +439 -0
- package/dist/pi-web-plugins/git/package.json +16 -0
- package/dist/pi-web-plugins/git/server-plugin.js +176 -0
- package/dist/pi-web-plugins/info/infoInternals.js +222 -0
- package/dist/pi-web-plugins/info/package.json +9 -0
- package/dist/pi-web-plugins/info/pi-web-plugin.js +48 -0
- package/dist/pi-web-plugins/relays/markdownDocument.js +81 -0
- package/dist/pi-web-plugins/relays/package.json +9 -0
- package/dist/pi-web-plugins/relays/pi-web-plugin.js +44 -0
- package/dist/pi-web-plugins/relays/relayDiscovery.js +228 -0
- package/dist/pi-web-plugins/relays/relaysPanelElement.js +550 -0
- package/dist/pi-web-plugins/relays/vendor/README.md +24 -0
- package/dist/pi-web-plugins/relays/vendor/marked.esm.js +76 -0
- package/dist/pi-web-plugins/updates/package.json +9 -0
- package/dist/pi-web-plugins/updates/pi-web-plugin.js +185 -0
- package/dist/pi-web-plugins/updates/updatesLogic.js +97 -0
- package/dist/pi-web-plugins/workspace-tasks/config.js +91 -0
- package/dist/pi-web-plugins/workspace-tasks/package.json +9 -0
- package/dist/pi-web-plugins/workspace-tasks/pi-web-plugin.js +48 -0
- package/dist/pi-web-plugins/workspace-tasks/taskRunner.js +12 -0
- package/dist/pi-web-plugins/workspace-tasks/tasksPanelElement.js +288 -0
- package/dist/pi-web-plugins/workspace-tasks/workspaceTasksClient.js +47 -0
- package/dist/piWebVersionReport.js +278 -0
- package/dist/piWebVersionReport.js.map +1 -0
- package/dist/plugin-api.d.ts +222 -0
- package/dist/pluginRecoveryCli.js +172 -0
- package/dist/pluginRecoveryCli.js.map +1 -0
- package/dist/server/activeAgentProfileProvider.js +25 -0
- package/dist/server/activeAgentProfileProvider.js.map +1 -0
- package/dist/server/activity/workspaceActivityService.js +106 -0
- package/dist/server/activity/workspaceActivityService.js.map +1 -0
- package/dist/server/app.js +218 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/browserMessageProjection.js +54 -0
- package/dist/server/browserMessageProjection.js.map +1 -0
- package/dist/server/configRoutes.js +293 -0
- package/dist/server/configRoutes.js.map +1 -0
- package/dist/server/diagnostics/nodePtyNativeModule.js +36 -0
- package/dist/server/diagnostics/nodePtyNativeModule.js.map +1 -0
- package/dist/server/diagnostics/nodePtySpawnHelper.js +135 -0
- package/dist/server/diagnostics/nodePtySpawnHelper.js.map +1 -0
- package/dist/server/git/gitEnv.js +15 -0
- package/dist/server/git/gitEnv.js.map +1 -0
- package/dist/server/goals/goalArchive.js +184 -0
- package/dist/server/goals/goalArchive.js.map +1 -0
- package/dist/server/goals/goalFile.js +200 -0
- package/dist/server/goals/goalFile.js.map +1 -0
- package/dist/server/goals/goalStore.js +65 -0
- package/dist/server/goals/goalStore.js.map +1 -0
- package/dist/server/index.js +7 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/machines/machineClient.js +191 -0
- package/dist/server/machines/machineClient.js.map +1 -0
- package/dist/server/machines/machinePluginProxyRoutes.js +247 -0
- package/dist/server/machines/machinePluginProxyRoutes.js.map +1 -0
- package/dist/server/machines/machineProxyRoutes.js +318 -0
- package/dist/server/machines/machineProxyRoutes.js.map +1 -0
- package/dist/server/machines/machineRoutes.js +56 -0
- package/dist/server/machines/machineRoutes.js.map +1 -0
- package/dist/server/machines/machineService.js +274 -0
- package/dist/server/machines/machineService.js.map +1 -0
- package/dist/server/machines/machineStore.js +143 -0
- package/dist/server/machines/machineStore.js.map +1 -0
- package/dist/server/piPackageRoutes.js +91 -0
- package/dist/server/piPackageRoutes.js.map +1 -0
- package/dist/server/piPackageService.js +101 -0
- package/dist/server/piPackageService.js.map +1 -0
- package/dist/server/piWebPluginCatalog.js +584 -0
- package/dist/server/piWebPluginCatalog.js.map +1 -0
- package/dist/server/piWebPluginLifecycle.js +162 -0
- package/dist/server/piWebPluginLifecycle.js.map +1 -0
- package/dist/server/piWebPluginService.js +224 -0
- package/dist/server/piWebPluginService.js.map +1 -0
- package/dist/server/piWebReleaseLookupCache.js +37 -0
- package/dist/server/piWebReleaseLookupCache.js.map +1 -0
- package/dist/server/piWebStatus.js +624 -0
- package/dist/server/piWebStatus.js.map +1 -0
- package/dist/server/piWebStatusCache.js +45 -0
- package/dist/server/piWebStatusCache.js.map +1 -0
- package/dist/server/plugins/pluginBackendProxyRoutes.js +69 -0
- package/dist/server/plugins/pluginBackendProxyRoutes.js.map +1 -0
- package/dist/server/plugins/serverPluginExec.js +184 -0
- package/dist/server/plugins/serverPluginExec.js.map +1 -0
- package/dist/server/plugins/serverPluginRuntime.js +480 -0
- package/dist/server/plugins/serverPluginRuntime.js.map +1 -0
- package/dist/server/projectTrustRoutes.js +80 -0
- package/dist/server/projectTrustRoutes.js.map +1 -0
- package/dist/server/projects/directorySuggestions.js +207 -0
- package/dist/server/projects/directorySuggestions.js.map +1 -0
- package/dist/server/projects/projectService.js +33 -0
- package/dist/server/projects/projectService.js.map +1 -0
- package/dist/server/realtime/sessionEventHub.js +93 -0
- package/dist/server/realtime/sessionEventHub.js.map +1 -0
- package/dist/server/requestCancellation.js +32 -0
- package/dist/server/requestCancellation.js.map +1 -0
- package/dist/server/sessiond/agentHttpDispatcher.js +108 -0
- package/dist/server/sessiond/agentHttpDispatcher.js.map +1 -0
- package/dist/server/sessiond/agentProcessEnvironment.js +61 -0
- package/dist/server/sessiond/agentProcessEnvironment.js.map +1 -0
- package/dist/server/sessiond/pluginBackendRoutes.js +64 -0
- package/dist/server/sessiond/pluginBackendRoutes.js.map +1 -0
- package/dist/server/sessiond/sessionDaemonShutdown.js +31 -0
- package/dist/server/sessiond/sessionDaemonShutdown.js.map +1 -0
- package/dist/server/sessiond/sessionProxyRoutes.js +61 -0
- package/dist/server/sessiond/sessionProxyRoutes.js.map +1 -0
- package/dist/server/sessiond/sessionServiceDependencies.js +34 -0
- package/dist/server/sessiond/sessionServiceDependencies.js.map +1 -0
- package/dist/server/sessiond/sessiondStateOwnership.js +235 -0
- package/dist/server/sessiond/sessiondStateOwnership.js.map +1 -0
- package/dist/server/sessiond/workspaceCatalogRoutes.js +31 -0
- package/dist/server/sessiond/workspaceCatalogRoutes.js.map +1 -0
- package/dist/server/sessiond/workspaceRemovalRoutes.js +41 -0
- package/dist/server/sessiond/workspaceRemovalRoutes.js.map +1 -0
- package/dist/server/sessiond.js +387 -0
- package/dist/server/sessiond.js.map +1 -0
- package/dist/server/sessions/askUserTool.js +102 -0
- package/dist/server/sessions/askUserTool.js.map +1 -0
- package/dist/server/sessions/attachmentService.js +119 -0
- package/dist/server/sessions/attachmentService.js.map +1 -0
- package/dist/server/sessions/authProviderOptions.js +50 -0
- package/dist/server/sessions/authProviderOptions.js.map +1 -0
- package/dist/server/sessions/authRoutes.js +59 -0
- package/dist/server/sessions/authRoutes.js.map +1 -0
- package/dist/server/sessions/authService.js +122 -0
- package/dist/server/sessions/authService.js.map +1 -0
- package/dist/server/sessions/builtinCommands.js +27 -0
- package/dist/server/sessions/builtinCommands.js.map +1 -0
- package/dist/server/sessions/dockerEnvironmentFacts.js +254 -0
- package/dist/server/sessions/dockerEnvironmentFacts.js.map +1 -0
- package/dist/server/sessions/editPreview.js +196 -0
- package/dist/server/sessions/editPreview.js.map +1 -0
- package/dist/server/sessions/extensionDialogWaiters.js +76 -0
- package/dist/server/sessions/extensionDialogWaiters.js.map +1 -0
- package/dist/server/sessions/globalProviderPolicy.js +167 -0
- package/dist/server/sessions/globalProviderPolicy.js.map +1 -0
- package/dist/server/sessions/interruptedRunStore.js +147 -0
- package/dist/server/sessions/interruptedRunStore.js.map +1 -0
- package/dist/server/sessions/messagePaging.js +43 -0
- package/dist/server/sessions/messagePaging.js.map +1 -0
- package/dist/server/sessions/modelCatalogRefresher.js +193 -0
- package/dist/server/sessions/modelCatalogRefresher.js.map +1 -0
- package/dist/server/sessions/oauthLoginFlowService.js +346 -0
- package/dist/server/sessions/oauthLoginFlowService.js.map +1 -0
- package/dist/server/sessions/pendingAskStore.js +275 -0
- package/dist/server/sessions/pendingAskStore.js.map +1 -0
- package/dist/server/sessions/pendingExtensionDialogStore.js +196 -0
- package/dist/server/sessions/pendingExtensionDialogStore.js.map +1 -0
- package/dist/server/sessions/piSessionManagerGateway.js +246 -0
- package/dist/server/sessions/piSessionManagerGateway.js.map +1 -0
- package/dist/server/sessions/piSessionService.js +4059 -0
- package/dist/server/sessions/piSessionService.js.map +1 -0
- package/dist/server/sessions/plainTextTheme.js +112 -0
- package/dist/server/sessions/plainTextTheme.js.map +1 -0
- package/dist/server/sessions/sessionArchiveStore.js +273 -0
- package/dist/server/sessions/sessionArchiveStore.js.map +1 -0
- package/dist/server/sessions/sessionArchiveTree.js +35 -0
- package/dist/server/sessions/sessionArchiveTree.js.map +1 -0
- package/dist/server/sessions/sessionCleanup.js +157 -0
- package/dist/server/sessions/sessionCleanup.js.map +1 -0
- package/dist/server/sessions/sessionCommandService.js +336 -0
- package/dist/server/sessions/sessionCommandService.js.map +1 -0
- package/dist/server/sessions/sessionEnvironmentFacts.js +48 -0
- package/dist/server/sessions/sessionEnvironmentFacts.js.map +1 -0
- package/dist/server/sessions/sessionFileFormat.js +27 -0
- package/dist/server/sessions/sessionFileFormat.js.map +1 -0
- package/dist/server/sessions/sessionFileHeader.js +104 -0
- package/dist/server/sessions/sessionFileHeader.js.map +1 -0
- package/dist/server/sessions/sessionModelScope.js +141 -0
- package/dist/server/sessions/sessionModelScope.js.map +1 -0
- package/dist/server/sessions/sessionNameGenerator.js +90 -0
- package/dist/server/sessions/sessionNameGenerator.js.map +1 -0
- package/dist/server/sessions/sessionNotificationStore.js +484 -0
- package/dist/server/sessions/sessionNotificationStore.js.map +1 -0
- package/dist/server/sessions/sessionRoutes.js +744 -0
- package/dist/server/sessions/sessionRoutes.js.map +1 -0
- package/dist/server/sessions/sessionRuntimeStore.js +2 -0
- package/dist/server/sessions/sessionRuntimeStore.js.map +1 -0
- package/dist/server/sessions/sessionService.js +2 -0
- package/dist/server/sessions/sessionService.js.map +1 -0
- package/dist/server/sessions/sessionSummaryScanner.js +491 -0
- package/dist/server/sessions/sessionSummaryScanner.js.map +1 -0
- package/dist/server/sessions/sessionTreeProjection.js +250 -0
- package/dist/server/sessions/sessionTreeProjection.js.map +1 -0
- package/dist/server/sessions/sessionUnreadStore.js +520 -0
- package/dist/server/sessions/sessionUnreadStore.js.map +1 -0
- package/dist/server/sessions/shutdownDrain.js +73 -0
- package/dist/server/sessions/shutdownDrain.js.map +1 -0
- package/dist/server/sessions/spawnSessionTool.js +48 -0
- package/dist/server/sessions/spawnSessionTool.js.map +1 -0
- package/dist/server/sessions/spawnSubsessionTool.js +216 -0
- package/dist/server/sessions/spawnSubsessionTool.js.map +1 -0
- package/dist/server/sessions/spawnTargetResolver.js +23 -0
- package/dist/server/sessions/spawnTargetResolver.js.map +1 -0
- package/dist/server/sessions/subsessionTranscript.js +235 -0
- package/dist/server/sessions/subsessionTranscript.js.map +1 -0
- package/dist/server/status/machineStatusRoutes.js +8 -0
- package/dist/server/status/machineStatusRoutes.js.map +1 -0
- package/dist/server/status/machineStatusService.js +181 -0
- package/dist/server/status/machineStatusService.js.map +1 -0
- package/dist/server/status/workspaceAttribution.js +87 -0
- package/dist/server/status/workspaceAttribution.js.map +1 -0
- package/dist/server/storage/projectStore.js +88 -0
- package/dist/server/storage/projectStore.js.map +1 -0
- package/dist/server/terminalProxyRoutes.js +141 -0
- package/dist/server/terminalProxyRoutes.js.map +1 -0
- package/dist/server/terminals/terminalRoutes.js +155 -0
- package/dist/server/terminals/terminalRoutes.js.map +1 -0
- package/dist/server/terminals/terminalService.js +305 -0
- package/dist/server/terminals/terminalService.js.map +1 -0
- package/dist/server/terminals/terminalSize.js +17 -0
- package/dist/server/terminals/terminalSize.js.map +1 -0
- package/dist/server/types.js +2 -0
- package/dist/server/types.js.map +1 -0
- package/dist/server/updates/fleetRoutes.js +111 -0
- package/dist/server/updates/fleetRoutes.js.map +1 -0
- package/dist/server/updates/restartRoutes.js +48 -0
- package/dist/server/updates/restartRoutes.js.map +1 -0
- package/dist/server/updates/selfUpdateRoutes.js +145 -0
- package/dist/server/updates/selfUpdateRoutes.js.map +1 -0
- package/dist/server/webSocketBridge.js +32 -0
- package/dist/server/webSocketBridge.js.map +1 -0
- package/dist/server/workingDirectory.js +44 -0
- package/dist/server/workingDirectory.js.map +1 -0
- package/dist/server/workspaceExplorerRoutes.js +147 -0
- package/dist/server/workspaceExplorerRoutes.js.map +1 -0
- package/dist/server/workspaces/effectivePathAccess.js +8 -0
- package/dist/server/workspaces/effectivePathAccess.js.map +1 -0
- package/dist/server/workspaces/fileContentService.js +190 -0
- package/dist/server/workspaces/fileContentService.js.map +1 -0
- package/dist/server/workspaces/filePreviewResponseHeaders.js +18 -0
- package/dist/server/workspaces/filePreviewResponseHeaders.js.map +1 -0
- package/dist/server/workspaces/filePreviewResponsePolicy.js +55 -0
- package/dist/server/workspaces/filePreviewResponsePolicy.js.map +1 -0
- package/dist/server/workspaces/filePreviewService.js +81 -0
- package/dist/server/workspaces/filePreviewService.js.map +1 -0
- package/dist/server/workspaces/fileSuggestions.js +520 -0
- package/dist/server/workspaces/fileSuggestions.js.map +1 -0
- package/dist/server/workspaces/fileTreeService.js +35 -0
- package/dist/server/workspaces/fileTreeService.js.map +1 -0
- package/dist/server/workspaces/pathAccessPolicy.js +90 -0
- package/dist/server/workspaces/pathAccessPolicy.js.map +1 -0
- package/dist/server/workspaces/pathSafety.js +45 -0
- package/dist/server/workspaces/pathSafety.js.map +1 -0
- package/dist/server/workspaces/projectPiWebConfig.js +61 -0
- package/dist/server/workspaces/projectPiWebConfig.js.map +1 -0
- package/dist/server/workspaces/projectWorkspaceCwds.js +17 -0
- package/dist/server/workspaces/projectWorkspaceCwds.js.map +1 -0
- package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js +402 -0
- package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js.map +1 -0
- package/dist/server/workspaces/workspaceCatalog.js +47 -0
- package/dist/server/workspaces/workspaceCatalog.js.map +1 -0
- package/dist/server/workspaces/workspaceContext.js +6 -0
- package/dist/server/workspaces/workspaceContext.js.map +1 -0
- package/dist/server/workspaces/workspaceDeletionRoutes.js +49 -0
- package/dist/server/workspaces/workspaceDeletionRoutes.js.map +1 -0
- package/dist/server/workspaces/workspaceProviderRegistry.js +651 -0
- package/dist/server/workspaces/workspaceProviderRegistry.js.map +1 -0
- package/dist/server/workspaces/workspaceRemovalService.js +256 -0
- package/dist/server/workspaces/workspaceRemovalService.js.map +1 -0
- package/dist/server/workspaces/workspaceRouteErrors.js +7 -0
- package/dist/server/workspaces/workspaceRouteErrors.js.map +1 -0
- package/dist/server/workspaces/worktreePreRemoveHook.js +39 -0
- package/dist/server/workspaces/worktreePreRemoveHook.js.map +1 -0
- package/dist/server-plugin-api.d.ts +140 -0
- package/dist/server-plugin-api.js +2 -0
- package/dist/server-plugin-api.js.map +1 -0
- package/dist/serverPluginRecovery.js +138 -0
- package/dist/serverPluginRecovery.js.map +1 -0
- package/dist/sessiond/activeAgentProfile.js +12 -0
- package/dist/sessiond/activeAgentProfile.js.map +1 -0
- package/dist/sessiond/config.js +20 -0
- package/dist/sessiond/config.js.map +1 -0
- package/dist/sessiond/sessionDaemonClient.js +105 -0
- package/dist/sessiond/sessionDaemonClient.js.map +1 -0
- package/dist/shared/activeAgentProfile.js +37 -0
- package/dist/shared/activeAgentProfile.js.map +1 -0
- package/dist/shared/activity.js +16 -0
- package/dist/shared/activity.js.map +1 -0
- package/dist/shared/apiTypes.js +41 -0
- package/dist/shared/apiTypes.js.map +1 -0
- package/dist/shared/capabilities.js +37 -0
- package/dist/shared/capabilities.js.map +1 -0
- package/dist/shared/federatedRoutes.js +127 -0
- package/dist/shared/federatedRoutes.js.map +1 -0
- package/dist/shared/machinePluginIds.js +43 -0
- package/dist/shared/machinePluginIds.js.map +1 -0
- package/dist/shared/machineStatus.js +94 -0
- package/dist/shared/machineStatus.js.map +1 -0
- package/dist/shared/piWebStatusParsing.js +141 -0
- package/dist/shared/piWebStatusParsing.js.map +1 -0
- package/dist/shared/pluginApiTypes.d.ts +150 -0
- package/dist/shared/pluginApiTypes.js +3 -0
- package/dist/shared/pluginApiTypes.js.map +1 -0
- package/dist/shared/pluginBackendProtocol.js +110 -0
- package/dist/shared/pluginBackendProtocol.js.map +1 -0
- package/dist/shared/pluginIds.js +10 -0
- package/dist/shared/pluginIds.js.map +1 -0
- package/dist/shared/pluginRecoveryCommands.js +14 -0
- package/dist/shared/pluginRecoveryCommands.js.map +1 -0
- package/dist/shared/promptAttachments.js +92 -0
- package/dist/shared/promptAttachments.js.map +1 -0
- package/dist/shared/selfUpdate.js +3 -0
- package/dist/shared/selfUpdate.js.map +1 -0
- package/dist/shared/thinkingLevels.js +31 -0
- package/dist/shared/thinkingLevels.js.map +1 -0
- package/dist/shared/workspaceDeletion.js +12 -0
- package/dist/shared/workspaceDeletion.js.map +1 -0
- package/dist/shared/workspaceFiles.js +42 -0
- package/dist/shared/workspaceFiles.js.map +1 -0
- package/dist/shared/workspaceRemovalProtocol.js +24 -0
- package/dist/shared/workspaceRemovalProtocol.js.map +1 -0
- package/docs/assets/favicon.svg +11 -0
- package/docs/assets/pi-web-banner.png +0 -0
- package/docs/assets/pi-web-desktop.png +0 -0
- package/docs/assets/pi-web-mobile.png +0 -0
- package/docs/assets/pi-web-tablet.png +0 -0
- package/docs/config.md +444 -0
- package/docs/plugins.md +1305 -0
- package/examples/workspace-provider-plugin/README.md +52 -0
- package/examples/workspace-provider-plugin/package.json +25 -0
- package/examples/workspace-provider-plugin/src/browser/index.ts +69 -0
- package/examples/workspace-provider-plugin/src/server.ts +64 -0
- package/examples/workspace-provider-plugin/tsconfig.json +21 -0
- package/extensions/README-pi-web-restart-migration.md +45 -0
- package/extensions/pi-web.ts +324 -0
- package/install.sh +5 -0
- package/package.json +173 -0
- package/plugin-api.d.ts +1 -0
- 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
|
+
}
|