@buoy-gg/external-sync 7.0.21 → 7.0.22

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.
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.attachDeviceSync = attachDeviceSync;
7
7
  var _logger = require("./utils/logger");
8
+ var _crashFlush = require("./crashFlush");
8
9
  var _emitBudget = require("./emitBudget");
9
10
  var _types = require("./types");
10
11
  const LOG = "[ExternalSync]";
@@ -79,6 +80,31 @@ function attachDeviceSync({
79
80
  }, throttleMs - elapsed);
80
81
  };
81
82
 
83
+ /**
84
+ * Emit a tool's snapshot immediately, cancelling any throttled push.
85
+ * The escape hatch for "the tree is about to die, get this out now" — see
86
+ * ./crashFlush. Ignores watch MODE: a crash flush is worth sending even to a
87
+ * dashboard that only asked to record.
88
+ *
89
+ * It does NOT ignore "is anyone there". Building a snapshot is the expensive
90
+ * part of this tool (deep-copying every captured entry, then walking it to
91
+ * price it), and `socket.emit` on a disconnected socket queues into
92
+ * socket.io's unbounded send buffer. Without these two guards, an app logging
93
+ * errors in a loop with no dashboard attached would pay that cost several
94
+ * times a second and grow the buffer until it ran out of memory — in a
95
+ * customer's app, for no observer.
96
+ */
97
+ const flushNow = toolId => {
98
+ if (!socket.connected) return;
99
+ if (!unsubs.has(toolId)) return; // nobody is watching this tool
100
+ if (timers[toolId]) {
101
+ clearTimeout(timers[toolId]);
102
+ delete timers[toolId];
103
+ }
104
+ sendSnapshot(toolId);
105
+ };
106
+ (0, _crashFlush.registerToolFlusher)(flushNow);
107
+
82
108
  /** The mode each currently-subscribed tool is wired for. */
83
109
  const activeModes = new Map();
84
110
  const watchTool = (toolId, mode) => {
@@ -200,6 +226,7 @@ function attachDeviceSync({
200
226
  socket.on("request-devtool-state", handleRequest);
201
227
  socket.on("devtool-action", handleAction);
202
228
  return () => {
229
+ (0, _crashFlush.unregisterToolFlusher)(flushNow);
203
230
  socket.off("connect", handleConnect);
204
231
  socket.off("devtool-watch", handleWatch);
205
232
  socket.off("request-devtool-state", handleRequest);
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.flushToolSyncNow = flushToolSyncNow;
7
+ exports.registerToolFlusher = registerToolFlusher;
8
+ exports.unregisterToolFlusher = unregisterToolFlusher;
9
+ /**
10
+ * crashFlush — a global "push this tool's snapshot RIGHT NOW" seam.
11
+ *
12
+ * Why a global and not an import: the device sync bridge lives in the React
13
+ * tree (`useExternalSync`), but the moment worth capturing most — a fatal
14
+ * render error — is the moment that tree dies. React unmounts the root right
15
+ * after the error is reported, which detaches the bridge and cancels the
16
+ * normal throttled snapshot push, so the crash never reaches the dashboard or
17
+ * the MCP server. The app then looks like it simply stopped answering.
18
+ *
19
+ * A tool that knows it is about to lose the tree (see @buoy-gg/console's fatal
20
+ * hook) calls {@link flushToolSyncNow} synchronously from its error handler,
21
+ * while the socket is still attached, and the snapshot goes out immediately.
22
+ *
23
+ * It is a global because the tools deliberately do NOT depend on this package
24
+ * (their adapters are duck-typed and picked up by auto-discovery), so there is
25
+ * no import edge to hang a registry on. Callers must treat it as best-effort:
26
+ * absent bridge = no-op.
27
+ */
28
+
29
+ const FLUSH_KEY = "__buoyFlushToolSync";
30
+
31
+ /** Emit `toolId`'s current snapshot now, bypassing the throttle. */
32
+
33
+ /**
34
+ * A SET, not a single slot: React can mount a second provider (or re-run the
35
+ * attach effect setup before the old one's cleanup) so two bridges are briefly
36
+ * live at once. A last-writer-wins slot would let the second registration
37
+ * silently disable the first's crash flush — failing in exactly the moment it
38
+ * is the only thing that gets the crash out, with no error and no log.
39
+ */
40
+
41
+ function flushers() {
42
+ const g = globalThis;
43
+ if (!g[FLUSH_KEY]) g[FLUSH_KEY] = new Set();
44
+ return g[FLUSH_KEY];
45
+ }
46
+
47
+ /** Install a bridge's flusher. Called by `attachDeviceSync`. */
48
+ function registerToolFlusher(flush) {
49
+ flushers().add(flush);
50
+ }
51
+
52
+ /** Remove one bridge's flusher, leaving any others intact. */
53
+ function unregisterToolFlusher(flush) {
54
+ flushers().delete(flush);
55
+ }
56
+
57
+ /**
58
+ * Push a tool's snapshot immediately through every attached sync bridge.
59
+ * Never throws — a crash path must not crash.
60
+ */
61
+ function flushToolSyncNow(toolId) {
62
+ try {
63
+ for (const flush of flushers()) {
64
+ try {
65
+ flush(toolId);
66
+ } catch {
67
+ // One bad bridge must not stop the others.
68
+ }
69
+ }
70
+ } catch {
71
+ // Best-effort by design.
72
+ }
73
+ }
@@ -15,6 +15,12 @@ Object.defineProperty(exports, "SYNC_PROTOCOL_VERSION", {
15
15
  return _types.SYNC_PROTOCOL_VERSION;
16
16
  }
17
17
  });
18
+ Object.defineProperty(exports, "flushToolSyncNow", {
19
+ enumerable: true,
20
+ get: function () {
21
+ return _crashFlush.flushToolSyncNow;
22
+ }
23
+ });
18
24
  Object.defineProperty(exports, "getDefaultSocketURL", {
19
25
  enumerable: true,
20
26
  get: function () {
@@ -33,6 +39,12 @@ Object.defineProperty(exports, "getSyncStatus", {
33
39
  return _syncStatusStore.getSyncStatus;
34
40
  }
35
41
  });
42
+ Object.defineProperty(exports, "registerToolFlusher", {
43
+ enumerable: true,
44
+ get: function () {
45
+ return _crashFlush.registerToolFlusher;
46
+ }
47
+ });
36
48
  Object.defineProperty(exports, "setSyncStatus", {
37
49
  enumerable: true,
38
50
  get: function () {
@@ -45,6 +57,12 @@ Object.defineProperty(exports, "subscribeSyncStatus", {
45
57
  return _syncStatusStore.subscribeSyncStatus;
46
58
  }
47
59
  });
60
+ Object.defineProperty(exports, "unregisterToolFlusher", {
61
+ enumerable: true,
62
+ get: function () {
63
+ return _crashFlush.unregisterToolFlusher;
64
+ }
65
+ });
48
66
  Object.defineProperty(exports, "useExternalSync", {
49
67
  enumerable: true,
50
68
  get: function () {
@@ -60,5 +78,6 @@ Object.defineProperty(exports, "useExternalSyncSocket", {
60
78
  var _useExternalSync = require("./useExternalSync");
61
79
  var _useExternalSyncSocket = require("./useExternalSyncSocket");
62
80
  var _getDefaultSocketURL = require("./utils/getDefaultSocketURL");
81
+ var _crashFlush = require("./crashFlush");
63
82
  var _syncStatusStore = require("./syncStatusStore");
64
83
  var _types = require("./types");
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  import { log } from "./utils/logger";
4
+ import { registerToolFlusher, unregisterToolFlusher } from "./crashFlush";
4
5
  import { MAX_ACTION_RESULT_BYTES, MAX_SNAPSHOT_EMIT_BYTES, approxSizeOver } from "./emitBudget";
5
6
  import { SYNC_PROTOCOL_VERSION } from "./types";
6
7
  const LOG = "[ExternalSync]";
@@ -75,6 +76,31 @@ export function attachDeviceSync({
75
76
  }, throttleMs - elapsed);
76
77
  };
77
78
 
79
+ /**
80
+ * Emit a tool's snapshot immediately, cancelling any throttled push.
81
+ * The escape hatch for "the tree is about to die, get this out now" — see
82
+ * ./crashFlush. Ignores watch MODE: a crash flush is worth sending even to a
83
+ * dashboard that only asked to record.
84
+ *
85
+ * It does NOT ignore "is anyone there". Building a snapshot is the expensive
86
+ * part of this tool (deep-copying every captured entry, then walking it to
87
+ * price it), and `socket.emit` on a disconnected socket queues into
88
+ * socket.io's unbounded send buffer. Without these two guards, an app logging
89
+ * errors in a loop with no dashboard attached would pay that cost several
90
+ * times a second and grow the buffer until it ran out of memory — in a
91
+ * customer's app, for no observer.
92
+ */
93
+ const flushNow = toolId => {
94
+ if (!socket.connected) return;
95
+ if (!unsubs.has(toolId)) return; // nobody is watching this tool
96
+ if (timers[toolId]) {
97
+ clearTimeout(timers[toolId]);
98
+ delete timers[toolId];
99
+ }
100
+ sendSnapshot(toolId);
101
+ };
102
+ registerToolFlusher(flushNow);
103
+
78
104
  /** The mode each currently-subscribed tool is wired for. */
79
105
  const activeModes = new Map();
80
106
  const watchTool = (toolId, mode) => {
@@ -196,6 +222,7 @@ export function attachDeviceSync({
196
222
  socket.on("request-devtool-state", handleRequest);
197
223
  socket.on("devtool-action", handleAction);
198
224
  return () => {
225
+ unregisterToolFlusher(flushNow);
199
226
  socket.off("connect", handleConnect);
200
227
  socket.off("devtool-watch", handleWatch);
201
228
  socket.off("request-devtool-state", handleRequest);
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * crashFlush — a global "push this tool's snapshot RIGHT NOW" seam.
5
+ *
6
+ * Why a global and not an import: the device sync bridge lives in the React
7
+ * tree (`useExternalSync`), but the moment worth capturing most — a fatal
8
+ * render error — is the moment that tree dies. React unmounts the root right
9
+ * after the error is reported, which detaches the bridge and cancels the
10
+ * normal throttled snapshot push, so the crash never reaches the dashboard or
11
+ * the MCP server. The app then looks like it simply stopped answering.
12
+ *
13
+ * A tool that knows it is about to lose the tree (see @buoy-gg/console's fatal
14
+ * hook) calls {@link flushToolSyncNow} synchronously from its error handler,
15
+ * while the socket is still attached, and the snapshot goes out immediately.
16
+ *
17
+ * It is a global because the tools deliberately do NOT depend on this package
18
+ * (their adapters are duck-typed and picked up by auto-discovery), so there is
19
+ * no import edge to hang a registry on. Callers must treat it as best-effort:
20
+ * absent bridge = no-op.
21
+ */
22
+
23
+ const FLUSH_KEY = "__buoyFlushToolSync";
24
+
25
+ /** Emit `toolId`'s current snapshot now, bypassing the throttle. */
26
+
27
+ /**
28
+ * A SET, not a single slot: React can mount a second provider (or re-run the
29
+ * attach effect setup before the old one's cleanup) so two bridges are briefly
30
+ * live at once. A last-writer-wins slot would let the second registration
31
+ * silently disable the first's crash flush — failing in exactly the moment it
32
+ * is the only thing that gets the crash out, with no error and no log.
33
+ */
34
+
35
+ function flushers() {
36
+ const g = globalThis;
37
+ if (!g[FLUSH_KEY]) g[FLUSH_KEY] = new Set();
38
+ return g[FLUSH_KEY];
39
+ }
40
+
41
+ /** Install a bridge's flusher. Called by `attachDeviceSync`. */
42
+ export function registerToolFlusher(flush) {
43
+ flushers().add(flush);
44
+ }
45
+
46
+ /** Remove one bridge's flusher, leaving any others intact. */
47
+ export function unregisterToolFlusher(flush) {
48
+ flushers().delete(flush);
49
+ }
50
+
51
+ /**
52
+ * Push a tool's snapshot immediately through every attached sync bridge.
53
+ * Never throws — a crash path must not crash.
54
+ */
55
+ export function flushToolSyncNow(toolId) {
56
+ try {
57
+ for (const flush of flushers()) {
58
+ try {
59
+ flush(toolId);
60
+ } catch {
61
+ // One bad bridge must not stop the others.
62
+ }
63
+ }
64
+ } catch {
65
+ // Best-effort by design.
66
+ }
67
+ }
@@ -11,6 +11,10 @@ export { useExternalSyncSocket } from "./useExternalSyncSocket";
11
11
  // ============================================================
12
12
  export { getDefaultSocketURL, getMetroHost, DEFAULT_BROKER_PORT } from "./utils/getDefaultSocketURL";
13
13
 
14
+ // ============================================================
15
+ // CRASH FLUSH (get a tool's snapshot out before the tree dies)
16
+ // ============================================================
17
+ export { flushToolSyncNow, registerToolFlusher, unregisterToolFlusher } from "./crashFlush";
14
18
  // ============================================================
15
19
  // SYNC STATUS (read by the floating menu's settings modal)
16
20
  // ============================================================
@@ -1 +1 @@
1
- {"version":3,"file":"attachDeviceSync.d.ts","sourceRoot":"","sources":["../../src/attachDeviceSync.ts"],"names":[],"mappings":"AAMA,OAAO,EAQL,KAAK,eAAe,EACrB,MAAM,SAAS,CAAC;AAIjB,2DAA2D;AAC3D,MAAM,MAAM,UAAU,GAAG;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;IACrD,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,KAAK,OAAO,CAAC;IACnE,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,KAAK,OAAO,CAAC;CACrE,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE7C,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAChD;;;;OAIG;IACH,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,UAAgB,EAChB,UAAkB,GACnB,EAAE,uBAAuB,GAAG,MAAM,IAAI,CA2NtC"}
1
+ {"version":3,"file":"attachDeviceSync.d.ts","sourceRoot":"","sources":["../../src/attachDeviceSync.ts"],"names":[],"mappings":"AAOA,OAAO,EAQL,KAAK,eAAe,EACrB,MAAM,SAAS,CAAC;AAIjB,2DAA2D;AAC3D,MAAM,MAAM,UAAU,GAAG;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;IACrD,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,KAAK,OAAO,CAAC;IACnE,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,KAAK,OAAO,CAAC;CACrE,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE7C,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAChD;;;;OAIG;IACH,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,UAAgB,EAChB,UAAkB,GACnB,EAAE,uBAAuB,GAAG,MAAM,IAAI,CAqPtC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * crashFlush — a global "push this tool's snapshot RIGHT NOW" seam.
3
+ *
4
+ * Why a global and not an import: the device sync bridge lives in the React
5
+ * tree (`useExternalSync`), but the moment worth capturing most — a fatal
6
+ * render error — is the moment that tree dies. React unmounts the root right
7
+ * after the error is reported, which detaches the bridge and cancels the
8
+ * normal throttled snapshot push, so the crash never reaches the dashboard or
9
+ * the MCP server. The app then looks like it simply stopped answering.
10
+ *
11
+ * A tool that knows it is about to lose the tree (see @buoy-gg/console's fatal
12
+ * hook) calls {@link flushToolSyncNow} synchronously from its error handler,
13
+ * while the socket is still attached, and the snapshot goes out immediately.
14
+ *
15
+ * It is a global because the tools deliberately do NOT depend on this package
16
+ * (their adapters are duck-typed and picked up by auto-discovery), so there is
17
+ * no import edge to hang a registry on. Callers must treat it as best-effort:
18
+ * absent bridge = no-op.
19
+ */
20
+ /** Emit `toolId`'s current snapshot now, bypassing the throttle. */
21
+ export type ToolFlusher = (toolId: string) => void;
22
+ /** Install a bridge's flusher. Called by `attachDeviceSync`. */
23
+ export declare function registerToolFlusher(flush: ToolFlusher): void;
24
+ /** Remove one bridge's flusher, leaving any others intact. */
25
+ export declare function unregisterToolFlusher(flush: ToolFlusher): void;
26
+ /**
27
+ * Push a tool's snapshot immediately through every attached sync bridge.
28
+ * Never throws — a crash path must not crash.
29
+ */
30
+ export declare function flushToolSyncNow(toolId: string): void;
31
+ //# sourceMappingURL=crashFlush.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crashFlush.d.ts","sourceRoot":"","sources":["../../src/crashFlush.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,oEAAoE;AACpE,MAAM,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;AAiBnD,gEAAgE;AAChE,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAE5D;AAED,8DAA8D;AAC9D,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAE9D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAYrD"}
@@ -1,6 +1,8 @@
1
1
  export { useExternalSync } from "./useExternalSync";
2
2
  export { useExternalSyncSocket } from "./useExternalSyncSocket";
3
3
  export { getDefaultSocketURL, getMetroHost, DEFAULT_BROKER_PORT, } from "./utils/getDefaultSocketURL";
4
+ export { flushToolSyncNow, registerToolFlusher, unregisterToolFlusher, } from "./crashFlush";
5
+ export type { ToolFlusher } from "./crashFlush";
4
6
  export { getSyncStatus, setSyncStatus, subscribeSyncStatus, } from "./syncStatusStore";
5
7
  export type { SyncStatus } from "./syncStatusStore";
6
8
  export { SYNC_PROTOCOL_VERSION } from "./types";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAKhE,OAAO,EACL,mBAAmB,EACnB,YAAY,EACZ,mBAAmB,GACpB,MAAM,6BAA6B,CAAC;AAKrC,OAAO,EACL,aAAa,EACb,aAAa,EACb,mBAAmB,GACpB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAKpD,OAAO,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAKhD,YAAY,EACV,eAAe,EACf,UAAU,EACV,cAAc,EACd,QAAQ,EACR,oBAAoB,EACpB,0BAA0B,EAC1B,yBAAyB,EACzB,kBAAkB,EAClB,oBAAoB,EACpB,0BAA0B,EAC1B,mBAAmB,EACnB,qBAAqB,EACrB,UAAU,GACX,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAKhE,OAAO,EACL,mBAAmB,EACnB,YAAY,EACZ,mBAAmB,GACpB,MAAM,6BAA6B,CAAC;AAKrC,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAKhD,OAAO,EACL,aAAa,EACb,aAAa,EACb,mBAAmB,GACpB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAKpD,OAAO,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAKhD,YAAY,EACV,eAAe,EACf,UAAU,EACV,cAAc,EACd,QAAQ,EACR,oBAAoB,EACpB,0BAA0B,EAC1B,yBAAyB,EACzB,kBAAkB,EAClB,oBAAoB,EACpB,0BAA0B,EAC1B,mBAAmB,EACnB,qBAAqB,EACrB,UAAU,GACX,MAAM,SAAS,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buoy-gg/external-sync",
3
- "version": "7.0.21",
3
+ "version": "7.0.22",
4
4
  "description": "Generic device-to-desktop sync for Buoy dev tools",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",