@agimon-ai/doompi-web 0.0.1-alpha.0
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 +29 -0
- package/README.md +196 -0
- package/dist/_virtual/_rolldown/runtime.cjs +1 -0
- package/dist/bin/serve.cjs +3 -0
- package/dist/bin/serve.cjs.map +1 -0
- package/dist/bin/serve.d.cts +1 -0
- package/dist/bin/serve.d.mts +1 -0
- package/dist/bin/serve.mjs +3 -0
- package/dist/bin/serve.mjs.map +1 -0
- package/dist/bundler.cjs +1 -0
- package/dist/bundler.d.cts +2 -0
- package/dist/bundler.d.mts +2 -0
- package/dist/bundler.mjs +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +7 -0
- package/dist/index.d.mts +7 -0
- package/dist/index.mjs +1 -0
- package/dist/src/adapters/authRoutes.cjs +2 -0
- package/dist/src/adapters/authRoutes.cjs.map +1 -0
- package/dist/src/adapters/authRoutes.mjs +2 -0
- package/dist/src/adapters/authRoutes.mjs.map +1 -0
- package/dist/src/adapters/bundledServer.cjs +2 -0
- package/dist/src/adapters/bundledServer.cjs.map +1 -0
- package/dist/src/adapters/bundledServer.mjs +2 -0
- package/dist/src/adapters/bundledServer.mjs.map +1 -0
- package/dist/src/adapters/directoryListing.cjs +2 -0
- package/dist/src/adapters/directoryListing.cjs.map +1 -0
- package/dist/src/adapters/directoryListing.mjs +2 -0
- package/dist/src/adapters/directoryListing.mjs.map +1 -0
- package/dist/src/adapters/directorySearch.cjs +3 -0
- package/dist/src/adapters/directorySearch.cjs.map +1 -0
- package/dist/src/adapters/directorySearch.mjs +3 -0
- package/dist/src/adapters/directorySearch.mjs.map +1 -0
- package/dist/src/adapters/gitStatus.cjs +2 -0
- package/dist/src/adapters/gitStatus.cjs.map +1 -0
- package/dist/src/adapters/gitStatus.mjs +2 -0
- package/dist/src/adapters/gitStatus.mjs.map +1 -0
- package/dist/src/adapters/httpServer.cjs +2 -0
- package/dist/src/adapters/httpServer.cjs.map +1 -0
- package/dist/src/adapters/httpServer.d.cts +14 -0
- package/dist/src/adapters/httpServer.d.cts.map +1 -0
- package/dist/src/adapters/httpServer.d.mts +14 -0
- package/dist/src/adapters/httpServer.d.mts.map +1 -0
- package/dist/src/adapters/httpServer.mjs +2 -0
- package/dist/src/adapters/httpServer.mjs.map +1 -0
- package/dist/src/adapters/hubProbe.cjs +2 -0
- package/dist/src/adapters/hubProbe.cjs.map +1 -0
- package/dist/src/adapters/hubProbe.mjs +2 -0
- package/dist/src/adapters/hubProbe.mjs.map +1 -0
- package/dist/src/adapters/packageApiProxy.cjs +2 -0
- package/dist/src/adapters/packageApiProxy.cjs.map +1 -0
- package/dist/src/adapters/packageApiProxy.mjs +2 -0
- package/dist/src/adapters/packageApiProxy.mjs.map +1 -0
- package/dist/src/adapters/piHubService.cjs +2 -0
- package/dist/src/adapters/piHubService.cjs.map +1 -0
- package/dist/src/adapters/piHubService.mjs +2 -0
- package/dist/src/adapters/piHubService.mjs.map +1 -0
- package/dist/src/adapters/piWebSocketListener.cjs +2 -0
- package/dist/src/adapters/piWebSocketListener.cjs.map +1 -0
- package/dist/src/adapters/piWebSocketListener.mjs +2 -0
- package/dist/src/adapters/piWebSocketListener.mjs.map +1 -0
- package/dist/src/adapters/providerAuth.cjs +2 -0
- package/dist/src/adapters/providerAuth.cjs.map +1 -0
- package/dist/src/adapters/providerAuth.mjs +2 -0
- package/dist/src/adapters/providerAuth.mjs.map +1 -0
- package/dist/src/adapters/registryWatcher.cjs +2 -0
- package/dist/src/adapters/registryWatcher.cjs.map +1 -0
- package/dist/src/adapters/registryWatcher.mjs +2 -0
- package/dist/src/adapters/registryWatcher.mjs.map +1 -0
- package/dist/src/adapters/serverSpawner.cjs +2 -0
- package/dist/src/adapters/serverSpawner.cjs.map +1 -0
- package/dist/src/adapters/serverSpawner.mjs +2 -0
- package/dist/src/adapters/serverSpawner.mjs.map +1 -0
- package/dist/src/adapters/sessionFiles.cjs +3 -0
- package/dist/src/adapters/sessionFiles.cjs.map +1 -0
- package/dist/src/adapters/sessionFiles.mjs +3 -0
- package/dist/src/adapters/sessionFiles.mjs.map +1 -0
- package/dist/src/adapters/sessionHub.cjs +2 -0
- package/dist/src/adapters/sessionHub.cjs.map +1 -0
- package/dist/src/adapters/sessionHub.mjs +2 -0
- package/dist/src/adapters/sessionHub.mjs.map +1 -0
- package/dist/src/adapters/sessionSocketClient.cjs +2 -0
- package/dist/src/adapters/sessionSocketClient.cjs.map +1 -0
- package/dist/src/adapters/sessionSocketClient.mjs +2 -0
- package/dist/src/adapters/sessionSocketClient.mjs.map +1 -0
- package/dist/src/adapters/syncGuard.cjs +2 -0
- package/dist/src/adapters/syncGuard.cjs.map +1 -0
- package/dist/src/adapters/syncGuard.mjs +2 -0
- package/dist/src/adapters/syncGuard.mjs.map +1 -0
- package/dist/src/adapters/threadJournals.cjs +2 -0
- package/dist/src/adapters/threadJournals.cjs.map +1 -0
- package/dist/src/adapters/threadJournals.mjs +2 -0
- package/dist/src/adapters/threadJournals.mjs.map +1 -0
- package/dist/src/adapters/webBundler.cjs +2 -0
- package/dist/src/adapters/webBundler.cjs.map +1 -0
- package/dist/src/adapters/webBundler.d.cts +28 -0
- package/dist/src/adapters/webBundler.d.cts.map +1 -0
- package/dist/src/adapters/webBundler.d.mts +28 -0
- package/dist/src/adapters/webBundler.d.mts.map +1 -0
- package/dist/src/adapters/webBundler.mjs +2 -0
- package/dist/src/adapters/webBundler.mjs.map +1 -0
- package/dist/src/adapters/webHubPluginLoader.cjs +2 -0
- package/dist/src/adapters/webHubPluginLoader.cjs.map +1 -0
- package/dist/src/adapters/webHubPluginLoader.mjs +2 -0
- package/dist/src/adapters/webHubPluginLoader.mjs.map +1 -0
- package/dist/src/adapters/webHubPlugins.generated.cjs +2 -0
- package/dist/src/adapters/webHubPlugins.generated.cjs.map +1 -0
- package/dist/src/adapters/webHubPlugins.generated.mjs +2 -0
- package/dist/src/adapters/webHubPlugins.generated.mjs.map +1 -0
- package/dist/src/adapters/webPluginGenerate.cjs +4 -0
- package/dist/src/adapters/webPluginGenerate.cjs.map +1 -0
- package/dist/src/adapters/webPluginGenerate.mjs +4 -0
- package/dist/src/adapters/webPluginGenerate.mjs.map +1 -0
- package/dist/src/adapters/webPluginScan.cjs +2 -0
- package/dist/src/adapters/webPluginScan.cjs.map +1 -0
- package/dist/src/adapters/webPluginScan.mjs +2 -0
- package/dist/src/adapters/webPluginScan.mjs.map +1 -0
- package/dist/src/adapters/webPluginVite.cjs +2 -0
- package/dist/src/adapters/webPluginVite.cjs.map +1 -0
- package/dist/src/adapters/webPluginVite.mjs +2 -0
- package/dist/src/adapters/webPluginVite.mjs.map +1 -0
- package/dist/src/services/directoryMatch.cjs +2 -0
- package/dist/src/services/directoryMatch.cjs.map +1 -0
- package/dist/src/services/directoryMatch.mjs +2 -0
- package/dist/src/services/directoryMatch.mjs.map +1 -0
- package/dist/src/services/fileMatch.cjs +2 -0
- package/dist/src/services/fileMatch.cjs.map +1 -0
- package/dist/src/services/fileMatch.mjs +2 -0
- package/dist/src/services/fileMatch.mjs.map +1 -0
- package/dist/src/services/fileMedia.cjs +2 -0
- package/dist/src/services/fileMedia.cjs.map +1 -0
- package/dist/src/services/fileMedia.mjs +2 -0
- package/dist/src/services/fileMedia.mjs.map +1 -0
- package/dist/src/services/frameRing.cjs +2 -0
- package/dist/src/services/frameRing.cjs.map +1 -0
- package/dist/src/services/frameRing.mjs +2 -0
- package/dist/src/services/frameRing.mjs.map +1 -0
- package/dist/src/services/journalTail.cjs +3 -0
- package/dist/src/services/journalTail.cjs.map +1 -0
- package/dist/src/services/journalTail.mjs +3 -0
- package/dist/src/services/journalTail.mjs.map +1 -0
- package/dist/src/services/loginFlow.cjs +2 -0
- package/dist/src/services/loginFlow.cjs.map +1 -0
- package/dist/src/services/loginFlow.mjs +2 -0
- package/dist/src/services/loginFlow.mjs.map +1 -0
- package/dist/src/services/registryStore.cjs +2 -0
- package/dist/src/services/registryStore.cjs.map +1 -0
- package/dist/src/services/registryStore.mjs +2 -0
- package/dist/src/services/registryStore.mjs.map +1 -0
- package/dist/src/services/retryPolicy.cjs +2 -0
- package/dist/src/services/retryPolicy.cjs.map +1 -0
- package/dist/src/services/retryPolicy.mjs +2 -0
- package/dist/src/services/retryPolicy.mjs.map +1 -0
- package/dist/src/services/serveOptions.cjs +2 -0
- package/dist/src/services/serveOptions.cjs.map +1 -0
- package/dist/src/services/serveOptions.d.cts +21 -0
- package/dist/src/services/serveOptions.d.cts.map +1 -0
- package/dist/src/services/serveOptions.d.mts +21 -0
- package/dist/src/services/serveOptions.d.mts.map +1 -0
- package/dist/src/services/serveOptions.mjs +2 -0
- package/dist/src/services/serveOptions.mjs.map +1 -0
- package/dist/src/services/sessionFraming.cjs +4 -0
- package/dist/src/services/sessionFraming.cjs.map +1 -0
- package/dist/src/services/sessionFraming.mjs +4 -0
- package/dist/src/services/sessionFraming.mjs.map +1 -0
- package/dist/src/services/sessionPresence.cjs +2 -0
- package/dist/src/services/sessionPresence.cjs.map +1 -0
- package/dist/src/services/sessionPresence.mjs +2 -0
- package/dist/src/services/sessionPresence.mjs.map +1 -0
- package/dist/src/services/staticAssets.cjs +2 -0
- package/dist/src/services/staticAssets.cjs.map +1 -0
- package/dist/src/services/staticAssets.mjs +2 -0
- package/dist/src/services/staticAssets.mjs.map +1 -0
- package/dist/src/services/webDevRoots.cjs +2 -0
- package/dist/src/services/webDevRoots.cjs.map +1 -0
- package/dist/src/services/webDevRoots.mjs +2 -0
- package/dist/src/services/webDevRoots.mjs.map +1 -0
- package/dist/src/services/webPluginManifest.cjs +2 -0
- package/dist/src/services/webPluginManifest.cjs.map +1 -0
- package/dist/src/services/webPluginManifest.mjs +2 -0
- package/dist/src/services/webPluginManifest.mjs.map +1 -0
- package/dist/src/types/auth.cjs +2 -0
- package/dist/src/types/auth.cjs.map +1 -0
- package/dist/src/types/auth.d.cts +94 -0
- package/dist/src/types/auth.d.cts.map +1 -0
- package/dist/src/types/auth.d.mts +94 -0
- package/dist/src/types/auth.d.mts.map +1 -0
- package/dist/src/types/auth.mjs +2 -0
- package/dist/src/types/auth.mjs.map +1 -0
- package/dist/src/types/bridge.d.cts +31 -0
- package/dist/src/types/bridge.d.cts.map +1 -0
- package/dist/src/types/bridge.d.mts +31 -0
- package/dist/src/types/bridge.d.mts.map +1 -0
- package/dist/src/types/hub.cjs +2 -0
- package/dist/src/types/hub.cjs.map +1 -0
- package/dist/src/types/hub.d.cts +58 -0
- package/dist/src/types/hub.d.cts.map +1 -0
- package/dist/src/types/hub.d.mts +58 -0
- package/dist/src/types/hub.d.mts.map +1 -0
- package/dist/src/types/hub.mjs +2 -0
- package/dist/src/types/hub.mjs.map +1 -0
- package/dist/src/types/media.cjs +2 -0
- package/dist/src/types/media.cjs.map +1 -0
- package/dist/src/types/media.mjs +2 -0
- package/dist/src/types/media.mjs.map +1 -0
- package/dist/src/types/registry.cjs +1 -0
- package/dist/src/types/registry.d.cts +2 -0
- package/dist/src/types/registry.d.mts +2 -0
- package/dist/src/types/registry.mjs +1 -0
- package/dist/src/types/session.cjs +2 -0
- package/dist/src/types/session.cjs.map +1 -0
- package/dist/src/types/session.d.cts +24 -0
- package/dist/src/types/session.d.cts.map +1 -0
- package/dist/src/types/session.d.mts +24 -0
- package/dist/src/types/session.d.mts.map +1 -0
- package/dist/src/types/session.mjs +2 -0
- package/dist/src/types/session.mjs.map +1 -0
- package/dist/web/apple-touch-icon.png +0 -0
- package/dist/web/assets/index-Cg52ngKv.css +2 -0
- package/dist/web/assets/index-DQO7NTIP.js +90 -0
- package/dist/web/assets/index-DQO7NTIP.js.map +1 -0
- package/dist/web/favicon-16.png +0 -0
- package/dist/web/favicon-32.png +0 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/favicon.svg +16 -0
- package/dist/web/icon-512.png +0 -0
- package/dist/web/index.html +17 -0
- package/llms.txt +8 -0
- package/package.json +106 -0
- package/src/adapters/authRoutes.ts +95 -0
- package/src/adapters/bundledServer.ts +64 -0
- package/src/adapters/directoryListing.ts +127 -0
- package/src/adapters/directorySearch.ts +124 -0
- package/src/adapters/gitStatus.ts +27 -0
- package/src/adapters/httpServer.ts +556 -0
- package/src/adapters/hubProbe.ts +27 -0
- package/src/adapters/packageApiProxy.ts +60 -0
- package/src/adapters/piHubService.ts +129 -0
- package/src/adapters/piWebSocketListener.ts +98 -0
- package/src/adapters/providerAuth.ts +181 -0
- package/src/adapters/registryWatcher.ts +112 -0
- package/src/adapters/serverSpawner.ts +181 -0
- package/src/adapters/sessionFiles.ts +99 -0
- package/src/adapters/sessionHub.ts +567 -0
- package/src/adapters/sessionSocketClient.ts +105 -0
- package/src/adapters/syncGuard.ts +119 -0
- package/src/adapters/threadJournals.ts +184 -0
- package/src/adapters/webBundler.ts +83 -0
- package/src/adapters/webHubPluginLoader.ts +67 -0
- package/src/adapters/webHubPlugins.generated.ts +4 -0
- package/src/adapters/webPluginGenerate.ts +172 -0
- package/src/adapters/webPluginScan.ts +62 -0
- package/src/adapters/webPluginVite.ts +51 -0
- package/src/bin/serve.ts +64 -0
- package/src/exports/bundler.ts +1 -0
- package/src/exports/index.ts +9 -0
- package/src/services/directoryMatch.ts +71 -0
- package/src/services/fileMatch.ts +37 -0
- package/src/services/fileMedia.ts +27 -0
- package/src/services/frameRing.ts +33 -0
- package/src/services/journalTail.ts +47 -0
- package/src/services/loginFlow.ts +144 -0
- package/src/services/registryStore.ts +83 -0
- package/src/services/retryPolicy.ts +15 -0
- package/src/services/serveOptions.ts +63 -0
- package/src/services/sessionFraming.ts +32 -0
- package/src/services/sessionPresence.ts +131 -0
- package/src/services/staticAssets.ts +37 -0
- package/src/services/webDevRoots.ts +36 -0
- package/src/services/webPluginManifest.ts +149 -0
- package/src/types/auth.ts +118 -0
- package/src/types/bridge.ts +41 -0
- package/src/types/hub.ts +290 -0
- package/src/types/media.ts +38 -0
- package/src/types/registry.ts +2 -0
- package/src/types/session.ts +36 -0
- package/src/web/app/Providers.tsx +46 -0
- package/src/web/app/protocolRuntime.ts +106 -0
- package/src/web/app/sessionRuntime.ts +189 -0
- package/src/web/app/webPlugins.generated.ts +4 -0
- package/src/web/components/MascotMark.tsx +46 -0
- package/src/web/components/PluginSurface.tsx +10 -0
- package/src/web/components/ToolRendererBoundary.tsx +32 -0
- package/src/web/features/activity/ActivityDock.tsx +125 -0
- package/src/web/features/connection/RefusedCard.tsx +63 -0
- package/src/web/features/dialogs/DialogOverlay.tsx +176 -0
- package/src/web/features/leader/CommandPalette.tsx +386 -0
- package/src/web/features/selection/SelectionBar.tsx +529 -0
- package/src/web/features/session/Composer.tsx +368 -0
- package/src/web/features/session/MentionPreviews.tsx +58 -0
- package/src/web/features/session/ThreadView.tsx +35 -0
- package/src/web/features/session/Timeline.tsx +382 -0
- package/src/web/features/session/ToolCard.tsx +79 -0
- package/src/web/features/sessions/NewSessionDialog.tsx +222 -0
- package/src/web/features/sessions/SessionRail.tsx +411 -0
- package/src/web/features/settings/AppearanceSettings.tsx +88 -0
- package/src/web/features/settings/LoginFlowDialog.tsx +221 -0
- package/src/web/features/settings/PluginSettings.tsx +92 -0
- package/src/web/features/settings/ProviderSettings.tsx +238 -0
- package/src/web/features/settings/SettingsMenu.tsx +32 -0
- package/src/web/features/status/TopBar.tsx +208 -0
- package/src/web/index.html +16 -0
- package/src/web/lib/authApi.ts +90 -0
- package/src/web/lib/commands.ts +61 -0
- package/src/web/lib/composition.ts +225 -0
- package/src/web/lib/fileMentions.ts +32 -0
- package/src/web/lib/hubApi.ts +86 -0
- package/src/web/lib/leaderTree.ts +159 -0
- package/src/web/lib/piTransport.ts +61 -0
- package/src/web/lib/pluginRegistry.ts +488 -0
- package/src/web/lib/pluginSlotProps.ts +50 -0
- package/src/web/lib/promptFocus.ts +25 -0
- package/src/web/lib/protocolTimeline.ts +73 -0
- package/src/web/lib/sessionModel.ts +673 -0
- package/src/web/lib/sessionSummary.ts +58 -0
- package/src/web/lib/settingsSections.ts +21 -0
- package/src/web/lib/statusLine.ts +90 -0
- package/src/web/lib/threadRenderer.ts +25 -0
- package/src/web/lib/toolMessageProps.ts +21 -0
- package/src/web/lib/transport.ts +30 -0
- package/src/web/lib/wsClient.ts +73 -0
- package/src/web/main.tsx +17 -0
- package/src/web/public/apple-touch-icon.png +0 -0
- package/src/web/public/favicon-16.png +0 -0
- package/src/web/public/favicon-32.png +0 -0
- package/src/web/public/favicon.ico +0 -0
- package/src/web/public/favicon.svg +16 -0
- package/src/web/public/icon-512.png +0 -0
- package/src/web/routes/CockpitPage.tsx +98 -0
- package/src/web/routes/SettingsPage.tsx +60 -0
- package/src/web/routes/routeTree.tsx +49 -0
- package/src/web/stores/menuStore.ts +78 -0
- package/src/web/stores/paletteStore.ts +25 -0
- package/src/web/stores/sessionStore.ts +303 -0
- package/src/web/stores/sessionsStore.ts +155 -0
- package/src/web/stores/themeStore.ts +52 -0
- package/src/web/stores/threadStore.ts +68 -0
- package/src/web/stores/transientTabsStore.ts +63 -0
- package/src/web/stores/uiStore.ts +28 -0
- package/src/web/stores/useOpenTab.ts +19 -0
- package/src/web/stores/usePluginSlotProps.ts +22 -0
- package/src/web/styles/app.css +15 -0
- package/src/web/styles/webPluginSources.generated.css +1 -0
- package/src/web/vite-env.d.ts +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
The DoomPi Web License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Agimon Pty Ltd
|
|
4
|
+
|
|
5
|
+
With regard to the DoomPi Web Software:
|
|
6
|
+
|
|
7
|
+
This software and associated documentation files (the "Software") may be
|
|
8
|
+
used, free of charge, by any person or entity, for any purpose, including
|
|
9
|
+
production and commercial use. You may copy the Software as reasonably
|
|
10
|
+
necessary to install, run, develop against, test, and back it up. You are
|
|
11
|
+
free to modify the Software for your own use and to publish patches to the
|
|
12
|
+
Software. You agree that the licensor retains all right, title, and interest
|
|
13
|
+
in and to the Software, and that your modifications and patches may only be
|
|
14
|
+
used, copied, modified, displayed, distributed, or otherwise exploited
|
|
15
|
+
together with the Software. You are not granted any other rights beyond what
|
|
16
|
+
is expressly stated herein. Subject to the foregoing, it is forbidden to
|
|
17
|
+
publish, distribute, sublicense, and/or sell the Software or any copy of it,
|
|
18
|
+
or to offer the Software to third parties as a hosted or managed service.
|
|
19
|
+
|
|
20
|
+
The above copyright notice and this permission notice shall be included in
|
|
21
|
+
all copies or substantial portions of the Software.
|
|
22
|
+
|
|
23
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
24
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
25
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
26
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
27
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
28
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
29
|
+
IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# @agimon-ai/doompi-web
|
|
2
|
+
|
|
3
|
+
Web cockpit for DoomPi: a multi-session hub that serves the browser console, discovers every
|
|
4
|
+
running [doompi-server](https://www.npmjs.com/package/@agimon-ai/doompi-server) through the session
|
|
5
|
+
registry, and multiplexes their authenticated sockets behind one page connection
|
|
6
|
+
|
|
7
|
+
This package is a composable [DoomPi](https://www.npmjs.com/package/@agimon-ai/doompi) subsystem.
|
|
8
|
+
It registers no Pi extension and adds nothing to an interactive session.
|
|
9
|
+
|
|
10
|
+
## Enabling it
|
|
11
|
+
|
|
12
|
+
This package is still in development, so `doompi init` does not reference it and there is nothing
|
|
13
|
+
to add to `.doom/modes.yaml`. It is a standalone executable you run yourself, as shown under Run
|
|
14
|
+
below, or the process `doompi-server --web` starts for you.
|
|
15
|
+
|
|
16
|
+
## Requirements
|
|
17
|
+
|
|
18
|
+
- Node.js 22.19.0 or newer
|
|
19
|
+
- Running `doompi-server` processes registering their session sockets
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install -g @agimon-ai/doompi-web
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Run
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
doompi-web
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Then open `http://127.0.0.1:7433`. That is the whole command: no flag is required, and every one
|
|
34
|
+
below is an override. `doompi-web` is the hub. It watches the session registry (`~/.doompi/run` by
|
|
35
|
+
default), attaches to every registered session, shows them all in the rail, and can start new ones
|
|
36
|
+
from the page. Sessions it starts run the `doompi-server` and the agent from this installation, so
|
|
37
|
+
the cockpit and the sessions it creates are always the same build.
|
|
38
|
+
|
|
39
|
+
Running it twice is not an error. The second one finds the first over the port, prints its URL, and
|
|
40
|
+
exits, because one hub already serves every session.
|
|
41
|
+
|
|
42
|
+
Working on this repository, one command builds what it needs and starts it:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pnpm cockpit
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Nx caches the build, so a warm run is effectively just the hub. Sessions created from the page run
|
|
49
|
+
this checkout's builds, which matters: a session on some other `doompi` is a different composition,
|
|
50
|
+
and its extensions, commands, and minor modes are not the ones being edited here, so a change looks
|
|
51
|
+
like it did nothing.
|
|
52
|
+
|
|
53
|
+
| Flag | Default | Meaning |
|
|
54
|
+
| ----------------- | --------------- | ------------------------------------------------ |
|
|
55
|
+
| `--registry-dir` | `~/.doompi/run` | Registry to watch (also `DOOMPI_RUNTIME_DIR`) |
|
|
56
|
+
| `--spawn-command` | this install | Command launching sessions created from the page |
|
|
57
|
+
| `--port` | `7433` | HTTP port |
|
|
58
|
+
| `--host` | `127.0.0.1` | Bind address |
|
|
59
|
+
| `--assets` | bundled | Override the built SPA directory |
|
|
60
|
+
|
|
61
|
+
Installing this package gives you `doompi-web` and nothing else. For a session server you run
|
|
62
|
+
yourself, install [doompi-server](https://www.npmjs.com/package/@agimon-ai/doompi-server); the hub
|
|
63
|
+
does not need it on PATH to create sessions.
|
|
64
|
+
|
|
65
|
+
## Security
|
|
66
|
+
|
|
67
|
+
Attach tokens are read by this process from the token files the registry names and never reach the
|
|
68
|
+
browser. The server binds loopback by default, so reaching the cockpit already means having an
|
|
69
|
+
account on the machine. Remote access means tunnelling the port, for example over SSH; do not bind
|
|
70
|
+
a public address and call it done.
|
|
71
|
+
|
|
72
|
+
## What the cockpit shows
|
|
73
|
+
|
|
74
|
+
Everything comes from Pi's RPC protocol, so a bare Pi session and a DoomPi session both work and
|
|
75
|
+
neither is shown facts it did not report:
|
|
76
|
+
|
|
77
|
+
- **Sessions rail:** every registered session with its live status line, git branch, and working
|
|
78
|
+
directory. Cards switch focus (digits `1`-`9` too), `ctrl+t` or the button creates a session in a
|
|
79
|
+
directory you pick, and the hub launches a `doompi-server` there.
|
|
80
|
+
- **Timeline:** user prompts, streamed assistant text and reasoning, and tool calls from running to
|
|
81
|
+
result, including failures. Scoped to the focused session; the others keep streaming into the
|
|
82
|
+
hub's ring for when you switch.
|
|
83
|
+
- **Composer:** prompts while idle, steering while a run is in flight, queued follow-ups, and abort.
|
|
84
|
+
- **Status rail:** connection state with replay and drop counts, model, thinking level, context
|
|
85
|
+
usage, cost, and session identity.
|
|
86
|
+
- **Dialogs:** the extension UI sub-protocol (`select`, `confirm`, `input`, `editor`), which is how
|
|
87
|
+
a permission prompt reaches the browser. Cancelling answers the agent rather than stranding it.
|
|
88
|
+
- **Leader Space:** `ctrl+k`, `ctrl+space`, or space in an empty composer. Keys walk the tree the
|
|
89
|
+
installed web plugins declared (`SPC w r` opens the workflows tab, `SPC g e` toggles goal mode),
|
|
90
|
+
backspace climbs, and `/` searches whatever `get_commands` reported to run it as a slash prompt.
|
|
91
|
+
- **Settings:** the gear at the foot of the rail opens the settings pages. The providers page lists
|
|
92
|
+
every model provider Pi knows with whether it is authenticated, signs in with an API key or OAuth
|
|
93
|
+
through the hub, and signs out. The hub uses the same `auth.json` as the sessions, so a login
|
|
94
|
+
here is a login for every session on the machine.
|
|
95
|
+
|
|
96
|
+
## Attach and reattach
|
|
97
|
+
|
|
98
|
+
The hub, not the page, holds each session: it is the one client `doompi-server` allows, and every
|
|
99
|
+
browser tab multiplexes behind it, so two tabs on one session work. Losing the browser changes
|
|
100
|
+
nothing for the agent; the hub keeps a bounded ring of each session's frames and replays it when a
|
|
101
|
+
page subscribes, reporting how many frames the ring had to drop. If another client holds a socket,
|
|
102
|
+
the rail says so and the hub keeps retrying with backoff until it can take over. A page cannot
|
|
103
|
+
perform the handshake itself: `attach` frames sent from the browser are refused.
|
|
104
|
+
|
|
105
|
+
## Current limits
|
|
106
|
+
|
|
107
|
+
- One agent per `doompi-server` process; the hub presents many such servers as one working set.
|
|
108
|
+
- The four DoomPi selection axes (profile, major mode, domains, minor modes) are not exposed over
|
|
109
|
+
Pi RPC, so the rail does not display them. The palette can still invoke the commands that change
|
|
110
|
+
them.
|
|
111
|
+
- Subagent, workflow, and runner surfaces report summaries only; per-run detail has no RPC source
|
|
112
|
+
to render from yet.
|
|
113
|
+
|
|
114
|
+
## Public API
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { serveWeb } from '@agimon-ai/doompi-web';
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Development
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
pnpm build
|
|
124
|
+
pnpm typecheck
|
|
125
|
+
pnpm test
|
|
126
|
+
pnpm test:e2e
|
|
127
|
+
pnpm lint
|
|
128
|
+
pnpm exec vibe-lint check .
|
|
129
|
+
npm pack --dry-run
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Plugin dev loop
|
|
133
|
+
|
|
134
|
+
Two servers, so two terminals, and neither takes a flag. `pnpm dev` serves the cockpit from source
|
|
135
|
+
on port 7434 and proxies `/api` (including the socket) to the hub on 7433. Point it at plugin
|
|
136
|
+
packages and their `web/` sources hot-reload too, generated into `.dev/` and aliased over the empty
|
|
137
|
+
committed registry exactly as `doompi sync` does:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
pnpm cockpit # terminal one, at the repository root: the hub on 7433
|
|
141
|
+
pnpm dev # terminal two, here: the composition the last doompi sync bundled
|
|
142
|
+
DOOMPI_WEB_PLUGIN_ROOTS=/path/to/pkg pnpm dev # or an explicit list, path-delimiter separated
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The roots come from `pluginRoots.json`, which `doompi sync` writes beside its bundle under
|
|
146
|
+
`~/.doompi/web/current`, unless the environment names them. The hub side is unchanged: a plugin's
|
|
147
|
+
hub channel is still the built `dist/webHub.mjs` the synced registry names, so a hub-side change
|
|
148
|
+
needs `pnpm build` in that package and a hub restart.
|
|
149
|
+
|
|
150
|
+
`pnpm test:e2e` drives the built executable in a real browser against a scripted session socket, so
|
|
151
|
+
run `pnpm build` first.
|
|
152
|
+
|
|
153
|
+
Maintained by [Agimon](https://agimon.ai/about).
|
|
154
|
+
|
|
155
|
+
## Web plugins
|
|
156
|
+
|
|
157
|
+
Tabs beyond the conversation are plugins, and no plugin package is a dependency of this one. A
|
|
158
|
+
package declares a `doompiWeb` block in its package.json naming a client entry (a `webPlugin`
|
|
159
|
+
definition: tab, panel, badge, session data channels, tool renderers for the timeline cards of
|
|
160
|
+
the tools the package registers, slots it opens for other plugins, fills into slots others open,
|
|
161
|
+
and the host's own slot contributions) and an optional hub entry (`webHubChannels` plus its built
|
|
162
|
+
dist). This package's own bundle carries no plugins; the full set is assembled on your machine:
|
|
163
|
+
`doompi sync` discovers the installed composition's manifests, generates the import entries, and
|
|
164
|
+
rebuilds the SPA through the `./bundler` subpath into `~/.doompi/web/current`, which the server
|
|
165
|
+
prefers over the packaged bundle (`--assets` and `DOOMPI_WEB_DIST` override it). The synced bundle
|
|
166
|
+
carries `webPlugins.server.json` naming each plugin's built hub entry, imported lazily: a missing
|
|
167
|
+
plugin package logs a notice and its tab shows an empty state. The subagents tab in
|
|
168
|
+
`@agimon-ai/doompi-team` is the reference plugin; the workflows tab ships with
|
|
169
|
+
`@agimon-ai/doompi-workflow`. Contracts come from `@agimon-ai/doompi-web-contracts`.
|
|
170
|
+
|
|
171
|
+
A tool call's timeline item belongs to the plugin that registers the tool: its `toolRenderers`
|
|
172
|
+
entry names the tool (or claims it with `matches` when the name is only known at runtime) and
|
|
173
|
+
supplies one `message` component that owns the whole item, composed from the components package's
|
|
174
|
+
`MessageItem` so it looks like every other item; the host only marks the row, catches a renderer
|
|
175
|
+
that throws (the item falls back to the host's own and the rest of the timeline survives), and
|
|
176
|
+
stands in for a tool nobody claims. Tool messages receive the same actions as every other plugin
|
|
177
|
+
component (`sendSessionFrame`, `openTab`, `renderSlot`), so a card can act, MCP-app style.
|
|
178
|
+
|
|
179
|
+
Plugins are independent. A manifest names no other plugin and `registrationOrder` is only an
|
|
180
|
+
optional tiebreak (default 1000, then plugin id): every relation between two plugins resolves by
|
|
181
|
+
name once all of them are installed. A plugin opens slots named `<pluginId>.<name>` and renders
|
|
182
|
+
them with the `renderSlot` and `slotData` props its components receive; any plugin fills them, and
|
|
183
|
+
the host's `overlay`, `rail`, `selection-bar`, `activity`, and `activity.<group>` slots, without
|
|
184
|
+
knowing whether the owner is installed. A fill into a slot nobody declares, or two plugins wanting
|
|
185
|
+
one tab id, tool name, group name, or Leader Space leaf, never fails the page: the install resolves
|
|
186
|
+
it (the first plugin keeps a shared name, a later binding takes a leaf over as the TUI documents)
|
|
187
|
+
and records an install diagnostic, which `webPluginDiagnostics()` lists and the page logs once with
|
|
188
|
+
`console.warn`. `doompi sync` treats a malformed or half-installed plugin package the same way, as a
|
|
189
|
+
notice that skips that package. The repository's own composition is held to zero notices and zero
|
|
190
|
+
diagnostics by `tests/contract/workspaceWebPlugins.test.ts`.
|
|
191
|
+
|
|
192
|
+
## License
|
|
193
|
+
|
|
194
|
+
Source available under the DoomPi Web License (see LICENSE). Use is free for any purpose, including
|
|
195
|
+
production and commercial use, and you may modify it and publish patches. Redistributing the
|
|
196
|
+
software, or offering it to third parties as a hosted or managed service, is not permitted.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,s)=>(s=n==null?{}:e(i(n)),o(r||!n||!n.__esModule||!a.call(n,`default`)?t(s,`default`,{value:n,enumerable:!0}):s,n));exports.__toESM=s;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../src/services/registryStore.cjs"),n=require("../src/adapters/httpServer.cjs"),r=require("../src/services/serveOptions.cjs"),i=require("../src/adapters/hubProbe.cjs");let a=require("node:os");a=e.__toESM(a,1);function o(e){process.stderr.write(`[doompi-web] ${e}\n`)}async function s(){let e=r.parseServeOptions(process.argv.slice(2)),s=`http://${e.host}:${String(e.port)}`;if(await i.hubAnswers(e.host,e.port)){o(`cockpit already running at ${s}`);return}let c;try{c=await n.serveWeb({port:e.port,host:e.host,assetsDir:e.assetsDir,registryDir:t.resolveRegistryDir({flagValue:e.registryDir,envValue:process.env[t.REGISTRY_DIR_ENV],homeDir:a.default.homedir()}),spawnCommand:e.spawnCommand,onNotice:o})}catch(t){if(t.code!==`EADDRINUSE`)throw t;if(await i.hubAnswers(e.host,e.port)){o(`cockpit already running at ${s}`);return}throw Error(`Port ${String(e.port)} is taken by something that is not a DoomPi cockpit; pass --port.`)}let l=()=>{c.close().then(()=>{process.exit(0)})};process.on(`SIGINT`,l),process.on(`SIGTERM`,l)}s().catch(e=>{o(e instanceof Error?e.message:String(e)),process.exitCode=1});
|
|
3
|
+
//# sourceMappingURL=serve.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve.cjs","names":["parseServeOptions","hubAnswers","serveWeb","resolveRegistryDir","REGISTRY_DIR_ENV","os"],"sources":["../../src/bin/serve.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport os from 'node:os';\nimport { hubAnswers } from '../adapters/hubProbe.ts';\nimport { serveWeb } from '../adapters/httpServer.ts';\nimport { REGISTRY_DIR_ENV, resolveRegistryDir } from '../services/registryStore.ts';\nimport { parseServeOptions } from '../services/serveOptions.ts';\nimport type { WebServer } from '../types/bridge.ts';\n\nconst ADDRESS_IN_USE = 'EADDRINUSE';\n\nfunction notice(message: string): void {\n process.stderr.write(`[doompi-web] ${message}\\n`);\n}\n\nasync function main(): Promise<void> {\n const options = parseServeOptions(process.argv.slice(2));\n const url = `http://${options.host}:${String(options.port)}`;\n\n // One hub serves every session, so a second start is a request to use the\n // running one, not an error worth a stack trace.\n if (await hubAnswers(options.host, options.port)) {\n notice(`cockpit already running at ${url}`);\n return;\n }\n\n let server: WebServer;\n try {\n server = await serveWeb({\n port: options.port,\n host: options.host,\n assetsDir: options.assetsDir,\n registryDir: resolveRegistryDir({\n flagValue: options.registryDir,\n envValue: process.env[REGISTRY_DIR_ENV],\n homeDir: os.homedir(),\n }),\n spawnCommand: options.spawnCommand,\n onNotice: notice,\n });\n } catch (error) {\n // Two cockpits starting together can both find the port free; the loser\n // settles for the winner rather than reporting a clash nobody caused.\n if ((error as NodeJS.ErrnoException).code !== ADDRESS_IN_USE) throw error;\n if (await hubAnswers(options.host, options.port)) {\n notice(`cockpit already running at ${url}`);\n return;\n }\n throw new Error(`Port ${String(options.port)} is taken by something that is not a DoomPi cockpit; pass --port.`);\n }\n\n const stop = (): void => {\n void server.close().then(() => {\n process.exit(0);\n });\n };\n process.on('SIGINT', stop);\n process.on('SIGTERM', stop);\n}\n\nmain().catch((error: unknown) => {\n notice(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n});\n"],"mappings":";kRAWA,SAAS,EAAO,EAAuB,CACrC,QAAQ,OAAO,MAAM,gBAAgB,EAAQ,GAAG,CAClD,CAEA,eAAe,GAAsB,CACnC,IAAM,EAAUA,EAAAA,kBAAkB,QAAQ,KAAK,MAAM,CAAC,CAAC,EACjD,EAAM,UAAU,EAAQ,KAAK,GAAG,OAAO,EAAQ,IAAI,IAIzD,GAAI,MAAMC,EAAAA,WAAW,EAAQ,KAAM,EAAQ,IAAI,EAAG,CAChD,EAAO,8BAA8B,GAAK,EAC1C,MACF,CAEA,IAAI,EACJ,GAAI,CACF,EAAS,MAAMC,EAAAA,SAAS,CACtB,KAAM,EAAQ,KACd,KAAM,EAAQ,KACd,UAAW,EAAQ,UACnB,YAAaC,EAAAA,mBAAmB,CAC9B,UAAW,EAAQ,YACnB,SAAU,QAAQ,IAAIC,EAAAA,kBACtB,QAASC,EAAAA,QAAG,QAAQ,CACtB,CAAC,EACD,aAAc,EAAQ,aACtB,SAAU,CACZ,CAAC,CACH,OAAS,EAAO,CAGd,GAAK,EAAgC,OAAS,aAAgB,MAAM,EACpE,GAAI,MAAMJ,EAAAA,WAAW,EAAQ,KAAM,EAAQ,IAAI,EAAG,CAChD,EAAO,8BAA8B,GAAK,EAC1C,MACF,CACA,MAAU,MAAM,QAAQ,OAAO,EAAQ,IAAI,EAAE,kEAAkE,CACjH,CAEA,IAAM,MAAmB,CACvB,EAAY,MAAM,CAAC,CAAC,SAAW,CAC7B,QAAQ,KAAK,CAAC,CAChB,CAAC,CACH,EACA,QAAQ,GAAG,SAAU,CAAI,EACzB,QAAQ,GAAG,UAAW,CAAI,CAC5B,CAEA,EAAK,CAAC,CAAC,MAAO,GAAmB,CAC/B,EAAO,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,CAAC,EAC7D,QAAQ,SAAW,CACrB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{REGISTRY_DIR_ENV as e,resolveRegistryDir as t}from"../src/services/registryStore.mjs";import{serveWeb as n}from"../src/adapters/httpServer.mjs";import{parseServeOptions as r}from"../src/services/serveOptions.mjs";import{hubAnswers as i}from"../src/adapters/hubProbe.mjs";import a from"node:os";function o(e){process.stderr.write(`[doompi-web] ${e}\n`)}async function s(){let s=r(process.argv.slice(2)),c=`http://${s.host}:${String(s.port)}`;if(await i(s.host,s.port)){o(`cockpit already running at ${c}`);return}let l;try{l=await n({port:s.port,host:s.host,assetsDir:s.assetsDir,registryDir:t({flagValue:s.registryDir,envValue:process.env[e],homeDir:a.homedir()}),spawnCommand:s.spawnCommand,onNotice:o})}catch(e){if(e.code!==`EADDRINUSE`)throw e;if(await i(s.host,s.port)){o(`cockpit already running at ${c}`);return}throw Error(`Port ${String(s.port)} is taken by something that is not a DoomPi cockpit; pass --port.`)}let u=()=>{l.close().then(()=>{process.exit(0)})};process.on(`SIGINT`,u),process.on(`SIGTERM`,u)}s().catch(e=>{o(e instanceof Error?e.message:String(e)),process.exitCode=1});export{};
|
|
3
|
+
//# sourceMappingURL=serve.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve.mjs","names":[],"sources":["../../src/bin/serve.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport os from 'node:os';\nimport { hubAnswers } from '../adapters/hubProbe.ts';\nimport { serveWeb } from '../adapters/httpServer.ts';\nimport { REGISTRY_DIR_ENV, resolveRegistryDir } from '../services/registryStore.ts';\nimport { parseServeOptions } from '../services/serveOptions.ts';\nimport type { WebServer } from '../types/bridge.ts';\n\nconst ADDRESS_IN_USE = 'EADDRINUSE';\n\nfunction notice(message: string): void {\n process.stderr.write(`[doompi-web] ${message}\\n`);\n}\n\nasync function main(): Promise<void> {\n const options = parseServeOptions(process.argv.slice(2));\n const url = `http://${options.host}:${String(options.port)}`;\n\n // One hub serves every session, so a second start is a request to use the\n // running one, not an error worth a stack trace.\n if (await hubAnswers(options.host, options.port)) {\n notice(`cockpit already running at ${url}`);\n return;\n }\n\n let server: WebServer;\n try {\n server = await serveWeb({\n port: options.port,\n host: options.host,\n assetsDir: options.assetsDir,\n registryDir: resolveRegistryDir({\n flagValue: options.registryDir,\n envValue: process.env[REGISTRY_DIR_ENV],\n homeDir: os.homedir(),\n }),\n spawnCommand: options.spawnCommand,\n onNotice: notice,\n });\n } catch (error) {\n // Two cockpits starting together can both find the port free; the loser\n // settles for the winner rather than reporting a clash nobody caused.\n if ((error as NodeJS.ErrnoException).code !== ADDRESS_IN_USE) throw error;\n if (await hubAnswers(options.host, options.port)) {\n notice(`cockpit already running at ${url}`);\n return;\n }\n throw new Error(`Port ${String(options.port)} is taken by something that is not a DoomPi cockpit; pass --port.`);\n }\n\n const stop = (): void => {\n void server.close().then(() => {\n process.exit(0);\n });\n };\n process.on('SIGINT', stop);\n process.on('SIGTERM', stop);\n}\n\nmain().catch((error: unknown) => {\n notice(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n});\n"],"mappings":";6SAWA,SAAS,EAAO,EAAuB,CACrC,QAAQ,OAAO,MAAM,gBAAgB,EAAQ,GAAG,CAClD,CAEA,eAAe,GAAsB,CACnC,IAAM,EAAU,EAAkB,QAAQ,KAAK,MAAM,CAAC,CAAC,EACjD,EAAM,UAAU,EAAQ,KAAK,GAAG,OAAO,EAAQ,IAAI,IAIzD,GAAI,MAAM,EAAW,EAAQ,KAAM,EAAQ,IAAI,EAAG,CAChD,EAAO,8BAA8B,GAAK,EAC1C,MACF,CAEA,IAAI,EACJ,GAAI,CACF,EAAS,MAAM,EAAS,CACtB,KAAM,EAAQ,KACd,KAAM,EAAQ,KACd,UAAW,EAAQ,UACnB,YAAa,EAAmB,CAC9B,UAAW,EAAQ,YACnB,SAAU,QAAQ,IAAI,GACtB,QAAS,EAAG,QAAQ,CACtB,CAAC,EACD,aAAc,EAAQ,aACtB,SAAU,CACZ,CAAC,CACH,OAAS,EAAO,CAGd,GAAK,EAAgC,OAAS,aAAgB,MAAM,EACpE,GAAI,MAAM,EAAW,EAAQ,KAAM,EAAQ,IAAI,EAAG,CAChD,EAAO,8BAA8B,GAAK,EAC1C,MACF,CACA,MAAU,MAAM,QAAQ,OAAO,EAAQ,IAAI,EAAE,kEAAkE,CACjH,CAEA,IAAM,MAAmB,CACvB,EAAY,MAAM,CAAC,CAAC,SAAW,CAC7B,QAAQ,KAAK,CAAC,CAChB,CAAC,CACH,EACA,QAAQ,GAAG,SAAU,CAAI,EACzB,QAAQ,GAAG,UAAW,CAAI,CAC5B,CAEA,EAAK,CAAC,CAAC,MAAO,GAAmB,CAC/B,EAAO,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,CAAC,EAC7D,QAAQ,SAAW,CACrB,CAAC"}
|
package/dist/bundler.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./src/adapters/webBundler.cjs");exports.bundleCockpitWeb=e.bundleCockpitWeb;
|
package/dist/bundler.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{bundleCockpitWeb as e}from"./src/adapters/webBundler.mjs";export{e as bundleCockpitWeb};
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./src/types/hub.cjs");require("./src/types/registry.cjs");const t=require("./src/adapters/httpServer.cjs"),n=require("./src/services/serveOptions.cjs");let r=require("@agimon-ai/doompi-server");exports.HUB_PROTOCOL_VERSION=e.HUB_PROTOCOL_VERSION,exports.HUB_ROLE=e.HUB_ROLE,exports.SESSIONS_API_ROUTE=e.SESSIONS_API_ROUTE,Object.defineProperty(exports,"SESSION_RECORD_VERSION",{enumerable:!0,get:function(){return r.SESSION_RECORD_VERSION}}),exports.parseServeOptions=n.parseServeOptions,exports.serveWeb=t.serveWeb;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { BridgeState, BridgeStatusFrame, SessionFrame } from "./src/types/session.cjs";
|
|
2
|
+
import { SessionAttachment, WebServer, WebServerOptions } from "./src/types/bridge.cjs";
|
|
3
|
+
import { serveWeb } from "./src/adapters/httpServer.cjs";
|
|
4
|
+
import { ServeOptions, parseServeOptions } from "./src/services/serveOptions.cjs";
|
|
5
|
+
import { HUB_PROTOCOL_VERSION, HUB_ROLE, SESSIONS_API_ROUTE, SessionPhase, SessionSummary } from "./src/types/hub.cjs";
|
|
6
|
+
import { SESSION_RECORD_VERSION, SessionRecord } from "./src/types/registry.cjs";
|
|
7
|
+
export { type BridgeState, type BridgeStatusFrame, HUB_PROTOCOL_VERSION, HUB_ROLE, SESSIONS_API_ROUTE, SESSION_RECORD_VERSION, type ServeOptions, type SessionAttachment, type SessionFrame, type SessionPhase, type SessionRecord, type SessionSummary, type WebServer, type WebServerOptions, parseServeOptions, serveWeb };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { BridgeState, BridgeStatusFrame, SessionFrame } from "./src/types/session.mjs";
|
|
2
|
+
import { SessionAttachment, WebServer, WebServerOptions } from "./src/types/bridge.mjs";
|
|
3
|
+
import { serveWeb } from "./src/adapters/httpServer.mjs";
|
|
4
|
+
import { ServeOptions, parseServeOptions } from "./src/services/serveOptions.mjs";
|
|
5
|
+
import { HUB_PROTOCOL_VERSION, HUB_ROLE, SESSIONS_API_ROUTE, SessionPhase, SessionSummary } from "./src/types/hub.mjs";
|
|
6
|
+
import { SESSION_RECORD_VERSION, SessionRecord } from "./src/types/registry.mjs";
|
|
7
|
+
export { type BridgeState, type BridgeStatusFrame, HUB_PROTOCOL_VERSION, HUB_ROLE, SESSIONS_API_ROUTE, SESSION_RECORD_VERSION, type ServeOptions, type SessionAttachment, type SessionFrame, type SessionPhase, type SessionRecord, type SessionSummary, type WebServer, type WebServerOptions, parseServeOptions, serveWeb };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{HUB_PROTOCOL_VERSION as e,HUB_ROLE as t,SESSIONS_API_ROUTE as n}from"./src/types/hub.mjs";import{SESSION_RECORD_VERSION as r}from"./src/types/registry.mjs";import{serveWeb as i}from"./src/adapters/httpServer.mjs";import{parseServeOptions as a}from"./src/services/serveOptions.mjs";export{e as HUB_PROTOCOL_VERSION,t as HUB_ROLE,n as SESSIONS_API_ROUTE,r as SESSION_RECORD_VERSION,a as parseServeOptions,i as serveWeb};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("../types/auth.cjs"),t=[`api_key`,`oauth`];function n(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function r(e){return typeof e==`string`&&t.includes(e)}function i(e){return e instanceof Error?e.message:String(e)}function a(a,o){a.get(e.AUTH_PROVIDERS_API_ROUTE,async e=>{try{return e.json({providers:await o.listProviders()})}catch(t){return e.json({error:`Provider auth is unavailable: ${i(t)}`},502)}}),a.delete(`${e.AUTH_PROVIDERS_API_ROUTE}/:providerId`,async e=>{let t=e.req.param(`providerId`);try{let n=await o.logout(t);return n.ok?e.json({providerId:t}):e.json({error:n.error},n.code===`unknown_provider`?404:502)}catch(t){return e.json({error:`Provider auth is unavailable: ${i(t)}`},502)}}),a.post(e.AUTH_LOGINS_API_ROUTE,async e=>{let a;try{a=await e.req.json()}catch{return e.json({error:`The request body must be JSON.`},400)}if(!n(a)||typeof a.providerId!=`string`||a.providerId===``)return e.json({error:`A providerId string is required.`},400);if(!r(a.type))return e.json({error:`type must be one of ${t.join(`, `)}.`},400);try{let t=await o.startLogin(a.providerId,a.type);if(t.ok)return e.json({flow:t.flow},201);let n=t.code===`unknown_provider`?404:t.code===`busy`?409:400;return e.json({error:t.error},n)}catch(t){return e.json({error:`Provider auth is unavailable: ${i(t)}`},502)}}),a.get(`${e.AUTH_LOGINS_API_ROUTE}/:flowId`,e=>{let t=o.getLogin(e.req.param(`flowId`));return t?e.json({flow:t}):e.json({error:`No such login flow.`},404)}),a.post(`${e.AUTH_LOGINS_API_ROUTE}/:flowId/answer`,async e=>{let t=e.req.param(`flowId`),r;try{r=await e.req.json()}catch{return e.json({error:`The request body must be JSON.`},400)}if(!n(r)||typeof r.promptId!=`string`||typeof r.value!=`string`)return e.json({error:`promptId and value strings are required.`},400);let i=o.answerLogin(t,r.promptId,r.value);return i===`unknown_flow`?e.json({error:`No such login flow.`},404):i===`not_waiting`?e.json({error:`The flow is not waiting on that prompt.`},409):e.json({flow:o.getLogin(t)})}),a.delete(`${e.AUTH_LOGINS_API_ROUTE}/:flowId`,e=>{let t=o.cancelLogin(e.req.param(`flowId`));return t?e.json({flow:t},202):e.json({error:`No such login flow.`},404)})}exports.registerAuthRoutes=a;
|
|
2
|
+
//# sourceMappingURL=authRoutes.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authRoutes.cjs","names":["AUTH_PROVIDERS_API_ROUTE","AUTH_LOGINS_API_ROUTE"],"sources":["../../../src/adapters/authRoutes.ts"],"sourcesContent":["import type { Hono } from 'hono';\nimport { AUTH_LOGINS_API_ROUTE, AUTH_PROVIDERS_API_ROUTE, type AuthMethodType } from '../types/auth.ts';\nimport type { ProviderAuth } from './providerAuth.ts';\n\nconst METHOD_TYPES: readonly AuthMethodType[] = ['api_key', 'oauth'];\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isMethodType(value: unknown): value is AuthMethodType {\n return typeof value === 'string' && (METHOD_TYPES as readonly string[]).includes(value);\n}\n\nfunction describe(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/**\n * The provider-auth REST surface. Every handler that reaches the Pi runtime\n * turns a load failure into 502 with the cause, so a machine without a usable\n * Pi install explains itself on the providers page instead of hanging it.\n */\nexport function registerAuthRoutes(app: Hono, auth: ProviderAuth): void {\n app.get(AUTH_PROVIDERS_API_ROUTE, async (context) => {\n try {\n return context.json({ providers: await auth.listProviders() });\n } catch (error) {\n return context.json({ error: `Provider auth is unavailable: ${describe(error)}` }, 502);\n }\n });\n\n app.delete(`${AUTH_PROVIDERS_API_ROUTE}/:providerId`, async (context) => {\n const providerId = context.req.param('providerId');\n try {\n const outcome = await auth.logout(providerId);\n if (outcome.ok) return context.json({ providerId });\n return context.json({ error: outcome.error }, outcome.code === 'unknown_provider' ? 404 : 502);\n } catch (error) {\n return context.json({ error: `Provider auth is unavailable: ${describe(error)}` }, 502);\n }\n });\n\n app.post(AUTH_LOGINS_API_ROUTE, async (context) => {\n let body: unknown;\n try {\n body = await context.req.json();\n } catch {\n return context.json({ error: 'The request body must be JSON.' }, 400);\n }\n if (!isRecord(body) || typeof body.providerId !== 'string' || body.providerId === '') {\n return context.json({ error: 'A providerId string is required.' }, 400);\n }\n if (!isMethodType(body.type)) {\n return context.json({ error: `type must be one of ${METHOD_TYPES.join(', ')}.` }, 400);\n }\n try {\n const outcome = await auth.startLogin(body.providerId, body.type);\n if (outcome.ok) return context.json({ flow: outcome.flow }, 201);\n const status = outcome.code === 'unknown_provider' ? 404 : outcome.code === 'busy' ? 409 : 400;\n return context.json({ error: outcome.error }, status);\n } catch (error) {\n return context.json({ error: `Provider auth is unavailable: ${describe(error)}` }, 502);\n }\n });\n\n app.get(`${AUTH_LOGINS_API_ROUTE}/:flowId`, (context) => {\n const flow = auth.getLogin(context.req.param('flowId'));\n if (!flow) return context.json({ error: 'No such login flow.' }, 404);\n return context.json({ flow });\n });\n\n app.post(`${AUTH_LOGINS_API_ROUTE}/:flowId/answer`, async (context) => {\n const flowId = context.req.param('flowId');\n let body: unknown;\n try {\n body = await context.req.json();\n } catch {\n return context.json({ error: 'The request body must be JSON.' }, 400);\n }\n if (!isRecord(body) || typeof body.promptId !== 'string' || typeof body.value !== 'string') {\n return context.json({ error: 'promptId and value strings are required.' }, 400);\n }\n const outcome = auth.answerLogin(flowId, body.promptId, body.value);\n if (outcome === 'unknown_flow') return context.json({ error: 'No such login flow.' }, 404);\n if (outcome === 'not_waiting') return context.json({ error: 'The flow is not waiting on that prompt.' }, 409);\n return context.json({ flow: auth.getLogin(flowId) });\n });\n\n app.delete(`${AUTH_LOGINS_API_ROUTE}/:flowId`, (context) => {\n const flow = auth.cancelLogin(context.req.param('flowId'));\n if (!flow) return context.json({ error: 'No such login flow.' }, 404);\n return context.json({ flow }, 202);\n });\n}\n"],"mappings":"qCAIM,EAA0C,CAAC,UAAW,OAAO,EAEnE,SAAS,EAAS,EAAkD,CAClE,OAAO,OAAO,GAAU,YAAY,GAAkB,CAAC,MAAM,QAAQ,CAAK,CAC5E,CAEA,SAAS,EAAa,EAAyC,CAC7D,OAAO,OAAO,GAAU,UAAa,EAAmC,SAAS,CAAK,CACxF,CAEA,SAAS,EAAS,EAAwB,CACxC,OAAO,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,CAC9D,CAOA,SAAgB,EAAmB,EAAW,EAA0B,CACtE,EAAI,IAAIA,EAAAA,yBAA0B,KAAO,IAAY,CACnD,GAAI,CACF,OAAO,EAAQ,KAAK,CAAE,UAAW,MAAM,EAAK,cAAc,CAAE,CAAC,CAC/D,OAAS,EAAO,CACd,OAAO,EAAQ,KAAK,CAAE,MAAO,iCAAiC,EAAS,CAAK,GAAI,EAAG,GAAG,CACxF,CACF,CAAC,EAED,EAAI,OAAO,GAAGA,EAAAA,yBAAyB,cAAe,KAAO,IAAY,CACvE,IAAM,EAAa,EAAQ,IAAI,MAAM,YAAY,EACjD,GAAI,CACF,IAAM,EAAU,MAAM,EAAK,OAAO,CAAU,EAE5C,OADI,EAAQ,GAAW,EAAQ,KAAK,CAAE,YAAW,CAAC,EAC3C,EAAQ,KAAK,CAAE,MAAO,EAAQ,KAAM,EAAG,EAAQ,OAAS,mBAAqB,IAAM,GAAG,CAC/F,OAAS,EAAO,CACd,OAAO,EAAQ,KAAK,CAAE,MAAO,iCAAiC,EAAS,CAAK,GAAI,EAAG,GAAG,CACxF,CACF,CAAC,EAED,EAAI,KAAKC,EAAAA,sBAAuB,KAAO,IAAY,CACjD,IAAI,EACJ,GAAI,CACF,EAAO,MAAM,EAAQ,IAAI,KAAK,CAChC,MAAQ,CACN,OAAO,EAAQ,KAAK,CAAE,MAAO,gCAAiC,EAAG,GAAG,CACtE,CACA,GAAI,CAAC,EAAS,CAAI,GAAK,OAAO,EAAK,YAAe,UAAY,EAAK,aAAe,GAChF,OAAO,EAAQ,KAAK,CAAE,MAAO,kCAAmC,EAAG,GAAG,EAExE,GAAI,CAAC,EAAa,EAAK,IAAI,EACzB,OAAO,EAAQ,KAAK,CAAE,MAAO,uBAAuB,EAAa,KAAK,IAAI,EAAE,EAAG,EAAG,GAAG,EAEvF,GAAI,CACF,IAAM,EAAU,MAAM,EAAK,WAAW,EAAK,WAAY,EAAK,IAAI,EAChE,GAAI,EAAQ,GAAI,OAAO,EAAQ,KAAK,CAAE,KAAM,EAAQ,IAAK,EAAG,GAAG,EAC/D,IAAM,EAAS,EAAQ,OAAS,mBAAqB,IAAM,EAAQ,OAAS,OAAS,IAAM,IAC3F,OAAO,EAAQ,KAAK,CAAE,MAAO,EAAQ,KAAM,EAAG,CAAM,CACtD,OAAS,EAAO,CACd,OAAO,EAAQ,KAAK,CAAE,MAAO,iCAAiC,EAAS,CAAK,GAAI,EAAG,GAAG,CACxF,CACF,CAAC,EAED,EAAI,IAAI,GAAGA,EAAAA,sBAAsB,UAAY,GAAY,CACvD,IAAM,EAAO,EAAK,SAAS,EAAQ,IAAI,MAAM,QAAQ,CAAC,EAEtD,OADK,EACE,EAAQ,KAAK,CAAE,MAAK,CAAC,EADV,EAAQ,KAAK,CAAE,MAAO,qBAAsB,EAAG,GAAG,CAEtE,CAAC,EAED,EAAI,KAAK,GAAGA,EAAAA,sBAAsB,iBAAkB,KAAO,IAAY,CACrE,IAAM,EAAS,EAAQ,IAAI,MAAM,QAAQ,EACrC,EACJ,GAAI,CACF,EAAO,MAAM,EAAQ,IAAI,KAAK,CAChC,MAAQ,CACN,OAAO,EAAQ,KAAK,CAAE,MAAO,gCAAiC,EAAG,GAAG,CACtE,CACA,GAAI,CAAC,EAAS,CAAI,GAAK,OAAO,EAAK,UAAa,UAAY,OAAO,EAAK,OAAU,SAChF,OAAO,EAAQ,KAAK,CAAE,MAAO,0CAA2C,EAAG,GAAG,EAEhF,IAAM,EAAU,EAAK,YAAY,EAAQ,EAAK,SAAU,EAAK,KAAK,EAGlE,OAFI,IAAY,eAAuB,EAAQ,KAAK,CAAE,MAAO,qBAAsB,EAAG,GAAG,EACrF,IAAY,cAAsB,EAAQ,KAAK,CAAE,MAAO,yCAA0C,EAAG,GAAG,EACrG,EAAQ,KAAK,CAAE,KAAM,EAAK,SAAS,CAAM,CAAE,CAAC,CACrD,CAAC,EAED,EAAI,OAAO,GAAGA,EAAAA,sBAAsB,UAAY,GAAY,CAC1D,IAAM,EAAO,EAAK,YAAY,EAAQ,IAAI,MAAM,QAAQ,CAAC,EAEzD,OADK,EACE,EAAQ,KAAK,CAAE,MAAK,EAAG,GAAG,EADf,EAAQ,KAAK,CAAE,MAAO,qBAAsB,EAAG,GAAG,CAEtE,CAAC,CACH"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{AUTH_LOGINS_API_ROUTE as e,AUTH_PROVIDERS_API_ROUTE as t}from"../types/auth.mjs";const n=[`api_key`,`oauth`];function r(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function i(e){return typeof e==`string`&&n.includes(e)}function a(e){return e instanceof Error?e.message:String(e)}function o(o,s){o.get(t,async e=>{try{return e.json({providers:await s.listProviders()})}catch(t){return e.json({error:`Provider auth is unavailable: ${a(t)}`},502)}}),o.delete(`${t}/:providerId`,async e=>{let t=e.req.param(`providerId`);try{let n=await s.logout(t);return n.ok?e.json({providerId:t}):e.json({error:n.error},n.code===`unknown_provider`?404:502)}catch(t){return e.json({error:`Provider auth is unavailable: ${a(t)}`},502)}}),o.post(e,async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:`The request body must be JSON.`},400)}if(!r(t)||typeof t.providerId!=`string`||t.providerId===``)return e.json({error:`A providerId string is required.`},400);if(!i(t.type))return e.json({error:`type must be one of ${n.join(`, `)}.`},400);try{let n=await s.startLogin(t.providerId,t.type);if(n.ok)return e.json({flow:n.flow},201);let r=n.code===`unknown_provider`?404:n.code===`busy`?409:400;return e.json({error:n.error},r)}catch(t){return e.json({error:`Provider auth is unavailable: ${a(t)}`},502)}}),o.get(`${e}/:flowId`,e=>{let t=s.getLogin(e.req.param(`flowId`));return t?e.json({flow:t}):e.json({error:`No such login flow.`},404)}),o.post(`${e}/:flowId/answer`,async e=>{let t=e.req.param(`flowId`),n;try{n=await e.req.json()}catch{return e.json({error:`The request body must be JSON.`},400)}if(!r(n)||typeof n.promptId!=`string`||typeof n.value!=`string`)return e.json({error:`promptId and value strings are required.`},400);let i=s.answerLogin(t,n.promptId,n.value);return i===`unknown_flow`?e.json({error:`No such login flow.`},404):i===`not_waiting`?e.json({error:`The flow is not waiting on that prompt.`},409):e.json({flow:s.getLogin(t)})}),o.delete(`${e}/:flowId`,e=>{let t=s.cancelLogin(e.req.param(`flowId`));return t?e.json({flow:t},202):e.json({error:`No such login flow.`},404)})}export{o as registerAuthRoutes};
|
|
2
|
+
//# sourceMappingURL=authRoutes.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authRoutes.mjs","names":[],"sources":["../../../src/adapters/authRoutes.ts"],"sourcesContent":["import type { Hono } from 'hono';\nimport { AUTH_LOGINS_API_ROUTE, AUTH_PROVIDERS_API_ROUTE, type AuthMethodType } from '../types/auth.ts';\nimport type { ProviderAuth } from './providerAuth.ts';\n\nconst METHOD_TYPES: readonly AuthMethodType[] = ['api_key', 'oauth'];\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isMethodType(value: unknown): value is AuthMethodType {\n return typeof value === 'string' && (METHOD_TYPES as readonly string[]).includes(value);\n}\n\nfunction describe(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/**\n * The provider-auth REST surface. Every handler that reaches the Pi runtime\n * turns a load failure into 502 with the cause, so a machine without a usable\n * Pi install explains itself on the providers page instead of hanging it.\n */\nexport function registerAuthRoutes(app: Hono, auth: ProviderAuth): void {\n app.get(AUTH_PROVIDERS_API_ROUTE, async (context) => {\n try {\n return context.json({ providers: await auth.listProviders() });\n } catch (error) {\n return context.json({ error: `Provider auth is unavailable: ${describe(error)}` }, 502);\n }\n });\n\n app.delete(`${AUTH_PROVIDERS_API_ROUTE}/:providerId`, async (context) => {\n const providerId = context.req.param('providerId');\n try {\n const outcome = await auth.logout(providerId);\n if (outcome.ok) return context.json({ providerId });\n return context.json({ error: outcome.error }, outcome.code === 'unknown_provider' ? 404 : 502);\n } catch (error) {\n return context.json({ error: `Provider auth is unavailable: ${describe(error)}` }, 502);\n }\n });\n\n app.post(AUTH_LOGINS_API_ROUTE, async (context) => {\n let body: unknown;\n try {\n body = await context.req.json();\n } catch {\n return context.json({ error: 'The request body must be JSON.' }, 400);\n }\n if (!isRecord(body) || typeof body.providerId !== 'string' || body.providerId === '') {\n return context.json({ error: 'A providerId string is required.' }, 400);\n }\n if (!isMethodType(body.type)) {\n return context.json({ error: `type must be one of ${METHOD_TYPES.join(', ')}.` }, 400);\n }\n try {\n const outcome = await auth.startLogin(body.providerId, body.type);\n if (outcome.ok) return context.json({ flow: outcome.flow }, 201);\n const status = outcome.code === 'unknown_provider' ? 404 : outcome.code === 'busy' ? 409 : 400;\n return context.json({ error: outcome.error }, status);\n } catch (error) {\n return context.json({ error: `Provider auth is unavailable: ${describe(error)}` }, 502);\n }\n });\n\n app.get(`${AUTH_LOGINS_API_ROUTE}/:flowId`, (context) => {\n const flow = auth.getLogin(context.req.param('flowId'));\n if (!flow) return context.json({ error: 'No such login flow.' }, 404);\n return context.json({ flow });\n });\n\n app.post(`${AUTH_LOGINS_API_ROUTE}/:flowId/answer`, async (context) => {\n const flowId = context.req.param('flowId');\n let body: unknown;\n try {\n body = await context.req.json();\n } catch {\n return context.json({ error: 'The request body must be JSON.' }, 400);\n }\n if (!isRecord(body) || typeof body.promptId !== 'string' || typeof body.value !== 'string') {\n return context.json({ error: 'promptId and value strings are required.' }, 400);\n }\n const outcome = auth.answerLogin(flowId, body.promptId, body.value);\n if (outcome === 'unknown_flow') return context.json({ error: 'No such login flow.' }, 404);\n if (outcome === 'not_waiting') return context.json({ error: 'The flow is not waiting on that prompt.' }, 409);\n return context.json({ flow: auth.getLogin(flowId) });\n });\n\n app.delete(`${AUTH_LOGINS_API_ROUTE}/:flowId`, (context) => {\n const flow = auth.cancelLogin(context.req.param('flowId'));\n if (!flow) return context.json({ error: 'No such login flow.' }, 404);\n return context.json({ flow }, 202);\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("../../_virtual/_rolldown/runtime.cjs");let t=require("node:fs");t=e.__toESM(t,1);let n=require("node:path");n=e.__toESM(n,1);let r=require("node:url"),i=require("node:module");const a=`DOOMPI_AGENT_COMMAND`,o=`DOOMPI_WEB_MODULE`,s=[`node_modules`,`@agimon-ai`,`doompi`,`dist`,`bin`,`cli.mjs`];function c(e,t,r){let a=(0,i.createRequire)(r).resolve(`${e}/package.json`);return n.default.join(n.default.dirname(a),...t)}function l(e,r=t.default.existsSync){for(let t=n.default.resolve(e);;t=n.default.dirname(t)){let e=n.default.join(t,...s);if(r(e))return e;if(t===n.default.dirname(t))return}}function u(e,n,i=require("url").pathToFileURL(__filename).href,s=t.default.existsSync){let u=c(`@agimon-ai/doompi-server`,[`dist`,`bin`,`serve.mjs`],i),d=c(`@agimon-ai/doompi`,[`dist`,`bin`,`cli.mjs`],i),f=c(`@agimon-ai/doompi-web`,[`dist`,`index.mjs`],i),p=n[a],m=l(e,s),h=p||m?{...n}:{...n,[a]:d};return{command:process.execPath,args:[u],environment:{...h,[o]:n[o]||(0,r.pathToFileURL)(f).href}}}exports.defaultServerLaunch=u,exports.repositoryDoomPiCli=l;
|
|
2
|
+
//# sourceMappingURL=bundledServer.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundledServer.cjs","names":["createRequire","path","fs","pathToFileURL"],"sources":["../../../src/adapters/bundledServer.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nconst AGENT_COMMAND_ENV = 'DOOMPI_AGENT_COMMAND';\nconst WEB_MODULE_ENV = 'DOOMPI_WEB_MODULE';\nconst REPOSITORY_CLI = ['node_modules', '@agimon-ai', 'doompi', 'dist', 'bin', 'cli.mjs'];\n\nexport interface BundledServerLaunch {\n readonly command: string;\n /** Leading arguments the command needs; a caller appends its own after these. */\n readonly args: readonly string[];\n readonly environment: NodeJS.ProcessEnv;\n}\n\ntype FileExists = (file: string) => boolean;\n\nfunction packageFile(packageName: string, segments: readonly string[], parentUrl: string): string {\n const manifest = createRequire(parentUrl).resolve(`${packageName}/package.json`);\n return path.join(path.dirname(manifest), ...segments);\n}\n\n/** Finds the DoomPi CLI pinned by the repository containing the requested session. */\nexport function repositoryDoomPiCli(cwd: string, exists: FileExists = fs.existsSync): string | undefined {\n for (let directory = path.resolve(cwd); ; directory = path.dirname(directory)) {\n const cli = path.join(directory, ...REPOSITORY_CLI);\n if (exists(cli)) return cli;\n if (directory === path.dirname(directory)) return undefined;\n }\n}\n\n/**\n * Resolves the Server and fallback agent shipped by a DoomPi Web installation.\n *\n * This is what makes `doompi-web` need no flags: the hub launches the\n * doompi-server sitting in its own dependency tree rather than whichever one\n * PATH happens to name, and points it at the agent from the same install, so a\n * checkout runs its own build. A repository that pins its own DoomPi still\n * wins, since a session created there should run that repository's agent.\n */\nexport function defaultServerLaunch(\n cwd: string,\n environment: NodeJS.ProcessEnv,\n parentUrl: string = import.meta.url,\n exists: FileExists = fs.existsSync,\n): BundledServerLaunch {\n const server = packageFile('@agimon-ai/doompi-server', ['dist', 'bin', 'serve.mjs'], parentUrl);\n const bundledAgent = packageFile('@agimon-ai/doompi', ['dist', 'bin', 'cli.mjs'], parentUrl);\n const bundledWeb = packageFile('@agimon-ai/doompi-web', ['dist', 'index.mjs'], parentUrl);\n const configuredAgent = environment[AGENT_COMMAND_ENV];\n const localAgent = repositoryDoomPiCli(cwd, exists);\n const agentEnvironment =\n configuredAgent || localAgent ? { ...environment } : { ...environment, [AGENT_COMMAND_ENV]: bundledAgent };\n\n return {\n command: process.execPath,\n args: [server],\n environment: {\n ...agentEnvironment,\n [WEB_MODULE_ENV]: environment[WEB_MODULE_ENV] || pathToFileURL(bundledWeb).href,\n },\n };\n}\n"],"mappings":"iMAKA,MAAM,EAAoB,uBACpB,EAAiB,oBACjB,EAAiB,CAAC,eAAgB,aAAc,SAAU,OAAQ,MAAO,SAAS,EAWxF,SAAS,EAAY,EAAqB,EAA6B,EAA2B,CAChG,IAAM,GAAA,EAAWA,EAAAA,cAAAA,CAAc,CAAS,CAAC,CAAC,QAAQ,GAAG,EAAY,cAAc,EAC/E,OAAOC,EAAAA,QAAK,KAAKA,EAAAA,QAAK,QAAQ,CAAQ,EAAG,GAAG,CAAQ,CACtD,CAGA,SAAgB,EAAoB,EAAa,EAAqBC,EAAAA,QAAG,WAAgC,CACvG,IAAK,IAAI,EAAYD,EAAAA,QAAK,QAAQ,CAAG,GAAK,EAAYA,EAAAA,QAAK,QAAQ,CAAS,EAAG,CAC7E,IAAM,EAAMA,EAAAA,QAAK,KAAK,EAAW,GAAG,CAAc,EAClD,GAAI,EAAO,CAAG,EAAG,OAAO,EACxB,GAAI,IAAcA,EAAAA,QAAK,QAAQ,CAAS,EAAG,MAC7C,CACF,CAWA,SAAgB,EACd,EACA,EACA,EAAA,QAAA,KAAA,CAAA,CAAA,cAAA,UAAA,CAAA,CAAA,KACA,EAAqBC,EAAAA,QAAG,WACH,CACrB,IAAM,EAAS,EAAY,2BAA4B,CAAC,OAAQ,MAAO,WAAW,EAAG,CAAS,EACxF,EAAe,EAAY,oBAAqB,CAAC,OAAQ,MAAO,SAAS,EAAG,CAAS,EACrF,EAAa,EAAY,wBAAyB,CAAC,OAAQ,WAAW,EAAG,CAAS,EAClF,EAAkB,EAAY,GAC9B,EAAa,EAAoB,EAAK,CAAM,EAC5C,EACJ,GAAmB,EAAa,CAAE,GAAG,CAAY,EAAI,CAAE,GAAG,GAAc,GAAoB,CAAa,EAE3G,MAAO,CACL,QAAS,QAAQ,SACjB,KAAM,CAAC,CAAM,EACb,YAAa,CACX,GAAG,GACF,GAAiB,EAAY,KAAA,EAAmBC,EAAAA,cAAAA,CAAc,CAAU,CAAC,CAAC,IAC7E,CACF,CACF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{createRequire as e}from"node:module";import t from"node:fs";import n from"node:path";import{pathToFileURL as r}from"node:url";const i=`DOOMPI_AGENT_COMMAND`,a=`DOOMPI_WEB_MODULE`,o=[`node_modules`,`@agimon-ai`,`doompi`,`dist`,`bin`,`cli.mjs`];function s(t,r,i){let a=e(i).resolve(`${t}/package.json`);return n.join(n.dirname(a),...r)}function c(e,r=t.existsSync){for(let t=n.resolve(e);;t=n.dirname(t)){let e=n.join(t,...o);if(r(e))return e;if(t===n.dirname(t))return}}function l(e,n,o=import.meta.url,l=t.existsSync){let u=s(`@agimon-ai/doompi-server`,[`dist`,`bin`,`serve.mjs`],o),d=s(`@agimon-ai/doompi`,[`dist`,`bin`,`cli.mjs`],o),f=s(`@agimon-ai/doompi-web`,[`dist`,`index.mjs`],o),p=n[i],m=c(e,l),h=p||m?{...n}:{...n,[i]:d};return{command:process.execPath,args:[u],environment:{...h,[a]:n[a]||r(f).href}}}export{l as defaultServerLaunch,c as repositoryDoomPiCli};
|
|
2
|
+
//# sourceMappingURL=bundledServer.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundledServer.mjs","names":[],"sources":["../../../src/adapters/bundledServer.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nconst AGENT_COMMAND_ENV = 'DOOMPI_AGENT_COMMAND';\nconst WEB_MODULE_ENV = 'DOOMPI_WEB_MODULE';\nconst REPOSITORY_CLI = ['node_modules', '@agimon-ai', 'doompi', 'dist', 'bin', 'cli.mjs'];\n\nexport interface BundledServerLaunch {\n readonly command: string;\n /** Leading arguments the command needs; a caller appends its own after these. */\n readonly args: readonly string[];\n readonly environment: NodeJS.ProcessEnv;\n}\n\ntype FileExists = (file: string) => boolean;\n\nfunction packageFile(packageName: string, segments: readonly string[], parentUrl: string): string {\n const manifest = createRequire(parentUrl).resolve(`${packageName}/package.json`);\n return path.join(path.dirname(manifest), ...segments);\n}\n\n/** Finds the DoomPi CLI pinned by the repository containing the requested session. */\nexport function repositoryDoomPiCli(cwd: string, exists: FileExists = fs.existsSync): string | undefined {\n for (let directory = path.resolve(cwd); ; directory = path.dirname(directory)) {\n const cli = path.join(directory, ...REPOSITORY_CLI);\n if (exists(cli)) return cli;\n if (directory === path.dirname(directory)) return undefined;\n }\n}\n\n/**\n * Resolves the Server and fallback agent shipped by a DoomPi Web installation.\n *\n * This is what makes `doompi-web` need no flags: the hub launches the\n * doompi-server sitting in its own dependency tree rather than whichever one\n * PATH happens to name, and points it at the agent from the same install, so a\n * checkout runs its own build. A repository that pins its own DoomPi still\n * wins, since a session created there should run that repository's agent.\n */\nexport function defaultServerLaunch(\n cwd: string,\n environment: NodeJS.ProcessEnv,\n parentUrl: string = import.meta.url,\n exists: FileExists = fs.existsSync,\n): BundledServerLaunch {\n const server = packageFile('@agimon-ai/doompi-server', ['dist', 'bin', 'serve.mjs'], parentUrl);\n const bundledAgent = packageFile('@agimon-ai/doompi', ['dist', 'bin', 'cli.mjs'], parentUrl);\n const bundledWeb = packageFile('@agimon-ai/doompi-web', ['dist', 'index.mjs'], parentUrl);\n const configuredAgent = environment[AGENT_COMMAND_ENV];\n const localAgent = repositoryDoomPiCli(cwd, exists);\n const agentEnvironment =\n configuredAgent || localAgent ? { ...environment } : { ...environment, [AGENT_COMMAND_ENV]: bundledAgent };\n\n return {\n command: process.execPath,\n args: [server],\n environment: {\n ...agentEnvironment,\n [WEB_MODULE_ENV]: environment[WEB_MODULE_ENV] || pathToFileURL(bundledWeb).href,\n },\n };\n}\n"],"mappings":"qIAKA,MAAM,EAAoB,uBACpB,EAAiB,oBACjB,EAAiB,CAAC,eAAgB,aAAc,SAAU,OAAQ,MAAO,SAAS,EAWxF,SAAS,EAAY,EAAqB,EAA6B,EAA2B,CAChG,IAAM,EAAW,EAAc,CAAS,CAAC,CAAC,QAAQ,GAAG,EAAY,cAAc,EAC/E,OAAO,EAAK,KAAK,EAAK,QAAQ,CAAQ,EAAG,GAAG,CAAQ,CACtD,CAGA,SAAgB,EAAoB,EAAa,EAAqB,EAAG,WAAgC,CACvG,IAAK,IAAI,EAAY,EAAK,QAAQ,CAAG,GAAK,EAAY,EAAK,QAAQ,CAAS,EAAG,CAC7E,IAAM,EAAM,EAAK,KAAK,EAAW,GAAG,CAAc,EAClD,GAAI,EAAO,CAAG,EAAG,OAAO,EACxB,GAAI,IAAc,EAAK,QAAQ,CAAS,EAAG,MAC7C,CACF,CAWA,SAAgB,EACd,EACA,EACA,EAAoB,YAAY,IAChC,EAAqB,EAAG,WACH,CACrB,IAAM,EAAS,EAAY,2BAA4B,CAAC,OAAQ,MAAO,WAAW,EAAG,CAAS,EACxF,EAAe,EAAY,oBAAqB,CAAC,OAAQ,MAAO,SAAS,EAAG,CAAS,EACrF,EAAa,EAAY,wBAAyB,CAAC,OAAQ,WAAW,EAAG,CAAS,EAClF,EAAkB,EAAY,GAC9B,EAAa,EAAoB,EAAK,CAAM,EAC5C,EACJ,GAAmB,EAAa,CAAE,GAAG,CAAY,EAAI,CAAE,GAAG,GAAc,GAAoB,CAAa,EAE3G,MAAO,CACL,QAAS,QAAQ,SACjB,KAAM,CAAC,CAAM,EACb,YAAa,CACX,GAAG,GACF,GAAiB,EAAY,IAAmB,EAAc,CAAU,CAAC,CAAC,IAC7E,CACF,CACF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../services/directoryMatch.cjs"),n=require("./directorySearch.cjs");let r=require("node:fs");r=e.__toESM(r,1);let i=require("node:path");i=e.__toESM(i,1);let a=require("node:os");a=e.__toESM(a,1);const o=/^\^?\\?\./;function s(e){if(!i.default.isAbsolute(e))return;let t=e.lastIndexOf(`/`);return{parent:t===0?`/`:e.slice(0,t),pattern:e.slice(t+1)}}function c(e){if(e===``)return()=>!0;try{let t=new RegExp(e,`i`);return e=>t.test(e)}catch{let t=e.toLowerCase();return e=>e.toLowerCase().includes(t)}}async function l(e,t){if(!t.isSymbolicLink())return t.isDirectory();try{return(await r.default.promises.stat(i.default.join(e,t.name))).isDirectory()}catch{return!1}}async function u(e,t=12){let n=s(e);if(n===void 0)return[];let a;try{a=await r.default.promises.readdir(n.parent,{withFileTypes:!0})}catch{return[]}let u=o.test(n.pattern),d=c(n.pattern),f=[];for(let e of a)e.name.startsWith(`.`)&&!u||d(e.name)&&await l(n.parent,e)&&f.push(e.name);return f.sort((e,t)=>e.localeCompare(t)),f.slice(0,t).map(e=>i.default.join(n.parent,e))}function d(e){return[e]}async function f(e,r={}){let i=r.limit??12,o=await u(e,i);if(o.length>0)return o;let s=t.searchTermFor(e);if(s===``)return[];let c=r.homeDirectory??a.default.homedir(),l=r.search??n.searchDirectoryTree,f=[];for(let e of d(c))try{f.push(...await l(e,s))}catch{}return t.rankDirectories(f,s,i)}exports.listDirectories=u,exports.splitDirectoryQuery=s,exports.suggestDirectories=f;
|
|
2
|
+
//# sourceMappingURL=directoryListing.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directoryListing.cjs","names":["path","fs","searchTermFor","os","searchDirectoryTree","rankDirectories"],"sources":["../../../src/adapters/directoryListing.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { rankDirectories, searchTermFor } from '../services/directoryMatch.ts';\nimport { searchDirectoryTree } from './directorySearch.ts';\n\nconst PATH_SEPARATOR = '/';\nconst HIDDEN_PREFIX = '.';\n/** A pattern that starts with a dot, literal or escaped, anchored or not, is asking for dotdirs. */\nconst ASKS_FOR_HIDDEN = /^\\^?\\\\?\\./;\n/** The picker shows a handful; a longer list is a sign to type more. */\nconst DEFAULT_LIMIT = 12;\n\nexport interface DirectoryQuery {\n /** The directory whose children are listed. */\n parent: string;\n /** The typed trailing segment, applied to child names as a regular expression. */\n pattern: string;\n}\n\n/**\n * Splits a typed path into the directory to list and the filter for its\n * children: everything up to the last slash is the parent, the rest is the\n * pattern. Only absolute paths qualify, matching what a session accepts.\n */\nexport function splitDirectoryQuery(typed: string): DirectoryQuery | undefined {\n if (!path.isAbsolute(typed)) return undefined;\n const cut = typed.lastIndexOf(PATH_SEPARATOR);\n return { parent: cut === 0 ? PATH_SEPARATOR : typed.slice(0, cut), pattern: typed.slice(cut + 1) };\n}\n\nfunction nameMatcher(pattern: string): (name: string) => boolean {\n if (pattern === '') return () => true;\n try {\n const regex = new RegExp(pattern, 'i');\n return (name) => regex.test(name);\n } catch {\n // A pattern still being typed (\"src/[\") is not a regex yet; substring\n // matching keeps the list narrowing instead of going blank.\n const lowered = pattern.toLowerCase();\n return (name) => name.toLowerCase().includes(lowered);\n }\n}\n\nasync function isDirectory(parent: string, entry: fs.Dirent): Promise<boolean> {\n if (!entry.isSymbolicLink()) return entry.isDirectory();\n try {\n return (await fs.promises.stat(path.join(parent, entry.name))).isDirectory();\n } catch {\n return false; // A dangling link is not somewhere a session can start.\n }\n}\n\n/**\n * Absolute paths of the directories the typed path could complete to, for\n * the new-session picker. Hidden directories stay out of the way unless the\n * pattern itself begins with a dot; an unreadable or missing parent yields\n * nothing rather than an error, since the person is still typing.\n */\nexport async function listDirectories(typed: string, limit = DEFAULT_LIMIT): Promise<string[]> {\n const query = splitDirectoryQuery(typed);\n if (query === undefined) return [];\n let entries: fs.Dirent[];\n try {\n entries = await fs.promises.readdir(query.parent, { withFileTypes: true });\n } catch {\n return [];\n }\n const showHidden = ASKS_FOR_HIDDEN.test(query.pattern);\n const matches = nameMatcher(query.pattern);\n const names: string[] = [];\n for (const entry of entries) {\n if (entry.name.startsWith(HIDDEN_PREFIX) && !showHidden) continue;\n if (!matches(entry.name)) continue;\n if (await isDirectory(query.parent, entry)) names.push(entry.name);\n }\n names.sort((left, right) => left.localeCompare(right));\n return names.slice(0, limit).map((name) => path.join(query.parent, name));\n}\n\n/**\n * Where a fuzzy search starts when the typed value names no place that exists.\n * Home is where projects live; a path outside it is still reachable by typing\n * it out, which is what the completion above is for.\n */\nfunction searchRoots(homeDirectory: string): string[] {\n return [homeDirectory];\n}\n\nexport interface SuggestDirectoriesOptions {\n limit?: number;\n /** Injectable so a test does not search the machine it runs on. */\n homeDirectory?: string;\n /** Injectable seam over the tree search. */\n search?: (root: string, query: string) => Promise<string[]>;\n}\n\n/**\n * The directories to offer for a typed value, however it was typed.\n *\n * Completion comes first: an absolute path whose parent exists is someone\n * drilling in, and listing that parent's children is exactly right. Anything\n * else is a name, not a location. A bare \"agirepo\", or a path remembered from\n * another machine where the folder sat somewhere else, both mean the same\n * thing, and answering \"No such directory\" to either is a dead end when the\n * folder is sitting in the reader's home directory under a different parent.\n */\nexport async function suggestDirectories(typed: string, options: SuggestDirectoriesOptions = {}): Promise<string[]> {\n const limit = options.limit ?? DEFAULT_LIMIT;\n const completions = await listDirectories(typed, limit);\n if (completions.length > 0) return completions;\n\n const query = searchTermFor(typed);\n if (query === '') return [];\n const home = options.homeDirectory ?? os.homedir();\n const search = options.search ?? searchDirectoryTree;\n\n const found: string[] = [];\n for (const root of searchRoots(home)) {\n try {\n found.push(...(await search(root, query)));\n } catch {\n // A root that cannot be searched simply contributes nothing.\n }\n }\n return rankDirectories(found, query, limit);\n}\n"],"mappings":"uQAMA,MAGM,EAAkB,YAgBxB,SAAgB,EAAoB,EAA2C,CAC7E,GAAI,CAACA,EAAAA,QAAK,WAAW,CAAK,EAAG,OAC7B,IAAM,EAAM,EAAM,YAAY,GAAc,EAC5C,MAAO,CAAE,OAAQ,IAAQ,EAAI,IAAiB,EAAM,MAAM,EAAG,CAAG,EAAG,QAAS,EAAM,MAAM,EAAM,CAAC,CAAE,CACnG,CAEA,SAAS,EAAY,EAA4C,CAC/D,GAAI,IAAY,GAAI,UAAa,GACjC,GAAI,CACF,IAAM,EAAQ,IAAI,OAAO,EAAS,GAAG,EACrC,MAAQ,IAAS,EAAM,KAAK,CAAI,CAClC,MAAQ,CAGN,IAAM,EAAU,EAAQ,YAAY,EACpC,MAAQ,IAAS,EAAK,YAAY,CAAC,CAAC,SAAS,CAAO,CACtD,CACF,CAEA,eAAe,EAAY,EAAgB,EAAoC,CAC7E,GAAI,CAAC,EAAM,eAAe,EAAG,OAAO,EAAM,YAAY,EACtD,GAAI,CACF,OAAQ,MAAMC,EAAAA,QAAG,SAAS,KAAKD,EAAAA,QAAK,KAAK,EAAQ,EAAM,IAAI,CAAC,EAAA,CAAG,YAAY,CAC7E,MAAQ,CACN,MAAO,EACT,CACF,CAQA,eAAsB,EAAgB,EAAe,EAAQ,GAAkC,CAC7F,IAAM,EAAQ,EAAoB,CAAK,EACvC,GAAI,IAAU,IAAA,GAAW,MAAO,CAAC,EACjC,IAAI,EACJ,GAAI,CACF,EAAU,MAAMC,EAAAA,QAAG,SAAS,QAAQ,EAAM,OAAQ,CAAE,cAAe,EAAK,CAAC,CAC3E,MAAQ,CACN,MAAO,CAAC,CACV,CACA,IAAM,EAAa,EAAgB,KAAK,EAAM,OAAO,EAC/C,EAAU,EAAY,EAAM,OAAO,EACnC,EAAkB,CAAC,EACzB,IAAK,IAAM,KAAS,EACd,EAAM,KAAK,WAAW,GAAa,GAAK,CAAC,GACxC,EAAQ,EAAM,IAAI,GACnB,MAAM,EAAY,EAAM,OAAQ,CAAK,GAAG,EAAM,KAAK,EAAM,IAAI,EAGnE,OADA,EAAM,MAAM,EAAM,IAAU,EAAK,cAAc,CAAK,CAAC,EAC9C,EAAM,MAAM,EAAG,CAAK,CAAC,CAAC,IAAK,GAASD,EAAAA,QAAK,KAAK,EAAM,OAAQ,CAAI,CAAC,CAC1E,CAOA,SAAS,EAAY,EAAiC,CACpD,MAAO,CAAC,CAAa,CACvB,CAoBA,eAAsB,EAAmB,EAAe,EAAqC,CAAC,EAAsB,CAClH,IAAM,EAAQ,EAAQ,OAAS,GACzB,EAAc,MAAM,EAAgB,EAAO,CAAK,EACtD,GAAI,EAAY,OAAS,EAAG,OAAO,EAEnC,IAAM,EAAQE,EAAAA,cAAc,CAAK,EACjC,GAAI,IAAU,GAAI,MAAO,CAAC,EAC1B,IAAM,EAAO,EAAQ,eAAiBC,EAAAA,QAAG,QAAQ,EAC3C,EAAS,EAAQ,QAAUC,EAAAA,oBAE3B,EAAkB,CAAC,EACzB,IAAK,IAAM,KAAQ,EAAY,CAAI,EACjC,GAAI,CACF,EAAM,KAAK,GAAI,MAAM,EAAO,EAAM,CAAK,CAAE,CAC3C,MAAQ,CAER,CAEF,OAAOC,EAAAA,gBAAgB,EAAO,EAAO,CAAK,CAC5C"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{rankDirectories as e,searchTermFor as t}from"../services/directoryMatch.mjs";import{searchDirectoryTree as n}from"./directorySearch.mjs";import r from"node:fs";import i from"node:path";import a from"node:os";const o=/^\^?\\?\./;function s(e){if(!i.isAbsolute(e))return;let t=e.lastIndexOf(`/`);return{parent:t===0?`/`:e.slice(0,t),pattern:e.slice(t+1)}}function c(e){if(e===``)return()=>!0;try{let t=new RegExp(e,`i`);return e=>t.test(e)}catch{let t=e.toLowerCase();return e=>e.toLowerCase().includes(t)}}async function l(e,t){if(!t.isSymbolicLink())return t.isDirectory();try{return(await r.promises.stat(i.join(e,t.name))).isDirectory()}catch{return!1}}async function u(e,t=12){let n=s(e);if(n===void 0)return[];let a;try{a=await r.promises.readdir(n.parent,{withFileTypes:!0})}catch{return[]}let u=o.test(n.pattern),d=c(n.pattern),f=[];for(let e of a)e.name.startsWith(`.`)&&!u||d(e.name)&&await l(n.parent,e)&&f.push(e.name);return f.sort((e,t)=>e.localeCompare(t)),f.slice(0,t).map(e=>i.join(n.parent,e))}function d(e){return[e]}async function f(r,i={}){let o=i.limit??12,s=await u(r,o);if(s.length>0)return s;let c=t(r);if(c===``)return[];let l=i.homeDirectory??a.homedir(),f=i.search??n,p=[];for(let e of d(l))try{p.push(...await f(e,c))}catch{}return e(p,c,o)}export{u as listDirectories,s as splitDirectoryQuery,f as suggestDirectories};
|
|
2
|
+
//# sourceMappingURL=directoryListing.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directoryListing.mjs","names":[],"sources":["../../../src/adapters/directoryListing.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { rankDirectories, searchTermFor } from '../services/directoryMatch.ts';\nimport { searchDirectoryTree } from './directorySearch.ts';\n\nconst PATH_SEPARATOR = '/';\nconst HIDDEN_PREFIX = '.';\n/** A pattern that starts with a dot, literal or escaped, anchored or not, is asking for dotdirs. */\nconst ASKS_FOR_HIDDEN = /^\\^?\\\\?\\./;\n/** The picker shows a handful; a longer list is a sign to type more. */\nconst DEFAULT_LIMIT = 12;\n\nexport interface DirectoryQuery {\n /** The directory whose children are listed. */\n parent: string;\n /** The typed trailing segment, applied to child names as a regular expression. */\n pattern: string;\n}\n\n/**\n * Splits a typed path into the directory to list and the filter for its\n * children: everything up to the last slash is the parent, the rest is the\n * pattern. Only absolute paths qualify, matching what a session accepts.\n */\nexport function splitDirectoryQuery(typed: string): DirectoryQuery | undefined {\n if (!path.isAbsolute(typed)) return undefined;\n const cut = typed.lastIndexOf(PATH_SEPARATOR);\n return { parent: cut === 0 ? PATH_SEPARATOR : typed.slice(0, cut), pattern: typed.slice(cut + 1) };\n}\n\nfunction nameMatcher(pattern: string): (name: string) => boolean {\n if (pattern === '') return () => true;\n try {\n const regex = new RegExp(pattern, 'i');\n return (name) => regex.test(name);\n } catch {\n // A pattern still being typed (\"src/[\") is not a regex yet; substring\n // matching keeps the list narrowing instead of going blank.\n const lowered = pattern.toLowerCase();\n return (name) => name.toLowerCase().includes(lowered);\n }\n}\n\nasync function isDirectory(parent: string, entry: fs.Dirent): Promise<boolean> {\n if (!entry.isSymbolicLink()) return entry.isDirectory();\n try {\n return (await fs.promises.stat(path.join(parent, entry.name))).isDirectory();\n } catch {\n return false; // A dangling link is not somewhere a session can start.\n }\n}\n\n/**\n * Absolute paths of the directories the typed path could complete to, for\n * the new-session picker. Hidden directories stay out of the way unless the\n * pattern itself begins with a dot; an unreadable or missing parent yields\n * nothing rather than an error, since the person is still typing.\n */\nexport async function listDirectories(typed: string, limit = DEFAULT_LIMIT): Promise<string[]> {\n const query = splitDirectoryQuery(typed);\n if (query === undefined) return [];\n let entries: fs.Dirent[];\n try {\n entries = await fs.promises.readdir(query.parent, { withFileTypes: true });\n } catch {\n return [];\n }\n const showHidden = ASKS_FOR_HIDDEN.test(query.pattern);\n const matches = nameMatcher(query.pattern);\n const names: string[] = [];\n for (const entry of entries) {\n if (entry.name.startsWith(HIDDEN_PREFIX) && !showHidden) continue;\n if (!matches(entry.name)) continue;\n if (await isDirectory(query.parent, entry)) names.push(entry.name);\n }\n names.sort((left, right) => left.localeCompare(right));\n return names.slice(0, limit).map((name) => path.join(query.parent, name));\n}\n\n/**\n * Where a fuzzy search starts when the typed value names no place that exists.\n * Home is where projects live; a path outside it is still reachable by typing\n * it out, which is what the completion above is for.\n */\nfunction searchRoots(homeDirectory: string): string[] {\n return [homeDirectory];\n}\n\nexport interface SuggestDirectoriesOptions {\n limit?: number;\n /** Injectable so a test does not search the machine it runs on. */\n homeDirectory?: string;\n /** Injectable seam over the tree search. */\n search?: (root: string, query: string) => Promise<string[]>;\n}\n\n/**\n * The directories to offer for a typed value, however it was typed.\n *\n * Completion comes first: an absolute path whose parent exists is someone\n * drilling in, and listing that parent's children is exactly right. Anything\n * else is a name, not a location. A bare \"agirepo\", or a path remembered from\n * another machine where the folder sat somewhere else, both mean the same\n * thing, and answering \"No such directory\" to either is a dead end when the\n * folder is sitting in the reader's home directory under a different parent.\n */\nexport async function suggestDirectories(typed: string, options: SuggestDirectoriesOptions = {}): Promise<string[]> {\n const limit = options.limit ?? DEFAULT_LIMIT;\n const completions = await listDirectories(typed, limit);\n if (completions.length > 0) return completions;\n\n const query = searchTermFor(typed);\n if (query === '') return [];\n const home = options.homeDirectory ?? os.homedir();\n const search = options.search ?? searchDirectoryTree;\n\n const found: string[] = [];\n for (const root of searchRoots(home)) {\n try {\n found.push(...(await search(root, query)));\n } catch {\n // A root that cannot be searched simply contributes nothing.\n }\n }\n return rankDirectories(found, query, limit);\n}\n"],"mappings":"uNAMA,MAGM,EAAkB,YAgBxB,SAAgB,EAAoB,EAA2C,CAC7E,GAAI,CAAC,EAAK,WAAW,CAAK,EAAG,OAC7B,IAAM,EAAM,EAAM,YAAY,GAAc,EAC5C,MAAO,CAAE,OAAQ,IAAQ,EAAI,IAAiB,EAAM,MAAM,EAAG,CAAG,EAAG,QAAS,EAAM,MAAM,EAAM,CAAC,CAAE,CACnG,CAEA,SAAS,EAAY,EAA4C,CAC/D,GAAI,IAAY,GAAI,UAAa,GACjC,GAAI,CACF,IAAM,EAAQ,IAAI,OAAO,EAAS,GAAG,EACrC,MAAQ,IAAS,EAAM,KAAK,CAAI,CAClC,MAAQ,CAGN,IAAM,EAAU,EAAQ,YAAY,EACpC,MAAQ,IAAS,EAAK,YAAY,CAAC,CAAC,SAAS,CAAO,CACtD,CACF,CAEA,eAAe,EAAY,EAAgB,EAAoC,CAC7E,GAAI,CAAC,EAAM,eAAe,EAAG,OAAO,EAAM,YAAY,EACtD,GAAI,CACF,OAAQ,MAAM,EAAG,SAAS,KAAK,EAAK,KAAK,EAAQ,EAAM,IAAI,CAAC,EAAA,CAAG,YAAY,CAC7E,MAAQ,CACN,MAAO,EACT,CACF,CAQA,eAAsB,EAAgB,EAAe,EAAQ,GAAkC,CAC7F,IAAM,EAAQ,EAAoB,CAAK,EACvC,GAAI,IAAU,IAAA,GAAW,MAAO,CAAC,EACjC,IAAI,EACJ,GAAI,CACF,EAAU,MAAM,EAAG,SAAS,QAAQ,EAAM,OAAQ,CAAE,cAAe,EAAK,CAAC,CAC3E,MAAQ,CACN,MAAO,CAAC,CACV,CACA,IAAM,EAAa,EAAgB,KAAK,EAAM,OAAO,EAC/C,EAAU,EAAY,EAAM,OAAO,EACnC,EAAkB,CAAC,EACzB,IAAK,IAAM,KAAS,EACd,EAAM,KAAK,WAAW,GAAa,GAAK,CAAC,GACxC,EAAQ,EAAM,IAAI,GACnB,MAAM,EAAY,EAAM,OAAQ,CAAK,GAAG,EAAM,KAAK,EAAM,IAAI,EAGnE,OADA,EAAM,MAAM,EAAM,IAAU,EAAK,cAAc,CAAK,CAAC,EAC9C,EAAM,MAAM,EAAG,CAAK,CAAC,CAAC,IAAK,GAAS,EAAK,KAAK,EAAM,OAAQ,CAAI,CAAC,CAC1E,CAOA,SAAS,EAAY,EAAiC,CACpD,MAAO,CAAC,CAAa,CACvB,CAoBA,eAAsB,EAAmB,EAAe,EAAqC,CAAC,EAAsB,CAClH,IAAM,EAAQ,EAAQ,OAAS,GACzB,EAAc,MAAM,EAAgB,EAAO,CAAK,EACtD,GAAI,EAAY,OAAS,EAAG,OAAO,EAEnC,IAAM,EAAQ,EAAc,CAAK,EACjC,GAAI,IAAU,GAAI,MAAO,CAAC,EAC1B,IAAM,EAAO,EAAQ,eAAiB,EAAG,QAAQ,EAC3C,EAAS,EAAQ,QAAU,EAE3B,EAAkB,CAAC,EACzB,IAAK,IAAM,KAAQ,EAAY,CAAI,EACjC,GAAI,CACF,EAAM,KAAK,GAAI,MAAM,EAAO,EAAM,CAAK,CAAE,CAC3C,MAAQ,CAER,CAEF,OAAO,EAAgB,EAAO,EAAO,CAAK,CAC5C"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
const e=require("../../_virtual/_rolldown/runtime.cjs");let t=require("node:fs");t=e.__toESM(t,1);let n=require("node:path");n=e.__toESM(n,1);let r=require("node:child_process");const i=2e4,a=new Set([`node_modules`,`Library`,`Applications`,`dist`,`build`,`out`,`target`,`vendor`,`coverage`,`.git`,`.nx`,`.next`,`.cache`,`.venv`,`__pycache__`]);function o(e,t){return a.has(e)?!0:e.startsWith(`.`)&&!t}async function s(e,t,n){let i=[`--type`,`d`,`--absolute-path`,`--fixed-strings`,`--color`,`never`,`--max-depth`,`6`,...[...a].flatMap(e=>[`--exclude`,e]),...n?[`--hidden`]:[],`--`,t,e];return new Promise(e=>{(0,r.execFile)(`fd`,i,{timeout:1500,maxBuffer:4194304},(t,n)=>{if(t&&n.length===0&&t.code!==1){e(void 0);return}e(n.split(`
|
|
2
|
+
`).map(e=>e.trimEnd().replace(/\/$/u,``)).filter(e=>e!==``))})})}async function c(e,r){let a=[],s=[e],c=0;for(let e=0;e<6&&s.length>0&&c<i;e+=1){let e=[];for(let l of s){if(c>=i)break;let s;try{s=await t.default.promises.readdir(l,{withFileTypes:!0})}catch{continue}for(let t of s){if(!t.isDirectory()||o(t.name,r))continue;c+=1;let i=n.default.join(l,t.name);a.push(i),e.push(i)}}s=e}return a}async function l(e,t){let n=t.startsWith(`.`);return await s(e,t,n)??await c(e,n)}exports.searchDirectoryTree=l;
|
|
3
|
+
//# sourceMappingURL=directorySearch.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directorySearch.cjs","names":["fs","path"],"sources":["../../../src/adapters/directorySearch.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\n/** How deep a search descends; a project someone starts a session in is never far from home. */\nconst MAX_DEPTH = 6;\n/** A ceiling on the walk so a pathological tree cannot hold the request open. */\nconst MAX_VISITED = 20_000;\n/** `fd` is fast; if it has not answered by now the fallback will beat waiting for it. */\nconst FD_TIMEOUT_MS = 1_500;\nconst FD_MAX_BUFFER = 4 * 1024 * 1024;\n\n/**\n * Directories nobody starts a session in, and which dwarf everything else in\n * a tree. Pruning them is the difference between a walk that answers now and\n * one that reads a million files.\n */\nconst PRUNED = new Set([\n 'node_modules',\n 'Library',\n 'Applications',\n 'dist',\n 'build',\n 'out',\n 'target',\n 'vendor',\n 'coverage',\n '.git',\n '.nx',\n '.next',\n '.cache',\n '.venv',\n '__pycache__',\n]);\n\nconst HIDDEN_PREFIX = '.';\n\nfunction skips(name: string, wantsHidden: boolean): boolean {\n if (PRUNED.has(name)) return true;\n return name.startsWith(HIDDEN_PREFIX) && !wantsHidden;\n}\n\n/**\n * `fd` if this machine has it: it is built for exactly this, prunes ignored\n * paths itself, and is far quicker than walking from Node. A missing binary,\n * a non-zero exit, or a slow answer all fall through to the walk rather than\n * failing the request, because a suggestion list is a convenience.\n */\nasync function searchWithFd(root: string, query: string, wantsHidden: boolean): Promise<string[] | undefined> {\n const args = [\n '--type',\n 'd',\n '--absolute-path',\n '--fixed-strings',\n '--color',\n 'never',\n '--max-depth',\n String(MAX_DEPTH),\n // The same prune list the walk uses, so which engine answered is invisible\n // to the reader: no caches, no build output, no OS folders nobody codes in.\n ...[...PRUNED].flatMap((name) => ['--exclude', name]),\n ...(wantsHidden ? ['--hidden'] : []),\n '--',\n query,\n root,\n ];\n return new Promise((resolve) => {\n execFile('fd', args, { timeout: FD_TIMEOUT_MS, maxBuffer: FD_MAX_BUFFER }, (error, stdout) => {\n // fd exits non-zero when it matches nothing, which is an answer, not a\n // failure; only a missing binary or a timeout sends us to the walk.\n if (error && stdout.length === 0 && error.code !== 1) {\n resolve(undefined);\n return;\n }\n resolve(\n stdout\n .split('\\n')\n .map((line) => line.trimEnd().replace(/\\/$/u, ''))\n .filter((line) => line !== ''),\n );\n });\n });\n}\n\n/** Every directory under a root, breadth first, bounded in depth and in total work. */\nasync function walkDirectories(root: string, wantsHidden: boolean): Promise<string[]> {\n const found: string[] = [];\n let frontier = [root];\n let visited = 0;\n\n for (let depth = 0; depth < MAX_DEPTH && frontier.length > 0 && visited < MAX_VISITED; depth += 1) {\n const next: string[] = [];\n for (const directory of frontier) {\n if (visited >= MAX_VISITED) break;\n let entries: fs.Dirent[];\n try {\n entries = await fs.promises.readdir(directory, { withFileTypes: true });\n } catch {\n continue; // Unreadable is not an error here, just nothing to offer.\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || skips(entry.name, wantsHidden)) continue;\n visited += 1;\n const child = path.join(directory, entry.name);\n found.push(child);\n next.push(child);\n }\n }\n frontier = next;\n }\n return found;\n}\n\n/**\n * Directories under a root whose path could answer the query.\n *\n * Matching happens in the caller, against the whole candidate set, so the\n * ranking is the same whichever engine produced it.\n */\nexport async function searchDirectoryTree(root: string, query: string): Promise<string[]> {\n const wantsHidden = query.startsWith(HIDDEN_PREFIX);\n const viaFd = await searchWithFd(root, query, wantsHidden);\n return viaFd ?? (await walkDirectories(root, wantsHidden));\n}\n"],"mappings":"kLAKA,MAEM,EAAc,IAUd,EAAS,IAAI,IAAI,CACrB,eACA,UACA,eACA,OACA,QACA,MACA,SACA,SACA,WACA,OACA,MACA,QACA,SACA,QACA,aACF,CAAC,EAID,SAAS,EAAM,EAAc,EAA+B,CAE1D,OADI,EAAO,IAAI,CAAI,EAAU,GACtB,EAAK,WAAW,GAAa,GAAK,CAAC,CAC5C,CAQA,eAAe,EAAa,EAAc,EAAe,EAAqD,CAC5G,IAAM,EAAO,CACX,SACA,IACA,kBACA,kBACA,UACA,QACA,cACA,IAGA,GAAG,CAAC,GAAG,CAAM,CAAC,CAAC,QAAS,GAAS,CAAC,YAAa,CAAI,CAAC,EACpD,GAAI,EAAc,CAAC,UAAU,EAAI,CAAC,EAClC,KACA,EACA,CACF,EACA,OAAO,IAAI,QAAS,GAAY,EAC9B,EAAA,EAAA,SAAA,CAAS,KAAM,EAAM,CAAE,QAAS,KAAe,UAAW,OAAc,GAAI,EAAO,IAAW,CAG5F,GAAI,GAAS,EAAO,SAAW,GAAK,EAAM,OAAS,EAAG,CACpD,EAAQ,IAAA,EAAS,EACjB,MACF,CACA,EACE,EACG,MAAM;CAAI,CAAC,CACX,IAAK,GAAS,EAAK,QAAQ,CAAC,CAAC,QAAQ,OAAQ,EAAE,CAAC,CAAC,CACjD,OAAQ,GAAS,IAAS,EAAE,CACjC,CACF,CAAC,CACH,CAAC,CACH,CAGA,eAAe,EAAgB,EAAc,EAAyC,CACpF,IAAM,EAAkB,CAAC,EACrB,EAAW,CAAC,CAAI,EAChB,EAAU,EAEd,IAAK,IAAI,EAAQ,EAAG,EAAQ,GAAa,EAAS,OAAS,GAAK,EAAU,EAAa,GAAS,EAAG,CACjG,IAAM,EAAiB,CAAC,EACxB,IAAK,IAAM,KAAa,EAAU,CAChC,GAAI,GAAW,EAAa,MAC5B,IAAI,EACJ,GAAI,CACF,EAAU,MAAMA,EAAAA,QAAG,SAAS,QAAQ,EAAW,CAAE,cAAe,EAAK,CAAC,CACxE,MAAQ,CACN,QACF,CACA,IAAK,IAAM,KAAS,EAAS,CAC3B,GAAI,CAAC,EAAM,YAAY,GAAK,EAAM,EAAM,KAAM,CAAW,EAAG,SAC5D,GAAW,EACX,IAAM,EAAQC,EAAAA,QAAK,KAAK,EAAW,EAAM,IAAI,EAC7C,EAAM,KAAK,CAAK,EAChB,EAAK,KAAK,CAAK,CACjB,CACF,CACA,EAAW,CACb,CACA,OAAO,CACT,CAQA,eAAsB,EAAoB,EAAc,EAAkC,CACxF,IAAM,EAAc,EAAM,WAAW,GAAa,EAElD,OAAO,MADa,EAAa,EAAM,EAAO,CAAW,GACxC,MAAM,EAAgB,EAAM,CAAW,CAC1D"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import e from"node:fs";import t from"node:path";import{execFile as n}from"node:child_process";const r=2e4,i=new Set([`node_modules`,`Library`,`Applications`,`dist`,`build`,`out`,`target`,`vendor`,`coverage`,`.git`,`.nx`,`.next`,`.cache`,`.venv`,`__pycache__`]);function a(e,t){return i.has(e)?!0:e.startsWith(`.`)&&!t}async function o(e,t,r){let a=[`--type`,`d`,`--absolute-path`,`--fixed-strings`,`--color`,`never`,`--max-depth`,`6`,...[...i].flatMap(e=>[`--exclude`,e]),...r?[`--hidden`]:[],`--`,t,e];return new Promise(e=>{n(`fd`,a,{timeout:1500,maxBuffer:4194304},(t,n)=>{if(t&&n.length===0&&t.code!==1){e(void 0);return}e(n.split(`
|
|
2
|
+
`).map(e=>e.trimEnd().replace(/\/$/u,``)).filter(e=>e!==``))})})}async function s(n,i){let o=[],s=[n],c=0;for(let n=0;n<6&&s.length>0&&c<r;n+=1){let n=[];for(let l of s){if(c>=r)break;let s;try{s=await e.promises.readdir(l,{withFileTypes:!0})}catch{continue}for(let e of s){if(!e.isDirectory()||a(e.name,i))continue;c+=1;let r=t.join(l,e.name);o.push(r),n.push(r)}}s=n}return o}async function c(e,t){let n=t.startsWith(`.`);return await o(e,t,n)??await s(e,n)}export{c as searchDirectoryTree};
|
|
3
|
+
//# sourceMappingURL=directorySearch.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directorySearch.mjs","names":[],"sources":["../../../src/adapters/directorySearch.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\n/** How deep a search descends; a project someone starts a session in is never far from home. */\nconst MAX_DEPTH = 6;\n/** A ceiling on the walk so a pathological tree cannot hold the request open. */\nconst MAX_VISITED = 20_000;\n/** `fd` is fast; if it has not answered by now the fallback will beat waiting for it. */\nconst FD_TIMEOUT_MS = 1_500;\nconst FD_MAX_BUFFER = 4 * 1024 * 1024;\n\n/**\n * Directories nobody starts a session in, and which dwarf everything else in\n * a tree. Pruning them is the difference between a walk that answers now and\n * one that reads a million files.\n */\nconst PRUNED = new Set([\n 'node_modules',\n 'Library',\n 'Applications',\n 'dist',\n 'build',\n 'out',\n 'target',\n 'vendor',\n 'coverage',\n '.git',\n '.nx',\n '.next',\n '.cache',\n '.venv',\n '__pycache__',\n]);\n\nconst HIDDEN_PREFIX = '.';\n\nfunction skips(name: string, wantsHidden: boolean): boolean {\n if (PRUNED.has(name)) return true;\n return name.startsWith(HIDDEN_PREFIX) && !wantsHidden;\n}\n\n/**\n * `fd` if this machine has it: it is built for exactly this, prunes ignored\n * paths itself, and is far quicker than walking from Node. A missing binary,\n * a non-zero exit, or a slow answer all fall through to the walk rather than\n * failing the request, because a suggestion list is a convenience.\n */\nasync function searchWithFd(root: string, query: string, wantsHidden: boolean): Promise<string[] | undefined> {\n const args = [\n '--type',\n 'd',\n '--absolute-path',\n '--fixed-strings',\n '--color',\n 'never',\n '--max-depth',\n String(MAX_DEPTH),\n // The same prune list the walk uses, so which engine answered is invisible\n // to the reader: no caches, no build output, no OS folders nobody codes in.\n ...[...PRUNED].flatMap((name) => ['--exclude', name]),\n ...(wantsHidden ? ['--hidden'] : []),\n '--',\n query,\n root,\n ];\n return new Promise((resolve) => {\n execFile('fd', args, { timeout: FD_TIMEOUT_MS, maxBuffer: FD_MAX_BUFFER }, (error, stdout) => {\n // fd exits non-zero when it matches nothing, which is an answer, not a\n // failure; only a missing binary or a timeout sends us to the walk.\n if (error && stdout.length === 0 && error.code !== 1) {\n resolve(undefined);\n return;\n }\n resolve(\n stdout\n .split('\\n')\n .map((line) => line.trimEnd().replace(/\\/$/u, ''))\n .filter((line) => line !== ''),\n );\n });\n });\n}\n\n/** Every directory under a root, breadth first, bounded in depth and in total work. */\nasync function walkDirectories(root: string, wantsHidden: boolean): Promise<string[]> {\n const found: string[] = [];\n let frontier = [root];\n let visited = 0;\n\n for (let depth = 0; depth < MAX_DEPTH && frontier.length > 0 && visited < MAX_VISITED; depth += 1) {\n const next: string[] = [];\n for (const directory of frontier) {\n if (visited >= MAX_VISITED) break;\n let entries: fs.Dirent[];\n try {\n entries = await fs.promises.readdir(directory, { withFileTypes: true });\n } catch {\n continue; // Unreadable is not an error here, just nothing to offer.\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || skips(entry.name, wantsHidden)) continue;\n visited += 1;\n const child = path.join(directory, entry.name);\n found.push(child);\n next.push(child);\n }\n }\n frontier = next;\n }\n return found;\n}\n\n/**\n * Directories under a root whose path could answer the query.\n *\n * Matching happens in the caller, against the whole candidate set, so the\n * ranking is the same whichever engine produced it.\n */\nexport async function searchDirectoryTree(root: string, query: string): Promise<string[]> {\n const wantsHidden = query.startsWith(HIDDEN_PREFIX);\n const viaFd = await searchWithFd(root, query, wantsHidden);\n return viaFd ?? (await walkDirectories(root, wantsHidden));\n}\n"],"mappings":"8FAKA,MAEM,EAAc,IAUd,EAAS,IAAI,IAAI,CACrB,eACA,UACA,eACA,OACA,QACA,MACA,SACA,SACA,WACA,OACA,MACA,QACA,SACA,QACA,aACF,CAAC,EAID,SAAS,EAAM,EAAc,EAA+B,CAE1D,OADI,EAAO,IAAI,CAAI,EAAU,GACtB,EAAK,WAAW,GAAa,GAAK,CAAC,CAC5C,CAQA,eAAe,EAAa,EAAc,EAAe,EAAqD,CAC5G,IAAM,EAAO,CACX,SACA,IACA,kBACA,kBACA,UACA,QACA,cACA,IAGA,GAAG,CAAC,GAAG,CAAM,CAAC,CAAC,QAAS,GAAS,CAAC,YAAa,CAAI,CAAC,EACpD,GAAI,EAAc,CAAC,UAAU,EAAI,CAAC,EAClC,KACA,EACA,CACF,EACA,OAAO,IAAI,QAAS,GAAY,CAC9B,EAAS,KAAM,EAAM,CAAE,QAAS,KAAe,UAAW,OAAc,GAAI,EAAO,IAAW,CAG5F,GAAI,GAAS,EAAO,SAAW,GAAK,EAAM,OAAS,EAAG,CACpD,EAAQ,IAAA,EAAS,EACjB,MACF,CACA,EACE,EACG,MAAM;CAAI,CAAC,CACX,IAAK,GAAS,EAAK,QAAQ,CAAC,CAAC,QAAQ,OAAQ,EAAE,CAAC,CAAC,CACjD,OAAQ,GAAS,IAAS,EAAE,CACjC,CACF,CAAC,CACH,CAAC,CACH,CAGA,eAAe,EAAgB,EAAc,EAAyC,CACpF,IAAM,EAAkB,CAAC,EACrB,EAAW,CAAC,CAAI,EAChB,EAAU,EAEd,IAAK,IAAI,EAAQ,EAAG,EAAQ,GAAa,EAAS,OAAS,GAAK,EAAU,EAAa,GAAS,EAAG,CACjG,IAAM,EAAiB,CAAC,EACxB,IAAK,IAAM,KAAa,EAAU,CAChC,GAAI,GAAW,EAAa,MAC5B,IAAI,EACJ,GAAI,CACF,EAAU,MAAM,EAAG,SAAS,QAAQ,EAAW,CAAE,cAAe,EAAK,CAAC,CACxE,MAAQ,CACN,QACF,CACA,IAAK,IAAM,KAAS,EAAS,CAC3B,GAAI,CAAC,EAAM,YAAY,GAAK,EAAM,EAAM,KAAM,CAAW,EAAG,SAC5D,GAAW,EACX,IAAM,EAAQ,EAAK,KAAK,EAAW,EAAM,IAAI,EAC7C,EAAM,KAAK,CAAK,EAChB,EAAK,KAAK,CAAK,CACjB,CACF,CACA,EAAW,CACb,CACA,OAAO,CACT,CAQA,eAAsB,EAAoB,EAAc,EAAkC,CACxF,IAAM,EAAc,EAAM,WAAW,GAAa,EAElD,OAAO,MADa,EAAa,EAAM,EAAO,CAAW,GACxC,MAAM,EAAgB,EAAM,CAAW,CAC1D"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
let e=require("node:child_process");function t(t,n){return new Promise(r=>{(0,e.execFile)(`git`,n,{cwd:t,timeout:2e3},(e,t)=>{r(e?void 0:t)})})}async function n(e){let n=(await t(e,[`rev-parse`,`--abbrev-ref`,`HEAD`]))?.trim();if(!n)return;let r=await t(e,[`status`,`--porcelain`]);return{branch:n,dirty:r!==void 0&&r.trim().length>0}}exports.readGitStatus=n;
|
|
2
|
+
//# sourceMappingURL=gitStatus.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitStatus.cjs","names":[],"sources":["../../../src/adapters/gitStatus.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport type { SessionGitStatus } from '../types/hub.ts';\n\nconst GIT_TIMEOUT_MS = 2000;\n\nfunction git(cwd: string, args: string[]): Promise<string | undefined> {\n return new Promise((resolve) => {\n execFile('git', args, { cwd, timeout: GIT_TIMEOUT_MS }, (error, stdout) => {\n // A non-repo cwd, missing git, or timeout all mean the same thing to the\n // rail: no branch chip.\n resolve(error ? undefined : stdout);\n });\n });\n}\n\n/**\n * Reads the branch chip for one session's working directory.\n *\n * Asks git itself rather than parsing .git/HEAD: worktrees (where .git is a\n * file) come free, and only git can answer whether the tree is dirty.\n */\nexport async function readGitStatus(cwd: string): Promise<SessionGitStatus | undefined> {\n const branch = (await git(cwd, ['rev-parse', '--abbrev-ref', 'HEAD']))?.trim();\n if (!branch) return undefined;\n const status = await git(cwd, ['status', '--porcelain']);\n return { branch, dirty: status !== undefined && status.trim().length > 0 };\n}\n"],"mappings":"oCAKA,SAAS,EAAI,EAAa,EAA6C,CACrE,OAAO,IAAI,QAAS,GAAY,EAC9B,EAAA,EAAA,SAAA,CAAS,MAAO,EAAM,CAAE,MAAK,QAAS,GAAe,GAAI,EAAO,IAAW,CAGzE,EAAQ,EAAQ,IAAA,GAAY,CAAM,CACpC,CAAC,CACH,CAAC,CACH,CAQA,eAAsB,EAAc,EAAoD,CACtF,IAAM,GAAU,MAAM,EAAI,EAAK,CAAC,YAAa,eAAgB,MAAM,CAAC,EAAA,EAAI,KAAK,EAC7E,GAAI,CAAC,EAAQ,OACb,IAAM,EAAS,MAAM,EAAI,EAAK,CAAC,SAAU,aAAa,CAAC,EACvD,MAAO,CAAE,SAAQ,MAAO,IAAW,IAAA,IAAa,EAAO,KAAK,CAAC,CAAC,OAAS,CAAE,CAC3E"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{execFile as e}from"node:child_process";function t(t,n){return new Promise(r=>{e(`git`,n,{cwd:t,timeout:2e3},(e,t)=>{r(e?void 0:t)})})}async function n(e){let n=(await t(e,[`rev-parse`,`--abbrev-ref`,`HEAD`]))?.trim();if(!n)return;let r=await t(e,[`status`,`--porcelain`]);return{branch:n,dirty:r!==void 0&&r.trim().length>0}}export{n as readGitStatus};
|
|
2
|
+
//# sourceMappingURL=gitStatus.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitStatus.mjs","names":[],"sources":["../../../src/adapters/gitStatus.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport type { SessionGitStatus } from '../types/hub.ts';\n\nconst GIT_TIMEOUT_MS = 2000;\n\nfunction git(cwd: string, args: string[]): Promise<string | undefined> {\n return new Promise((resolve) => {\n execFile('git', args, { cwd, timeout: GIT_TIMEOUT_MS }, (error, stdout) => {\n // A non-repo cwd, missing git, or timeout all mean the same thing to the\n // rail: no branch chip.\n resolve(error ? undefined : stdout);\n });\n });\n}\n\n/**\n * Reads the branch chip for one session's working directory.\n *\n * Asks git itself rather than parsing .git/HEAD: worktrees (where .git is a\n * file) come free, and only git can answer whether the tree is dirty.\n */\nexport async function readGitStatus(cwd: string): Promise<SessionGitStatus | undefined> {\n const branch = (await git(cwd, ['rev-parse', '--abbrev-ref', 'HEAD']))?.trim();\n if (!branch) return undefined;\n const status = await git(cwd, ['status', '--porcelain']);\n return { branch, dirty: status !== undefined && status.trim().length > 0 };\n}\n"],"mappings":"8CAKA,SAAS,EAAI,EAAa,EAA6C,CACrE,OAAO,IAAI,QAAS,GAAY,CAC9B,EAAS,MAAO,EAAM,CAAE,MAAK,QAAS,GAAe,GAAI,EAAO,IAAW,CAGzE,EAAQ,EAAQ,IAAA,GAAY,CAAM,CACpC,CAAC,CACH,CAAC,CACH,CAQA,eAAsB,EAAc,EAAoD,CACtF,IAAM,GAAU,MAAM,EAAI,EAAK,CAAC,YAAa,eAAgB,MAAM,CAAC,EAAA,EAAI,KAAK,EAC7E,GAAI,CAAC,EAAQ,OACb,IAAM,EAAS,MAAM,EAAI,EAAK,CAAC,SAAU,aAAa,CAAC,EACvD,MAAO,CAAE,SAAQ,MAAO,IAAW,IAAA,IAAa,EAAO,KAAK,CAAC,CAAC,OAAS,CAAE,CAC3E"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("./piHubService.cjs"),n=require("./syncGuard.cjs"),r=require("./piWebSocketListener.cjs"),i=require("../types/media.cjs"),a=require("../services/fileMedia.cjs"),o=require("../services/staticAssets.cjs"),s=require("../types/hub.cjs");require("../types/session.cjs");const c=require("./gitStatus.cjs"),l=require("./registryWatcher.cjs"),u=require("./serverSpawner.cjs"),d=require("./sessionHub.cjs"),f=require("./authRoutes.cjs"),p=require("./providerAuth.cjs"),m=require("./directoryListing.cjs"),h=require("./sessionFiles.cjs"),g=require("./packageApiProxy.cjs"),_=require("./threadJournals.cjs"),v=require("./webHubPluginLoader.cjs");let y=require("node:fs");y=e.__toESM(y,1);let b=require("node:path");b=e.__toESM(b,1);let x=require("node:url"),S=require("node:os");S=e.__toESM(S,1);let C=require("@hono/node-server"),w=require("@hono/node-ws"),T=require("@earendil-works/pi-server"),E=require("hono"),D=require("@agimon-ai/doompi-extension-contracts/package-api"),O=require("@agimon-ai/doompi-extension-contracts/package-api-loader");const k=`index.html`,A=[`.doompi`,`web`,`current`,`web`];function j(){let e=b.default.dirname((0,x.fileURLToPath)(require("url").pathToFileURL(__filename).href));for(;;){if(y.default.existsSync(b.default.join(e,`package.json`)))return b.default.join(e,`dist`,`web`);let t=b.default.dirname(e);if(t===e)throw Error(`doompi-web could not locate its own package root.`);e=t}}function M(e,t){if(e!==void 0)return e;let n=process.env.DOOMPI_WEB_DIST;if(n!==void 0&&n!==``)return n;let r=b.default.join(S.default.homedir(),...A);return y.default.existsSync(b.default.join(r,k))?(t(`serving the synced cockpit bundle from ${r}`),r):j()}function N(e){try{return y.default.statSync(e).isFile()?y.default.readFileSync(e):void 0}catch{return}}function P(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function F(e){return e instanceof Error?e.message:String(e)}function I(e,t){return`${e}\n${t}`}function L(e,t,n){return d.createSessionHub({source:l.watchRegistry(e.registryDir,t),spawner:u.createServerSpawner({registryDir:e.registryDir,command:e.spawnCommand,onNotice:t}),readGit:c.readGitStatus,channels:n,onNotice:t})}function R(e,t,n){let r=[];for(let i of t){let t=`${D.DOOM_API_ROUTE_PREFIX}/${i.basePath}`,a;try{a=i.start({scope:`hub`,onNotice:n})}catch(e){n(`hub API '${i.basePath}' did not start (${F(e)}); its routes stay unmounted`);continue}r.push(a),e.all(`${t}/*`,async e=>{if(e.req.query(`session`)!==void 0)return e.notFound();let r=new URL(e.req.url);r.pathname=r.pathname.slice(t.length)||`/`;try{return await a.fetch(new Request(r,e.req.raw))}catch(t){return n(`hub API '${i.basePath}' failed on ${r.pathname} (${F(t)})`),e.json({error:`The '${i.basePath}' API failed.`},500)}})}return r}function z(e,t,n){e.all(`${D.DOOM_API_ROUTE_PREFIX}/*`,async e=>{let r=e.req.query(s.API_SESSION_QUERY_PARAM);if(r===void 0)return e.notFound();let i=t.snapshot().find(e=>e.id===r);if(i===void 0)return e.json({error:`No session ${r}.`},404);if(i.apiSocketPath===void 0)return e.json({error:`Session ${r} serves no package API.`},404);let a=new URL(e.req.url);a.searchParams.delete(s.API_SESSION_QUERY_PARAM);try{return await g.proxyToSocket({socketPath:i.apiSocketPath,path:`${a.pathname}${a.search}`,method:e.req.method,headers:e.req.raw.headers,body:e.req.raw.body})}catch(t){return n(`session ${r} API is unreachable (${F(t)})`),e.json({error:`Session ${r} is not answering.`},502)}})}async function B(e){let c=e.host??`127.0.0.1`,l=e.onNotice??(()=>{}),u=M(e.assetsDir,l),d=L(e,l,await v.loadHubChannels(u,l)),g=_.createThreadJournals({resolve:(e,t)=>d.threadJournal(e,t),onNotice:l}),y=n.createSyncGuard({repoRoot:process.cwd(),onNotice:l});await y.ensureSynced();let x=new Set,S=new E.Hono,D=(0,w.createNodeWebSocket)({app:S}),A=r.createPiWebSocketListener({onError:e=>l(`protocol: ${e.message}`)}),j=new T.PiServer(t.createPiHubService({records:()=>d.records(),spawn:e=>d.create(e),onNotice:l}),{listeners:[A],onError:e=>l(`protocol: ${e.message}`)});await j.start();let F=p.createProviderAuth({runtime:e.authRuntime,onNotice:l});f.registerAuthRoutes(S,F);let B=R(S,await(0,O.loadPackageApis)(`hub`,{onNotice:l}),l);return z(S,d,l),S.get(`/api/health`,e=>e.json({ok:!0,role:`hub`,protocol:1,sessions:d.snapshot().length,pid:process.pid})),S.post(s.SESSIONS_API_ROUTE,async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:`The request body must be JSON.`},400)}if(!P(t)||typeof t.cwd!=`string`||t.cwd===``)return e.json({error:`A cwd string is required.`},400);let n=typeof t.name==`string`&&t.name!==``?t.name:void 0;await y.ensureSynced();let r=await d.create({cwd:t.cwd,name:n});return r.ok?e.json({sessionId:r.sessionId},201):e.json({error:r.error},r.code===`invalid_request`?400:502)}),S.delete(`${s.SESSIONS_API_ROUTE}/:sessionId`,e=>{let t=e.req.param(`sessionId`),n=d.stop(t);return n.ok?e.json({sessionId:t},202):e.json({error:n.error},n.code===`unknown`?404:n.code===`self`?409:502)}),S.get(`/api/sessions/:sessionId/files`,async e=>{let t=e.req.param(`sessionId`),n=d.snapshot().find(e=>e.id===t);if(!n)return e.json({error:`Unknown session.`},404);let r=e.req.query(`q`)??``,i=await h.listSessionFiles(n.cwd,r,20);return e.json({files:i})}),S.get(s.DIRECTORIES_API_ROUTE,async e=>{let t=await m.suggestDirectories(e.req.query(`q`)??``,{limit:12});return e.json({directories:t})}),S.get(i.SESSION_FILE_ROUTE,async e=>{let t=e.req.param(`sessionId`),n=d.snapshot().find(e=>e.id===t);if(!n)return e.json({error:`Unknown session.`},404);let r=e.req.query(`path`)??``,o=await h.readSessionFile(n.cwd,r,i.MAX_SESSION_FILE_BYTES);return o.status===`forbidden`?e.json({error:`The path leaves the session directory.`},403):o.status===`not-found`?e.json({error:`No such file.`},404):o.status===`too-large`?e.json({error:`The file is too large to preview.`},413):e.body(new Uint8Array(o.body),200,a.sessionFileHeaders(r))}),S.get(`/api/session`,D.upgradeWebSocket(()=>{let e=new Set,t=new Map,n,r,i,a=e=>{for(let[n,r]of t)(e===void 0||r.sessionId===e)&&(t.delete(n),g.unsubscribe(r.sessionId,r.threadId))};return{onOpen(o,c){let u=e=>{try{c.send(JSON.stringify(e))}catch{}};i=u,x.add(u),u(s.hubHello(d.channelTypes())),u({type:s.SESSIONS_SNAPSHOT_TYPE,sessions:d.snapshot()}),n=d.onEvent(t=>{t.kind===`upsert`?u({type:s.SESSION_UPSERT_TYPE,session:t.session}):t.kind===`removed`?(e.delete(t.sessionId),a(t.sessionId),u({type:s.SESSION_REMOVED_TYPE,sessionId:t.sessionId})):t.kind===`channel`?e.has(t.sessionId)&&u({type:t.frameType,sessionId:t.sessionId,payload:t.payload}):e.has(t.sessionId)&&u(s.sessionFrameEnvelope(t.sessionId,t.frame))}),r=g.onFrame(e=>{t.has(I(e.sessionId,e.threadId))&&u(s.threadFrameEnvelope(e.sessionId,e.threadId,e.frame))}),l(`browser attached`)},onMessage(n,r){if(typeof n.data!=`string`)return;let i;try{i=JSON.parse(n.data)}catch{return}if(!P(i)||typeof i.sessionId!=`string`)return;let a=i.sessionId;if(i.type===`subscribe`){let t=d.backlog(a);if(!t)return;e.add(a);try{r.send(JSON.stringify(t));for(let e of d.channelFrames(a))r.send(JSON.stringify(e))}catch{}return}if(i.type===`unsubscribe`){e.delete(a);return}if(i.type===`history_request`){let e=d.history(a,{...typeof i.before==`string`?{before:i.before}:{},...typeof i.limit==`number`?{limit:i.limit}:{}});if(!e)return;try{r.send(JSON.stringify(e))}catch{}return}if(i.type===`subscribe_thread`||i.type===`unsubscribe_thread`){if(typeof i.threadId!=`string`)return;let e=i.threadId,n=I(a,e);if(i.type===`unsubscribe_thread`){t.delete(n)&&g.unsubscribe(a,e);return}if(t.has(n)||d.backlog(a)===void 0)return;t.set(n,{sessionId:a,threadId:e});try{r.send(JSON.stringify(s.threadBacklog(a,e,g.subscribe(a,e))))}catch{}return}if(i.type===`session_command`&&P(i.frame)){if(i.frame.type===`attach`)return;d.command(a,i.frame)}},onClose(){i&&x.delete(i),i=void 0,n?.(),n=void 0,r?.(),r=void 0,e.clear(),a(),l(`browser detached`)}}})),S.get(`/api/pi`,D.upgradeWebSocket(()=>{let e;return{onOpen(t,n){e=A.accept({send:e=>n.send(e),close:()=>n.close(),get readyState(){return n.readyState}})},onMessage(t){let n=t.data;typeof n!=`string`&&(n instanceof ArrayBuffer?e?.onData(new Uint8Array(n)):ArrayBuffer.isView(n)&&e?.onData(new Uint8Array(n.buffer,n.byteOffset,n.byteLength)))},onClose(){e?.onClose(),e=void 0},onError(t){e?.onError(t instanceof Error?t:Error(`The protocol socket failed`)),e=void 0}}})),S.get(`*`,e=>{let t=new URL(e.req.url).pathname,n=t===`/`?void 0:o.resolveAssetPath(u,t),r=n?N(n):void 0;if(n&&r)return e.body(new Uint8Array(r),200,{"Content-Type":o.contentTypeFor(n)});let i=N(b.default.join(u,k));return i?e.body(new Uint8Array(i),200,{"Content-Type":`text/html; charset=utf-8`}):e.text(`The cockpit bundle is missing. Run the package build.`,500)}),y.watch(()=>{for(let e of x)e({type:s.HUB_RESYNCED_TYPE})}),new Promise((t,n)=>{let r=(0,C.serve)({fetch:S.fetch,port:e.port,hostname:c},e=>{D.injectWebSocket(r);let n=`http://${c}:${e.port}`;l(`cockpit on ${n}`),t({url:n,port:e.port,close:()=>new Promise(e=>{g.close(),y.close(),j.close(),d.close(),F.close();for(let e of B)e.close();for(let e of D.wss.clients)e.terminate();D.wss.close(),r.closeAllConnections?.(),r.close(()=>e())})})});r.once(`error`,e=>{g.close(),d.close(),F.close();for(let e of B)e.close();n(e)})})}exports.serveWeb=B;
|
|
2
|
+
//# sourceMappingURL=httpServer.cjs.map
|