@briancray/belte 0.1.0 → 0.2.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 (98) hide show
  1. package/bin/belte.ts +25 -12
  2. package/package.json +2 -1
  3. package/src/appEntry.ts +124 -0
  4. package/src/belteResolverPlugin.ts +217 -194
  5. package/src/build.ts +6 -67
  6. package/src/buildCli.ts +36 -63
  7. package/src/buildDisconnected.ts +127 -0
  8. package/src/bundleApp.ts +123 -0
  9. package/src/bundleDisconnectedEntry.ts +17 -0
  10. package/src/cliEntry.ts +3 -9
  11. package/src/compile.ts +4 -15
  12. package/src/controlServerWorker.ts +261 -0
  13. package/src/dedupeSveltePlugin.ts +66 -0
  14. package/src/discoveryEntry.ts +12 -11
  15. package/src/lib/browser/cache.ts +3 -6
  16. package/src/lib/browser/page.svelte.ts +19 -21
  17. package/src/lib/browser/socketChannel.ts +11 -1
  18. package/src/lib/browser/types/Pages.ts +1 -1
  19. package/src/lib/bundle/BundleMenu.ts +11 -0
  20. package/src/lib/bundle/BundleMenuItem.ts +24 -0
  21. package/src/lib/bundle/BundleWindow.ts +20 -0
  22. package/src/lib/bundle/bindConnectedFlag.ts +29 -0
  23. package/src/lib/bundle/bindRequestNavigate.ts +31 -0
  24. package/src/lib/bundle/buildWebviewLib.ts +111 -0
  25. package/src/lib/bundle/disconnected.css +9 -0
  26. package/src/lib/bundle/disconnected.svelte +192 -0
  27. package/src/lib/bundle/ensureWebviewLib.ts +20 -0
  28. package/src/lib/bundle/exitWithParent.ts +28 -0
  29. package/src/lib/bundle/findFreePort.ts +14 -0
  30. package/src/lib/bundle/infoPlist.ts +46 -0
  31. package/src/lib/bundle/installMacMenu.ts +39 -0
  32. package/src/lib/bundle/listenLocalControlServer.ts +19 -0
  33. package/src/lib/bundle/native/belteMenu.mm +298 -0
  34. package/src/lib/bundle/native/webview.h +4557 -0
  35. package/src/lib/bundle/onMenu.ts +26 -0
  36. package/src/lib/bundle/openWebview.ts +81 -0
  37. package/src/lib/bundle/pngToIcns.ts +47 -0
  38. package/src/lib/bundle/probeBelteServer.ts +34 -0
  39. package/src/lib/bundle/resolveServerBinary.ts +12 -0
  40. package/src/lib/bundle/resolveWebviewLib.ts +51 -0
  41. package/src/lib/bundle/serverBinaryFilename.ts +8 -0
  42. package/src/lib/bundle/stableLocalPort.ts +19 -0
  43. package/src/lib/bundle/waitForServer.ts +23 -0
  44. package/src/lib/bundle/webviewBuildRevision.ts +9 -0
  45. package/src/lib/bundle/webviewCachePath.ts +23 -0
  46. package/src/lib/bundle/webviewLibName.ts +11 -0
  47. package/src/lib/bundle/webviewVersion.ts +7 -0
  48. package/src/lib/cli/createClient.ts +34 -36
  49. package/src/lib/cli/printHelp.ts +45 -2
  50. package/src/lib/cli/runCli.ts +12 -3
  51. package/src/lib/mcp/createMcpResourceServer.ts +1 -1
  52. package/src/lib/mcp/dispatchMcpRequest.ts +53 -73
  53. package/src/lib/server/AppModule.ts +2 -2
  54. package/src/lib/server/cli/handleCliDownload.ts +4 -5
  55. package/src/lib/server/cli/handleCliInstall.ts +17 -0
  56. package/src/lib/server/error.ts +23 -9
  57. package/src/lib/server/json.ts +5 -5
  58. package/src/lib/server/jsonl.ts +10 -5
  59. package/src/lib/server/prompts/definePrompt.ts +6 -6
  60. package/src/lib/server/prompts/renderPromptTemplate.ts +16 -0
  61. package/src/lib/server/prompts/types/Prompt.ts +8 -9
  62. package/src/lib/server/prompts/types/PromptOptions.ts +7 -12
  63. package/src/lib/server/prompts/types/PromptRegistryEntry.ts +3 -5
  64. package/src/lib/server/prompts/types/PromptRoutes.ts +4 -4
  65. package/src/lib/server/redirect.ts +13 -8
  66. package/src/lib/server/rpc/defineVerb.ts +4 -3
  67. package/src/lib/server/rpc/findVerbByCommandName.ts +18 -0
  68. package/src/lib/server/rpc/types/RemoteFunction.ts +1 -1
  69. package/src/lib/server/rpc/types/RemoteHandler.ts +4 -0
  70. package/src/lib/server/runtime/acceptsZstd.ts +8 -0
  71. package/src/lib/server/runtime/buildOpenApiSpec.ts +2 -0
  72. package/src/lib/server/runtime/cacheControlForAsset.ts +7 -2
  73. package/src/lib/server/runtime/createAssetHeaderCache.ts +35 -0
  74. package/src/lib/server/runtime/createPublicAssetServer.ts +6 -20
  75. package/src/lib/server/runtime/createServer.ts +50 -58
  76. package/src/lib/server/runtime/registryManifests.ts +33 -15
  77. package/src/lib/server/runtime/types/RequestStore.ts +2 -3
  78. package/src/lib/server/runtime/withResponseDefaults.ts +24 -0
  79. package/src/lib/server/sockets/createSocketDispatcher.ts +10 -7
  80. package/src/lib/server/sse.ts +10 -5
  81. package/src/lib/shared/cacheControlValues.ts +10 -2
  82. package/src/lib/shared/canonicalJson.ts +1 -5
  83. package/src/lib/shared/createCacheStore.ts +29 -20
  84. package/src/lib/shared/exitOnBuildFailure.ts +17 -0
  85. package/src/lib/shared/fileStem.ts +9 -0
  86. package/src/lib/shared/jsonSchemaForPromptArguments.ts +29 -0
  87. package/src/lib/shared/keyForRemoteCall.ts +7 -5
  88. package/src/lib/shared/parsePromptMarkdown.ts +34 -0
  89. package/src/lib/shared/promptNameForFile.ts +5 -5
  90. package/src/lib/shared/subscribableFromResponse.ts +104 -215
  91. package/src/lib/shared/types/PromptArgument.ts +12 -0
  92. package/src/lib/shared/writeRoutesDts.ts +5 -7
  93. package/src/serverBuildPlugins.ts +25 -0
  94. package/src/serverEntry.ts +4 -0
  95. package/template/package.json +2 -1
  96. package/src/lib/server/prompt.ts +0 -30
  97. package/src/lib/server/prompts/types/PromptMessage.ts +0 -10
  98. package/src/lib/shared/preparePromptModule.ts +0 -36
@@ -0,0 +1,298 @@
1
+ // belte's native macOS shim, compiled into the same dylib as the vendored
2
+ // webview header. The upstream webview library creates a bare NSWindow with no
3
+ // application menu bar, so a non-bundled webview app has no Quit item and the
4
+ // standard Edit shortcuts (Cmd-C/V/X/A/Z) never reach the WKWebView. This
5
+ // installs the conventional menu so the window behaves like a normal Mac app,
6
+ // the built-in File menu (Start / Disconnect), and the bundle's custom menus
7
+ // whose items emit events into the page.
8
+ #import <Cocoa/Cocoa.h>
9
+ #include <cstdlib>
10
+ #include <cstring>
11
+
12
+ // webview C entry points, also in this dylib — used to drive the live window.
13
+ extern "C" int webview_navigate(void *w, const char *url);
14
+ extern "C" int webview_eval(void *w, const char *js);
15
+ // Marshals a callback onto the UI thread; the only safe way to touch the window
16
+ // from another thread (the launcher runs its control server off the main thread).
17
+ extern "C" int webview_dispatch(void *w, void (*fn)(void *w, void *arg), void *arg);
18
+
19
+ // Exported despite -fvisibility=hidden so belte's FFI layer can resolve it.
20
+ #define BELTE_EXPORT __attribute__((visibility("default")))
21
+
22
+ /*
23
+ Process-wide connection flag the menu validation reads. The launcher owns every
24
+ connection transition and flips this via belte_set_connected, so the Server menu
25
+ items enable/disable correctly: menu validation runs each time a menu opens, so
26
+ just storing the bool is enough — no explicit revalidation needed.
27
+ */
28
+ static int g_belte_connected = 0;
29
+
30
+ // Roles for the built-in Server menu items, parsed from each item's `role` field.
31
+ // `none` is any non-Server navigate item (always enabled).
32
+ typedef enum {
33
+ BelteRoleNone = 0,
34
+ BelteRoleStart,
35
+ BelteRoleDisconnect,
36
+ } BelteRole;
37
+
38
+ // Sets the connection flag the Server menu's validateMenuItem: reads.
39
+ extern "C" BELTE_EXPORT void belte_set_connected(int connected) {
40
+ g_belte_connected = connected;
41
+ }
42
+
43
+ // Runs on the UI thread via webview_dispatch: navigate, then free the copy made
44
+ // below (the original buffer is long gone — the dispatch is asynchronous).
45
+ static void belteDispatchNavigate(void *w, void *arg) {
46
+ char *url = (char *)arg;
47
+ webview_navigate(w, url);
48
+ free(url);
49
+ }
50
+
51
+ /*
52
+ Navigate the live window to `url` from any thread. The launcher's control server
53
+ runs off the main thread, so it can't call webview_navigate (a Cocoa/UI call)
54
+ directly; this hops onto the UI thread via webview_dispatch. The URL is copied
55
+ synchronously here because the dispatch runs later, after the caller's buffer is
56
+ gone — belteDispatchNavigate frees the copy once it has navigated.
57
+ */
58
+ extern "C" BELTE_EXPORT void belte_request_navigate(void *w, const char *url) {
59
+ webview_dispatch(w, belteDispatchNavigate, strdup(url));
60
+ }
61
+
62
+ // Maps a JSON `role` string to its enum; unknown/absent → none.
63
+ static BelteRole roleFromString(NSString *role) {
64
+ if ([role isEqualToString:@"start"]) {
65
+ return BelteRoleStart;
66
+ }
67
+ if ([role isEqualToString:@"disconnect"]) {
68
+ return BelteRoleDisconnect;
69
+ }
70
+ return BelteRoleNone;
71
+ }
72
+
73
+ // Builds a JS string literal for `value`, safely escaped via NSJSONSerialization.
74
+ static NSString *jsString(NSString *value) {
75
+ NSData *json = [NSJSONSerialization dataWithJSONObject:value
76
+ options:NSJSONWritingFragmentsAllowed
77
+ error:nil];
78
+ return [[NSString alloc] initWithData:json encoding:NSUTF8StringEncoding];
79
+ }
80
+
81
+ /*
82
+ Action target for a menu item. Owns the webview handle so a click can drive the
83
+ live window: a `navigate` item repoints the window at a URL (the Server menu's
84
+ Start/Disconnect, gated by `role`), a `emit` item dispatches a belte:menu
85
+ event into the page. Lives for the whole process (NSMenuItem does not retain its
86
+ target), matching the never-released menu objects below.
87
+ */
88
+ @interface BelteMenuAction : NSObject {
89
+ @public
90
+ void *webviewHandle;
91
+ NSString *navigateUrl; // target URL for a navigate item; nil otherwise
92
+ NSString *emitName; // event name for an emit item; nil otherwise
93
+ BelteRole role; // gating role for a Server navigate item; none otherwise
94
+ }
95
+ - (void)navigateTo:(id)sender;
96
+ - (void)emit:(id)sender;
97
+ - (BOOL)validateMenuItem:(NSMenuItem *)item;
98
+ @end
99
+
100
+ @implementation BelteMenuAction
101
+ // Points the live webview at this item's URL (already on the UI thread here).
102
+ - (void)navigateTo:(id)sender {
103
+ if (navigateUrl != nil) {
104
+ webview_navigate(webviewHandle, [navigateUrl UTF8String]);
105
+ }
106
+ }
107
+
108
+ // Dispatches a belte:menu CustomEvent (detail `{ name }`) into the page, so the
109
+ // app's own code handles it — including computing args for a parameterised call.
110
+ - (void)emit:(id)sender {
111
+ NSString *js = [NSString
112
+ stringWithFormat:
113
+ @"window.dispatchEvent(new CustomEvent('belte:menu',{detail:{name:%@}}))",
114
+ jsString(emitName)];
115
+ webview_eval(webviewHandle, [js UTF8String]);
116
+ }
117
+
118
+ /*
119
+ Drives the enabled state per connection. Server navigate items follow the truth
120
+ table — Start only when disconnected, Disconnect only when connected — emit items
121
+ only fire into a loaded page (so connected), and plain navigate items (role none)
122
+ are always enabled.
123
+ */
124
+ - (BOOL)validateMenuItem:(NSMenuItem *)item {
125
+ if (navigateUrl != nil) {
126
+ switch (role) {
127
+ case BelteRoleStart:
128
+ return g_belte_connected == 0;
129
+ case BelteRoleDisconnect:
130
+ return g_belte_connected != 0;
131
+ default:
132
+ return YES;
133
+ }
134
+ }
135
+ if (emitName != nil) {
136
+ return g_belte_connected != 0;
137
+ }
138
+ return YES;
139
+ }
140
+ @end
141
+
142
+ // Builds one bundle submenu from its JSON description and items, or nil when
143
+ // the description is malformed. Each item is a separator, a `navigate` item
144
+ // (optionally roled), or an `emit` item.
145
+ static NSMenu *buildBundleMenu(NSDictionary *menuDef, void *webview_handle) {
146
+ if (![menuDef isKindOfClass:[NSDictionary class]]) {
147
+ return nil;
148
+ }
149
+ NSMenu *menu = [[NSMenu alloc] initWithTitle:menuDef[@"label"] ?: @""];
150
+ for (NSDictionary *item in menuDef[@"items"]) {
151
+ if ([item[@"separator"] boolValue]) {
152
+ [menu addItem:[NSMenuItem separatorItem]];
153
+ continue;
154
+ }
155
+ NSString *navigate = item[@"navigate"];
156
+ NSString *emitName = item[@"emit"];
157
+ BelteMenuAction *target = [[BelteMenuAction alloc] init];
158
+ target->webviewHandle = webview_handle;
159
+ SEL action;
160
+ if ([navigate isKindOfClass:[NSString class]]) {
161
+ target->navigateUrl = [navigate copy];
162
+ target->role = roleFromString(item[@"role"]);
163
+ action = @selector(navigateTo:);
164
+ } else if ([emitName isKindOfClass:[NSString class]]) {
165
+ target->emitName = [emitName copy];
166
+ action = @selector(emit:);
167
+ } else {
168
+ continue;
169
+ }
170
+ NSMenuItem *menuItem = [menu addItemWithTitle:(item[@"label"] ?: @"")
171
+ action:action
172
+ keyEquivalent:(item[@"shortcut"] ?: @"")];
173
+ [menuItem setTarget:target];
174
+ }
175
+ return menu;
176
+ }
177
+
178
+ /*
179
+ Builds and installs the macOS main menu on the shared application: App, File (the
180
+ launcher's built-in Start/Disconnect), Edit, the bundle's custom menus, and
181
+ Window.
182
+
183
+ Safe to call after webview_create — which has already created the
184
+ NSApplication — and must run before webview_run so the menu is present when the
185
+ run loop starts. `webview_handle` is the value returned by webview_create,
186
+ captured so the menu can drive it. `config_json` is a JSON object
187
+ `{ "appName": string, "fileMenu"?: { label, items }, "menu"?: [{ label, items }] }`,
188
+ where each item is `{ separator: true }`, `{ label, shortcut?, navigate, role? }`,
189
+ or `{ label, shortcut?, emit }`; pass NULL for the standard menus only.
190
+
191
+ Compiled without ARC (matching the webview header's manual memory model): the
192
+ menu objects and action targets intentionally live for the whole process, so
193
+ the +1 alloc counts are never released.
194
+ */
195
+ extern "C" BELTE_EXPORT void belte_install_app_menu(void *webview_handle,
196
+ const char *config_json) {
197
+ @autoreleasepool {
198
+ NSDictionary *config = nil;
199
+ if (config_json) {
200
+ NSData *data = [[NSString stringWithUTF8String:config_json]
201
+ dataUsingEncoding:NSUTF8StringEncoding];
202
+ id parsed = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
203
+ if ([parsed isKindOfClass:[NSDictionary class]]) {
204
+ config = parsed;
205
+ }
206
+ }
207
+ NSApplication *app = [NSApplication sharedApplication];
208
+ NSString *appName = config[@"appName"] ?: @"App";
209
+
210
+ NSMenu *mainMenu = [[NSMenu alloc] init];
211
+
212
+ // Application menu — the bold first menu. Its title is ignored by macOS
213
+ // (the process/bundle name wins), but its items are what users reach.
214
+ NSMenuItem *appMenuItem = [[NSMenuItem alloc] init];
215
+ [mainMenu addItem:appMenuItem];
216
+ NSMenu *appMenu = [[NSMenu alloc] init];
217
+ [appMenuItem setSubmenu:appMenu];
218
+
219
+ [appMenu addItemWithTitle:[@"About " stringByAppendingString:appName]
220
+ action:@selector(orderFrontStandardAboutPanel:)
221
+ keyEquivalent:@""];
222
+ [appMenu addItem:[NSMenuItem separatorItem]];
223
+ [appMenu addItemWithTitle:[@"Hide " stringByAppendingString:appName]
224
+ action:@selector(hide:)
225
+ keyEquivalent:@"h"];
226
+ NSMenuItem *hideOthers = [appMenu addItemWithTitle:@"Hide Others"
227
+ action:@selector(hideOtherApplications:)
228
+ keyEquivalent:@"h"];
229
+ [hideOthers setKeyEquivalentModifierMask:(NSEventModifierFlagCommand |
230
+ NSEventModifierFlagOption)];
231
+ [appMenu addItemWithTitle:@"Show All"
232
+ action:@selector(unhideAllApplications:)
233
+ keyEquivalent:@""];
234
+ [appMenu addItem:[NSMenuItem separatorItem]];
235
+ [appMenu addItemWithTitle:[@"Quit " stringByAppendingString:appName]
236
+ action:@selector(terminate:)
237
+ keyEquivalent:@"q"];
238
+
239
+ // File menu — the launcher's Start/Disconnect, in the conventional
240
+ // slot right after the App menu and before Edit. Built from the same item
241
+ // shape as the custom menus, so `role` gating applies.
242
+ NSMenu *fileMenu = buildBundleMenu(config[@"fileMenu"], webview_handle);
243
+ if (fileMenu) {
244
+ NSMenuItem *fileMenuItem = [[NSMenuItem alloc] init];
245
+ [mainMenu addItem:fileMenuItem];
246
+ [fileMenuItem setSubmenu:fileMenu];
247
+ }
248
+
249
+ // Edit menu — the actions resolve against the first responder, which is
250
+ // the WKWebView, so Cmd-Z/X/C/V/A operate on the page's text fields.
251
+ NSMenuItem *editMenuItem = [[NSMenuItem alloc] init];
252
+ [mainMenu addItem:editMenuItem];
253
+ NSMenu *editMenu = [[NSMenu alloc] initWithTitle:@"Edit"];
254
+ [editMenuItem setSubmenu:editMenu];
255
+ [editMenu addItemWithTitle:@"Undo" action:@selector(undo:) keyEquivalent:@"z"];
256
+ NSMenuItem *redo = [editMenu addItemWithTitle:@"Redo"
257
+ action:@selector(redo:)
258
+ keyEquivalent:@"z"];
259
+ [redo setKeyEquivalentModifierMask:(NSEventModifierFlagCommand |
260
+ NSEventModifierFlagShift)];
261
+ [editMenu addItem:[NSMenuItem separatorItem]];
262
+ [editMenu addItemWithTitle:@"Cut" action:@selector(cut:) keyEquivalent:@"x"];
263
+ [editMenu addItemWithTitle:@"Copy" action:@selector(copy:) keyEquivalent:@"c"];
264
+ [editMenu addItemWithTitle:@"Paste" action:@selector(paste:) keyEquivalent:@"v"];
265
+ [editMenu addItemWithTitle:@"Select All"
266
+ action:@selector(selectAll:)
267
+ keyEquivalent:@"a"];
268
+
269
+ // Bundle's menus, inserted between Edit and Window — the launcher passes
270
+ // its built-in Server menu first, followed by the app's custom menus.
271
+ for (NSDictionary *menuDef in config[@"menu"]) {
272
+ NSMenu *bundleMenu = buildBundleMenu(menuDef, webview_handle);
273
+ if (bundleMenu) {
274
+ NSMenuItem *bundleMenuItem = [[NSMenuItem alloc] init];
275
+ [mainMenu addItem:bundleMenuItem];
276
+ [bundleMenuItem setSubmenu:bundleMenu];
277
+ }
278
+ }
279
+
280
+ // Window menu — Minimize/Zoom/Close, registered so macOS tracks the
281
+ // app's windows in it automatically.
282
+ NSMenuItem *windowMenuItem = [[NSMenuItem alloc] init];
283
+ [mainMenu addItem:windowMenuItem];
284
+ NSMenu *windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
285
+ [windowMenuItem setSubmenu:windowMenu];
286
+ [windowMenu addItemWithTitle:@"Minimize"
287
+ action:@selector(performMiniaturize:)
288
+ keyEquivalent:@"m"];
289
+ [windowMenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""];
290
+ [windowMenu addItem:[NSMenuItem separatorItem]];
291
+ [windowMenu addItemWithTitle:@"Close"
292
+ action:@selector(performClose:)
293
+ keyEquivalent:@"w"];
294
+ [app setWindowsMenu:windowMenu];
295
+
296
+ [app setMainMenu:mainMenu];
297
+ }
298
+ }