@buoy-gg/core 6.0.1 → 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 +7 -1
  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 +18 -7
package/README.md CHANGED
@@ -63,7 +63,7 @@ npm i @buoy-gg/{core,network,storage,env,react-query,route-events,debug-borders,
63
63
  Every tool runs inside your app's process. The phone, the desktop, and your agent all see the same session, live.
64
64
 
65
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.
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
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
68
 
69
69
  ## Free vs Pro
@@ -88,6 +88,12 @@ Everything runs in your app's process. The broker that mirrors the session to de
88
88
 
89
89
  That one line is the whole production story: internal builds and flagged users get the full toolkit; everyone else gets nothing.
90
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
+
91
97
  ## Links
92
98
 
93
99
  - [Full documentation](https://buoy.gg/buoy/latest/docs/overview)
@@ -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
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.reportOptionalRequireError = reportOptionalRequireError;
7
+ const isDev = typeof __DEV__ !== "undefined" && __DEV__;
8
+ const reported = new Set();
9
+ const MODULE_NOT_FOUND_RE = /could not be found within the project|Cannot find module|Unable to resolve module/i;
10
+
11
+ /**
12
+ * Call from the CATCH block of a guarded optional require (the require
13
+ * literal itself must stay lexically inside try{} for Metro to treat the
14
+ * dependency as optional — this helper only inspects the failure).
15
+ *
16
+ * Silent when the package simply isn't installed. Warns once per module
17
+ * (dev only) when the package IS present but threw during import — a broken
18
+ * transitive resolve (subpath exports, version skew) or a top-level crash —
19
+ * which otherwise fails silently and makes tools vanish with no clue.
20
+ */
21
+ function reportOptionalRequireError(moduleName, err) {
22
+ if (!isDev) return;
23
+ const message = err instanceof Error ? err.message : String(err);
24
+ const code = err?.code;
25
+ const isNotFound = code === "MODULE_NOT_FOUND" || MODULE_NOT_FOUND_RE.test(message);
26
+ // "Not found" naming the module itself = expected (not installed).
27
+ // "Not found" naming a DIFFERENT module = broken transitive dep inside an
28
+ // installed package — a real error worth surfacing.
29
+ if (isNotFound && message.includes(moduleName)) return;
30
+ if (reported.has(moduleName)) return;
31
+ reported.add(moduleName);
32
+ console.warn(`[buoy] "${moduleName}" is installed but failed to load — its tools won't appear. ` + `Underlying error: ${message}`);
33
+ }
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.useDesktopSyncStatus = useDesktopSyncStatus;
7
+ var _react = require("react");
8
+ var _optionalRequire = require("./optionalRequire.js");
9
+ /* eslint-disable @typescript-eslint/no-var-requires */
10
+
11
+ // Guarded module-level require (same Metro constraint as autoExternalSync:
12
+ // the require literal must sit lexically inside try{} to stay optional).
13
+ let syncModule = null;
14
+ try {
15
+ syncModule = require("@buoy-gg/external-sync");
16
+ } catch (e) {
17
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/external-sync", e);
18
+ }
19
+ const noopSubscribe = () => () => {};
20
+
21
+ /**
22
+ * Live desktop-sync status for the settings modal. Returns null when
23
+ * external-sync isn't installed or predates the sync-status store — the
24
+ * caller hides the status row in that case.
25
+ */
26
+ function useDesktopSyncStatus() {
27
+ return (0, _react.useSyncExternalStore)(syncModule?.subscribeSyncStatus ?? noopSubscribe, () => syncModule?.getSyncStatus?.() ?? null);
28
+ }