@buoy-gg/core 6.0.0 → 7.0.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 (40) hide show
  1. package/README.md +91 -28
  2. package/lib/commonjs/floatingMenu/DevToolsSettingsModal.js +64 -0
  3. package/lib/commonjs/floatingMenu/FloatingDevTools.js +34 -10
  4. package/lib/commonjs/floatingMenu/FloatingDevTools.web.js +7 -1
  5. package/lib/commonjs/floatingMenu/autoDiscoverPresets.js +30 -15
  6. package/lib/commonjs/floatingMenu/autoExternalSync.js +43 -34
  7. package/lib/commonjs/floatingMenu/optionalRequire.js +33 -0
  8. package/lib/commonjs/floatingMenu/useDesktopSyncStatus.js +28 -0
  9. package/lib/module/floatingMenu/DevToolsSettingsModal.js +65 -1
  10. package/lib/module/floatingMenu/FloatingDevTools.js +35 -11
  11. package/lib/module/floatingMenu/FloatingDevTools.web.js +7 -1
  12. package/lib/module/floatingMenu/autoDiscoverPresets.js +31 -15
  13. package/lib/module/floatingMenu/autoExternalSync.js +44 -34
  14. package/lib/module/floatingMenu/optionalRequire.js +29 -0
  15. package/lib/module/floatingMenu/useDesktopSyncStatus.js +25 -0
  16. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -1
  17. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.d.ts +18 -4
  18. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.d.ts.map +1 -1
  19. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.web.d.ts +6 -1
  20. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.web.d.ts.map +1 -1
  21. package/lib/typescript/commonjs/floatingMenu/autoDiscoverPresets.d.ts.map +1 -1
  22. package/lib/typescript/commonjs/floatingMenu/autoExternalSync.d.ts +6 -1
  23. package/lib/typescript/commonjs/floatingMenu/autoExternalSync.d.ts.map +1 -1
  24. package/lib/typescript/commonjs/floatingMenu/optionalRequire.d.ts +12 -0
  25. package/lib/typescript/commonjs/floatingMenu/optionalRequire.d.ts.map +1 -0
  26. package/lib/typescript/commonjs/floatingMenu/useDesktopSyncStatus.d.ts +14 -0
  27. package/lib/typescript/commonjs/floatingMenu/useDesktopSyncStatus.d.ts.map +1 -0
  28. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -1
  29. package/lib/typescript/module/floatingMenu/FloatingDevTools.d.ts +18 -4
  30. package/lib/typescript/module/floatingMenu/FloatingDevTools.d.ts.map +1 -1
  31. package/lib/typescript/module/floatingMenu/FloatingDevTools.web.d.ts +6 -1
  32. package/lib/typescript/module/floatingMenu/FloatingDevTools.web.d.ts.map +1 -1
  33. package/lib/typescript/module/floatingMenu/autoDiscoverPresets.d.ts.map +1 -1
  34. package/lib/typescript/module/floatingMenu/autoExternalSync.d.ts +6 -1
  35. package/lib/typescript/module/floatingMenu/autoExternalSync.d.ts.map +1 -1
  36. package/lib/typescript/module/floatingMenu/optionalRequire.d.ts +12 -0
  37. package/lib/typescript/module/floatingMenu/optionalRequire.d.ts.map +1 -0
  38. package/lib/typescript/module/floatingMenu/useDesktopSyncStatus.d.ts +14 -0
  39. package/lib/typescript/module/floatingMenu/useDesktopSyncStatus.d.ts.map +1 -0
  40. package/package.json +19 -8
package/README.md CHANGED
@@ -1,43 +1,106 @@
1
- # @buoy/core
1
+ # @buoy-gg/core
2
2
 
3
- [![npm](https://img.shields.io/npm/v/@buoy%2Fcore)](https://www.npmjs.com/package/@buoy/core)
3
+ [![npm version](https://img.shields.io/npm/v/@buoy-gg/core?style=flat-square&labelColor=1c1c1c&color=10B981)](https://www.npmjs.com/package/@buoy-gg/core)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@buoy-gg/core?style=flat-square&labelColor=1c1c1c&color=10B981&label=downloads%2Fmonth)](https://www.npmjs.com/package/@buoy-gg/core)
4
5
 
6
+ **Devtools that live in your app. And answer to your agent.**
5
7
 
6
- Floating dev tools launcher and host provider for React Native apps.
8
+ Buoy is a floating dev menu that ships inside your React Native app. Every request, state change, render, and frame — live on the phone, on your desktop, and in Claude or Cursor. Drop in one component, install the tools you want, and they appear in the menu on their own.
9
+
10
+ ![Buoy demo — the floating dev menu in action](https://github.com/user-attachments/assets/a732d6a3-9963-49e3-b0f1-0d974a0a74d7)
7
11
 
8
12
  ## Install
13
+
9
14
  ```bash
10
- pnpm add @buoy/core @buoy/shared-ui
15
+ npm install @buoy-gg/core
11
16
  ```
12
17
 
13
- ## Quick Start
18
+ ## Quick start
19
+
14
20
  ```tsx
15
- import { AppHostProvider, FloatingMenu } from '@buoy/core';
16
- import type { InstalledApp } from '@buoy/core';
17
- import { EnvVarsModal } from '@buoy/env';
18
-
19
- const APPS: InstalledApp[] = [
20
- {
21
- id: 'env',
22
- name: 'Environment',
23
- icon: '🛰️',
24
- component: EnvVarsModal,
25
- singleton: true,
26
- },
27
- ];
28
-
29
- export function DevToolsHost() {
21
+ import { FloatingDevTools } from "@buoy-gg/core";
22
+
23
+ export default function App() {
30
24
  return (
31
- <AppHostProvider>
32
- <FloatingMenu apps={APPS} />
33
- </AppHostProvider>
25
+ <>
26
+ {/* Your app */}
27
+ <FloatingDevTools />
28
+ </>
34
29
  );
35
30
  }
36
31
  ```
37
32
 
38
- ## Key Exports
39
- - `AppHostProvider` – Context provider that manages installed dev tools.
40
- - `FloatingMenu` – Cyberpunk-inspired floating entrypoint for tools.
41
- - `AppOverlay` & `useAppHost()` – Programmatic APIs for opening tools.
33
+ That's the whole setup — a floating dev menu appears inside your app. Install any tool package and it auto-appears in the menu; most need zero config, a few take one line (like passing `zustandStores` or calling `watchAtoms()`). Add a `licenseKey` prop to unlock [Pro](https://buoy.gg/pricing): production builds, the MCP server, and unlimited capture.
34
+
35
+ ## The 15 tools
36
+
37
+ | Tool | Package | What it does |
38
+ | --- | --- | --- |
39
+ | [Network](https://buoy.gg/buoy/latest/docs/tools/network) | `@buoy-gg/network` | Every request, response, timing & error — GraphQL operations extracted and named |
40
+ | [Storage](https://buoy.gg/buoy/latest/docs/tools/storage) | `@buoy-gg/storage` | Browse & edit AsyncStorage, MMKV & SecureStore with change history |
41
+ | [Env](https://buoy.gg/buoy/latest/docs/tools/env) | `@buoy-gg/env` | Auto-discovers `EXPO_PUBLIC_` vars, validates types, scores config health |
42
+ | [Query](https://buoy.gg/buoy/latest/docs/tools/react-query) | `@buoy-gg/react-query` | TanStack Query cache — refetch, invalidate, simulate loading & error states |
43
+ | [Routes](https://buoy.gg/buoy/latest/docs/tools/routes) | `@buoy-gg/route-events` | Navigation events, the live stack, and the full sitemap — jump to any screen |
44
+ | [Debug Borders](https://buoy.gg/buoy/latest/docs/tools/debug-borders) | `@buoy-gg/debug-borders` | Depth-colored layout borders — tap any label for testID, a11y props & styles |
45
+ | [Highlight Updates](https://buoy.gg/buoy/latest/docs/tools/highlight-updates) | `@buoy-gg/highlight-updates` | Every render, with the cause: mount, state, props, or parent |
46
+ | [Bench](https://buoy.gg/buoy/latest/docs/tools/perf-monitor) | `@buoy-gg/perf-monitor` | UI/JS FPS, CPU, memory & jank on a real device — batch benchmarks, ranked reports |
47
+ | [Events](https://buoy.gg/buoy/latest/docs/tools/events) | `@buoy-gg/events` | One timeline across every tool, with LLM-friendly export presets |
48
+ | [Console](https://buoy.gg/buoy/latest/docs/tools/console) | `@buoy-gg/console` | Chrome-DevTools console on device — read logs with no cable and no Metro |
49
+ | [Redux](https://buoy.gg/buoy/latest/docs/tools/redux) | `@buoy-gg/redux` | Inspect, dispatch & time travel — flags actions that blow the frame budget |
50
+ | [Zustand](https://buoy.gg/buoy/latest/docs/tools/zustand) | `@buoy-gg/zustand` | State, diffs, jump-to-state, one-tap reset — no middleware needed |
51
+ | [Jotai](https://buoy.gg/buoy/latest/docs/tools/jotai) | `@buoy-gg/jotai` | One `watchAtoms()` call — every event shows `prev → next` |
52
+ | [Impersonate](https://buoy.gg/buoy/latest/docs/tools/impersonate) | `@buoy-gg/impersonate` | Switch users, roles & flags without rebuilding |
53
+ | [Overlay](https://buoy.gg/buoy/latest/docs/tools/image-overlay) | `@buoy-gg/image-overlay` | Pin Figma mockups pixel-perfect over the app |
54
+
55
+ Grab everything in one line:
56
+
57
+ ```bash
58
+ npm i @buoy-gg/{core,network,storage,env,react-query,route-events,debug-borders,highlight-updates,perf-monitor,events,console,redux,zustand,jotai,impersonate,image-overlay}
59
+ ```
60
+
61
+ ## Three surfaces, one live session
62
+
63
+ Every tool runs inside your app's process. The phone, the desktop, and your agent all see the same session, live.
64
+
65
+ - **On the phone** — tap the floating menu. Works on any device, no cable, no desktop app, no Metro.
66
+ - **On your desktop** — [Buoy Desktop](https://github.com/Buoy-gg/Buoy-Desktop) (free) mirrors the tools to full-screen panels on macOS, Windows & Linux, with a live FPS / CPU / memory HUD and remote control of the device. Devices connect automatically — physical phones included, no URLs to configure.
67
+ - **Through your agent** — the [Buoy MCP server](https://buoy.gg/buoy/latest/docs/mcp) gives Claude Code, Cursor, or any MCP editor structured tool calls into the running app: read live state, tap real buttons, run benchmarks on a physical device.
68
+
69
+ ## Free vs Pro
70
+
71
+ Every tool is **free** — no key, no signup, no time limit.
72
+
73
+ **Pro** unlocks production builds, the MCP server, and unlimited capture. Activate with one prop:
74
+
75
+ ```tsx
76
+ <FloatingDevTools licenseKey="YOUR_LICENSE_KEY" />
77
+ ```
78
+
79
+ **Weekend Pass:** every Saturday and Sunday, every Pro feature unlocks free for everyone — built into the product, not a promo. Details at [buoy.gg/pricing](https://buoy.gg/pricing).
80
+
81
+ ## Safe in production
82
+
83
+ Everything runs in your app's process. The broker that mirrors the session to desktop and MCP binds to localhost only — nothing ever leaves your machine. You decide who sees the tools:
84
+
85
+ ```tsx
86
+ {isInternalUser && <FloatingDevTools />}
87
+ ```
88
+
89
+ That one line is the whole production story: internal builds and flagged users get the full toolkit; everyone else gets nothing.
90
+
91
+ Need the session with **no on-device UI at all** — field builds or devices handed to non-developers? Headless mode keeps every tool syncing to Buoy Desktop and MCP while rendering nothing on the device:
92
+
93
+ ```tsx
94
+ <FloatingDevTools headless />
95
+ ```
96
+
97
+ ## Links
98
+
99
+ - [Full documentation](https://buoy.gg/buoy/latest/docs/overview)
100
+ - [GitHub](https://github.com/Buoy-gg/buoy)
101
+ - [Buoy Desktop](https://github.com/Buoy-gg/Buoy-Desktop)
102
+ - [Pricing](https://buoy.gg/pricing)
103
+
104
+ ## License
42
105
 
43
- The menu hosts any `InstalledApp` descriptor. Combine it with other `@buoy/*` packages for a full suite of debug panels.
106
+ Proprietary software. © Buoy LLC. All rights reserved. See the [Terms of Service](https://buoy.gg/terms).
@@ -7,6 +7,7 @@ exports.useDevToolsSettings = exports.DevToolsSettingsModal = void 0;
7
7
  var _react = require("react");
8
8
  var _reactNative = require("react-native");
9
9
  var _settingsBus = require("./settingsBus.js");
10
+ var _useDesktopSyncStatus = require("./useDesktopSyncStatus.js");
10
11
  var _sharedUi = require("@buoy-gg/shared-ui");
11
12
  var _floatingToolsCore = require("@buoy-gg/floating-tools-core");
12
13
  var _DefaultConfigContext = require("./DefaultConfigContext.js");
@@ -158,6 +159,9 @@ const DevToolsSettingsModal = ({
158
159
  const [activeTabLoaded, setActiveTabLoaded] = (0, _react.useState)(false);
159
160
  const [expandedSettings, setExpandedSettings] = (0, _react.useState)(new Set());
160
161
  const [showLicenseModal, setShowLicenseModal] = (0, _react.useState)(false);
162
+ // null when external-sync isn't installed (or predates the status store);
163
+ // "idle" until AutoExternalSync initializes — the row hides in both cases.
164
+ const desktopSyncStatus = (0, _useDesktopSyncStatus.useDesktopSyncStatus)();
161
165
 
162
166
  // Load persisted active tab on mount
163
167
  (0, _react.useEffect)(() => {
@@ -808,6 +812,66 @@ const DevToolsSettingsModal = ({
808
812
  children: clearSuccess ? "CLEARED" : isClearing ? "CLEARING..." : "CLEAR ALL SETTINGS"
809
813
  })]
810
814
  })]
815
+ }), desktopSyncStatus && desktopSyncStatus.state !== "idle" && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
816
+ style: [styles.storageStatusCard, {
817
+ borderColor: desktopSyncStatus.state === "connected" ? _sharedUi.buoyColors.success + "60" : _sharedUi.buoyColors.warning + "60"
818
+ }],
819
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
820
+ style: styles.storageStatusHeader,
821
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
822
+ style: [styles.storageStatusIcon, {
823
+ backgroundColor: desktopSyncStatus.state === "connected" ? _sharedUi.buoyColors.success + "15" : _sharedUi.buoyColors.warning + "15"
824
+ }],
825
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Globe, {
826
+ size: 18,
827
+ color: desktopSyncStatus.state === "connected" ? _sharedUi.buoyColors.success : _sharedUi.buoyColors.warning
828
+ })
829
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
830
+ style: styles.storageStatusInfo,
831
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
832
+ style: styles.storageStatusLabel,
833
+ children: "DESKTOP SYNC"
834
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
835
+ style: styles.storageStatusBadgeContainer,
836
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
837
+ style: [styles.storageStatusBadge, {
838
+ backgroundColor: desktopSyncStatus.state === "connected" ? _sharedUi.buoyColors.success + "20" : _sharedUi.buoyColors.warning + "20",
839
+ borderColor: desktopSyncStatus.state === "connected" ? _sharedUi.buoyColors.success + "60" : _sharedUi.buoyColors.warning + "60"
840
+ }],
841
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
842
+ style: [styles.storageStatusBadgeText, {
843
+ color: desktopSyncStatus.state === "connected" ? _sharedUi.buoyColors.success : _sharedUi.buoyColors.warning
844
+ }],
845
+ children: desktopSyncStatus.state === "connected" ? "CONNECTED" : desktopSyncStatus.state === "connecting" ? "CONNECTING..." : "RETRYING"
846
+ })
847
+ }), desktopSyncStatus.state === "connected" ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.CheckCircle2, {
848
+ size: 14,
849
+ color: _sharedUi.buoyColors.success
850
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.AlertTriangle, {
851
+ size: 14,
852
+ color: _sharedUi.buoyColors.warning
853
+ })]
854
+ })]
855
+ })]
856
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
857
+ style: styles.storageStatusDescription,
858
+ children: [desktopSyncStatus.state === "connected" ? "Streaming tool data to Buoy Desktop at " : "Trying to reach Buoy Desktop at ", /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
859
+ style: styles.adviceHintCode,
860
+ children: desktopSyncStatus.targetURL ?? "(unknown URL)"
861
+ }), desktopSyncStatus.state !== "connected" && " — is the desktop app running? Override with the externalSync socketURL option if it runs elsewhere."]
862
+ }), desktopSyncStatus.state === "retrying" && desktopSyncStatus.lastError && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
863
+ style: styles.adviceHint,
864
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.AlertTriangle, {
865
+ size: 14,
866
+ color: _sharedUi.buoyColors.warning
867
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
868
+ style: styles.adviceHintText,
869
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
870
+ style: styles.adviceHintBold,
871
+ children: "Last error:"
872
+ }), " ", desktopSyncStatus.lastError]
873
+ })]
874
+ })]
811
875
  }), renderGlobalSettingCard("enableSharedModalDimensions", "SHARED MODAL SIZE", "MODAL", "Sync dimensions across all tools", "When enabled, all tool modals will share the same size and position. Resizing one modal will affect all others. When disabled, each tool remembers its own size and position independently.", "Keep OFF for the best experience. This allows you to customize each tool's modal size separately. Enable only if you prefer uniform modal sizes across all dev tools."), _reactNative.Platform.OS !== "web" && renderGlobalSettingCard("expandableWindowControls", "EXPAND CONTROLS", "MODAL", "iPad-style expandable window buttons", "When enabled, the window control buttons (minimize, toggle mode, close) start as small dots and expand into larger, easy-to-tap buttons when pressed — similar to iPad window controls. When disabled, buttons are directly tappable at their small size.", "Keep ON for touch devices where the small buttons are hard to press. Turn OFF if you prefer direct single-tap access (e.g. when using a mouse or simulator)."), isDevelopmentMode && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
812
876
  style: styles.exportConfigCard,
813
877
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
@@ -10,6 +10,7 @@ var _AppHost = require("./AppHost.js");
10
10
  var _FloatingMenu = require("./FloatingMenu.js");
11
11
  var _autoDiscoverPresets = require("./autoDiscoverPresets.js");
12
12
  var _DevToolsVisibilityContext = require("./DevToolsVisibilityContext.js");
13
+ var _optionalRequire = require("./optionalRequire.js");
13
14
  var _sharedUi = require("@buoy-gg/shared-ui");
14
15
  var _MinimizedToolsContext = require("./MinimizedToolsContext.js");
15
16
  var _defaultConfig = require("./defaultConfig.js");
@@ -115,6 +116,7 @@ const FloatingDevTools = ({
115
116
  zustandStores,
116
117
  environment,
117
118
  externalSync = true,
119
+ headless = false,
118
120
  ...props
119
121
  }) => {
120
122
  const resolvedEnvironment = environment ?? (0, _sharedUi.normalizeEnvironment)(process.env.NODE_ENV ?? "dev");
@@ -167,8 +169,8 @@ const FloatingDevTools = ({
167
169
  watchStores
168
170
  } = require("@buoy-gg/zustand");
169
171
  return watchStores(zustandStores);
170
- } catch {
171
- // @buoy-gg/zustand not installed
172
+ } catch (e) {
173
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/zustand", e);
172
174
  }
173
175
  }, []); // eslint-disable-line react-hooks/exhaustive-deps
174
176
 
@@ -196,7 +198,7 @@ const FloatingDevTools = ({
196
198
  requiredEnvVars: createEnvVarConfig(requiredEnvVars)
197
199
  }));
198
200
  } catch (error) {
199
- // Package not installed, skip
201
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/env", error);
200
202
  }
201
203
  }
202
204
 
@@ -210,7 +212,7 @@ const FloatingDevTools = ({
210
212
  requiredStorageKeys
211
213
  }));
212
214
  } catch (error) {
213
- // Package not installed, skip
215
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/storage", error);
214
216
  }
215
217
  }
216
218
  return overrides;
@@ -234,7 +236,8 @@ const FloatingDevTools = ({
234
236
  DebugBordersStandaloneOverlay
235
237
  } = require("@buoy-gg/debug-borders");
236
238
  return DebugBordersStandaloneOverlay;
237
- } catch {
239
+ } catch (e) {
240
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/debug-borders", e);
238
241
  return null;
239
242
  }
240
243
  }, []);
@@ -247,7 +250,8 @@ const FloatingDevTools = ({
247
250
  HighlightUpdatesOverlay: Overlay
248
251
  } = require("@buoy-gg/highlight-updates");
249
252
  return Overlay;
250
- } catch {
253
+ } catch (e) {
254
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/highlight-updates", e);
251
255
  return null;
252
256
  }
253
257
  }, []);
@@ -260,7 +264,8 @@ const FloatingDevTools = ({
260
264
  ImageOverlayStandalone
261
265
  } = require("@buoy-gg/image-overlay");
262
266
  return ImageOverlayStandalone;
263
- } catch {
267
+ } catch (e) {
268
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/image-overlay", e);
264
269
  return null;
265
270
  }
266
271
  }, []);
@@ -273,7 +278,8 @@ const FloatingDevTools = ({
273
278
  PerfMonitorOverlay: Overlay
274
279
  } = require("@buoy-gg/perf-monitor");
275
280
  return Overlay;
276
- } catch {
281
+ } catch (e) {
282
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/perf-monitor", e);
277
283
  return null;
278
284
  }
279
285
  }, []);
@@ -287,7 +293,8 @@ const FloatingDevTools = ({
287
293
  RouteTracker: Tracker
288
294
  } = require("@buoy-gg/route-events");
289
295
  return Tracker;
290
- } catch {
296
+ } catch (e) {
297
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/route-events", e);
291
298
  return null;
292
299
  }
293
300
  }, []);
@@ -300,7 +307,8 @@ const FloatingDevTools = ({
300
307
  ImpersonateOverlay: Overlay
301
308
  } = require("@buoy-gg/impersonate");
302
309
  return Overlay;
303
- } catch {
310
+ } catch (e) {
311
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/impersonate", e);
304
312
  return null;
305
313
  }
306
314
  }, []);
@@ -326,6 +334,22 @@ const FloatingDevTools = ({
326
334
  return null;
327
335
  }
328
336
 
337
+ // Headless (sync-only): tool adapters + route tracking + desktop sync, no
338
+ // on-device UI. requireLicense is intentionally skipped — there's nothing
339
+ // to gate. All hooks above already ran, so hook order is preserved, and
340
+ // AutoExternalSync is additionally dev-gated (the broker must never be
341
+ // contacted in production).
342
+ if (headless) {
343
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
344
+ children: [RouteTracker && /*#__PURE__*/(0, _jsxRuntime.jsx)(RouteTracker, {}), isDevelopment && externalSync !== false && (0, _autoExternalSync.isExternalSyncAvailable)() && /*#__PURE__*/(0, _jsxRuntime.jsx)(_autoExternalSync.AutoExternalSync, {
345
+ options: typeof externalSync === "object" ? externalSync : undefined,
346
+ requiredEnvVars: requiredEnvVars,
347
+ isPro: isLicensed,
348
+ licenseKey: licenseKey
349
+ })]
350
+ });
351
+ }
352
+
329
353
  // Gate: If requireLicense is enabled and no license, show only the license modal
330
354
  if (requireLicense && isInitialized && !licenseKey) {
331
355
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.LicenseEntryModal, {
@@ -57,7 +57,8 @@ function FloatingDevTools({
57
57
  availableEnvironments: _availableEnvironments,
58
58
  onEnvironmentSwitch: _onEnvironmentSwitch,
59
59
  apps,
60
- disableHints: _disableHints
60
+ disableHints: _disableHints,
61
+ headless = false
61
62
  }) {
62
63
  // Internal state for dial and settings
63
64
  const [isDialOpen, setIsDialOpen] = (0, _react.useState)(false);
@@ -135,6 +136,11 @@ function FloatingDevTools({
135
136
  setIsSettingsOpen(true);
136
137
  }, []);
137
138
  const handleCloseSettings = (0, _react.useCallback)(() => setIsSettingsOpen(false), []);
139
+
140
+ // Headless: desktop sync is native-only, so there is nothing to mount on
141
+ // web — accept the prop for API parity and render nothing. (After all
142
+ // hooks so hook order is preserved.)
143
+ if (headless) return null;
138
144
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
139
145
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_floatingToolsWeb.FloatingTools, {
140
146
  enablePositionPersistence: true,
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.autoDiscoverPresets = autoDiscoverPresets;
7
7
  exports.autoDiscoverPresetsWithCustom = autoDiscoverPresetsWithCustom;
8
+ var _optionalRequire = require("./optionalRequire.js");
8
9
  /**
9
10
  * Automatically discovers and loads dev tool presets from installed packages.
10
11
  *
@@ -68,7 +69,8 @@ function autoDiscoverPresets() {
68
69
  envToolPreset
69
70
  } = require("@buoy-gg/env");
70
71
  return envToolPreset;
71
- } catch {
72
+ } catch (e) {
73
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/env", e);
72
74
  return null;
73
75
  }
74
76
  }
@@ -83,7 +85,8 @@ function autoDiscoverPresets() {
83
85
  networkToolPreset
84
86
  } = require("@buoy-gg/network");
85
87
  return networkToolPreset;
86
- } catch {
88
+ } catch (e) {
89
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/network", e);
87
90
  return null;
88
91
  }
89
92
  }
@@ -98,7 +101,8 @@ function autoDiscoverPresets() {
98
101
  storageToolPreset
99
102
  } = require("@buoy-gg/storage");
100
103
  return storageToolPreset;
101
- } catch {
104
+ } catch (e) {
105
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/storage", e);
102
106
  return null;
103
107
  }
104
108
  }
@@ -114,7 +118,8 @@ function autoDiscoverPresets() {
114
118
  wifiTogglePreset
115
119
  } = require("@buoy-gg/react-query");
116
120
  return [reactQueryToolPreset, wifiTogglePreset];
117
- } catch {
121
+ } catch (e) {
122
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/react-query", e);
118
123
  return null;
119
124
  }
120
125
  }
@@ -129,7 +134,8 @@ function autoDiscoverPresets() {
129
134
  routeEventsToolPreset
130
135
  } = require("@buoy-gg/route-events");
131
136
  return routeEventsToolPreset;
132
- } catch {
137
+ } catch (e) {
138
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/route-events", e);
133
139
  return null;
134
140
  }
135
141
  }
@@ -144,7 +150,8 @@ function autoDiscoverPresets() {
144
150
  debugBordersToolPreset
145
151
  } = require("@buoy-gg/debug-borders");
146
152
  return debugBordersToolPreset;
147
- } catch {
153
+ } catch (e) {
154
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/debug-borders", e);
148
155
  return null;
149
156
  }
150
157
  }
@@ -160,7 +167,8 @@ function autoDiscoverPresets() {
160
167
  highlightUpdatesModalPreset
161
168
  } = require("@buoy-gg/highlight-updates");
162
169
  return [highlightUpdatesModalPreset, highlightUpdatesPreset];
163
- } catch {
170
+ } catch (e) {
171
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/highlight-updates", e);
164
172
  return null;
165
173
  }
166
174
  }
@@ -175,7 +183,8 @@ function autoDiscoverPresets() {
175
183
  reduxToolPreset
176
184
  } = require("@buoy-gg/redux");
177
185
  return reduxToolPreset;
178
- } catch {
186
+ } catch (e) {
187
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/redux", e);
179
188
  return null;
180
189
  }
181
190
  }
@@ -190,7 +199,8 @@ function autoDiscoverPresets() {
190
199
  zustandToolPreset
191
200
  } = require("@buoy-gg/zustand");
192
201
  return zustandToolPreset;
193
- } catch {
202
+ } catch (e) {
203
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/zustand", e);
194
204
  return null;
195
205
  }
196
206
  }
@@ -205,7 +215,8 @@ function autoDiscoverPresets() {
205
215
  eventsToolPreset
206
216
  } = require("@buoy-gg/events");
207
217
  return eventsToolPreset;
208
- } catch {
218
+ } catch (e) {
219
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/events", e);
209
220
  return null;
210
221
  }
211
222
  }
@@ -220,7 +231,8 @@ function autoDiscoverPresets() {
220
231
  jotaiToolPreset
221
232
  } = require("@buoy-gg/jotai");
222
233
  return jotaiToolPreset;
223
- } catch {
234
+ } catch (e) {
235
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/jotai", e);
224
236
  return null;
225
237
  }
226
238
  }
@@ -235,7 +247,8 @@ function autoDiscoverPresets() {
235
247
  imageOverlayToolPreset
236
248
  } = require("@buoy-gg/image-overlay");
237
249
  return imageOverlayToolPreset;
238
- } catch {
250
+ } catch (e) {
251
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/image-overlay", e);
239
252
  return null;
240
253
  }
241
254
  }
@@ -251,7 +264,8 @@ function autoDiscoverPresets() {
251
264
  perfMonitorModalPreset
252
265
  } = require("@buoy-gg/perf-monitor");
253
266
  return [perfMonitorPreset, perfMonitorModalPreset];
254
- } catch {
267
+ } catch (e) {
268
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/perf-monitor", e);
255
269
  return null;
256
270
  }
257
271
  }
@@ -272,8 +286,9 @@ function autoDiscoverPresets() {
272
286
  }
273
287
  }
274
288
  } catch (error) {
275
- // Silently skip packages that aren't installed
276
- // This is expected behavior - not all packages will be installed
289
+ // Loaders swallow their own require failures; anything reaching here
290
+ // is a preset-shape problem in an installed package — surface it.
291
+ (0, _optionalRequire.reportOptionalRequireError)(name, error);
277
292
  }
278
293
  }
279
294
  return discoveredPresets;
@@ -7,12 +7,14 @@ exports.AutoExternalSync = AutoExternalSync;
7
7
  exports.isExternalSyncAvailable = isExternalSyncAvailable;
8
8
  var _react = require("react");
9
9
  var _reactNative = require("react-native");
10
+ var _optionalRequire = require("./optionalRequire.js");
10
11
  /**
11
12
  * Zero-config device→desktop sync for FloatingDevTools.
12
13
  *
13
14
  * When @buoy-gg/external-sync is installed, FloatingDevTools renders
14
15
  * <AutoExternalSync /> in dev builds: it connects to the Buoy Desktop broker
15
- * (localhost:42831 — a silent no-op when the desktop app isn't running) and
16
+ * (port 42831 on the Metro host, so physical devices reach the dev machine
17
+ * automatically — a background retry when the desktop app isn't running) and
16
18
  * registers a sync adapter for every installed tool package, discovered with
17
19
  * the same guarded-require pattern as autoDiscoverPresets. No <DevToolsSync>
18
20
  * wiring needed in the app.
@@ -47,85 +49,90 @@ let debugBordersModule = null;
47
49
  let impersonateModule = null;
48
50
  let perfMonitorModule = null;
49
51
  let expoConstantsModule = null;
52
+
53
+ // Catch bodies report through reportOptionalRequireError: silent for a
54
+ // genuinely absent package, one dev warning when an installed package fails
55
+ // to load (broken transitive resolve, top-level crash).
50
56
  try {
51
57
  externalSyncModule = require("@buoy-gg/external-sync");
52
- } catch {
53
- // not installed
58
+ } catch (e) {
59
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/external-sync", e);
54
60
  }
55
61
  try {
56
62
  storageModule = require("@buoy-gg/storage");
57
- } catch {
58
- // not installed
63
+ } catch (e) {
64
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/storage", e);
59
65
  }
60
66
  try {
61
67
  networkModule = require("@buoy-gg/network");
62
- } catch {
63
- // not installed
68
+ } catch (e) {
69
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/network", e);
64
70
  }
65
71
  try {
66
72
  reactQueryModule = require("@buoy-gg/react-query");
67
- } catch {
68
- // not installed
73
+ } catch (e) {
74
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/react-query", e);
69
75
  }
70
76
  try {
71
77
  routeEventsModule = require("@buoy-gg/route-events");
72
- } catch {
73
- // not installed
78
+ } catch (e) {
79
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/route-events", e);
74
80
  }
75
81
  try {
76
82
  reduxModule = require("@buoy-gg/redux");
77
- } catch {
78
- // not installed
83
+ } catch (e) {
84
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/redux", e);
79
85
  }
80
86
  try {
81
87
  zustandModule = require("@buoy-gg/zustand");
82
- } catch {
83
- // not installed
88
+ } catch (e) {
89
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/zustand", e);
84
90
  }
85
91
  try {
86
92
  jotaiModule = require("@buoy-gg/jotai");
87
- } catch {
88
- // not installed
93
+ } catch (e) {
94
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/jotai", e);
89
95
  }
90
96
  try {
91
97
  eventsModule = require("@buoy-gg/events");
92
- } catch {
93
- // not installed
98
+ } catch (e) {
99
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/events", e);
94
100
  }
95
101
  try {
96
102
  consoleModule = require("@buoy-gg/console");
97
- } catch {
98
- // not installed
103
+ } catch (e) {
104
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/console", e);
99
105
  }
100
106
  try {
101
107
  envModule = require("@buoy-gg/env");
102
- } catch {
103
- // not installed
108
+ } catch (e) {
109
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/env", e);
104
110
  }
105
111
  try {
106
112
  highlightUpdatesModule = require("@buoy-gg/highlight-updates");
107
- } catch {
108
- // not installed
113
+ } catch (e) {
114
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/highlight-updates", e);
109
115
  }
110
116
  try {
111
117
  debugBordersModule = require("@buoy-gg/debug-borders");
112
- } catch {
113
- // not installed
118
+ } catch (e) {
119
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/debug-borders", e);
114
120
  }
115
121
  try {
116
122
  impersonateModule = require("@buoy-gg/impersonate");
117
- } catch {
118
- // not installed
123
+ } catch (e) {
124
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/impersonate", e);
119
125
  }
120
126
  try {
121
127
  perfMonitorModule = require("@buoy-gg/perf-monitor");
122
- } catch {
123
- // not installed
128
+ } catch (e) {
129
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/perf-monitor", e);
124
130
  }
125
131
  try {
126
132
  expoConstantsModule = require("expo-constants");
127
- } catch {
128
- // not installed (RN CLI apps)
133
+ } catch (e) {
134
+ // not installed (RN CLI apps) — still report real load failures
135
+ (0, _optionalRequire.reportOptionalRequireError)("expo-constants", e);
129
136
  }
130
137
 
131
138
  /** Whether zero-config sync can run (the external-sync package is installed). */
@@ -165,7 +172,9 @@ function AutoExternalSync({
165
172
  socket
166
173
  } = useExternalSyncSocket({
167
174
  deviceName: options?.deviceName ?? identity.deviceName,
168
- socketURL: options?.socketURL ?? "http://localhost:42831",
175
+ // Older external-sync versions predate getDefaultSocketURL and require
176
+ // an explicit URL — keep the literal fallback for them.
177
+ socketURL: options?.socketURL ?? externalSyncModule.getDefaultSocketURL?.() ?? "http://localhost:42831",
169
178
  persistentDeviceId: deviceId,
170
179
  platform: _reactNative.Platform.OS,
171
180
  enableLogs: options?.enableLogs