@idealyst/mcp-server 1.2.107 → 1.2.109

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.
@@ -36,12 +36,16 @@ __export(tools_exports, {
36
36
  getAudioGuide: () => getAudioGuide,
37
37
  getAudioGuideDefinition: () => getAudioGuideDefinition,
38
38
  getAvailableComponents: () => getAvailableComponents,
39
+ getBiometricsGuide: () => getBiometricsGuide,
40
+ getBiometricsGuideDefinition: () => getBiometricsGuideDefinition,
39
41
  getCameraGuide: () => getCameraGuide,
40
42
  getCameraGuideDefinition: () => getCameraGuideDefinition,
41
43
  getChartsGuide: () => getChartsGuide,
42
44
  getChartsGuideDefinition: () => getChartsGuideDefinition,
43
45
  getCliUsage: () => getCliUsage,
44
46
  getCliUsageDefinition: () => getCliUsageDefinition,
47
+ getClipboardGuide: () => getClipboardGuide,
48
+ getClipboardGuideDefinition: () => getClipboardGuideDefinition,
45
49
  getComponentDocs: () => getComponentDocs,
46
50
  getComponentDocsDefinition: () => getComponentDocsDefinition,
47
51
  getComponentExample: () => getComponentExample,
@@ -73,6 +77,8 @@ __export(tools_exports, {
73
77
  getOauthClientGuideDefinition: () => getOauthClientGuideDefinition,
74
78
  getPackageDocs: () => getPackageDocs,
75
79
  getPackageDocsDefinition: () => getPackageDocsDefinition,
80
+ getPaymentsGuide: () => getPaymentsGuide,
81
+ getPaymentsGuideDefinition: () => getPaymentsGuideDefinition,
76
82
  getRecipe: () => getRecipe,
77
83
  getRecipeDefinition: () => getRecipeDefinition,
78
84
  getRegistryThemeValues: () => getRegistryThemeValues,
@@ -288,8 +294,8 @@ var getStorageGuideDefinition = {
288
294
  properties: {
289
295
  topic: {
290
296
  type: "string",
291
- description: "Topic to get docs for: 'overview', 'api', 'examples'",
292
- enum: ["overview", "api", "examples"]
297
+ description: "Topic to get docs for: 'overview', 'api', 'examples', 'secure'",
298
+ enum: ["overview", "api", "examples", "secure"]
293
299
  }
294
300
  },
295
301
  required: ["topic"]
@@ -460,6 +466,51 @@ var getChartsGuideDefinition = {
460
466
  required: ["topic"]
461
467
  }
462
468
  };
469
+ var getClipboardGuideDefinition = {
470
+ name: "get_clipboard_guide",
471
+ description: "Get documentation for @idealyst/clipboard cross-platform clipboard and OTP autofill package. Covers copy/paste API, useOTPAutoFill hook, and examples.",
472
+ inputSchema: {
473
+ type: "object",
474
+ properties: {
475
+ topic: {
476
+ type: "string",
477
+ description: "Topic to get docs for: 'overview', 'api', 'examples'",
478
+ enum: ["overview", "api", "examples"]
479
+ }
480
+ },
481
+ required: ["topic"]
482
+ }
483
+ };
484
+ var getBiometricsGuideDefinition = {
485
+ name: "get_biometrics_guide",
486
+ description: "Get documentation for @idealyst/biometrics cross-platform biometric authentication and passkeys (WebAuthn/FIDO2) package. Covers local biometric auth, passkey registration/login, and examples.",
487
+ inputSchema: {
488
+ type: "object",
489
+ properties: {
490
+ topic: {
491
+ type: "string",
492
+ description: "Topic to get docs for: 'overview', 'api', 'examples'",
493
+ enum: ["overview", "api", "examples"]
494
+ }
495
+ },
496
+ required: ["topic"]
497
+ }
498
+ };
499
+ var getPaymentsGuideDefinition = {
500
+ name: "get_payments_guide",
501
+ description: "Get documentation for @idealyst/payments cross-platform payment provider package. Covers Apple Pay, Google Pay, Stripe Platform Pay, usePayments hook, and examples.",
502
+ inputSchema: {
503
+ type: "object",
504
+ properties: {
505
+ topic: {
506
+ type: "string",
507
+ description: "Topic to get docs for: 'overview', 'api', 'examples'",
508
+ enum: ["overview", "api", "examples"]
509
+ }
510
+ },
511
+ required: ["topic"]
512
+ }
513
+ };
463
514
  var listPackagesDefinition = {
464
515
  name: "list_packages",
465
516
  description: "List all available Idealyst packages with descriptions, categories, and documentation status. Use this to discover what packages are available in the framework.",
@@ -614,6 +665,9 @@ var toolDefinitions = [
614
665
  getMarkdownGuideDefinition,
615
666
  getConfigGuideDefinition,
616
667
  getChartsGuideDefinition,
668
+ getClipboardGuideDefinition,
669
+ getBiometricsGuideDefinition,
670
+ getPaymentsGuideDefinition,
617
671
  // Package tools
618
672
  listPackagesDefinition,
619
673
  getPackageDocsDefinition,
@@ -2436,6 +2490,7 @@ Cross-platform storage solution for React and React Native applications. Provide
2436
2490
  - **React Native** - Uses MMKV for high-performance storage
2437
2491
  - **Web** - Uses localStorage with proper error handling
2438
2492
  - **TypeScript** - Full type safety and IntelliSense support
2493
+ - **Secure Storage** - Optional encrypted storage via \`createSecureStorage()\` (Keychain on native, Web Crypto on web)
2439
2494
 
2440
2495
  ## Installation
2441
2496
 
@@ -2839,6 +2894,127 @@ async function safeStorageOperation() {
2839
2894
  6. **Data Size** - Keep stored objects reasonably sized
2840
2895
  7. **Cleanup** - Periodically clean up unused data
2841
2896
  8. **Type Safety** - Create typed wrapper functions for better TypeScript support
2897
+ 9. **Use Secure Storage for Secrets** - Use \`createSecureStorage()\` for auth tokens, API keys, and sensitive data
2898
+ `,
2899
+ "idealyst://storage/secure": `# Secure Storage
2900
+
2901
+ Encrypted storage for sensitive data like auth tokens, API keys, and secrets. Uses the same \`IStorage\` interface as regular storage \u2014 drop-in replacement.
2902
+
2903
+ ## Installation
2904
+
2905
+ \`\`\`bash
2906
+ yarn add @idealyst/storage
2907
+
2908
+ # React Native also needs (for secure storage):
2909
+ yarn add react-native-keychain react-native-mmkv
2910
+ cd ios && pod install
2911
+ \`\`\`
2912
+
2913
+ ## Quick Start
2914
+
2915
+ \`\`\`tsx
2916
+ import { createSecureStorage } from '@idealyst/storage';
2917
+
2918
+ // Create a secure storage instance
2919
+ const secureStorage = createSecureStorage();
2920
+
2921
+ // Same API as regular storage
2922
+ await secureStorage.setItem('authToken', 'eyJhbGciOiJIUzI1NiIs...');
2923
+ const token = await secureStorage.getItem('authToken');
2924
+ await secureStorage.removeItem('authToken');
2925
+ await secureStorage.clear();
2926
+ const keys = await secureStorage.getAllKeys();
2927
+
2928
+ // Listeners work too
2929
+ const unsubscribe = secureStorage.addListener((key, value) => {
2930
+ console.log('Secure storage changed:', key);
2931
+ });
2932
+ \`\`\`
2933
+
2934
+ ## Options
2935
+
2936
+ \`\`\`tsx
2937
+ import { createSecureStorage, SecureStorageOptions } from '@idealyst/storage';
2938
+
2939
+ const secureStorage = createSecureStorage({
2940
+ prefix: 'myapp', // Namespace for keys (default: 'secure')
2941
+ });
2942
+ \`\`\`
2943
+
2944
+ The \`prefix\` option controls:
2945
+ - **Native**: Keychain service name and MMKV instance ID
2946
+ - **Web**: localStorage key prefix and IndexedDB key name
2947
+
2948
+ Use different prefixes to create isolated secure storage instances.
2949
+
2950
+ ## How It Works
2951
+
2952
+ ### React Native
2953
+ 1. A random 16-byte encryption key is generated on first use
2954
+ 2. The key is stored in the **iOS Keychain** / **Android Keystore** (hardware-backed)
2955
+ 3. An encrypted MMKV instance is created using that key
2956
+ 4. All data is encrypted at rest by MMKV's native AES encryption
2957
+ 5. Keychain accessibility is set to \`WHEN_UNLOCKED_THIS_DEVICE_ONLY\` (not backed up, only accessible when device is unlocked)
2958
+
2959
+ ### Web
2960
+ 1. A non-extractable AES-256-GCM \`CryptoKey\` is generated on first use
2961
+ 2. The key is stored in **IndexedDB** (non-extractable \u2014 cannot be read as raw bytes)
2962
+ 3. Each value is encrypted with a unique random IV before storing in localStorage
2963
+ 4. Requires a **secure context** (HTTPS) for \`crypto.subtle\` access
2964
+
2965
+ ## Usage Example: Secure Auth Service
2966
+
2967
+ \`\`\`tsx
2968
+ import { createSecureStorage } from '@idealyst/storage';
2969
+
2970
+ const secureStorage = createSecureStorage({ prefix: 'auth' });
2971
+
2972
+ interface AuthTokens {
2973
+ accessToken: string;
2974
+ refreshToken: string;
2975
+ expiresAt: number;
2976
+ }
2977
+
2978
+ class SecureAuthService {
2979
+ static async saveTokens(tokens: AuthTokens) {
2980
+ await secureStorage.setItem('tokens', JSON.stringify(tokens));
2981
+ }
2982
+
2983
+ static async getTokens(): Promise<AuthTokens | null> {
2984
+ const data = await secureStorage.getItem('tokens');
2985
+ return data ? JSON.parse(data) as AuthTokens : null;
2986
+ }
2987
+
2988
+ static async clearTokens() {
2989
+ await secureStorage.removeItem('tokens');
2990
+ }
2991
+
2992
+ static async saveApiKey(key: string) {
2993
+ await secureStorage.setItem('apiKey', key);
2994
+ }
2995
+
2996
+ static async getApiKey(): Promise<string | null> {
2997
+ return secureStorage.getItem('apiKey');
2998
+ }
2999
+ }
3000
+ \`\`\`
3001
+
3002
+ ## When to Use Secure vs Regular Storage
3003
+
3004
+ | Data Type | Use |
3005
+ |-----------|-----|
3006
+ | Auth tokens, refresh tokens | \`createSecureStorage()\` |
3007
+ | API keys, client secrets | \`createSecureStorage()\` |
3008
+ | User preferences, theme | \`storage\` (regular) |
3009
+ | Cache data | \`storage\` (regular) |
3010
+ | Session IDs | \`createSecureStorage()\` |
3011
+ | Language preference | \`storage\` (regular) |
3012
+
3013
+ ## Platform Requirements
3014
+
3015
+ - **React Native**: Requires \`react-native-keychain\` (>=9.0.0) and \`react-native-mmkv\` (>=4.0.0)
3016
+ - **Web**: Requires secure context (HTTPS) and IndexedDB support
3017
+ - **Web limitation**: IndexedDB may not be available in some private browsing modes
2842
3018
  `
2843
3019
  };
2844
3020
 
@@ -2870,7 +3046,8 @@ yarn add @idealyst/audio
2870
3046
  1. **PCM Streaming** \u2014 Audio data is delivered as \`PCMData\` chunks via callbacks, not as files
2871
3047
  2. **Audio Session** \u2014 On iOS/Android, configure the audio session category before recording/playback
2872
3048
  3. **Audio Profiles** \u2014 Pre-configured \`AudioConfig\` presets: \`speech\`, \`highQuality\`, \`studio\`, \`phone\`
2873
- 4. **Session Presets** \u2014 Pre-configured \`AudioSessionConfig\` presets: \`playback\`, \`record\`, \`voiceChat\`, \`ambient\`, \`default\`
3049
+ 4. **Session Presets** \u2014 Pre-configured \`AudioSessionConfig\` presets: \`playback\`, \`record\`, \`voiceChat\`, \`ambient\`, \`default\`, \`backgroundRecord\`
3050
+ 5. **Background Recording** \u2014 \`useBackgroundRecorder\` hook for recording that continues when the app is backgrounded (iOS/Android). Requires app-level native entitlements.
2874
3051
 
2875
3052
  ## Exports
2876
3053
 
@@ -2879,10 +3056,14 @@ import {
2879
3056
  useRecorder,
2880
3057
  usePlayer,
2881
3058
  useAudio,
3059
+ useBackgroundRecorder,
2882
3060
  AUDIO_PROFILES,
2883
3061
  SESSION_PRESETS,
2884
3062
  } from '@idealyst/audio';
2885
- import type { PCMData, AudioConfig, AudioLevel } from '@idealyst/audio';
3063
+ import type {
3064
+ PCMData, AudioConfig, AudioLevel,
3065
+ BackgroundRecorderStatus, BackgroundLifecycleInfo,
3066
+ } from '@idealyst/audio';
2886
3067
  \`\`\`
2887
3068
  `,
2888
3069
  "idealyst://audio/api": `# @idealyst/audio \u2014 API Reference
@@ -2992,6 +3173,84 @@ interface UseAudioOptions {
2992
3173
 
2993
3174
  ---
2994
3175
 
3176
+ ### useBackgroundRecorder(options?)
3177
+
3178
+ Background-aware recording hook. Wraps \`useRecorder\` with app lifecycle management for recording that continues when the app is backgrounded on iOS/Android. On web, works identically to \`useRecorder\` (background events never fire).
3179
+
3180
+ > **Requires app-level native configuration** \u2014 see "Background Recording Setup" below.
3181
+
3182
+ \`\`\`typescript
3183
+ interface UseBackgroundRecorderOptions {
3184
+ config?: Partial<AudioConfig>; // Audio config
3185
+ session?: Partial<AudioSessionConfig>; // Session config (default: SESSION_PRESETS.backgroundRecord)
3186
+ autoRequestPermission?: boolean; // Auto-request mic permission on mount
3187
+ levelUpdateInterval?: number; // Level update interval in ms (default: 100)
3188
+ maxBackgroundDuration?: number; // Max background recording time in ms (undefined = no limit)
3189
+ autoConfigureSession?: boolean; // Auto-configure session for background (default: true)
3190
+ onLifecycleEvent?: BackgroundLifecycleCallback; // Lifecycle event callback
3191
+ }
3192
+ \`\`\`
3193
+
3194
+ **Returns \`UseBackgroundRecorderResult\`:**
3195
+
3196
+ All properties from \`useRecorder\`, plus:
3197
+
3198
+ | Property | Type | Description |
3199
+ |----------|------|-------------|
3200
+ | isInBackground | boolean | Whether the app is currently backgrounded |
3201
+ | wasInterrupted | boolean | Whether recording was interrupted (phone call, Siri, etc.) |
3202
+ | backgroundDuration | number | Total time spent recording in background (ms) |
3203
+ | appState | AppStateStatus | Current app state (\`'active' \\| 'background' \\| 'inactive'\`) |
3204
+
3205
+ **Lifecycle events** (via \`onLifecycleEvent\`):
3206
+
3207
+ | Event | When | Extra fields |
3208
+ |-------|------|-------------|
3209
+ | \`'backgrounded'\` | App enters background while recording | \u2014 |
3210
+ | \`'foregrounded'\` | App returns to foreground while recording | \`backgroundDuration\` |
3211
+ | \`'interrupted'\` | OS interrupts recording (phone call, Siri) | \u2014 |
3212
+ | \`'interruptionEnded'\` | OS interruption ends | \`shouldResume\` |
3213
+ | \`'maxDurationReached'\` | Background recording hit \`maxBackgroundDuration\` | \`backgroundDuration\` |
3214
+ | \`'stopped'\` | Recording stopped while in background | \`backgroundDuration\` |
3215
+
3216
+ > **Note:** Interruptions use notify-only \u2014 the hook does NOT auto-resume. The consumer decides via the \`shouldResume\` flag.
3217
+
3218
+ #### Background Recording Setup
3219
+
3220
+ The OS will not allow background recording without app-level entitlements:
3221
+
3222
+ **iOS** \u2014 \`Info.plist\`:
3223
+ \`\`\`xml
3224
+ <key>UIBackgroundModes</key>
3225
+ <array><string>audio</string></array>
3226
+ \`\`\`
3227
+
3228
+ **Android** \u2014 \`AndroidManifest.xml\`:
3229
+ \`\`\`xml
3230
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
3231
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
3232
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
3233
+ <service
3234
+ android:name="com.swmansion.audioapi.system.CentralizedForegroundService"
3235
+ android:foregroundServiceType="microphone" />
3236
+ \`\`\`
3237
+
3238
+ **Expo** \u2014 \`app.json\` plugin:
3239
+ \`\`\`json
3240
+ ["react-native-audio-api", {
3241
+ "iosBackgroundMode": true,
3242
+ "androidForegroundService": true,
3243
+ "androidFSTypes": ["microphone"],
3244
+ "androidPermissions": [
3245
+ "android.permission.FOREGROUND_SERVICE",
3246
+ "android.permission.FOREGROUND_SERVICE_MICROPHONE",
3247
+ "android.permission.RECORD_AUDIO"
3248
+ ]
3249
+ }]
3250
+ \`\`\`
3251
+
3252
+ ---
3253
+
2995
3254
  ## Types
2996
3255
 
2997
3256
  ### AudioConfig
@@ -3053,6 +3312,7 @@ type AudioErrorCode =
3053
3312
  | 'DEVICE_NOT_FOUND' | 'DEVICE_IN_USE' | 'NOT_SUPPORTED'
3054
3313
  | 'SOURCE_NOT_FOUND' | 'FORMAT_NOT_SUPPORTED' | 'DECODE_ERROR' | 'PLAYBACK_ERROR' | 'BUFFER_UNDERRUN'
3055
3314
  | 'RECORDING_ERROR'
3315
+ | 'BACKGROUND_NOT_SUPPORTED' | 'BACKGROUND_MAX_DURATION'
3056
3316
  | 'INITIALIZATION_FAILED' | 'INVALID_STATE' | 'INVALID_CONFIG' | 'UNKNOWN';
3057
3317
 
3058
3318
  interface AudioError {
@@ -3081,11 +3341,12 @@ const AUDIO_PROFILES: AudioProfiles = {
3081
3341
 
3082
3342
  \`\`\`typescript
3083
3343
  const SESSION_PRESETS: SessionPresets = {
3084
- playback: { category: 'playback', mode: 'default' },
3085
- record: { category: 'record', mode: 'default' },
3086
- voiceChat: { category: 'playAndRecord', mode: 'voiceChat', categoryOptions: ['allowBluetooth', 'defaultToSpeaker'] },
3087
- ambient: { category: 'ambient', mode: 'default' },
3088
- default: { category: 'soloAmbient', mode: 'default' },
3344
+ playback: { category: 'playback', mode: 'default' },
3345
+ record: { category: 'record', mode: 'default' },
3346
+ voiceChat: { category: 'playAndRecord', mode: 'voiceChat', categoryOptions: ['allowBluetooth', 'defaultToSpeaker'] },
3347
+ ambient: { category: 'ambient', mode: 'default' },
3348
+ default: { category: 'soloAmbient', mode: 'default' },
3349
+ backgroundRecord: { category: 'playAndRecord', mode: 'spokenAudio', categoryOptions: ['defaultToSpeaker', 'allowBluetooth', 'allowBluetoothA2DP', 'mixWithOthers'] },
3089
3350
  };
3090
3351
  \`\`\`
3091
3352
  `,
@@ -3258,6 +3519,77 @@ function VoiceChatScreen() {
3258
3519
  }
3259
3520
  \`\`\`
3260
3521
 
3522
+ ## Background Recording for Transcription
3523
+
3524
+ \`\`\`tsx
3525
+ import React, { useEffect } from 'react';
3526
+ import { View, Button, Text } from '@idealyst/components';
3527
+ import { useBackgroundRecorder, AUDIO_PROFILES } from '@idealyst/audio';
3528
+ import type { PCMData, BackgroundLifecycleInfo } from '@idealyst/audio';
3529
+
3530
+ function BackgroundTranscriber() {
3531
+ const recorder = useBackgroundRecorder({
3532
+ config: AUDIO_PROFILES.speech,
3533
+ maxBackgroundDuration: 5 * 60 * 1000, // 5 min max in background
3534
+ onLifecycleEvent: (info: BackgroundLifecycleInfo) => {
3535
+ switch (info.event) {
3536
+ case 'backgrounded':
3537
+ console.log('Recording continues in background');
3538
+ break;
3539
+ case 'foregrounded':
3540
+ console.log(\`Back from background after \${info.backgroundDuration}ms\`);
3541
+ break;
3542
+ case 'interrupted':
3543
+ console.log('Recording interrupted (phone call?)');
3544
+ break;
3545
+ case 'interruptionEnded':
3546
+ if (info.shouldResume) {
3547
+ recorder.resume(); // Consumer decides whether to resume
3548
+ }
3549
+ break;
3550
+ case 'maxDurationReached':
3551
+ console.log('Max background duration reached');
3552
+ break;
3553
+ }
3554
+ },
3555
+ });
3556
+
3557
+ // Stream PCM chunks to your speech-to-text service
3558
+ useEffect(() => {
3559
+ const unsub = recorder.subscribeToData((pcm: PCMData) => {
3560
+ // Send to STT API (e.g., Whisper, Deepgram)
3561
+ sendToTranscriptionService(pcm.toBase64());
3562
+ });
3563
+ return unsub;
3564
+ }, [recorder.subscribeToData]);
3565
+
3566
+ const handleToggle = async () => {
3567
+ if (recorder.isRecording) {
3568
+ await recorder.stop();
3569
+ } else {
3570
+ await recorder.start();
3571
+ }
3572
+ };
3573
+
3574
+ return (
3575
+ <View padding="md" gap="md">
3576
+ <Button
3577
+ onPress={handleToggle}
3578
+ intent={recorder.isRecording ? 'error' : 'primary'}
3579
+ >
3580
+ {recorder.isRecording ? 'Stop' : 'Record'}
3581
+ </Button>
3582
+ <Text>Duration: {Math.round(recorder.duration / 1000)}s</Text>
3583
+ {recorder.isInBackground && <Text>Recording in background...</Text>}
3584
+ {recorder.wasInterrupted && <Text>Recording was interrupted</Text>}
3585
+ <Text>Background time: {Math.round(recorder.backgroundDuration / 1000)}s</Text>
3586
+ </View>
3587
+ );
3588
+ }
3589
+ \`\`\`
3590
+
3591
+ > **Important:** Background recording requires native entitlements. See the \`useBackgroundRecorder\` API docs for iOS, Android, and Expo setup instructions.
3592
+
3261
3593
  ## Audio Level Visualization
3262
3594
 
3263
3595
  \`\`\`tsx
@@ -6751,146 +7083,1662 @@ function SalesOverview() {
6751
7083
  }
6752
7084
  \`\`\`
6753
7085
 
6754
- ## Multi-Series Line Chart
7086
+ ## Multi-Series Line Chart
7087
+
7088
+ \`\`\`tsx
7089
+ import React from 'react';
7090
+ import { LineChart } from '@idealyst/charts';
7091
+ import type { ChartDataSeries } from '@idealyst/charts';
7092
+
7093
+ // Each series has: id, name, data, color? \u2014 NO 'label' property
7094
+ const series: ChartDataSeries[] = [
7095
+ {
7096
+ id: 'product-a',
7097
+ name: 'Product A', // Use 'name' \u2014 NOT 'label'
7098
+ data: [
7099
+ { x: 'Q1', y: 120 },
7100
+ { x: 'Q2', y: 150 },
7101
+ { x: 'Q3', y: 180 },
7102
+ { x: 'Q4', y: 210 },
7103
+ ],
7104
+ color: '#2196F3',
7105
+ },
7106
+ {
7107
+ id: 'product-b',
7108
+ name: 'Product B', // Use 'name' \u2014 NOT 'label'
7109
+ data: [
7110
+ { x: 'Q1', y: 80 },
7111
+ { x: 'Q2', y: 110 },
7112
+ { x: 'Q3', y: 95 },
7113
+ { x: 'Q4', y: 140 },
7114
+ ],
7115
+ color: '#FF9800',
7116
+ },
7117
+ ];
7118
+
7119
+ function ComparisonChart() {
7120
+ return (
7121
+ <LineChart
7122
+ data={series}
7123
+ height={350}
7124
+ curve="monotone"
7125
+ showDots
7126
+ animate
7127
+ />
7128
+ );
7129
+ }
7130
+ \`\`\`
7131
+
7132
+ ## Bar Chart
7133
+
7134
+ \`\`\`tsx
7135
+ import React from 'react';
7136
+ import { View, Text } from '@idealyst/components';
7137
+ import { BarChart } from '@idealyst/charts';
7138
+
7139
+ const categories = [
7140
+ { x: 'Electronics', y: 45 },
7141
+ { x: 'Clothing', y: 32 },
7142
+ { x: 'Books', y: 18 },
7143
+ { x: 'Food', y: 56 },
7144
+ { x: 'Sports', y: 28 },
7145
+ ];
7146
+
7147
+ function CategoryBreakdown() {
7148
+ return (
7149
+ <View padding="md" gap="md">
7150
+ <Text typography="h6" weight="bold">Sales by Category</Text>
7151
+ <BarChart
7152
+ data={[{ id: 'units', name: 'Units Sold', data: categories }]}
7153
+ height={300}
7154
+ barRadius={4}
7155
+ animate
7156
+ yAxis={{ tickFormat: (value: number | string | Date) => \`\${value} units\` }}
7157
+ />
7158
+ </View>
7159
+ );
7160
+ }
7161
+ \`\`\`
7162
+
7163
+ ## Stacked Bar Chart
7164
+
7165
+ \`\`\`tsx
7166
+ import React from 'react';
7167
+ import { BarChart } from '@idealyst/charts';
7168
+
7169
+ function StackedBarExample() {
7170
+ return (
7171
+ <BarChart
7172
+ data={[
7173
+ {
7174
+ id: 'online',
7175
+ name: 'Online',
7176
+ data: [
7177
+ { x: 'Q1', y: 100 },
7178
+ { x: 'Q2', y: 120 },
7179
+ { x: 'Q3', y: 90 },
7180
+ ],
7181
+ color: '#4CAF50',
7182
+ },
7183
+ {
7184
+ id: 'in-store',
7185
+ name: 'In-Store',
7186
+ data: [
7187
+ { x: 'Q1', y: 60 },
7188
+ { x: 'Q2', y: 80 },
7189
+ { x: 'Q3', y: 70 },
7190
+ ],
7191
+ color: '#2196F3',
7192
+ },
7193
+ ]}
7194
+ height={300}
7195
+ stacked
7196
+ animate
7197
+ />
7198
+ );
7199
+ }
7200
+ \`\`\`
7201
+
7202
+ ## Horizontal Bar Chart
7203
+
7204
+ \`\`\`tsx
7205
+ import React from 'react';
7206
+ import { BarChart } from '@idealyst/charts';
7207
+
7208
+ function HorizontalBarExample() {
7209
+ const data = [
7210
+ { x: 'React', y: 85 },
7211
+ { x: 'Vue', y: 62 },
7212
+ { x: 'Angular', y: 45 },
7213
+ { x: 'Svelte', y: 38 },
7214
+ ];
7215
+
7216
+ return (
7217
+ <BarChart
7218
+ data={[{ id: 'popularity', name: 'Popularity', data }]}
7219
+ height={250}
7220
+ orientation="horizontal"
7221
+ animate
7222
+ />
7223
+ );
7224
+ }
7225
+ \`\`\`
7226
+ `
7227
+ };
7228
+
7229
+ // src/data/clipboard-guides.ts
7230
+ var clipboardGuides = {
7231
+ "idealyst://clipboard/overview": `# @idealyst/clipboard Overview
7232
+
7233
+ Cross-platform clipboard and OTP autofill for React and React Native applications. Provides a consistent async API for copy/paste operations, plus a mobile-only hook for automatic SMS OTP code detection.
7234
+
7235
+ ## Features
7236
+
7237
+ - **Cross-Platform Clipboard** - Copy and paste text on React Native and Web
7238
+ - **Simple API** - Async/await based with consistent interface
7239
+ - **React Native** - Uses @react-native-clipboard/clipboard
7240
+ - **Web** - Uses navigator.clipboard API
7241
+ - **OTP Auto-Fill (Android)** - Automatically reads OTP codes from SMS via SMS Retriever API (no permissions needed)
7242
+ - **OTP Auto-Fill (iOS)** - Provides TextInput props for native iOS keyboard OTP suggestion
7243
+ - **TypeScript** - Full type safety and IntelliSense support
7244
+
7245
+ ## Installation
7246
+
7247
+ \`\`\`bash
7248
+ yarn add @idealyst/clipboard
7249
+
7250
+ # React Native also needs:
7251
+ yarn add @react-native-clipboard/clipboard
7252
+ cd ios && pod install
7253
+
7254
+ # For OTP autofill on Android (optional):
7255
+ yarn add react-native-otp-verify
7256
+ \`\`\`
7257
+
7258
+ ## Quick Start
7259
+
7260
+ \`\`\`tsx
7261
+ import { clipboard } from '@idealyst/clipboard';
7262
+
7263
+ // Copy text
7264
+ await clipboard.copy('Hello, world!');
7265
+
7266
+ // Paste text
7267
+ const text = await clipboard.paste();
7268
+
7269
+ // Check if clipboard has text
7270
+ const hasText = await clipboard.hasText();
7271
+ \`\`\`
7272
+
7273
+ ## OTP Auto-Fill Quick Start
7274
+
7275
+ \`\`\`tsx
7276
+ import { useOTPAutoFill, OTP_INPUT_PROPS } from '@idealyst/clipboard';
7277
+ import { TextInput } from 'react-native';
7278
+
7279
+ function OTPScreen() {
7280
+ const { code, startListening, hash } = useOTPAutoFill({
7281
+ codeLength: 6,
7282
+ onCodeReceived: (otp) => verifyOTP(otp),
7283
+ });
7284
+
7285
+ useEffect(() => {
7286
+ startListening();
7287
+ }, []);
7288
+
7289
+ return (
7290
+ <TextInput
7291
+ value={code ?? ''}
7292
+ {...OTP_INPUT_PROPS}
7293
+ />
7294
+ );
7295
+ }
7296
+ \`\`\`
7297
+
7298
+ ## Import Options
7299
+
7300
+ \`\`\`tsx
7301
+ // Named import (recommended)
7302
+ import { clipboard } from '@idealyst/clipboard';
7303
+
7304
+ // Default import
7305
+ import clipboard from '@idealyst/clipboard';
7306
+
7307
+ // OTP hook and helpers
7308
+ import { useOTPAutoFill, OTP_INPUT_PROPS } from '@idealyst/clipboard';
7309
+ \`\`\`
7310
+
7311
+ ## Platform Details
7312
+
7313
+ - **React Native**: Uses \`@react-native-clipboard/clipboard\` for clipboard operations
7314
+ - **Web**: Uses \`navigator.clipboard\` API (requires secure context / HTTPS)
7315
+ - **OTP (Android)**: Uses SMS Retriever API via \`react-native-otp-verify\` \u2014 zero permissions
7316
+ - **OTP (iOS)**: Native keyboard autofill via \`textContentType="oneTimeCode"\`
7317
+ - **OTP (Web)**: No-op \u2014 returns null values and noop functions
7318
+ `,
7319
+ "idealyst://clipboard/api": `# Clipboard API Reference
7320
+
7321
+ Complete API reference for @idealyst/clipboard.
7322
+
7323
+ ## clipboard.copy
7324
+
7325
+ Copy text to the system clipboard.
7326
+
7327
+ \`\`\`tsx
7328
+ await clipboard.copy(text: string): Promise<void>
7329
+
7330
+ // Examples
7331
+ await clipboard.copy('Hello, world!');
7332
+ await clipboard.copy(inviteCode);
7333
+ await clipboard.copy(JSON.stringify(data));
7334
+ \`\`\`
7335
+
7336
+ ## clipboard.paste
7337
+
7338
+ Read text from the system clipboard.
7339
+
7340
+ \`\`\`tsx
7341
+ await clipboard.paste(): Promise<string>
7342
+
7343
+ // Examples
7344
+ const text = await clipboard.paste();
7345
+ const url = await clipboard.paste();
7346
+ \`\`\`
7347
+
7348
+ ## clipboard.hasText
7349
+
7350
+ Check if the clipboard contains text content.
7351
+
7352
+ \`\`\`tsx
7353
+ await clipboard.hasText(): Promise<boolean>
7354
+
7355
+ // Example
7356
+ const canPaste = await clipboard.hasText();
7357
+ if (canPaste) {
7358
+ const text = await clipboard.paste();
7359
+ }
7360
+ \`\`\`
7361
+
7362
+ ## clipboard.addListener
7363
+
7364
+ Listen for copy events (triggered when \`clipboard.copy()\` is called).
7365
+
7366
+ \`\`\`tsx
7367
+ const unsubscribe = clipboard.addListener((content: string) => {
7368
+ console.log('Copied:', content);
7369
+ });
7370
+
7371
+ // Later, unsubscribe
7372
+ unsubscribe();
7373
+ \`\`\`
7374
+
7375
+ ---
7376
+
7377
+ ## useOTPAutoFill
7378
+
7379
+ React hook for automatic OTP code detection from SMS on mobile.
7380
+
7381
+ **Android**: Uses SMS Retriever API to auto-read OTP from incoming SMS (no permissions required). SMS must include your app hash.
7382
+
7383
+ **iOS**: OTP is handled natively by the iOS keyboard. Use \`OTP_INPUT_PROPS\` on your TextInput to enable it.
7384
+
7385
+ **Web**: Returns no-op values.
7386
+
7387
+ \`\`\`tsx
7388
+ const {
7389
+ code, // string | null - received OTP code (Android only)
7390
+ startListening, // () => void - begin listening for SMS (Android only)
7391
+ stopListening, // () => void - stop listening (Android only)
7392
+ hash, // string | null - app hash for SMS body (Android only)
7393
+ } = useOTPAutoFill(options?: {
7394
+ codeLength?: number; // default: 6
7395
+ onCodeReceived?: (code: string) => void;
7396
+ });
7397
+ \`\`\`
7398
+
7399
+ ### Options
7400
+
7401
+ | Option | Type | Default | Description |
7402
+ |--------|------|---------|-------------|
7403
+ | codeLength | number | 6 | Expected digit count of OTP code |
7404
+ | onCodeReceived | (code: string) => void | \u2014 | Callback when OTP is detected |
7405
+
7406
+ ### Return Value
7407
+
7408
+ | Property | Type | Description |
7409
+ |----------|------|-------------|
7410
+ | code | string \\| null | Detected OTP code (Android). Null on iOS/web. |
7411
+ | startListening | () => void | Start SMS listener (Android). No-op on iOS/web. |
7412
+ | stopListening | () => void | Stop SMS listener (Android). No-op on iOS/web. |
7413
+ | hash | string \\| null | App hash for SMS Retriever (Android). Null on iOS/web. |
7414
+
7415
+ ### Android SMS Format
7416
+
7417
+ For the SMS Retriever API to detect the message, the SMS must:
7418
+ 1. Start with \`<#>\`
7419
+ 2. Contain the OTP code as a sequence of digits
7420
+ 3. End with your app's 11-character hash (available via \`hash\`)
7421
+
7422
+ Example SMS:
7423
+ \`\`\`
7424
+ <#> Your verification code is: 123456
7425
+ FA+9qCX9VSu
7426
+ \`\`\`
7427
+
7428
+ ---
7429
+
7430
+ ## OTP_INPUT_PROPS
7431
+
7432
+ Constant with TextInput props to enable native OTP keyboard autofill.
7433
+
7434
+ \`\`\`tsx
7435
+ import { OTP_INPUT_PROPS } from '@idealyst/clipboard';
7436
+
7437
+ // Value:
7438
+ // {
7439
+ // textContentType: 'oneTimeCode',
7440
+ // autoComplete: 'sms-otp',
7441
+ // }
7442
+
7443
+ <TextInput
7444
+ {...OTP_INPUT_PROPS}
7445
+ value={code}
7446
+ onChangeText={setCode}
7447
+ />
7448
+ \`\`\`
7449
+
7450
+ - **iOS**: Enables the keyboard to suggest OTP codes from received SMS (iOS 12+)
7451
+ - **Android**: Maps to the correct \`autoComplete\` value
7452
+ - **Web**: Harmless \u2014 ignored by web TextInput
7453
+ `,
7454
+ "idealyst://clipboard/examples": `# Clipboard Examples
7455
+
7456
+ Complete code examples for common @idealyst/clipboard patterns.
7457
+
7458
+ ## Copy to Clipboard with Feedback
7459
+
7460
+ \`\`\`tsx
7461
+ import { clipboard } from '@idealyst/clipboard';
7462
+ import { useState, useCallback } from 'react';
7463
+
7464
+ function CopyButton({ text }: { text: string }) {
7465
+ const [copied, setCopied] = useState(false);
7466
+
7467
+ const handleCopy = useCallback(async () => {
7468
+ await clipboard.copy(text);
7469
+ setCopied(true);
7470
+ setTimeout(() => setCopied(false), 2000);
7471
+ }, [text]);
7472
+
7473
+ return (
7474
+ <Button
7475
+ label={copied ? 'Copied!' : 'Copy'}
7476
+ intent={copied ? 'positive' : 'neutral'}
7477
+ onPress={handleCopy}
7478
+ />
7479
+ );
7480
+ }
7481
+ \`\`\`
7482
+
7483
+ ## Share / Copy Link
7484
+
7485
+ \`\`\`tsx
7486
+ import { clipboard } from '@idealyst/clipboard';
7487
+
7488
+ async function copyShareLink(itemId: string) {
7489
+ const url = \`https://myapp.com/items/\${itemId}\`;
7490
+ await clipboard.copy(url);
7491
+ }
7492
+ \`\`\`
7493
+
7494
+ ## Paste from Clipboard
7495
+
7496
+ \`\`\`tsx
7497
+ import { clipboard } from '@idealyst/clipboard';
7498
+
7499
+ function PasteInput() {
7500
+ const [value, setValue] = useState('');
7501
+
7502
+ const handlePaste = useCallback(async () => {
7503
+ const hasText = await clipboard.hasText();
7504
+ if (hasText) {
7505
+ const text = await clipboard.paste();
7506
+ setValue(text);
7507
+ }
7508
+ }, []);
7509
+
7510
+ return (
7511
+ <View>
7512
+ <Input value={value} onChangeText={setValue} placeholder="Enter or paste text" />
7513
+ <Button label="Paste" onPress={handlePaste} iconName="content-paste" />
7514
+ </View>
7515
+ );
7516
+ }
7517
+ \`\`\`
7518
+
7519
+ ## useClipboard Hook
7520
+
7521
+ \`\`\`tsx
7522
+ import { clipboard } from '@idealyst/clipboard';
7523
+ import { useState, useCallback } from 'react';
7524
+
7525
+ export function useClipboard() {
7526
+ const [copiedText, setCopiedText] = useState<string | null>(null);
7527
+
7528
+ const copy = useCallback(async (text: string) => {
7529
+ await clipboard.copy(text);
7530
+ setCopiedText(text);
7531
+ }, []);
7532
+
7533
+ const paste = useCallback(async () => {
7534
+ return clipboard.paste();
7535
+ }, []);
7536
+
7537
+ const reset = useCallback(() => {
7538
+ setCopiedText(null);
7539
+ }, []);
7540
+
7541
+ return { copy, paste, copiedText, reset };
7542
+ }
7543
+ \`\`\`
7544
+
7545
+ ## OTP Verification Screen
7546
+
7547
+ \`\`\`tsx
7548
+ import { useOTPAutoFill, OTP_INPUT_PROPS } from '@idealyst/clipboard';
7549
+ import { useEffect, useState } from 'react';
7550
+ import { TextInput, Platform } from 'react-native';
7551
+
7552
+ function OTPVerificationScreen({ phoneNumber, onVerify }: {
7553
+ phoneNumber: string;
7554
+ onVerify: (code: string) => void;
7555
+ }) {
7556
+ const [code, setCode] = useState('');
7557
+
7558
+ const otp = useOTPAutoFill({
7559
+ codeLength: 6,
7560
+ onCodeReceived: (receivedCode) => {
7561
+ setCode(receivedCode);
7562
+ onVerify(receivedCode);
7563
+ },
7564
+ });
7565
+
7566
+ // Start listening when screen mounts
7567
+ useEffect(() => {
7568
+ otp.startListening();
7569
+ return () => otp.stopListening();
7570
+ }, []);
7571
+
7572
+ // Auto-fill from hook on Android
7573
+ useEffect(() => {
7574
+ if (otp.code) {
7575
+ setCode(otp.code);
7576
+ }
7577
+ }, [otp.code]);
7578
+
7579
+ return (
7580
+ <View>
7581
+ <Text>Enter the code sent to {phoneNumber}</Text>
7582
+
7583
+ <TextInput
7584
+ value={code}
7585
+ onChangeText={(text) => {
7586
+ setCode(text);
7587
+ if (text.length === 6) onVerify(text);
7588
+ }}
7589
+ keyboardType="number-pad"
7590
+ maxLength={6}
7591
+ {...OTP_INPUT_PROPS}
7592
+ />
7593
+
7594
+ {Platform.OS === 'android' && otp.hash && (
7595
+ <Text style={{ fontSize: 10, color: '#999' }}>
7596
+ App hash (for SMS setup): {otp.hash}
7597
+ </Text>
7598
+ )}
7599
+ </View>
7600
+ );
7601
+ }
7602
+ \`\`\`
7603
+
7604
+ ## Copy Invite Code
7605
+
7606
+ \`\`\`tsx
7607
+ import { clipboard } from '@idealyst/clipboard';
7608
+
7609
+ function InviteCard({ code }: { code: string }) {
7610
+ const [copied, setCopied] = useState(false);
7611
+
7612
+ return (
7613
+ <Card>
7614
+ <Text>Your invite code</Text>
7615
+ <Text variant="headline">{code}</Text>
7616
+ <Button
7617
+ label={copied ? 'Copied!' : 'Copy Code'}
7618
+ iconName={copied ? 'check' : 'content-copy'}
7619
+ onPress={async () => {
7620
+ await clipboard.copy(code);
7621
+ setCopied(true);
7622
+ setTimeout(() => setCopied(false), 2000);
7623
+ }}
7624
+ />
7625
+ </Card>
7626
+ );
7627
+ }
7628
+ \`\`\`
7629
+
7630
+ ## Error Handling
7631
+
7632
+ \`\`\`tsx
7633
+ import { clipboard } from '@idealyst/clipboard';
7634
+
7635
+ async function safeCopy(text: string): Promise<boolean> {
7636
+ try {
7637
+ await clipboard.copy(text);
7638
+ return true;
7639
+ } catch (error) {
7640
+ console.error('Clipboard copy failed:', error);
7641
+ // On web, this can fail if not in a secure context or without user gesture
7642
+ return false;
7643
+ }
7644
+ }
7645
+
7646
+ async function safePaste(): Promise<string | null> {
7647
+ try {
7648
+ const hasText = await clipboard.hasText();
7649
+ if (!hasText) return null;
7650
+ return await clipboard.paste();
7651
+ } catch (error) {
7652
+ console.error('Clipboard paste failed:', error);
7653
+ // Browser may deny permission
7654
+ return null;
7655
+ }
7656
+ }
7657
+ \`\`\`
7658
+
7659
+ ## Best Practices
7660
+
7661
+ 1. **Always use try-catch** \u2014 Clipboard operations can fail (permissions, secure context)
7662
+ 2. **Provide visual feedback** \u2014 Show a "Copied!" confirmation after copying
7663
+ 3. **Use OTP_INPUT_PROPS** \u2014 Always spread these on OTP text inputs for cross-platform autofill
7664
+ 4. **Start OTP listener on mount** \u2014 Call \`startListening()\` in useEffect, clean up with \`stopListening()\`
7665
+ 5. **Log the Android hash** \u2014 During development, log \`hash\` to configure your SMS gateway
7666
+ 6. **Graceful degradation** \u2014 OTP features degrade gracefully if native modules aren't installed
7667
+ 7. **Secure context (web)** \u2014 Clipboard API requires HTTPS on web
7668
+ `
7669
+ };
7670
+
7671
+ // src/data/biometrics-guides.ts
7672
+ var biometricsGuides = {
7673
+ "idealyst://biometrics/overview": `# @idealyst/biometrics Overview
7674
+
7675
+ Cross-platform biometric authentication and passkeys (WebAuthn/FIDO2) for React and React Native applications.
7676
+
7677
+ ## Features
7678
+
7679
+ - **Local Biometric Auth** \u2014 FaceID, TouchID, fingerprint, iris to gate access
7680
+ - **Passkeys (WebAuthn/FIDO2)** \u2014 Passwordless login with cryptographic credentials
7681
+ - **Cross-Platform** \u2014 Single API for React Native (iOS/Android) and Web
7682
+ - **React Native** \u2014 Uses expo-local-authentication for biometrics, react-native-passkeys for passkeys
7683
+ - **Web** \u2014 Uses WebAuthn API for both biometrics and passkeys
7684
+ - **Graceful Degradation** \u2014 Falls back cleanly when native modules aren't installed
7685
+ - **TypeScript** \u2014 Full type safety and IntelliSense support
7686
+
7687
+ ## Installation
7688
+
7689
+ \`\`\`bash
7690
+ yarn add @idealyst/biometrics
7691
+
7692
+ # React Native \u2014 biometric auth:
7693
+ yarn add expo-local-authentication
7694
+ cd ios && pod install
7695
+
7696
+ # React Native \u2014 passkeys (optional):
7697
+ yarn add react-native-passkeys
7698
+ cd ios && pod install
7699
+ \`\`\`
7700
+
7701
+ ## Quick Start \u2014 Biometric Auth
7702
+
7703
+ \`\`\`tsx
7704
+ import { isBiometricAvailable, authenticate } from '@idealyst/biometrics';
7705
+
7706
+ // Check availability
7707
+ const available = await isBiometricAvailable();
7708
+
7709
+ // Prompt user
7710
+ if (available) {
7711
+ const result = await authenticate({
7712
+ promptMessage: 'Verify your identity',
7713
+ });
7714
+
7715
+ if (result.success) {
7716
+ // Authenticated!
7717
+ } else {
7718
+ console.log(result.error, result.message);
7719
+ }
7720
+ }
7721
+ \`\`\`
7722
+
7723
+ ## Quick Start \u2014 Passkeys
7724
+
7725
+ \`\`\`tsx
7726
+ import { isPasskeySupported, createPasskey, getPasskey } from '@idealyst/biometrics';
7727
+
7728
+ // Check support
7729
+ const supported = await isPasskeySupported();
7730
+
7731
+ // Register a new passkey
7732
+ const credential = await createPasskey({
7733
+ challenge: serverChallenge,
7734
+ rp: { id: 'example.com', name: 'My App' },
7735
+ user: { id: userId, name: email, displayName: name },
7736
+ });
7737
+ // Send credential to server for verification
7738
+
7739
+ // Sign in with passkey
7740
+ const assertion = await getPasskey({
7741
+ challenge: serverChallenge,
7742
+ rpId: 'example.com',
7743
+ });
7744
+ // Send assertion to server for verification
7745
+ \`\`\`
7746
+
7747
+ ## Platform Details
7748
+
7749
+ - **React Native (biometrics)**: Uses \`expo-local-authentication\` \u2014 FaceID, TouchID, fingerprint, iris
7750
+ - **React Native (passkeys)**: Uses \`react-native-passkeys\` \u2014 system passkey UI on iOS 16+ and Android 9+
7751
+ - **Web (biometrics)**: Uses WebAuthn with \`userVerification: 'required'\` to trigger platform authenticator
7752
+ - **Web (passkeys)**: Uses \`navigator.credentials.create/get\` with the PublicKey API
7753
+ `,
7754
+ "idealyst://biometrics/api": `# Biometrics API Reference
7755
+
7756
+ Complete API reference for @idealyst/biometrics.
7757
+
7758
+ ---
7759
+
7760
+ ## Biometric Authentication Functions
7761
+
7762
+ ### isBiometricAvailable
7763
+
7764
+ Check whether biometric auth hardware is available and enrolled.
7765
+
7766
+ \`\`\`tsx
7767
+ await isBiometricAvailable(): Promise<boolean>
7768
+
7769
+ // Native: checks hardware + enrollment via expo-local-authentication
7770
+ // Web: checks PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
7771
+ \`\`\`
7772
+
7773
+ ### getBiometricTypes
7774
+
7775
+ Return the biometric types available on this device.
7776
+
7777
+ \`\`\`tsx
7778
+ await getBiometricTypes(): Promise<BiometricType[]>
7779
+
7780
+ type BiometricType = 'fingerprint' | 'facial_recognition' | 'iris';
7781
+
7782
+ // Native: returns specific types (fingerprint, facial_recognition, iris)
7783
+ // Web: returns ['fingerprint'] as generic indicator, or [] if unavailable
7784
+ \`\`\`
7785
+
7786
+ ### getSecurityLevel
7787
+
7788
+ Get the security level of biometric authentication on the device.
7789
+
7790
+ \`\`\`tsx
7791
+ await getSecurityLevel(): Promise<SecurityLevel>
7792
+
7793
+ type SecurityLevel = 'none' | 'device_credential' | 'biometric_weak' | 'biometric_strong';
7794
+
7795
+ // Native: maps expo-local-authentication SecurityLevel enum
7796
+ // Web: returns 'biometric_strong' if platform authenticator available, else 'none'
7797
+ \`\`\`
7798
+
7799
+ ### authenticate
7800
+
7801
+ Prompt the user for biometric authentication.
7802
+
7803
+ \`\`\`tsx
7804
+ await authenticate(options?: AuthenticateOptions): Promise<AuthResult>
7805
+ \`\`\`
7806
+
7807
+ **AuthenticateOptions:**
7808
+
7809
+ | Option | Type | Default | Description |
7810
+ |--------|------|---------|-------------|
7811
+ | promptMessage | string | 'Authenticate' | Message shown alongside the biometric prompt |
7812
+ | cancelLabel | string | \u2014 | Label for the cancel button |
7813
+ | fallbackLabel | string | \u2014 | iOS: label for passcode fallback button |
7814
+ | disableDeviceFallback | boolean | false | Prevent PIN/passcode fallback after biometric failure |
7815
+ | requireStrongBiometric | boolean | false | Android: require Class 3 (strong) biometric |
7816
+
7817
+ **AuthResult:**
7818
+
7819
+ \`\`\`tsx
7820
+ type AuthResult =
7821
+ | { success: true }
7822
+ | { success: false; error: AuthError; message?: string };
7823
+
7824
+ type AuthError =
7825
+ | 'not_available'
7826
+ | 'not_enrolled'
7827
+ | 'user_cancel'
7828
+ | 'lockout'
7829
+ | 'system_cancel'
7830
+ | 'passcode_not_set'
7831
+ | 'authentication_failed'
7832
+ | 'unknown';
7833
+ \`\`\`
7834
+
7835
+ ### cancelAuthentication
7836
+
7837
+ Cancel an in-progress authentication prompt (Android only). No-op on iOS and web.
7838
+
7839
+ \`\`\`tsx
7840
+ await cancelAuthentication(): Promise<void>
7841
+ \`\`\`
7842
+
7843
+ ---
7844
+
7845
+ ## Passkey Functions
7846
+
7847
+ ### isPasskeySupported
7848
+
7849
+ Check if passkeys (WebAuthn/FIDO2) are supported on this device/browser.
7850
+
7851
+ \`\`\`tsx
7852
+ await isPasskeySupported(): Promise<boolean>
7853
+
7854
+ // Web: checks PublicKeyCredential + isUserVerifyingPlatformAuthenticatorAvailable
7855
+ // Native: checks react-native-passkeys Passkey.isSupported()
7856
+ \`\`\`
7857
+
7858
+ ### createPasskey
7859
+
7860
+ Create a new passkey credential (registration / attestation ceremony).
7861
+
7862
+ \`\`\`tsx
7863
+ await createPasskey(options: PasskeyCreateOptions): Promise<PasskeyCreateResult>
7864
+ \`\`\`
7865
+
7866
+ **PasskeyCreateOptions:**
7867
+
7868
+ | Option | Type | Required | Description |
7869
+ |--------|------|----------|-------------|
7870
+ | challenge | string | Yes | Base64url-encoded challenge from server |
7871
+ | rp | { id: string; name: string } | Yes | Relying party info |
7872
+ | user | { id: string; name: string; displayName: string } | Yes | User info (id is base64url) |
7873
+ | pubKeyCredParams | PublicKeyCredentialParam[] | No | Defaults to ES256 + RS256 |
7874
+ | timeout | number | No | Timeout in ms (default 60000) |
7875
+ | authenticatorSelection | object | No | Authenticator criteria |
7876
+ | excludeCredentials | CredentialDescriptor[] | No | Prevent re-registration |
7877
+ | attestation | string | No | 'none' \\| 'indirect' \\| 'direct' \\| 'enterprise' |
7878
+
7879
+ **PasskeyCreateResult:**
7880
+
7881
+ | Property | Type | Description |
7882
+ |----------|------|-------------|
7883
+ | id | string | Credential ID (base64url) |
7884
+ | rawId | string | Raw credential ID (base64url) |
7885
+ | type | 'public-key' | Always 'public-key' |
7886
+ | response.clientDataJSON | string | Client data (base64url) |
7887
+ | response.attestationObject | string | Attestation object (base64url) |
7888
+
7889
+ ### getPasskey
7890
+
7891
+ Authenticate with an existing passkey (assertion ceremony).
7892
+
7893
+ \`\`\`tsx
7894
+ await getPasskey(options: PasskeyGetOptions): Promise<PasskeyGetResult>
7895
+ \`\`\`
7896
+
7897
+ **PasskeyGetOptions:**
7898
+
7899
+ | Option | Type | Required | Description |
7900
+ |--------|------|----------|-------------|
7901
+ | challenge | string | Yes | Base64url-encoded challenge from server |
7902
+ | rpId | string | No | Relying party ID |
7903
+ | allowCredentials | CredentialDescriptor[] | No | Allowed credentials (empty = discoverable) |
7904
+ | timeout | number | No | Timeout in ms (default 60000) |
7905
+ | userVerification | string | No | 'required' \\| 'preferred' \\| 'discouraged' |
7906
+
7907
+ **PasskeyGetResult:**
7908
+
7909
+ | Property | Type | Description |
7910
+ |----------|------|-------------|
7911
+ | id | string | Credential ID (base64url) |
7912
+ | rawId | string | Raw credential ID (base64url) |
7913
+ | type | 'public-key' | Always 'public-key' |
7914
+ | response.clientDataJSON | string | Client data (base64url) |
7915
+ | response.authenticatorData | string | Authenticator data (base64url) |
7916
+ | response.signature | string | Signature (base64url) |
7917
+ | response.userHandle | string \\| undefined | User handle (base64url) |
7918
+
7919
+ ### PasskeyError
7920
+
7921
+ Both \`createPasskey\` and \`getPasskey\` throw a \`PasskeyError\` on failure:
7922
+
7923
+ \`\`\`tsx
7924
+ interface PasskeyError {
7925
+ code: 'not_supported' | 'cancelled' | 'invalid_state' | 'not_allowed' | 'unknown';
7926
+ message: string;
7927
+ }
7928
+
7929
+ try {
7930
+ const result = await createPasskey(options);
7931
+ } catch (err) {
7932
+ const passkeyErr = err as PasskeyError;
7933
+ console.log(passkeyErr.code, passkeyErr.message);
7934
+ }
7935
+ \`\`\`
7936
+
7937
+ ---
7938
+
7939
+ ## Base64url Helpers
7940
+
7941
+ Shared utilities for encoding/decoding WebAuthn binary data.
7942
+
7943
+ \`\`\`tsx
7944
+ import { base64urlToBuffer, bufferToBase64url } from '@idealyst/biometrics';
7945
+
7946
+ // Convert base64url string to ArrayBuffer
7947
+ const buffer: ArrayBuffer = base64urlToBuffer(base64urlString);
7948
+
7949
+ // Convert ArrayBuffer to base64url string
7950
+ const str: string = bufferToBase64url(arrayBuffer);
7951
+ \`\`\`
7952
+ `,
7953
+ "idealyst://biometrics/examples": `# Biometrics Examples
7954
+
7955
+ Complete code examples for common @idealyst/biometrics patterns.
7956
+
7957
+ ## Gate Screen Access with Biometrics
7958
+
7959
+ \`\`\`tsx
7960
+ import { isBiometricAvailable, authenticate } from '@idealyst/biometrics';
7961
+ import { useEffect, useState } from 'react';
7962
+
7963
+ function ProtectedScreen({ children }: { children: React.ReactNode }) {
7964
+ const [unlocked, setUnlocked] = useState(false);
7965
+ const [error, setError] = useState<string | null>(null);
7966
+
7967
+ const unlock = async () => {
7968
+ const available = await isBiometricAvailable();
7969
+ if (!available) {
7970
+ // No biometrics \u2014 fall back to PIN or allow access
7971
+ setUnlocked(true);
7972
+ return;
7973
+ }
7974
+
7975
+ const result = await authenticate({
7976
+ promptMessage: 'Unlock to continue',
7977
+ cancelLabel: 'Cancel',
7978
+ });
7979
+
7980
+ if (result.success) {
7981
+ setUnlocked(true);
7982
+ } else {
7983
+ setError(result.message ?? 'Authentication failed');
7984
+ }
7985
+ };
7986
+
7987
+ useEffect(() => {
7988
+ unlock();
7989
+ }, []);
7990
+
7991
+ if (!unlocked) {
7992
+ return (
7993
+ <View>
7994
+ <Text>Please authenticate to continue</Text>
7995
+ {error && <Text intent="negative">{error}</Text>}
7996
+ <Button label="Try Again" onPress={unlock} />
7997
+ </View>
7998
+ );
7999
+ }
8000
+
8001
+ return <>{children}</>;
8002
+ }
8003
+ \`\`\`
8004
+
8005
+ ## Confirm Sensitive Action
8006
+
8007
+ \`\`\`tsx
8008
+ import { authenticate } from '@idealyst/biometrics';
8009
+
8010
+ async function confirmTransfer(amount: number, recipient: string) {
8011
+ const result = await authenticate({
8012
+ promptMessage: \`Confirm transfer of $\${amount} to \${recipient}\`,
8013
+ disableDeviceFallback: false,
8014
+ });
8015
+
8016
+ if (!result.success) {
8017
+ throw new Error(result.message ?? 'Authentication required');
8018
+ }
8019
+
8020
+ // Proceed with transfer
8021
+ await api.transfer({ amount, recipient });
8022
+ }
8023
+ \`\`\`
8024
+
8025
+ ## Show Biometric Info
8026
+
8027
+ \`\`\`tsx
8028
+ import {
8029
+ isBiometricAvailable,
8030
+ getBiometricTypes,
8031
+ getSecurityLevel,
8032
+ } from '@idealyst/biometrics';
8033
+
8034
+ function BiometricSettings() {
8035
+ const [info, setInfo] = useState({
8036
+ available: false,
8037
+ types: [] as string[],
8038
+ level: 'none',
8039
+ });
8040
+
8041
+ useEffect(() => {
8042
+ async function load() {
8043
+ const [available, types, level] = await Promise.all([
8044
+ isBiometricAvailable(),
8045
+ getBiometricTypes(),
8046
+ getSecurityLevel(),
8047
+ ]);
8048
+ setInfo({ available, types, level });
8049
+ }
8050
+ load();
8051
+ }, []);
8052
+
8053
+ return (
8054
+ <Card>
8055
+ <Text>Biometric available: {info.available ? 'Yes' : 'No'}</Text>
8056
+ <Text>Types: {info.types.join(', ') || 'None'}</Text>
8057
+ <Text>Security level: {info.level}</Text>
8058
+ </Card>
8059
+ );
8060
+ }
8061
+ \`\`\`
8062
+
8063
+ ## Passkey Registration Flow
8064
+
8065
+ \`\`\`tsx
8066
+ import { isPasskeySupported, createPasskey } from '@idealyst/biometrics';
8067
+ import type { PasskeyError } from '@idealyst/biometrics';
8068
+
8069
+ async function registerPasskey(user: { id: string; email: string; name: string }) {
8070
+ const supported = await isPasskeySupported();
8071
+ if (!supported) {
8072
+ alert('Passkeys are not supported on this device');
8073
+ return;
8074
+ }
8075
+
8076
+ // 1. Get challenge from server
8077
+ const { challenge, rpId, rpName } = await api.getRegistrationChallenge();
8078
+
8079
+ try {
8080
+ // 2. Create the passkey
8081
+ const credential = await createPasskey({
8082
+ challenge,
8083
+ rp: { id: rpId, name: rpName },
8084
+ user: {
8085
+ id: user.id,
8086
+ name: user.email,
8087
+ displayName: user.name,
8088
+ },
8089
+ authenticatorSelection: {
8090
+ residentKey: 'required',
8091
+ userVerification: 'required',
8092
+ },
8093
+ });
8094
+
8095
+ // 3. Send to server for verification and storage
8096
+ await api.verifyRegistration({
8097
+ id: credential.id,
8098
+ rawId: credential.rawId,
8099
+ clientDataJSON: credential.response.clientDataJSON,
8100
+ attestationObject: credential.response.attestationObject,
8101
+ });
8102
+
8103
+ alert('Passkey registered successfully!');
8104
+ } catch (err) {
8105
+ const passkeyErr = err as PasskeyError;
8106
+ if (passkeyErr.code === 'cancelled') {
8107
+ // User cancelled \u2014 do nothing
8108
+ return;
8109
+ }
8110
+ alert(\`Failed to register passkey: \${passkeyErr.message}\`);
8111
+ }
8112
+ }
8113
+ \`\`\`
8114
+
8115
+ ## Passkey Login Flow
8116
+
8117
+ \`\`\`tsx
8118
+ import { getPasskey } from '@idealyst/biometrics';
8119
+ import type { PasskeyError } from '@idealyst/biometrics';
8120
+
8121
+ async function loginWithPasskey() {
8122
+ // 1. Get challenge from server
8123
+ const { challenge, rpId } = await api.getAuthenticationChallenge();
8124
+
8125
+ try {
8126
+ // 2. Authenticate with passkey
8127
+ const assertion = await getPasskey({
8128
+ challenge,
8129
+ rpId,
8130
+ userVerification: 'required',
8131
+ });
8132
+
8133
+ // 3. Send to server for verification
8134
+ const session = await api.verifyAuthentication({
8135
+ id: assertion.id,
8136
+ rawId: assertion.rawId,
8137
+ clientDataJSON: assertion.response.clientDataJSON,
8138
+ authenticatorData: assertion.response.authenticatorData,
8139
+ signature: assertion.response.signature,
8140
+ userHandle: assertion.response.userHandle,
8141
+ });
8142
+
8143
+ return session;
8144
+ } catch (err) {
8145
+ const passkeyErr = err as PasskeyError;
8146
+ if (passkeyErr.code === 'cancelled') return null;
8147
+ throw passkeyErr;
8148
+ }
8149
+ }
8150
+ \`\`\`
8151
+
8152
+ ## Login Screen with Passkey + Fallback
8153
+
8154
+ \`\`\`tsx
8155
+ import { isPasskeySupported, getPasskey } from '@idealyst/biometrics';
8156
+ import type { PasskeyError } from '@idealyst/biometrics';
8157
+
8158
+ function LoginScreen() {
8159
+ const [passkeyAvailable, setPasskeyAvailable] = useState(false);
8160
+ const [loading, setLoading] = useState(false);
8161
+
8162
+ useEffect(() => {
8163
+ isPasskeySupported().then(setPasskeyAvailable);
8164
+ }, []);
8165
+
8166
+ const handlePasskeyLogin = async () => {
8167
+ setLoading(true);
8168
+ try {
8169
+ const { challenge, rpId } = await api.getAuthenticationChallenge();
8170
+ const assertion = await getPasskey({ challenge, rpId });
8171
+ const session = await api.verifyAuthentication(assertion);
8172
+ navigateToHome(session);
8173
+ } catch (err) {
8174
+ const e = err as PasskeyError;
8175
+ if (e.code !== 'cancelled') {
8176
+ showError(e.message);
8177
+ }
8178
+ } finally {
8179
+ setLoading(false);
8180
+ }
8181
+ };
8182
+
8183
+ return (
8184
+ <View>
8185
+ <Text variant="headline">Welcome Back</Text>
8186
+
8187
+ {passkeyAvailable && (
8188
+ <Button
8189
+ label="Sign in with Passkey"
8190
+ iconName="fingerprint"
8191
+ onPress={handlePasskeyLogin}
8192
+ loading={loading}
8193
+ />
8194
+ )}
8195
+
8196
+ <Button
8197
+ label="Sign in with Email"
8198
+ intent="neutral"
8199
+ onPress={navigateToEmailLogin}
8200
+ />
8201
+ </View>
8202
+ );
8203
+ }
8204
+ \`\`\`
8205
+
8206
+ ## Best Practices
8207
+
8208
+ 1. **Always check availability first** \u2014 Call \`isBiometricAvailable()\` or \`isPasskeySupported()\` before prompting
8209
+ 2. **Provide fallbacks** \u2014 Not all devices support biometrics. Offer PIN/password alternatives
8210
+ 3. **Handle cancellation gracefully** \u2014 Users may cancel the prompt. Don't show errors for \`user_cancel\` / \`cancelled\`
8211
+ 4. **Use try/catch for passkeys** \u2014 Passkey functions throw \`PasskeyError\` on failure
8212
+ 5. **Server-side validation** \u2014 Always verify passkey responses on your server. The client is untrusted
8213
+ 6. **Base64url encoding** \u2014 All binary WebAuthn data is encoded as base64url strings for transport
8214
+ 7. **Set rpId correctly** \u2014 The relying party ID must match your domain. On native, it's your associated domain
8215
+ 8. **Lockout handling** \u2014 After too many failed biometric attempts, the device locks out. Handle the \`lockout\` error
8216
+ 9. **iOS permissions** \u2014 FaceID requires \`NSFaceIDUsageDescription\` in Info.plist
8217
+ 10. **Android associated domains** \u2014 Passkeys on Android require a Digital Asset Links file at \`/.well-known/assetlinks.json\`
8218
+ `
8219
+ };
8220
+
8221
+ // src/data/payments-guides.ts
8222
+ var paymentsGuides = {
8223
+ "idealyst://payments/overview": `# @idealyst/payments Overview
8224
+
8225
+ Cross-platform payment provider abstractions for React and React Native. Wraps Stripe's Platform Pay API for Apple Pay and Google Pay on mobile.
8226
+
8227
+ ## Features
8228
+
8229
+ - **Apple Pay** \u2014 Native iOS payment sheet via Stripe SDK
8230
+ - **Google Pay** \u2014 Native Android payment sheet via Stripe SDK
8231
+ - **Cross-Platform** \u2014 Single API for React Native and web (web is stub/noop)
8232
+ - **Flat Functions + Hook** \u2014 Use \`initializePayments()\`, \`confirmPayment()\` directly, or \`usePayments()\` hook
8233
+ - **Stripe Platform Pay** \u2014 Wraps \`@stripe/stripe-react-native\` Platform Pay API
8234
+ - **Graceful Degradation** \u2014 Falls back cleanly when Stripe SDK isn't installed
8235
+ - **TypeScript** \u2014 Full type safety
8236
+
8237
+ ## Installation
8238
+
8239
+ \`\`\`bash
8240
+ yarn add @idealyst/payments
8241
+
8242
+ # React Native \u2014 required for mobile payments:
8243
+ yarn add @stripe/stripe-react-native
8244
+ cd ios && pod install
8245
+ \`\`\`
8246
+
8247
+ ## Web Support
8248
+
8249
+ Web provides a functional stub \u2014 \`initializePayments()\` succeeds but all payment methods report as unavailable. Payment actions throw descriptive errors pointing to Stripe Elements (\`@stripe/react-stripe-js\`).
8250
+
8251
+ ## Quick Start
8252
+
8253
+ \`\`\`tsx
8254
+ import { usePayments } from '@idealyst/payments';
8255
+
8256
+ function CheckoutScreen() {
8257
+ const {
8258
+ isReady,
8259
+ isApplePayAvailable,
8260
+ isGooglePayAvailable,
8261
+ isProcessing,
8262
+ confirmPayment,
8263
+ } = usePayments({
8264
+ config: {
8265
+ publishableKey: 'pk_test_xxx',
8266
+ merchantIdentifier: 'merchant.com.myapp',
8267
+ merchantName: 'My App',
8268
+ merchantCountryCode: 'US',
8269
+ },
8270
+ });
8271
+
8272
+ const handlePay = async () => {
8273
+ const result = await confirmPayment({
8274
+ clientSecret: 'pi_xxx_secret_xxx', // from server
8275
+ amount: { amount: 1099, currencyCode: 'usd' },
8276
+ });
8277
+ console.log('Paid:', result.paymentIntentId);
8278
+ };
8279
+
8280
+ if (!isReady) return null;
8281
+
8282
+ return (
8283
+ <Button
8284
+ onPress={handlePay}
8285
+ disabled={isProcessing}
8286
+ label={isApplePayAvailable ? 'Apple Pay' : 'Google Pay'}
8287
+ />
8288
+ );
8289
+ }
8290
+ \`\`\`
8291
+
8292
+ ## Platform Support
8293
+
8294
+ | Feature | iOS | Android | Web |
8295
+ |---------|-----|---------|-----|
8296
+ | Apple Pay | Yes | \u2014 | \u2014 |
8297
+ | Google Pay | \u2014 | Yes | \u2014 |
8298
+ | usePayments hook | Yes | Yes | Stub |
8299
+ | confirmPayment | Yes | Yes | Throws |
8300
+ | createPaymentMethod | Yes | Yes | Throws |
8301
+ `,
8302
+ "idealyst://payments/api": `# Payments API Reference
8303
+
8304
+ Complete API reference for @idealyst/payments.
8305
+
8306
+ ---
8307
+
8308
+ ## Flat Functions
8309
+
8310
+ ### initializePayments
8311
+
8312
+ Initialize the Stripe SDK for platform payments.
8313
+
8314
+ \`\`\`tsx
8315
+ import { initializePayments } from '@idealyst/payments';
8316
+
8317
+ await initializePayments({
8318
+ publishableKey: 'pk_test_xxx',
8319
+ merchantIdentifier: 'merchant.com.myapp', // iOS Apple Pay
8320
+ merchantName: 'My App',
8321
+ merchantCountryCode: 'US',
8322
+ urlScheme: 'com.myapp', // for 3D Secure redirects
8323
+ testEnvironment: true, // Google Pay test mode
8324
+ });
8325
+ \`\`\`
8326
+
8327
+ **PaymentConfig:**
8328
+
8329
+ | Option | Type | Required | Description |
8330
+ |--------|------|----------|-------------|
8331
+ | publishableKey | string | Yes | Stripe publishable key |
8332
+ | merchantIdentifier | string | No | Apple Pay merchant ID (iOS) |
8333
+ | merchantName | string | Yes | Display name on payment sheet |
8334
+ | merchantCountryCode | string | Yes | ISO 3166-1 alpha-2 country |
8335
+ | urlScheme | string | No | URL scheme for 3D Secure |
8336
+ | testEnvironment | boolean | No | Google Pay test mode (default: false) |
8337
+
8338
+ ### checkPaymentAvailability
8339
+
8340
+ Check which payment methods are available on the current device.
8341
+
8342
+ \`\`\`tsx
8343
+ import { checkPaymentAvailability } from '@idealyst/payments';
8344
+
8345
+ const methods = await checkPaymentAvailability();
8346
+ // [
8347
+ // { type: 'apple_pay', isAvailable: true },
8348
+ // { type: 'google_pay', isAvailable: false, unavailableReason: '...' },
8349
+ // { type: 'card', isAvailable: true },
8350
+ // ]
8351
+ \`\`\`
8352
+
8353
+ ### confirmPayment
8354
+
8355
+ Present the platform payment sheet and confirm a PaymentIntent. Requires a \`clientSecret\` from a server-created PaymentIntent.
8356
+
8357
+ \`\`\`tsx
8358
+ import { confirmPayment } from '@idealyst/payments';
8359
+
8360
+ const result = await confirmPayment({
8361
+ clientSecret: 'pi_xxx_secret_xxx',
8362
+ amount: { amount: 1099, currencyCode: 'usd' },
8363
+ lineItems: [
8364
+ { label: 'Widget', amount: 999 },
8365
+ { label: 'Tax', amount: 100 },
8366
+ ],
8367
+ billingAddress: {
8368
+ isRequired: true,
8369
+ format: 'FULL',
8370
+ },
8371
+ });
8372
+
8373
+ console.log(result.paymentIntentId); // 'pi_xxx'
8374
+ console.log(result.status); // 'succeeded'
8375
+ \`\`\`
8376
+
8377
+ ### createPaymentMethod
8378
+
8379
+ Present the payment sheet and create a payment method without confirming. Returns a payment method ID for server-side processing.
8380
+
8381
+ \`\`\`tsx
8382
+ import { createPaymentMethod } from '@idealyst/payments';
8383
+
8384
+ const result = await createPaymentMethod({
8385
+ amount: { amount: 1099, currencyCode: 'usd' },
8386
+ });
8387
+
8388
+ console.log(result.paymentMethodId); // 'pm_xxx'
8389
+ // Send to your server for processing
8390
+ \`\`\`
8391
+
8392
+ ### getPaymentStatus
8393
+
8394
+ Get the current payment provider status.
8395
+
8396
+ \`\`\`tsx
8397
+ import { getPaymentStatus } from '@idealyst/payments';
8398
+
8399
+ const status = getPaymentStatus();
8400
+ // { state: 'ready', availablePaymentMethods: [...], isPaymentAvailable: true }
8401
+ \`\`\`
8402
+
8403
+ ---
8404
+
8405
+ ## usePayments Hook
8406
+
8407
+ Convenience hook that wraps the flat functions with React state management.
8408
+
8409
+ \`\`\`tsx
8410
+ import { usePayments } from '@idealyst/payments';
8411
+
8412
+ const {
8413
+ // State
8414
+ status, // PaymentProviderStatus
8415
+ isReady, // boolean
8416
+ isProcessing, // boolean
8417
+ availablePaymentMethods, // PaymentMethodAvailability[]
8418
+ isApplePayAvailable, // boolean
8419
+ isGooglePayAvailable,// boolean
8420
+ isPaymentAvailable, // boolean
8421
+ error, // PaymentError | null
8422
+
8423
+ // Actions
8424
+ initialize, // (config: PaymentConfig) => Promise<void>
8425
+ checkAvailability, // () => Promise<PaymentMethodAvailability[]>
8426
+ confirmPayment, // (request: PaymentSheetRequest) => Promise<PaymentResult>
8427
+ createPaymentMethod, // (request: PaymentSheetRequest) => Promise<PaymentResult>
8428
+ clearError, // () => void
8429
+ } = usePayments(options?);
8430
+ \`\`\`
8431
+
8432
+ **UsePaymentsOptions:**
8433
+
8434
+ | Option | Type | Default | Description |
8435
+ |--------|------|---------|-------------|
8436
+ | config | PaymentConfig | \u2014 | Auto-initialize with this config |
8437
+ | autoCheckAvailability | boolean | true | Check availability after init |
8438
+
8439
+ ---
8440
+
8441
+ ## Types
8442
+
8443
+ ### PaymentMethodType
8444
+
8445
+ \`\`\`tsx
8446
+ type PaymentMethodType = 'apple_pay' | 'google_pay' | 'card';
8447
+ \`\`\`
8448
+
8449
+ ### PaymentAmount
8450
+
8451
+ \`\`\`tsx
8452
+ interface PaymentAmount {
8453
+ amount: number; // smallest currency unit (cents)
8454
+ currencyCode: string; // ISO 4217 (e.g., 'usd')
8455
+ }
8456
+ \`\`\`
8457
+
8458
+ ### PaymentSheetRequest
8459
+
8460
+ \`\`\`tsx
8461
+ interface PaymentSheetRequest {
8462
+ clientSecret?: string; // required for confirmPayment
8463
+ amount: PaymentAmount;
8464
+ lineItems?: PaymentLineItem[];
8465
+ billingAddress?: BillingAddressConfig;
8466
+ allowedPaymentMethods?: PaymentMethodType[];
8467
+ }
8468
+ \`\`\`
8469
+
8470
+ ### PaymentResult
8471
+
8472
+ \`\`\`tsx
8473
+ interface PaymentResult {
8474
+ paymentMethodType: PaymentMethodType;
8475
+ paymentMethodId?: string; // for createPaymentMethod
8476
+ paymentIntentId?: string; // for confirmPayment
8477
+ status?: string; // 'succeeded', 'requires_capture', etc.
8478
+ }
8479
+ \`\`\`
8480
+
8481
+ ### PaymentError
6755
8482
 
6756
8483
  \`\`\`tsx
6757
- import React from 'react';
6758
- import { LineChart } from '@idealyst/charts';
6759
- import type { ChartDataSeries } from '@idealyst/charts';
8484
+ interface PaymentError {
8485
+ code: PaymentErrorCode;
8486
+ message: string;
8487
+ originalError?: unknown;
8488
+ }
8489
+
8490
+ type PaymentErrorCode =
8491
+ | 'not_initialized'
8492
+ | 'not_available'
8493
+ | 'not_supported'
8494
+ | 'user_cancelled'
8495
+ | 'payment_failed'
8496
+ | 'network_error'
8497
+ | 'invalid_config'
8498
+ | 'invalid_request'
8499
+ | 'provider_error'
8500
+ | 'unknown';
8501
+ \`\`\`
8502
+ `,
8503
+ "idealyst://payments/examples": `# Payments Examples
6760
8504
 
6761
- // Each series has: id, name, data, color? \u2014 NO 'label' property
6762
- const series: ChartDataSeries[] = [
6763
- {
6764
- id: 'product-a',
6765
- name: 'Product A', // Use 'name' \u2014 NOT 'label'
6766
- data: [
6767
- { x: 'Q1', y: 120 },
6768
- { x: 'Q2', y: 150 },
6769
- { x: 'Q3', y: 180 },
6770
- { x: 'Q4', y: 210 },
6771
- ],
6772
- color: '#2196F3',
6773
- },
6774
- {
6775
- id: 'product-b',
6776
- name: 'Product B', // Use 'name' \u2014 NOT 'label'
6777
- data: [
6778
- { x: 'Q1', y: 80 },
6779
- { x: 'Q2', y: 110 },
6780
- { x: 'Q3', y: 95 },
6781
- { x: 'Q4', y: 140 },
6782
- ],
6783
- color: '#FF9800',
6784
- },
6785
- ];
8505
+ Complete code examples for common @idealyst/payments patterns.
8506
+
8507
+ ## Basic Checkout with Hook
8508
+
8509
+ \`\`\`tsx
8510
+ import { usePayments } from '@idealyst/payments';
8511
+ import { Button, View, Text } from '@idealyst/components';
8512
+
8513
+ function CheckoutScreen({ clientSecret }: { clientSecret: string }) {
8514
+ const {
8515
+ isReady,
8516
+ isApplePayAvailable,
8517
+ isGooglePayAvailable,
8518
+ isProcessing,
8519
+ error,
8520
+ confirmPayment,
8521
+ clearError,
8522
+ } = usePayments({
8523
+ config: {
8524
+ publishableKey: 'pk_test_xxx',
8525
+ merchantIdentifier: 'merchant.com.myapp',
8526
+ merchantName: 'My App',
8527
+ merchantCountryCode: 'US',
8528
+ testEnvironment: __DEV__,
8529
+ },
8530
+ });
8531
+
8532
+ const handlePay = async () => {
8533
+ try {
8534
+ const result = await confirmPayment({
8535
+ clientSecret,
8536
+ amount: { amount: 1099, currencyCode: 'usd' },
8537
+ lineItems: [
8538
+ { label: 'Widget', amount: 999 },
8539
+ { label: 'Tax', amount: 100 },
8540
+ ],
8541
+ });
8542
+
8543
+ // Payment succeeded
8544
+ console.log('Payment confirmed:', result.paymentIntentId);
8545
+ } catch (err) {
8546
+ // Error is automatically set in hook state
8547
+ console.error('Payment failed:', err);
8548
+ }
8549
+ };
8550
+
8551
+ if (!isReady) {
8552
+ return <Text>Loading payment methods...</Text>;
8553
+ }
8554
+
8555
+ const canPay = isApplePayAvailable || isGooglePayAvailable;
6786
8556
 
6787
- function ComparisonChart() {
6788
8557
  return (
6789
- <LineChart
6790
- data={series}
6791
- height={350}
6792
- curve="monotone"
6793
- showDots
6794
- animate
6795
- />
8558
+ <View>
8559
+ {canPay ? (
8560
+ <Button
8561
+ onPress={handlePay}
8562
+ disabled={isProcessing}
8563
+ intent="primary"
8564
+ >
8565
+ {isApplePayAvailable ? 'Pay with Apple Pay' : 'Pay with Google Pay'}
8566
+ </Button>
8567
+ ) : (
8568
+ <Text>No payment methods available</Text>
8569
+ )}
8570
+
8571
+ {error && (
8572
+ <View>
8573
+ <Text intent="danger">{error.message}</Text>
8574
+ <Button onPress={clearError}>Dismiss</Button>
8575
+ </View>
8576
+ )}
8577
+ </View>
6796
8578
  );
6797
8579
  }
6798
8580
  \`\`\`
6799
8581
 
6800
- ## Bar Chart
8582
+ ## Create Payment Method (Server-Side Confirm)
6801
8583
 
6802
8584
  \`\`\`tsx
6803
- import React from 'react';
6804
- import { View, Text } from '@idealyst/components';
6805
- import { BarChart } from '@idealyst/charts';
8585
+ import { usePayments } from '@idealyst/payments';
8586
+
8587
+ function DonateScreen() {
8588
+ const { isReady, isPaymentAvailable, createPaymentMethod } = usePayments({
8589
+ config: {
8590
+ publishableKey: 'pk_test_xxx',
8591
+ merchantName: 'Charity',
8592
+ merchantCountryCode: 'US',
8593
+ },
8594
+ });
6806
8595
 
6807
- const categories = [
6808
- { x: 'Electronics', y: 45 },
6809
- { x: 'Clothing', y: 32 },
6810
- { x: 'Books', y: 18 },
6811
- { x: 'Food', y: 56 },
6812
- { x: 'Sports', y: 28 },
6813
- ];
8596
+ const handleDonate = async (amountCents: number) => {
8597
+ const result = await createPaymentMethod({
8598
+ amount: { amount: amountCents, currencyCode: 'usd' },
8599
+ });
8600
+
8601
+ // Send payment method to your server
8602
+ await fetch('/api/donate', {
8603
+ method: 'POST',
8604
+ body: JSON.stringify({
8605
+ paymentMethodId: result.paymentMethodId,
8606
+ amount: amountCents,
8607
+ }),
8608
+ });
8609
+ };
6814
8610
 
6815
- function CategoryBreakdown() {
6816
8611
  return (
6817
- <View padding="md" gap="md">
6818
- <Text typography="h6" weight="bold">Sales by Category</Text>
6819
- <BarChart
6820
- data={[{ id: 'units', name: 'Units Sold', data: categories }]}
6821
- height={300}
6822
- barRadius={4}
6823
- animate
6824
- yAxis={{ tickFormat: (value: number | string | Date) => \`\${value} units\` }}
6825
- />
8612
+ <View>
8613
+ <Button onPress={() => handleDonate(500)} disabled={!isPaymentAvailable}>
8614
+ Donate $5
8615
+ </Button>
8616
+ <Button onPress={() => handleDonate(1000)} disabled={!isPaymentAvailable}>
8617
+ Donate $10
8618
+ </Button>
6826
8619
  </View>
6827
8620
  );
6828
8621
  }
6829
8622
  \`\`\`
6830
8623
 
6831
- ## Stacked Bar Chart
8624
+ ## Flat Functions (No Hook)
6832
8625
 
6833
8626
  \`\`\`tsx
6834
- import React from 'react';
6835
- import { BarChart } from '@idealyst/charts';
8627
+ import {
8628
+ initializePayments,
8629
+ checkPaymentAvailability,
8630
+ confirmPayment,
8631
+ getPaymentStatus,
8632
+ } from '@idealyst/payments';
8633
+ import type { PaymentError } from '@idealyst/payments';
8634
+
8635
+ // Initialize once at app startup
8636
+ async function setupPayments() {
8637
+ await initializePayments({
8638
+ publishableKey: 'pk_test_xxx',
8639
+ merchantIdentifier: 'merchant.com.myapp',
8640
+ merchantName: 'My App',
8641
+ merchantCountryCode: 'US',
8642
+ });
6836
8643
 
6837
- function StackedBarExample() {
6838
- return (
6839
- <BarChart
6840
- data={[
6841
- {
6842
- id: 'online',
6843
- name: 'Online',
6844
- data: [
6845
- { x: 'Q1', y: 100 },
6846
- { x: 'Q2', y: 120 },
6847
- { x: 'Q3', y: 90 },
6848
- ],
6849
- color: '#4CAF50',
6850
- },
6851
- {
6852
- id: 'in-store',
6853
- name: 'In-Store',
6854
- data: [
6855
- { x: 'Q1', y: 60 },
6856
- { x: 'Q2', y: 80 },
6857
- { x: 'Q3', y: 70 },
6858
- ],
6859
- color: '#2196F3',
6860
- },
6861
- ]}
6862
- height={300}
6863
- stacked
6864
- animate
6865
- />
6866
- );
8644
+ const status = getPaymentStatus();
8645
+ console.log('Payment ready:', status.state === 'ready');
8646
+ }
8647
+
8648
+ // Check availability
8649
+ async function canPay() {
8650
+ const methods = await checkPaymentAvailability();
8651
+ return methods.some(m => m.isAvailable);
8652
+ }
8653
+
8654
+ // Process payment
8655
+ async function processPayment(clientSecret: string, totalCents: number) {
8656
+ try {
8657
+ const result = await confirmPayment({
8658
+ clientSecret,
8659
+ amount: { amount: totalCents, currencyCode: 'usd' },
8660
+ });
8661
+ return result;
8662
+ } catch (err) {
8663
+ const paymentErr = err as PaymentError;
8664
+ if (paymentErr.code === 'user_cancelled') {
8665
+ return null; // User cancelled \u2014 not an error
8666
+ }
8667
+ throw paymentErr;
8668
+ }
6867
8669
  }
6868
8670
  \`\`\`
6869
8671
 
6870
- ## Horizontal Bar Chart
8672
+ ## Platform-Conditional UI
6871
8673
 
6872
8674
  \`\`\`tsx
6873
- import React from 'react';
6874
- import { BarChart } from '@idealyst/charts';
8675
+ import { usePayments } from '@idealyst/payments';
8676
+ import { Platform } from 'react-native';
6875
8677
 
6876
- function HorizontalBarExample() {
6877
- const data = [
6878
- { x: 'React', y: 85 },
6879
- { x: 'Vue', y: 62 },
6880
- { x: 'Angular', y: 45 },
6881
- { x: 'Svelte', y: 38 },
6882
- ];
8678
+ function PaymentButtons({ clientSecret }: { clientSecret: string }) {
8679
+ const {
8680
+ isApplePayAvailable,
8681
+ isGooglePayAvailable,
8682
+ confirmPayment,
8683
+ isProcessing,
8684
+ } = usePayments({
8685
+ config: {
8686
+ publishableKey: 'pk_test_xxx',
8687
+ merchantName: 'My Store',
8688
+ merchantCountryCode: 'US',
8689
+ },
8690
+ });
8691
+
8692
+ const handlePlatformPay = () =>
8693
+ confirmPayment({
8694
+ clientSecret,
8695
+ amount: { amount: 2499, currencyCode: 'usd' },
8696
+ });
6883
8697
 
6884
8698
  return (
6885
- <BarChart
6886
- data={[{ id: 'popularity', name: 'Popularity', data }]}
6887
- height={250}
6888
- orientation="horizontal"
6889
- animate
6890
- />
8699
+ <View>
8700
+ {isApplePayAvailable && (
8701
+ <Button
8702
+ onPress={handlePlatformPay}
8703
+ disabled={isProcessing}
8704
+ iconName="apple"
8705
+ >
8706
+ Apple Pay
8707
+ </Button>
8708
+ )}
8709
+
8710
+ {isGooglePayAvailable && (
8711
+ <Button
8712
+ onPress={handlePlatformPay}
8713
+ disabled={isProcessing}
8714
+ iconName="google"
8715
+ >
8716
+ Google Pay
8717
+ </Button>
8718
+ )}
8719
+
8720
+ {/* Always show a card fallback */}
8721
+ <Button
8722
+ onPress={() => navigateToCardForm()}
8723
+ intent="secondary"
8724
+ >
8725
+ Pay with Card
8726
+ </Button>
8727
+ </View>
6891
8728
  );
6892
8729
  }
6893
8730
  \`\`\`
8731
+
8732
+ ## Best Practices
8733
+
8734
+ 1. **Server-side PaymentIntent** \u2014 Always create PaymentIntents on your server, never on the client
8735
+ 2. **Handle cancellation** \u2014 \`user_cancelled\` is not an error, don't show error UI for it
8736
+ 3. **Test environment** \u2014 Set \`testEnvironment: true\` during development for Google Pay
8737
+ 4. **Apple Pay merchant ID** \u2014 Requires Apple Developer Program and Xcode capability setup
8738
+ 5. **Amounts in cents** \u2014 All amounts are in the smallest currency unit (1099 = $10.99)
8739
+ 6. **Web fallback** \u2014 On web, use \`@stripe/react-stripe-js\` (Stripe Elements) directly
8740
+ 7. **3D Secure** \u2014 Set \`urlScheme\` in config for 3D Secure / bank redirect flows on native
8741
+ 8. **Error handling** \u2014 Always wrap \`confirmPayment\` / \`createPaymentMethod\` in try/catch
6894
8742
  `
6895
8743
  };
6896
8744
 
@@ -7811,6 +9659,156 @@ function UploadForm() {
7811
9659
  "Use customMimeTypes for specific formats like 'application/pdf'"
7812
9660
  ],
7813
9661
  relatedPackages: ["components", "camera", "storage"]
9662
+ },
9663
+ clipboard: {
9664
+ name: "Clipboard",
9665
+ npmName: "@idealyst/clipboard",
9666
+ description: "Cross-platform clipboard and OTP autofill for React and React Native. Copy/paste text and auto-detect SMS verification codes on mobile.",
9667
+ category: "utility",
9668
+ platforms: ["web", "native"],
9669
+ documentationStatus: "full",
9670
+ installation: "yarn add @idealyst/clipboard",
9671
+ peerDependencies: [
9672
+ "@react-native-clipboard/clipboard (native)",
9673
+ "react-native-otp-verify (Android OTP, optional)"
9674
+ ],
9675
+ features: [
9676
+ "Cross-platform copy/paste with async API",
9677
+ "Android SMS OTP auto-read via SMS Retriever API (no permissions)",
9678
+ "iOS OTP keyboard autofill via TextInput props",
9679
+ "Clipboard change listeners",
9680
+ "Graceful degradation when native modules missing"
9681
+ ],
9682
+ quickStart: `import { clipboard } from '@idealyst/clipboard';
9683
+
9684
+ // Copy text
9685
+ await clipboard.copy('Hello, world!');
9686
+
9687
+ // Paste text
9688
+ const text = await clipboard.paste();
9689
+
9690
+ // OTP auto-fill (mobile)
9691
+ import { useOTPAutoFill, OTP_INPUT_PROPS } from '@idealyst/clipboard';
9692
+
9693
+ function OTPScreen() {
9694
+ const { code, startListening } = useOTPAutoFill({
9695
+ codeLength: 6,
9696
+ onCodeReceived: (otp) => verifyOTP(otp),
9697
+ });
9698
+
9699
+ useEffect(() => { startListening(); }, []);
9700
+
9701
+ return <TextInput value={code ?? ''} {...OTP_INPUT_PROPS} />;
9702
+ }`,
9703
+ apiHighlights: [
9704
+ "clipboard.copy(text) - Copy text to clipboard",
9705
+ "clipboard.paste() - Read text from clipboard",
9706
+ "clipboard.hasText() - Check if clipboard has text",
9707
+ "clipboard.addListener(fn) - Listen for copy events",
9708
+ "useOTPAutoFill({ codeLength, onCodeReceived }) - Auto-detect SMS OTP codes (Android)",
9709
+ "OTP_INPUT_PROPS - TextInput props for iOS OTP keyboard autofill"
9710
+ ],
9711
+ relatedPackages: ["storage", "components"]
9712
+ },
9713
+ biometrics: {
9714
+ name: "Biometrics",
9715
+ npmName: "@idealyst/biometrics",
9716
+ description: "Cross-platform biometric authentication and passkeys (WebAuthn/FIDO2) for React and React Native. FaceID, TouchID, fingerprint, iris, and passwordless login.",
9717
+ category: "auth",
9718
+ platforms: ["web", "native"],
9719
+ documentationStatus: "full",
9720
+ installation: "yarn add @idealyst/biometrics",
9721
+ peerDependencies: [
9722
+ "expo-local-authentication (native biometrics)",
9723
+ "react-native-passkeys (native passkeys, optional)"
9724
+ ],
9725
+ features: [
9726
+ "Local biometric auth \u2014 FaceID, TouchID, fingerprint, iris",
9727
+ "Passkeys (WebAuthn/FIDO2) \u2014 passwordless login with cryptographic credentials",
9728
+ "Cross-platform \u2014 single API for web and React Native",
9729
+ "Web biometrics via WebAuthn userVerification",
9730
+ "Web passkeys via navigator.credentials",
9731
+ "Graceful degradation when native modules missing",
9732
+ "Base64url encoding helpers for WebAuthn data"
9733
+ ],
9734
+ quickStart: `import { isBiometricAvailable, authenticate } from '@idealyst/biometrics';
9735
+
9736
+ // Check biometric availability
9737
+ const available = await isBiometricAvailable();
9738
+
9739
+ // Authenticate
9740
+ if (available) {
9741
+ const result = await authenticate({ promptMessage: 'Verify your identity' });
9742
+ if (result.success) {
9743
+ // Authenticated!
9744
+ }
9745
+ }
9746
+
9747
+ // Passkeys
9748
+ import { isPasskeySupported, createPasskey, getPasskey } from '@idealyst/biometrics';
9749
+
9750
+ const credential = await createPasskey({
9751
+ challenge: serverChallenge,
9752
+ rp: { id: 'example.com', name: 'My App' },
9753
+ user: { id: userId, name: email, displayName: name },
9754
+ });`,
9755
+ apiHighlights: [
9756
+ "isBiometricAvailable() - Check biometric hardware and enrollment",
9757
+ "getBiometricTypes() - Get available biometric types",
9758
+ "getSecurityLevel() - Get device security level",
9759
+ "authenticate(options) - Prompt for biometric auth",
9760
+ "cancelAuthentication() - Cancel auth (Android)",
9761
+ "isPasskeySupported() - Check passkey support",
9762
+ "createPasskey(options) - Register a new passkey",
9763
+ "getPasskey(options) - Authenticate with passkey"
9764
+ ],
9765
+ relatedPackages: ["oauth-client", "storage"]
9766
+ },
9767
+ payments: {
9768
+ name: "Payments",
9769
+ npmName: "@idealyst/payments",
9770
+ description: "Cross-platform payment provider abstractions for React and React Native. Wraps Stripe Platform Pay API for Apple Pay and Google Pay on mobile. Web provides a stub directing to Stripe Elements.",
9771
+ category: "utility",
9772
+ platforms: ["web", "native"],
9773
+ documentationStatus: "full",
9774
+ installation: "yarn add @idealyst/payments @stripe/stripe-react-native",
9775
+ peerDependencies: [
9776
+ "@stripe/stripe-react-native (native)"
9777
+ ],
9778
+ features: [
9779
+ "Apple Pay via Stripe Platform Pay (iOS)",
9780
+ "Google Pay via Stripe Platform Pay (Android)",
9781
+ "Flat function API + usePayments convenience hook",
9782
+ "PaymentIntent confirmation flow",
9783
+ "Payment method creation flow",
9784
+ "Normalized error handling across platforms",
9785
+ "Graceful degradation when Stripe SDK missing",
9786
+ "Web stub with guidance to Stripe Elements"
9787
+ ],
9788
+ quickStart: `import { usePayments } from '@idealyst/payments';
9789
+
9790
+ const { isReady, isApplePayAvailable, confirmPayment } = usePayments({
9791
+ config: {
9792
+ publishableKey: 'pk_test_xxx',
9793
+ merchantIdentifier: 'merchant.com.myapp',
9794
+ merchantName: 'My App',
9795
+ merchantCountryCode: 'US',
9796
+ },
9797
+ });
9798
+
9799
+ const result = await confirmPayment({
9800
+ clientSecret: 'pi_xxx_secret_xxx',
9801
+ amount: { amount: 1099, currencyCode: 'usd' },
9802
+ });`,
9803
+ apiHighlights: [
9804
+ "initializePayments(config) - Initialize Stripe SDK",
9805
+ "checkPaymentAvailability() - Check Apple Pay / Google Pay / card",
9806
+ "confirmPayment(request) - Present sheet and confirm PaymentIntent",
9807
+ "createPaymentMethod(request) - Present sheet and create payment method",
9808
+ "getPaymentStatus() - Get current provider status",
9809
+ "usePayments(options?) - Convenience hook with state management"
9810
+ ],
9811
+ relatedPackages: ["biometrics", "oauth-client"]
7814
9812
  }
7815
9813
  };
7816
9814
  function getPackagesByCategory() {
@@ -19909,7 +21907,7 @@ function getStorageGuide(args) {
19909
21907
  const guide = storageGuides[uri];
19910
21908
  if (!guide) {
19911
21909
  return textResponse(
19912
- `Topic "${topic}" not found. Available topics: overview, api, examples`
21910
+ `Topic "${topic}" not found. Available topics: overview, api, examples, secure`
19913
21911
  );
19914
21912
  }
19915
21913
  return textResponse(guide);
@@ -20035,6 +22033,39 @@ function getChartsGuide(args) {
20035
22033
  }
20036
22034
  return textResponse(guide);
20037
22035
  }
22036
+ function getClipboardGuide(args) {
22037
+ const topic = args.topic;
22038
+ const uri = `idealyst://clipboard/${topic}`;
22039
+ const guide = clipboardGuides[uri];
22040
+ if (!guide) {
22041
+ return textResponse(
22042
+ `Topic "${topic}" not found. Available topics: overview, api, examples`
22043
+ );
22044
+ }
22045
+ return textResponse(guide);
22046
+ }
22047
+ function getBiometricsGuide(args) {
22048
+ const topic = args.topic;
22049
+ const uri = `idealyst://biometrics/${topic}`;
22050
+ const guide = biometricsGuides[uri];
22051
+ if (!guide) {
22052
+ return textResponse(
22053
+ `Topic "${topic}" not found. Available topics: overview, api, examples`
22054
+ );
22055
+ }
22056
+ return textResponse(guide);
22057
+ }
22058
+ function getPaymentsGuide(args) {
22059
+ const topic = args.topic;
22060
+ const uri = `idealyst://payments/${topic}`;
22061
+ const guide = paymentsGuides[uri];
22062
+ if (!guide) {
22063
+ return textResponse(
22064
+ `Topic "${topic}" not found. Available topics: overview, api, examples`
22065
+ );
22066
+ }
22067
+ return textResponse(guide);
22068
+ }
20038
22069
  function listPackages(args = {}) {
20039
22070
  const category = args.category;
20040
22071
  if (category) {
@@ -20280,6 +22311,9 @@ var toolHandlers = {
20280
22311
  get_markdown_guide: getMarkdownGuide,
20281
22312
  get_config_guide: getConfigGuide,
20282
22313
  get_charts_guide: getChartsGuide,
22314
+ get_clipboard_guide: getClipboardGuide,
22315
+ get_biometrics_guide: getBiometricsGuide,
22316
+ get_payments_guide: getPaymentsGuide,
20283
22317
  list_packages: listPackages,
20284
22318
  get_package_docs: getPackageDocs,
20285
22319
  search_packages: searchPackages2,
@@ -20304,12 +22338,16 @@ function callTool(name, args = {}) {
20304
22338
  getAudioGuide,
20305
22339
  getAudioGuideDefinition,
20306
22340
  getAvailableComponents,
22341
+ getBiometricsGuide,
22342
+ getBiometricsGuideDefinition,
20307
22343
  getCameraGuide,
20308
22344
  getCameraGuideDefinition,
20309
22345
  getChartsGuide,
20310
22346
  getChartsGuideDefinition,
20311
22347
  getCliUsage,
20312
22348
  getCliUsageDefinition,
22349
+ getClipboardGuide,
22350
+ getClipboardGuideDefinition,
20313
22351
  getComponentDocs,
20314
22352
  getComponentDocsDefinition,
20315
22353
  getComponentExample,
@@ -20341,6 +22379,8 @@ function callTool(name, args = {}) {
20341
22379
  getOauthClientGuideDefinition,
20342
22380
  getPackageDocs,
20343
22381
  getPackageDocsDefinition,
22382
+ getPaymentsGuide,
22383
+ getPaymentsGuideDefinition,
20344
22384
  getRecipe,
20345
22385
  getRecipeDefinition,
20346
22386
  getRegistryThemeValues,