@belte/belte 0.19.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/CHANGELOG.md +313 -0
- package/LICENSE +21 -0
- package/README.md +559 -0
- package/bin/belte.ts +183 -0
- package/package.json +110 -0
- package/src/App.svelte +31 -0
- package/src/appEntry.ts +151 -0
- package/src/assets/app.html +14 -0
- package/src/belteResolverPlugin.ts +858 -0
- package/src/build.ts +147 -0
- package/src/buildCli.ts +129 -0
- package/src/buildDisconnected.ts +122 -0
- package/src/bundleApp.ts +149 -0
- package/src/bundleDisconnectedEntry.ts +17 -0
- package/src/cliEntry.ts +25 -0
- package/src/clientBuildPlugins.ts +41 -0
- package/src/clientEntry.ts +7 -0
- package/src/compile.ts +64 -0
- package/src/controlServerWorker.ts +422 -0
- package/src/dedupeSveltePlugin.ts +66 -0
- package/src/devEntry.ts +169 -0
- package/src/discoveryEntry.ts +81 -0
- package/src/lib/browser/applyStreamedResolution.ts +33 -0
- package/src/lib/browser/cacheEntryFromSnapshot.ts +48 -0
- package/src/lib/browser/flushUnresolvedPlaceholders.ts +16 -0
- package/src/lib/browser/installStreamingPlaceholders.ts +32 -0
- package/src/lib/browser/openResolveStream.ts +42 -0
- package/src/lib/browser/page.svelte.ts +258 -0
- package/src/lib/browser/pageStreamController.ts +17 -0
- package/src/lib/browser/refetchPlaceholder.ts +12 -0
- package/src/lib/browser/remoteProxy.ts +37 -0
- package/src/lib/browser/socketChannel.ts +192 -0
- package/src/lib/browser/socketProxy.ts +57 -0
- package/src/lib/browser/startClient.ts +153 -0
- package/src/lib/browser/subscribe.ts +131 -0
- package/src/lib/browser/types/Errors.ts +9 -0
- package/src/lib/browser/types/Layouts.ts +7 -0
- package/src/lib/browser/types/Pages.ts +7 -0
- package/src/lib/browser/types/StreamingDeferred.ts +9 -0
- package/src/lib/bundle/BundleMenu.ts +11 -0
- package/src/lib/bundle/BundleMenuItem.ts +24 -0
- package/src/lib/bundle/BundleWindow.ts +36 -0
- package/src/lib/bundle/WEBVIEW_BUILD_REVISION.ts +9 -0
- package/src/lib/bundle/WEBVIEW_VERSION.ts +7 -0
- package/src/lib/bundle/bindConnectedFlag.ts +29 -0
- package/src/lib/bundle/bindRequestNavigate.ts +31 -0
- package/src/lib/bundle/buildWebviewLib.ts +111 -0
- package/src/lib/bundle/disconnected.css +9 -0
- package/src/lib/bundle/disconnected.svelte +386 -0
- package/src/lib/bundle/ensureWebviewLib.ts +20 -0
- package/src/lib/bundle/exitWithParent.ts +28 -0
- package/src/lib/bundle/infoPlist.ts +46 -0
- package/src/lib/bundle/installDownloads.ts +24 -0
- package/src/lib/bundle/installMacMenu.ts +39 -0
- package/src/lib/bundle/listenLocalControlServer.ts +19 -0
- package/src/lib/bundle/native/belteMenu.mm +422 -0
- package/src/lib/bundle/native/webview.h +4557 -0
- package/src/lib/bundle/onMenu.ts +41 -0
- package/src/lib/bundle/openWebview.ts +104 -0
- package/src/lib/bundle/pngToIcns.ts +47 -0
- package/src/lib/bundle/probeBelteServer.ts +34 -0
- package/src/lib/bundle/resolveServerBinary.ts +12 -0
- package/src/lib/bundle/resolveWebviewLib.ts +53 -0
- package/src/lib/bundle/serverBinaryFilename.ts +8 -0
- package/src/lib/bundle/signMacApp.ts +35 -0
- package/src/lib/bundle/spawnEmbeddedServer.ts +65 -0
- package/src/lib/bundle/stableLocalPort.ts +19 -0
- package/src/lib/bundle/waitForServer.ts +23 -0
- package/src/lib/bundle/webviewCachePath.ts +23 -0
- package/src/lib/bundle/webviewLibName.ts +11 -0
- package/src/lib/cli/connectToServer.ts +23 -0
- package/src/lib/cli/createClient.ts +170 -0
- package/src/lib/cli/dispatchCommand.ts +71 -0
- package/src/lib/cli/loadEnvFromBinaryDir.ts +16 -0
- package/src/lib/cli/parseArgvForRpc.ts +97 -0
- package/src/lib/cli/printHelp.ts +119 -0
- package/src/lib/cli/printSessionHelp.ts +27 -0
- package/src/lib/cli/printSessionStatus.ts +21 -0
- package/src/lib/cli/printTrimmed.ts +8 -0
- package/src/lib/cli/printValue.ts +10 -0
- package/src/lib/cli/resolveCliTarget.ts +48 -0
- package/src/lib/cli/runCli.ts +139 -0
- package/src/lib/cli/runSession.ts +105 -0
- package/src/lib/cli/startLocalInstance.ts +14 -0
- package/src/lib/cli/tokenizeLine.ts +51 -0
- package/src/lib/cli/types/CliManifest.ts +9 -0
- package/src/lib/cli/types/CliManifestEntry.ts +17 -0
- package/src/lib/cli/types/CliTarget.ts +13 -0
- package/src/lib/mcp/annotationsForMethod.ts +29 -0
- package/src/lib/mcp/createMcpResourceServer.ts +101 -0
- package/src/lib/mcp/createMcpServer.ts +42 -0
- package/src/lib/mcp/dispatchMcpRequest.ts +146 -0
- package/src/lib/mcp/mcpResourceServerSlot.ts +18 -0
- package/src/lib/mcp/mcpSurface.ts +265 -0
- package/src/lib/mcp/toolResultFromResponse.ts +66 -0
- package/src/lib/mcp/types/JsonRpcRequest.ts +12 -0
- package/src/lib/mcp/types/JsonRpcResponse.ts +20 -0
- package/src/lib/mcp/types/McpResourceContents.ts +10 -0
- package/src/lib/mcp/types/McpResourceDescriptor.ts +6 -0
- package/src/lib/mcp/types/McpResourceServer.ts +12 -0
- package/src/lib/mcp/types/McpServer.ts +9 -0
- package/src/lib/mcp/types/McpServerOptions.ts +16 -0
- package/src/lib/server/AppModule.ts +33 -0
- package/src/lib/server/DELETE.ts +9 -0
- package/src/lib/server/GET.ts +9 -0
- package/src/lib/server/HEAD.ts +9 -0
- package/src/lib/server/PATCH.ts +9 -0
- package/src/lib/server/POST.ts +9 -0
- package/src/lib/server/PUT.ts +9 -0
- package/src/lib/server/agent.ts +76 -0
- package/src/lib/server/appDataDir.ts +15 -0
- package/src/lib/server/cli/buildEnvContent.ts +19 -0
- package/src/lib/server/cli/createTarGz.ts +76 -0
- package/src/lib/server/cli/handleCliDownload.ts +153 -0
- package/src/lib/server/cli/handleCliInstall.ts +37 -0
- package/src/lib/server/cli/installScript.ts +29 -0
- package/src/lib/server/cli/maxSourceMtime.ts +26 -0
- package/src/lib/server/cookies.ts +29 -0
- package/src/lib/server/env.ts +50 -0
- package/src/lib/server/error.ts +70 -0
- package/src/lib/server/json.ts +28 -0
- package/src/lib/server/jsonl.ts +46 -0
- package/src/lib/server/prompts/definePrompt.ts +20 -0
- package/src/lib/server/prompts/promptRegistry.ts +9 -0
- package/src/lib/server/prompts/registerPrompt.ts +6 -0
- package/src/lib/server/prompts/renderPromptTemplate.ts +16 -0
- package/src/lib/server/prompts/types/Prompt.ts +13 -0
- package/src/lib/server/prompts/types/PromptOptions.ts +12 -0
- package/src/lib/server/prompts/types/PromptRegistryEntry.ts +13 -0
- package/src/lib/server/prompts/types/PromptRoutes.ts +10 -0
- package/src/lib/server/redirect.ts +42 -0
- package/src/lib/server/request.ts +18 -0
- package/src/lib/server/rpc/defineVerb.ts +133 -0
- package/src/lib/server/rpc/dispatchVerbInProcess.ts +46 -0
- package/src/lib/server/rpc/findVerbByCommandName.ts +18 -0
- package/src/lib/server/rpc/parseArgs.ts +95 -0
- package/src/lib/server/rpc/registerVerb.ts +6 -0
- package/src/lib/server/rpc/types/RemoteHandler.ts +27 -0
- package/src/lib/server/rpc/types/RemoteRoutes.ts +13 -0
- package/src/lib/server/rpc/types/TypedResponse.ts +18 -0
- package/src/lib/server/rpc/types/VerbHelper.ts +68 -0
- package/src/lib/server/rpc/types/VerbRegistryEntry.ts +29 -0
- package/src/lib/server/rpc/unprocessed.ts +14 -0
- package/src/lib/server/rpc/verbRegistry.ts +11 -0
- package/src/lib/server/runtime/DEFAULT_PORT.ts +6 -0
- package/src/lib/server/runtime/DEV_REBUILD_MESSAGE.ts +4 -0
- package/src/lib/server/runtime/DEV_RELOAD_CLIENT_SCRIPT.ts +29 -0
- package/src/lib/server/runtime/acceptsZstd.ts +8 -0
- package/src/lib/server/runtime/buildOpenApiSpec.ts +106 -0
- package/src/lib/server/runtime/cacheControlForAsset.ts +22 -0
- package/src/lib/server/runtime/containsTraversal.ts +37 -0
- package/src/lib/server/runtime/createAssetHeaderCache.ts +35 -0
- package/src/lib/server/runtime/createPublicAssetServer.ts +63 -0
- package/src/lib/server/runtime/createRouteDispatcher.ts +100 -0
- package/src/lib/server/runtime/createServer.ts +692 -0
- package/src/lib/server/runtime/devReloadResponse.ts +35 -0
- package/src/lib/server/runtime/disableIdleTimeoutForStream.ts +27 -0
- package/src/lib/server/runtime/envSchemaStore.ts +15 -0
- package/src/lib/server/runtime/findOpenPort.ts +35 -0
- package/src/lib/server/runtime/getActiveServer.ts +6 -0
- package/src/lib/server/runtime/globToPathSet.ts +29 -0
- package/src/lib/server/runtime/inProcessServer.ts +20 -0
- package/src/lib/server/runtime/internalErrorResponse.ts +25 -0
- package/src/lib/server/runtime/isCrossOriginUpgrade.ts +19 -0
- package/src/lib/server/runtime/listenOnOpenPort.ts +36 -0
- package/src/lib/server/runtime/logExposedSurfaces.ts +162 -0
- package/src/lib/server/runtime/mimeForExtension.ts +20 -0
- package/src/lib/server/runtime/parseIdleTimeout.ts +10 -0
- package/src/lib/server/runtime/parsePort.ts +11 -0
- package/src/lib/server/runtime/registryManifests.ts +66 -0
- package/src/lib/server/runtime/requestContext.ts +5 -0
- package/src/lib/server/runtime/resolveStreamResponse.ts +29 -0
- package/src/lib/server/runtime/runWithRequestScope.ts +57 -0
- package/src/lib/server/runtime/safeJsonForScript.ts +17 -0
- package/src/lib/server/runtime/serializeCacheSnapshot.ts +45 -0
- package/src/lib/server/runtime/serverSlot.ts +13 -0
- package/src/lib/server/runtime/setActiveServer.ts +6 -0
- package/src/lib/server/runtime/snapshotEntryFromCache.ts +81 -0
- package/src/lib/server/runtime/streamCacheResolutions.ts +37 -0
- package/src/lib/server/runtime/streamFromIterator.ts +86 -0
- package/src/lib/server/runtime/streamStash.ts +64 -0
- package/src/lib/server/runtime/types/Assets.ts +1 -0
- package/src/lib/server/runtime/types/RequestStore.ts +27 -0
- package/src/lib/server/runtime/withResponseDefaults.ts +24 -0
- package/src/lib/server/server.ts +32 -0
- package/src/lib/server/socket.ts +31 -0
- package/src/lib/server/sockets/createSocketDispatcher.ts +311 -0
- package/src/lib/server/sockets/defineSocket.ts +167 -0
- package/src/lib/server/sockets/lookupSocket.ts +6 -0
- package/src/lib/server/sockets/recentHistory.ts +11 -0
- package/src/lib/server/sockets/registerSocket.ts +6 -0
- package/src/lib/server/sockets/socketOperations.ts +35 -0
- package/src/lib/server/sockets/socketRegistry.ts +9 -0
- package/src/lib/server/sockets/types/Socket.ts +21 -0
- package/src/lib/server/sockets/types/SocketClientFrame.ts +18 -0
- package/src/lib/server/sockets/types/SocketOperation.ts +22 -0
- package/src/lib/server/sockets/types/SocketOptions.ts +22 -0
- package/src/lib/server/sockets/types/SocketRegistryEntry.ts +17 -0
- package/src/lib/server/sockets/types/SocketRoutes.ts +10 -0
- package/src/lib/server/sockets/types/SocketServerFrame.ts +15 -0
- package/src/lib/server/sse.ts +53 -0
- package/src/lib/shared/BELTE_PACKAGE_NAME.ts +7 -0
- package/src/lib/shared/CACHE_CONTROL_VALUES.ts +16 -0
- package/src/lib/shared/HttpError.ts +19 -0
- package/src/lib/shared/RESOLVE_STREAM_PATH.ts +7 -0
- package/src/lib/shared/STREAMING_CONTENT_TYPES.ts +11 -0
- package/src/lib/shared/activeCacheStore.ts +20 -0
- package/src/lib/shared/appDataDir.ts +34 -0
- package/src/lib/shared/belteImportName.ts +44 -0
- package/src/lib/shared/browserClientFlags.ts +10 -0
- package/src/lib/shared/buildRpcRequest.ts +70 -0
- package/src/lib/shared/bundleLayout.ts +36 -0
- package/src/lib/shared/bundled.ts +34 -0
- package/src/lib/shared/cache.ts +559 -0
- package/src/lib/shared/cacheStoreSlot.ts +16 -0
- package/src/lib/shared/canonicalJson.ts +63 -0
- package/src/lib/shared/carriesBodyArgs.ts +13 -0
- package/src/lib/shared/clearLastConnection.ts +7 -0
- package/src/lib/shared/commandNameForUrl.ts +17 -0
- package/src/lib/shared/createCacheStore.ts +75 -0
- package/src/lib/shared/createPushIterator.ts +93 -0
- package/src/lib/shared/createRemoteFunction.ts +99 -0
- package/src/lib/shared/decodeResponse.ts +47 -0
- package/src/lib/shared/detectTarget.ts +27 -0
- package/src/lib/shared/exeSuffix.ts +9 -0
- package/src/lib/shared/exitOnBuildFailure.ts +17 -0
- package/src/lib/shared/extraForwardHeaders.ts +16 -0
- package/src/lib/shared/fileStem.ts +9 -0
- package/src/lib/shared/findExportCallSite.ts +479 -0
- package/src/lib/shared/forwardHeaders.ts +41 -0
- package/src/lib/shared/getRemoteMeta.ts +5 -0
- package/src/lib/shared/globalCacheStore.ts +15 -0
- package/src/lib/shared/globalCacheStoreSlot.ts +14 -0
- package/src/lib/shared/importNamesToStrip.ts +13 -0
- package/src/lib/shared/invalidateEvent.ts +11 -0
- package/src/lib/shared/isCompileTarget.ts +15 -0
- package/src/lib/shared/isDebugEnabled.ts +23 -0
- package/src/lib/shared/isModuleNotFound.ts +16 -0
- package/src/lib/shared/isReadOnlyMethod.ts +14 -0
- package/src/lib/shared/isStreamingResponse.ts +11 -0
- package/src/lib/shared/jsonSchemaForPromptArguments.ts +29 -0
- package/src/lib/shared/jsonSchemaForSchema.ts +32 -0
- package/src/lib/shared/jsonlErrorFrame.ts +24 -0
- package/src/lib/shared/keyForRemoteCall.ts +29 -0
- package/src/lib/shared/lastConnectionPath.ts +7 -0
- package/src/lib/shared/loadEnvFile.ts +17 -0
- package/src/lib/shared/loadEnvFromDataDir.ts +15 -0
- package/src/lib/shared/loadSvelteConfig.ts +18 -0
- package/src/lib/shared/log.ts +104 -0
- package/src/lib/shared/manifestModule.ts +39 -0
- package/src/lib/shared/memoizeByKey.ts +24 -0
- package/src/lib/shared/nearestLayoutPrefix.ts +36 -0
- package/src/lib/shared/normalizeTarget.ts +10 -0
- package/src/lib/shared/pageUrlForFile.ts +14 -0
- package/src/lib/shared/parseBoundedEnvInt.ts +20 -0
- package/src/lib/shared/parseEnv.ts +30 -0
- package/src/lib/shared/parsePromptMarkdown.ts +34 -0
- package/src/lib/shared/parseRouteSegments.ts +22 -0
- package/src/lib/shared/prepareRpcModule.ts +59 -0
- package/src/lib/shared/prepareSocketModule.ts +49 -0
- package/src/lib/shared/programNameForPackage.ts +14 -0
- package/src/lib/shared/promptNameForFile.ts +10 -0
- package/src/lib/shared/queryStringFromArgs.ts +27 -0
- package/src/lib/shared/readEnvFile.ts +15 -0
- package/src/lib/shared/readLastConnection.ts +18 -0
- package/src/lib/shared/readPackageJson.ts +9 -0
- package/src/lib/shared/recordRemoteMeta.ts +5 -0
- package/src/lib/shared/remoteMetaStore.ts +16 -0
- package/src/lib/shared/resolveClientFlags.ts +20 -0
- package/src/lib/shared/responseErrorText.ts +9 -0
- package/src/lib/shared/rpcUrlForFile.ts +19 -0
- package/src/lib/shared/runningAsStandaloneBinary.ts +13 -0
- package/src/lib/shared/serializeEnv.ts +18 -0
- package/src/lib/shared/setCacheStoreResolver.ts +6 -0
- package/src/lib/shared/setGlobalCacheStoreResolver.ts +6 -0
- package/src/lib/shared/socketNameForFile.ts +11 -0
- package/src/lib/shared/sseErrorFrame.ts +29 -0
- package/src/lib/shared/streamResponse.ts +169 -0
- package/src/lib/shared/stripImport.ts +27 -0
- package/src/lib/shared/subscribableFromResponse.ts +51 -0
- package/src/lib/shared/toBunRoutePattern.ts +28 -0
- package/src/lib/shared/types/CacheEntry.ts +63 -0
- package/src/lib/shared/types/CacheInvalidation.ts +9 -0
- package/src/lib/shared/types/CacheOptions.ts +33 -0
- package/src/lib/shared/types/CacheSnapshot.ts +16 -0
- package/src/lib/shared/types/CacheSnapshotEntry.ts +15 -0
- package/src/lib/shared/types/CacheStore.ts +32 -0
- package/src/lib/shared/types/ClientFlags.ts +11 -0
- package/src/lib/shared/types/CompileTarget.ts +6 -0
- package/src/lib/shared/types/HttpVerb.ts +1 -0
- package/src/lib/shared/types/LastConnection.ts +9 -0
- package/src/lib/shared/types/PromptArgument.ts +12 -0
- package/src/lib/shared/types/RawRemoteFunction.ts +13 -0
- package/src/lib/shared/types/RemoteFunction.ts +42 -0
- package/src/lib/shared/types/StandardSchemaV1.ts +57 -0
- package/src/lib/shared/types/StreamedResolution.ts +10 -0
- package/src/lib/shared/types/StreamingPlaceholder.ts +13 -0
- package/src/lib/shared/types/Subscribable.ts +15 -0
- package/src/lib/shared/types/SvelteConfig.ts +5 -0
- package/src/lib/shared/withJsonSchema.ts +20 -0
- package/src/lib/shared/writeLastConnection.ts +13 -0
- package/src/lib/shared/writeRoutesDts.ts +67 -0
- package/src/lib/test/clearVerbRegistry.ts +11 -0
- package/src/lib/test/createTestClient.ts +78 -0
- package/src/preload.ts +20 -0
- package/src/scaffold.ts +92 -0
- package/src/serverBuildPlugins.ts +25 -0
- package/src/serverEntry.ts +94 -0
- package/src/sveltePlugin.ts +58 -0
- package/src/tailwindStylePreprocessor.ts +62 -0
- package/template/bunfig.toml +4 -0
- package/template/package.json +19 -0
- package/template/src/app.ts +23 -0
- package/template/src/browser/app.css +21 -0
- package/template/src/browser/app.html +24 -0
- package/template/src/browser/pages/about/page.svelte +5 -0
- package/template/src/browser/pages/layout.svelte +26 -0
- package/template/src/browser/pages/page.svelte +20 -0
- package/template/src/bundle/icon.png +0 -0
- package/template/src/cli/banner.txt +3 -0
- package/template/src/cli/footer.txt +1 -0
- package/template/src/server/config.ts +17 -0
- package/template/src/server/rpc/getHello.ts +35 -0
- package/template/svelte.config.js +12 -0
- package/template/tsconfig.json +18 -0
- package/tsconfig.app.json +16 -0
package/README.md
ADDED
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
# belte
|
|
2
|
+
|
|
3
|
+
**Write one function. Get a web app, a CLI, and an AI tool — from the same line of code.**
|
|
4
|
+
|
|
5
|
+
belte is an HTTP framework for Bun + Svelte where a single declared function is
|
|
6
|
+
*simultaneously* an SSR call, a browser fetch, an MCP tool, a CLI subcommand, and
|
|
7
|
+
an OpenAPI operation. You don't wire up five surfaces. You write one handler; the
|
|
8
|
+
bundler swaps the runtime per target.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
// src/server/rpc/getPost.ts — the filename is the export, the URL, and the command name
|
|
12
|
+
import { GET } from '@belte/belte/server/GET'
|
|
13
|
+
import { json } from '@belte/belte/server/json'
|
|
14
|
+
import { z } from 'zod'
|
|
15
|
+
|
|
16
|
+
export const getPost = GET(async ({ id }) => json(await db.post(id)), {
|
|
17
|
+
inputSchema: z.object({ id: z.string() }),
|
|
18
|
+
})
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
That one file is now all of this:
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
src/server/rpc/getPost.ts
|
|
25
|
+
export const getPost = GET(fn, { inputSchema })
|
|
26
|
+
│
|
|
27
|
+
├─ browser await cache(getPost)({ id }) decoded body, SSR-hydrated
|
|
28
|
+
├─ http GET /rpc/getPost?id=… the wire endpoint
|
|
29
|
+
├─ openapi GET /rpc/getPost in /openapi.json operationId: getPost
|
|
30
|
+
├─ mcp tool "getPost" read-only ⇒ auto-exposed
|
|
31
|
+
└─ cli app getPost --id … schema ⇒ typed flags
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
A bare `GET(fn)` always reaches browser, http, and openapi. The MCP tool and CLI
|
|
35
|
+
subcommand turn on once the declaration carries a Standard Schema — the schema is
|
|
36
|
+
what makes a machine-facing surface safe to advertise.
|
|
37
|
+
|
|
38
|
+
Don't take the diagram's word for it — belte prints the exact map at boot
|
|
39
|
+
(`DEBUG=belte`):
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
pages:
|
|
43
|
+
page layout error
|
|
44
|
+
/ / ·
|
|
45
|
+
/posts/[id] / ·
|
|
46
|
+
sockets:
|
|
47
|
+
socket schema browser mcp cli publish
|
|
48
|
+
chat ✓ ✓ ✓ ✓ ✓
|
|
49
|
+
rpcs:
|
|
50
|
+
http schema browser mcp cli
|
|
51
|
+
GET /rpc/getPost ✓ ✓ ✓ ✓
|
|
52
|
+
POST /rpc/createPost ✓ ✓ · ✓
|
|
53
|
+
GET /rpc/listFeed · ✓ · ·
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Scan a column to spot a missing surface, a row to see one declaration's reach. A
|
|
57
|
+
schemaless verb (`listFeed`) reddens its `schema` cell — its machine surfaces are
|
|
58
|
+
gated until a schema lands. Every surface a function reaches is auditable in one
|
|
59
|
+
place; no surface is ever exposed by accident.
|
|
60
|
+
|
|
61
|
+
## Why it's built this way
|
|
62
|
+
|
|
63
|
+
- **Zero runtime dependencies.** `package.json` declares no `dependencies` — only
|
|
64
|
+
optional `peerDependencies` (`svelte`, and `tailwindcss` / `bun-plugin-tailwind`
|
|
65
|
+
for styling). Everything else is Web platform and `Bun.*` (`Bun.serve`,
|
|
66
|
+
`Bun.CookieMap`, `Bun.zstdCompress`, `Bun.YAML`, `Bun.file`).
|
|
67
|
+
- **No magic strings.** The rpc/socket swap is a real tokenizer
|
|
68
|
+
(`findExportCallSite`) that walks source character-by-character, skipping
|
|
69
|
+
strings, templates, comments, regexes, and TypeScript generics — so a `GET`
|
|
70
|
+
inside a docstring or a nested `GET<Map<K, V>>(` is never mistaken for the call.
|
|
71
|
+
- **Safe by default for machines.** A read-only verb (`GET`/`HEAD`) with a schema
|
|
72
|
+
auto-exposes to MCP; a mutating verb (`POST`/`PUT`/`PATCH`/`DELETE`) never does
|
|
73
|
+
unless you pass `clients: { mcp: true }` explicitly — a model can't delete data
|
|
74
|
+
just because the handler carries a schema.
|
|
75
|
+
|
|
76
|
+
## Scope — read this before you adopt
|
|
77
|
+
|
|
78
|
+
- **Bun-only, by design.** `engines.bun >= 1.3.0`. There is no Node fallback; the
|
|
79
|
+
runtime is built directly on `Bun.serve` and `Bun.*` APIs.
|
|
80
|
+
- **Svelte-only web surface.** Pages, layouts, and error pages are Svelte 5
|
|
81
|
+
components; SSR runs through `svelte/server`. There is no other view layer.
|
|
82
|
+
- **Pre-1.0.** The core (rpc, pages, cache, sockets) is the mature center. The
|
|
83
|
+
machine-facing satellites — MCP, the CLI binary, and the desktop bundle — are
|
|
84
|
+
newer and move faster. APIs may still shift.
|
|
85
|
+
|
|
86
|
+
## The mental model
|
|
87
|
+
|
|
88
|
+
Three ideas carry the whole framework.
|
|
89
|
+
|
|
90
|
+
1. **One runtime — dev equals build.** `belte dev` and a compiled binary run the
|
|
91
|
+
same server code over the same registry. There is no separate dev path to
|
|
92
|
+
diverge from production.
|
|
93
|
+
2. **Declare once.** A file under `src/server/rpc/` holds exactly one
|
|
94
|
+
`export const <name> = VERB(fn)`. The filename is the export name, the URL
|
|
95
|
+
(`/rpc/<path>`), the MCP tool name, and the CLI subcommand. The HTTP verb you
|
|
96
|
+
import picks the method.
|
|
97
|
+
3. **The namespace marks the side.** The import path tells you where a name runs.
|
|
98
|
+
|
|
99
|
+
| Namespace | Runs | Public names |
|
|
100
|
+
| --- | --- | --- |
|
|
101
|
+
| `belte/server/*` | server only | `GET` `POST` `PUT` `PATCH` `DELETE` `HEAD`, `socket`, `json` `jsonl` `sse` `error` `redirect`, `request` `cookies` `server` `env` `appDataDir`, `AppModule` |
|
|
102
|
+
| `belte/browser/*` | client only | `page` `navigate` `subscribe` |
|
|
103
|
+
| `belte/shared/*` | isomorphic | `cache` `HttpError` `withJsonSchema` `bundled` `log` |
|
|
104
|
+
| `belte/bundle/*` | desktop launcher | `BundleWindow` `BundleMenu` `BundleMenuItem` `onMenu` |
|
|
105
|
+
| `belte/test/*` | tests | `createTestClient` `clearVerbRegistry` |
|
|
106
|
+
|
|
107
|
+
There is no umbrella `index.ts` and no `.` export. Every public name has its own
|
|
108
|
+
module path (`belte/server/json`, `belte/shared/cache`, …), so importing one name
|
|
109
|
+
never drags a side-effecting sibling into the bundle. `shared/*` names are the
|
|
110
|
+
same callable with the same behaviour on both sides; the bundler swaps only the
|
|
111
|
+
underlying runtime.
|
|
112
|
+
|
|
113
|
+
## One function, every surface
|
|
114
|
+
|
|
115
|
+
A single schema-bearing verb, consumed from all five surfaces.
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
// src/server/rpc/createOrder.ts
|
|
119
|
+
import { POST } from '@belte/belte/server/POST'
|
|
120
|
+
import { json } from '@belte/belte/server/json'
|
|
121
|
+
import { z } from 'zod'
|
|
122
|
+
|
|
123
|
+
export const createOrder = POST(async ({ sku, qty }) => json(await orders.create(sku, qty)), {
|
|
124
|
+
inputSchema: z.object({ sku: z.string(), qty: z.number() }),
|
|
125
|
+
clients: { mcp: true }, // a mutating verb must opt into MCP explicitly
|
|
126
|
+
})
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
```svelte
|
|
130
|
+
<!-- browser: SSR + hydrate -->
|
|
131
|
+
<script lang="ts">
|
|
132
|
+
import { cache } from '@belte/belte/shared/cache'
|
|
133
|
+
import { createOrder } from '$server/rpc/createOrder'
|
|
134
|
+
const result = $derived(await cache(createOrder)({ sku: 'A1', qty: 2 }))
|
|
135
|
+
</script>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
```sh
|
|
139
|
+
# http
|
|
140
|
+
curl -X POST localhost:3000/rpc/createOrder -d '{"sku":"A1","qty":2}'
|
|
141
|
+
|
|
142
|
+
# cli (schema → flags)
|
|
143
|
+
app createOrder --sku A1 --qty 2
|
|
144
|
+
|
|
145
|
+
# mcp — tool "createOrder", dispatched at POST /__belte/mcp
|
|
146
|
+
|
|
147
|
+
# openapi — POST /rpc/createOrder, operationId createOrder, in /openapi.json
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Server
|
|
151
|
+
|
|
152
|
+
### Server / rpc
|
|
153
|
+
|
|
154
|
+
#### Declaring
|
|
155
|
+
|
|
156
|
+
Each file under `src/server/rpc/` exports one verb-bound function. Import a verb
|
|
157
|
+
helper from its own path: `belte/server/GET`, `.../POST`, `.../PUT`, `.../PATCH`,
|
|
158
|
+
`.../DELETE`, `.../HEAD`.
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
type Verb = <Return = unknown, InputSchema = StandardSchemaV1>(
|
|
162
|
+
handler: (args: InferOutput<InputSchema>) => Response | Promise<Response>,
|
|
163
|
+
opts?: {
|
|
164
|
+
inputSchema?: InputSchema // validates args; 422 on failure
|
|
165
|
+
outputSchema?: StandardSchemaV1 // describes the 200 body (OpenAPI + MCP)
|
|
166
|
+
filesSchema?: StandardSchemaV1 // validates multipart File parts
|
|
167
|
+
clients?: Partial<{ browser; mcp; cli }>
|
|
168
|
+
},
|
|
169
|
+
) => RemoteFunction<InferInput<InputSchema>, Return>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
| Option | Effect | Default |
|
|
173
|
+
| --- | --- | --- |
|
|
174
|
+
| `inputSchema` | Validates args (any Standard Schema lib). `Args` infers from it; replies 422 with `{ issues }` on failure. | none |
|
|
175
|
+
| `outputSchema` | Standard Schema for the success body — feeds the OpenAPI 200 response and the MCP tool `outputSchema`. | none |
|
|
176
|
+
| `filesSchema` | Validates the `File` parts of a multipart upload (kept out of the JSON-Schema projection). | none |
|
|
177
|
+
| `clients` | Which surfaces expose the verb: `{ browser, mcp, cli }`. | `browser: true`; `mcp`/`cli` auto-on with a schema (MCP only for read-only verbs) |
|
|
178
|
+
|
|
179
|
+
`Args` is inferred from `inputSchema` or from the handler's parameter type; `Return`
|
|
180
|
+
is inferred from the handler's return via the `TypedResponse<T>` brand on the
|
|
181
|
+
response helpers, so `GET(() => json({ ... }))` types end-to-end with no annotation.
|
|
182
|
+
|
|
183
|
+
**Response helpers** — one per file under `belte/server/*`. All default to
|
|
184
|
+
`Cache-Control: no-store` (intermediary caches shouldn't memoise rpc replies).
|
|
185
|
+
|
|
186
|
+
| Helper | Returns | Notes |
|
|
187
|
+
| --- | --- | --- |
|
|
188
|
+
| `json(data, init?)` | `application/json` | Like `Response.json` plus the no-store default. |
|
|
189
|
+
| `error(status, message?, init?)` | `text/plain` | `message` defaults to the status reason phrase. Body reaches `HttpError.response.text()` verbatim. |
|
|
190
|
+
| `redirect(url, status?, init?)` | 3xx | Accepts relative URLs; defaults to 302. Statuses `301/302/303/307/308`. |
|
|
191
|
+
| `jsonl(iterable, init?)` | `application/jsonl` | One JSON value per line from an `AsyncIterable`. Errors emit a final `{"$error":"…"}` line. |
|
|
192
|
+
| `sse(iterable, init?)` | `text/event-stream` | One `data:` event per frame, 15s keepalive comments. Errors emit an `event: error` frame. |
|
|
193
|
+
|
|
194
|
+
**Request-scoped helpers** — resolve only while an SSR render or rpc handler is in
|
|
195
|
+
flight (they throw at module top level or in `app.ts` `init`):
|
|
196
|
+
|
|
197
|
+
- `request()` → the inbound `Request` (`belte/server/request`).
|
|
198
|
+
- `cookies()` → the live `Bun.CookieMap`; `.set`/`.delete` flush as `Set-Cookie`
|
|
199
|
+
when the handler returns (`belte/server/cookies`).
|
|
200
|
+
- `server()` → the active `Bun.Server` (`belte/server/server`).
|
|
201
|
+
|
|
202
|
+
In-process calls (SSR, MCP, CLI) forward only an allowlist of inbound headers onto
|
|
203
|
+
the synthesized rpc `Request`: `cookie`, `authorization`, `x-forwarded-for`,
|
|
204
|
+
`x-forwarded-proto`, `x-forwarded-host`. Every other header is dropped. Extend the
|
|
205
|
+
allowlist with the `forwardHeaders` export in `src/app.ts` (e.g. `accept-language`,
|
|
206
|
+
`x-tenant-id`).
|
|
207
|
+
|
|
208
|
+
**Multipart uploads** — pass `filesSchema` alongside `inputSchema`. The handler
|
|
209
|
+
receives the validated text fields merged with the validated `File` parts; the
|
|
210
|
+
call site sends a `FormData`. Files stay out of `inputSchema`, so its JSON-Schema
|
|
211
|
+
projection (OpenAPI/MCP/CLI) never has to model a binary.
|
|
212
|
+
|
|
213
|
+
**`withJsonSchema`** — attaches a `toJSONSchema()` projection to a schema whose
|
|
214
|
+
library doesn't expose one (Zod 4 / Effect / Arktype carry their own):
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
import { withJsonSchema } from '@belte/belte/shared/withJsonSchema'
|
|
218
|
+
export const fn = POST(handler, { inputSchema: withJsonSchema(vSchema, (s) => toJsonSchema(s)) })
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
#### Consuming
|
|
222
|
+
|
|
223
|
+
A verb's value is a `RemoteFunction` with the same call signature on both sides.
|
|
224
|
+
|
|
225
|
+
| Form | Resolves to | On non-2xx |
|
|
226
|
+
| --- | --- | --- |
|
|
227
|
+
| `fn(args)` | the Content-Type-decoded body (`Promise<Return>`) | throws `HttpError` |
|
|
228
|
+
| `fn.raw(args)` | the underlying `Response` | no throw — inspect status/headers/body |
|
|
229
|
+
| `fn.stream(args?)` | a `Subscribable<Return>` view of the body (SSE/JSONL frames, or the decoded body once) | surfaced via `subscribe.error` |
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
const post = await getPost({ id }) // decoded body, throws HttpError on 4xx/5xx
|
|
233
|
+
const res = await getPost.raw({ id }) // Response; res.status, res.headers
|
|
234
|
+
const live = orderFeed.stream({ since }) // Subscribable — pass to subscribe()
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**`HttpError`** (`belte/shared/HttpError`) carries `status`, `statusText`, and the
|
|
238
|
+
raw `response` so a call site can render error UI without opting into `.raw`.
|
|
239
|
+
|
|
240
|
+
**OpenAPI** — every verb is described at `/openapi.json` (OpenAPI 3.1) regardless
|
|
241
|
+
of which machine clients it advertises. `GET`/`DELETE`/`HEAD` args become query
|
|
242
|
+
parameters; `POST`/`PUT`/`PATCH` args become a JSON request body (or
|
|
243
|
+
`multipart/form-data` when `filesSchema` is set). `operationId` is the
|
|
244
|
+
folder-prefixed command name.
|
|
245
|
+
|
|
246
|
+
### Server / sockets
|
|
247
|
+
|
|
248
|
+
A bidirectional named broadcast primitive. One file per socket under
|
|
249
|
+
`src/server/sockets/`; import the helper from `belte/server/socket`.
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
type socket = <T>(opts?: {
|
|
253
|
+
history?: number // messages replayed to a new subscriber (default 0)
|
|
254
|
+
ttl?: number // ms; history entries older than this are evicted lazily
|
|
255
|
+
clientPublish?: boolean // allow clients to publish over the wire (default false)
|
|
256
|
+
schema?: StandardSchemaV1 // validates publish payloads; infers T; unlocks mcp/cli
|
|
257
|
+
clients?: Partial<{ browser; mcp; cli }>
|
|
258
|
+
}) => Socket<T>
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
// src/server/sockets/chat.ts
|
|
263
|
+
import { socket } from '@belte/belte/server/socket'
|
|
264
|
+
import { z } from 'zod'
|
|
265
|
+
|
|
266
|
+
export const chat = socket({
|
|
267
|
+
history: 50,
|
|
268
|
+
clientPublish: true,
|
|
269
|
+
schema: z.object({ user: z.string(), text: z.string() }),
|
|
270
|
+
})
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**Publishing** is isomorphic: `chat.publish(msg)` notifies in-process iterators and
|
|
274
|
+
fans out to remote subscribers via Bun's native `server.publish`. With a `schema`,
|
|
275
|
+
publish validates synchronously and throws on a bad payload.
|
|
276
|
+
|
|
277
|
+
**Consuming** — a `Socket<T>` is an `AsyncIterable<T>`:
|
|
278
|
+
|
|
279
|
+
```ts
|
|
280
|
+
for await (const msg of chat) { /* replays history, then tails live */ }
|
|
281
|
+
for await (const msg of chat.tail(10)) { /* last 10, then live */ }
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
`.tail(count)` replays the last `count` items (default `0`, clamped to the configured
|
|
285
|
+
`history`) before tailing. In a Svelte component, layer `subscribe()` on top instead
|
|
286
|
+
of iterating by hand.
|
|
287
|
+
|
|
288
|
+
## Clients
|
|
289
|
+
|
|
290
|
+
### Shared
|
|
291
|
+
|
|
292
|
+
**`cache(fn, options?)`** (`belte/shared/cache`) returns an invoker; calling it
|
|
293
|
+
checks a store and shares the in-flight promise on a hit, or runs `fn` once on a
|
|
294
|
+
miss. `fn` is a verb helper, `fn.raw`, or a plain producer.
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
type cache = (fn, options?: {
|
|
298
|
+
ttl?: number // ms past resolve: omitted = forever, 0 = dedupe only
|
|
299
|
+
scope?: string | string[] // tags for grouped cache.invalidate({ scope })
|
|
300
|
+
global?: boolean // process-level store instead of request-scoped (server)
|
|
301
|
+
invalidate?: { throttle?: number } | { debounce?: number } // coalesce refetches
|
|
302
|
+
}) => (args?) => Promise<Return>
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
// server (SSR) or client
|
|
307
|
+
const post = await cache(getPost)({ id }) // decoded body
|
|
308
|
+
const res = await cache(getPost.raw)({ id }) // raw Response, same cache key
|
|
309
|
+
const rates = await cache(fetchRates)() // plain producer (hoist for dedupe)
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
`cache.invalidate(selector?)`, `cache.pending(selector?)`, and
|
|
313
|
+
`cache.refreshing(selector?)` share one selector grammar: omitted = all, a function
|
|
314
|
+
= that function's calls, `{ scope }` = a tagged group. Keys are auto-derived from
|
|
315
|
+
`method + url + args` (or `producer-ref + args`); arg keys distinguish `Date`,
|
|
316
|
+
`Map`, `Set`, and `bigint` from look-alike values via `canonicalJson` (a `Date`
|
|
317
|
+
never aliases its ISO string; a `Map` never aliases a plain object).
|
|
318
|
+
|
|
319
|
+
**SSR mode is chosen by how you read** — per Svelte's `{#await}` rule:
|
|
320
|
+
|
|
321
|
+
```svelte
|
|
322
|
+
<script>const post = await cache(getPost)({ id })</script> <!-- blocks render → inlined in initial HTML -->
|
|
323
|
+
|
|
324
|
+
{#await cache(getPost)({ id }) then post} <!-- pending → shell flushes, value streams in -->
|
|
325
|
+
{post.title}
|
|
326
|
+
{/await}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
A top-level `await` flips the whole component instance into await-everything mode;
|
|
330
|
+
to mix blocking and streaming reads, isolate each blocking read in its own child
|
|
331
|
+
component. Reactivity is implicit — the invoker registers the surrounding
|
|
332
|
+
`$derived`/`$effect` via `createSubscriber`, so `cache.invalidate` re-runs it.
|
|
333
|
+
|
|
334
|
+
**`bundled()`** (`belte/shared/bundled`) returns whether the code is running inside
|
|
335
|
+
the desktop bundle — `true` in the bundle's webview or its embedded server process,
|
|
336
|
+
`false` in a plain browser tab or a standalone server binary. Same name, same
|
|
337
|
+
meaning on both sides; each side detects it differently (client: the webview's init
|
|
338
|
+
script; server: the launcher's `BELTE_PARENT_PID`). Branch UI or behaviour on it
|
|
339
|
+
without threading a flag through your code.
|
|
340
|
+
|
|
341
|
+
### Browser
|
|
342
|
+
|
|
343
|
+
**Pages** are Svelte 5 components. Routing is file-based under `src/browser/pages/`:
|
|
344
|
+
every leaf lives in its own folder as `page.svelte`, `layout.svelte`, or
|
|
345
|
+
`error.svelte`. Dynamic segments use `[id]` / `[...rest]` folder names.
|
|
346
|
+
|
|
347
|
+
- **Layouts** are nearest-only: the deepest `layout.svelte` ancestor wraps a page.
|
|
348
|
+
- **Error pages** (`error.svelte`) render server-side for a 404 (unknown route) or
|
|
349
|
+
a throw during a page render, nearest-only, with `{ status, message, stack }`
|
|
350
|
+
props. The document ships static (no hydration).
|
|
351
|
+
|
|
352
|
+
**`navigate(href, options?)`** (`belte/browser/navigate`) is the SPA navigation
|
|
353
|
+
entry point.
|
|
354
|
+
|
|
355
|
+
```ts
|
|
356
|
+
type navigate = (href: string, options?: { replace?: boolean; scroll?: boolean }) => Promise<void>
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
A same-pathname change (search/hash only) skips the network round-trip; a
|
|
360
|
+
cross-origin or non-SPA target hard-navigates cleanly.
|
|
361
|
+
|
|
362
|
+
**`page`** (`belte/browser/page`) is reactive route state — a discriminated union
|
|
363
|
+
on `route`, so narrowing `page.route` types `page.params`.
|
|
364
|
+
|
|
365
|
+
| Field | Type | Notes |
|
|
366
|
+
| --- | --- | --- |
|
|
367
|
+
| `page.route` | the matched route key | discriminant |
|
|
368
|
+
| `page.params` | param shape for that route | typed from the generated routes |
|
|
369
|
+
| `page.url` | live `URL` | reassigned on every nav so `$derived` re-runs |
|
|
370
|
+
|
|
371
|
+
**`subscribe(subscribable)`** (`belte/browser/subscribe`) reactively reads a
|
|
372
|
+
streaming source — a `Socket<T>` or `fn.stream(args)`:
|
|
373
|
+
|
|
374
|
+
```svelte
|
|
375
|
+
<script lang="ts">
|
|
376
|
+
import { subscribe } from '@belte/belte/browser/subscribe'
|
|
377
|
+
import { chat } from '$server/sockets/chat'
|
|
378
|
+
const latest = $derived(subscribe(chat)) // T | undefined
|
|
379
|
+
const status = $derived(subscribe.status(chat)) // 'pending' | 'open' | 'done' | 'error'
|
|
380
|
+
const err = $derived(subscribe.error(chat)) // Error | undefined
|
|
381
|
+
</script>
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
It opens the iterator on the first `$derived` read and closes it when the last
|
|
385
|
+
reader stops; many `$derived`s reading the same source share one subscription
|
|
386
|
+
(deduped by name). It's a no-op during SSR — seed initial HTML via `cache()` against
|
|
387
|
+
an HTTP rpc and layer `subscribe()` on top for live updates after hydration.
|
|
388
|
+
|
|
389
|
+
### Mcp
|
|
390
|
+
|
|
391
|
+
The MCP server is fully framework-generated at `/__belte/mcp` (JSON-RPC, protocol
|
|
392
|
+
`2025-06-18`) — there is no module to author.
|
|
393
|
+
|
|
394
|
+
- **Tools** come from every verb with `clients.mcp` (read-only + schema → auto;
|
|
395
|
+
mutating → explicit `clients.mcp`) and from every mcp-exposed socket: a
|
|
396
|
+
`<base>-tail` read tool plus a `<base>-publish` tool when `clientPublish` is set.
|
|
397
|
+
The HTTP verb feeds each tool's annotations (`readOnlyHint`/`destructiveHint`/
|
|
398
|
+
`idempotentHint`). Tool calls run through the same `verb.fetch` seam as the HTTP
|
|
399
|
+
router, inheriting forwarded auth headers.
|
|
400
|
+
- **Resources** are files under `src/mcp/resources/`, served under the
|
|
401
|
+
`belte://resources/<path>` URI namespace (text inline, binary as base64).
|
|
402
|
+
- **Prompts** are `.md` files under `src/mcp/prompts/`. YAML frontmatter carries
|
|
403
|
+
`description` and an `arguments` list; the body interpolates `{{name}}`
|
|
404
|
+
placeholders at `prompts/get`.
|
|
405
|
+
|
|
406
|
+
```md
|
|
407
|
+
---
|
|
408
|
+
description: Summarize a thread
|
|
409
|
+
arguments:
|
|
410
|
+
- name: topic
|
|
411
|
+
description: what to focus on
|
|
412
|
+
required: true
|
|
413
|
+
---
|
|
414
|
+
Summarize the discussion, focusing on {{topic}}.
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Cli
|
|
418
|
+
|
|
419
|
+
`belte cli` builds a thin remote-client binary (no handler code) that ships the
|
|
420
|
+
compiled server beside it. It talks to a running server over HTTP, or boots a local
|
|
421
|
+
one with `<name> /start`.
|
|
422
|
+
|
|
423
|
+
| First arg | Action |
|
|
424
|
+
| --- | --- |
|
|
425
|
+
| `--help` / `-h`, `/help [cmd]` | top-level or per-command help |
|
|
426
|
+
| (none) on a TTY | interactive session, resuming the saved connection |
|
|
427
|
+
| `/connect <url>` | connect to a remote server, open a session |
|
|
428
|
+
| `/start` | boot a local instance, open a session |
|
|
429
|
+
| `/disconnect` | forget the saved connection |
|
|
430
|
+
| `<cmd> [--flags]` | one-shot rpc against the resumed target |
|
|
431
|
+
|
|
432
|
+
Connection target comes from `BELTE_APP_URL` / `BELTE_APP_TOKEN` (precedence: shell
|
|
433
|
+
> data-dir `.env` > binary-dir `.env`). The token sets a `Bearer` auth header, so an
|
|
434
|
+
authenticated server's CLI (and its authenticated binary downloads via
|
|
435
|
+
`/__belte/cli`) work the same.
|
|
436
|
+
|
|
437
|
+
Each rpc becomes a subcommand; the input schema derives the flags:
|
|
438
|
+
|
|
439
|
+
| Schema type | Flag form |
|
|
440
|
+
| --- | --- |
|
|
441
|
+
| `boolean` | `--name` / `--no-name` |
|
|
442
|
+
| `number` / `integer` | `--name <n>` (coerced) |
|
|
443
|
+
| `array` | repeated `--name <v>` |
|
|
444
|
+
| anything else | `--name <value>` |
|
|
445
|
+
| complex / nested | `--json '<args>'`, or pipe a JSON object on stdin |
|
|
446
|
+
|
|
447
|
+
Optional `src/cli/banner.txt` prints above top-level help; `src/cli/footer.txt`
|
|
448
|
+
below it.
|
|
449
|
+
|
|
450
|
+
### Bundle
|
|
451
|
+
|
|
452
|
+
`belte bundle` assembles a movable, self-contained desktop app for the host
|
|
453
|
+
platform — the server binary, the launcher, and the webview lib together (a `.app`
|
|
454
|
+
on macOS, a flat directory elsewhere). On macOS the `.app` is **ad-hoc
|
|
455
|
+
code-signed** (`codesign --sign -`, no certificate) so it launches on other Macs; a
|
|
456
|
+
quarantined copy may still need `xattr -cr <app>` once. Full distribution still
|
|
457
|
+
needs a Developer ID signature and notarization.
|
|
458
|
+
|
|
459
|
+
The app boots into a connect screen that either starts the embedded server or
|
|
460
|
+
connects to a remote one. Customize via files under `src/bundle/`:
|
|
461
|
+
|
|
462
|
+
- **`window.ts`** default-exports a `BundleWindow` (`belte/bundle/BundleWindow`):
|
|
463
|
+
`title`, `width`, `height`, custom `menu` (`BundleMenu` / `BundleMenuItem`), and a
|
|
464
|
+
`config` schema override for the first-run setup form (defaults to the
|
|
465
|
+
`src/server/config.ts` env schema).
|
|
466
|
+
- **`disconnected.svelte`** overrides the connect screen.
|
|
467
|
+
- **`onMenu`** (`belte/bundle/onMenu`) subscribes to custom menu clicks, returning
|
|
468
|
+
an unsubscribe for `$effect`:
|
|
469
|
+
|
|
470
|
+
```ts
|
|
471
|
+
$effect(() => onMenu('reload', () => location.reload()))
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
- **`icon.png`** is the app icon.
|
|
475
|
+
|
|
476
|
+
## Some details
|
|
477
|
+
|
|
478
|
+
**Config / env** — optional `src/server/config.ts` calls `env(schema)`
|
|
479
|
+
(`belte/server/env`) to validate `Bun.env` against a Standard Schema at boot; a
|
|
480
|
+
missing/malformed var fails the boot with every issue listed. belte eager-imports
|
|
481
|
+
the file (no import from your code); import `config` from `$server/config`
|
|
482
|
+
server-side. `appDataDir()` (`belte/server/appDataDir`) returns the bundled app's
|
|
483
|
+
per-user data directory.
|
|
484
|
+
|
|
485
|
+
**App hooks** — optional exports from `src/app.ts` (`belte/server/AppModule` types
|
|
486
|
+
them):
|
|
487
|
+
|
|
488
|
+
| Export | Runs |
|
|
489
|
+
| --- | --- |
|
|
490
|
+
| `forwardHeaders` | extra inbound header names to forward onto in-process rpc Requests |
|
|
491
|
+
| `init({ server })` | once after `Bun.serve` is up; return a cleanup for SIGINT/SIGTERM |
|
|
492
|
+
| `handle(request, next)` | middleware wrapping the request pipeline |
|
|
493
|
+
| `handleError(error, request)` | custom 500 fallback |
|
|
494
|
+
|
|
495
|
+
**Project layout**
|
|
496
|
+
|
|
497
|
+
```text
|
|
498
|
+
src/
|
|
499
|
+
app.ts optional hooks
|
|
500
|
+
server/
|
|
501
|
+
config.ts optional env(schema), eager-imported at boot
|
|
502
|
+
rpc/<name>.ts one verb-bound function per file → /rpc/<path>
|
|
503
|
+
sockets/<name>.ts one socket per file
|
|
504
|
+
lib/ userland (declare your own aliases)
|
|
505
|
+
browser/
|
|
506
|
+
app.html optional shell override
|
|
507
|
+
pages/**/page.svelte routes (layout.svelte, error.svelte nearest-only)
|
|
508
|
+
public/ served at the site root
|
|
509
|
+
mcp/
|
|
510
|
+
prompts/*.md MCP prompts
|
|
511
|
+
resources/** MCP resources
|
|
512
|
+
bundle/
|
|
513
|
+
window.ts BundleWindow config
|
|
514
|
+
disconnected.svelte connect-screen override
|
|
515
|
+
icon.png
|
|
516
|
+
cli/
|
|
517
|
+
banner.txt footer.txt CLI help chrome
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
Aliases `$server`, `$browser`, `$shared`, `$mcp`, `$cli` resolve to the top-level
|
|
521
|
+
project directories; `lib/` is userland.
|
|
522
|
+
|
|
523
|
+
**CLI commands**
|
|
524
|
+
|
|
525
|
+
| Command | Does |
|
|
526
|
+
| --- | --- |
|
|
527
|
+
| `bunx belte scaffold <name>` | scaffold a new project |
|
|
528
|
+
| `belte dev` | build the client + run the server with browser live-reload |
|
|
529
|
+
| `belte build` | build the client into `dist/_app/` |
|
|
530
|
+
| `belte start` | run the production server against `dist/` |
|
|
531
|
+
| `belte run <file> [args]` | run a script under the belte preload (same runtime as the server) |
|
|
532
|
+
| `belte compile [--target] [--out]` | build a standalone server executable |
|
|
533
|
+
| `belte cli [--target] [--out] [--platforms a,b,c]` | build the thin CLI binary (ships the server beside it) |
|
|
534
|
+
| `belte bundle` | build a movable, self-contained desktop app (macOS `.app` ad-hoc signed) |
|
|
535
|
+
|
|
536
|
+
**Bundling targets**
|
|
537
|
+
|
|
538
|
+
| Target | Output | Surface |
|
|
539
|
+
| --- | --- | --- |
|
|
540
|
+
| `belte build` | `dist/_app/` client assets | web (with `belte start`) |
|
|
541
|
+
| `belte compile` | one server executable | self-hosted HTTP |
|
|
542
|
+
| `belte cli` | thin client binary + sibling server | CLI / scripting |
|
|
543
|
+
| `belte bundle` | movable app (`.app` on macOS) | desktop |
|
|
544
|
+
|
|
545
|
+
**`public/` files** under `src/browser/public/` are served at the site root,
|
|
546
|
+
bypassing the request scope and middleware.
|
|
547
|
+
|
|
548
|
+
**Logging** — `log` (`belte/shared/log`) wraps `console.*` with a `[belte]` prefix
|
|
549
|
+
and per-method/status coloring. `DEBUG` follows the `debug` convention; `DEBUG=belte`
|
|
550
|
+
turns on request logging and prints the boot surface map.
|
|
551
|
+
|
|
552
|
+
**Environment variables**
|
|
553
|
+
|
|
554
|
+
| Var | Effect |
|
|
555
|
+
| --- | --- |
|
|
556
|
+
| `PORT` | bind this exact port; unset scans upward from 3000 |
|
|
557
|
+
| `BELTE_IDLE_TIMEOUT` | Bun per-connection idle timeout (seconds; default 10) |
|
|
558
|
+
| `DEBUG` | `belte` enables request logs + the surface map |
|
|
559
|
+
| `BELTE_APP_URL` / `BELTE_APP_TOKEN` | CLI binary's default server + bearer token |
|