@ebowwa/coder 0.7.64 → 0.7.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/index.js +36168 -32
  2. package/dist/interfaces/ui/terminal/cli/index.js +34253 -158
  3. package/dist/interfaces/ui/terminal/native/README.md +53 -0
  4. package/dist/interfaces/ui/terminal/native/claude_code_native.darwin-x64.node +0 -0
  5. package/dist/interfaces/ui/terminal/native/claude_code_native.dylib +0 -0
  6. package/dist/interfaces/ui/terminal/native/index.d.ts +0 -0
  7. package/dist/interfaces/ui/terminal/native/index.darwin-arm64.node +0 -0
  8. package/dist/interfaces/ui/terminal/native/index.js +43 -0
  9. package/dist/interfaces/ui/terminal/native/index.node +0 -0
  10. package/dist/interfaces/ui/terminal/native/package.json +34 -0
  11. package/dist/native/README.md +53 -0
  12. package/dist/native/claude_code_native.darwin-x64.node +0 -0
  13. package/dist/native/claude_code_native.dylib +0 -0
  14. package/dist/native/index.d.ts +0 -480
  15. package/dist/native/index.darwin-arm64.node +0 -0
  16. package/dist/native/index.js +43 -1625
  17. package/dist/native/index.node +0 -0
  18. package/dist/native/package.json +34 -0
  19. package/native/index.darwin-arm64.node +0 -0
  20. package/native/index.js +33 -19
  21. package/package.json +3 -2
  22. package/packages/src/core/agent-loop/__tests__/compaction.test.ts +17 -14
  23. package/packages/src/core/agent-loop/compaction.ts +6 -2
  24. package/packages/src/core/agent-loop/index.ts +2 -0
  25. package/packages/src/core/agent-loop/loop-state.ts +1 -1
  26. package/packages/src/core/agent-loop/turn-executor.ts +4 -0
  27. package/packages/src/core/agent-loop/types.ts +4 -0
  28. package/packages/src/core/api-client-impl.ts +283 -173
  29. package/packages/src/core/cognitive-security/hooks.ts +2 -1
  30. package/packages/src/core/config/todo +7 -0
  31. package/packages/src/core/context/__tests__/integration.test.ts +334 -0
  32. package/packages/src/core/context/compaction.ts +170 -0
  33. package/packages/src/core/context/constants.ts +58 -0
  34. package/packages/src/core/context/extraction.ts +85 -0
  35. package/packages/src/core/context/index.ts +66 -0
  36. package/packages/src/core/context/summarization.ts +251 -0
  37. package/packages/src/core/context/token-estimation.ts +98 -0
  38. package/packages/src/core/context/types.ts +59 -0
  39. package/packages/src/core/models.ts +81 -4
  40. package/packages/src/core/normalizers/todo +5 -1
  41. package/packages/src/core/providers/README.md +230 -0
  42. package/packages/src/core/providers/__tests__/providers.test.ts +135 -0
  43. package/packages/src/core/providers/index.ts +419 -0
  44. package/packages/src/core/providers/types.ts +132 -0
  45. package/packages/src/core/retry.ts +10 -0
  46. package/packages/src/ecosystem/tools/index.ts +174 -0
  47. package/packages/src/index.ts +23 -2
  48. package/packages/src/interfaces/ui/index.ts +17 -20
  49. package/packages/src/interfaces/ui/spinner.ts +2 -2
  50. package/packages/src/interfaces/ui/terminal/bridge/index.ts +370 -0
  51. package/packages/src/interfaces/ui/terminal/bridge/ipc.ts +829 -0
  52. package/packages/src/interfaces/ui/terminal/bridge/screen-export.ts +968 -0
  53. package/packages/src/interfaces/ui/terminal/bridge/types.ts +226 -0
  54. package/packages/src/interfaces/ui/terminal/bridge/useBridge.ts +210 -0
  55. package/packages/src/interfaces/ui/terminal/cli/bootstrap.ts +132 -0
  56. package/packages/src/interfaces/ui/terminal/cli/index.ts +200 -13
  57. package/packages/src/interfaces/ui/terminal/cli/interactive/index.ts +110 -0
  58. package/packages/src/interfaces/ui/terminal/cli/interactive/input-handler.ts +393 -0
  59. package/packages/src/interfaces/ui/terminal/cli/interactive/interactive-runner.ts +820 -0
  60. package/packages/src/interfaces/ui/terminal/cli/interactive/message-store.ts +299 -0
  61. package/packages/src/interfaces/ui/terminal/cli/interactive/types.ts +274 -0
  62. package/packages/src/interfaces/ui/terminal/shared/index.ts +13 -0
  63. package/packages/src/interfaces/ui/terminal/shared/query.ts +9 -3
  64. package/packages/src/interfaces/ui/terminal/shared/setup.ts +5 -1
  65. package/packages/src/interfaces/ui/terminal/shared/spinner-frames.ts +73 -0
  66. package/packages/src/interfaces/ui/terminal/shared/status-line.ts +10 -2
  67. package/packages/src/native/index.ts +404 -27
  68. package/packages/src/native/tui_v2_types.ts +39 -0
  69. package/packages/src/teammates/coordination.test.ts +279 -0
  70. package/packages/src/teammates/coordination.ts +646 -0
  71. package/packages/src/teammates/index.ts +95 -25
  72. package/packages/src/teammates/integration.test.ts +272 -0
  73. package/packages/src/teammates/runner.test.ts +235 -0
  74. package/packages/src/teammates/runner.ts +750 -0
  75. package/packages/src/teammates/schemas.ts +673 -0
  76. package/packages/src/types/index.ts +1 -0
  77. package/packages/src/core/context-compaction.ts +0 -578
  78. package/packages/src/interfaces/ui/Screenshot 2026-03-02 at 9.23.10/342/200/257PM.png +0 -0
  79. package/packages/src/interfaces/ui/Screenshot 2026-03-03 at 10.55.11/342/200/257AM.png +0 -0
  80. package/packages/src/interfaces/ui/terminal/tui/HelpPanel.tsx +0 -262
  81. package/packages/src/interfaces/ui/terminal/tui/InputContext.tsx +0 -232
  82. package/packages/src/interfaces/ui/terminal/tui/InputField.tsx +0 -62
  83. package/packages/src/interfaces/ui/terminal/tui/InteractiveTUI.tsx +0 -537
  84. package/packages/src/interfaces/ui/terminal/tui/MessageArea.tsx +0 -107
  85. package/packages/src/interfaces/ui/terminal/tui/MessageStore.tsx +0 -240
  86. package/packages/src/interfaces/ui/terminal/tui/StatusBar.tsx +0 -54
  87. package/packages/src/interfaces/ui/terminal/tui/commands.ts +0 -438
  88. package/packages/src/interfaces/ui/terminal/tui/components/InteractiveElements.tsx +0 -584
  89. package/packages/src/interfaces/ui/terminal/tui/components/MultilineInput.tsx +0 -614
  90. package/packages/src/interfaces/ui/terminal/tui/components/PaneManager.tsx +0 -333
  91. package/packages/src/interfaces/ui/terminal/tui/components/Sidebar.tsx +0 -604
  92. package/packages/src/interfaces/ui/terminal/tui/components/index.ts +0 -118
  93. package/packages/src/interfaces/ui/terminal/tui/console.ts +0 -49
  94. package/packages/src/interfaces/ui/terminal/tui/index.ts +0 -90
  95. package/packages/src/interfaces/ui/terminal/tui/run.tsx +0 -42
  96. package/packages/src/interfaces/ui/terminal/tui/spinner.ts +0 -69
  97. package/packages/src/interfaces/ui/terminal/tui/tui-app.tsx +0 -390
  98. package/packages/src/interfaces/ui/terminal/tui/tui-footer.ts +0 -422
  99. package/packages/src/interfaces/ui/terminal/tui/types.ts +0 -186
  100. package/packages/src/interfaces/ui/terminal/tui/useInputHandler.ts +0 -104
  101. package/packages/src/interfaces/ui/terminal/tui/useNativeInput.ts +0 -239
@@ -1,584 +0,0 @@
1
- /** @jsx React.createElement */
2
- /**
3
- * Interactive Elements for TUI
4
- *
5
- * Components:
6
- * - Toast: Notification messages that auto-dismiss
7
- * - Modal: Dialog overlay for confirmations
8
- * - SelectableList: Keyboard-navigable list with selection
9
- * - Link: Clickable OSC 8 hyperlinks
10
- *
11
- * NOTE: All components use InputContext for keyboard input.
12
- * Do NOT use process.stdin directly - it conflicts with the main input loop.
13
- */
14
-
15
- import React, { useState, useEffect, useCallback, useRef, createContext, useContext } from "react";
16
- import { Box, Text, useStdout } from "ink";
17
- import { useInputHandler, InputPriority, type NativeKeyEvent } from "../InputContext.js";
18
-
19
- // ============================================
20
- // TOAST COMPONENT
21
- // ============================================
22
-
23
- export type ToastType = "info" | "success" | "warning" | "error";
24
-
25
- export interface ToastMessage {
26
- id: string;
27
- type: ToastType;
28
- message: string;
29
- duration?: number; // ms, 0 = no auto-dismiss
30
- timestamp: number;
31
- }
32
-
33
- export interface ToastProps {
34
- toasts: ToastMessage[];
35
- position?: "top" | "bottom";
36
- maxVisible?: number;
37
- onDismiss?: (id: string) => void;
38
- }
39
-
40
- const TOAST_ICONS: Record<ToastType, string> = {
41
- info: "i",
42
- success: "+",
43
- warning: "!",
44
- error: "x",
45
- };
46
-
47
- const TOAST_COLORS: Record<ToastType, string> = {
48
- info: "blue",
49
- success: "green",
50
- warning: "yellow",
51
- error: "red",
52
- };
53
-
54
- export function Toast({ toasts, position = "top", maxVisible = 3, onDismiss }: ToastProps) {
55
- const visibleToasts = toasts.slice(0, maxVisible);
56
-
57
- if (visibleToasts.length === 0) return null;
58
-
59
- return (
60
- <Box
61
- flexDirection="column"
62
- width="100%"
63
- paddingX={1}
64
- >
65
- {visibleToasts.map((toast) => (
66
- <Box
67
- key={toast.id}
68
- borderStyle="round"
69
- borderColor={TOAST_COLORS[toast.type]}
70
- paddingX={1}
71
- marginBottom={1}
72
- >
73
- <Text color={TOAST_COLORS[toast.type]} bold>
74
- [{TOAST_ICONS[toast.type]}]
75
- </Text>
76
- <Text> {toast.message}</Text>
77
- </Box>
78
- ))}
79
- </Box>
80
- );
81
- }
82
-
83
- // Toast Manager Hook
84
- export interface ToastManager {
85
- toasts: ToastMessage[];
86
- showToast: (type: ToastType, message: string, duration?: number) => string;
87
- dismissToast: (id: string) => void;
88
- clearAllToasts: () => void;
89
- }
90
-
91
- export function useToast(defaultDuration = 5000): ToastManager {
92
- const [toasts, setToasts] = useState<ToastMessage[]>([]);
93
- const timersRef = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());
94
-
95
- const showToast = useCallback((type: ToastType, message: string, duration = defaultDuration): string => {
96
- const id = `toast-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
97
- const toast: ToastMessage = {
98
- id,
99
- type,
100
- message,
101
- duration,
102
- timestamp: Date.now(),
103
- };
104
-
105
- setToasts((prev) => [...prev, toast]);
106
-
107
- // Auto-dismiss
108
- if (duration > 0) {
109
- const timer = setTimeout(() => {
110
- setToasts((prev) => prev.filter((t) => t.id !== id));
111
- timersRef.current.delete(id);
112
- }, duration);
113
- timersRef.current.set(id, timer);
114
- }
115
-
116
- return id;
117
- }, [defaultDuration]);
118
-
119
- const dismissToast = useCallback((id: string) => {
120
- const timer = timersRef.current.get(id);
121
- if (timer) {
122
- clearTimeout(timer);
123
- timersRef.current.delete(id);
124
- }
125
- setToasts((prev) => prev.filter((t) => t.id !== id));
126
- }, []);
127
-
128
- const clearAllToasts = useCallback(() => {
129
- timersRef.current.forEach((timer) => clearTimeout(timer));
130
- timersRef.current.clear();
131
- setToasts([]);
132
- }, []);
133
-
134
- // Cleanup on unmount
135
- useEffect(() => {
136
- return () => {
137
- timersRef.current.forEach((timer) => clearTimeout(timer));
138
- };
139
- }, []);
140
-
141
- return { toasts, showToast, dismissToast, clearAllToasts };
142
- }
143
-
144
- // ============================================
145
- // MODAL COMPONENT
146
- // ============================================
147
-
148
- export interface ModalAction {
149
- id: string;
150
- label: string;
151
- style?: "primary" | "secondary" | "danger";
152
- onActivate?: () => void | Promise<void>;
153
- }
154
-
155
- export interface ModalProps {
156
- isOpen: boolean;
157
- title?: string;
158
- message: string;
159
- actions: ModalAction[];
160
- defaultAction?: string;
161
- onDismiss?: () => void;
162
- width?: number;
163
- }
164
-
165
- const ACTION_COLORS: Record<string, string> = {
166
- primary: "cyan",
167
- secondary: "gray",
168
- danger: "red",
169
- };
170
-
171
- export function Modal({
172
- isOpen,
173
- title,
174
- message,
175
- actions,
176
- defaultAction,
177
- onDismiss,
178
- width = 60,
179
- }: ModalProps) {
180
- const [selectedIndex, setSelectedIndex] = useState(0);
181
- const { stdout } = useStdout();
182
-
183
- useEffect(() => {
184
- // Reset selection when modal opens
185
- if (isOpen) {
186
- const defaultIndex = defaultAction
187
- ? actions.findIndex((a) => a.id === defaultAction)
188
- : 0;
189
- setSelectedIndex(defaultIndex >= 0 ? defaultIndex : 0);
190
- }
191
- }, [isOpen, defaultAction, actions]);
192
-
193
- // Handle keyboard input via centralized InputContext
194
- const handleKey = useCallback((event: NativeKeyEvent): boolean => {
195
- if (!isOpen) return false;
196
-
197
- // Escape or q to dismiss
198
- if ((event.code === "escape" && event.kind === "press") ||
199
- (event.code === "q" && !event.ctrl && event.kind === "press")) {
200
- onDismiss?.();
201
- return true;
202
- }
203
-
204
- // Enter to select
205
- if (event.code === "enter" && event.kind === "press") {
206
- const action = actions[selectedIndex];
207
- if (action?.onActivate) {
208
- action.onActivate();
209
- }
210
- return true;
211
- }
212
-
213
- // Left arrow or h
214
- if ((event.code === "left" && event.kind === "press") ||
215
- (event.code === "h" && !event.ctrl && event.kind === "press")) {
216
- setSelectedIndex((prev) => Math.max(0, prev - 1));
217
- return true;
218
- }
219
-
220
- // Right arrow or l
221
- if ((event.code === "right" && event.kind === "press") ||
222
- (event.code === "l" && !event.ctrl && event.kind === "press")) {
223
- setSelectedIndex((prev) => Math.min(actions.length - 1, prev + 1));
224
- return true;
225
- }
226
-
227
- return false;
228
- }, [isOpen, actions, selectedIndex, onDismiss]);
229
-
230
- // Register with input system - modals have high priority
231
- useInputHandler("modal", handleKey, {
232
- priority: InputPriority.MODAL,
233
- isActive: isOpen,
234
- });
235
-
236
- if (!isOpen) return null;
237
-
238
- const modalWidth = Math.min(width, stdout.columns || 80);
239
-
240
- return (
241
- <Box
242
- flexDirection="column"
243
- borderStyle="double"
244
- borderColor="cyan"
245
- width={modalWidth}
246
- paddingX={2}
247
- paddingY={1}
248
- >
249
- {/* Title */}
250
- {title && (
251
- <Box>
252
- <Text bold color="cyan">{title}</Text>
253
- </Box>
254
- )}
255
-
256
- {/* Message */}
257
- <Box marginBottom={1}>
258
- <Text>{message}</Text>
259
- </Box>
260
-
261
- {/* Actions */}
262
- <Box justifyContent="center">
263
- {actions.map((action, index) => {
264
- const isSelected = index === selectedIndex;
265
- const color = ACTION_COLORS[action.style || "secondary"];
266
-
267
- return (
268
- <Box
269
- key={action.id}
270
- borderStyle={isSelected ? "single" : undefined}
271
- borderColor={isSelected ? color : undefined}
272
- paddingX={1}
273
- marginRight={1}
274
- >
275
- <Text
276
- color={color}
277
- bold={isSelected}
278
- inverse={isSelected}
279
- >
280
- {action.label}
281
- </Text>
282
- </Box>
283
- );
284
- })}
285
- </Box>
286
-
287
- {/* Hint */}
288
- <Box marginTop={1}>
289
- <Text dimColor>
290
- {"<-"}/{"->"} or h/l to select | Enter to confirm | Esc to cancel
291
- </Text>
292
- </Box>
293
- </Box>
294
- );
295
- }
296
-
297
- // Confirmation Modal Helper
298
- export interface ConfirmModalProps {
299
- isOpen: boolean;
300
- title?: string;
301
- message: string;
302
- onConfirm: () => void | Promise<void>;
303
- onCancel: () => void;
304
- confirmLabel?: string;
305
- cancelLabel?: string;
306
- }
307
-
308
- export function ConfirmModal({
309
- isOpen,
310
- title = "Confirm",
311
- message,
312
- onConfirm,
313
- onCancel,
314
- confirmLabel = "Confirm",
315
- cancelLabel = "Cancel",
316
- }: ConfirmModalProps) {
317
- return (
318
- <Modal
319
- isOpen={isOpen}
320
- title={title}
321
- message={message}
322
- actions={[
323
- {
324
- id: "cancel",
325
- label: cancelLabel,
326
- style: "secondary",
327
- onActivate: onCancel,
328
- },
329
- {
330
- id: "confirm",
331
- label: confirmLabel,
332
- style: "primary",
333
- onActivate: onConfirm,
334
- },
335
- ]}
336
- defaultAction="confirm"
337
- onDismiss={onCancel}
338
- />
339
- );
340
- }
341
-
342
- // ============================================
343
- // SELECTABLE LIST COMPONENT
344
- // ============================================
345
-
346
- export interface SelectableItem {
347
- id: string;
348
- label: string;
349
- description?: string;
350
- icon?: string;
351
- metadata?: Record<string, unknown>;
352
- }
353
-
354
- export interface SelectableListProps {
355
- items: SelectableItem[];
356
- selectedIndex?: number;
357
- onSelect?: (item: SelectableItem, index: number) => void;
358
- onActivate?: (item: SelectableItem, index: number) => void;
359
- showIndices?: boolean;
360
- showDescriptions?: boolean;
361
- maxHeight?: number;
362
- emptyMessage?: string;
363
- /** Whether this list is focused (receives input) */
364
- isFocused?: boolean;
365
- /** Unique ID for input handling */
366
- inputId?: string;
367
- }
368
-
369
- export function SelectableList({
370
- items,
371
- selectedIndex = 0,
372
- onSelect,
373
- onActivate,
374
- showIndices = true,
375
- showDescriptions = true,
376
- maxHeight = 10,
377
- emptyMessage = "No items",
378
- isFocused = true,
379
- inputId = "selectable-list",
380
- }: SelectableListProps) {
381
- const [internalIndex, setInternalIndex] = useState(selectedIndex);
382
-
383
- useEffect(() => {
384
- setInternalIndex(selectedIndex);
385
- }, [selectedIndex]);
386
-
387
- // Handle keyboard input via centralized InputContext
388
- const handleKey = useCallback((event: NativeKeyEvent): boolean => {
389
- if (!isFocused || items.length === 0) return false;
390
-
391
- // Up arrow or k
392
- if ((event.code === "up" && event.kind === "press") ||
393
- (event.code === "k" && !event.ctrl && event.kind === "press")) {
394
- const newIndex = Math.max(0, internalIndex - 1);
395
- setInternalIndex(newIndex);
396
- const item = items[newIndex];
397
- if (item) onSelect?.(item, newIndex);
398
- return true;
399
- }
400
-
401
- // Down arrow or j
402
- if ((event.code === "down" && event.kind === "press") ||
403
- (event.code === "j" && !event.ctrl && event.kind === "press")) {
404
- const newIndex = Math.min(items.length - 1, internalIndex + 1);
405
- setInternalIndex(newIndex);
406
- const item = items[newIndex];
407
- if (item) onSelect?.(item, newIndex);
408
- return true;
409
- }
410
-
411
- // Enter or l to activate
412
- if ((event.code === "enter" && event.kind === "press") ||
413
- (event.code === "l" && !event.ctrl && event.kind === "press")) {
414
- const item = items[internalIndex];
415
- if (item) {
416
- onActivate?.(item, internalIndex);
417
- }
418
- return true;
419
- }
420
-
421
- return false;
422
- }, [isFocused, items, internalIndex, onSelect, onActivate]);
423
-
424
- // Register with input system
425
- useInputHandler(inputId, handleKey, {
426
- priority: InputPriority.LIST,
427
- isActive: isFocused && items.length > 0,
428
- });
429
-
430
- if (items.length === 0) {
431
- return (
432
- <Box>
433
- <Text dimColor>{emptyMessage}</Text>
434
- </Box>
435
- );
436
- }
437
-
438
- const visibleItems = items.slice(0, maxHeight);
439
-
440
- return (
441
- <Box flexDirection="column">
442
- {visibleItems.map((item, index) => {
443
- const isSelected = index === internalIndex;
444
- const prefix = showIndices
445
- ? `${String(index + 1).padStart(2, " ")}. `
446
- : "";
447
-
448
- return (
449
- <Box
450
- key={item.id}
451
- flexDirection="column"
452
- marginBottom={1}
453
- >
454
- <Box>
455
- {isSelected && (
456
- <Text color="cyan" bold>{"->"} </Text>
457
- )}
458
- {!isSelected && <Text> </Text>}
459
- <Text dimColor>{prefix}</Text>
460
- {item.icon && <Text> {item.icon} </Text>}
461
- <Text
462
- color={isSelected ? "cyan" : "white"}
463
- bold={isSelected}
464
- >
465
- {item.label}
466
- </Text>
467
- </Box>
468
- {showDescriptions && item.description && (
469
- <Box marginLeft={4}>
470
- <Text dimColor>{item.description}</Text>
471
- </Box>
472
- )}
473
- </Box>
474
- );
475
- })}
476
-
477
- {items.length > maxHeight && (
478
- <Box>
479
- <Text dimColor>
480
- ... and {items.length - maxHeight} more (j to scroll)
481
- </Text>
482
- </Box>
483
- )}
484
-
485
- <Box marginTop={1}>
486
- <Text dimColor>
487
- up/down or j/k to navigate | Enter or l to select
488
- </Text>
489
- </Box>
490
- </Box>
491
- );
492
- }
493
-
494
- // ============================================
495
- // LINK COMPONENT (OSC 8 HYPERLINKS)
496
- // ============================================
497
-
498
- export interface LinkProps {
499
- url: string;
500
- text?: string;
501
- fallback?: string; // Text to show if OSC 8 not supported
502
- }
503
-
504
- /**
505
- * Create an OSC 8 hyperlink
506
- * OSC 8 format: ESC ] 8 ;; <url> BEL <text> ESC ] 8 ;; BEL
507
- */
508
- export function createOsc8Link(url: string, text: string): string {
509
- // OSC 8 hyperlink format: \x1b]8;;URL\x07TEXT\x1b]8;;\x07
510
- return `\x1b]8;;${url}\x07${text}\x1b]8;;\x07`;
511
- }
512
-
513
- export function Link({ url, text, fallback }: LinkProps) {
514
- const displayText = text || url;
515
- const fallbackText = fallback || displayText;
516
-
517
- // Check if terminal likely supports OSC 8
518
- // Most modern terminals do, but we provide fallback
519
- const supportsOsc8 = process.env.TERM_PROGRAM === "iTerm.app" ||
520
- process.env.TERM_PROGRAM === "Terminal.app" ||
521
- process.env.TERM?.includes("xterm") ||
522
- process.env.WT_SESSION_ID !== undefined; // Windows Terminal
523
-
524
- if (supportsOsc8) {
525
- // Render with OSC 8 hyperlink
526
- // Note: Ink may escape this, so we use the raw text
527
- return (
528
- <Text color="blue">
529
- {createOsc8Link(url, displayText)}
530
- </Text>
531
- );
532
- }
533
-
534
- // Fallback for terminals without OSC 8 support
535
- return (
536
- <Text color="blue" underline>
537
- {fallbackText}
538
- </Text>
539
- );
540
- }
541
-
542
- // ============================================
543
- // CONTEXT PROVIDERS
544
- // ============================================
545
-
546
- interface InteractiveContextValue {
547
- toast: ToastManager;
548
- }
549
-
550
- const InteractiveContext = createContext<InteractiveContextValue | null>(null);
551
-
552
- export function InteractiveProvider({ children }: { children: React.ReactNode }) {
553
- const toast = useToast();
554
-
555
- return (
556
- <InteractiveContext.Provider value={{ toast }}>
557
- {children}
558
- </InteractiveContext.Provider>
559
- );
560
- }
561
-
562
- export function useInteractive(): InteractiveContextValue {
563
- const context = useContext(InteractiveContext);
564
- if (!context) {
565
- throw new Error("useInteractive must be used within InteractiveProvider");
566
- }
567
- return context;
568
- }
569
-
570
- // ============================================
571
- // EXPORTS
572
- // ============================================
573
-
574
- export default {
575
- Toast,
576
- useToast,
577
- Modal,
578
- ConfirmModal,
579
- SelectableList,
580
- Link,
581
- createOsc8Link,
582
- InteractiveProvider,
583
- useInteractive,
584
- };