@runhuman/sensor 0.2.5 → 0.3.1

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.
package/dist/index.d.mts CHANGED
@@ -52,6 +52,7 @@ interface SessionManagerConfig {
52
52
  }
53
53
  declare class SessionManager {
54
54
  private state;
55
+ private _disabled;
55
56
  private sessionId;
56
57
  private activeJobId;
57
58
  private pollTimer;
@@ -66,6 +67,8 @@ declare class SessionManager {
66
67
  getSnapshot(): SessionState;
67
68
  getSessionId(): string | null;
68
69
  getActiveJobId(): string | null;
70
+ /** True when the API rejected the session (e.g. subscription tier). Hides the overlay. */
71
+ isDisabled(): boolean;
69
72
  /**
70
73
  * Subscribe to state changes. Returns an unsubscribe function.
71
74
  * Compatible with React's useSyncExternalStore.
@@ -168,20 +171,40 @@ declare class Runhuman {
168
171
  * After calling destroy(), you can call init() again.
169
172
  */
170
173
  destroy(): Promise<void>;
171
- /** Access the session manager (used by <RunhumanOverlay /> for reactive state) */
174
+ /** Access the session manager (used by <RunhumanProvider /> for reactive state) */
172
175
  getSessionManager(): SessionManager;
173
- /** Access the API client (used by <RunhumanOverlay /> for short code resolution) */
176
+ /** Access the API client (used by <RunhumanProvider /> for short code resolution) */
174
177
  getApiClient(): ApiClient;
175
178
  private log;
176
179
  }
177
180
 
178
181
  type OverlayPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
179
- interface RunhumanOverlayProps {
182
+ interface RunhumanProviderProps {
180
183
  children: React.ReactNode;
184
+ /** API key for authentication (e.g., 'rh_...') */
185
+ apiKey: string;
186
+ /** Set to false to disable the sensor entirely (e.g., in production). Default: true */
187
+ enabled?: boolean;
181
188
  /** Corner for the overlay UI (default: 'bottom-right') */
182
189
  position?: OverlayPosition;
190
+ /** Base URL of the Runhuman API (defaults to production) */
191
+ baseUrl?: string;
192
+ /** Job ID if known upfront (e.g., from deep link params at app launch) */
193
+ jobId?: string;
194
+ /** Target platform (defaults to 'react-native') */
195
+ platform?: TelemetryPlatform;
196
+ /** How often to flush buffered events in ms (default: 5000) */
197
+ flushIntervalMs?: number;
198
+ /** How often to poll for job status in ms (default: 10000) */
199
+ pollIntervalMs?: number;
200
+ /** Max events in the ring buffer before FIFO eviction (default: 1000) */
201
+ maxBufferSize?: number;
202
+ /** Enable deep link activation (default: true) */
203
+ enableDeepLinks?: boolean;
204
+ /** Log debug info to console (default: false) */
205
+ debug?: boolean;
183
206
  }
184
- declare function RunhumanOverlay({ children, position }: RunhumanOverlayProps): react_jsx_runtime.JSX.Element;
207
+ declare function RunhumanProvider({ children, apiKey, enabled, position, baseUrl, jobId, platform, flushIntervalMs, pollIntervalMs, maxBufferSize, enableDeepLinks, debug, }: RunhumanProviderProps): react_jsx_runtime.JSX.Element;
185
208
 
186
209
  /**
187
210
  * React hook for subscribing to sensor state changes.
@@ -192,6 +215,7 @@ interface SensorState {
192
215
  state: SessionState;
193
216
  activeJobId: string | null;
194
217
  sessionId: string | null;
218
+ disabled: boolean;
195
219
  }
196
220
  /**
197
221
  * Subscribe to the sensor's session state.
@@ -199,4 +223,4 @@ interface SensorState {
199
223
  */
200
224
  declare function useSensorState(): SensorState;
201
225
 
202
- export { ApiClient, type OverlayPosition, Runhuman, type RunhumanConfig, RunhumanOverlay, type RunhumanOverlayProps, type SensorState, type SessionState, useSensorState };
226
+ export { ApiClient, type OverlayPosition, Runhuman, type RunhumanConfig, RunhumanProvider, type RunhumanProviderProps, type SensorState, type SessionState, useSensorState };
package/dist/index.d.ts CHANGED
@@ -52,6 +52,7 @@ interface SessionManagerConfig {
52
52
  }
53
53
  declare class SessionManager {
54
54
  private state;
55
+ private _disabled;
55
56
  private sessionId;
56
57
  private activeJobId;
57
58
  private pollTimer;
@@ -66,6 +67,8 @@ declare class SessionManager {
66
67
  getSnapshot(): SessionState;
67
68
  getSessionId(): string | null;
68
69
  getActiveJobId(): string | null;
70
+ /** True when the API rejected the session (e.g. subscription tier). Hides the overlay. */
71
+ isDisabled(): boolean;
69
72
  /**
70
73
  * Subscribe to state changes. Returns an unsubscribe function.
71
74
  * Compatible with React's useSyncExternalStore.
@@ -168,20 +171,40 @@ declare class Runhuman {
168
171
  * After calling destroy(), you can call init() again.
169
172
  */
170
173
  destroy(): Promise<void>;
171
- /** Access the session manager (used by <RunhumanOverlay /> for reactive state) */
174
+ /** Access the session manager (used by <RunhumanProvider /> for reactive state) */
172
175
  getSessionManager(): SessionManager;
173
- /** Access the API client (used by <RunhumanOverlay /> for short code resolution) */
176
+ /** Access the API client (used by <RunhumanProvider /> for short code resolution) */
174
177
  getApiClient(): ApiClient;
175
178
  private log;
176
179
  }
177
180
 
178
181
  type OverlayPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
179
- interface RunhumanOverlayProps {
182
+ interface RunhumanProviderProps {
180
183
  children: React.ReactNode;
184
+ /** API key for authentication (e.g., 'rh_...') */
185
+ apiKey: string;
186
+ /** Set to false to disable the sensor entirely (e.g., in production). Default: true */
187
+ enabled?: boolean;
181
188
  /** Corner for the overlay UI (default: 'bottom-right') */
182
189
  position?: OverlayPosition;
190
+ /** Base URL of the Runhuman API (defaults to production) */
191
+ baseUrl?: string;
192
+ /** Job ID if known upfront (e.g., from deep link params at app launch) */
193
+ jobId?: string;
194
+ /** Target platform (defaults to 'react-native') */
195
+ platform?: TelemetryPlatform;
196
+ /** How often to flush buffered events in ms (default: 5000) */
197
+ flushIntervalMs?: number;
198
+ /** How often to poll for job status in ms (default: 10000) */
199
+ pollIntervalMs?: number;
200
+ /** Max events in the ring buffer before FIFO eviction (default: 1000) */
201
+ maxBufferSize?: number;
202
+ /** Enable deep link activation (default: true) */
203
+ enableDeepLinks?: boolean;
204
+ /** Log debug info to console (default: false) */
205
+ debug?: boolean;
183
206
  }
184
- declare function RunhumanOverlay({ children, position }: RunhumanOverlayProps): react_jsx_runtime.JSX.Element;
207
+ declare function RunhumanProvider({ children, apiKey, enabled, position, baseUrl, jobId, platform, flushIntervalMs, pollIntervalMs, maxBufferSize, enableDeepLinks, debug, }: RunhumanProviderProps): react_jsx_runtime.JSX.Element;
185
208
 
186
209
  /**
187
210
  * React hook for subscribing to sensor state changes.
@@ -192,6 +215,7 @@ interface SensorState {
192
215
  state: SessionState;
193
216
  activeJobId: string | null;
194
217
  sessionId: string | null;
218
+ disabled: boolean;
195
219
  }
196
220
  /**
197
221
  * Subscribe to the sensor's session state.
@@ -199,4 +223,4 @@ interface SensorState {
199
223
  */
200
224
  declare function useSensorState(): SensorState;
201
225
 
202
- export { ApiClient, type OverlayPosition, Runhuman, type RunhumanConfig, RunhumanOverlay, type RunhumanOverlayProps, type SensorState, type SessionState, useSensorState };
226
+ export { ApiClient, type OverlayPosition, Runhuman, type RunhumanConfig, RunhumanProvider, type RunhumanProviderProps, type SensorState, type SessionState, useSensorState };
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,13 +17,21 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
21
31
  var index_exports = {};
22
32
  __export(index_exports, {
23
33
  Runhuman: () => Runhuman,
24
- RunhumanOverlay: () => RunhumanOverlay,
34
+ RunhumanProvider: () => RunhumanProvider,
25
35
  useSensorState: () => useSensorState
26
36
  });
27
37
  module.exports = __toCommonJS(index_exports);
@@ -185,6 +195,10 @@ var apiRoutes = {
185
195
  jobs: defineRoute("/jobs"),
186
196
  /** Get/update/delete specific job */
187
197
  job: defineRoute("/jobs/:jobId"),
198
+ /** Cancel a job */
199
+ jobCancel: defineRoute("/jobs/:jobId/cancel"),
200
+ /** Create a GitHub issue from an extracted finding (user-initiated) */
201
+ jobCreateIssue: defineRoute("/jobs/:jobId/create-issue"),
188
202
  /** Get job status (for polling) */
189
203
  jobStatus: defineRoute("/jobs/:jobId/status"),
190
204
  /** Get individual job artifact by type */
@@ -303,6 +317,8 @@ var apiRoutes = {
303
317
  githubLink: defineRoute("/github/link"),
304
318
  /** List issues for a repo (by owner/repo) */
305
319
  githubIssuesByRepo: defineRoute("/github/issues/:owner/:repo"),
320
+ /** Get a single issue for a repo (by owner/repo/issueNumber) */
321
+ githubIssueByRepo: defineRoute("/github/issues/:owner/:repo/:issueNumber"),
306
322
  /** List issues (by projectId query param) */
307
323
  githubIssues: defineRoute("/github/issues"),
308
324
  /** Get a single issue */
@@ -457,8 +473,10 @@ var apiRoutes = {
457
473
  organizationJobs: defineRoute("/organizations/:organizationId/jobs"),
458
474
  /** Transfer organization ownership */
459
475
  organizationTransferOwnership: defineRoute("/organizations/:organizationId/transfer-ownership"),
460
- /** Create organization API key */
476
+ /** List/create organization API keys */
461
477
  organizationApiKeys: defineRoute("/organizations/:organizationId/api-keys"),
478
+ /** Get/revoke/delete a specific organization API key */
479
+ organizationApiKey: defineRoute("/organizations/:organizationId/api-keys/:keyId"),
462
480
  /** Get GitHub installations for an organization */
463
481
  organizationGitHubInstallations: defineRoute("/organizations/:organizationId/github/installations"),
464
482
  /** Get/delete specific GitHub installation for an organization */
@@ -567,7 +585,7 @@ var ApiClient = class {
567
585
  return this.get(apiRoutes.telemetrySessionStatus.build({ jobId }));
568
586
  }
569
587
  async resolveShortCode(code) {
570
- const normalized = code.replace(/^RH-/i, "").toUpperCase();
588
+ const normalized = code.toUpperCase().trim();
571
589
  try {
572
590
  return await this.get(
573
591
  apiRoutes.telemetryShortCodeResolve.build({ code: normalized })
@@ -842,6 +860,7 @@ var InterceptorManager = class {
842
860
  var SessionManager = class {
843
861
  constructor(config) {
844
862
  this.state = "idle";
863
+ this._disabled = false;
845
864
  this.sessionId = null;
846
865
  this.activeJobId = null;
847
866
  this.pollTimer = null;
@@ -864,6 +883,10 @@ var SessionManager = class {
864
883
  getActiveJobId() {
865
884
  return this.activeJobId;
866
885
  }
886
+ /** True when the API rejected the session (e.g. subscription tier). Hides the overlay. */
887
+ isDisabled() {
888
+ return this._disabled;
889
+ }
867
890
  /**
868
891
  * Subscribe to state changes. Returns an unsubscribe function.
869
892
  * Compatible with React's useSyncExternalStore.
@@ -948,12 +971,22 @@ var SessionManager = class {
948
971
  async startSession() {
949
972
  this.setState("active");
950
973
  const now = /* @__PURE__ */ new Date();
951
- const response = await this.config.apiClient.createSession({
952
- jobId: this.activeJobId,
953
- platform: this.config.platform,
954
- sdkVersion: this.config.sdkVersion,
955
- clientStartTime: now.toISOString()
956
- });
974
+ let response;
975
+ try {
976
+ response = await this.config.apiClient.createSession({
977
+ jobId: this.activeJobId,
978
+ platform: this.config.platform,
979
+ sdkVersion: this.config.sdkVersion,
980
+ clientStartTime: now.toISOString()
981
+ });
982
+ } catch (error) {
983
+ const is403 = error instanceof Error && error.message.includes("403");
984
+ this.log(is403 ? "Subscription not eligible \u2014 disabling sensor" : "Session creation failed", { error });
985
+ if (is403) this._disabled = true;
986
+ this.activeJobId = null;
987
+ this.setState("idle");
988
+ return;
989
+ }
957
990
  this.sessionId = response.sessionId;
958
991
  const sessionStartTime = now.getTime();
959
992
  this.log("Session started", {
@@ -1161,11 +1194,11 @@ var Runhuman = class _Runhuman {
1161
1194
  _Runhuman.instance = null;
1162
1195
  this.log("Destroyed");
1163
1196
  }
1164
- /** Access the session manager (used by <RunhumanOverlay /> for reactive state) */
1197
+ /** Access the session manager (used by <RunhumanProvider /> for reactive state) */
1165
1198
  getSessionManager() {
1166
1199
  return this.sessionManager;
1167
1200
  }
1168
- /** Access the API client (used by <RunhumanOverlay /> for short code resolution) */
1201
+ /** Access the API client (used by <RunhumanProvider /> for short code resolution) */
1169
1202
  getApiClient() {
1170
1203
  return this.apiClient;
1171
1204
  }
@@ -1176,13 +1209,14 @@ var Runhuman = class _Runhuman {
1176
1209
  }
1177
1210
  };
1178
1211
 
1179
- // src/overlay/RunhumanOverlay.tsx
1212
+ // src/overlay/RunhumanProvider.tsx
1213
+ var import_react5 = require("react");
1180
1214
  var import_react_native4 = require("react-native");
1181
1215
 
1182
1216
  // src/overlay/use-sensor-state.ts
1183
1217
  var import_react = require("react");
1184
1218
  var import_react2 = require("react");
1185
- var IDLE_STATE = { state: "idle", activeJobId: null, sessionId: null };
1219
+ var IDLE_STATE = { state: "idle", activeJobId: null, sessionId: null, disabled: false };
1186
1220
  function tryGetInstance() {
1187
1221
  try {
1188
1222
  return Runhuman.getInstance();
@@ -1218,11 +1252,12 @@ function useSensorState() {
1218
1252
  const state = sm.getSnapshot();
1219
1253
  const activeJobId = sm.getActiveJobId();
1220
1254
  const sessionId = sm.getSessionId();
1255
+ const disabled = sm.isDisabled();
1221
1256
  const prev = cached.current;
1222
- if (prev.state === state && prev.activeJobId === activeJobId && prev.sessionId === sessionId) {
1257
+ if (prev.state === state && prev.activeJobId === activeJobId && prev.sessionId === sessionId && prev.disabled === disabled) {
1223
1258
  return prev;
1224
1259
  }
1225
- cached.current = { state, activeJobId, sessionId };
1260
+ cached.current = { state, activeJobId, sessionId, disabled };
1226
1261
  return cached.current;
1227
1262
  }, []);
1228
1263
  return (0, import_react2.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
@@ -1263,6 +1298,11 @@ var overlayStyles = import_react_native.StyleSheet.create({
1263
1298
  fontWeight: "600",
1264
1299
  marginBottom: 8
1265
1300
  },
1301
+ inputRow: {
1302
+ flexDirection: "row",
1303
+ alignItems: "center",
1304
+ gap: 6
1305
+ },
1266
1306
  input: {
1267
1307
  backgroundColor: "rgba(255, 255, 255, 0.1)",
1268
1308
  borderRadius: 8,
@@ -1276,9 +1316,25 @@ var overlayStyles = import_react_native.StyleSheet.create({
1276
1316
  padding: 10,
1277
1317
  textAlign: "center"
1278
1318
  },
1319
+ inputFlex: {
1320
+ flex: 1
1321
+ },
1279
1322
  inputError: {
1280
1323
  borderColor: BRAND_RED
1281
1324
  },
1325
+ pasteButton: {
1326
+ backgroundColor: "rgba(255, 255, 255, 0.1)",
1327
+ borderRadius: 8,
1328
+ borderWidth: 1,
1329
+ borderColor: OVERLAY_BORDER,
1330
+ paddingVertical: 10,
1331
+ paddingHorizontal: 8
1332
+ },
1333
+ pasteButtonText: {
1334
+ color: "rgba(255, 255, 255, 0.6)",
1335
+ fontSize: 11,
1336
+ fontWeight: "600"
1337
+ },
1282
1338
  submitButton: {
1283
1339
  backgroundColor: BRAND_BLUE,
1284
1340
  borderRadius: 8,
@@ -1345,6 +1401,14 @@ var overlayStyles = import_react_native.StyleSheet.create({
1345
1401
  }
1346
1402
  });
1347
1403
 
1404
+ // src/overlay/clipboard.ts
1405
+ var ReactNative = __toESM(require("react-native"));
1406
+ var RN = ReactNative;
1407
+ var clipboard = RN["Clipboard"];
1408
+ async function getClipboardText() {
1409
+ return clipboard.getString();
1410
+ }
1411
+
1348
1412
  // src/overlay/CodeEntryPanel.tsx
1349
1413
  var import_jsx_runtime = require("react/jsx-runtime");
1350
1414
  function CodeEntryPanel({ position }) {
@@ -1357,38 +1421,55 @@ function CodeEntryPanel({ position }) {
1357
1421
  if (!trimmed) return;
1358
1422
  setResolving(true);
1359
1423
  setError(null);
1360
- const instance = Runhuman.getInstance();
1361
- const result = await instance.getApiClient().resolveShortCode(trimmed);
1362
- if (result) {
1363
- instance.activate(result.jobId);
1364
- } else {
1365
- setError("Invalid or expired code");
1424
+ try {
1425
+ const instance = Runhuman.getInstance();
1426
+ const result = await instance.getApiClient().resolveShortCode(trimmed);
1427
+ if (result) {
1428
+ await instance.activate(result.jobId);
1429
+ } else {
1430
+ setError("Invalid or expired code");
1431
+ setResolving(false);
1432
+ }
1433
+ } catch (err) {
1434
+ const message = err instanceof Error ? err.message : "Activation failed";
1435
+ setError(message);
1366
1436
  setResolving(false);
1367
1437
  }
1368
1438
  };
1439
+ const handlePaste = async () => {
1440
+ const text = await getClipboardText();
1441
+ const trimmed = text.trim().toUpperCase().slice(0, 6);
1442
+ if (trimmed) {
1443
+ setCode(trimmed);
1444
+ setError(null);
1445
+ }
1446
+ };
1369
1447
  if (minimized) {
1370
1448
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Pressable, { style: [overlayStyles.fab, overlayStyles[position]], onPress: () => setMinimized(false), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: overlayStyles.fabText, children: "RH" }) });
1371
1449
  }
1372
1450
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: [overlayStyles.panel, overlayStyles[position]], children: [
1373
1451
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Pressable, { style: overlayStyles.minimizeButton, onPress: () => setMinimized(true), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: overlayStyles.minimizeText, children: "-" }) }),
1374
1452
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: overlayStyles.panelTitle, children: "Runhuman Sensor" }),
1375
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1376
- import_react_native2.TextInput,
1377
- {
1378
- style: error ? { ...overlayStyles.input, ...overlayStyles.inputError } : overlayStyles.input,
1379
- value: code,
1380
- onChangeText: (text) => {
1381
- setCode(text.toUpperCase());
1382
- setError(null);
1383
- },
1384
- placeholder: "RH-XXXX",
1385
- placeholderTextColor: "rgba(255,255,255,0.3)",
1386
- autoCapitalize: "characters",
1387
- maxLength: 10,
1388
- editable: !resolving,
1389
- onSubmitEditing: handleSubmit
1390
- }
1391
- ),
1453
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: overlayStyles.inputRow, children: [
1454
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1455
+ import_react_native2.TextInput,
1456
+ {
1457
+ style: [error ? { ...overlayStyles.input, ...overlayStyles.inputError } : overlayStyles.input, overlayStyles.inputFlex],
1458
+ value: code,
1459
+ onChangeText: (text) => {
1460
+ setCode(text.toUpperCase());
1461
+ setError(null);
1462
+ },
1463
+ placeholder: "XXXX",
1464
+ placeholderTextColor: "rgba(255,255,255,0.3)",
1465
+ autoCapitalize: "characters",
1466
+ maxLength: 6,
1467
+ editable: !resolving,
1468
+ onSubmitEditing: handleSubmit
1469
+ }
1470
+ ),
1471
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Pressable, { style: overlayStyles.pasteButton, onPress: handlePaste, disabled: resolving, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: overlayStyles.pasteButtonText, children: "Paste" }) })
1472
+ ] }),
1392
1473
  error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: overlayStyles.errorText, children: error }) : null,
1393
1474
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1394
1475
  import_react_native2.Pressable,
@@ -1431,7 +1512,7 @@ function ActiveIndicator({ state, position }) {
1431
1512
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Animated.View, { style: [overlayStyles.indicator, overlayStyles[position], colorStyle, { opacity: pulse }] });
1432
1513
  }
1433
1514
 
1434
- // src/overlay/RunhumanOverlay.tsx
1515
+ // src/overlay/RunhumanProvider.tsx
1435
1516
  var import_jsx_runtime3 = require("react/jsx-runtime");
1436
1517
  var positionMap = {
1437
1518
  "top-left": "topLeft",
@@ -1439,13 +1520,48 @@ var positionMap = {
1439
1520
  "bottom-left": "bottomLeft",
1440
1521
  "bottom-right": "bottomRight"
1441
1522
  };
1442
- function RunhumanOverlay({ children, position = "bottom-right" }) {
1443
- const { state, activeJobId } = useSensorState();
1523
+ function RunhumanProvider({
1524
+ children,
1525
+ apiKey,
1526
+ enabled = true,
1527
+ position = "bottom-right",
1528
+ baseUrl,
1529
+ jobId,
1530
+ platform,
1531
+ flushIntervalMs,
1532
+ pollIntervalMs,
1533
+ maxBufferSize,
1534
+ enableDeepLinks,
1535
+ debug
1536
+ }) {
1537
+ const initializedRef = (0, import_react5.useRef)(false);
1538
+ (0, import_react5.useEffect)(() => {
1539
+ if (!enabled || initializedRef.current) return;
1540
+ initializedRef.current = true;
1541
+ const config = {
1542
+ apiKey,
1543
+ baseUrl,
1544
+ jobId,
1545
+ platform,
1546
+ flushIntervalMs,
1547
+ pollIntervalMs,
1548
+ maxBufferSize,
1549
+ enableDeepLinks,
1550
+ debug
1551
+ };
1552
+ Runhuman.init(config);
1553
+ return () => {
1554
+ initializedRef.current = false;
1555
+ Runhuman.getInstance().destroy();
1556
+ };
1557
+ }, [enabled]);
1558
+ const { state, activeJobId, disabled } = useSensorState();
1559
+ const showOverlay = enabled && !disabled;
1444
1560
  const posKey = positionMap[position];
1445
1561
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles.container, children: [
1446
1562
  children,
1447
- state === "idle" && !activeJobId ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CodeEntryPanel, { position: posKey }) : null,
1448
- state !== "idle" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ActiveIndicator, { state, position: posKey }) : null
1563
+ showOverlay && state === "idle" && !activeJobId ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CodeEntryPanel, { position: posKey }) : null,
1564
+ showOverlay && state !== "idle" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ActiveIndicator, { state, position: posKey }) : null
1449
1565
  ] });
1450
1566
  }
1451
1567
  var styles = import_react_native4.StyleSheet.create({
@@ -1456,7 +1572,7 @@ var styles = import_react_native4.StyleSheet.create({
1456
1572
  // Annotate the CommonJS export names for ESM import in node:
1457
1573
  0 && (module.exports = {
1458
1574
  Runhuman,
1459
- RunhumanOverlay,
1575
+ RunhumanProvider,
1460
1576
  useSensorState
1461
1577
  });
1462
1578
  //# sourceMappingURL=index.js.map