@buoy-gg/core 7.0.35 → 7.0.38

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 (111) hide show
  1. package/README.md +57 -66
  2. package/bin/buoy.js +71 -20
  3. package/lib/commonjs/Buoy.js +3 -16
  4. package/lib/commonjs/floatingMenu/AppHost.js +1 -1
  5. package/lib/commonjs/floatingMenu/AppHost.web.js +1 -1
  6. package/lib/commonjs/floatingMenu/AppIcon.web.js +53 -0
  7. package/lib/commonjs/floatingMenu/DevToolsSettingsModal.js +12 -5
  8. package/lib/commonjs/floatingMenu/DevToolsSettingsModal.web.js +14 -34
  9. package/lib/commonjs/floatingMenu/FloatingDevTools.js +32 -48
  10. package/lib/commonjs/floatingMenu/FloatingDevTools.web.js +24 -4
  11. package/lib/commonjs/floatingMenu/FloatingMenu.js +1 -1
  12. package/lib/commonjs/floatingMenu/MinimizedToolsStack.js +83 -4
  13. package/lib/commonjs/floatingMenu/MinimizedToolsStack.web.js +90 -6
  14. package/lib/commonjs/floatingMenu/ToolHost.js +1 -0
  15. package/lib/commonjs/floatingMenu/autoExternalSync.js +3 -2
  16. package/lib/commonjs/floatingMenu/dial/DialDevTools.js +43 -10
  17. package/lib/commonjs/floatingMenu/dial/DialDevTools.web.js +18 -5
  18. package/lib/commonjs/floatingMenu/dial/dialUsageStore.js +13 -17
  19. package/lib/commonjs/floatingMenu/dial/onboardingConstants.js +37 -65
  20. package/lib/commonjs/floatingMenu/dial/onboardingConstants.web.js +37 -29
  21. package/lib/commonjs/floatingMenu/floatingTools.js +23 -889
  22. package/lib/commonjs/floatingMenu/floatingTools.web.js +24 -514
  23. package/lib/commonjs/floatingMenu/optionalRequire.js +4 -0
  24. package/lib/commonjs/telemetry/installPing.js +7 -0
  25. package/lib/commonjs/version.js +1 -1
  26. package/lib/module/Buoy.js +3 -16
  27. package/lib/module/floatingMenu/AppHost.js +1 -1
  28. package/lib/module/floatingMenu/AppHost.web.js +1 -1
  29. package/lib/module/floatingMenu/AppIcon.web.js +47 -0
  30. package/lib/module/floatingMenu/DevToolsSettingsModal.js +13 -6
  31. package/lib/module/floatingMenu/DevToolsSettingsModal.web.js +15 -35
  32. package/lib/module/floatingMenu/FloatingDevTools.js +35 -49
  33. package/lib/module/floatingMenu/FloatingDevTools.web.js +24 -3
  34. package/lib/module/floatingMenu/FloatingMenu.js +1 -1
  35. package/lib/module/floatingMenu/MinimizedToolsStack.js +88 -8
  36. package/lib/module/floatingMenu/MinimizedToolsStack.web.js +92 -8
  37. package/lib/module/floatingMenu/ToolHost.js +1 -0
  38. package/lib/module/floatingMenu/autoExternalSync.js +3 -2
  39. package/lib/module/floatingMenu/dial/DialDevTools.js +43 -10
  40. package/lib/module/floatingMenu/dial/DialDevTools.web.js +18 -5
  41. package/lib/module/floatingMenu/dial/dialUsageStore.js +14 -18
  42. package/lib/module/floatingMenu/dial/onboardingConstants.js +1 -62
  43. package/lib/module/floatingMenu/dial/onboardingConstants.web.js +1 -25
  44. package/lib/module/floatingMenu/floatingTools.js +15 -890
  45. package/lib/module/floatingMenu/floatingTools.web.js +1 -517
  46. package/lib/module/floatingMenu/optionalRequire.js +4 -0
  47. package/lib/module/telemetry/installPing.js +7 -0
  48. package/lib/module/version.js +1 -1
  49. package/lib/typescript/commonjs/Buoy.d.ts +14 -3
  50. package/lib/typescript/commonjs/Buoy.d.ts.map +1 -1
  51. package/lib/typescript/commonjs/floatingMenu/AppIcon.web.d.ts +14 -0
  52. package/lib/typescript/commonjs/floatingMenu/AppIcon.web.d.ts.map +1 -0
  53. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -1
  54. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.web.d.ts.map +1 -1
  55. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.d.ts +4 -14
  56. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.d.ts.map +1 -1
  57. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.web.d.ts +4 -19
  58. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.web.d.ts.map +1 -1
  59. package/lib/typescript/commonjs/floatingMenu/MinimizedToolsStack.d.ts.map +1 -1
  60. package/lib/typescript/commonjs/floatingMenu/MinimizedToolsStack.web.d.ts.map +1 -1
  61. package/lib/typescript/commonjs/floatingMenu/ToolHost.d.ts.map +1 -1
  62. package/lib/typescript/commonjs/floatingMenu/autoExternalSync.d.ts +3 -4
  63. package/lib/typescript/commonjs/floatingMenu/autoExternalSync.d.ts.map +1 -1
  64. package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.d.ts.map +1 -1
  65. package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.web.d.ts.map +1 -1
  66. package/lib/typescript/commonjs/floatingMenu/dial/dialUsageStore.d.ts +3 -15
  67. package/lib/typescript/commonjs/floatingMenu/dial/dialUsageStore.d.ts.map +1 -1
  68. package/lib/typescript/commonjs/floatingMenu/dial/onboardingConstants.d.ts +1 -29
  69. package/lib/typescript/commonjs/floatingMenu/dial/onboardingConstants.d.ts.map +1 -1
  70. package/lib/typescript/commonjs/floatingMenu/dial/onboardingConstants.web.d.ts +1 -12
  71. package/lib/typescript/commonjs/floatingMenu/dial/onboardingConstants.web.d.ts.map +1 -1
  72. package/lib/typescript/commonjs/floatingMenu/floatingTools.d.ts +6 -55
  73. package/lib/typescript/commonjs/floatingMenu/floatingTools.d.ts.map +1 -1
  74. package/lib/typescript/commonjs/floatingMenu/floatingTools.web.d.ts +3 -53
  75. package/lib/typescript/commonjs/floatingMenu/floatingTools.web.d.ts.map +1 -1
  76. package/lib/typescript/commonjs/floatingMenu/optionalRequire.d.ts.map +1 -1
  77. package/lib/typescript/commonjs/telemetry/installPing.d.ts +8 -1
  78. package/lib/typescript/commonjs/telemetry/installPing.d.ts.map +1 -1
  79. package/lib/typescript/commonjs/version.d.ts +1 -1
  80. package/lib/typescript/module/Buoy.d.ts +14 -3
  81. package/lib/typescript/module/Buoy.d.ts.map +1 -1
  82. package/lib/typescript/module/floatingMenu/AppIcon.web.d.ts +14 -0
  83. package/lib/typescript/module/floatingMenu/AppIcon.web.d.ts.map +1 -0
  84. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -1
  85. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.web.d.ts.map +1 -1
  86. package/lib/typescript/module/floatingMenu/FloatingDevTools.d.ts +4 -14
  87. package/lib/typescript/module/floatingMenu/FloatingDevTools.d.ts.map +1 -1
  88. package/lib/typescript/module/floatingMenu/FloatingDevTools.web.d.ts +4 -19
  89. package/lib/typescript/module/floatingMenu/FloatingDevTools.web.d.ts.map +1 -1
  90. package/lib/typescript/module/floatingMenu/MinimizedToolsStack.d.ts.map +1 -1
  91. package/lib/typescript/module/floatingMenu/MinimizedToolsStack.web.d.ts.map +1 -1
  92. package/lib/typescript/module/floatingMenu/ToolHost.d.ts.map +1 -1
  93. package/lib/typescript/module/floatingMenu/autoExternalSync.d.ts +3 -4
  94. package/lib/typescript/module/floatingMenu/autoExternalSync.d.ts.map +1 -1
  95. package/lib/typescript/module/floatingMenu/dial/DialDevTools.d.ts.map +1 -1
  96. package/lib/typescript/module/floatingMenu/dial/DialDevTools.web.d.ts.map +1 -1
  97. package/lib/typescript/module/floatingMenu/dial/dialUsageStore.d.ts +3 -15
  98. package/lib/typescript/module/floatingMenu/dial/dialUsageStore.d.ts.map +1 -1
  99. package/lib/typescript/module/floatingMenu/dial/onboardingConstants.d.ts +1 -29
  100. package/lib/typescript/module/floatingMenu/dial/onboardingConstants.d.ts.map +1 -1
  101. package/lib/typescript/module/floatingMenu/dial/onboardingConstants.web.d.ts +1 -12
  102. package/lib/typescript/module/floatingMenu/dial/onboardingConstants.web.d.ts.map +1 -1
  103. package/lib/typescript/module/floatingMenu/floatingTools.d.ts +6 -55
  104. package/lib/typescript/module/floatingMenu/floatingTools.d.ts.map +1 -1
  105. package/lib/typescript/module/floatingMenu/floatingTools.web.d.ts +3 -53
  106. package/lib/typescript/module/floatingMenu/floatingTools.web.d.ts.map +1 -1
  107. package/lib/typescript/module/floatingMenu/optionalRequire.d.ts.map +1 -1
  108. package/lib/typescript/module/telemetry/installPing.d.ts +8 -1
  109. package/lib/typescript/module/telemetry/installPing.d.ts.map +1 -1
  110. package/lib/typescript/module/version.d.ts +1 -1
  111. package/package.json +6 -6
package/README.md CHANGED
@@ -3,106 +3,97 @@
3
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
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)
5
5
 
6
- **Devtools that live in your app. And answer to your agent.**
6
+ Buoy adds a floating developer-tools menu to your React Native app. Install the tools you need to inspect captured requests, app state, storage, and performance.
7
7
 
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.
8
+ ## Install and quick start
9
9
 
10
- ![Buoy demo — the floating dev menu in action](https://github.com/user-attachments/assets/a732d6a3-9963-49e3-b0f1-0d974a0a74d7)
11
-
12
- ## Install
10
+ Install from your app’s directory, then sign in:
13
11
 
14
12
  ```bash
15
- npm install @buoy-gg/core
13
+ npm install @buoy-gg/core @buoy-gg/network
14
+ npx --package=@buoy-gg/core buoy login
16
15
  ```
17
16
 
18
- ## Quick start
17
+ In an Expo Router project, keep your existing navigator and providers. For example:
19
18
 
20
19
  ```tsx
21
- import { FloatingDevTools } from "@buoy-gg/core";
20
+ import { Stack } from "expo-router";
21
+ import { Buoy, FloatingDevTools } from "@buoy-gg/core";
22
+
23
+ Buoy.init({ licenseKey: process.env.EXPO_PUBLIC_BUOY_KEY });
22
24
 
23
- export default function App() {
25
+ export default function RootLayout() {
24
26
  return (
25
27
  <>
26
- {/* Your app */}
28
+ <Stack />
27
29
  <FloatingDevTools />
28
30
  </>
29
31
  );
30
32
  }
31
33
  ```
32
34
 
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
35
+ The login command writes the Expo key to `.env.local`. Keep the navigator and menu inside your existing providers. Without Expo Router, keep your app’s root content in place of `Stack`. For React Native CLI, load the key through your app’s environment configuration and pass it to `Buoy.init`; `.env.local` is not loaded automatically.
62
36
 
63
- Every tool runs inside your app's process. The phone, the desktop, and your agent all see the same session, live.
37
+ Restart the development server after installation. Open Buoy, complete any account prompt, and select Network. Trigger a new HTTP request in your app and check its URL and status. If no request appears, check whether the app used cached data or an unsupported transport.
64
38
 
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.
39
+ See the [Quick Start](https://buoy.gg/buoy/latest/docs/quick-start) for the full setup and [Installation](https://buoy.gg/buoy/latest/docs/installation) for requirements. Other tools may need store registration, provider context, or native dependencies.
68
40
 
69
- ## Free vs Pro
41
+ The dial shows the most recently opened tool first. Tools you haven’t opened keep their registration order.
70
42
 
71
- Every tool is **free** on every tier — only how much history each keeps changes. A free key (`npx buoy login`, no card) restores the full free tier.
43
+ ## Tools
72
44
 
73
- **Pro** unlocks production builds, the MCP server, and unlimited capture. Activate with one prop:
45
+ | Tool | Package | What it does |
46
+ | --- | --- | --- |
47
+ | [Network](https://buoy.gg/buoy/latest/docs/tools/network) | `@buoy-gg/network` | Inspect HTTP requests captured through your React Native app’s global `fetch` and `XMLHttpRequest` hooks. Open a request to see its URL, status, headers, timing, and captured body. |
48
+ | [Storage](https://buoy.gg/buoy/latest/docs/tools/storage) | `@buoy-gg/storage` | Browse and edit supported AsyncStorage, registered MMKV instances, and registered SecureStore keys inside your app. |
49
+ | [Time Machine](https://buoy.gg/buoy/latest/docs/tools/time-machine) | `@buoy-gg/time-machine` | Save and restore supported client state; external side effects are not reversed. |
50
+ | [Ask Buoy](https://buoy.gg/buoy/latest/docs/tools/ask-buoy) | `@buoy-gg/ask-buoy` | Use an in-app assistant with your model endpoint and configured tools. |
51
+ | [Env](https://buoy.gg/buoy/latest/docs/tools/env) | `@buoy-gg/env` | Inspect runtime environment values and validate required variables. |
52
+ | [Query](https://buoy.gg/buoy/latest/docs/tools/react-query) | `@buoy-gg/react-query` | Inspect the TanStack Query cache used by your app. Refetch or invalidate a query and simulate states while checking the screen that consumes it. |
53
+ | [Routes](https://buoy.gg/buoy/latest/docs/tools/routes) | `@buoy-gg/route-events` | Inspect recorded navigation events and supported routes. |
54
+ | [Debug Borders](https://buoy.gg/buoy/latest/docs/tools/debug-borders) | `@buoy-gg/debug-borders` | Show layout borders and component labels over your React Native app. Tap a label to inspect the component’s available properties. |
55
+ | [Highlight Updates](https://buoy.gg/buoy/latest/docs/tools/highlight-updates) | `@buoy-gg/highlight-updates` | Highlight observed React component updates in development builds and inspect the available render details. |
56
+ | [Bench](https://buoy.gg/buoy/latest/docs/tools/perf-monitor) | `@buoy-gg/perf-monitor` | Measure available frame-rate, CPU, and memory metrics on a device. Record comparable runs to investigate performance changes. |
57
+ | [JS Top](https://buoy.gg/buoy/latest/docs/tools/js-top) | `@buoy-gg/js-top` | Task Manager for the JS thread — which timers, Promise chains & callbacks eat your JS FPS |
58
+ | [Images](https://buoy.gg/buoy/latest/docs/tools/images) | `@buoy-gg/images` | Inspect image loads observed by Buoy’s supported image hooks, including timing, dimensions, and estimated decoded memory. |
59
+ | [Assets](https://buoy.gg/buoy/latest/docs/tools/assets) | `@buoy-gg/assets` | Inspect assets visible to the runtime registry and, in development, the Metro dependency graph. Compare sizes and find possible duplicates. |
60
+ | [Events](https://buoy.gg/buoy/latest/docs/tools/events) | `@buoy-gg/events` | Review events from configured Buoy tools in one timeline. Filter by source, inspect an event, and export a selection for debugging. |
61
+ | [Console](https://buoy.gg/buoy/latest/docs/tools/console) | `@buoy-gg/console` | Read captured JavaScript console logs in your React Native app. Filter messages by level, search their contents, and inspect logged objects. |
62
+ | [Sentry](https://buoy.gg/buoy/latest/docs/tools/sentry) | `@buoy-gg/sentry` | Inspect captured Sentry envelopes and diagnostic estimates. |
63
+ | [Redux](https://buoy.gg/buoy/latest/docs/tools/redux) | `@buoy-gg/redux` | Inspect your connected Redux store, review captured actions and state changes, and dispatch actions from your device. |
64
+ | [Zustand](https://buoy.gg/buoy/latest/docs/tools/zustand) | `@buoy-gg/zustand` | Inspect the Zustand stores you register with Buoy. Review changes, edit state, and restore retained snapshots. |
65
+ | [Jotai](https://buoy.gg/buoy/latest/docs/tools/jotai) | `@buoy-gg/jotai` | Inspect registered Jotai atoms and their captured changes, with live values and per-atom history. |
66
+ | [Impersonate](https://buoy.gg/buoy/latest/docs/tools/impersonate) | `@buoy-gg/impersonate` | Connect your app’s user-switching flow to Buoy so testers can switch test accounts, roles, or flags without rebuilding. |
67
+ | [Overlay](https://buoy.gg/buoy/latest/docs/tools/image-overlay) | `@buoy-gg/image-overlay` | Place a reference image over your app to compare spacing, alignment, and sizing on the device. |
74
68
 
75
- ```tsx
76
- <FloatingDevTools licenseKey="YOUR_LICENSE_KEY" />
77
- ```
69
+ TV tools and the iOS Simulator camera have separate [platform requirements](https://buoy.gg/buoy/latest/docs/overview).
78
70
 
79
- **Weekend Pass:** every Saturday and Sunday, every Pro feature unlocks free for anyone holding a key — including a free one. Built into the product, not a promo. Details at [buoy.gg/pricing](https://buoy.gg/pricing).
71
+ ## Desktop and AI
80
72
 
81
- ## Safe in production
73
+ Inspect configured tools in the floating menu on your device. You can also connect them to:
82
74
 
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:
75
+ - [Buoy Desktop](https://github.com/Buoy-gg/Buoy-Desktop), for desktop panels and remote actions. React Native apps need `@buoy-gg/external-sync`; follow the [connection guide](https://buoy.gg/buoy/latest/docs/desktop) and sign in to Desktop separately.
76
+ - [MCP](https://buoy.gg/buoy/latest/docs/mcp), for an AI editor to inspect and act on the app. Configure both process and device accounts; data and action tools require Pro.
77
+ - [Ask Buoy](https://buoy.gg/buoy/latest/docs/tools/ask-buoy), for an in-app assistant. It requires Pro, a model endpoint you configure, and the tool integrations its tasks depend on.
84
78
 
85
- ```tsx
86
- {isInternalUser && <FloatingDevTools />}
87
- ```
79
+ Supported features vary by platform, build, and installed tools. [Flutter](https://buoy.gg/buoy/latest/docs/flutter/quick-start) has a separate debug-build setup.
88
80
 
89
- That one line is the whole production story: internal builds and flagged users get the full toolkit; everyone else gets nothing.
81
+ ## Account and plan requirements
90
82
 
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:
83
+ Use a Free or Pro Buoy account. History limits and paid features vary by tool; see [pricing](https://buoy.gg/pricing). Production access requires Pro where supported. Development-only hooks and actions remain unavailable in release builds.
92
84
 
93
- ```tsx
94
- <FloatingDevTools headless />
95
- ```
85
+ ## Production and connections
96
86
 
97
- ## Links
87
+ Start with a development build. Before enabling Buoy in a shipped app, restrict access using your app’s authorization checks and review the [component reference](https://buoy.gg/buoy/latest/docs/floating-devtools). Production access requires Pro, and development-only capabilities remain unavailable.
98
88
 
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)
89
+ Hiding the menu is not a substitute for backend authorization. Headless mode hides the on-device UI; it still requires account admission and sync configuration. Release sync is disabled by default and must be explicitly enabled.
103
90
 
104
- > 🎯 **Flutter (beta):** Buoy is now a full suite for Flutter — `flutter pub add buoy`. Network, storage, console, env, routes, images, Riverpod, events, and a live perf HUD, all streaming to the same desktop dashboard and MCP server. [Details →](https://buoy.gg/flutter)
91
+ Device sessions can travel over your LAN to the configured broker. Buoy also makes account and license requests; development telemetry is described in [Telemetry](https://buoy.gg/buoy/latest/docs/telemetry). Ask Buoy sends model requests to the endpoint you configure.
105
92
 
106
93
  ## License
107
94
 
108
95
  Proprietary software. © Buoy LLC. All rights reserved. See the [Terms of Service](https://buoy.gg/terms).
96
+
97
+ ### Custom floating controls
98
+
99
+ The DevTools bubble uses the shared `FloatingTool` shell. Custom tools can use the same shell with `openFloatingTool`; `FloatingDevTools` already mounts the host. See the [integration guide](https://buoy.gg/buoy/latest/docs/custom-tools/floating-tools).
package/bin/buoy.js CHANGED
@@ -93,9 +93,43 @@ function detectEnvVarName(root) {
93
93
  return 'BUOY_KEY';
94
94
  }
95
95
 
96
- /** Upsert `NAME=value` in .env.local, preserving everything else in the file. */
97
- function writeEnvVar(root, name, value) {
98
- const file = path.join(root, '.env.local');
96
+ /**
97
+ * Which env file the key belongs in.
98
+ *
99
+ * Expo inlines `.env.local` into EVERY bundle, release builds included, and
100
+ * `npx expo export` forces NODE_ENV=production — so a free key written there
101
+ * ships inside the app to every phone that installs it. Each of those phones
102
+ * then validates against the license server, which is how one free account
103
+ * spent the whole daily quota in a morning. `.env.development.local` is only
104
+ * loaded under NODE_ENV=development, so a free key there can only ever reach
105
+ * a developer's own device.
106
+ *
107
+ * Paid keys keep `.env.local`: a Pro customer may want the tools in a
108
+ * TestFlight or QA build, and that is their call to make.
109
+ */
110
+ function envFileFor(varName, tier) {
111
+ if (varName !== 'EXPO_PUBLIC_BUOY_KEY') return '.env.local';
112
+ return tier === 'pro' || tier === 'business' ? '.env.local' : '.env.development.local';
113
+ }
114
+
115
+ /** Drop `NAME=...` from an env file if present. True when a line was removed. */
116
+ function removeEnvVar(root, name, fileName) {
117
+ const file = path.join(root, fileName);
118
+ let contents;
119
+ try {
120
+ contents = fs.readFileSync(file, 'utf8');
121
+ } catch {
122
+ return false;
123
+ }
124
+ const pattern = new RegExp('^' + name + '=.*\\n?', 'm');
125
+ if (!pattern.test(contents)) return false;
126
+ fs.writeFileSync(file, contents.replace(pattern, ''), { mode: 0o600 });
127
+ return true;
128
+ }
129
+
130
+ /** Upsert `NAME=value` in an env file, preserving everything else in the file. */
131
+ function writeEnvVar(root, name, value, fileName = '.env.local') {
132
+ const file = path.join(root, fileName);
99
133
  let contents = '';
100
134
  try {
101
135
  contents = fs.readFileSync(file, 'utf8');
@@ -118,13 +152,13 @@ function writeEnvVar(root, name, value) {
118
152
  }
119
153
 
120
154
  /**
121
- * Make sure .env.local is ignored.
155
+ * Make sure the env file is ignored.
122
156
  *
123
157
  * The whole point of a per-developer key is that it identifies one person; a
124
158
  * key committed to a shared repo turns the count back into "repos", which is
125
159
  * exactly what this flow exists to avoid.
126
160
  */
127
- function ensureGitignored(root) {
161
+ function ensureGitignored(root, fileName = '.env.local') {
128
162
  const file = path.join(root, '.gitignore');
129
163
  let contents = '';
130
164
  try {
@@ -132,10 +166,12 @@ function ensureGitignored(root) {
132
166
  } catch {
133
167
  return false;
134
168
  }
135
- if (/^\.env\.local$|^\.env\*|^\*\.local$/m.test(contents)) return false;
169
+ const exact = new RegExp('^' + fileName.replace(/\./g, '\\.') + '$', 'm');
170
+ // `.env*`, `*.local` and `.env*.local` all cover both files we might write.
171
+ if (exact.test(contents) || /^\.env\*|^\*\.local$|^\.env\*\.local$/m.test(contents)) return false;
136
172
 
137
173
  if (contents && !contents.endsWith('\n')) contents += '\n';
138
- contents += '\n# Buoy license key (per developer - do not commit)\n.env.local\n';
174
+ contents += `\n# Buoy license key (per developer - do not commit)\n${fileName}\n`;
139
175
  fs.writeFileSync(file, contents);
140
176
  return true;
141
177
  }
@@ -219,7 +255,7 @@ function awaitKeyFromBrowser() {
219
255
 
220
256
  log('');
221
257
  log(` Opening ${cyan(url)}`);
222
- log(dim(' If your browser did not open, paste that URL into it.'));
258
+ log(dim(' If your browser did not open, paste that URL into a browser on this computer.'));
223
259
  log('');
224
260
  log(dim(' Waiting for you to finish signing in...'));
225
261
 
@@ -245,23 +281,31 @@ async function login() {
245
281
  }
246
282
 
247
283
  const varName = detectEnvVarName(root);
248
- const file = writeEnvVar(root, varName, result.key);
249
- const addedIgnore = ensureGitignored(root);
250
- const relative = path.relative(root, file) || '.env.local';
284
+ const fileName = envFileFor(varName, result.tier);
285
+ const file = writeEnvVar(root, varName, result.key, fileName);
286
+ // An earlier login may have left the key in .env.local; a free key there
287
+ // would still ride along into release builds.
288
+ const moved = fileName !== '.env.local' && removeEnvVar(root, varName, '.env.local');
289
+ const addedIgnore = ensureGitignored(root, fileName);
290
+ const relative = path.relative(root, file) || fileName;
251
291
 
252
292
  log('');
253
293
  log(
254
294
  `${green('OK')} Signed in${result.email ? ' as ' + bold(result.email) : ''} - ${bold(result.tier)} tier`
255
295
  );
256
296
  log(` Wrote ${cyan(varName)} to ${cyan(relative)}`);
257
- if (addedIgnore) log(dim(' Added .env.local to .gitignore'));
297
+ if (moved) log(dim(` Removed the old ${varName} line from .env.local`));
298
+ if (fileName === '.env.development.local') {
299
+ log(dim(' Expo loads this file for `expo start` only, so the key stays out of release builds.'));
300
+ }
301
+ if (addedIgnore) log(dim(` Added ${fileName} to .gitignore`));
258
302
  log('');
259
303
  log(' Use it in your app:');
260
304
  log('');
261
- log(cyan(` Buoy.init({ licenseKey: process.env.${varName} });`));
305
+ log(cyan(` Buoy.init({ licenseKey: ${varName} });`));
262
306
  log('');
263
- log(dim(' Restart your bundler with its cache cleared to pick this up:'));
264
- log(dim(' npx expo start --clear (Expo inlines EXPO_PUBLIC_* at build time)'));
307
+ log(dim(' Swift: pass the key through BuoyConfig.licenseKey in your development build.'));
308
+ log(dim(' Bare React Native does not load .env.local automatically; wire your existing env loader.'));
265
309
  log('');
266
310
  }
267
311
 
@@ -270,16 +314,21 @@ function help() {
270
314
  ${bold('buoy')} - React Buoy CLI
271
315
 
272
316
  ${bold('Usage')}
273
- npx buoy <command>
274
- npx --package=@buoy-gg/core buoy <command> ${dim('(if npx picks the wrong one)')}
317
+ npx --package=@buoy-gg/core buoy <command>
318
+
319
+ ${dim('(if npx picks the wrong one)')}
275
320
 
276
321
  ${bold('Commands')}
277
- login Sign in and write your license key to .env.local
322
+ login Sign in and write your license key to an env file
278
323
  help Show this message
279
324
 
280
325
  ${bold('Notes')}
281
- A free key takes about thirty seconds and needs no card. Paid keys use the
282
- same command - it writes whichever key your account already holds.
326
+ Run login from your app directory. Complete browser sign-in to receive
327
+ your account key. On Expo, a free key goes to .env.development.local so
328
+ it never ships in a release build; a paid key goes to .env.local. Other
329
+ projects receive BUOY_KEY in .env.local and must configure their own
330
+ environment loader and variable mapping.
331
+ This command does not install tools or verify a Desktop connection.
283
332
 
284
333
  Docs: ${SITE}/buoy/latest/docs/installation
285
334
  `);
@@ -301,7 +350,9 @@ if (require.main === module) {
301
350
 
302
351
  module.exports = {
303
352
  detectEnvVarName,
353
+ envFileFor,
304
354
  writeEnvVar,
355
+ removeEnvVar,
305
356
  ensureGitignored,
306
357
  awaitKeyFromBrowser,
307
358
  };
@@ -61,16 +61,7 @@ const Buoy = exports.Buoy = {
61
61
  */
62
62
  init(config) {
63
63
  if (config.telemetry === false) telemetryEnabled = false;
64
-
65
- // If no license key or empty string, clear any cached license
66
- if (!config.licenseKey || config.licenseKey.trim() === "") {
67
- _license.LicenseManager.initialize().then(() => _license.LicenseManager.clearLicense()).catch(() => {});
68
- return;
69
- }
70
-
71
- // Initialize the license manager and set the key
72
- const key = config.licenseKey; // We've already checked it's not empty above
73
- _license.LicenseManager.initialize().then(() => _license.LicenseManager.setLicenseKey(key)).catch(error => {
64
+ void _license.LicenseManager.configure(config.licenseKey).catch(error => {
74
65
  console.warn("[Buoy] License initialization failed:", error);
75
66
  });
76
67
  },
@@ -88,12 +79,8 @@ const Buoy = exports.Buoy = {
88
79
  * ```
89
80
  */
90
81
  async initAsync(config) {
91
- if (!config.licenseKey) {
92
- console.warn("[Buoy] No license key provided to Buoy.initAsync()");
93
- return false;
94
- }
95
- await _license.LicenseManager.initialize();
96
- return _license.LicenseManager.setLicenseKey(config.licenseKey);
82
+ if (config.telemetry === false) telemetryEnabled = false;
83
+ return _license.LicenseManager.configure(config.licenseKey);
97
84
  },
98
85
  /**
99
86
  * Check if Buoy Pro is active
@@ -8,7 +8,7 @@ var _sharedUi = require("@buoy-gg/shared-ui");
8
8
  var _react = _interopRequireWildcard(require("react"));
9
9
  var _reactNative = require("react-native");
10
10
  var _AppHostLogic = require("./AppHostLogic.js");
11
- var _AppIcon = require("./AppIcon.js");
11
+ var _AppIcon = require("./AppIcon");
12
12
  var _MinimizedToolsContext = require("./MinimizedToolsContext.js");
13
13
  var _ToolHost = require("./ToolHost.js");
14
14
  var _DevToolsSettingsModal = require("./DevToolsSettingsModal");
@@ -7,7 +7,7 @@ exports.useAppHost = exports.AppOverlay = exports.AppHostProvider = void 0;
7
7
  var _sharedUi = require("@buoy-gg/shared-ui");
8
8
  var _react = require("react");
9
9
  var _AppHostLogic = require("./AppHostLogic.js");
10
- var _AppIcon = require("./AppIcon.js");
10
+ var _AppIcon = require("./AppIcon");
11
11
  var _floatingToolsWeb = require("./floatingTools.web.js");
12
12
  var _jsxRuntime = require("react/jsx-runtime");
13
13
  /**
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.AppTextIcon = AppTextIcon;
7
+ exports.renderAppIcon = renderAppIcon;
8
+ var _react = _interopRequireDefault(require("react"));
9
+ var _sharedUi = require("@buoy-gg/shared-ui");
10
+ var _jsxRuntime = require("react/jsx-runtime");
11
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
12
+ /** DOM twin of the native icon resolver; importing RN breaks pure web hosts. */
13
+ function AppTextIcon({
14
+ label,
15
+ size,
16
+ color = _sharedUi.toolTheme.colors.text
17
+ }) {
18
+ const chars = Math.max(Array.from(label.replace(/[\uFE00-\uFE0F\u200D]/g, "")).length, 1);
19
+ const maxWidth = chars <= 2 ? size : size * 1.6;
20
+ const fontSize = Math.max(7, Math.min(size * (chars === 1 ? 0.7 : 0.62), maxWidth / (0.62 * chars)));
21
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
22
+ style: {
23
+ display: "inline-flex",
24
+ minWidth: size,
25
+ maxWidth,
26
+ height: size,
27
+ alignItems: "center",
28
+ justifyContent: "center",
29
+ color,
30
+ fontSize,
31
+ fontWeight: 800,
32
+ letterSpacing: 0.5,
33
+ whiteSpace: "nowrap",
34
+ overflow: "hidden"
35
+ },
36
+ children: label
37
+ });
38
+ }
39
+ function renderAppIcon(icon, ctx) {
40
+ if (icon == null) return icon;
41
+ if (typeof icon === "function") return /*#__PURE__*/_react.default.createElement(icon, {
42
+ slot: ctx.slot,
43
+ size: ctx.size,
44
+ state: ctx.state,
45
+ actions: ctx.actions
46
+ });
47
+ if (typeof icon === "string" || typeof icon === "number") return /*#__PURE__*/(0, _jsxRuntime.jsx)(AppTextIcon, {
48
+ label: String(icon),
49
+ size: ctx.size,
50
+ color: ctx.color
51
+ });
52
+ return icon;
53
+ }
@@ -11,7 +11,7 @@ var _useDesktopSyncStatus = require("./useDesktopSyncStatus.js");
11
11
  var _sharedUi = require("@buoy-gg/shared-ui");
12
12
  var _floatingToolsCore = require("@buoy-gg/floating-tools-core");
13
13
  var _DefaultConfigContext = require("./DefaultConfigContext.js");
14
- var _AppIcon = require("./AppIcon.js");
14
+ var _AppIcon = require("./AppIcon");
15
15
  var _jsxRuntime = require("react/jsx-runtime");
16
16
  const STORAGE_KEY = "@react_buoy_dev_tools_settings";
17
17
  const MAX_DIAL_SLOTS = 6;
@@ -376,10 +376,9 @@ const DevToolsSettingsModal = ({
376
376
  size: 18
377
377
  });
378
378
  case "sentry":
379
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SentryBugIcon, {
379
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SentryIcon, {
380
380
  size: 18,
381
- colorPreset: "red",
382
- noBackground: true
381
+ color: "#9945FF"
383
382
  });
384
383
  case "storage":
385
384
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_floatingToolsCore.StorageIcon, {
@@ -597,6 +596,7 @@ const DevToolsSettingsModal = ({
597
596
  contentContainerStyle: styles.scrollContainer,
598
597
  children: [activeTab === "floating" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
599
598
  children: renderSection("Floating Tools", /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightCard, {
599
+ glass: true,
600
600
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightRows, {
601
601
  children: Object.entries(settings.floatingTools).map(([key, value]) => renderToolRow(key, value, false, () => toggleFloatingTool(key)))
602
602
  })
@@ -605,11 +605,13 @@ const DevToolsSettingsModal = ({
605
605
  }))
606
606
  }), activeTab === "settings" && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
607
607
  children: [renderSection("Background", /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightCard, {
608
+ glass: true,
608
609
  padded: true,
609
610
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.BackgroundSwitcher, {
610
611
  embedded: true
611
612
  })
612
613
  })), renderSection("Storage", /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.NightCard, {
614
+ glass: true,
613
615
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
614
616
  activeOpacity: 0.7,
615
617
  onPress: () => setIsStorageExpanded(!isStorageExpanded),
@@ -690,6 +692,7 @@ const DevToolsSettingsModal = ({
690
692
  })]
691
693
  })]
692
694
  })), desktopSyncStatus && desktopSyncStatus.state !== "idle" && renderSection("Desktop Sync", /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.NightCard, {
695
+ glass: true,
693
696
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
694
697
  activeOpacity: 0.7,
695
698
  onPress: () => toggleSettingExpanded("desktopSync"),
@@ -731,12 +734,14 @@ const DevToolsSettingsModal = ({
731
734
  })]
732
735
  })]
733
736
  })), renderSection("Modal Behavior", /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightCard, {
737
+ glass: true,
734
738
  children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.NightRows, {
735
739
  children: [renderGlobalSettingRow("enableSharedModalDimensions", "Shared Modal Size", "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."), _reactNative.Platform.OS !== "web" ? renderGlobalSettingRow("expandableWindowControls", "Expand Controls", "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. 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.") : null]
736
740
  })
737
741
  }))]
738
742
  }), activeTab === "pro" && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
739
743
  children: [renderSection("License", /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightCard, {
744
+ glass: true,
740
745
  children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
741
746
  style: styles.settingRow,
742
747
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
@@ -757,6 +762,7 @@ const DevToolsSettingsModal = ({
757
762
  children: isPro ? "You have full access to all Buoy DevTools features." : licenseError ? licenseErrorIsTransport ? `Couldn't reach the license server (${licenseError}). A VPN or corporate proxy can block api.keygen.sh — turn it off and reload. Your key hasn't been rejected.` : `License check failed: ${licenseError}.` : "Upgrade to Pro to unlock all features and support development."
758
763
  })), !isPro && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
759
764
  children: [renderSection("Pro Features", /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightCard, {
765
+ glass: true,
760
766
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightRows, {
761
767
  children: ["Advanced Settings", "Priority Support"].map(feature => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
762
768
  style: styles.featureRow,
@@ -792,6 +798,7 @@ const DevToolsSettingsModal = ({
792
798
  customContent: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
793
799
  style: styles.headerRow,
794
800
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightSegmented, {
801
+ glass: true,
795
802
  tabs: [{
796
803
  key: "floating",
797
804
  label: "Floating"
@@ -1083,7 +1090,7 @@ const styles = _reactNative.StyleSheet.create({
1083
1090
  maxHeight: 150
1084
1091
  },
1085
1092
  storageKeyItem: {
1086
- backgroundColor: _sharedUi.night.color.surfaceElevated,
1093
+ backgroundColor: _sharedUi.night.color.glassElevated,
1087
1094
  borderRadius: _sharedUi.night.radius.segment,
1088
1095
  paddingHorizontal: 10,
1089
1096
  paddingVertical: 7,