@runhuman/sensor 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,1099 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // ../shared/dist/routes/route-builder.js
9
+ var API_PREFIX = "/api";
10
+ function defineRoute(pattern) {
11
+ const build = (params) => {
12
+ if (!params)
13
+ return pattern;
14
+ let result = pattern;
15
+ for (const [key, value] of Object.entries(params)) {
16
+ if (value !== void 0) {
17
+ result = result.replace(`:${key}?`, value);
18
+ result = result.replace(`:${key}`, value);
19
+ }
20
+ }
21
+ result = result.replace(/\/:[^/]+\?/g, "");
22
+ result = result.replace(/\/+/g, "/");
23
+ if (result.length > 1 && result.endsWith("/")) {
24
+ result = result.slice(0, -1);
25
+ }
26
+ return result;
27
+ };
28
+ return {
29
+ pattern,
30
+ build
31
+ };
32
+ }
33
+
34
+ // ../shared/dist/routes/web-routes.js
35
+ var webRoutes = {
36
+ // ============================================
37
+ // Non-project routes (no projectId required)
38
+ // ============================================
39
+ /** Dashboard home page */
40
+ dashboard: defineRoute("/dashboard"),
41
+ /** Subscription management page */
42
+ managePlan: defineRoute("/dashboard/manage-plan"),
43
+ /** Plan selection / upgrade page */
44
+ plans: defineRoute("/dashboard/plans"),
45
+ /** Simple job view (redirects to full URL with projectId) */
46
+ jobSimple: defineRoute("/dashboard/jobs/:jobId"),
47
+ // ============================================
48
+ // Admin routes (@volter.ai only)
49
+ // ============================================
50
+ /** Admin panel */
51
+ admin: defineRoute("/dashboard/admin"),
52
+ // ============================================
53
+ // Organizations routes
54
+ // ============================================
55
+ /** Organizations list page */
56
+ organizations: defineRoute("/dashboard/organizations"),
57
+ /** Organization dashboard */
58
+ organization: defineRoute("/dashboard/organizations/:organizationId"),
59
+ /** Organization members page */
60
+ organizationMembers: defineRoute("/dashboard/organizations/:organizationId/members"),
61
+ /** Organization settings page */
62
+ organizationSettings: defineRoute("/dashboard/organizations/:organizationId/settings"),
63
+ /** Organization usage/billing page */
64
+ organizationUsage: defineRoute("/dashboard/organizations/:organizationId/usage"),
65
+ /** Organization projects page */
66
+ organizationProjects: defineRoute("/dashboard/organizations/:organizationId/projects"),
67
+ /** Organization jobs page (jobs across all projects) */
68
+ organizationJobs: defineRoute("/dashboard/organizations/:organizationId/jobs"),
69
+ /** Organization API keys page */
70
+ organizationApiKeys: defineRoute("/dashboard/organizations/:organizationId/api-keys"),
71
+ /** Organization integrations page */
72
+ organizationIntegrations: defineRoute("/dashboard/organizations/:organizationId/integrations"),
73
+ /** Organization GitHub integrations page */
74
+ organizationGitHub: defineRoute("/dashboard/organizations/:organizationId/github"),
75
+ /** Organization SSO/SAML configuration page */
76
+ organizationSso: defineRoute("/dashboard/organizations/:organizationId/sso"),
77
+ /** Import from GitHub page */
78
+ organizationImportGitHub: defineRoute("/dashboard/organizations/:organizationId/import"),
79
+ // ============================================
80
+ // User settings routes
81
+ // ============================================
82
+ /** Settings root (redirects to account) */
83
+ settings: defineRoute("/dashboard/settings"),
84
+ /** Account settings page */
85
+ settingsAccount: defineRoute("/dashboard/settings/account"),
86
+ // ============================================
87
+ // Tester portal routes
88
+ // ============================================
89
+ /** Tester dashboard/portal home */
90
+ tester: defineRoute("/tester"),
91
+ /** Tester available jobs list */
92
+ testerJobs: defineRoute("/tester/jobs"),
93
+ /** Tester settings page */
94
+ testerSettings: defineRoute("/tester/settings"),
95
+ /** Tester active test session */
96
+ testerTest: defineRoute("/tester/test/:jobId"),
97
+ // ============================================
98
+ // Project routes (require projectId)
99
+ // ============================================
100
+ /** Project root (redirects to playground) */
101
+ project: defineRoute("/dashboard/:projectId"),
102
+ /** Playground page for testing */
103
+ playground: defineRoute("/dashboard/:projectId/playground"),
104
+ /** Jobs list page */
105
+ jobs: defineRoute("/dashboard/:projectId/jobs"),
106
+ /** Single job detail page */
107
+ job: defineRoute("/dashboard/:projectId/jobs/:jobId"),
108
+ /** Templates page */
109
+ templates: defineRoute("/dashboard/:projectId/templates"),
110
+ /** Single template detail (modal) */
111
+ template: defineRoute("/dashboard/:projectId/templates/:templateId"),
112
+ /** GitHub issues page */
113
+ issues: defineRoute("/dashboard/:projectId/issues"),
114
+ /** Single issue detail (modal) */
115
+ issue: defineRoute("/dashboard/:projectId/issues/:issueNumber"),
116
+ /** Issue test sessions page */
117
+ issueSessions: defineRoute("/dashboard/:projectId/issue-sessions"),
118
+ /** Single test session detail (modal) */
119
+ issueSession: defineRoute("/dashboard/:projectId/issue-sessions/:sessionId"),
120
+ /** Project settings page */
121
+ projectSettings: defineRoute("/dashboard/:projectId/settings"),
122
+ /** Flow charts page */
123
+ flowCharts: defineRoute("/dashboard/:projectId/flowcharts"),
124
+ /** Single flow chart detail (embedded in dashboard - deprecated, use flowChartView) */
125
+ flowChart: defineRoute("/dashboard/:projectId/flowcharts/:flowchartId"),
126
+ /** Full-page flow chart viewer (outside dashboard layout) */
127
+ flowChartView: defineRoute("/view/flowchart/:projectId/:flowchartId"),
128
+ // ============================================
129
+ // External/public routes
130
+ // ============================================
131
+ /** Organization invite acceptance page */
132
+ invite: defineRoute("/invite/:token"),
133
+ /** Quick start flow for new users (pre-auth) */
134
+ quickStart: defineRoute("/start"),
135
+ /** Public job view with token-secured access */
136
+ publicJob: defineRoute("/j/:jobId/:token"),
137
+ /** Public system status page */
138
+ statuspage: defineRoute("/statuspage"),
139
+ /** Documentation */
140
+ docs: defineRoute("/docs/setup"),
141
+ /** Pricing page */
142
+ pricing: defineRoute("/pricing"),
143
+ /** Home page */
144
+ home: defineRoute("/"),
145
+ // ============================================
146
+ // Learn Platform routes
147
+ // ============================================
148
+ /** Learn catalogue (default product) */
149
+ learn: defineRoute("/learn"),
150
+ /** Single lesson view */
151
+ learnLesson: defineRoute("/learn/:lessonSlug")
152
+ };
153
+
154
+ // ../shared/dist/routes/api-routes.js
155
+ var apiRoutes = {
156
+ // ============================================
157
+ // Jobs endpoints
158
+ // ============================================
159
+ /** List jobs (GET) or create job (POST) */
160
+ jobs: defineRoute("/jobs"),
161
+ /** Get/update/delete specific job */
162
+ job: defineRoute("/jobs/:jobId"),
163
+ /** Get job status (for polling) */
164
+ jobStatus: defineRoute("/jobs/:jobId/status"),
165
+ /** Update job feedback and rating */
166
+ jobFeedback: defineRoute("/jobs/:jobId/feedback"),
167
+ /** Enable/disable public sharing for a job (POST = enable, DELETE = disable) */
168
+ jobShare: defineRoute("/jobs/:jobId/share"),
169
+ /** Claim a job (for testers) */
170
+ jobClaim: defineRoute("/jobs/:jobId/claim"),
171
+ /** Synchronous job execution */
172
+ run: defineRoute("/run"),
173
+ /** Get public job (no auth required, token-secured) */
174
+ publicJob: defineRoute("/public/jobs/:jobId/:token"),
175
+ // ============================================
176
+ // Tester App Job endpoints (secured by testerToken)
177
+ // ============================================
178
+ /** Get/update job for tester app */
179
+ testerJob: defineRoute("/tester/jobs/:jobId"),
180
+ /** Get presigned upload URLs for artifacts */
181
+ testerJobUploadUrls: defineRoute("/tester/jobs/:jobId/upload-urls"),
182
+ /** Process test results with AI */
183
+ testerJobProcessResults: defineRoute("/tester/jobs/:jobId/process-results"),
184
+ /** Get processing status */
185
+ testerJobProcessingStatus: defineRoute("/tester/jobs/:jobId/processing/:processingJobId"),
186
+ /** Extract frames from video */
187
+ testerJobExtractFrames: defineRoute("/tester/jobs/:jobId/extract-frames"),
188
+ /** Abort job due to platform issue */
189
+ testerJobAbort: defineRoute("/tester/jobs/:jobId/abort"),
190
+ /** Mark job as invalid due to bad instructions */
191
+ testerJobInvalid: defineRoute("/tester/jobs/:jobId/invalid"),
192
+ /** Notify phase transition from tester app */
193
+ testerJobPhase: defineRoute("/tester/jobs/:jobId/phase"),
194
+ // ============================================
195
+ // LiveKit endpoints (screen sharing & recording)
196
+ // ============================================
197
+ /** Start LiveKit session + get tester token (testerToken auth) */
198
+ livekitTesterToken: defineRoute("/tester/jobs/:jobId/livekit-token"),
199
+ /** End LiveKit session + stop egress (testerToken auth) */
200
+ livekitEndSession: defineRoute("/tester/jobs/:jobId/livekit-end"),
201
+ /** Save transcript entries from Web Speech API (testerToken auth) */
202
+ livekitTranscript: defineRoute("/tester/jobs/:jobId/livekit-transcript"),
203
+ /** Get viewer token for live watching (Clerk JWT auth) */
204
+ livekitViewerToken: defineRoute("/jobs/:jobId/livekit-viewer-token"),
205
+ /** Check if LiveKit session is active (Clerk JWT or testerToken auth) */
206
+ livekitStatus: defineRoute("/jobs/:jobId/livekit-status"),
207
+ /** Get presigned recording URL (Clerk JWT auth) */
208
+ livekitRecording: defineRoute("/jobs/:jobId/livekit-recording"),
209
+ // ============================================
210
+ // API Keys endpoints
211
+ // ============================================
212
+ /** List API keys (GET) or create key (POST) */
213
+ keys: defineRoute("/keys"),
214
+ /** Get/update/delete specific key */
215
+ key: defineRoute("/keys/:keyId"),
216
+ /** Revoke an API key */
217
+ keyRevoke: defineRoute("/keys/:keyId/revoke"),
218
+ /** Get API key info from header (for authenticated requests) */
219
+ keyInfo: defineRoute("/key-info"),
220
+ // ============================================
221
+ // Personal Access Tokens (PATs) endpoints
222
+ // ============================================
223
+ /** List PATs (GET) or create PAT (POST) */
224
+ pats: defineRoute("/pats"),
225
+ /** Get/delete specific PAT */
226
+ pat: defineRoute("/pats/:patId"),
227
+ /** Revoke a PAT */
228
+ patRevoke: defineRoute("/pats/:patId/revoke"),
229
+ /** Get PAT info from header (for authenticated requests) */
230
+ patInfo: defineRoute("/pat-info"),
231
+ // ============================================
232
+ // Projects endpoints
233
+ // ============================================
234
+ /** List projects (GET) or create project (POST) */
235
+ projects: defineRoute("/projects"),
236
+ /** Get/update/delete specific project */
237
+ project: defineRoute("/projects/:projectId"),
238
+ /** Get jobs for a project */
239
+ projectJobs: defineRoute("/projects/:projectId/jobs"),
240
+ /** Get API keys for a project */
241
+ projectApiKeys: defineRoute("/projects/:projectId/api-keys"),
242
+ /** Transfer a project to another user or organization */
243
+ projectTransfer: defineRoute("/projects/:projectId/transfer"),
244
+ /** List templates for a project (GET) or create template (POST) */
245
+ projectTemplates: defineRoute("/projects/:projectId/templates"),
246
+ /** Get/update/delete specific template */
247
+ projectTemplate: defineRoute("/projects/:projectId/templates/:templateId"),
248
+ /** List flow charts for a project (GET) or upload flow chart (POST) */
249
+ projectFlowCharts: defineRoute("/projects/:projectId/flowcharts"),
250
+ /** Upload flow chart to a project */
251
+ projectFlowChartsUpload: defineRoute("/projects/:projectId/flowcharts/upload"),
252
+ /** Get/delete specific flow chart */
253
+ projectFlowChart: defineRoute("/projects/:projectId/flowcharts/:flowchartId"),
254
+ /** Get flow chart data */
255
+ projectFlowChartData: defineRoute("/projects/:projectId/flowcharts/:flowchartId/data"),
256
+ /** Chat with AI about a flow chart */
257
+ projectFlowChartChat: defineRoute("/projects/:projectId/flowcharts/:flowchartId/chat"),
258
+ /** Enable/disable public sharing for a flow chart */
259
+ projectFlowChartShare: defineRoute("/projects/:projectId/flowcharts/:flowchartId/share"),
260
+ /** Bulk create projects from GitHub repos */
261
+ bulkCreateProjects: defineRoute("/projects/bulk"),
262
+ // ============================================
263
+ // GitHub endpoints
264
+ // ============================================
265
+ /** GitHub OAuth authorize */
266
+ githubOAuthAuthorize: defineRoute("/github/oauth/authorize"),
267
+ /** GitHub OAuth callback */
268
+ githubCallback: defineRoute("/github/oauth/callback"),
269
+ /** Link GitHub account (get GitHub username via OAuth) */
270
+ githubLink: defineRoute("/github/link"),
271
+ /** List issues for a repo (by owner/repo) */
272
+ githubIssuesByRepo: defineRoute("/github/issues/:owner/:repo"),
273
+ /** List issues (by projectId query param) */
274
+ githubIssues: defineRoute("/github/issues"),
275
+ /** Get a single issue */
276
+ githubIssue: defineRoute("/github/issues/:issueNumber"),
277
+ /** Get comments for an issue */
278
+ githubIssueComments: defineRoute("/github/issues/:issueNumber/comments"),
279
+ /** Get labels for a repo */
280
+ githubIssueLabels: defineRoute("/github/issues/labels"),
281
+ /** Get assignees for a repo */
282
+ githubIssueAssignees: defineRoute("/github/issues/assignees"),
283
+ /** Test a single issue */
284
+ githubIssueTest: defineRoute("/github/issues/test"),
285
+ /** Bulk test multiple issues */
286
+ githubIssuesBulkTest: defineRoute("/github/issues/bulk-test"),
287
+ /** List test sessions */
288
+ githubTestSessions: defineRoute("/github/issues/test-sessions"),
289
+ /** Get/delete a test session */
290
+ githubTestSession: defineRoute("/github/issues/test-sessions/:sessionId"),
291
+ /** Mark a test session as seen */
292
+ githubTestSessionSeen: defineRoute("/github/issues/test-sessions/:sessionId/seen"),
293
+ /** Get counts of running and unseen test sessions */
294
+ githubTestSessionsCounts: defineRoute("/github/issues/test-sessions/counts"),
295
+ /** Bulk test GitHub issues (legacy route) */
296
+ githubBulkTest: defineRoute("/github/bulk-test"),
297
+ /** GitHub App webhooks (receives issue_comment events etc.) */
298
+ githubWebhooks: defineRoute("/github/webhooks"),
299
+ // ============================================
300
+ // Auth endpoints (Clerk-based)
301
+ // ============================================
302
+ /** Sync user data from Clerk after sign-in */
303
+ authSync: defineRoute("/auth/sync"),
304
+ /** Get current user info */
305
+ authMe: defineRoute("/auth/me"),
306
+ /** Mark user as startup (bonus tokens) */
307
+ authStartup: defineRoute("/auth/startup"),
308
+ /** Delete user account and all associated data */
309
+ authDeleteAccount: defineRoute("/auth/account"),
310
+ /** Preview account deletion impact (owned orgs, members, projects) */
311
+ authDeletionPreview: defineRoute("/auth/account/deletion-preview"),
312
+ // ============================================
313
+ // Billing endpoints (Polar-based credits)
314
+ // ============================================
315
+ /** Get user's credit balance from Polar */
316
+ billingBalance: defineRoute("/billing/balance"),
317
+ /** Check if user has available credits */
318
+ billingHasCredits: defineRoute("/billing/has-credits"),
319
+ /** Get a customer portal URL for managing billing */
320
+ billingPortal: defineRoute("/billing/portal"),
321
+ /** Create a checkout session for purchasing credits */
322
+ billingCheckout: defineRoute("/billing/checkout"),
323
+ /** Get active subscription details (tier, cycle, etc.) */
324
+ billingSubscription: defineRoute("/billing/subscription"),
325
+ /** Update subscription plan (change tier/cycle) */
326
+ billingChangePlan: defineRoute("/billing/change-plan"),
327
+ // ============================================
328
+ // Other endpoints
329
+ // ============================================
330
+ /** Health check */
331
+ health: defineRoute("/health"),
332
+ /** Detailed system status */
333
+ status: defineRoute("/status"),
334
+ /** List templates */
335
+ templates: defineRoute("/templates"),
336
+ /** Issue analyzer */
337
+ issueAnalyzer: defineRoute("/issue-analyzer"),
338
+ /** PR analyzer */
339
+ prAnalyzer: defineRoute("/pr-analyzer"),
340
+ /** Logs endpoint */
341
+ logs: defineRoute("/logs"),
342
+ // ============================================
343
+ // Relevant Issues endpoints
344
+ // ============================================
345
+ /** Run relevant issues discovery for user */
346
+ relevantIssuesDiscover: defineRoute("/relevant-issues/discover"),
347
+ /** Get latest cached relevant issues result */
348
+ relevantIssuesLatest: defineRoute("/relevant-issues/latest"),
349
+ // ============================================
350
+ // Onboarding endpoints
351
+ // ============================================
352
+ /** Get/update user's onboarding state */
353
+ onboarding: defineRoute("/onboarding"),
354
+ /** Mark onboarding as complete */
355
+ onboardingComplete: defineRoute("/onboarding/complete"),
356
+ /** Check if user needs onboarding */
357
+ onboardingCheck: defineRoute("/onboarding/check"),
358
+ // ============================================
359
+ // Search endpoints
360
+ // ============================================
361
+ /** Global search across jobs, projects, issues, sessions */
362
+ search: defineRoute("/search"),
363
+ // ============================================
364
+ // Tester Profile endpoints
365
+ // ============================================
366
+ /** Apply to become a tester */
367
+ testerApply: defineRoute("/tester/apply"),
368
+ /** Get/update own tester profile */
369
+ testerProfile: defineRoute("/tester/profile"),
370
+ /** Get public tester profile */
371
+ testerPublicProfile: defineRoute("/testers/:testerId/profile"),
372
+ /** Download tester app */
373
+ testerDownloadApp: defineRoute("/tester/download-app"),
374
+ /** Download browser extension */
375
+ testerDownloadExtension: defineRoute("/tester/download-extension"),
376
+ /** Download mobile tester app (Android APK) */
377
+ testerDownloadMobileApp: defineRoute("/tester/download-mobile-app"),
378
+ /** List available (unclaimed) jobs for testers */
379
+ testerAvailableJobs: defineRoute("/tester/jobs/available"),
380
+ /** Get presigned URL for avatar upload */
381
+ testerAvatarUploadUrl: defineRoute("/tester/profile/avatar-upload-url"),
382
+ /** Get tester app version */
383
+ testerAppVersion: defineRoute("/tester/app-version"),
384
+ /** Register/unregister Expo push token for mobile notifications */
385
+ testerPushToken: defineRoute("/tester/push-token"),
386
+ // ============================================
387
+ // Extension endpoints (browser extension)
388
+ // ============================================
389
+ /** List extension tokens (GET) or create token (POST) */
390
+ extensionTokens: defineRoute("/tester/extension-tokens"),
391
+ /** Revoke (DELETE) a specific extension token */
392
+ extensionToken: defineRoute("/tester/extension-tokens/:tokenId"),
393
+ /** WebSocket endpoint for extension data streaming */
394
+ extensionStream: defineRoute("/extension/stream"),
395
+ /** Correlate extension data to a test job's time window */
396
+ testerJobCorrelate: defineRoute("/tester/jobs/:jobId/correlate"),
397
+ /** Check if extension data is flowing for this tester (polling endpoint) */
398
+ testerJobDataStatus: defineRoute("/tester/jobs/:jobId/data-status"),
399
+ /** Create extension token for auto-configuring the extension from the test page */
400
+ testerJobExtensionToken: defineRoute("/tester/jobs/:jobId/extension-token"),
401
+ // ============================================
402
+ // Repo Templates endpoints
403
+ // ============================================
404
+ /** List templates from a GitHub repo's .runhuman/templates/ directory */
405
+ repoTemplates: defineRoute("/repos/:owner/:repo/templates"),
406
+ /** Get a single template from a GitHub repo */
407
+ repoTemplate: defineRoute("/repos/:owner/:repo/templates/:templateName"),
408
+ // ============================================
409
+ // Organizations endpoints
410
+ // ============================================
411
+ /** List organizations (GET) or create organization (POST) */
412
+ organizations: defineRoute("/organizations"),
413
+ /** Get/update/delete specific organization */
414
+ organization: defineRoute("/organizations/:organizationId"),
415
+ /** List organization members */
416
+ organizationMembers: defineRoute("/organizations/:organizationId/members"),
417
+ /** Invite member to organization */
418
+ organizationInvite: defineRoute("/organizations/:organizationId/invite"),
419
+ /** Remove member from organization */
420
+ organizationMember: defineRoute("/organizations/:organizationId/members/:userId"),
421
+ /** List organization projects */
422
+ organizationProjects: defineRoute("/organizations/:organizationId/projects"),
423
+ /** Get organization jobs (jobs across all projects in the organization) */
424
+ organizationJobs: defineRoute("/organizations/:organizationId/jobs"),
425
+ /** Transfer organization ownership */
426
+ organizationTransferOwnership: defineRoute("/organizations/:organizationId/transfer-ownership"),
427
+ /** Create organization API key */
428
+ organizationApiKeys: defineRoute("/organizations/:organizationId/api-keys"),
429
+ /** Get GitHub installations for an organization */
430
+ organizationGitHubInstallations: defineRoute("/organizations/:organizationId/github/installations"),
431
+ /** Get/delete specific GitHub installation for an organization */
432
+ organizationGitHubInstallation: defineRoute("/organizations/:organizationId/github/installations/:installationId"),
433
+ /** List repos accessible to an organization via its GitHub installations */
434
+ organizationGitHubRepos: defineRoute("/organizations/:organizationId/github/repos"),
435
+ /** Refresh repos for an organization's GitHub installation */
436
+ organizationGitHubInstallationRefresh: defineRoute("/organizations/:organizationId/github/installations/:installationId/refresh"),
437
+ /** Check if org has access to a specific repo via its GitHub installations */
438
+ organizationGitHubRepoCheckAccess: defineRoute("/organizations/:organizationId/github/repos/check-access"),
439
+ /** Find deployed URL for a repo via org's GitHub installations */
440
+ organizationGitHubRepoFindUrl: defineRoute("/organizations/:organizationId/github/repos/find-url"),
441
+ /** Get organization billing balance (Polar) */
442
+ organizationBilling: defineRoute("/organizations/:organizationId/billing"),
443
+ // ============================================
444
+ // Transfers endpoints
445
+ // ============================================
446
+ /** Get pending incoming transfers for current user */
447
+ transfersPending: defineRoute("/transfers/pending"),
448
+ /** Get pending outgoing transfers (initiated by current user) */
449
+ transfersOutgoing: defineRoute("/transfers/outgoing"),
450
+ /** Accept a transfer */
451
+ transferAccept: defineRoute("/transfers/:transferId/accept"),
452
+ /** Reject a transfer */
453
+ transferReject: defineRoute("/transfers/:transferId/reject"),
454
+ /** Cancel a transfer */
455
+ transferCancel: defineRoute("/transfers/:transferId/cancel"),
456
+ // ============================================
457
+ // Invites endpoints (public)
458
+ // ============================================
459
+ /** Get invite details by token (public) */
460
+ invite: defineRoute("/invites/:token"),
461
+ /** Redeem an invite (authenticated) */
462
+ inviteRedeem: defineRoute("/invites/:token/redeem"),
463
+ // ============================================
464
+ // User Agreements endpoints
465
+ // ============================================
466
+ /** Record user agreement acceptance (POST) or get user's agreements (GET) */
467
+ agreements: defineRoute("/agreements"),
468
+ /** Check if user has accepted a specific agreement */
469
+ agreementCheck: defineRoute("/agreements/check"),
470
+ // ============================================
471
+ // Organization SSO endpoints
472
+ // ============================================
473
+ /** List SSO connections for an organization (GET) or create one (POST) */
474
+ organizationSsoConnections: defineRoute("/organizations/:organizationId/sso/connections"),
475
+ /** Get/delete a specific SSO connection */
476
+ organizationSsoConnection: defineRoute("/organizations/:organizationId/sso/connections/:connectionId"),
477
+ // ============================================
478
+ // Enterprise endpoints
479
+ // ============================================
480
+ /** Submit enterprise contact inquiry (POST) */
481
+ enterpriseInquiry: defineRoute("/enterprise/inquiry"),
482
+ // ============================================
483
+ // Changelog endpoints
484
+ // ============================================
485
+ /** List changelog entries (public) */
486
+ changelog: defineRoute("/changelog"),
487
+ /** Get unread changelog count (authenticated) */
488
+ changelogUnreadCount: defineRoute("/changelog/unread-count"),
489
+ /** Mark changelog as read (authenticated) */
490
+ changelogMarkRead: defineRoute("/changelog/mark-read"),
491
+ // ============================================
492
+ // Telemetry SDK endpoints
493
+ // ============================================
494
+ /** Create telemetry session (POST) — SDK calls this when a test session starts */
495
+ telemetrySessions: defineRoute("/telemetry/sessions"),
496
+ /** End telemetry session (POST) — SDK calls this when testing completes */
497
+ telemetrySessionEnd: defineRoute("/telemetry/sessions/:sessionId/end"),
498
+ /** Submit telemetry event batch (POST) — SDK periodically flushes captured events */
499
+ telemetryBatch: defineRoute("/telemetry/sessions/:sessionId/events"),
500
+ /** Check session status for a job (GET) — SDK polls to know when to activate */
501
+ telemetrySessionStatus: defineRoute("/telemetry/jobs/:jobId/status")
502
+ };
503
+
504
+ // src/api-client.ts
505
+ var ApiClient = class {
506
+ constructor(baseUrl, apiKey) {
507
+ this.baseUrl = baseUrl.replace(/\/$/, "");
508
+ this.apiKey = apiKey;
509
+ this.fetchFn = globalThis.fetch.bind(globalThis);
510
+ }
511
+ async createSession(req) {
512
+ return this.post(
513
+ apiRoutes.telemetrySessions.build(),
514
+ req
515
+ );
516
+ }
517
+ async submitBatch(sessionId, req) {
518
+ return this.post(
519
+ apiRoutes.telemetryBatch.build({ sessionId }),
520
+ req
521
+ );
522
+ }
523
+ async endSession(sessionId) {
524
+ return this.post(
525
+ apiRoutes.telemetrySessionEnd.build({ sessionId }),
526
+ void 0
527
+ );
528
+ }
529
+ async getSessionStatus(jobId) {
530
+ return this.get(apiRoutes.telemetrySessionStatus.build({ jobId }));
531
+ }
532
+ url(routePath) {
533
+ return `${this.baseUrl}${API_PREFIX}${routePath}`;
534
+ }
535
+ async post(routePath, body) {
536
+ const response = await this.fetchFn(this.url(routePath), {
537
+ method: "POST",
538
+ headers: {
539
+ "Content-Type": "application/json",
540
+ Authorization: `Bearer ${this.apiKey}`
541
+ },
542
+ body: body !== void 0 ? JSON.stringify(body) : void 0
543
+ });
544
+ if (!response.ok) {
545
+ const text = await response.text();
546
+ throw new Error(`Runhuman API error ${response.status}: ${text}`);
547
+ }
548
+ return response.json();
549
+ }
550
+ async get(routePath) {
551
+ const response = await this.fetchFn(this.url(routePath), {
552
+ method: "GET",
553
+ headers: {
554
+ Authorization: `Bearer ${this.apiKey}`
555
+ }
556
+ });
557
+ if (!response.ok) {
558
+ const text = await response.text();
559
+ throw new Error(`Runhuman API error ${response.status}: ${text}`);
560
+ }
561
+ return response.json();
562
+ }
563
+ };
564
+
565
+ // src/event-buffer.ts
566
+ var EventBuffer = class {
567
+ constructor(maxCapacity = 1e3) {
568
+ this.buffer = [];
569
+ this.maxCapacity = maxCapacity;
570
+ }
571
+ get size() {
572
+ return this.buffer.length;
573
+ }
574
+ push(event) {
575
+ if (this.buffer.length >= this.maxCapacity) {
576
+ this.buffer.shift();
577
+ }
578
+ this.buffer.push(event);
579
+ }
580
+ drain() {
581
+ const events = this.buffer;
582
+ this.buffer = [];
583
+ return events;
584
+ }
585
+ clear() {
586
+ this.buffer = [];
587
+ }
588
+ };
589
+
590
+ // src/interceptors/network.interceptor.ts
591
+ var STRIPPED_HEADERS = /* @__PURE__ */ new Set([
592
+ "authorization",
593
+ "cookie",
594
+ "set-cookie",
595
+ "x-api-key",
596
+ "proxy-authorization"
597
+ ]);
598
+ function scrubHeaders(headers) {
599
+ const result = {};
600
+ headers.forEach((value, key) => {
601
+ result[key] = STRIPPED_HEADERS.has(key.toLowerCase()) ? "[REDACTED]" : value;
602
+ });
603
+ return result;
604
+ }
605
+ var NetworkInterceptor = class {
606
+ constructor() {
607
+ this.originalFetch = null;
608
+ this.sessionStartTime = 0;
609
+ }
610
+ install(onEvent) {
611
+ this.originalFetch = globalThis.fetch;
612
+ this.sessionStartTime = Date.now();
613
+ const captured = this.originalFetch;
614
+ const startTime = this.sessionStartTime;
615
+ globalThis.fetch = async function interceptedFetch(input, init) {
616
+ const requestStart = Date.now();
617
+ const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
618
+ const method = init?.method ?? "GET";
619
+ const response = await captured.call(globalThis, input, init);
620
+ const event = {
621
+ type: "network",
622
+ relativeTimestampMs: requestStart - startTime,
623
+ clientTimestamp: new Date(requestStart).toISOString(),
624
+ data: {
625
+ url,
626
+ method: method.toUpperCase(),
627
+ status: response.status,
628
+ statusText: response.statusText,
629
+ durationMs: Date.now() - requestStart,
630
+ responseHeaders: scrubHeaders(response.headers)
631
+ }
632
+ };
633
+ onEvent(event);
634
+ return response;
635
+ };
636
+ }
637
+ uninstall() {
638
+ if (this.originalFetch) {
639
+ globalThis.fetch = this.originalFetch;
640
+ this.originalFetch = null;
641
+ }
642
+ }
643
+ /** Update the session start time (called when a new session begins) */
644
+ setSessionStartTime(startTime) {
645
+ this.sessionStartTime = startTime;
646
+ }
647
+ };
648
+
649
+ // src/interceptors/console.interceptor.ts
650
+ var CONSOLE_METHODS = ["log", "warn", "error", "info", "debug"];
651
+ function stringify(args) {
652
+ return args.map((arg) => {
653
+ if (typeof arg === "string") return arg;
654
+ try {
655
+ return JSON.stringify(arg);
656
+ } catch {
657
+ return String(arg);
658
+ }
659
+ }).join(" ");
660
+ }
661
+ var ConsoleInterceptor = class {
662
+ constructor() {
663
+ this.originals = {};
664
+ this.sessionStartTime = 0;
665
+ }
666
+ install(onEvent) {
667
+ this.sessionStartTime = Date.now();
668
+ const startTime = this.sessionStartTime;
669
+ for (const method of CONSOLE_METHODS) {
670
+ this.originals[method] = console[method].bind(console);
671
+ console[method] = (...args) => {
672
+ const now = Date.now();
673
+ const event = {
674
+ type: "console",
675
+ relativeTimestampMs: now - startTime,
676
+ clientTimestamp: new Date(now).toISOString(),
677
+ data: {
678
+ level: method,
679
+ message: stringify(args)
680
+ }
681
+ };
682
+ onEvent(event);
683
+ this.originals[method](...args);
684
+ };
685
+ }
686
+ }
687
+ uninstall() {
688
+ for (const method of CONSOLE_METHODS) {
689
+ if (this.originals[method]) {
690
+ console[method] = this.originals[method];
691
+ }
692
+ }
693
+ this.originals = {};
694
+ }
695
+ setSessionStartTime(startTime) {
696
+ this.sessionStartTime = startTime;
697
+ }
698
+ };
699
+
700
+ // src/interceptors/error.interceptor.ts
701
+ var ErrorInterceptor = class {
702
+ constructor() {
703
+ this.previousHandler = null;
704
+ this.previousOnError = null;
705
+ this.sessionStartTime = 0;
706
+ }
707
+ install(onEvent) {
708
+ this.sessionStartTime = Date.now();
709
+ const startTime = this.sessionStartTime;
710
+ if (typeof ErrorUtils !== "undefined") {
711
+ this.previousHandler = ErrorUtils.getGlobalHandler();
712
+ const prev = this.previousHandler;
713
+ ErrorUtils.setGlobalHandler((error, isFatal) => {
714
+ const now = Date.now();
715
+ const event = {
716
+ type: isFatal ? "crash" : "error",
717
+ relativeTimestampMs: now - startTime,
718
+ clientTimestamp: new Date(now).toISOString(),
719
+ data: {
720
+ message: error.message,
721
+ stack: error.stack,
722
+ name: error.name,
723
+ isFatal
724
+ }
725
+ };
726
+ onEvent(event);
727
+ prev(error, isFatal);
728
+ });
729
+ return;
730
+ }
731
+ this.previousOnError = globalThis.onerror;
732
+ globalThis.onerror = (message, source, lineno, colno, error) => {
733
+ const now = Date.now();
734
+ const event = {
735
+ type: "error",
736
+ relativeTimestampMs: now - startTime,
737
+ clientTimestamp: new Date(now).toISOString(),
738
+ data: {
739
+ message: typeof message === "string" ? message : message.type,
740
+ stack: error?.stack,
741
+ name: error?.name,
742
+ source,
743
+ lineno,
744
+ colno
745
+ }
746
+ };
747
+ onEvent(event);
748
+ if (typeof this.previousOnError === "function") {
749
+ return this.previousOnError.call(globalThis, message, source, lineno, colno, error);
750
+ }
751
+ };
752
+ }
753
+ uninstall() {
754
+ if (typeof ErrorUtils !== "undefined" && this.previousHandler) {
755
+ ErrorUtils.setGlobalHandler(this.previousHandler);
756
+ this.previousHandler = null;
757
+ }
758
+ if (this.previousOnError !== null) {
759
+ globalThis.onerror = this.previousOnError;
760
+ this.previousOnError = null;
761
+ }
762
+ }
763
+ setSessionStartTime(startTime) {
764
+ this.sessionStartTime = startTime;
765
+ }
766
+ };
767
+
768
+ // src/interceptors/index.ts
769
+ var InterceptorManager = class {
770
+ constructor() {
771
+ this.network = new NetworkInterceptor();
772
+ this.console = new ConsoleInterceptor();
773
+ this.error = new ErrorInterceptor();
774
+ this.interceptors = [this.network, this.console, this.error];
775
+ }
776
+ install(onEvent) {
777
+ for (const interceptor of this.interceptors) {
778
+ interceptor.install(onEvent);
779
+ }
780
+ }
781
+ uninstall() {
782
+ for (let i = this.interceptors.length - 1; i >= 0; i--) {
783
+ this.interceptors[i].uninstall();
784
+ }
785
+ }
786
+ setSessionStartTime(startTime) {
787
+ this.network.setSessionStartTime(startTime);
788
+ this.console.setSessionStartTime(startTime);
789
+ this.error.setSessionStartTime(startTime);
790
+ }
791
+ };
792
+
793
+ // src/session-manager.ts
794
+ var SessionManager = class {
795
+ constructor(config) {
796
+ this.state = "idle";
797
+ this.sessionId = null;
798
+ this.activeJobId = null;
799
+ this.pollTimer = null;
800
+ this.flushTimer = null;
801
+ this.config = config;
802
+ this.buffer = new EventBuffer(config.maxBufferSize);
803
+ this.interceptors = new InterceptorManager();
804
+ }
805
+ getState() {
806
+ return this.state;
807
+ }
808
+ getSessionId() {
809
+ return this.sessionId;
810
+ }
811
+ /**
812
+ * Start polling for job activation.
813
+ * The sensor calls this on init — polling continues until a job is detected
814
+ * or stopPolling() is called.
815
+ */
816
+ startPolling(jobId) {
817
+ if (this.state !== "idle") return;
818
+ this.activeJobId = jobId;
819
+ this.state = "polling";
820
+ this.log("Polling started", { jobId });
821
+ this.pollTimer = setInterval(() => this.pollOnce(), this.config.pollIntervalMs);
822
+ this.pollOnce();
823
+ }
824
+ stopPolling() {
825
+ if (this.pollTimer) {
826
+ clearInterval(this.pollTimer);
827
+ this.pollTimer = null;
828
+ }
829
+ if (this.state === "polling") {
830
+ this.state = "idle";
831
+ this.log("Polling stopped");
832
+ }
833
+ }
834
+ /**
835
+ * Activate a session immediately (e.g., from a deep link).
836
+ * Skips polling — goes straight to session creation.
837
+ */
838
+ async activate(jobId) {
839
+ if (this.state === "active") {
840
+ this.log("Already active, ignoring activate()", { jobId });
841
+ return;
842
+ }
843
+ this.stopPolling();
844
+ this.activeJobId = jobId;
845
+ await this.startSession();
846
+ }
847
+ /**
848
+ * End the current session and return to idle.
849
+ */
850
+ async deactivate() {
851
+ if (this.state !== "active") return;
852
+ await this.endSession();
853
+ }
854
+ /**
855
+ * Full teardown — stop everything and clean up.
856
+ */
857
+ async destroy() {
858
+ this.stopPolling();
859
+ if (this.state === "active") {
860
+ await this.endSession();
861
+ }
862
+ }
863
+ async pollOnce() {
864
+ if (this.state !== "polling" || !this.activeJobId) return;
865
+ try {
866
+ const status = await this.config.apiClient.getSessionStatus(this.activeJobId);
867
+ if (status.jobActive && status.sdkEnabled && status.activeSessionId) {
868
+ this.log("Job has active session, starting capture", {
869
+ jobId: this.activeJobId,
870
+ activeSessionId: status.activeSessionId
871
+ });
872
+ this.stopPolling();
873
+ await this.startSession();
874
+ }
875
+ } catch (error) {
876
+ this.log("Poll failed", { error });
877
+ }
878
+ }
879
+ async startSession() {
880
+ this.state = "active";
881
+ const now = /* @__PURE__ */ new Date();
882
+ const response = await this.config.apiClient.createSession({
883
+ jobId: this.activeJobId,
884
+ platform: this.config.platform,
885
+ sdkVersion: this.config.sdkVersion,
886
+ clientStartTime: now.toISOString()
887
+ });
888
+ this.sessionId = response.sessionId;
889
+ const sessionStartTime = now.getTime();
890
+ this.log("Session started", {
891
+ sessionId: this.sessionId,
892
+ clockOffsetMs: response.clockOffsetMs
893
+ });
894
+ this.interceptors.setSessionStartTime(sessionStartTime);
895
+ this.interceptors.install((event) => this.buffer.push(event));
896
+ this.flushTimer = setInterval(() => this.flush(), this.config.flushIntervalMs);
897
+ }
898
+ async endSession() {
899
+ this.state = "ending";
900
+ if (this.flushTimer) {
901
+ clearInterval(this.flushTimer);
902
+ this.flushTimer = null;
903
+ }
904
+ this.interceptors.uninstall();
905
+ await this.flush();
906
+ if (this.sessionId) {
907
+ try {
908
+ const result = await this.config.apiClient.endSession(this.sessionId);
909
+ this.log("Session ended", {
910
+ sessionId: this.sessionId,
911
+ eventCount: result.eventCount,
912
+ correlated: result.correlated
913
+ });
914
+ } catch (error) {
915
+ this.log("Failed to end session", { sessionId: this.sessionId, error });
916
+ }
917
+ }
918
+ this.sessionId = null;
919
+ this.activeJobId = null;
920
+ this.buffer.clear();
921
+ this.state = "idle";
922
+ }
923
+ async flush() {
924
+ if (!this.sessionId) return;
925
+ const events = this.buffer.drain();
926
+ if (events.length === 0) return;
927
+ try {
928
+ const result = await this.config.apiClient.submitBatch(this.sessionId, { events });
929
+ this.log("Flushed events", {
930
+ accepted: result.accepted,
931
+ sessionEventCount: result.sessionEventCount
932
+ });
933
+ } catch (error) {
934
+ this.log("Flush failed, events lost", { count: events.length, error });
935
+ }
936
+ }
937
+ log(message, data) {
938
+ if (this.config.debug) {
939
+ console.debug(`[Runhuman] ${message}`, data);
940
+ }
941
+ }
942
+ };
943
+
944
+ // src/deep-link-handler.ts
945
+ var DeepLinkHandler = class {
946
+ constructor(sessionManager, debug) {
947
+ this.subscription = null;
948
+ this.sessionManager = sessionManager;
949
+ this.debug = debug;
950
+ }
951
+ async install() {
952
+ const Linking = this.getLinking();
953
+ if (!Linking) {
954
+ this.log("react-native Linking not available, deep links disabled");
955
+ return;
956
+ }
957
+ this.subscription = Linking.addEventListener("url", ({ url }) => {
958
+ this.handleUrl(url);
959
+ });
960
+ const initialUrl = await Linking.getInitialURL();
961
+ if (initialUrl) {
962
+ this.handleUrl(initialUrl);
963
+ }
964
+ }
965
+ uninstall() {
966
+ if (this.subscription) {
967
+ this.subscription.remove();
968
+ this.subscription = null;
969
+ }
970
+ }
971
+ handleUrl(url) {
972
+ const jobId = this.extractJobId(url);
973
+ if (!jobId) return;
974
+ this.log("Deep link received", { url, jobId });
975
+ this.sessionManager.activate(jobId);
976
+ }
977
+ /**
978
+ * Extract jobId from a URL matching: <scheme>://runhuman?jobId=<id>
979
+ * Returns null if the URL doesn't match the expected pattern.
980
+ */
981
+ extractJobId(url) {
982
+ try {
983
+ const parsed = new URL(url);
984
+ const isRunhuman = parsed.hostname === "runhuman" || parsed.pathname.includes("runhuman");
985
+ if (!isRunhuman) return null;
986
+ return parsed.searchParams.get("jobId");
987
+ } catch {
988
+ return null;
989
+ }
990
+ }
991
+ getLinking() {
992
+ try {
993
+ const { Linking } = __require("react-native");
994
+ return Linking;
995
+ } catch {
996
+ return null;
997
+ }
998
+ }
999
+ log(message, data) {
1000
+ if (this.debug) {
1001
+ console.debug(`[Runhuman] ${message}`, data);
1002
+ }
1003
+ }
1004
+ };
1005
+
1006
+ // src/runhuman.ts
1007
+ var PRODUCTION_BASE_URL = "https://qa-experiment.fly.dev";
1008
+ var SDK_VERSION = "0.1.0";
1009
+ var _Runhuman = class _Runhuman {
1010
+ constructor(config) {
1011
+ this.debug = config.debug === true;
1012
+ const apiClient = new ApiClient(
1013
+ config.baseUrl ?? PRODUCTION_BASE_URL,
1014
+ config.apiKey
1015
+ );
1016
+ this.sessionManager = new SessionManager({
1017
+ apiClient,
1018
+ platform: config.platform ?? "react-native",
1019
+ sdkVersion: SDK_VERSION,
1020
+ flushIntervalMs: config.flushIntervalMs ?? 5e3,
1021
+ pollIntervalMs: config.pollIntervalMs ?? 1e4,
1022
+ maxBufferSize: config.maxBufferSize ?? 1e3,
1023
+ debug: this.debug
1024
+ });
1025
+ const enableDeepLinks = config.enableDeepLinks !== false;
1026
+ if (enableDeepLinks) {
1027
+ this.deepLinkHandler = new DeepLinkHandler(this.sessionManager, this.debug);
1028
+ this.deepLinkHandler.install();
1029
+ } else {
1030
+ this.deepLinkHandler = null;
1031
+ }
1032
+ this.log("Initialized");
1033
+ if (config.jobId) {
1034
+ this.sessionManager.startPolling(config.jobId);
1035
+ }
1036
+ }
1037
+ /**
1038
+ * Initialize the Runhuman sensor. Call once at app startup.
1039
+ *
1040
+ * @example
1041
+ * ```ts
1042
+ * Runhuman.init({ apiKey: 'rh_your_api_key' });
1043
+ * ```
1044
+ */
1045
+ static init(config) {
1046
+ if (_Runhuman.instance) {
1047
+ throw new Error("Runhuman.init() called twice \u2014 use Runhuman.getInstance() or call destroy() first");
1048
+ }
1049
+ _Runhuman.instance = new _Runhuman(config);
1050
+ return _Runhuman.instance;
1051
+ }
1052
+ /**
1053
+ * Get the existing Runhuman instance.
1054
+ * Throws if init() hasn't been called.
1055
+ */
1056
+ static getInstance() {
1057
+ if (!_Runhuman.instance) {
1058
+ throw new Error("Runhuman not initialized \u2014 call Runhuman.init() first");
1059
+ }
1060
+ return _Runhuman.instance;
1061
+ }
1062
+ /**
1063
+ * Manually activate a telemetry session for a specific job.
1064
+ * Use this when you receive a deep link or know the job ID.
1065
+ */
1066
+ activate(jobId) {
1067
+ this.sessionManager.activate(jobId);
1068
+ }
1069
+ /**
1070
+ * Manually deactivate the current session.
1071
+ * Flushes remaining events and ends the session.
1072
+ */
1073
+ async deactivate() {
1074
+ await this.sessionManager.deactivate();
1075
+ }
1076
+ /**
1077
+ * Full teardown — stops polling, ends any active session, cleans up listeners.
1078
+ * After calling destroy(), you can call init() again.
1079
+ */
1080
+ async destroy() {
1081
+ await this.sessionManager.destroy();
1082
+ if (this.deepLinkHandler) {
1083
+ this.deepLinkHandler.uninstall();
1084
+ }
1085
+ _Runhuman.instance = null;
1086
+ this.log("Destroyed");
1087
+ }
1088
+ log(message) {
1089
+ if (this.debug) {
1090
+ console.debug(`[Runhuman] ${message}`);
1091
+ }
1092
+ }
1093
+ };
1094
+ _Runhuman.instance = null;
1095
+ var Runhuman = _Runhuman;
1096
+ export {
1097
+ Runhuman
1098
+ };
1099
+ //# sourceMappingURL=index.js.map