@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.
Files changed (326) hide show
  1. package/CHANGELOG.md +313 -0
  2. package/LICENSE +21 -0
  3. package/README.md +559 -0
  4. package/bin/belte.ts +183 -0
  5. package/package.json +110 -0
  6. package/src/App.svelte +31 -0
  7. package/src/appEntry.ts +151 -0
  8. package/src/assets/app.html +14 -0
  9. package/src/belteResolverPlugin.ts +858 -0
  10. package/src/build.ts +147 -0
  11. package/src/buildCli.ts +129 -0
  12. package/src/buildDisconnected.ts +122 -0
  13. package/src/bundleApp.ts +149 -0
  14. package/src/bundleDisconnectedEntry.ts +17 -0
  15. package/src/cliEntry.ts +25 -0
  16. package/src/clientBuildPlugins.ts +41 -0
  17. package/src/clientEntry.ts +7 -0
  18. package/src/compile.ts +64 -0
  19. package/src/controlServerWorker.ts +422 -0
  20. package/src/dedupeSveltePlugin.ts +66 -0
  21. package/src/devEntry.ts +169 -0
  22. package/src/discoveryEntry.ts +81 -0
  23. package/src/lib/browser/applyStreamedResolution.ts +33 -0
  24. package/src/lib/browser/cacheEntryFromSnapshot.ts +48 -0
  25. package/src/lib/browser/flushUnresolvedPlaceholders.ts +16 -0
  26. package/src/lib/browser/installStreamingPlaceholders.ts +32 -0
  27. package/src/lib/browser/openResolveStream.ts +42 -0
  28. package/src/lib/browser/page.svelte.ts +258 -0
  29. package/src/lib/browser/pageStreamController.ts +17 -0
  30. package/src/lib/browser/refetchPlaceholder.ts +12 -0
  31. package/src/lib/browser/remoteProxy.ts +37 -0
  32. package/src/lib/browser/socketChannel.ts +192 -0
  33. package/src/lib/browser/socketProxy.ts +57 -0
  34. package/src/lib/browser/startClient.ts +153 -0
  35. package/src/lib/browser/subscribe.ts +131 -0
  36. package/src/lib/browser/types/Errors.ts +9 -0
  37. package/src/lib/browser/types/Layouts.ts +7 -0
  38. package/src/lib/browser/types/Pages.ts +7 -0
  39. package/src/lib/browser/types/StreamingDeferred.ts +9 -0
  40. package/src/lib/bundle/BundleMenu.ts +11 -0
  41. package/src/lib/bundle/BundleMenuItem.ts +24 -0
  42. package/src/lib/bundle/BundleWindow.ts +36 -0
  43. package/src/lib/bundle/WEBVIEW_BUILD_REVISION.ts +9 -0
  44. package/src/lib/bundle/WEBVIEW_VERSION.ts +7 -0
  45. package/src/lib/bundle/bindConnectedFlag.ts +29 -0
  46. package/src/lib/bundle/bindRequestNavigate.ts +31 -0
  47. package/src/lib/bundle/buildWebviewLib.ts +111 -0
  48. package/src/lib/bundle/disconnected.css +9 -0
  49. package/src/lib/bundle/disconnected.svelte +386 -0
  50. package/src/lib/bundle/ensureWebviewLib.ts +20 -0
  51. package/src/lib/bundle/exitWithParent.ts +28 -0
  52. package/src/lib/bundle/infoPlist.ts +46 -0
  53. package/src/lib/bundle/installDownloads.ts +24 -0
  54. package/src/lib/bundle/installMacMenu.ts +39 -0
  55. package/src/lib/bundle/listenLocalControlServer.ts +19 -0
  56. package/src/lib/bundle/native/belteMenu.mm +422 -0
  57. package/src/lib/bundle/native/webview.h +4557 -0
  58. package/src/lib/bundle/onMenu.ts +41 -0
  59. package/src/lib/bundle/openWebview.ts +104 -0
  60. package/src/lib/bundle/pngToIcns.ts +47 -0
  61. package/src/lib/bundle/probeBelteServer.ts +34 -0
  62. package/src/lib/bundle/resolveServerBinary.ts +12 -0
  63. package/src/lib/bundle/resolveWebviewLib.ts +53 -0
  64. package/src/lib/bundle/serverBinaryFilename.ts +8 -0
  65. package/src/lib/bundle/signMacApp.ts +35 -0
  66. package/src/lib/bundle/spawnEmbeddedServer.ts +65 -0
  67. package/src/lib/bundle/stableLocalPort.ts +19 -0
  68. package/src/lib/bundle/waitForServer.ts +23 -0
  69. package/src/lib/bundle/webviewCachePath.ts +23 -0
  70. package/src/lib/bundle/webviewLibName.ts +11 -0
  71. package/src/lib/cli/connectToServer.ts +23 -0
  72. package/src/lib/cli/createClient.ts +170 -0
  73. package/src/lib/cli/dispatchCommand.ts +71 -0
  74. package/src/lib/cli/loadEnvFromBinaryDir.ts +16 -0
  75. package/src/lib/cli/parseArgvForRpc.ts +97 -0
  76. package/src/lib/cli/printHelp.ts +119 -0
  77. package/src/lib/cli/printSessionHelp.ts +27 -0
  78. package/src/lib/cli/printSessionStatus.ts +21 -0
  79. package/src/lib/cli/printTrimmed.ts +8 -0
  80. package/src/lib/cli/printValue.ts +10 -0
  81. package/src/lib/cli/resolveCliTarget.ts +48 -0
  82. package/src/lib/cli/runCli.ts +139 -0
  83. package/src/lib/cli/runSession.ts +105 -0
  84. package/src/lib/cli/startLocalInstance.ts +14 -0
  85. package/src/lib/cli/tokenizeLine.ts +51 -0
  86. package/src/lib/cli/types/CliManifest.ts +9 -0
  87. package/src/lib/cli/types/CliManifestEntry.ts +17 -0
  88. package/src/lib/cli/types/CliTarget.ts +13 -0
  89. package/src/lib/mcp/annotationsForMethod.ts +29 -0
  90. package/src/lib/mcp/createMcpResourceServer.ts +101 -0
  91. package/src/lib/mcp/createMcpServer.ts +42 -0
  92. package/src/lib/mcp/dispatchMcpRequest.ts +146 -0
  93. package/src/lib/mcp/mcpResourceServerSlot.ts +18 -0
  94. package/src/lib/mcp/mcpSurface.ts +265 -0
  95. package/src/lib/mcp/toolResultFromResponse.ts +66 -0
  96. package/src/lib/mcp/types/JsonRpcRequest.ts +12 -0
  97. package/src/lib/mcp/types/JsonRpcResponse.ts +20 -0
  98. package/src/lib/mcp/types/McpResourceContents.ts +10 -0
  99. package/src/lib/mcp/types/McpResourceDescriptor.ts +6 -0
  100. package/src/lib/mcp/types/McpResourceServer.ts +12 -0
  101. package/src/lib/mcp/types/McpServer.ts +9 -0
  102. package/src/lib/mcp/types/McpServerOptions.ts +16 -0
  103. package/src/lib/server/AppModule.ts +33 -0
  104. package/src/lib/server/DELETE.ts +9 -0
  105. package/src/lib/server/GET.ts +9 -0
  106. package/src/lib/server/HEAD.ts +9 -0
  107. package/src/lib/server/PATCH.ts +9 -0
  108. package/src/lib/server/POST.ts +9 -0
  109. package/src/lib/server/PUT.ts +9 -0
  110. package/src/lib/server/agent.ts +76 -0
  111. package/src/lib/server/appDataDir.ts +15 -0
  112. package/src/lib/server/cli/buildEnvContent.ts +19 -0
  113. package/src/lib/server/cli/createTarGz.ts +76 -0
  114. package/src/lib/server/cli/handleCliDownload.ts +153 -0
  115. package/src/lib/server/cli/handleCliInstall.ts +37 -0
  116. package/src/lib/server/cli/installScript.ts +29 -0
  117. package/src/lib/server/cli/maxSourceMtime.ts +26 -0
  118. package/src/lib/server/cookies.ts +29 -0
  119. package/src/lib/server/env.ts +50 -0
  120. package/src/lib/server/error.ts +70 -0
  121. package/src/lib/server/json.ts +28 -0
  122. package/src/lib/server/jsonl.ts +46 -0
  123. package/src/lib/server/prompts/definePrompt.ts +20 -0
  124. package/src/lib/server/prompts/promptRegistry.ts +9 -0
  125. package/src/lib/server/prompts/registerPrompt.ts +6 -0
  126. package/src/lib/server/prompts/renderPromptTemplate.ts +16 -0
  127. package/src/lib/server/prompts/types/Prompt.ts +13 -0
  128. package/src/lib/server/prompts/types/PromptOptions.ts +12 -0
  129. package/src/lib/server/prompts/types/PromptRegistryEntry.ts +13 -0
  130. package/src/lib/server/prompts/types/PromptRoutes.ts +10 -0
  131. package/src/lib/server/redirect.ts +42 -0
  132. package/src/lib/server/request.ts +18 -0
  133. package/src/lib/server/rpc/defineVerb.ts +133 -0
  134. package/src/lib/server/rpc/dispatchVerbInProcess.ts +46 -0
  135. package/src/lib/server/rpc/findVerbByCommandName.ts +18 -0
  136. package/src/lib/server/rpc/parseArgs.ts +95 -0
  137. package/src/lib/server/rpc/registerVerb.ts +6 -0
  138. package/src/lib/server/rpc/types/RemoteHandler.ts +27 -0
  139. package/src/lib/server/rpc/types/RemoteRoutes.ts +13 -0
  140. package/src/lib/server/rpc/types/TypedResponse.ts +18 -0
  141. package/src/lib/server/rpc/types/VerbHelper.ts +68 -0
  142. package/src/lib/server/rpc/types/VerbRegistryEntry.ts +29 -0
  143. package/src/lib/server/rpc/unprocessed.ts +14 -0
  144. package/src/lib/server/rpc/verbRegistry.ts +11 -0
  145. package/src/lib/server/runtime/DEFAULT_PORT.ts +6 -0
  146. package/src/lib/server/runtime/DEV_REBUILD_MESSAGE.ts +4 -0
  147. package/src/lib/server/runtime/DEV_RELOAD_CLIENT_SCRIPT.ts +29 -0
  148. package/src/lib/server/runtime/acceptsZstd.ts +8 -0
  149. package/src/lib/server/runtime/buildOpenApiSpec.ts +106 -0
  150. package/src/lib/server/runtime/cacheControlForAsset.ts +22 -0
  151. package/src/lib/server/runtime/containsTraversal.ts +37 -0
  152. package/src/lib/server/runtime/createAssetHeaderCache.ts +35 -0
  153. package/src/lib/server/runtime/createPublicAssetServer.ts +63 -0
  154. package/src/lib/server/runtime/createRouteDispatcher.ts +100 -0
  155. package/src/lib/server/runtime/createServer.ts +692 -0
  156. package/src/lib/server/runtime/devReloadResponse.ts +35 -0
  157. package/src/lib/server/runtime/disableIdleTimeoutForStream.ts +27 -0
  158. package/src/lib/server/runtime/envSchemaStore.ts +15 -0
  159. package/src/lib/server/runtime/findOpenPort.ts +35 -0
  160. package/src/lib/server/runtime/getActiveServer.ts +6 -0
  161. package/src/lib/server/runtime/globToPathSet.ts +29 -0
  162. package/src/lib/server/runtime/inProcessServer.ts +20 -0
  163. package/src/lib/server/runtime/internalErrorResponse.ts +25 -0
  164. package/src/lib/server/runtime/isCrossOriginUpgrade.ts +19 -0
  165. package/src/lib/server/runtime/listenOnOpenPort.ts +36 -0
  166. package/src/lib/server/runtime/logExposedSurfaces.ts +162 -0
  167. package/src/lib/server/runtime/mimeForExtension.ts +20 -0
  168. package/src/lib/server/runtime/parseIdleTimeout.ts +10 -0
  169. package/src/lib/server/runtime/parsePort.ts +11 -0
  170. package/src/lib/server/runtime/registryManifests.ts +66 -0
  171. package/src/lib/server/runtime/requestContext.ts +5 -0
  172. package/src/lib/server/runtime/resolveStreamResponse.ts +29 -0
  173. package/src/lib/server/runtime/runWithRequestScope.ts +57 -0
  174. package/src/lib/server/runtime/safeJsonForScript.ts +17 -0
  175. package/src/lib/server/runtime/serializeCacheSnapshot.ts +45 -0
  176. package/src/lib/server/runtime/serverSlot.ts +13 -0
  177. package/src/lib/server/runtime/setActiveServer.ts +6 -0
  178. package/src/lib/server/runtime/snapshotEntryFromCache.ts +81 -0
  179. package/src/lib/server/runtime/streamCacheResolutions.ts +37 -0
  180. package/src/lib/server/runtime/streamFromIterator.ts +86 -0
  181. package/src/lib/server/runtime/streamStash.ts +64 -0
  182. package/src/lib/server/runtime/types/Assets.ts +1 -0
  183. package/src/lib/server/runtime/types/RequestStore.ts +27 -0
  184. package/src/lib/server/runtime/withResponseDefaults.ts +24 -0
  185. package/src/lib/server/server.ts +32 -0
  186. package/src/lib/server/socket.ts +31 -0
  187. package/src/lib/server/sockets/createSocketDispatcher.ts +311 -0
  188. package/src/lib/server/sockets/defineSocket.ts +167 -0
  189. package/src/lib/server/sockets/lookupSocket.ts +6 -0
  190. package/src/lib/server/sockets/recentHistory.ts +11 -0
  191. package/src/lib/server/sockets/registerSocket.ts +6 -0
  192. package/src/lib/server/sockets/socketOperations.ts +35 -0
  193. package/src/lib/server/sockets/socketRegistry.ts +9 -0
  194. package/src/lib/server/sockets/types/Socket.ts +21 -0
  195. package/src/lib/server/sockets/types/SocketClientFrame.ts +18 -0
  196. package/src/lib/server/sockets/types/SocketOperation.ts +22 -0
  197. package/src/lib/server/sockets/types/SocketOptions.ts +22 -0
  198. package/src/lib/server/sockets/types/SocketRegistryEntry.ts +17 -0
  199. package/src/lib/server/sockets/types/SocketRoutes.ts +10 -0
  200. package/src/lib/server/sockets/types/SocketServerFrame.ts +15 -0
  201. package/src/lib/server/sse.ts +53 -0
  202. package/src/lib/shared/BELTE_PACKAGE_NAME.ts +7 -0
  203. package/src/lib/shared/CACHE_CONTROL_VALUES.ts +16 -0
  204. package/src/lib/shared/HttpError.ts +19 -0
  205. package/src/lib/shared/RESOLVE_STREAM_PATH.ts +7 -0
  206. package/src/lib/shared/STREAMING_CONTENT_TYPES.ts +11 -0
  207. package/src/lib/shared/activeCacheStore.ts +20 -0
  208. package/src/lib/shared/appDataDir.ts +34 -0
  209. package/src/lib/shared/belteImportName.ts +44 -0
  210. package/src/lib/shared/browserClientFlags.ts +10 -0
  211. package/src/lib/shared/buildRpcRequest.ts +70 -0
  212. package/src/lib/shared/bundleLayout.ts +36 -0
  213. package/src/lib/shared/bundled.ts +34 -0
  214. package/src/lib/shared/cache.ts +559 -0
  215. package/src/lib/shared/cacheStoreSlot.ts +16 -0
  216. package/src/lib/shared/canonicalJson.ts +63 -0
  217. package/src/lib/shared/carriesBodyArgs.ts +13 -0
  218. package/src/lib/shared/clearLastConnection.ts +7 -0
  219. package/src/lib/shared/commandNameForUrl.ts +17 -0
  220. package/src/lib/shared/createCacheStore.ts +75 -0
  221. package/src/lib/shared/createPushIterator.ts +93 -0
  222. package/src/lib/shared/createRemoteFunction.ts +99 -0
  223. package/src/lib/shared/decodeResponse.ts +47 -0
  224. package/src/lib/shared/detectTarget.ts +27 -0
  225. package/src/lib/shared/exeSuffix.ts +9 -0
  226. package/src/lib/shared/exitOnBuildFailure.ts +17 -0
  227. package/src/lib/shared/extraForwardHeaders.ts +16 -0
  228. package/src/lib/shared/fileStem.ts +9 -0
  229. package/src/lib/shared/findExportCallSite.ts +479 -0
  230. package/src/lib/shared/forwardHeaders.ts +41 -0
  231. package/src/lib/shared/getRemoteMeta.ts +5 -0
  232. package/src/lib/shared/globalCacheStore.ts +15 -0
  233. package/src/lib/shared/globalCacheStoreSlot.ts +14 -0
  234. package/src/lib/shared/importNamesToStrip.ts +13 -0
  235. package/src/lib/shared/invalidateEvent.ts +11 -0
  236. package/src/lib/shared/isCompileTarget.ts +15 -0
  237. package/src/lib/shared/isDebugEnabled.ts +23 -0
  238. package/src/lib/shared/isModuleNotFound.ts +16 -0
  239. package/src/lib/shared/isReadOnlyMethod.ts +14 -0
  240. package/src/lib/shared/isStreamingResponse.ts +11 -0
  241. package/src/lib/shared/jsonSchemaForPromptArguments.ts +29 -0
  242. package/src/lib/shared/jsonSchemaForSchema.ts +32 -0
  243. package/src/lib/shared/jsonlErrorFrame.ts +24 -0
  244. package/src/lib/shared/keyForRemoteCall.ts +29 -0
  245. package/src/lib/shared/lastConnectionPath.ts +7 -0
  246. package/src/lib/shared/loadEnvFile.ts +17 -0
  247. package/src/lib/shared/loadEnvFromDataDir.ts +15 -0
  248. package/src/lib/shared/loadSvelteConfig.ts +18 -0
  249. package/src/lib/shared/log.ts +104 -0
  250. package/src/lib/shared/manifestModule.ts +39 -0
  251. package/src/lib/shared/memoizeByKey.ts +24 -0
  252. package/src/lib/shared/nearestLayoutPrefix.ts +36 -0
  253. package/src/lib/shared/normalizeTarget.ts +10 -0
  254. package/src/lib/shared/pageUrlForFile.ts +14 -0
  255. package/src/lib/shared/parseBoundedEnvInt.ts +20 -0
  256. package/src/lib/shared/parseEnv.ts +30 -0
  257. package/src/lib/shared/parsePromptMarkdown.ts +34 -0
  258. package/src/lib/shared/parseRouteSegments.ts +22 -0
  259. package/src/lib/shared/prepareRpcModule.ts +59 -0
  260. package/src/lib/shared/prepareSocketModule.ts +49 -0
  261. package/src/lib/shared/programNameForPackage.ts +14 -0
  262. package/src/lib/shared/promptNameForFile.ts +10 -0
  263. package/src/lib/shared/queryStringFromArgs.ts +27 -0
  264. package/src/lib/shared/readEnvFile.ts +15 -0
  265. package/src/lib/shared/readLastConnection.ts +18 -0
  266. package/src/lib/shared/readPackageJson.ts +9 -0
  267. package/src/lib/shared/recordRemoteMeta.ts +5 -0
  268. package/src/lib/shared/remoteMetaStore.ts +16 -0
  269. package/src/lib/shared/resolveClientFlags.ts +20 -0
  270. package/src/lib/shared/responseErrorText.ts +9 -0
  271. package/src/lib/shared/rpcUrlForFile.ts +19 -0
  272. package/src/lib/shared/runningAsStandaloneBinary.ts +13 -0
  273. package/src/lib/shared/serializeEnv.ts +18 -0
  274. package/src/lib/shared/setCacheStoreResolver.ts +6 -0
  275. package/src/lib/shared/setGlobalCacheStoreResolver.ts +6 -0
  276. package/src/lib/shared/socketNameForFile.ts +11 -0
  277. package/src/lib/shared/sseErrorFrame.ts +29 -0
  278. package/src/lib/shared/streamResponse.ts +169 -0
  279. package/src/lib/shared/stripImport.ts +27 -0
  280. package/src/lib/shared/subscribableFromResponse.ts +51 -0
  281. package/src/lib/shared/toBunRoutePattern.ts +28 -0
  282. package/src/lib/shared/types/CacheEntry.ts +63 -0
  283. package/src/lib/shared/types/CacheInvalidation.ts +9 -0
  284. package/src/lib/shared/types/CacheOptions.ts +33 -0
  285. package/src/lib/shared/types/CacheSnapshot.ts +16 -0
  286. package/src/lib/shared/types/CacheSnapshotEntry.ts +15 -0
  287. package/src/lib/shared/types/CacheStore.ts +32 -0
  288. package/src/lib/shared/types/ClientFlags.ts +11 -0
  289. package/src/lib/shared/types/CompileTarget.ts +6 -0
  290. package/src/lib/shared/types/HttpVerb.ts +1 -0
  291. package/src/lib/shared/types/LastConnection.ts +9 -0
  292. package/src/lib/shared/types/PromptArgument.ts +12 -0
  293. package/src/lib/shared/types/RawRemoteFunction.ts +13 -0
  294. package/src/lib/shared/types/RemoteFunction.ts +42 -0
  295. package/src/lib/shared/types/StandardSchemaV1.ts +57 -0
  296. package/src/lib/shared/types/StreamedResolution.ts +10 -0
  297. package/src/lib/shared/types/StreamingPlaceholder.ts +13 -0
  298. package/src/lib/shared/types/Subscribable.ts +15 -0
  299. package/src/lib/shared/types/SvelteConfig.ts +5 -0
  300. package/src/lib/shared/withJsonSchema.ts +20 -0
  301. package/src/lib/shared/writeLastConnection.ts +13 -0
  302. package/src/lib/shared/writeRoutesDts.ts +67 -0
  303. package/src/lib/test/clearVerbRegistry.ts +11 -0
  304. package/src/lib/test/createTestClient.ts +78 -0
  305. package/src/preload.ts +20 -0
  306. package/src/scaffold.ts +92 -0
  307. package/src/serverBuildPlugins.ts +25 -0
  308. package/src/serverEntry.ts +94 -0
  309. package/src/sveltePlugin.ts +58 -0
  310. package/src/tailwindStylePreprocessor.ts +62 -0
  311. package/template/bunfig.toml +4 -0
  312. package/template/package.json +19 -0
  313. package/template/src/app.ts +23 -0
  314. package/template/src/browser/app.css +21 -0
  315. package/template/src/browser/app.html +24 -0
  316. package/template/src/browser/pages/about/page.svelte +5 -0
  317. package/template/src/browser/pages/layout.svelte +26 -0
  318. package/template/src/browser/pages/page.svelte +20 -0
  319. package/template/src/bundle/icon.png +0 -0
  320. package/template/src/cli/banner.txt +3 -0
  321. package/template/src/cli/footer.txt +1 -0
  322. package/template/src/server/config.ts +17 -0
  323. package/template/src/server/rpc/getHello.ts +35 -0
  324. package/template/svelte.config.js +12 -0
  325. package/template/tsconfig.json +18 -0
  326. package/tsconfig.app.json +16 -0
@@ -0,0 +1,170 @@
1
+ import { dispatchVerbInProcess } from '../server/rpc/dispatchVerbInProcess.ts'
2
+ import { findVerbByCommandName } from '../server/rpc/findVerbByCommandName.ts'
3
+ import { buildRpcRequest } from '../shared/buildRpcRequest.ts'
4
+ import { decodeResponse } from '../shared/decodeResponse.ts'
5
+ import type { HttpVerb } from '../shared/types/HttpVerb.ts'
6
+ import type { CliManifest } from './types/CliManifest.ts'
7
+
8
+ /*
9
+ Each property of the client is a callable: invoking it decodes the body
10
+ (plain call), while `.raw(args)` returns the underlying Response without
11
+ decoding or throwing on non-2xx — the escape hatch the CLI uses to sniff
12
+ the Content-Type and stream sse/jsonl bodies frame-by-frame instead of
13
+ buffering through decodeResponse.
14
+ */
15
+ type ClientInvoker = ((args?: unknown) => Promise<unknown>) & {
16
+ raw: (args?: unknown) => Promise<Response>
17
+ }
18
+
19
+ type AnyApi = Record<string, ClientInvoker>
20
+
21
+ /*
22
+ A command resolved to its raw-dispatch closure. `method`/`url` label the
23
+ command for error messages; `send` issues one call — over the network in
24
+ remote mode, through dispatchVerbInProcess in in-process mode.
25
+ */
26
+ type ResolvedSend = {
27
+ method: HttpVerb
28
+ url: string
29
+ send: (args: unknown) => Promise<Response>
30
+ }
31
+
32
+ /*
33
+ Builds a typed proxy over the project's RPCs for use in scripts, tests,
34
+ server-to-server calls, and the standalone CLI binary. Modes are
35
+ decided at construction:
36
+
37
+ - With `url`: remote-mode. Each property access becomes an HTTP call
38
+ against `<url>/<manifest[name].url>` using the manifest's method.
39
+ Auth header is set from `token` when provided.
40
+ - Without `url`: in-process mode. Each property access looks up the
41
+ verb in the registry (populated by importing the project's rpc
42
+ modules) and calls `verb.fetch(synthesizedRequest)` — same code
43
+ path the HTTP router uses, no network hop.
44
+
45
+ The `manifest` is the bundler-emitted CLI manifest baked into the thin
46
+ binary. In in-process mode it's optional (registry is the source of
47
+ truth); in remote mode it supplies the method + url per command without
48
+ needing the rpc modules loaded. The mode is chosen solely by whether
49
+ `url` is set — the shipped CLI binary (see runCli) always passes `url`,
50
+ so it runs remote-only; in-process mode is for same-project scripts and
51
+ tests that import this directly without a `url`.
52
+ */
53
+ export function createClient<Api extends AnyApi = AnyApi>(opts?: {
54
+ url?: string
55
+ token?: string
56
+ manifest?: CliManifest
57
+ }): Api {
58
+ const url = opts?.url
59
+ const token = opts?.token
60
+ const manifest = opts?.manifest
61
+
62
+ // Auth + content-negotiation headers both dispatch modes attach.
63
+ function requestHeaders(accept?: string): Headers {
64
+ const headers = new Headers()
65
+ if (token) {
66
+ headers.set('authorization', `Bearer ${token}`)
67
+ }
68
+ if (accept) {
69
+ headers.set('accept', accept)
70
+ }
71
+ return headers
72
+ }
73
+
74
+ /*
75
+ Resolves a command name to its dispatch closure, or undefined when the
76
+ name is unknown in the active mode. Remote mode (url set) resolves
77
+ method + url from the baked manifest — registry fallback for same-project
78
+ callers — and sends the synthesized Request over the network. In-process
79
+ mode resolves the verb from the registry and routes through
80
+ dispatchVerbInProcess, the same synthesize-and-fetch the MCP dispatcher
81
+ uses, so the two consumer surfaces can't drift on how a verb is invoked.
82
+ */
83
+ function resolve(name: string): ResolvedSend | undefined {
84
+ if (url) {
85
+ const command = manifest?.[name] ?? registryCommand(name)
86
+ if (!command) {
87
+ return undefined
88
+ }
89
+ return {
90
+ method: command.method,
91
+ url: command.url,
92
+ send: (args) =>
93
+ fetch(
94
+ buildRpcRequest({
95
+ method: command.method,
96
+ url: command.url,
97
+ args,
98
+ baseUrl: url,
99
+ headers: requestHeaders(command.accept),
100
+ }),
101
+ ),
102
+ }
103
+ }
104
+ const entry = findVerbByCommandName(name)
105
+ if (!entry) {
106
+ return undefined
107
+ }
108
+ return {
109
+ method: entry.remote.method,
110
+ url: entry.remote.url,
111
+ send: (args) =>
112
+ dispatchVerbInProcess({
113
+ entry,
114
+ args,
115
+ baseUrl: 'http://localhost/',
116
+ headers: requestHeaders(),
117
+ }),
118
+ }
119
+ }
120
+
121
+ // Remote-mode registry fallback for callers passing a url but no manifest.
122
+ function registryCommand(
123
+ name: string,
124
+ ): { method: HttpVerb; url: string; accept?: string } | undefined {
125
+ const found = findVerbByCommandName(name)
126
+ return found ? { method: found.remote.method, url: found.remote.url } : undefined
127
+ }
128
+
129
+ /*
130
+ Memoise per-name so repeated `client.foo` accesses skip both the
131
+ registry scan in resolve() and a fresh closure allocation. The
132
+ manifest + registry are fixed for a client's lifetime, so a resolved
133
+ invoker (or its absence) never changes.
134
+ */
135
+ const invokerCache = new Map<string, ClientInvoker | undefined>()
136
+
137
+ /*
138
+ Build a memoised invoker for a resolved command. The plain call and
139
+ `.raw` share one `send`, so they can't diverge on URL/headers — the
140
+ plain call just decodes the body and throws on non-2xx on the way out.
141
+ */
142
+ function buildInvoker(resolved: ResolvedSend): ClientInvoker {
143
+ const invoker = (async (args?: unknown) => {
144
+ const response = await resolved.send(args)
145
+ if (!response.ok) {
146
+ throw new Error(
147
+ `${resolved.method} ${resolved.url} failed: ${response.status} ${response.statusText}`,
148
+ )
149
+ }
150
+ return decodeResponse(response)
151
+ }) as ClientInvoker
152
+ invoker.raw = (args?: unknown) => resolved.send(args)
153
+ return invoker
154
+ }
155
+
156
+ return new Proxy({} as Api, {
157
+ get(_target, prop): ClientInvoker | undefined {
158
+ if (typeof prop !== 'string') {
159
+ return undefined
160
+ }
161
+ if (invokerCache.has(prop)) {
162
+ return invokerCache.get(prop)
163
+ }
164
+ const resolved = resolve(prop)
165
+ const invoker = resolved ? buildInvoker(resolved) : undefined
166
+ invokerCache.set(prop, invoker)
167
+ return invoker
168
+ },
169
+ })
170
+ }
@@ -0,0 +1,71 @@
1
+ import { decodeResponse } from '../shared/decodeResponse.ts'
2
+ import { isStreamingResponse } from '../shared/isStreamingResponse.ts'
3
+ import { responseErrorText } from '../shared/responseErrorText.ts'
4
+ import { streamResponse } from '../shared/streamResponse.ts'
5
+ import { createClient } from './createClient.ts'
6
+ import { parseArgvForRpc } from './parseArgvForRpc.ts'
7
+ import { printValue } from './printValue.ts'
8
+ import type { CliManifest } from './types/CliManifest.ts'
9
+
10
+ /*
11
+ Runs one RPC command against a target server and prints the result, returning a
12
+ process exit code. Shared by the one-shot path (runCli) and the interactive
13
+ session (runSession) so a command behaves identically typed at the shell or at
14
+ the session prompt. Streaming responses (sse/jsonl — a streaming verb or a socket
15
+ `tail`) print frame-by-frame as NDJSON; everything else decodes and prints once.
16
+ */
17
+ export async function dispatchCommand({
18
+ programName,
19
+ manifest,
20
+ command,
21
+ argvTail,
22
+ url,
23
+ token,
24
+ }: {
25
+ programName: string
26
+ manifest: CliManifest
27
+ command: string
28
+ argvTail: string[]
29
+ url: string
30
+ token?: string
31
+ }): Promise<number> {
32
+ const entry = manifest[command]
33
+ if (!entry) {
34
+ console.error(
35
+ `${programName}: unknown command "${command}" — run \`${programName} --help\` for the list`,
36
+ )
37
+ return 1
38
+ }
39
+
40
+ let args: Record<string, unknown> | undefined
41
+ try {
42
+ args = await parseArgvForRpc(argvTail, entry.jsonSchema)
43
+ } catch (error) {
44
+ console.error(`${programName}: ${error instanceof Error ? error.message : String(error)}`)
45
+ return 1
46
+ }
47
+
48
+ const client = createClient({ url, token, manifest })
49
+ const fn = client[command]
50
+ if (!fn) {
51
+ console.error(`${programName}: command "${command}" not in client`)
52
+ return 1
53
+ }
54
+ try {
55
+ const response = await fn.raw(args)
56
+ if (isStreamingResponse(response)) {
57
+ for await (const frame of streamResponse(response)) {
58
+ printValue(frame, false)
59
+ }
60
+ return 0
61
+ }
62
+ if (!response.ok) {
63
+ throw new Error(await responseErrorText(response))
64
+ }
65
+ printValue(await decodeResponse(response), true)
66
+ return 0
67
+ } catch (error) {
68
+ console.error(`${programName}: ${error instanceof Error ? error.message : String(error)}`)
69
+ return 1
70
+ }
71
+ }
@@ -0,0 +1,16 @@
1
+ import { dirname } from 'node:path'
2
+ import { bundleLayout } from '../shared/bundleLayout.ts'
3
+ import { loadEnvFile } from '../shared/loadEnvFile.ts'
4
+
5
+ /*
6
+ Loads the bundle's shipped `.env` into `process.env`, resolved from the running
7
+ binary's directory (`process.execPath`) via bundleLayout — beside the binary in
8
+ the flat layout, under `Contents/Resources` in a macOS `.app`. This is the file the
9
+ install tarball ships beside the executable — and, for a bundle, the one `bundleApp`
10
+ copies from the project's `bundle.env`. It carries the app's shipped defaults; the
11
+ fill-when-unset merge (see loadEnvFile) lets per-shell exports, Bun's CWD
12
+ `.env`, and the user's data-dir config all override it.
13
+ */
14
+ export async function loadEnvFromBinaryDir(): Promise<void> {
15
+ await loadEnvFile(bundleLayout(dirname(process.execPath)).envPath)
16
+ }
@@ -0,0 +1,97 @@
1
+ /*
2
+ Parses an argv tail into the JSON args bag for an RPC. The JSON Schema
3
+ on the manifest entry (when present) drives flag typing:
4
+ - properties whose type is "boolean" accept `--name` / `--no-name`
5
+ - properties whose type is "number" / "integer" accept `--name <n>` and
6
+ coerce with Number()
7
+ - properties whose type is "array" accept repeated `--name <v>`
8
+ - anything else accepts `--name <value>` as a string
9
+
10
+ For complex shapes (nested objects, unions, anyOf) the CLI exposes
11
+ `--json <stringified-args>` as an escape hatch that supplies the whole
12
+ args bag verbatim. Stdin: if a JSON object arrives piped in, it's used
13
+ as the full args bag (flags layer on top).
14
+
15
+ Unrecognised flags throw — early loud feedback is more useful than
16
+ silent drops.
17
+ */
18
+ export async function parseArgvForRpc(
19
+ argv: string[],
20
+ jsonSchema: Record<string, unknown> | undefined,
21
+ ): Promise<Record<string, unknown> | undefined> {
22
+ const properties =
23
+ (jsonSchema?.properties as Record<string, { type?: string }> | undefined) ?? {}
24
+ const args: Record<string, unknown> = {}
25
+
26
+ /*
27
+ Stdin override: if a JSON object is piped in, treat it as the
28
+ starting args bag. `Bun.stdin.text()` reads the whole pipe; if
29
+ nothing was piped, the read resolves with an empty string.
30
+ */
31
+ if (!process.stdin.isTTY) {
32
+ const text = await Bun.stdin.text()
33
+ if (text.trim()) {
34
+ try {
35
+ const piped = JSON.parse(text)
36
+ if (piped && typeof piped === 'object' && !Array.isArray(piped)) {
37
+ Object.assign(args, piped)
38
+ }
39
+ } catch {
40
+ throw new Error(`stdin is not a valid JSON object: ${text.slice(0, 80)}…`)
41
+ }
42
+ }
43
+ }
44
+
45
+ for (let index = 0; index < argv.length; index++) {
46
+ const token = argv[index] as string
47
+ if (token === '--json') {
48
+ const next = argv[++index]
49
+ if (!next) {
50
+ throw new Error('--json requires a value')
51
+ }
52
+ const parsed = JSON.parse(next)
53
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
54
+ throw new Error('--json value must be a JSON object')
55
+ }
56
+ Object.assign(args, parsed)
57
+ continue
58
+ }
59
+ if (!token.startsWith('--')) {
60
+ throw new Error(`unexpected positional argument: ${token}`)
61
+ }
62
+ const isNegated = token.startsWith('--no-')
63
+ const rawName = isNegated ? token.slice('--no-'.length) : token.slice('--'.length)
64
+ const [name, eqValue] = rawName.includes('=')
65
+ ? [rawName.slice(0, rawName.indexOf('=')), rawName.slice(rawName.indexOf('=') + 1)]
66
+ : [rawName, undefined]
67
+ const prop = properties[name]
68
+ const propType = prop?.type
69
+ if (propType === 'boolean') {
70
+ args[name] = !isNegated
71
+ continue
72
+ }
73
+ if (isNegated) {
74
+ throw new Error(`--no-${name} is only valid on boolean flags`)
75
+ }
76
+ const value = eqValue ?? argv[++index]
77
+ if (value === undefined) {
78
+ throw new Error(`--${name} requires a value`)
79
+ }
80
+ if (propType === 'number' || propType === 'integer') {
81
+ const n = Number(value)
82
+ if (Number.isNaN(n)) {
83
+ throw new Error(`--${name} expects a number, got ${value}`)
84
+ }
85
+ args[name] = n
86
+ continue
87
+ }
88
+ if (propType === 'array') {
89
+ const existing = args[name]
90
+ args[name] = Array.isArray(existing) ? [...existing, value] : [value]
91
+ continue
92
+ }
93
+ args[name] = value
94
+ }
95
+
96
+ return Object.keys(args).length === 0 ? undefined : args
97
+ }
@@ -0,0 +1,119 @@
1
+ import { printTrimmed } from './printTrimmed.ts'
2
+ import type { CliManifest } from './types/CliManifest.ts'
3
+ import type { CliManifestEntry } from './types/CliManifestEntry.ts'
4
+
5
+ /*
6
+ Compact one-line flag signature for the top-level listing: required
7
+ flags are shown bare, optional flags wrapped in `[ ]`. Booleans drop the
8
+ value placeholder; arrays show `<value...>` to hint repetition. Full
9
+ per-flag types + descriptions live in `<cmd> --help`.
10
+ */
11
+ function flagSignature(jsonSchema: CliManifestEntry['jsonSchema']): string {
12
+ const properties =
13
+ (jsonSchema?.properties as Record<string, { type?: string }> | undefined) ?? {}
14
+ const required = new Set((jsonSchema?.required as string[] | undefined) ?? [])
15
+ return Object.entries(properties)
16
+ .map(([key, value]) => {
17
+ const placeholder =
18
+ value.type === 'boolean'
19
+ ? `--${key}`
20
+ : value.type === 'array'
21
+ ? `--${key} <value...>`
22
+ : `--${key} <${value.type ?? 'value'}>`
23
+ return required.has(key) ? placeholder : `[${placeholder}]`
24
+ })
25
+ .join(' ')
26
+ }
27
+
28
+ /*
29
+ Top-level help (no subcommand) lists every available command with a
30
+ one-line summary. Per-command help (`<cmd> --help`) prints the flags
31
+ derived from the command's JSON Schema. Output goes to stdout in both
32
+ cases; the caller exits zero after printing.
33
+ */
34
+ export function printTopLevelHelp(
35
+ programName: string,
36
+ manifest: CliManifest,
37
+ banner = '',
38
+ footer = '',
39
+ ): void {
40
+ if (banner.trim()) {
41
+ printTrimmed(banner)
42
+ console.log('')
43
+ }
44
+ const names = Object.keys(manifest).toSorted()
45
+ console.log(`usage: ${programName} <command> [--flags]\n`)
46
+ console.log('commands:')
47
+ for (const name of names) {
48
+ const entry = manifest[name]
49
+ if (!entry) {
50
+ continue
51
+ }
52
+ /*
53
+ Summary line favours the schema's top-level description (carried
54
+ through by the vendor's JSON Schema conversion); falls back to
55
+ `method url` when the schema has none. The detail line below
56
+ always shows the args, plus method/url when it isn't already the
57
+ summary.
58
+ */
59
+ const description = entry.jsonSchema?.description as string | undefined
60
+ console.log(` ${name.padEnd(20)} ${description ?? `${entry.method} ${entry.url}`}`)
61
+ const signature = flagSignature(entry.jsonSchema)
62
+ const detail = [description && `${entry.method} ${entry.url}`, signature]
63
+ .filter(Boolean)
64
+ .join(' ')
65
+ if (detail) {
66
+ console.log(` ${' '.repeat(20)} ${detail}`)
67
+ }
68
+ }
69
+ console.log(`\nconnection (\`/\` manages the connection, a bare word runs a command):`)
70
+ console.log(` ${programName} /connect <url> connect to a remote server`)
71
+ console.log(` ${programName} /start start a local instance`)
72
+ console.log(` ${programName} /disconnect forget the saved connection`)
73
+ console.log(` ${programName} resume the saved connection (session)`)
74
+ console.log(`\n --help, -h show this help`)
75
+ console.log(` <command> --help show help for a specific command`)
76
+ console.log(`\nenv:`)
77
+ console.log(` BELTE_APP_URL default server URL (baked at install; shell-overridable)`)
78
+ console.log(` BELTE_APP_TOKEN sent as Authorization: Bearer <value>`)
79
+ if (footer.trim()) {
80
+ console.log('')
81
+ printTrimmed(footer)
82
+ }
83
+ }
84
+
85
+ export function printCommandHelp(programName: string, name: string, manifest: CliManifest): void {
86
+ const entry = manifest[name]
87
+ if (!entry) {
88
+ console.log(`unknown command: ${name}`)
89
+ return
90
+ }
91
+ console.log(`usage: ${programName} ${name} [--flags]\n`)
92
+ console.log(` ${entry.method} ${entry.url}\n`)
93
+ const schema = entry.jsonSchema
94
+ const commandDescription = schema?.description as string | undefined
95
+ if (commandDescription) {
96
+ console.log(`${commandDescription}\n`)
97
+ }
98
+ const properties =
99
+ (schema?.properties as
100
+ | Record<string, { type?: string; description?: string }>
101
+ | undefined) ?? {}
102
+ const required = new Set((schema?.required as string[] | undefined) ?? [])
103
+ if (Object.keys(properties).length === 0) {
104
+ console.log('flags: (none)')
105
+ } else {
106
+ console.log('flags:')
107
+ for (const [key, value] of Object.entries(properties)) {
108
+ const tag =
109
+ value.type === 'boolean'
110
+ ? `--${key} / --no-${key}`
111
+ : `--${key} <${value.type ?? 'value'}>`
112
+ const requiredTag = required.has(key) ? ' (required)' : ''
113
+ const description = value.description ? ` — ${value.description}` : ''
114
+ console.log(` ${tag.padEnd(28)}${requiredTag}${description}`)
115
+ }
116
+ }
117
+ console.log('\n --json <object> full args bag as JSON (overrides flags)')
118
+ console.log(' (stdin) pipe a JSON object as the args bag')
119
+ }
@@ -0,0 +1,27 @@
1
+ import { printCommandHelp, printTopLevelHelp } from './printHelp.ts'
2
+ import type { CliManifest } from './types/CliManifest.ts'
3
+
4
+ /*
5
+ Session `/help`: with a command name, the per-command flag help; otherwise the
6
+ meta-command list followed by the RPC command listing (no banner — already shown
7
+ at session start).
8
+ */
9
+ export function printSessionHelp(
10
+ programName: string,
11
+ manifest: CliManifest,
12
+ command?: string,
13
+ ): void {
14
+ if (command) {
15
+ printCommandHelp(programName, command, manifest)
16
+ return
17
+ }
18
+ console.log('session commands:')
19
+ console.log(' /connect <url> connect to a remote server')
20
+ console.log(' /start start a local instance')
21
+ console.log(' /disconnect disconnect and forget the saved connection')
22
+ console.log(' /help [command] show this help, or help for one command')
23
+ console.log(' /clear clear the screen')
24
+ console.log(' /exit leave the session')
25
+ console.log('')
26
+ printTopLevelHelp(programName, manifest)
27
+ }
@@ -0,0 +1,21 @@
1
+ import { probeBelteServer } from '../bundle/probeBelteServer.ts'
2
+ import type { CliTarget } from './types/CliTarget.ts'
3
+
4
+ /*
5
+ Prints the session's connection line. A local instance (spawned child) reads as
6
+ "running a local instance"; a remote one reads as "connected to <name>", using the
7
+ name the target already carries from its resolve-time probe and only re-probing
8
+ when it doesn't. No target → the not-connected hint listing the verbs.
9
+ */
10
+ export async function printSessionStatus(target: CliTarget | undefined): Promise<void> {
11
+ if (!target) {
12
+ console.log('(not connected — /connect <url> or /start)')
13
+ return
14
+ }
15
+ if (target.child) {
16
+ console.log(`running a local instance at ${target.url}`)
17
+ return
18
+ }
19
+ const name = target.name ?? (await probeBelteServer(target.url))?.name ?? target.url
20
+ console.log(`connected to ${name} at ${target.url}`)
21
+ }
@@ -0,0 +1,8 @@
1
+ // Prints a block of chrome (banner/footer) with its trailing newline stripped,
2
+ // or nothing when the text is blank. Shared by the help, banner, and session
3
+ // footer so the trim-and-skip idiom lives in one place.
4
+ export function printTrimmed(text: string): void {
5
+ if (text.trim()) {
6
+ console.log(text.replace(/\n$/, ''))
7
+ }
8
+ }
@@ -0,0 +1,10 @@
1
+ // String results print verbatim (with a trailing newline); everything else as a JSON line.
2
+ export function printValue(value: unknown, pretty: boolean): void {
3
+ if (typeof value === 'string') {
4
+ process.stdout.write(value.endsWith('\n') ? value : `${value}\n`)
5
+ return
6
+ }
7
+ if (value !== undefined) {
8
+ process.stdout.write(`${JSON.stringify(value, null, pretty ? 2 : undefined)}\n`)
9
+ }
10
+ }
@@ -0,0 +1,48 @@
1
+ import { probeBelteServer } from '../bundle/probeBelteServer.ts'
2
+ import { spawnEmbeddedServer } from '../bundle/spawnEmbeddedServer.ts'
3
+ import { log } from '../shared/log.ts'
4
+ import { readLastConnection } from '../shared/readLastConnection.ts'
5
+ import type { CliTarget } from './types/CliTarget.ts'
6
+
7
+ // Bound a resume boot so a slow/failed local start falls back to not-connected
8
+ // rather than hanging the CLI before the prompt appears.
9
+ const AUTO_START_CEILING_MS = 3000
10
+
11
+ /*
12
+ Resolves the connection to resume when the CLI runs without an explicit
13
+ connection verb — the terminal analogue of the bundle's resolveLaunchTarget.
14
+ Reads the saved intent:
15
+ - embedded → boot a fresh local instance (bounded; undefined on failure)
16
+ - url, still alive → connect to it
17
+ - url, now dead → warn, undefined (caller shows the not-connected prompt)
18
+ - nothing recorded → the baked/shell BELTE_APP_URL default, else undefined
19
+ Returns undefined when there's nothing live to talk to.
20
+ */
21
+ export async function resolveCliTarget(programName: string): Promise<CliTarget | undefined> {
22
+ const last = await readLastConnection(programName)
23
+ if (last?.kind === 'embedded') {
24
+ try {
25
+ const { url, child } = await spawnEmbeddedServer({
26
+ programName,
27
+ timeoutMs: AUTO_START_CEILING_MS,
28
+ })
29
+ return { url, child }
30
+ } catch (error) {
31
+ log.warn(
32
+ `could not start local instance: ${error instanceof Error ? error.message : String(error)}`,
33
+ )
34
+ return undefined
35
+ }
36
+ }
37
+ if (last?.kind === 'url') {
38
+ const identity = await probeBelteServer(last.url)
39
+ if (identity) {
40
+ return { url: last.url, token: process.env.BELTE_APP_TOKEN, name: identity.name }
41
+ }
42
+ log.warn(`last server at ${last.url} is not responding`)
43
+ return undefined
44
+ }
45
+ // Nothing recorded — fall back to the baked default / shell override.
46
+ const appUrl = process.env.BELTE_APP_URL
47
+ return appUrl ? { url: appUrl, token: process.env.BELTE_APP_TOKEN } : undefined
48
+ }