@skippr/live-agent-sdk 0.14.0 → 0.16.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.
@@ -1,53 +1,219 @@
1
1
  // src/components/LiveAgent.tsx
2
2
  import { LiveKitRoom, RoomAudioRenderer } from "@livekit/components-react";
3
- import { useCallback as useCallback5, useMemo as useMemo4, useState as useState5 } from "react";
3
+ import { useCallback as useCallback7, useMemo as useMemo4, useState as useState7 } from "react";
4
4
 
5
5
  // src/context/LiveAgentContext.tsx
6
6
  import { createContext } from "react";
7
7
  var LiveAgentContext = createContext(null);
8
8
 
9
- // src/hooks/useSession.ts
10
- import { useCallback, useState } from "react";
9
+ // src/hooks/useAuth.ts
10
+ import { useCallback, useEffect, useState } from "react";
11
11
  var API_URL = "https://skipprapi-production.up.railway.app";
12
- function resolveAuthHeaders(authToken, appKey) {
13
- if (authToken)
14
- return { Authorization: `Bearer ${authToken}` };
15
- if (appKey)
16
- return { "X-App-Key": appKey };
17
- return {};
12
+ function storageKey(appKey) {
13
+ return `skippr_auth_${appKey}`;
18
14
  }
19
- function useSession({ agentId, authToken, appKey }) {
20
- const [connection, setConnection] = useState(null);
21
- const [shouldConnect, setShouldConnect] = useState(false);
22
- const [isStarting, setIsStarting] = useState(false);
15
+ function getStoredToken(appKey) {
16
+ try {
17
+ return localStorage.getItem(storageKey(appKey));
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+ function setStoredToken(appKey, token) {
23
+ try {
24
+ localStorage.setItem(storageKey(appKey), token);
25
+ } catch (e) {
26
+ console.error("[Skippr] Failed to persist auth token:", e);
27
+ }
28
+ }
29
+ function clearStoredToken(appKey) {
30
+ try {
31
+ localStorage.removeItem(storageKey(appKey));
32
+ } catch (e) {
33
+ console.error("[Skippr] Failed to clear auth token:", e);
34
+ }
35
+ }
36
+ function useAuth({ appKey }) {
37
+ const [authToken, setAuthToken] = useState(null);
38
+ const [isValidating, setIsValidating] = useState(false);
39
+ const [isSubmitting, setIsSubmitting] = useState(false);
23
40
  const [error, setError] = useState("");
24
- const [sessionId, setSessionId] = useState(null);
25
- const [sessionToken, setSessionToken] = useState(null);
26
- const startSession = useCallback(async () => {
41
+ const isAuthenticated = authToken !== null;
42
+ useEffect(() => {
43
+ if (!appKey)
44
+ return;
45
+ const stored = getStoredToken(appKey);
46
+ if (!stored)
47
+ return;
48
+ async function validateStoredToken() {
49
+ setIsValidating(true);
50
+ try {
51
+ const res = await fetch(`${API_URL}/v1/auth/validate-token`, {
52
+ headers: { Authorization: `Bearer ${stored}` }
53
+ });
54
+ if (res.ok) {
55
+ setAuthToken(stored);
56
+ } else {
57
+ clearStoredToken(appKey);
58
+ }
59
+ } catch {
60
+ clearStoredToken(appKey);
61
+ } finally {
62
+ setIsValidating(false);
63
+ }
64
+ }
65
+ validateStoredToken();
66
+ }, [appKey]);
67
+ useEffect(() => {
68
+ function handleLogout() {
69
+ setAuthToken(null);
70
+ setError("");
71
+ }
72
+ window.addEventListener("skippr:logout", handleLogout);
73
+ return () => window.removeEventListener("skippr:logout", handleLogout);
74
+ }, []);
75
+ const requestOtp = useCallback(async (email) => {
76
+ if (!appKey)
77
+ return false;
78
+ setIsSubmitting(true);
79
+ setError("");
80
+ try {
81
+ const resp = await fetch(`${API_URL}/v1/auth/request-otp`, {
82
+ method: "POST",
83
+ headers: {
84
+ "Content-Type": "application/json",
85
+ "X-App-Key": appKey
86
+ },
87
+ body: JSON.stringify({ email })
88
+ });
89
+ if (!resp.ok) {
90
+ const body = await resp.json().catch(() => ({}));
91
+ throw new Error(body.detail || "Failed to send verification code");
92
+ }
93
+ return true;
94
+ } catch (e) {
95
+ setError(e instanceof Error ? e.message : "Failed to send verification code");
96
+ return false;
97
+ } finally {
98
+ setIsSubmitting(false);
99
+ }
100
+ }, [appKey]);
101
+ const verifyOtp = useCallback(async (email, code) => {
102
+ if (!appKey)
103
+ return;
104
+ setIsSubmitting(true);
105
+ setError("");
106
+ try {
107
+ const resp = await fetch(`${API_URL}/v1/auth/verify-otp`, {
108
+ method: "POST",
109
+ headers: {
110
+ "Content-Type": "application/json",
111
+ "X-App-Key": appKey
112
+ },
113
+ body: JSON.stringify({ email, code, recaptchaToken: "sdk" })
114
+ });
115
+ if (!resp.ok) {
116
+ const body = await resp.json().catch(() => ({}));
117
+ throw new Error(body.detail || "Invalid or expired verification code");
118
+ }
119
+ const { token } = await resp.json();
120
+ setStoredToken(appKey, token);
121
+ setAuthToken(token);
122
+ } catch (e) {
123
+ setError(e instanceof Error ? e.message : "Verification failed");
124
+ return;
125
+ } finally {
126
+ setIsSubmitting(false);
127
+ }
128
+ }, [appKey]);
129
+ const logout = useCallback(() => {
130
+ if (appKey)
131
+ clearStoredToken(appKey);
132
+ setAuthToken(null);
133
+ setError("");
134
+ }, [appKey]);
135
+ return {
136
+ isAuthenticated,
137
+ isValidating,
138
+ authToken,
139
+ requestOtp,
140
+ verifyOtp,
141
+ logout,
142
+ error,
143
+ isSubmitting
144
+ };
145
+ }
146
+
147
+ // src/hooks/useSession.ts
148
+ import { useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
149
+ var API_URL2 = "https://skipprapi-production.up.railway.app";
150
+ async function exchangeForBearerToken(appKey, userToken) {
151
+ const resp = await fetch(`${API_URL2}/v1/auth/token-exchange`, {
152
+ method: "POST",
153
+ headers: {
154
+ "X-App-Key": appKey,
155
+ "X-User-Token": userToken
156
+ }
157
+ });
158
+ if (!resp.ok) {
159
+ throw new Error(`Token exchange failed: ${resp.status}`);
160
+ }
161
+ const { token } = await resp.json();
162
+ return token;
163
+ }
164
+ function useSession({ agentId, authToken, appKey, userToken }) {
165
+ const [connection, setConnection] = useState2(null);
166
+ const [shouldConnect, setShouldConnect] = useState2(false);
167
+ const [isStarting, setIsStarting] = useState2(false);
168
+ const [error, setError] = useState2("");
169
+ const [sessionId, setSessionId] = useState2(null);
170
+ const [bearerToken, setBearerToken] = useState2(authToken ?? null);
171
+ useEffect2(() => {
172
+ let stale = false;
173
+ if (authToken) {
174
+ setBearerToken(authToken);
175
+ return;
176
+ }
177
+ if (appKey && userToken) {
178
+ exchangeForBearerToken(appKey, userToken).then((token) => {
179
+ if (!stale)
180
+ setBearerToken(token);
181
+ }).catch((e) => {
182
+ if (!stale)
183
+ setError(e instanceof Error ? e.message : "Token exchange failed");
184
+ });
185
+ }
186
+ return () => {
187
+ stale = true;
188
+ };
189
+ }, [authToken, appKey, userToken]);
190
+ const startSession = useCallback2(async () => {
191
+ if (!bearerToken) {
192
+ setError("No auth token available");
193
+ return;
194
+ }
195
+ const headers = { Authorization: `Bearer ${bearerToken}` };
27
196
  setIsStarting(true);
28
197
  setError("");
29
198
  try {
30
- const authHeaders = resolveAuthHeaders(authToken, appKey);
31
- const createResp = await fetch(`${API_URL}/v1/sessions`, {
199
+ const createResp = await fetch(`${API_URL2}/v1/sessions`, {
32
200
  method: "POST",
33
- headers: { "Content-Type": "application/json", ...authHeaders },
201
+ headers: { "Content-Type": "application/json", ...headers },
34
202
  body: JSON.stringify({ agentId })
35
203
  });
36
204
  if (!createResp.ok) {
37
205
  throw new Error(`Failed to create session: ${createResp.status}`);
38
206
  }
39
207
  const { session } = await createResp.json();
40
- const sessionHeaders = { "X-Session-Token": session.sessionToken };
41
- const startResp = await fetch(`${API_URL}/v1/sessions/${session.id}/start`, {
208
+ const startResp = await fetch(`${API_URL2}/v1/sessions/${session.id}/start`, {
42
209
  method: "POST",
43
- headers: sessionHeaders
210
+ headers
44
211
  });
45
212
  if (!startResp.ok) {
46
213
  throw new Error(`Failed to start session: ${startResp.status}`);
47
214
  }
48
215
  const { connection: conn } = await startResp.json();
49
216
  setSessionId(session.id);
50
- setSessionToken(session.sessionToken);
51
217
  setConnection({
52
218
  livekitUrl: conn.livekitUrl,
53
219
  token: conn.token
@@ -58,14 +224,13 @@ function useSession({ agentId, authToken, appKey }) {
58
224
  } finally {
59
225
  setIsStarting(false);
60
226
  }
61
- }, [agentId, authToken, appKey]);
62
- const disconnect = useCallback(async () => {
63
- if (sessionId && sessionToken) {
64
- const sessionHeaders = { "X-Session-Token": sessionToken };
227
+ }, [agentId, bearerToken]);
228
+ const disconnect = useCallback2(async () => {
229
+ if (sessionId && bearerToken) {
65
230
  try {
66
- await fetch(`${API_URL}/v1/sessions/${sessionId}/complete`, {
231
+ await fetch(`${API_URL2}/v1/sessions/${sessionId}/complete`, {
67
232
  method: "POST",
68
- headers: { "Content-Type": "application/json", ...sessionHeaders },
233
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${bearerToken}` },
69
234
  body: JSON.stringify({})
70
235
  });
71
236
  } catch {}
@@ -74,15 +239,14 @@ function useSession({ agentId, authToken, appKey }) {
74
239
  setShouldConnect(false);
75
240
  setConnection(null);
76
241
  setSessionId(null);
77
- setSessionToken(null);
78
- }, [sessionId, sessionToken]);
242
+ }, [sessionId, bearerToken]);
79
243
  return { connection, shouldConnect, isStarting, error, startSession, disconnect };
80
244
  }
81
245
 
82
246
  // src/components/Sidebar.tsx
83
247
  import { useConnectionState } from "@livekit/components-react";
84
248
  import { ConnectionState } from "livekit-client";
85
- import { useEffect as useEffect4 } from "react";
249
+ import { useEffect as useEffect7 } from "react";
86
250
 
87
251
  // src/hooks/useCombinedMessages.ts
88
252
  import { useVoiceAssistant } from "@livekit/components-react";
@@ -176,15 +340,15 @@ function useLiveAgent() {
176
340
  }
177
341
 
178
342
  // src/hooks/usePhaseUpdates.ts
179
- import { useCallback as useCallback2 } from "react";
343
+ import { useCallback as useCallback3 } from "react";
180
344
 
181
345
  // src/hooks/useAgentState.ts
182
346
  import { useRemoteParticipants } from "@livekit/components-react";
183
- import { useEffect, useState as useState2 } from "react";
347
+ import { useEffect as useEffect3, useState as useState3 } from "react";
184
348
  function useAgentState(attributeKey, parse, initial) {
185
- const [value, setValue] = useState2(initial);
349
+ const [value, setValue] = useState3(initial);
186
350
  const remoteParticipants = useRemoteParticipants();
187
- useEffect(() => {
351
+ useEffect3(() => {
188
352
  const agentParticipant = remoteParticipants.find((p) => p.attributes?.[attributeKey]);
189
353
  if (agentParticipant) {
190
354
  const attr = agentParticipant.attributes?.[attributeKey];
@@ -230,13 +394,13 @@ function parsePhases(json) {
230
394
  return null;
231
395
  }
232
396
  function usePhaseUpdates() {
233
- const parse = useCallback2(parsePhases, []);
397
+ const parse = useCallback3(parsePhases, []);
234
398
  const phases = useAgentState("phases", parse, []);
235
399
  return { phases };
236
400
  }
237
401
 
238
402
  // src/hooks/useQuestionUpdates.ts
239
- import { useCallback as useCallback3 } from "react";
403
+ import { useCallback as useCallback4 } from "react";
240
404
  function parseQuestions(json) {
241
405
  try {
242
406
  const data = JSON.parse(json);
@@ -247,7 +411,7 @@ function parseQuestions(json) {
247
411
  return null;
248
412
  }
249
413
  function useQuestionUpdates() {
250
- const parse = useCallback3(parseQuestions, []);
414
+ const parse = useCallback4(parseQuestions, []);
251
415
  const questions = useAgentState("questions", parse, []);
252
416
  return { questions };
253
417
  }
@@ -388,8 +552,14 @@ var Check = createLucideIcon("check", __iconNode5);
388
552
  // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/circle.js
389
553
  var __iconNode6 = [["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }]];
390
554
  var Circle = createLucideIcon("circle", __iconNode6);
391
- // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/message-circle.js
555
+ // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/mail.js
392
556
  var __iconNode7 = [
557
+ ["path", { d: "m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7", key: "132q7q" }],
558
+ ["rect", { x: "2", y: "4", width: "20", height: "16", rx: "2", key: "izxlao" }]
559
+ ];
560
+ var Mail = createLucideIcon("mail", __iconNode7);
561
+ // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/message-circle.js
562
+ var __iconNode8 = [
393
563
  [
394
564
  "path",
395
565
  {
@@ -398,9 +568,9 @@ var __iconNode7 = [
398
568
  }
399
569
  ]
400
570
  ];
401
- var MessageCircle = createLucideIcon("message-circle", __iconNode7);
571
+ var MessageCircle = createLucideIcon("message-circle", __iconNode8);
402
572
  // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/mic-off.js
403
- var __iconNode8 = [
573
+ var __iconNode9 = [
404
574
  ["path", { d: "M12 19v3", key: "npa21l" }],
405
575
  ["path", { d: "M15 9.34V5a3 3 0 0 0-5.68-1.33", key: "1gzdoj" }],
406
576
  ["path", { d: "M16.95 16.95A7 7 0 0 1 5 12v-2", key: "cqa7eg" }],
@@ -408,32 +578,32 @@ var __iconNode8 = [
408
578
  ["path", { d: "m2 2 20 20", key: "1ooewy" }],
409
579
  ["path", { d: "M9 9v3a3 3 0 0 0 5.12 2.12", key: "r2i35w" }]
410
580
  ];
411
- var MicOff = createLucideIcon("mic-off", __iconNode8);
581
+ var MicOff = createLucideIcon("mic-off", __iconNode9);
412
582
  // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/mic.js
413
- var __iconNode9 = [
583
+ var __iconNode10 = [
414
584
  ["path", { d: "M12 19v3", key: "npa21l" }],
415
585
  ["path", { d: "M19 10v2a7 7 0 0 1-14 0v-2", key: "1vc78b" }],
416
586
  ["rect", { x: "9", y: "2", width: "6", height: "13", rx: "3", key: "s6n7sd" }]
417
587
  ];
418
- var Mic = createLucideIcon("mic", __iconNode9);
588
+ var Mic = createLucideIcon("mic", __iconNode10);
419
589
  // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/monitor-off.js
420
- var __iconNode10 = [
590
+ var __iconNode11 = [
421
591
  ["path", { d: "M12 17v4", key: "1riwvh" }],
422
592
  ["path", { d: "M17 17H4a2 2 0 0 1-2-2V5a2 2 0 0 1 1.184-1.826", key: "cv7jms" }],
423
593
  ["path", { d: "m2 2 20 20", key: "1ooewy" }],
424
594
  ["path", { d: "M8 21h8", key: "1ev6f3" }],
425
595
  ["path", { d: "M8.656 3H20a2 2 0 0 1 2 2v10a2 2 0 0 1-.293 1.042", key: "z8ni2w" }]
426
596
  ];
427
- var MonitorOff = createLucideIcon("monitor-off", __iconNode10);
597
+ var MonitorOff = createLucideIcon("monitor-off", __iconNode11);
428
598
  // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/monitor.js
429
- var __iconNode11 = [
599
+ var __iconNode12 = [
430
600
  ["rect", { width: "20", height: "14", x: "2", y: "3", rx: "2", key: "48i651" }],
431
601
  ["line", { x1: "8", x2: "16", y1: "21", y2: "21", key: "1svkeh" }],
432
602
  ["line", { x1: "12", x2: "12", y1: "17", y2: "21", key: "vw1qmm" }]
433
603
  ];
434
- var Monitor = createLucideIcon("monitor", __iconNode11);
604
+ var Monitor = createLucideIcon("monitor", __iconNode12);
435
605
  // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/phone-off.js
436
- var __iconNode12 = [
606
+ var __iconNode13 = [
437
607
  [
438
608
  "path",
439
609
  {
@@ -450,13 +620,13 @@ var __iconNode12 = [
450
620
  }
451
621
  ]
452
622
  ];
453
- var PhoneOff = createLucideIcon("phone-off", __iconNode12);
623
+ var PhoneOff = createLucideIcon("phone-off", __iconNode13);
454
624
  // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/x.js
455
- var __iconNode13 = [
625
+ var __iconNode14 = [
456
626
  ["path", { d: "M18 6 6 18", key: "1bl5f8" }],
457
627
  ["path", { d: "m6 6 12 12", key: "d8bk6v" }]
458
628
  ];
459
- var X = createLucideIcon("x", __iconNode13);
629
+ var X = createLucideIcon("x", __iconNode14);
460
630
  // src/components/ui/button.tsx
461
631
  import { forwardRef as forwardRef3 } from "react";
462
632
  import { jsx } from "react/jsx-runtime";
@@ -518,10 +688,257 @@ function ChatHeader({ onClose }) {
518
688
  });
519
689
  }
520
690
 
691
+ // src/components/LoginFlow.tsx
692
+ import { useCallback as useCallback5, useEffect as useEffect4, useRef, useState as useState4 } from "react";
693
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
694
+ var OTP_LENGTH = 6;
695
+ var DIGIT_KEYS = ["d0", "d1", "d2", "d3", "d4", "d5"];
696
+ function LoginFlow({ requestOtp, verifyOtp, error, isSubmitting }) {
697
+ const [step, setStep] = useState4("email");
698
+ const [email, setEmail] = useState4("");
699
+ const handleRequestOtp = useCallback5(async (emailValue) => {
700
+ const success = await requestOtp(emailValue);
701
+ if (success)
702
+ setStep("otp");
703
+ }, [requestOtp]);
704
+ const handleVerifyOtp = useCallback5(async (code) => {
705
+ await verifyOtp(email, code);
706
+ }, [verifyOtp, email]);
707
+ const handleBack = useCallback5(() => {
708
+ setStep("email");
709
+ }, []);
710
+ const handleResend = useCallback5(async () => {
711
+ await requestOtp(email);
712
+ }, [requestOtp, email]);
713
+ if (step === "otp") {
714
+ return /* @__PURE__ */ jsx3(OtpStep, {
715
+ email,
716
+ onSubmit: handleVerifyOtp,
717
+ onResend: handleResend,
718
+ onBack: handleBack,
719
+ error,
720
+ isSubmitting
721
+ });
722
+ }
723
+ return /* @__PURE__ */ jsx3(EmailStep, {
724
+ email,
725
+ onEmailChange: setEmail,
726
+ onSubmit: handleRequestOtp,
727
+ error,
728
+ isSubmitting
729
+ });
730
+ }
731
+ function EmailStep({ email, onEmailChange, onSubmit, error, isSubmitting }) {
732
+ function handleSubmit(e) {
733
+ e.preventDefault();
734
+ if (email.trim())
735
+ onSubmit(email.trim());
736
+ }
737
+ return /* @__PURE__ */ jsxs2("div", {
738
+ className: "skippr:flex skippr:flex-1 skippr:flex-col skippr:px-4 skippr:py-4",
739
+ children: [
740
+ /* @__PURE__ */ jsxs2("div", {
741
+ className: "skippr:mb-4 skippr:text-center",
742
+ children: [
743
+ /* @__PURE__ */ jsx3(Mail, {
744
+ className: "skippr:mx-auto skippr:mb-2 skippr:size-6 skippr:text-primary"
745
+ }),
746
+ /* @__PURE__ */ jsx3("p", {
747
+ className: "skippr:text-sm skippr:font-medium skippr:text-foreground",
748
+ children: "Sign in to continue"
749
+ }),
750
+ /* @__PURE__ */ jsx3("p", {
751
+ className: "skippr:mt-1 skippr:text-xs skippr:text-muted-foreground",
752
+ children: "Your email will be used to identify you across sessions"
753
+ })
754
+ ]
755
+ }),
756
+ /* @__PURE__ */ jsxs2("form", {
757
+ onSubmit: handleSubmit,
758
+ className: "skippr:flex skippr:flex-col skippr:gap-3",
759
+ children: [
760
+ /* @__PURE__ */ jsx3("input", {
761
+ type: "email",
762
+ placeholder: "you@example.com",
763
+ value: email,
764
+ onChange: (e) => onEmailChange(e.target.value),
765
+ disabled: isSubmitting,
766
+ required: true,
767
+ className: "skippr:w-full skippr:rounded-md skippr:border skippr:border-border skippr:bg-background skippr:px-3 skippr:py-2 skippr:text-sm skippr:text-foreground skippr:placeholder-muted-foreground skippr:outline-none focus:skippr:ring-2 focus:skippr:ring-primary/30 focus:skippr:border-primary disabled:skippr:opacity-50"
768
+ }),
769
+ /* @__PURE__ */ jsx3(Button, {
770
+ type: "submit",
771
+ disabled: isSubmitting || !email.trim(),
772
+ className: "skippr:w-full",
773
+ children: isSubmitting ? /* @__PURE__ */ jsx3(LoaderCircle, {
774
+ className: "skippr:size-4 skippr:animate-spin"
775
+ }) : "Continue"
776
+ }),
777
+ error && /* @__PURE__ */ jsx3("p", {
778
+ className: "skippr:text-xs skippr:text-center skippr:text-destructive",
779
+ children: error
780
+ })
781
+ ]
782
+ })
783
+ ]
784
+ });
785
+ }
786
+ function OtpStep({ email, onSubmit, onResend, onBack, error, isSubmitting }) {
787
+ const [digits, setDigits] = useState4(Array(OTP_LENGTH).fill(""));
788
+ const [resendCooldown, setResendCooldown] = useState4(0);
789
+ const inputRefs = useRef([]);
790
+ const submittedRef = useRef(false);
791
+ useEffect4(() => {
792
+ inputRefs.current[0]?.focus();
793
+ }, []);
794
+ useEffect4(() => {
795
+ if (error)
796
+ submittedRef.current = false;
797
+ }, [error]);
798
+ useEffect4(() => {
799
+ if (resendCooldown <= 0)
800
+ return;
801
+ const timer = setTimeout(() => setResendCooldown((c) => c - 1), 1000);
802
+ return () => clearTimeout(timer);
803
+ }, [resendCooldown]);
804
+ const submitCode = useCallback5((code) => {
805
+ if (submittedRef.current || isSubmitting)
806
+ return;
807
+ submittedRef.current = true;
808
+ onSubmit(code);
809
+ }, [onSubmit, isSubmitting]);
810
+ const handleDigitChange = useCallback5((index2, value) => {
811
+ const digit = value.replace(/\D/g, "").slice(-1);
812
+ const newDigits = [...digits];
813
+ newDigits[index2] = digit;
814
+ setDigits(newDigits);
815
+ if (digit && index2 < OTP_LENGTH - 1) {
816
+ inputRefs.current[index2 + 1]?.focus();
817
+ }
818
+ if (digit && index2 === OTP_LENGTH - 1) {
819
+ const code = newDigits.join("");
820
+ if (code.length === OTP_LENGTH)
821
+ submitCode(code);
822
+ }
823
+ }, [digits, submitCode]);
824
+ const handleKeyDown = useCallback5((index2, e) => {
825
+ if (e.key === "Backspace" && !digits[index2] && index2 > 0) {
826
+ inputRefs.current[index2 - 1]?.focus();
827
+ }
828
+ }, [digits]);
829
+ const handlePaste = useCallback5((e) => {
830
+ e.preventDefault();
831
+ const pasted = e.clipboardData.getData("text").replace(/\D/g, "").slice(0, OTP_LENGTH);
832
+ if (pasted.length > 0) {
833
+ const newDigits = Array(OTP_LENGTH).fill("");
834
+ for (let i = 0;i < pasted.length; i++) {
835
+ newDigits[i] = pasted[i];
836
+ }
837
+ setDigits(newDigits);
838
+ if (pasted.length === OTP_LENGTH)
839
+ submitCode(pasted);
840
+ else
841
+ inputRefs.current[pasted.length]?.focus();
842
+ }
843
+ }, [submitCode]);
844
+ function handleSubmit(e) {
845
+ e.preventDefault();
846
+ const code = digits.join("");
847
+ if (code.length === OTP_LENGTH)
848
+ submitCode(code);
849
+ }
850
+ function handleResend() {
851
+ onResend();
852
+ setResendCooldown(30);
853
+ setDigits(Array(OTP_LENGTH).fill(""));
854
+ submittedRef.current = false;
855
+ inputRefs.current[0]?.focus();
856
+ }
857
+ return /* @__PURE__ */ jsxs2("div", {
858
+ className: "skippr:flex skippr:flex-1 skippr:flex-col skippr:px-4 skippr:py-4",
859
+ children: [
860
+ /* @__PURE__ */ jsxs2("div", {
861
+ className: "skippr:mb-4 skippr:text-center",
862
+ children: [
863
+ /* @__PURE__ */ jsx3("p", {
864
+ className: "skippr:text-sm skippr:font-medium skippr:text-foreground",
865
+ children: "Enter verification code"
866
+ }),
867
+ /* @__PURE__ */ jsxs2("p", {
868
+ className: "skippr:mt-1 skippr:text-xs skippr:text-muted-foreground",
869
+ children: [
870
+ "We sent a 6-digit code to",
871
+ " ",
872
+ /* @__PURE__ */ jsx3("span", {
873
+ className: "skippr:font-medium skippr:text-foreground",
874
+ children: email
875
+ })
876
+ ]
877
+ })
878
+ ]
879
+ }),
880
+ /* @__PURE__ */ jsxs2("form", {
881
+ onSubmit: handleSubmit,
882
+ className: "skippr:flex skippr:flex-col skippr:gap-3",
883
+ children: [
884
+ /* @__PURE__ */ jsx3("div", {
885
+ className: "skippr:flex skippr:justify-center skippr:gap-1.5",
886
+ children: digits.map((digit, index2) => /* @__PURE__ */ jsx3("input", {
887
+ ref: (el) => {
888
+ inputRefs.current[index2] = el;
889
+ },
890
+ type: "text",
891
+ inputMode: "numeric",
892
+ maxLength: 1,
893
+ value: digit,
894
+ onChange: (e) => handleDigitChange(index2, e.target.value),
895
+ onKeyDown: (e) => handleKeyDown(index2, e),
896
+ onPaste: handlePaste,
897
+ disabled: isSubmitting,
898
+ className: "skippr:h-10 skippr:w-10 skippr:rounded-md skippr:border skippr:border-border skippr:bg-background skippr:text-center skippr:text-sm skippr:font-semibold skippr:text-foreground skippr:outline-none focus:skippr:ring-2 focus:skippr:ring-primary/30 focus:skippr:border-primary disabled:skippr:opacity-50"
899
+ }, DIGIT_KEYS[index2]))
900
+ }),
901
+ error && /* @__PURE__ */ jsx3("p", {
902
+ className: "skippr:text-xs skippr:text-center skippr:text-destructive",
903
+ children: error
904
+ }),
905
+ /* @__PURE__ */ jsx3(Button, {
906
+ type: "submit",
907
+ disabled: isSubmitting || digits.join("").length !== OTP_LENGTH,
908
+ className: "skippr:w-full",
909
+ children: isSubmitting ? /* @__PURE__ */ jsx3(LoaderCircle, {
910
+ className: "skippr:size-4 skippr:animate-spin"
911
+ }) : "Verify"
912
+ }),
913
+ /* @__PURE__ */ jsxs2("div", {
914
+ className: "skippr:flex skippr:items-center skippr:justify-between skippr:text-xs",
915
+ children: [
916
+ /* @__PURE__ */ jsx3("button", {
917
+ type: "button",
918
+ onClick: onBack,
919
+ disabled: isSubmitting,
920
+ className: "skippr:text-muted-foreground hover:skippr:text-foreground skippr:transition-colors disabled:skippr:opacity-50",
921
+ children: "Change email"
922
+ }),
923
+ /* @__PURE__ */ jsx3("button", {
924
+ type: "button",
925
+ onClick: handleResend,
926
+ disabled: isSubmitting || resendCooldown > 0,
927
+ className: "skippr:text-primary hover:skippr:opacity-80 skippr:transition-opacity disabled:skippr:opacity-50",
928
+ children: resendCooldown > 0 ? `Resend in ${resendCooldown}s` : "Resend code"
929
+ })
930
+ ]
931
+ })
932
+ ]
933
+ })
934
+ ]
935
+ });
936
+ }
937
+
521
938
  // src/components/MeetingControls.tsx
522
939
  import { useLocalParticipant as useLocalParticipant3 } from "@livekit/components-react";
523
940
  import { ScreenSharePresets } from "livekit-client";
524
- import { useCallback as useCallback4, useEffect as useEffect2, useRef, useState as useState3 } from "react";
941
+ import { useCallback as useCallback6, useEffect as useEffect5, useRef as useRef2, useState as useState5 } from "react";
525
942
 
526
943
  // src/lib/format.ts
527
944
  function formatTime(seconds) {
@@ -535,12 +952,12 @@ function parseNumber(s) {
535
952
  }
536
953
 
537
954
  // src/components/SessionWarningBanner.tsx
538
- import { jsx as jsx3 } from "react/jsx-runtime";
955
+ import { jsx as jsx4 } from "react/jsx-runtime";
539
956
  var SESSION_WARNING_THRESHOLD_SECS = 60;
540
957
  function SessionWarningBanner({ remaining }) {
541
958
  if (remaining === null || remaining <= 0 || remaining > SESSION_WARNING_THRESHOLD_SECS)
542
959
  return null;
543
- return /* @__PURE__ */ jsx3("div", {
960
+ return /* @__PURE__ */ jsx4("div", {
544
961
  "data-testid": "session-warning-banner",
545
962
  className: "skippr:bg-red-50 skippr:px-4 skippr:py-1.5 skippr:text-center skippr:text-xs skippr:font-medium skippr:text-red-700",
546
963
  children: "Session ending soon"
@@ -548,15 +965,15 @@ function SessionWarningBanner({ remaining }) {
548
965
  }
549
966
 
550
967
  // src/components/MeetingControls.tsx
551
- import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
968
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
552
969
  function MeetingControls({ onHangUp }) {
553
970
  const maxCallDuration = useAgentState("maxCallDuration", parseNumber, null);
554
971
  const { localParticipant } = useLocalParticipant3();
555
972
  const isMuted = !localParticipant.isMicrophoneEnabled;
556
973
  const isScreenSharing = localParticipant.isScreenShareEnabled;
557
- const endTimeRef = useRef(null);
558
- const [remaining, setRemaining] = useState3(null);
559
- useEffect2(() => {
974
+ const endTimeRef = useRef2(null);
975
+ const [remaining, setRemaining] = useState5(null);
976
+ useEffect5(() => {
560
977
  if (maxCallDuration === null || endTimeRef.current !== null)
561
978
  return;
562
979
  endTimeRef.current = Date.now() + maxCallDuration * 1000;
@@ -568,14 +985,14 @@ function MeetingControls({ onHangUp }) {
568
985
  const id = setInterval(tick, 1000);
569
986
  return () => clearInterval(id);
570
987
  }, [maxCallDuration]);
571
- const toggleMute = useCallback4(async () => {
988
+ const toggleMute = useCallback6(async () => {
572
989
  try {
573
990
  await localParticipant.setMicrophoneEnabled(isMuted);
574
991
  } catch (e) {
575
992
  console.error("Failed to toggle microphone:", e);
576
993
  }
577
994
  }, [localParticipant, isMuted]);
578
- const toggleScreenShare = useCallback4(async () => {
995
+ const toggleScreenShare = useCallback6(async () => {
579
996
  try {
580
997
  await localParticipant.setScreenShareEnabled(!isScreenSharing, {
581
998
  video: { displaySurface: "browser" },
@@ -586,54 +1003,54 @@ function MeetingControls({ onHangUp }) {
586
1003
  console.error("Failed to toggle screen share:", e);
587
1004
  }
588
1005
  }, [localParticipant, isScreenSharing]);
589
- useEffect2(() => {
1006
+ useEffect5(() => {
590
1007
  toggleMute().then(() => toggleScreenShare());
591
1008
  }, []);
592
- return /* @__PURE__ */ jsxs2("div", {
1009
+ return /* @__PURE__ */ jsxs3("div", {
593
1010
  children: [
594
- /* @__PURE__ */ jsx4(SessionWarningBanner, {
1011
+ /* @__PURE__ */ jsx5(SessionWarningBanner, {
595
1012
  remaining
596
1013
  }),
597
- /* @__PURE__ */ jsxs2("div", {
1014
+ /* @__PURE__ */ jsxs3("div", {
598
1015
  className: "skippr:flex skippr:items-center skippr:justify-between skippr:border-b skippr:px-4 skippr:py-3",
599
1016
  children: [
600
- /* @__PURE__ */ jsxs2("div", {
1017
+ /* @__PURE__ */ jsxs3("div", {
601
1018
  className: "skippr:flex skippr:items-center skippr:gap-2",
602
1019
  children: [
603
- /* @__PURE__ */ jsx4(Button, {
1020
+ /* @__PURE__ */ jsx5(Button, {
604
1021
  size: "icon-sm",
605
1022
  variant: isMuted ? "destructive" : "outline",
606
1023
  onClick: toggleMute,
607
1024
  "aria-label": isMuted ? "Unmute" : "Mute",
608
- children: isMuted ? /* @__PURE__ */ jsx4(MicOff, {
1025
+ children: isMuted ? /* @__PURE__ */ jsx5(MicOff, {
609
1026
  className: "skippr:size-4"
610
- }) : /* @__PURE__ */ jsx4(Mic, {
1027
+ }) : /* @__PURE__ */ jsx5(Mic, {
611
1028
  className: "skippr:size-4"
612
1029
  })
613
1030
  }),
614
- /* @__PURE__ */ jsx4(Button, {
1031
+ /* @__PURE__ */ jsx5(Button, {
615
1032
  size: "icon-sm",
616
1033
  variant: isScreenSharing ? "default" : "outline",
617
1034
  onClick: toggleScreenShare,
618
1035
  "aria-label": isScreenSharing ? "Stop sharing" : "Share screen",
619
- children: isScreenSharing ? /* @__PURE__ */ jsx4(MonitorOff, {
1036
+ children: isScreenSharing ? /* @__PURE__ */ jsx5(MonitorOff, {
620
1037
  className: "skippr:size-4"
621
- }) : /* @__PURE__ */ jsx4(Monitor, {
1038
+ }) : /* @__PURE__ */ jsx5(Monitor, {
622
1039
  className: "skippr:size-4"
623
1040
  })
624
1041
  })
625
1042
  ]
626
1043
  }),
627
- remaining !== null && /* @__PURE__ */ jsx4("span", {
1044
+ remaining !== null && /* @__PURE__ */ jsx5("span", {
628
1045
  className: cn("skippr:text-sm skippr:font-medium skippr:tabular-nums", remaining <= SESSION_WARNING_THRESHOLD_SECS ? "skippr:text-red-600 skippr:animate-pulse" : "skippr:text-muted-foreground"),
629
1046
  children: formatTime(remaining)
630
1047
  }),
631
- /* @__PURE__ */ jsx4(Button, {
1048
+ /* @__PURE__ */ jsx5(Button, {
632
1049
  size: "icon-sm",
633
1050
  variant: "destructive",
634
1051
  onClick: onHangUp,
635
1052
  "aria-label": "Hang up",
636
- children: /* @__PURE__ */ jsx4(PhoneOff, {
1053
+ children: /* @__PURE__ */ jsx5(PhoneOff, {
637
1054
  className: "skippr:size-4"
638
1055
  })
639
1056
  })
@@ -644,13 +1061,13 @@ function MeetingControls({ onHangUp }) {
644
1061
  }
645
1062
 
646
1063
  // src/components/MessageList.tsx
647
- import { useEffect as useEffect3, useRef as useRef2 } from "react";
1064
+ import { useEffect as useEffect6, useRef as useRef3 } from "react";
648
1065
 
649
1066
  // src/components/ChatInput.tsx
650
- import { useState as useState4 } from "react";
651
- import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1067
+ import { useState as useState6 } from "react";
1068
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
652
1069
  function ChatInput({ sendChatMessage, isSendingChat }) {
653
- const [inputText, setInputText] = useState4("");
1070
+ const [inputText, setInputText] = useState6("");
654
1071
  const canSend = inputText.trim().length > 0 && !isSendingChat;
655
1072
  function handleSubmit(e) {
656
1073
  e.preventDefault();
@@ -660,11 +1077,11 @@ function ChatInput({ sendChatMessage, isSendingChat }) {
660
1077
  setInputText("");
661
1078
  sendChatMessage(text).catch(() => setInputText(text));
662
1079
  }
663
- return /* @__PURE__ */ jsxs3("form", {
1080
+ return /* @__PURE__ */ jsxs4("form", {
664
1081
  onSubmit: handleSubmit,
665
1082
  className: "skippr:flex skippr:items-center skippr:gap-2 skippr:border-t skippr:border-border skippr:px-3 skippr:py-2",
666
1083
  children: [
667
- /* @__PURE__ */ jsx5("input", {
1084
+ /* @__PURE__ */ jsx6("input", {
668
1085
  type: "text",
669
1086
  value: inputText,
670
1087
  onChange: (e) => setInputText(e.target.value),
@@ -672,12 +1089,12 @@ function ChatInput({ sendChatMessage, isSendingChat }) {
672
1089
  className: cn("skippr:flex-1 skippr:rounded-lg skippr:border skippr:border-border skippr:bg-background", "skippr:px-3 skippr:py-2 skippr:text-sm skippr:text-foreground", "skippr:placeholder:text-muted-foreground skippr:outline-none", "skippr:focus:ring-1 skippr:focus:ring-ring"),
673
1090
  disabled: isSendingChat
674
1091
  }),
675
- /* @__PURE__ */ jsx5("button", {
1092
+ /* @__PURE__ */ jsx6("button", {
676
1093
  type: "submit",
677
1094
  disabled: !canSend,
678
1095
  "aria-label": "Send message",
679
1096
  className: cn("skippr:flex skippr:size-9 skippr:shrink-0 skippr:items-center skippr:justify-center", "skippr:rounded-lg skippr:bg-primary skippr:text-primary-foreground", "skippr:transition-opacity", !canSend && "skippr:opacity-50 skippr:cursor-not-allowed"),
680
- children: /* @__PURE__ */ jsx5(SendHorizontal, {
1097
+ children: /* @__PURE__ */ jsx6(SendHorizontal, {
681
1098
  className: "skippr:size-4"
682
1099
  })
683
1100
  })
@@ -686,12 +1103,12 @@ function ChatInput({ sendChatMessage, isSendingChat }) {
686
1103
  }
687
1104
 
688
1105
  // src/components/ChatMessage.tsx
689
- import { jsx as jsx6 } from "react/jsx-runtime";
1106
+ import { jsx as jsx7 } from "react/jsx-runtime";
690
1107
  function ChatMessage({ message }) {
691
1108
  const isUser = message.role === "user";
692
- return /* @__PURE__ */ jsx6("div", {
1109
+ return /* @__PURE__ */ jsx7("div", {
693
1110
  className: cn("skippr:flex skippr:w-full skippr:px-4 skippr:py-1", isUser ? "skippr:justify-end" : "skippr:justify-start"),
694
- children: /* @__PURE__ */ jsx6("div", {
1111
+ children: /* @__PURE__ */ jsx7("div", {
695
1112
  className: cn("skippr:max-w-[85%] skippr:whitespace-pre-wrap skippr:rounded-2xl skippr:px-4 skippr:py-2.5 skippr:text-sm skippr:leading-relaxed", isUser ? "skippr:rounded-br-sm skippr:bg-primary skippr:text-primary-foreground" : "skippr:rounded-bl-sm skippr:bg-muted skippr:text-foreground"),
696
1113
  children: message.content
697
1114
  })
@@ -699,20 +1116,20 @@ function ChatMessage({ message }) {
699
1116
  }
700
1117
 
701
1118
  // src/components/TypingIndicator.tsx
702
- import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
1119
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
703
1120
  function TypingIndicator() {
704
- return /* @__PURE__ */ jsx7("div", {
1121
+ return /* @__PURE__ */ jsx8("div", {
705
1122
  className: "skippr:flex skippr:items-center skippr:gap-1 skippr:px-4 skippr:py-3",
706
- children: /* @__PURE__ */ jsxs4("div", {
1123
+ children: /* @__PURE__ */ jsxs5("div", {
707
1124
  className: "skippr:flex skippr:items-center skippr:gap-1 skippr:rounded-2xl skippr:rounded-bl-sm skippr:bg-muted skippr:px-4 skippr:py-2.5",
708
1125
  children: [
709
- /* @__PURE__ */ jsx7("span", {
1126
+ /* @__PURE__ */ jsx8("span", {
710
1127
  className: "skippr:size-1.5 skippr:animate-bounce skippr:rounded-full skippr:bg-muted-foreground/60 skippr:[animation-delay:0ms]"
711
1128
  }),
712
- /* @__PURE__ */ jsx7("span", {
1129
+ /* @__PURE__ */ jsx8("span", {
713
1130
  className: "skippr:size-1.5 skippr:animate-bounce skippr:rounded-full skippr:bg-muted-foreground/60 skippr:[animation-delay:150ms]"
714
1131
  }),
715
- /* @__PURE__ */ jsx7("span", {
1132
+ /* @__PURE__ */ jsx8("span", {
716
1133
  className: "skippr:size-1.5 skippr:animate-bounce skippr:rounded-full skippr:bg-muted-foreground/60 skippr:[animation-delay:300ms]"
717
1134
  })
718
1135
  ]
@@ -721,38 +1138,38 @@ function TypingIndicator() {
721
1138
  }
722
1139
 
723
1140
  // src/components/MessageList.tsx
724
- import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1141
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
725
1142
  function MessageList({
726
1143
  messages,
727
1144
  isStreaming,
728
1145
  sendChatMessage,
729
1146
  isSendingChat
730
1147
  }) {
731
- const scrollRef = useRef2(null);
1148
+ const scrollRef = useRef3(null);
732
1149
  const lastMessage = messages.length > 0 ? messages[messages.length - 1] : undefined;
733
- useEffect3(() => {
1150
+ useEffect6(() => {
734
1151
  scrollRef.current?.scrollIntoView({ behavior: "smooth" });
735
1152
  }, [messages.length, lastMessage?.content]);
736
1153
  const showTyping = isStreaming && lastMessage?.role === "assistant" && lastMessage.content === "";
737
- return /* @__PURE__ */ jsxs5("div", {
1154
+ return /* @__PURE__ */ jsxs6("div", {
738
1155
  className: "skippr:flex skippr:min-h-0 skippr:flex-1 skippr:flex-col",
739
1156
  children: [
740
- /* @__PURE__ */ jsx8("div", {
1157
+ /* @__PURE__ */ jsx9("div", {
741
1158
  className: "skippr:min-h-0 skippr:flex-1 skippr:overflow-y-auto",
742
- children: /* @__PURE__ */ jsxs5("div", {
1159
+ children: /* @__PURE__ */ jsxs6("div", {
743
1160
  className: "skippr:flex skippr:flex-col skippr:gap-1 skippr:py-3",
744
1161
  children: [
745
- messages.map((message) => /* @__PURE__ */ jsx8(ChatMessage, {
1162
+ messages.map((message) => /* @__PURE__ */ jsx9(ChatMessage, {
746
1163
  message
747
1164
  }, message.id)),
748
- showTyping && /* @__PURE__ */ jsx8(TypingIndicator, {}),
749
- /* @__PURE__ */ jsx8("div", {
1165
+ showTyping && /* @__PURE__ */ jsx9(TypingIndicator, {}),
1166
+ /* @__PURE__ */ jsx9("div", {
750
1167
  ref: scrollRef
751
1168
  })
752
1169
  ]
753
1170
  })
754
1171
  }),
755
- /* @__PURE__ */ jsx8(ChatInput, {
1172
+ /* @__PURE__ */ jsx9(ChatInput, {
756
1173
  sendChatMessage,
757
1174
  isSendingChat
758
1175
  })
@@ -761,30 +1178,30 @@ function MessageList({
761
1178
  }
762
1179
 
763
1180
  // src/components/QuickActions.tsx
764
- import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1181
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
765
1182
  function QuickActions({ onStartSession, isStarting, error }) {
766
- return /* @__PURE__ */ jsxs6("div", {
1183
+ return /* @__PURE__ */ jsxs7("div", {
767
1184
  className: "skippr:flex skippr:flex-1 skippr:flex-col skippr:items-center skippr:gap-6 skippr:overflow-y-auto skippr:px-4 skippr:py-4",
768
1185
  children: [
769
- /* @__PURE__ */ jsx9("p", {
1186
+ /* @__PURE__ */ jsx10("p", {
770
1187
  className: "skippr:mb-1 skippr:text-sm skippr:text-muted-foreground",
771
1188
  children: "How can I help you today?"
772
1189
  }),
773
- /* @__PURE__ */ jsxs6(Button, {
1190
+ /* @__PURE__ */ jsxs7(Button, {
774
1191
  variant: "outline",
775
1192
  className: "skippr:h-auto skippr:flex-col skippr:gap-1.5 skippr:whitespace-normal skippr:py-3 skippr:text-xs",
776
1193
  onClick: onStartSession,
777
1194
  disabled: isStarting,
778
1195
  children: [
779
- isStarting ? /* @__PURE__ */ jsx9(LoaderCircle, {
1196
+ isStarting ? /* @__PURE__ */ jsx10(LoaderCircle, {
780
1197
  className: "skippr:size-4 skippr:animate-spin skippr:text-primary"
781
- }) : /* @__PURE__ */ jsx9(MessageCircleQuestionMark, {
1198
+ }) : /* @__PURE__ */ jsx10(MessageCircleQuestionMark, {
782
1199
  className: "skippr:size-4 skippr:text-primary"
783
1200
  }),
784
1201
  isStarting ? "Starting..." : "Start Session"
785
1202
  ]
786
1203
  }),
787
- error && /* @__PURE__ */ jsx9("p", {
1204
+ error && /* @__PURE__ */ jsx10("p", {
788
1205
  className: "skippr:text-xs skippr:text-destructive",
789
1206
  children: error
790
1207
  })
@@ -793,52 +1210,52 @@ function QuickActions({ onStartSession, isStarting, error }) {
793
1210
  }
794
1211
 
795
1212
  // src/components/SessionAgenda.tsx
796
- import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
1213
+ import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
797
1214
  function SessionAgenda({ phases, questions = [] }) {
798
1215
  if (phases.length === 0) {
799
- return /* @__PURE__ */ jsxs7("div", {
1216
+ return /* @__PURE__ */ jsxs8("div", {
800
1217
  className: "skippr:flex skippr:flex-col skippr:gap-3 skippr:p-4",
801
1218
  children: [
802
- /* @__PURE__ */ jsx10("h3", {
1219
+ /* @__PURE__ */ jsx11("h3", {
803
1220
  className: "skippr:text-sm skippr:font-semibold",
804
1221
  children: "Agenda"
805
1222
  }),
806
- /* @__PURE__ */ jsx10("p", {
1223
+ /* @__PURE__ */ jsx11("p", {
807
1224
  className: "skippr:text-xs skippr:text-muted-foreground",
808
1225
  children: "Waiting for session..."
809
1226
  })
810
1227
  ]
811
1228
  });
812
1229
  }
813
- return /* @__PURE__ */ jsxs7("div", {
1230
+ return /* @__PURE__ */ jsxs8("div", {
814
1231
  className: "skippr:flex skippr:flex-col skippr:gap-3 skippr:p-4",
815
1232
  children: [
816
- /* @__PURE__ */ jsx10("h3", {
1233
+ /* @__PURE__ */ jsx11("h3", {
817
1234
  className: "skippr:text-sm skippr:font-semibold",
818
1235
  children: "Agenda"
819
1236
  }),
820
- /* @__PURE__ */ jsx10("ul", {
1237
+ /* @__PURE__ */ jsx11("ul", {
821
1238
  className: "skippr:flex skippr:flex-col skippr:gap-2",
822
1239
  children: phases.map((phase) => {
823
1240
  const phaseQuestions = questions.filter((q) => q.phaseName === phase.name);
824
1241
  const answeredCount = phaseQuestions.filter((q) => q.status === "answered").length;
825
1242
  const totalCount = phaseQuestions.length;
826
- return /* @__PURE__ */ jsxs7("li", {
1243
+ return /* @__PURE__ */ jsxs8("li", {
827
1244
  className: "skippr:flex skippr:flex-col skippr:gap-0.5",
828
1245
  children: [
829
- /* @__PURE__ */ jsxs7("div", {
1246
+ /* @__PURE__ */ jsxs8("div", {
830
1247
  className: "skippr:flex skippr:items-center skippr:gap-2 skippr:text-sm",
831
1248
  children: [
832
- /* @__PURE__ */ jsx10(PhaseIcon, {
1249
+ /* @__PURE__ */ jsx11(PhaseIcon, {
833
1250
  status: phase.status
834
1251
  }),
835
- /* @__PURE__ */ jsx10("span", {
1252
+ /* @__PURE__ */ jsx11("span", {
836
1253
  className: cn(phase.status === "completed" && "skippr:text-muted-foreground skippr:line-through", phase.status === "active" && "skippr:font-medium skippr:text-primary"),
837
1254
  children: phase.name
838
1255
  })
839
1256
  ]
840
1257
  }),
841
- totalCount > 0 && /* @__PURE__ */ jsxs7("span", {
1258
+ totalCount > 0 && /* @__PURE__ */ jsxs8("span", {
842
1259
  className: "skippr:ml-6 skippr:text-xs skippr:text-muted-foreground",
843
1260
  children: [
844
1261
  answeredCount,
@@ -856,62 +1273,78 @@ function SessionAgenda({ phases, questions = [] }) {
856
1273
  }
857
1274
  function PhaseIcon({ status }) {
858
1275
  if (status === "completed") {
859
- return /* @__PURE__ */ jsx10("div", {
1276
+ return /* @__PURE__ */ jsx11("div", {
860
1277
  className: "skippr:flex skippr:size-4 skippr:shrink-0 skippr:items-center skippr:justify-center skippr:rounded-full skippr:bg-primary",
861
- children: /* @__PURE__ */ jsx10(Check, {
1278
+ children: /* @__PURE__ */ jsx11(Check, {
862
1279
  className: "skippr:size-2.5 skippr:text-primary-foreground",
863
1280
  strokeWidth: 3
864
1281
  })
865
1282
  });
866
1283
  }
867
1284
  if (status === "active") {
868
- return /* @__PURE__ */ jsx10(LoaderCircle, {
1285
+ return /* @__PURE__ */ jsx11(LoaderCircle, {
869
1286
  className: "skippr:size-4 skippr:shrink-0 skippr:text-primary skippr:animate-spin"
870
1287
  });
871
1288
  }
872
- return /* @__PURE__ */ jsx10(Circle, {
1289
+ return /* @__PURE__ */ jsx11(Circle, {
873
1290
  className: "skippr:size-4 skippr:shrink-0 skippr:text-muted-foreground"
874
1291
  });
875
1292
  }
876
1293
 
877
1294
  // src/components/Sidebar.tsx
878
- import { jsx as jsx11, jsxs as jsxs8, Fragment } from "react/jsx-runtime";
1295
+ import { jsx as jsx12, jsxs as jsxs9, Fragment } from "react/jsx-runtime";
879
1296
  function Sidebar() {
880
- const { isConnected, isStarting, error, startSession, disconnect, isPanelOpen, closePanel } = useLiveAgent();
881
- useEffect4(() => {
1297
+ const {
1298
+ isConnected,
1299
+ isStarting,
1300
+ error,
1301
+ startSession,
1302
+ disconnect,
1303
+ isPanelOpen,
1304
+ closePanel,
1305
+ isAuthenticated,
1306
+ isValidating,
1307
+ authError,
1308
+ requestOtp,
1309
+ verifyOtp,
1310
+ isAuthSubmitting
1311
+ } = useLiveAgent();
1312
+ useEffect7(() => {
882
1313
  document.body.style.transition = "margin-right 300ms ease-in-out";
883
1314
  document.body.style.marginRight = isPanelOpen ? `${SIDEBAR_WIDTH}px` : "0px";
884
1315
  }, [isPanelOpen]);
885
- useEffect4(() => {
1316
+ useEffect7(() => {
886
1317
  return () => {
887
1318
  document.body.style.marginRight = "";
888
1319
  document.body.style.transition = "";
889
1320
  };
890
1321
  }, []);
891
- return /* @__PURE__ */ jsx11("div", {
1322
+ return /* @__PURE__ */ jsxs9("div", {
892
1323
  className: cn("skippr:fixed skippr:top-0 skippr:right-0 skippr:h-full skippr:z-[9999]", "skippr:bg-background skippr:border-l skippr:border-border", "skippr:flex skippr:flex-col", "skippr:transition-all skippr:duration-300 skippr:ease-in-out skippr:overflow-hidden", !isPanelOpen && "skippr:w-0 skippr:border-l-0"),
893
1324
  style: { width: isPanelOpen ? SIDEBAR_WIDTH : undefined },
894
- children: isConnected ? /* @__PURE__ */ jsxs8(Fragment, {
895
- children: [
896
- /* @__PURE__ */ jsx11(ChatHeader, {
897
- onClose: closePanel
898
- }),
899
- /* @__PURE__ */ jsx11(ConnectedContent, {
900
- onDisconnect: disconnect
901
- })
902
- ]
903
- }) : /* @__PURE__ */ jsxs8(Fragment, {
904
- children: [
905
- /* @__PURE__ */ jsx11(ChatHeader, {
906
- onClose: closePanel
907
- }),
908
- /* @__PURE__ */ jsx11(QuickActions, {
909
- onStartSession: startSession,
910
- isStarting,
911
- error
1325
+ children: [
1326
+ /* @__PURE__ */ jsx12(ChatHeader, {
1327
+ onClose: closePanel
1328
+ }),
1329
+ isConnected ? /* @__PURE__ */ jsx12(ConnectedContent, {
1330
+ onDisconnect: disconnect
1331
+ }) : isValidating ? /* @__PURE__ */ jsx12("div", {
1332
+ className: "skippr:flex skippr:flex-1 skippr:items-center skippr:justify-center",
1333
+ children: /* @__PURE__ */ jsx12("p", {
1334
+ className: "skippr:text-sm skippr:text-muted-foreground",
1335
+ children: "Loading..."
912
1336
  })
913
- ]
914
- })
1337
+ }) : isAuthenticated ? /* @__PURE__ */ jsx12(QuickActions, {
1338
+ onStartSession: startSession,
1339
+ isStarting,
1340
+ error
1341
+ }) : /* @__PURE__ */ jsx12(LoginFlow, {
1342
+ requestOtp,
1343
+ verifyOtp,
1344
+ error: authError,
1345
+ isSubmitting: isAuthSubmitting
1346
+ })
1347
+ ]
915
1348
  });
916
1349
  }
917
1350
  function ConnectedContent({ onDisconnect }) {
@@ -921,33 +1354,33 @@ function ConnectedContent({ onDisconnect }) {
921
1354
  const { phases } = usePhaseUpdates();
922
1355
  const { questions } = useQuestionUpdates();
923
1356
  if (!isConnected) {
924
- return /* @__PURE__ */ jsx11("div", {
1357
+ return /* @__PURE__ */ jsx12("div", {
925
1358
  className: "skippr:flex skippr:flex-1 skippr:items-center skippr:justify-center",
926
- children: /* @__PURE__ */ jsx11("p", {
1359
+ children: /* @__PURE__ */ jsx12("p", {
927
1360
  className: "skippr:text-sm skippr:text-muted-foreground",
928
1361
  children: "Connecting..."
929
1362
  })
930
1363
  });
931
1364
  }
932
1365
  const isAgentSpeaking = agentState === "speaking";
933
- return /* @__PURE__ */ jsxs8(Fragment, {
1366
+ return /* @__PURE__ */ jsxs9(Fragment, {
934
1367
  children: [
935
- /* @__PURE__ */ jsx11(MeetingControls, {
1368
+ /* @__PURE__ */ jsx12(MeetingControls, {
936
1369
  onHangUp: onDisconnect
937
1370
  }),
938
- /* @__PURE__ */ jsxs8("div", {
1371
+ /* @__PURE__ */ jsxs9("div", {
939
1372
  className: "skippr:flex skippr:min-h-0 skippr:flex-1",
940
1373
  children: [
941
- /* @__PURE__ */ jsx11("div", {
1374
+ /* @__PURE__ */ jsx12("div", {
942
1375
  className: "skippr:w-[260px] skippr:shrink-0 skippr:overflow-y-auto skippr:border-r",
943
- children: /* @__PURE__ */ jsx11(SessionAgenda, {
1376
+ children: /* @__PURE__ */ jsx12(SessionAgenda, {
944
1377
  phases,
945
1378
  questions
946
1379
  })
947
1380
  }),
948
- /* @__PURE__ */ jsx11("div", {
1381
+ /* @__PURE__ */ jsx12("div", {
949
1382
  className: "skippr:flex skippr:min-w-0 skippr:flex-1 skippr:flex-col",
950
- children: /* @__PURE__ */ jsx11(MessageList, {
1383
+ children: /* @__PURE__ */ jsx12(MessageList, {
951
1384
  messages: allMessages,
952
1385
  isStreaming: isAgentSpeaking,
953
1386
  sendChatMessage,
@@ -961,44 +1394,49 @@ function ConnectedContent({ onDisconnect }) {
961
1394
  }
962
1395
 
963
1396
  // src/components/SidebarTrigger.tsx
964
- import { jsx as jsx12 } from "react/jsx-runtime";
1397
+ import { jsx as jsx13 } from "react/jsx-runtime";
965
1398
  var TRIGGER_GAP = 16;
966
1399
  var TRIGGER_DEFAULT_RIGHT = 24;
967
1400
  function SidebarTrigger() {
968
1401
  const { isPanelOpen, togglePanel } = useLiveAgent();
969
- return /* @__PURE__ */ jsx12(Button, {
1402
+ return /* @__PURE__ */ jsx13(Button, {
970
1403
  size: "icon-lg",
971
1404
  onClick: togglePanel,
972
1405
  className: "skippr:fixed skippr:bottom-6 skippr:z-[9998] skippr:size-14 skippr:rounded-full skippr:shadow-lg skippr:transition-all skippr:duration-300",
973
1406
  style: { right: isPanelOpen ? SIDEBAR_WIDTH + TRIGGER_GAP : TRIGGER_DEFAULT_RIGHT },
974
1407
  title: isPanelOpen ? "Close chat" : "Chat with us",
975
- children: isPanelOpen ? /* @__PURE__ */ jsx12(X, {
1408
+ children: isPanelOpen ? /* @__PURE__ */ jsx13(X, {
976
1409
  className: "skippr:size-6"
977
- }) : /* @__PURE__ */ jsx12(MessageCircle, {
1410
+ }) : /* @__PURE__ */ jsx13(MessageCircle, {
978
1411
  className: "skippr:size-6"
979
1412
  })
980
1413
  });
981
1414
  }
982
1415
 
983
1416
  // src/components/LiveAgent.tsx
984
- import { jsx as jsx13, jsxs as jsxs9, Fragment as Fragment2 } from "react/jsx-runtime";
1417
+ import { jsx as jsx14, jsxs as jsxs10, Fragment as Fragment2 } from "react/jsx-runtime";
985
1418
  function LiveAgent({
986
1419
  agentId,
987
- authToken,
1420
+ authToken: authTokenProp,
988
1421
  appKey,
1422
+ userToken,
989
1423
  defaultOpen = false,
990
1424
  children
991
1425
  }) {
1426
+ const auth = useAuth({ appKey });
1427
+ const effectiveAuthToken = authTokenProp || auth.authToken || undefined;
992
1428
  const { connection, shouldConnect, isStarting, error, startSession, disconnect } = useSession({
993
1429
  agentId,
994
- authToken,
995
- appKey
1430
+ authToken: effectiveAuthToken,
1431
+ appKey,
1432
+ userToken
996
1433
  });
997
- const [isPanelOpen, setIsPanelOpen] = useState5(defaultOpen);
998
- const openPanel = useCallback5(() => setIsPanelOpen(true), []);
999
- const closePanel = useCallback5(() => setIsPanelOpen(false), []);
1000
- const togglePanel = useCallback5(() => setIsPanelOpen((prev) => !prev), []);
1434
+ const [isPanelOpen, setIsPanelOpen] = useState7(defaultOpen);
1435
+ const openPanel = useCallback7(() => setIsPanelOpen(true), []);
1436
+ const closePanel = useCallback7(() => setIsPanelOpen(false), []);
1437
+ const togglePanel = useCallback7(() => setIsPanelOpen((prev) => !prev), []);
1001
1438
  const isConnected = connection !== null;
1439
+ const isAuthenticated = !!userToken || !!authTokenProp || auth.isAuthenticated;
1002
1440
  const ctx = useMemo4(() => ({
1003
1441
  connection,
1004
1442
  shouldConnect,
@@ -1010,7 +1448,14 @@ function LiveAgent({
1010
1448
  isPanelOpen,
1011
1449
  openPanel,
1012
1450
  closePanel,
1013
- togglePanel
1451
+ togglePanel,
1452
+ isAuthenticated,
1453
+ isValidating: auth.isValidating,
1454
+ authError: auth.error,
1455
+ requestOtp: auth.requestOtp,
1456
+ verifyOtp: auth.verifyOtp,
1457
+ logoutAuth: auth.logout,
1458
+ isAuthSubmitting: auth.isSubmitting
1014
1459
  }), [
1015
1460
  connection,
1016
1461
  shouldConnect,
@@ -1022,19 +1467,26 @@ function LiveAgent({
1022
1467
  isPanelOpen,
1023
1468
  openPanel,
1024
1469
  closePanel,
1025
- togglePanel
1470
+ togglePanel,
1471
+ isAuthenticated,
1472
+ auth.isValidating,
1473
+ auth.error,
1474
+ auth.requestOtp,
1475
+ auth.verifyOtp,
1476
+ auth.logout,
1477
+ auth.isSubmitting
1026
1478
  ]);
1027
- const widgetContent = /* @__PURE__ */ jsxs9(Fragment2, {
1479
+ const widgetContent = /* @__PURE__ */ jsxs10(Fragment2, {
1028
1480
  children: [
1029
- connection && /* @__PURE__ */ jsx13(RoomAudioRenderer, {}),
1030
- /* @__PURE__ */ jsx13(SidebarTrigger, {}),
1031
- /* @__PURE__ */ jsx13(Sidebar, {}),
1481
+ connection && /* @__PURE__ */ jsx14(RoomAudioRenderer, {}),
1482
+ /* @__PURE__ */ jsx14(SidebarTrigger, {}),
1483
+ /* @__PURE__ */ jsx14(Sidebar, {}),
1032
1484
  children
1033
1485
  ]
1034
1486
  });
1035
- return /* @__PURE__ */ jsx13(LiveAgentContext.Provider, {
1487
+ return /* @__PURE__ */ jsx14(LiveAgentContext.Provider, {
1036
1488
  value: ctx,
1037
- children: connection ? /* @__PURE__ */ jsx13(LiveKitRoom, {
1489
+ children: connection ? /* @__PURE__ */ jsx14(LiveKitRoom, {
1038
1490
  serverUrl: connection.livekitUrl,
1039
1491
  token: connection.token,
1040
1492
  connect: shouldConnect,