@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.
@@ -187,8 +187,8 @@ var getStorageGuideDefinition = {
187
187
  properties: {
188
188
  topic: {
189
189
  type: "string",
190
- description: "Topic to get docs for: 'overview', 'api', 'examples'",
191
- enum: ["overview", "api", "examples"]
190
+ description: "Topic to get docs for: 'overview', 'api', 'examples', 'secure'",
191
+ enum: ["overview", "api", "examples", "secure"]
192
192
  }
193
193
  },
194
194
  required: ["topic"]
@@ -359,6 +359,51 @@ var getChartsGuideDefinition = {
359
359
  required: ["topic"]
360
360
  }
361
361
  };
362
+ var getClipboardGuideDefinition = {
363
+ name: "get_clipboard_guide",
364
+ description: "Get documentation for @idealyst/clipboard cross-platform clipboard and OTP autofill package. Covers copy/paste API, useOTPAutoFill hook, and examples.",
365
+ inputSchema: {
366
+ type: "object",
367
+ properties: {
368
+ topic: {
369
+ type: "string",
370
+ description: "Topic to get docs for: 'overview', 'api', 'examples'",
371
+ enum: ["overview", "api", "examples"]
372
+ }
373
+ },
374
+ required: ["topic"]
375
+ }
376
+ };
377
+ var getBiometricsGuideDefinition = {
378
+ name: "get_biometrics_guide",
379
+ description: "Get documentation for @idealyst/biometrics cross-platform biometric authentication and passkeys (WebAuthn/FIDO2) package. Covers local biometric auth, passkey registration/login, and examples.",
380
+ inputSchema: {
381
+ type: "object",
382
+ properties: {
383
+ topic: {
384
+ type: "string",
385
+ description: "Topic to get docs for: 'overview', 'api', 'examples'",
386
+ enum: ["overview", "api", "examples"]
387
+ }
388
+ },
389
+ required: ["topic"]
390
+ }
391
+ };
392
+ var getPaymentsGuideDefinition = {
393
+ name: "get_payments_guide",
394
+ description: "Get documentation for @idealyst/payments cross-platform payment provider package. Covers Apple Pay, Google Pay, Stripe Platform Pay, usePayments hook, and examples.",
395
+ inputSchema: {
396
+ type: "object",
397
+ properties: {
398
+ topic: {
399
+ type: "string",
400
+ description: "Topic to get docs for: 'overview', 'api', 'examples'",
401
+ enum: ["overview", "api", "examples"]
402
+ }
403
+ },
404
+ required: ["topic"]
405
+ }
406
+ };
362
407
  var listPackagesDefinition = {
363
408
  name: "list_packages",
364
409
  description: "List all available Idealyst packages with descriptions, categories, and documentation status. Use this to discover what packages are available in the framework.",
@@ -513,6 +558,9 @@ var toolDefinitions = [
513
558
  getMarkdownGuideDefinition,
514
559
  getConfigGuideDefinition,
515
560
  getChartsGuideDefinition,
561
+ getClipboardGuideDefinition,
562
+ getBiometricsGuideDefinition,
563
+ getPaymentsGuideDefinition,
516
564
  // Package tools
517
565
  listPackagesDefinition,
518
566
  getPackageDocsDefinition,
@@ -2335,6 +2383,7 @@ Cross-platform storage solution for React and React Native applications. Provide
2335
2383
  - **React Native** - Uses MMKV for high-performance storage
2336
2384
  - **Web** - Uses localStorage with proper error handling
2337
2385
  - **TypeScript** - Full type safety and IntelliSense support
2386
+ - **Secure Storage** - Optional encrypted storage via \`createSecureStorage()\` (Keychain on native, Web Crypto on web)
2338
2387
 
2339
2388
  ## Installation
2340
2389
 
@@ -2738,6 +2787,127 @@ async function safeStorageOperation() {
2738
2787
  6. **Data Size** - Keep stored objects reasonably sized
2739
2788
  7. **Cleanup** - Periodically clean up unused data
2740
2789
  8. **Type Safety** - Create typed wrapper functions for better TypeScript support
2790
+ 9. **Use Secure Storage for Secrets** - Use \`createSecureStorage()\` for auth tokens, API keys, and sensitive data
2791
+ `,
2792
+ "idealyst://storage/secure": `# Secure Storage
2793
+
2794
+ Encrypted storage for sensitive data like auth tokens, API keys, and secrets. Uses the same \`IStorage\` interface as regular storage \u2014 drop-in replacement.
2795
+
2796
+ ## Installation
2797
+
2798
+ \`\`\`bash
2799
+ yarn add @idealyst/storage
2800
+
2801
+ # React Native also needs (for secure storage):
2802
+ yarn add react-native-keychain react-native-mmkv
2803
+ cd ios && pod install
2804
+ \`\`\`
2805
+
2806
+ ## Quick Start
2807
+
2808
+ \`\`\`tsx
2809
+ import { createSecureStorage } from '@idealyst/storage';
2810
+
2811
+ // Create a secure storage instance
2812
+ const secureStorage = createSecureStorage();
2813
+
2814
+ // Same API as regular storage
2815
+ await secureStorage.setItem('authToken', 'eyJhbGciOiJIUzI1NiIs...');
2816
+ const token = await secureStorage.getItem('authToken');
2817
+ await secureStorage.removeItem('authToken');
2818
+ await secureStorage.clear();
2819
+ const keys = await secureStorage.getAllKeys();
2820
+
2821
+ // Listeners work too
2822
+ const unsubscribe = secureStorage.addListener((key, value) => {
2823
+ console.log('Secure storage changed:', key);
2824
+ });
2825
+ \`\`\`
2826
+
2827
+ ## Options
2828
+
2829
+ \`\`\`tsx
2830
+ import { createSecureStorage, SecureStorageOptions } from '@idealyst/storage';
2831
+
2832
+ const secureStorage = createSecureStorage({
2833
+ prefix: 'myapp', // Namespace for keys (default: 'secure')
2834
+ });
2835
+ \`\`\`
2836
+
2837
+ The \`prefix\` option controls:
2838
+ - **Native**: Keychain service name and MMKV instance ID
2839
+ - **Web**: localStorage key prefix and IndexedDB key name
2840
+
2841
+ Use different prefixes to create isolated secure storage instances.
2842
+
2843
+ ## How It Works
2844
+
2845
+ ### React Native
2846
+ 1. A random 16-byte encryption key is generated on first use
2847
+ 2. The key is stored in the **iOS Keychain** / **Android Keystore** (hardware-backed)
2848
+ 3. An encrypted MMKV instance is created using that key
2849
+ 4. All data is encrypted at rest by MMKV's native AES encryption
2850
+ 5. Keychain accessibility is set to \`WHEN_UNLOCKED_THIS_DEVICE_ONLY\` (not backed up, only accessible when device is unlocked)
2851
+
2852
+ ### Web
2853
+ 1. A non-extractable AES-256-GCM \`CryptoKey\` is generated on first use
2854
+ 2. The key is stored in **IndexedDB** (non-extractable \u2014 cannot be read as raw bytes)
2855
+ 3. Each value is encrypted with a unique random IV before storing in localStorage
2856
+ 4. Requires a **secure context** (HTTPS) for \`crypto.subtle\` access
2857
+
2858
+ ## Usage Example: Secure Auth Service
2859
+
2860
+ \`\`\`tsx
2861
+ import { createSecureStorage } from '@idealyst/storage';
2862
+
2863
+ const secureStorage = createSecureStorage({ prefix: 'auth' });
2864
+
2865
+ interface AuthTokens {
2866
+ accessToken: string;
2867
+ refreshToken: string;
2868
+ expiresAt: number;
2869
+ }
2870
+
2871
+ class SecureAuthService {
2872
+ static async saveTokens(tokens: AuthTokens) {
2873
+ await secureStorage.setItem('tokens', JSON.stringify(tokens));
2874
+ }
2875
+
2876
+ static async getTokens(): Promise<AuthTokens | null> {
2877
+ const data = await secureStorage.getItem('tokens');
2878
+ return data ? JSON.parse(data) as AuthTokens : null;
2879
+ }
2880
+
2881
+ static async clearTokens() {
2882
+ await secureStorage.removeItem('tokens');
2883
+ }
2884
+
2885
+ static async saveApiKey(key: string) {
2886
+ await secureStorage.setItem('apiKey', key);
2887
+ }
2888
+
2889
+ static async getApiKey(): Promise<string | null> {
2890
+ return secureStorage.getItem('apiKey');
2891
+ }
2892
+ }
2893
+ \`\`\`
2894
+
2895
+ ## When to Use Secure vs Regular Storage
2896
+
2897
+ | Data Type | Use |
2898
+ |-----------|-----|
2899
+ | Auth tokens, refresh tokens | \`createSecureStorage()\` |
2900
+ | API keys, client secrets | \`createSecureStorage()\` |
2901
+ | User preferences, theme | \`storage\` (regular) |
2902
+ | Cache data | \`storage\` (regular) |
2903
+ | Session IDs | \`createSecureStorage()\` |
2904
+ | Language preference | \`storage\` (regular) |
2905
+
2906
+ ## Platform Requirements
2907
+
2908
+ - **React Native**: Requires \`react-native-keychain\` (>=9.0.0) and \`react-native-mmkv\` (>=4.0.0)
2909
+ - **Web**: Requires secure context (HTTPS) and IndexedDB support
2910
+ - **Web limitation**: IndexedDB may not be available in some private browsing modes
2741
2911
  `
2742
2912
  };
2743
2913
 
@@ -2769,7 +2939,8 @@ yarn add @idealyst/audio
2769
2939
  1. **PCM Streaming** \u2014 Audio data is delivered as \`PCMData\` chunks via callbacks, not as files
2770
2940
  2. **Audio Session** \u2014 On iOS/Android, configure the audio session category before recording/playback
2771
2941
  3. **Audio Profiles** \u2014 Pre-configured \`AudioConfig\` presets: \`speech\`, \`highQuality\`, \`studio\`, \`phone\`
2772
- 4. **Session Presets** \u2014 Pre-configured \`AudioSessionConfig\` presets: \`playback\`, \`record\`, \`voiceChat\`, \`ambient\`, \`default\`
2942
+ 4. **Session Presets** \u2014 Pre-configured \`AudioSessionConfig\` presets: \`playback\`, \`record\`, \`voiceChat\`, \`ambient\`, \`default\`, \`backgroundRecord\`
2943
+ 5. **Background Recording** \u2014 \`useBackgroundRecorder\` hook for recording that continues when the app is backgrounded (iOS/Android). Requires app-level native entitlements.
2773
2944
 
2774
2945
  ## Exports
2775
2946
 
@@ -2778,10 +2949,14 @@ import {
2778
2949
  useRecorder,
2779
2950
  usePlayer,
2780
2951
  useAudio,
2952
+ useBackgroundRecorder,
2781
2953
  AUDIO_PROFILES,
2782
2954
  SESSION_PRESETS,
2783
2955
  } from '@idealyst/audio';
2784
- import type { PCMData, AudioConfig, AudioLevel } from '@idealyst/audio';
2956
+ import type {
2957
+ PCMData, AudioConfig, AudioLevel,
2958
+ BackgroundRecorderStatus, BackgroundLifecycleInfo,
2959
+ } from '@idealyst/audio';
2785
2960
  \`\`\`
2786
2961
  `,
2787
2962
  "idealyst://audio/api": `# @idealyst/audio \u2014 API Reference
@@ -2891,6 +3066,84 @@ interface UseAudioOptions {
2891
3066
 
2892
3067
  ---
2893
3068
 
3069
+ ### useBackgroundRecorder(options?)
3070
+
3071
+ 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).
3072
+
3073
+ > **Requires app-level native configuration** \u2014 see "Background Recording Setup" below.
3074
+
3075
+ \`\`\`typescript
3076
+ interface UseBackgroundRecorderOptions {
3077
+ config?: Partial<AudioConfig>; // Audio config
3078
+ session?: Partial<AudioSessionConfig>; // Session config (default: SESSION_PRESETS.backgroundRecord)
3079
+ autoRequestPermission?: boolean; // Auto-request mic permission on mount
3080
+ levelUpdateInterval?: number; // Level update interval in ms (default: 100)
3081
+ maxBackgroundDuration?: number; // Max background recording time in ms (undefined = no limit)
3082
+ autoConfigureSession?: boolean; // Auto-configure session for background (default: true)
3083
+ onLifecycleEvent?: BackgroundLifecycleCallback; // Lifecycle event callback
3084
+ }
3085
+ \`\`\`
3086
+
3087
+ **Returns \`UseBackgroundRecorderResult\`:**
3088
+
3089
+ All properties from \`useRecorder\`, plus:
3090
+
3091
+ | Property | Type | Description |
3092
+ |----------|------|-------------|
3093
+ | isInBackground | boolean | Whether the app is currently backgrounded |
3094
+ | wasInterrupted | boolean | Whether recording was interrupted (phone call, Siri, etc.) |
3095
+ | backgroundDuration | number | Total time spent recording in background (ms) |
3096
+ | appState | AppStateStatus | Current app state (\`'active' \\| 'background' \\| 'inactive'\`) |
3097
+
3098
+ **Lifecycle events** (via \`onLifecycleEvent\`):
3099
+
3100
+ | Event | When | Extra fields |
3101
+ |-------|------|-------------|
3102
+ | \`'backgrounded'\` | App enters background while recording | \u2014 |
3103
+ | \`'foregrounded'\` | App returns to foreground while recording | \`backgroundDuration\` |
3104
+ | \`'interrupted'\` | OS interrupts recording (phone call, Siri) | \u2014 |
3105
+ | \`'interruptionEnded'\` | OS interruption ends | \`shouldResume\` |
3106
+ | \`'maxDurationReached'\` | Background recording hit \`maxBackgroundDuration\` | \`backgroundDuration\` |
3107
+ | \`'stopped'\` | Recording stopped while in background | \`backgroundDuration\` |
3108
+
3109
+ > **Note:** Interruptions use notify-only \u2014 the hook does NOT auto-resume. The consumer decides via the \`shouldResume\` flag.
3110
+
3111
+ #### Background Recording Setup
3112
+
3113
+ The OS will not allow background recording without app-level entitlements:
3114
+
3115
+ **iOS** \u2014 \`Info.plist\`:
3116
+ \`\`\`xml
3117
+ <key>UIBackgroundModes</key>
3118
+ <array><string>audio</string></array>
3119
+ \`\`\`
3120
+
3121
+ **Android** \u2014 \`AndroidManifest.xml\`:
3122
+ \`\`\`xml
3123
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
3124
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
3125
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
3126
+ <service
3127
+ android:name="com.swmansion.audioapi.system.CentralizedForegroundService"
3128
+ android:foregroundServiceType="microphone" />
3129
+ \`\`\`
3130
+
3131
+ **Expo** \u2014 \`app.json\` plugin:
3132
+ \`\`\`json
3133
+ ["react-native-audio-api", {
3134
+ "iosBackgroundMode": true,
3135
+ "androidForegroundService": true,
3136
+ "androidFSTypes": ["microphone"],
3137
+ "androidPermissions": [
3138
+ "android.permission.FOREGROUND_SERVICE",
3139
+ "android.permission.FOREGROUND_SERVICE_MICROPHONE",
3140
+ "android.permission.RECORD_AUDIO"
3141
+ ]
3142
+ }]
3143
+ \`\`\`
3144
+
3145
+ ---
3146
+
2894
3147
  ## Types
2895
3148
 
2896
3149
  ### AudioConfig
@@ -2952,6 +3205,7 @@ type AudioErrorCode =
2952
3205
  | 'DEVICE_NOT_FOUND' | 'DEVICE_IN_USE' | 'NOT_SUPPORTED'
2953
3206
  | 'SOURCE_NOT_FOUND' | 'FORMAT_NOT_SUPPORTED' | 'DECODE_ERROR' | 'PLAYBACK_ERROR' | 'BUFFER_UNDERRUN'
2954
3207
  | 'RECORDING_ERROR'
3208
+ | 'BACKGROUND_NOT_SUPPORTED' | 'BACKGROUND_MAX_DURATION'
2955
3209
  | 'INITIALIZATION_FAILED' | 'INVALID_STATE' | 'INVALID_CONFIG' | 'UNKNOWN';
2956
3210
 
2957
3211
  interface AudioError {
@@ -2980,11 +3234,12 @@ const AUDIO_PROFILES: AudioProfiles = {
2980
3234
 
2981
3235
  \`\`\`typescript
2982
3236
  const SESSION_PRESETS: SessionPresets = {
2983
- playback: { category: 'playback', mode: 'default' },
2984
- record: { category: 'record', mode: 'default' },
2985
- voiceChat: { category: 'playAndRecord', mode: 'voiceChat', categoryOptions: ['allowBluetooth', 'defaultToSpeaker'] },
2986
- ambient: { category: 'ambient', mode: 'default' },
2987
- default: { category: 'soloAmbient', mode: 'default' },
3237
+ playback: { category: 'playback', mode: 'default' },
3238
+ record: { category: 'record', mode: 'default' },
3239
+ voiceChat: { category: 'playAndRecord', mode: 'voiceChat', categoryOptions: ['allowBluetooth', 'defaultToSpeaker'] },
3240
+ ambient: { category: 'ambient', mode: 'default' },
3241
+ default: { category: 'soloAmbient', mode: 'default' },
3242
+ backgroundRecord: { category: 'playAndRecord', mode: 'spokenAudio', categoryOptions: ['defaultToSpeaker', 'allowBluetooth', 'allowBluetoothA2DP', 'mixWithOthers'] },
2988
3243
  };
2989
3244
  \`\`\`
2990
3245
  `,
@@ -3157,6 +3412,77 @@ function VoiceChatScreen() {
3157
3412
  }
3158
3413
  \`\`\`
3159
3414
 
3415
+ ## Background Recording for Transcription
3416
+
3417
+ \`\`\`tsx
3418
+ import React, { useEffect } from 'react';
3419
+ import { View, Button, Text } from '@idealyst/components';
3420
+ import { useBackgroundRecorder, AUDIO_PROFILES } from '@idealyst/audio';
3421
+ import type { PCMData, BackgroundLifecycleInfo } from '@idealyst/audio';
3422
+
3423
+ function BackgroundTranscriber() {
3424
+ const recorder = useBackgroundRecorder({
3425
+ config: AUDIO_PROFILES.speech,
3426
+ maxBackgroundDuration: 5 * 60 * 1000, // 5 min max in background
3427
+ onLifecycleEvent: (info: BackgroundLifecycleInfo) => {
3428
+ switch (info.event) {
3429
+ case 'backgrounded':
3430
+ console.log('Recording continues in background');
3431
+ break;
3432
+ case 'foregrounded':
3433
+ console.log(\`Back from background after \${info.backgroundDuration}ms\`);
3434
+ break;
3435
+ case 'interrupted':
3436
+ console.log('Recording interrupted (phone call?)');
3437
+ break;
3438
+ case 'interruptionEnded':
3439
+ if (info.shouldResume) {
3440
+ recorder.resume(); // Consumer decides whether to resume
3441
+ }
3442
+ break;
3443
+ case 'maxDurationReached':
3444
+ console.log('Max background duration reached');
3445
+ break;
3446
+ }
3447
+ },
3448
+ });
3449
+
3450
+ // Stream PCM chunks to your speech-to-text service
3451
+ useEffect(() => {
3452
+ const unsub = recorder.subscribeToData((pcm: PCMData) => {
3453
+ // Send to STT API (e.g., Whisper, Deepgram)
3454
+ sendToTranscriptionService(pcm.toBase64());
3455
+ });
3456
+ return unsub;
3457
+ }, [recorder.subscribeToData]);
3458
+
3459
+ const handleToggle = async () => {
3460
+ if (recorder.isRecording) {
3461
+ await recorder.stop();
3462
+ } else {
3463
+ await recorder.start();
3464
+ }
3465
+ };
3466
+
3467
+ return (
3468
+ <View padding="md" gap="md">
3469
+ <Button
3470
+ onPress={handleToggle}
3471
+ intent={recorder.isRecording ? 'error' : 'primary'}
3472
+ >
3473
+ {recorder.isRecording ? 'Stop' : 'Record'}
3474
+ </Button>
3475
+ <Text>Duration: {Math.round(recorder.duration / 1000)}s</Text>
3476
+ {recorder.isInBackground && <Text>Recording in background...</Text>}
3477
+ {recorder.wasInterrupted && <Text>Recording was interrupted</Text>}
3478
+ <Text>Background time: {Math.round(recorder.backgroundDuration / 1000)}s</Text>
3479
+ </View>
3480
+ );
3481
+ }
3482
+ \`\`\`
3483
+
3484
+ > **Important:** Background recording requires native entitlements. See the \`useBackgroundRecorder\` API docs for iOS, Android, and Expo setup instructions.
3485
+
3160
3486
  ## Audio Level Visualization
3161
3487
 
3162
3488
  \`\`\`tsx
@@ -6650,146 +6976,1662 @@ function SalesOverview() {
6650
6976
  }
6651
6977
  \`\`\`
6652
6978
 
6653
- ## Multi-Series Line Chart
6979
+ ## Multi-Series Line Chart
6980
+
6981
+ \`\`\`tsx
6982
+ import React from 'react';
6983
+ import { LineChart } from '@idealyst/charts';
6984
+ import type { ChartDataSeries } from '@idealyst/charts';
6985
+
6986
+ // Each series has: id, name, data, color? \u2014 NO 'label' property
6987
+ const series: ChartDataSeries[] = [
6988
+ {
6989
+ id: 'product-a',
6990
+ name: 'Product A', // Use 'name' \u2014 NOT 'label'
6991
+ data: [
6992
+ { x: 'Q1', y: 120 },
6993
+ { x: 'Q2', y: 150 },
6994
+ { x: 'Q3', y: 180 },
6995
+ { x: 'Q4', y: 210 },
6996
+ ],
6997
+ color: '#2196F3',
6998
+ },
6999
+ {
7000
+ id: 'product-b',
7001
+ name: 'Product B', // Use 'name' \u2014 NOT 'label'
7002
+ data: [
7003
+ { x: 'Q1', y: 80 },
7004
+ { x: 'Q2', y: 110 },
7005
+ { x: 'Q3', y: 95 },
7006
+ { x: 'Q4', y: 140 },
7007
+ ],
7008
+ color: '#FF9800',
7009
+ },
7010
+ ];
7011
+
7012
+ function ComparisonChart() {
7013
+ return (
7014
+ <LineChart
7015
+ data={series}
7016
+ height={350}
7017
+ curve="monotone"
7018
+ showDots
7019
+ animate
7020
+ />
7021
+ );
7022
+ }
7023
+ \`\`\`
7024
+
7025
+ ## Bar Chart
7026
+
7027
+ \`\`\`tsx
7028
+ import React from 'react';
7029
+ import { View, Text } from '@idealyst/components';
7030
+ import { BarChart } from '@idealyst/charts';
7031
+
7032
+ const categories = [
7033
+ { x: 'Electronics', y: 45 },
7034
+ { x: 'Clothing', y: 32 },
7035
+ { x: 'Books', y: 18 },
7036
+ { x: 'Food', y: 56 },
7037
+ { x: 'Sports', y: 28 },
7038
+ ];
7039
+
7040
+ function CategoryBreakdown() {
7041
+ return (
7042
+ <View padding="md" gap="md">
7043
+ <Text typography="h6" weight="bold">Sales by Category</Text>
7044
+ <BarChart
7045
+ data={[{ id: 'units', name: 'Units Sold', data: categories }]}
7046
+ height={300}
7047
+ barRadius={4}
7048
+ animate
7049
+ yAxis={{ tickFormat: (value: number | string | Date) => \`\${value} units\` }}
7050
+ />
7051
+ </View>
7052
+ );
7053
+ }
7054
+ \`\`\`
7055
+
7056
+ ## Stacked Bar Chart
7057
+
7058
+ \`\`\`tsx
7059
+ import React from 'react';
7060
+ import { BarChart } from '@idealyst/charts';
7061
+
7062
+ function StackedBarExample() {
7063
+ return (
7064
+ <BarChart
7065
+ data={[
7066
+ {
7067
+ id: 'online',
7068
+ name: 'Online',
7069
+ data: [
7070
+ { x: 'Q1', y: 100 },
7071
+ { x: 'Q2', y: 120 },
7072
+ { x: 'Q3', y: 90 },
7073
+ ],
7074
+ color: '#4CAF50',
7075
+ },
7076
+ {
7077
+ id: 'in-store',
7078
+ name: 'In-Store',
7079
+ data: [
7080
+ { x: 'Q1', y: 60 },
7081
+ { x: 'Q2', y: 80 },
7082
+ { x: 'Q3', y: 70 },
7083
+ ],
7084
+ color: '#2196F3',
7085
+ },
7086
+ ]}
7087
+ height={300}
7088
+ stacked
7089
+ animate
7090
+ />
7091
+ );
7092
+ }
7093
+ \`\`\`
7094
+
7095
+ ## Horizontal Bar Chart
7096
+
7097
+ \`\`\`tsx
7098
+ import React from 'react';
7099
+ import { BarChart } from '@idealyst/charts';
7100
+
7101
+ function HorizontalBarExample() {
7102
+ const data = [
7103
+ { x: 'React', y: 85 },
7104
+ { x: 'Vue', y: 62 },
7105
+ { x: 'Angular', y: 45 },
7106
+ { x: 'Svelte', y: 38 },
7107
+ ];
7108
+
7109
+ return (
7110
+ <BarChart
7111
+ data={[{ id: 'popularity', name: 'Popularity', data }]}
7112
+ height={250}
7113
+ orientation="horizontal"
7114
+ animate
7115
+ />
7116
+ );
7117
+ }
7118
+ \`\`\`
7119
+ `
7120
+ };
7121
+
7122
+ // src/data/clipboard-guides.ts
7123
+ var clipboardGuides = {
7124
+ "idealyst://clipboard/overview": `# @idealyst/clipboard Overview
7125
+
7126
+ 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.
7127
+
7128
+ ## Features
7129
+
7130
+ - **Cross-Platform Clipboard** - Copy and paste text on React Native and Web
7131
+ - **Simple API** - Async/await based with consistent interface
7132
+ - **React Native** - Uses @react-native-clipboard/clipboard
7133
+ - **Web** - Uses navigator.clipboard API
7134
+ - **OTP Auto-Fill (Android)** - Automatically reads OTP codes from SMS via SMS Retriever API (no permissions needed)
7135
+ - **OTP Auto-Fill (iOS)** - Provides TextInput props for native iOS keyboard OTP suggestion
7136
+ - **TypeScript** - Full type safety and IntelliSense support
7137
+
7138
+ ## Installation
7139
+
7140
+ \`\`\`bash
7141
+ yarn add @idealyst/clipboard
7142
+
7143
+ # React Native also needs:
7144
+ yarn add @react-native-clipboard/clipboard
7145
+ cd ios && pod install
7146
+
7147
+ # For OTP autofill on Android (optional):
7148
+ yarn add react-native-otp-verify
7149
+ \`\`\`
7150
+
7151
+ ## Quick Start
7152
+
7153
+ \`\`\`tsx
7154
+ import { clipboard } from '@idealyst/clipboard';
7155
+
7156
+ // Copy text
7157
+ await clipboard.copy('Hello, world!');
7158
+
7159
+ // Paste text
7160
+ const text = await clipboard.paste();
7161
+
7162
+ // Check if clipboard has text
7163
+ const hasText = await clipboard.hasText();
7164
+ \`\`\`
7165
+
7166
+ ## OTP Auto-Fill Quick Start
7167
+
7168
+ \`\`\`tsx
7169
+ import { useOTPAutoFill, OTP_INPUT_PROPS } from '@idealyst/clipboard';
7170
+ import { TextInput } from 'react-native';
7171
+
7172
+ function OTPScreen() {
7173
+ const { code, startListening, hash } = useOTPAutoFill({
7174
+ codeLength: 6,
7175
+ onCodeReceived: (otp) => verifyOTP(otp),
7176
+ });
7177
+
7178
+ useEffect(() => {
7179
+ startListening();
7180
+ }, []);
7181
+
7182
+ return (
7183
+ <TextInput
7184
+ value={code ?? ''}
7185
+ {...OTP_INPUT_PROPS}
7186
+ />
7187
+ );
7188
+ }
7189
+ \`\`\`
7190
+
7191
+ ## Import Options
7192
+
7193
+ \`\`\`tsx
7194
+ // Named import (recommended)
7195
+ import { clipboard } from '@idealyst/clipboard';
7196
+
7197
+ // Default import
7198
+ import clipboard from '@idealyst/clipboard';
7199
+
7200
+ // OTP hook and helpers
7201
+ import { useOTPAutoFill, OTP_INPUT_PROPS } from '@idealyst/clipboard';
7202
+ \`\`\`
7203
+
7204
+ ## Platform Details
7205
+
7206
+ - **React Native**: Uses \`@react-native-clipboard/clipboard\` for clipboard operations
7207
+ - **Web**: Uses \`navigator.clipboard\` API (requires secure context / HTTPS)
7208
+ - **OTP (Android)**: Uses SMS Retriever API via \`react-native-otp-verify\` \u2014 zero permissions
7209
+ - **OTP (iOS)**: Native keyboard autofill via \`textContentType="oneTimeCode"\`
7210
+ - **OTP (Web)**: No-op \u2014 returns null values and noop functions
7211
+ `,
7212
+ "idealyst://clipboard/api": `# Clipboard API Reference
7213
+
7214
+ Complete API reference for @idealyst/clipboard.
7215
+
7216
+ ## clipboard.copy
7217
+
7218
+ Copy text to the system clipboard.
7219
+
7220
+ \`\`\`tsx
7221
+ await clipboard.copy(text: string): Promise<void>
7222
+
7223
+ // Examples
7224
+ await clipboard.copy('Hello, world!');
7225
+ await clipboard.copy(inviteCode);
7226
+ await clipboard.copy(JSON.stringify(data));
7227
+ \`\`\`
7228
+
7229
+ ## clipboard.paste
7230
+
7231
+ Read text from the system clipboard.
7232
+
7233
+ \`\`\`tsx
7234
+ await clipboard.paste(): Promise<string>
7235
+
7236
+ // Examples
7237
+ const text = await clipboard.paste();
7238
+ const url = await clipboard.paste();
7239
+ \`\`\`
7240
+
7241
+ ## clipboard.hasText
7242
+
7243
+ Check if the clipboard contains text content.
7244
+
7245
+ \`\`\`tsx
7246
+ await clipboard.hasText(): Promise<boolean>
7247
+
7248
+ // Example
7249
+ const canPaste = await clipboard.hasText();
7250
+ if (canPaste) {
7251
+ const text = await clipboard.paste();
7252
+ }
7253
+ \`\`\`
7254
+
7255
+ ## clipboard.addListener
7256
+
7257
+ Listen for copy events (triggered when \`clipboard.copy()\` is called).
7258
+
7259
+ \`\`\`tsx
7260
+ const unsubscribe = clipboard.addListener((content: string) => {
7261
+ console.log('Copied:', content);
7262
+ });
7263
+
7264
+ // Later, unsubscribe
7265
+ unsubscribe();
7266
+ \`\`\`
7267
+
7268
+ ---
7269
+
7270
+ ## useOTPAutoFill
7271
+
7272
+ React hook for automatic OTP code detection from SMS on mobile.
7273
+
7274
+ **Android**: Uses SMS Retriever API to auto-read OTP from incoming SMS (no permissions required). SMS must include your app hash.
7275
+
7276
+ **iOS**: OTP is handled natively by the iOS keyboard. Use \`OTP_INPUT_PROPS\` on your TextInput to enable it.
7277
+
7278
+ **Web**: Returns no-op values.
7279
+
7280
+ \`\`\`tsx
7281
+ const {
7282
+ code, // string | null - received OTP code (Android only)
7283
+ startListening, // () => void - begin listening for SMS (Android only)
7284
+ stopListening, // () => void - stop listening (Android only)
7285
+ hash, // string | null - app hash for SMS body (Android only)
7286
+ } = useOTPAutoFill(options?: {
7287
+ codeLength?: number; // default: 6
7288
+ onCodeReceived?: (code: string) => void;
7289
+ });
7290
+ \`\`\`
7291
+
7292
+ ### Options
7293
+
7294
+ | Option | Type | Default | Description |
7295
+ |--------|------|---------|-------------|
7296
+ | codeLength | number | 6 | Expected digit count of OTP code |
7297
+ | onCodeReceived | (code: string) => void | \u2014 | Callback when OTP is detected |
7298
+
7299
+ ### Return Value
7300
+
7301
+ | Property | Type | Description |
7302
+ |----------|------|-------------|
7303
+ | code | string \\| null | Detected OTP code (Android). Null on iOS/web. |
7304
+ | startListening | () => void | Start SMS listener (Android). No-op on iOS/web. |
7305
+ | stopListening | () => void | Stop SMS listener (Android). No-op on iOS/web. |
7306
+ | hash | string \\| null | App hash for SMS Retriever (Android). Null on iOS/web. |
7307
+
7308
+ ### Android SMS Format
7309
+
7310
+ For the SMS Retriever API to detect the message, the SMS must:
7311
+ 1. Start with \`<#>\`
7312
+ 2. Contain the OTP code as a sequence of digits
7313
+ 3. End with your app's 11-character hash (available via \`hash\`)
7314
+
7315
+ Example SMS:
7316
+ \`\`\`
7317
+ <#> Your verification code is: 123456
7318
+ FA+9qCX9VSu
7319
+ \`\`\`
7320
+
7321
+ ---
7322
+
7323
+ ## OTP_INPUT_PROPS
7324
+
7325
+ Constant with TextInput props to enable native OTP keyboard autofill.
7326
+
7327
+ \`\`\`tsx
7328
+ import { OTP_INPUT_PROPS } from '@idealyst/clipboard';
7329
+
7330
+ // Value:
7331
+ // {
7332
+ // textContentType: 'oneTimeCode',
7333
+ // autoComplete: 'sms-otp',
7334
+ // }
7335
+
7336
+ <TextInput
7337
+ {...OTP_INPUT_PROPS}
7338
+ value={code}
7339
+ onChangeText={setCode}
7340
+ />
7341
+ \`\`\`
7342
+
7343
+ - **iOS**: Enables the keyboard to suggest OTP codes from received SMS (iOS 12+)
7344
+ - **Android**: Maps to the correct \`autoComplete\` value
7345
+ - **Web**: Harmless \u2014 ignored by web TextInput
7346
+ `,
7347
+ "idealyst://clipboard/examples": `# Clipboard Examples
7348
+
7349
+ Complete code examples for common @idealyst/clipboard patterns.
7350
+
7351
+ ## Copy to Clipboard with Feedback
7352
+
7353
+ \`\`\`tsx
7354
+ import { clipboard } from '@idealyst/clipboard';
7355
+ import { useState, useCallback } from 'react';
7356
+
7357
+ function CopyButton({ text }: { text: string }) {
7358
+ const [copied, setCopied] = useState(false);
7359
+
7360
+ const handleCopy = useCallback(async () => {
7361
+ await clipboard.copy(text);
7362
+ setCopied(true);
7363
+ setTimeout(() => setCopied(false), 2000);
7364
+ }, [text]);
7365
+
7366
+ return (
7367
+ <Button
7368
+ label={copied ? 'Copied!' : 'Copy'}
7369
+ intent={copied ? 'positive' : 'neutral'}
7370
+ onPress={handleCopy}
7371
+ />
7372
+ );
7373
+ }
7374
+ \`\`\`
7375
+
7376
+ ## Share / Copy Link
7377
+
7378
+ \`\`\`tsx
7379
+ import { clipboard } from '@idealyst/clipboard';
7380
+
7381
+ async function copyShareLink(itemId: string) {
7382
+ const url = \`https://myapp.com/items/\${itemId}\`;
7383
+ await clipboard.copy(url);
7384
+ }
7385
+ \`\`\`
7386
+
7387
+ ## Paste from Clipboard
7388
+
7389
+ \`\`\`tsx
7390
+ import { clipboard } from '@idealyst/clipboard';
7391
+
7392
+ function PasteInput() {
7393
+ const [value, setValue] = useState('');
7394
+
7395
+ const handlePaste = useCallback(async () => {
7396
+ const hasText = await clipboard.hasText();
7397
+ if (hasText) {
7398
+ const text = await clipboard.paste();
7399
+ setValue(text);
7400
+ }
7401
+ }, []);
7402
+
7403
+ return (
7404
+ <View>
7405
+ <Input value={value} onChangeText={setValue} placeholder="Enter or paste text" />
7406
+ <Button label="Paste" onPress={handlePaste} iconName="content-paste" />
7407
+ </View>
7408
+ );
7409
+ }
7410
+ \`\`\`
7411
+
7412
+ ## useClipboard Hook
7413
+
7414
+ \`\`\`tsx
7415
+ import { clipboard } from '@idealyst/clipboard';
7416
+ import { useState, useCallback } from 'react';
7417
+
7418
+ export function useClipboard() {
7419
+ const [copiedText, setCopiedText] = useState<string | null>(null);
7420
+
7421
+ const copy = useCallback(async (text: string) => {
7422
+ await clipboard.copy(text);
7423
+ setCopiedText(text);
7424
+ }, []);
7425
+
7426
+ const paste = useCallback(async () => {
7427
+ return clipboard.paste();
7428
+ }, []);
7429
+
7430
+ const reset = useCallback(() => {
7431
+ setCopiedText(null);
7432
+ }, []);
7433
+
7434
+ return { copy, paste, copiedText, reset };
7435
+ }
7436
+ \`\`\`
7437
+
7438
+ ## OTP Verification Screen
7439
+
7440
+ \`\`\`tsx
7441
+ import { useOTPAutoFill, OTP_INPUT_PROPS } from '@idealyst/clipboard';
7442
+ import { useEffect, useState } from 'react';
7443
+ import { TextInput, Platform } from 'react-native';
7444
+
7445
+ function OTPVerificationScreen({ phoneNumber, onVerify }: {
7446
+ phoneNumber: string;
7447
+ onVerify: (code: string) => void;
7448
+ }) {
7449
+ const [code, setCode] = useState('');
7450
+
7451
+ const otp = useOTPAutoFill({
7452
+ codeLength: 6,
7453
+ onCodeReceived: (receivedCode) => {
7454
+ setCode(receivedCode);
7455
+ onVerify(receivedCode);
7456
+ },
7457
+ });
7458
+
7459
+ // Start listening when screen mounts
7460
+ useEffect(() => {
7461
+ otp.startListening();
7462
+ return () => otp.stopListening();
7463
+ }, []);
7464
+
7465
+ // Auto-fill from hook on Android
7466
+ useEffect(() => {
7467
+ if (otp.code) {
7468
+ setCode(otp.code);
7469
+ }
7470
+ }, [otp.code]);
7471
+
7472
+ return (
7473
+ <View>
7474
+ <Text>Enter the code sent to {phoneNumber}</Text>
7475
+
7476
+ <TextInput
7477
+ value={code}
7478
+ onChangeText={(text) => {
7479
+ setCode(text);
7480
+ if (text.length === 6) onVerify(text);
7481
+ }}
7482
+ keyboardType="number-pad"
7483
+ maxLength={6}
7484
+ {...OTP_INPUT_PROPS}
7485
+ />
7486
+
7487
+ {Platform.OS === 'android' && otp.hash && (
7488
+ <Text style={{ fontSize: 10, color: '#999' }}>
7489
+ App hash (for SMS setup): {otp.hash}
7490
+ </Text>
7491
+ )}
7492
+ </View>
7493
+ );
7494
+ }
7495
+ \`\`\`
7496
+
7497
+ ## Copy Invite Code
7498
+
7499
+ \`\`\`tsx
7500
+ import { clipboard } from '@idealyst/clipboard';
7501
+
7502
+ function InviteCard({ code }: { code: string }) {
7503
+ const [copied, setCopied] = useState(false);
7504
+
7505
+ return (
7506
+ <Card>
7507
+ <Text>Your invite code</Text>
7508
+ <Text variant="headline">{code}</Text>
7509
+ <Button
7510
+ label={copied ? 'Copied!' : 'Copy Code'}
7511
+ iconName={copied ? 'check' : 'content-copy'}
7512
+ onPress={async () => {
7513
+ await clipboard.copy(code);
7514
+ setCopied(true);
7515
+ setTimeout(() => setCopied(false), 2000);
7516
+ }}
7517
+ />
7518
+ </Card>
7519
+ );
7520
+ }
7521
+ \`\`\`
7522
+
7523
+ ## Error Handling
7524
+
7525
+ \`\`\`tsx
7526
+ import { clipboard } from '@idealyst/clipboard';
7527
+
7528
+ async function safeCopy(text: string): Promise<boolean> {
7529
+ try {
7530
+ await clipboard.copy(text);
7531
+ return true;
7532
+ } catch (error) {
7533
+ console.error('Clipboard copy failed:', error);
7534
+ // On web, this can fail if not in a secure context or without user gesture
7535
+ return false;
7536
+ }
7537
+ }
7538
+
7539
+ async function safePaste(): Promise<string | null> {
7540
+ try {
7541
+ const hasText = await clipboard.hasText();
7542
+ if (!hasText) return null;
7543
+ return await clipboard.paste();
7544
+ } catch (error) {
7545
+ console.error('Clipboard paste failed:', error);
7546
+ // Browser may deny permission
7547
+ return null;
7548
+ }
7549
+ }
7550
+ \`\`\`
7551
+
7552
+ ## Best Practices
7553
+
7554
+ 1. **Always use try-catch** \u2014 Clipboard operations can fail (permissions, secure context)
7555
+ 2. **Provide visual feedback** \u2014 Show a "Copied!" confirmation after copying
7556
+ 3. **Use OTP_INPUT_PROPS** \u2014 Always spread these on OTP text inputs for cross-platform autofill
7557
+ 4. **Start OTP listener on mount** \u2014 Call \`startListening()\` in useEffect, clean up with \`stopListening()\`
7558
+ 5. **Log the Android hash** \u2014 During development, log \`hash\` to configure your SMS gateway
7559
+ 6. **Graceful degradation** \u2014 OTP features degrade gracefully if native modules aren't installed
7560
+ 7. **Secure context (web)** \u2014 Clipboard API requires HTTPS on web
7561
+ `
7562
+ };
7563
+
7564
+ // src/data/biometrics-guides.ts
7565
+ var biometricsGuides = {
7566
+ "idealyst://biometrics/overview": `# @idealyst/biometrics Overview
7567
+
7568
+ Cross-platform biometric authentication and passkeys (WebAuthn/FIDO2) for React and React Native applications.
7569
+
7570
+ ## Features
7571
+
7572
+ - **Local Biometric Auth** \u2014 FaceID, TouchID, fingerprint, iris to gate access
7573
+ - **Passkeys (WebAuthn/FIDO2)** \u2014 Passwordless login with cryptographic credentials
7574
+ - **Cross-Platform** \u2014 Single API for React Native (iOS/Android) and Web
7575
+ - **React Native** \u2014 Uses expo-local-authentication for biometrics, react-native-passkeys for passkeys
7576
+ - **Web** \u2014 Uses WebAuthn API for both biometrics and passkeys
7577
+ - **Graceful Degradation** \u2014 Falls back cleanly when native modules aren't installed
7578
+ - **TypeScript** \u2014 Full type safety and IntelliSense support
7579
+
7580
+ ## Installation
7581
+
7582
+ \`\`\`bash
7583
+ yarn add @idealyst/biometrics
7584
+
7585
+ # React Native \u2014 biometric auth:
7586
+ yarn add expo-local-authentication
7587
+ cd ios && pod install
7588
+
7589
+ # React Native \u2014 passkeys (optional):
7590
+ yarn add react-native-passkeys
7591
+ cd ios && pod install
7592
+ \`\`\`
7593
+
7594
+ ## Quick Start \u2014 Biometric Auth
7595
+
7596
+ \`\`\`tsx
7597
+ import { isBiometricAvailable, authenticate } from '@idealyst/biometrics';
7598
+
7599
+ // Check availability
7600
+ const available = await isBiometricAvailable();
7601
+
7602
+ // Prompt user
7603
+ if (available) {
7604
+ const result = await authenticate({
7605
+ promptMessage: 'Verify your identity',
7606
+ });
7607
+
7608
+ if (result.success) {
7609
+ // Authenticated!
7610
+ } else {
7611
+ console.log(result.error, result.message);
7612
+ }
7613
+ }
7614
+ \`\`\`
7615
+
7616
+ ## Quick Start \u2014 Passkeys
7617
+
7618
+ \`\`\`tsx
7619
+ import { isPasskeySupported, createPasskey, getPasskey } from '@idealyst/biometrics';
7620
+
7621
+ // Check support
7622
+ const supported = await isPasskeySupported();
7623
+
7624
+ // Register a new passkey
7625
+ const credential = await createPasskey({
7626
+ challenge: serverChallenge,
7627
+ rp: { id: 'example.com', name: 'My App' },
7628
+ user: { id: userId, name: email, displayName: name },
7629
+ });
7630
+ // Send credential to server for verification
7631
+
7632
+ // Sign in with passkey
7633
+ const assertion = await getPasskey({
7634
+ challenge: serverChallenge,
7635
+ rpId: 'example.com',
7636
+ });
7637
+ // Send assertion to server for verification
7638
+ \`\`\`
7639
+
7640
+ ## Platform Details
7641
+
7642
+ - **React Native (biometrics)**: Uses \`expo-local-authentication\` \u2014 FaceID, TouchID, fingerprint, iris
7643
+ - **React Native (passkeys)**: Uses \`react-native-passkeys\` \u2014 system passkey UI on iOS 16+ and Android 9+
7644
+ - **Web (biometrics)**: Uses WebAuthn with \`userVerification: 'required'\` to trigger platform authenticator
7645
+ - **Web (passkeys)**: Uses \`navigator.credentials.create/get\` with the PublicKey API
7646
+ `,
7647
+ "idealyst://biometrics/api": `# Biometrics API Reference
7648
+
7649
+ Complete API reference for @idealyst/biometrics.
7650
+
7651
+ ---
7652
+
7653
+ ## Biometric Authentication Functions
7654
+
7655
+ ### isBiometricAvailable
7656
+
7657
+ Check whether biometric auth hardware is available and enrolled.
7658
+
7659
+ \`\`\`tsx
7660
+ await isBiometricAvailable(): Promise<boolean>
7661
+
7662
+ // Native: checks hardware + enrollment via expo-local-authentication
7663
+ // Web: checks PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
7664
+ \`\`\`
7665
+
7666
+ ### getBiometricTypes
7667
+
7668
+ Return the biometric types available on this device.
7669
+
7670
+ \`\`\`tsx
7671
+ await getBiometricTypes(): Promise<BiometricType[]>
7672
+
7673
+ type BiometricType = 'fingerprint' | 'facial_recognition' | 'iris';
7674
+
7675
+ // Native: returns specific types (fingerprint, facial_recognition, iris)
7676
+ // Web: returns ['fingerprint'] as generic indicator, or [] if unavailable
7677
+ \`\`\`
7678
+
7679
+ ### getSecurityLevel
7680
+
7681
+ Get the security level of biometric authentication on the device.
7682
+
7683
+ \`\`\`tsx
7684
+ await getSecurityLevel(): Promise<SecurityLevel>
7685
+
7686
+ type SecurityLevel = 'none' | 'device_credential' | 'biometric_weak' | 'biometric_strong';
7687
+
7688
+ // Native: maps expo-local-authentication SecurityLevel enum
7689
+ // Web: returns 'biometric_strong' if platform authenticator available, else 'none'
7690
+ \`\`\`
7691
+
7692
+ ### authenticate
7693
+
7694
+ Prompt the user for biometric authentication.
7695
+
7696
+ \`\`\`tsx
7697
+ await authenticate(options?: AuthenticateOptions): Promise<AuthResult>
7698
+ \`\`\`
7699
+
7700
+ **AuthenticateOptions:**
7701
+
7702
+ | Option | Type | Default | Description |
7703
+ |--------|------|---------|-------------|
7704
+ | promptMessage | string | 'Authenticate' | Message shown alongside the biometric prompt |
7705
+ | cancelLabel | string | \u2014 | Label for the cancel button |
7706
+ | fallbackLabel | string | \u2014 | iOS: label for passcode fallback button |
7707
+ | disableDeviceFallback | boolean | false | Prevent PIN/passcode fallback after biometric failure |
7708
+ | requireStrongBiometric | boolean | false | Android: require Class 3 (strong) biometric |
7709
+
7710
+ **AuthResult:**
7711
+
7712
+ \`\`\`tsx
7713
+ type AuthResult =
7714
+ | { success: true }
7715
+ | { success: false; error: AuthError; message?: string };
7716
+
7717
+ type AuthError =
7718
+ | 'not_available'
7719
+ | 'not_enrolled'
7720
+ | 'user_cancel'
7721
+ | 'lockout'
7722
+ | 'system_cancel'
7723
+ | 'passcode_not_set'
7724
+ | 'authentication_failed'
7725
+ | 'unknown';
7726
+ \`\`\`
7727
+
7728
+ ### cancelAuthentication
7729
+
7730
+ Cancel an in-progress authentication prompt (Android only). No-op on iOS and web.
7731
+
7732
+ \`\`\`tsx
7733
+ await cancelAuthentication(): Promise<void>
7734
+ \`\`\`
7735
+
7736
+ ---
7737
+
7738
+ ## Passkey Functions
7739
+
7740
+ ### isPasskeySupported
7741
+
7742
+ Check if passkeys (WebAuthn/FIDO2) are supported on this device/browser.
7743
+
7744
+ \`\`\`tsx
7745
+ await isPasskeySupported(): Promise<boolean>
7746
+
7747
+ // Web: checks PublicKeyCredential + isUserVerifyingPlatformAuthenticatorAvailable
7748
+ // Native: checks react-native-passkeys Passkey.isSupported()
7749
+ \`\`\`
7750
+
7751
+ ### createPasskey
7752
+
7753
+ Create a new passkey credential (registration / attestation ceremony).
7754
+
7755
+ \`\`\`tsx
7756
+ await createPasskey(options: PasskeyCreateOptions): Promise<PasskeyCreateResult>
7757
+ \`\`\`
7758
+
7759
+ **PasskeyCreateOptions:**
7760
+
7761
+ | Option | Type | Required | Description |
7762
+ |--------|------|----------|-------------|
7763
+ | challenge | string | Yes | Base64url-encoded challenge from server |
7764
+ | rp | { id: string; name: string } | Yes | Relying party info |
7765
+ | user | { id: string; name: string; displayName: string } | Yes | User info (id is base64url) |
7766
+ | pubKeyCredParams | PublicKeyCredentialParam[] | No | Defaults to ES256 + RS256 |
7767
+ | timeout | number | No | Timeout in ms (default 60000) |
7768
+ | authenticatorSelection | object | No | Authenticator criteria |
7769
+ | excludeCredentials | CredentialDescriptor[] | No | Prevent re-registration |
7770
+ | attestation | string | No | 'none' \\| 'indirect' \\| 'direct' \\| 'enterprise' |
7771
+
7772
+ **PasskeyCreateResult:**
7773
+
7774
+ | Property | Type | Description |
7775
+ |----------|------|-------------|
7776
+ | id | string | Credential ID (base64url) |
7777
+ | rawId | string | Raw credential ID (base64url) |
7778
+ | type | 'public-key' | Always 'public-key' |
7779
+ | response.clientDataJSON | string | Client data (base64url) |
7780
+ | response.attestationObject | string | Attestation object (base64url) |
7781
+
7782
+ ### getPasskey
7783
+
7784
+ Authenticate with an existing passkey (assertion ceremony).
7785
+
7786
+ \`\`\`tsx
7787
+ await getPasskey(options: PasskeyGetOptions): Promise<PasskeyGetResult>
7788
+ \`\`\`
7789
+
7790
+ **PasskeyGetOptions:**
7791
+
7792
+ | Option | Type | Required | Description |
7793
+ |--------|------|----------|-------------|
7794
+ | challenge | string | Yes | Base64url-encoded challenge from server |
7795
+ | rpId | string | No | Relying party ID |
7796
+ | allowCredentials | CredentialDescriptor[] | No | Allowed credentials (empty = discoverable) |
7797
+ | timeout | number | No | Timeout in ms (default 60000) |
7798
+ | userVerification | string | No | 'required' \\| 'preferred' \\| 'discouraged' |
7799
+
7800
+ **PasskeyGetResult:**
7801
+
7802
+ | Property | Type | Description |
7803
+ |----------|------|-------------|
7804
+ | id | string | Credential ID (base64url) |
7805
+ | rawId | string | Raw credential ID (base64url) |
7806
+ | type | 'public-key' | Always 'public-key' |
7807
+ | response.clientDataJSON | string | Client data (base64url) |
7808
+ | response.authenticatorData | string | Authenticator data (base64url) |
7809
+ | response.signature | string | Signature (base64url) |
7810
+ | response.userHandle | string \\| undefined | User handle (base64url) |
7811
+
7812
+ ### PasskeyError
7813
+
7814
+ Both \`createPasskey\` and \`getPasskey\` throw a \`PasskeyError\` on failure:
7815
+
7816
+ \`\`\`tsx
7817
+ interface PasskeyError {
7818
+ code: 'not_supported' | 'cancelled' | 'invalid_state' | 'not_allowed' | 'unknown';
7819
+ message: string;
7820
+ }
7821
+
7822
+ try {
7823
+ const result = await createPasskey(options);
7824
+ } catch (err) {
7825
+ const passkeyErr = err as PasskeyError;
7826
+ console.log(passkeyErr.code, passkeyErr.message);
7827
+ }
7828
+ \`\`\`
7829
+
7830
+ ---
7831
+
7832
+ ## Base64url Helpers
7833
+
7834
+ Shared utilities for encoding/decoding WebAuthn binary data.
7835
+
7836
+ \`\`\`tsx
7837
+ import { base64urlToBuffer, bufferToBase64url } from '@idealyst/biometrics';
7838
+
7839
+ // Convert base64url string to ArrayBuffer
7840
+ const buffer: ArrayBuffer = base64urlToBuffer(base64urlString);
7841
+
7842
+ // Convert ArrayBuffer to base64url string
7843
+ const str: string = bufferToBase64url(arrayBuffer);
7844
+ \`\`\`
7845
+ `,
7846
+ "idealyst://biometrics/examples": `# Biometrics Examples
7847
+
7848
+ Complete code examples for common @idealyst/biometrics patterns.
7849
+
7850
+ ## Gate Screen Access with Biometrics
7851
+
7852
+ \`\`\`tsx
7853
+ import { isBiometricAvailable, authenticate } from '@idealyst/biometrics';
7854
+ import { useEffect, useState } from 'react';
7855
+
7856
+ function ProtectedScreen({ children }: { children: React.ReactNode }) {
7857
+ const [unlocked, setUnlocked] = useState(false);
7858
+ const [error, setError] = useState<string | null>(null);
7859
+
7860
+ const unlock = async () => {
7861
+ const available = await isBiometricAvailable();
7862
+ if (!available) {
7863
+ // No biometrics \u2014 fall back to PIN or allow access
7864
+ setUnlocked(true);
7865
+ return;
7866
+ }
7867
+
7868
+ const result = await authenticate({
7869
+ promptMessage: 'Unlock to continue',
7870
+ cancelLabel: 'Cancel',
7871
+ });
7872
+
7873
+ if (result.success) {
7874
+ setUnlocked(true);
7875
+ } else {
7876
+ setError(result.message ?? 'Authentication failed');
7877
+ }
7878
+ };
7879
+
7880
+ useEffect(() => {
7881
+ unlock();
7882
+ }, []);
7883
+
7884
+ if (!unlocked) {
7885
+ return (
7886
+ <View>
7887
+ <Text>Please authenticate to continue</Text>
7888
+ {error && <Text intent="negative">{error}</Text>}
7889
+ <Button label="Try Again" onPress={unlock} />
7890
+ </View>
7891
+ );
7892
+ }
7893
+
7894
+ return <>{children}</>;
7895
+ }
7896
+ \`\`\`
7897
+
7898
+ ## Confirm Sensitive Action
7899
+
7900
+ \`\`\`tsx
7901
+ import { authenticate } from '@idealyst/biometrics';
7902
+
7903
+ async function confirmTransfer(amount: number, recipient: string) {
7904
+ const result = await authenticate({
7905
+ promptMessage: \`Confirm transfer of $\${amount} to \${recipient}\`,
7906
+ disableDeviceFallback: false,
7907
+ });
7908
+
7909
+ if (!result.success) {
7910
+ throw new Error(result.message ?? 'Authentication required');
7911
+ }
7912
+
7913
+ // Proceed with transfer
7914
+ await api.transfer({ amount, recipient });
7915
+ }
7916
+ \`\`\`
7917
+
7918
+ ## Show Biometric Info
7919
+
7920
+ \`\`\`tsx
7921
+ import {
7922
+ isBiometricAvailable,
7923
+ getBiometricTypes,
7924
+ getSecurityLevel,
7925
+ } from '@idealyst/biometrics';
7926
+
7927
+ function BiometricSettings() {
7928
+ const [info, setInfo] = useState({
7929
+ available: false,
7930
+ types: [] as string[],
7931
+ level: 'none',
7932
+ });
7933
+
7934
+ useEffect(() => {
7935
+ async function load() {
7936
+ const [available, types, level] = await Promise.all([
7937
+ isBiometricAvailable(),
7938
+ getBiometricTypes(),
7939
+ getSecurityLevel(),
7940
+ ]);
7941
+ setInfo({ available, types, level });
7942
+ }
7943
+ load();
7944
+ }, []);
7945
+
7946
+ return (
7947
+ <Card>
7948
+ <Text>Biometric available: {info.available ? 'Yes' : 'No'}</Text>
7949
+ <Text>Types: {info.types.join(', ') || 'None'}</Text>
7950
+ <Text>Security level: {info.level}</Text>
7951
+ </Card>
7952
+ );
7953
+ }
7954
+ \`\`\`
7955
+
7956
+ ## Passkey Registration Flow
7957
+
7958
+ \`\`\`tsx
7959
+ import { isPasskeySupported, createPasskey } from '@idealyst/biometrics';
7960
+ import type { PasskeyError } from '@idealyst/biometrics';
7961
+
7962
+ async function registerPasskey(user: { id: string; email: string; name: string }) {
7963
+ const supported = await isPasskeySupported();
7964
+ if (!supported) {
7965
+ alert('Passkeys are not supported on this device');
7966
+ return;
7967
+ }
7968
+
7969
+ // 1. Get challenge from server
7970
+ const { challenge, rpId, rpName } = await api.getRegistrationChallenge();
7971
+
7972
+ try {
7973
+ // 2. Create the passkey
7974
+ const credential = await createPasskey({
7975
+ challenge,
7976
+ rp: { id: rpId, name: rpName },
7977
+ user: {
7978
+ id: user.id,
7979
+ name: user.email,
7980
+ displayName: user.name,
7981
+ },
7982
+ authenticatorSelection: {
7983
+ residentKey: 'required',
7984
+ userVerification: 'required',
7985
+ },
7986
+ });
7987
+
7988
+ // 3. Send to server for verification and storage
7989
+ await api.verifyRegistration({
7990
+ id: credential.id,
7991
+ rawId: credential.rawId,
7992
+ clientDataJSON: credential.response.clientDataJSON,
7993
+ attestationObject: credential.response.attestationObject,
7994
+ });
7995
+
7996
+ alert('Passkey registered successfully!');
7997
+ } catch (err) {
7998
+ const passkeyErr = err as PasskeyError;
7999
+ if (passkeyErr.code === 'cancelled') {
8000
+ // User cancelled \u2014 do nothing
8001
+ return;
8002
+ }
8003
+ alert(\`Failed to register passkey: \${passkeyErr.message}\`);
8004
+ }
8005
+ }
8006
+ \`\`\`
8007
+
8008
+ ## Passkey Login Flow
8009
+
8010
+ \`\`\`tsx
8011
+ import { getPasskey } from '@idealyst/biometrics';
8012
+ import type { PasskeyError } from '@idealyst/biometrics';
8013
+
8014
+ async function loginWithPasskey() {
8015
+ // 1. Get challenge from server
8016
+ const { challenge, rpId } = await api.getAuthenticationChallenge();
8017
+
8018
+ try {
8019
+ // 2. Authenticate with passkey
8020
+ const assertion = await getPasskey({
8021
+ challenge,
8022
+ rpId,
8023
+ userVerification: 'required',
8024
+ });
8025
+
8026
+ // 3. Send to server for verification
8027
+ const session = await api.verifyAuthentication({
8028
+ id: assertion.id,
8029
+ rawId: assertion.rawId,
8030
+ clientDataJSON: assertion.response.clientDataJSON,
8031
+ authenticatorData: assertion.response.authenticatorData,
8032
+ signature: assertion.response.signature,
8033
+ userHandle: assertion.response.userHandle,
8034
+ });
8035
+
8036
+ return session;
8037
+ } catch (err) {
8038
+ const passkeyErr = err as PasskeyError;
8039
+ if (passkeyErr.code === 'cancelled') return null;
8040
+ throw passkeyErr;
8041
+ }
8042
+ }
8043
+ \`\`\`
8044
+
8045
+ ## Login Screen with Passkey + Fallback
8046
+
8047
+ \`\`\`tsx
8048
+ import { isPasskeySupported, getPasskey } from '@idealyst/biometrics';
8049
+ import type { PasskeyError } from '@idealyst/biometrics';
8050
+
8051
+ function LoginScreen() {
8052
+ const [passkeyAvailable, setPasskeyAvailable] = useState(false);
8053
+ const [loading, setLoading] = useState(false);
8054
+
8055
+ useEffect(() => {
8056
+ isPasskeySupported().then(setPasskeyAvailable);
8057
+ }, []);
8058
+
8059
+ const handlePasskeyLogin = async () => {
8060
+ setLoading(true);
8061
+ try {
8062
+ const { challenge, rpId } = await api.getAuthenticationChallenge();
8063
+ const assertion = await getPasskey({ challenge, rpId });
8064
+ const session = await api.verifyAuthentication(assertion);
8065
+ navigateToHome(session);
8066
+ } catch (err) {
8067
+ const e = err as PasskeyError;
8068
+ if (e.code !== 'cancelled') {
8069
+ showError(e.message);
8070
+ }
8071
+ } finally {
8072
+ setLoading(false);
8073
+ }
8074
+ };
8075
+
8076
+ return (
8077
+ <View>
8078
+ <Text variant="headline">Welcome Back</Text>
8079
+
8080
+ {passkeyAvailable && (
8081
+ <Button
8082
+ label="Sign in with Passkey"
8083
+ iconName="fingerprint"
8084
+ onPress={handlePasskeyLogin}
8085
+ loading={loading}
8086
+ />
8087
+ )}
8088
+
8089
+ <Button
8090
+ label="Sign in with Email"
8091
+ intent="neutral"
8092
+ onPress={navigateToEmailLogin}
8093
+ />
8094
+ </View>
8095
+ );
8096
+ }
8097
+ \`\`\`
8098
+
8099
+ ## Best Practices
8100
+
8101
+ 1. **Always check availability first** \u2014 Call \`isBiometricAvailable()\` or \`isPasskeySupported()\` before prompting
8102
+ 2. **Provide fallbacks** \u2014 Not all devices support biometrics. Offer PIN/password alternatives
8103
+ 3. **Handle cancellation gracefully** \u2014 Users may cancel the prompt. Don't show errors for \`user_cancel\` / \`cancelled\`
8104
+ 4. **Use try/catch for passkeys** \u2014 Passkey functions throw \`PasskeyError\` on failure
8105
+ 5. **Server-side validation** \u2014 Always verify passkey responses on your server. The client is untrusted
8106
+ 6. **Base64url encoding** \u2014 All binary WebAuthn data is encoded as base64url strings for transport
8107
+ 7. **Set rpId correctly** \u2014 The relying party ID must match your domain. On native, it's your associated domain
8108
+ 8. **Lockout handling** \u2014 After too many failed biometric attempts, the device locks out. Handle the \`lockout\` error
8109
+ 9. **iOS permissions** \u2014 FaceID requires \`NSFaceIDUsageDescription\` in Info.plist
8110
+ 10. **Android associated domains** \u2014 Passkeys on Android require a Digital Asset Links file at \`/.well-known/assetlinks.json\`
8111
+ `
8112
+ };
8113
+
8114
+ // src/data/payments-guides.ts
8115
+ var paymentsGuides = {
8116
+ "idealyst://payments/overview": `# @idealyst/payments Overview
8117
+
8118
+ Cross-platform payment provider abstractions for React and React Native. Wraps Stripe's Platform Pay API for Apple Pay and Google Pay on mobile.
8119
+
8120
+ ## Features
8121
+
8122
+ - **Apple Pay** \u2014 Native iOS payment sheet via Stripe SDK
8123
+ - **Google Pay** \u2014 Native Android payment sheet via Stripe SDK
8124
+ - **Cross-Platform** \u2014 Single API for React Native and web (web is stub/noop)
8125
+ - **Flat Functions + Hook** \u2014 Use \`initializePayments()\`, \`confirmPayment()\` directly, or \`usePayments()\` hook
8126
+ - **Stripe Platform Pay** \u2014 Wraps \`@stripe/stripe-react-native\` Platform Pay API
8127
+ - **Graceful Degradation** \u2014 Falls back cleanly when Stripe SDK isn't installed
8128
+ - **TypeScript** \u2014 Full type safety
8129
+
8130
+ ## Installation
8131
+
8132
+ \`\`\`bash
8133
+ yarn add @idealyst/payments
8134
+
8135
+ # React Native \u2014 required for mobile payments:
8136
+ yarn add @stripe/stripe-react-native
8137
+ cd ios && pod install
8138
+ \`\`\`
8139
+
8140
+ ## Web Support
8141
+
8142
+ 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\`).
8143
+
8144
+ ## Quick Start
8145
+
8146
+ \`\`\`tsx
8147
+ import { usePayments } from '@idealyst/payments';
8148
+
8149
+ function CheckoutScreen() {
8150
+ const {
8151
+ isReady,
8152
+ isApplePayAvailable,
8153
+ isGooglePayAvailable,
8154
+ isProcessing,
8155
+ confirmPayment,
8156
+ } = usePayments({
8157
+ config: {
8158
+ publishableKey: 'pk_test_xxx',
8159
+ merchantIdentifier: 'merchant.com.myapp',
8160
+ merchantName: 'My App',
8161
+ merchantCountryCode: 'US',
8162
+ },
8163
+ });
8164
+
8165
+ const handlePay = async () => {
8166
+ const result = await confirmPayment({
8167
+ clientSecret: 'pi_xxx_secret_xxx', // from server
8168
+ amount: { amount: 1099, currencyCode: 'usd' },
8169
+ });
8170
+ console.log('Paid:', result.paymentIntentId);
8171
+ };
8172
+
8173
+ if (!isReady) return null;
8174
+
8175
+ return (
8176
+ <Button
8177
+ onPress={handlePay}
8178
+ disabled={isProcessing}
8179
+ label={isApplePayAvailable ? 'Apple Pay' : 'Google Pay'}
8180
+ />
8181
+ );
8182
+ }
8183
+ \`\`\`
8184
+
8185
+ ## Platform Support
8186
+
8187
+ | Feature | iOS | Android | Web |
8188
+ |---------|-----|---------|-----|
8189
+ | Apple Pay | Yes | \u2014 | \u2014 |
8190
+ | Google Pay | \u2014 | Yes | \u2014 |
8191
+ | usePayments hook | Yes | Yes | Stub |
8192
+ | confirmPayment | Yes | Yes | Throws |
8193
+ | createPaymentMethod | Yes | Yes | Throws |
8194
+ `,
8195
+ "idealyst://payments/api": `# Payments API Reference
8196
+
8197
+ Complete API reference for @idealyst/payments.
8198
+
8199
+ ---
8200
+
8201
+ ## Flat Functions
8202
+
8203
+ ### initializePayments
8204
+
8205
+ Initialize the Stripe SDK for platform payments.
8206
+
8207
+ \`\`\`tsx
8208
+ import { initializePayments } from '@idealyst/payments';
8209
+
8210
+ await initializePayments({
8211
+ publishableKey: 'pk_test_xxx',
8212
+ merchantIdentifier: 'merchant.com.myapp', // iOS Apple Pay
8213
+ merchantName: 'My App',
8214
+ merchantCountryCode: 'US',
8215
+ urlScheme: 'com.myapp', // for 3D Secure redirects
8216
+ testEnvironment: true, // Google Pay test mode
8217
+ });
8218
+ \`\`\`
8219
+
8220
+ **PaymentConfig:**
8221
+
8222
+ | Option | Type | Required | Description |
8223
+ |--------|------|----------|-------------|
8224
+ | publishableKey | string | Yes | Stripe publishable key |
8225
+ | merchantIdentifier | string | No | Apple Pay merchant ID (iOS) |
8226
+ | merchantName | string | Yes | Display name on payment sheet |
8227
+ | merchantCountryCode | string | Yes | ISO 3166-1 alpha-2 country |
8228
+ | urlScheme | string | No | URL scheme for 3D Secure |
8229
+ | testEnvironment | boolean | No | Google Pay test mode (default: false) |
8230
+
8231
+ ### checkPaymentAvailability
8232
+
8233
+ Check which payment methods are available on the current device.
8234
+
8235
+ \`\`\`tsx
8236
+ import { checkPaymentAvailability } from '@idealyst/payments';
8237
+
8238
+ const methods = await checkPaymentAvailability();
8239
+ // [
8240
+ // { type: 'apple_pay', isAvailable: true },
8241
+ // { type: 'google_pay', isAvailable: false, unavailableReason: '...' },
8242
+ // { type: 'card', isAvailable: true },
8243
+ // ]
8244
+ \`\`\`
8245
+
8246
+ ### confirmPayment
8247
+
8248
+ Present the platform payment sheet and confirm a PaymentIntent. Requires a \`clientSecret\` from a server-created PaymentIntent.
8249
+
8250
+ \`\`\`tsx
8251
+ import { confirmPayment } from '@idealyst/payments';
8252
+
8253
+ const result = await confirmPayment({
8254
+ clientSecret: 'pi_xxx_secret_xxx',
8255
+ amount: { amount: 1099, currencyCode: 'usd' },
8256
+ lineItems: [
8257
+ { label: 'Widget', amount: 999 },
8258
+ { label: 'Tax', amount: 100 },
8259
+ ],
8260
+ billingAddress: {
8261
+ isRequired: true,
8262
+ format: 'FULL',
8263
+ },
8264
+ });
8265
+
8266
+ console.log(result.paymentIntentId); // 'pi_xxx'
8267
+ console.log(result.status); // 'succeeded'
8268
+ \`\`\`
8269
+
8270
+ ### createPaymentMethod
8271
+
8272
+ Present the payment sheet and create a payment method without confirming. Returns a payment method ID for server-side processing.
8273
+
8274
+ \`\`\`tsx
8275
+ import { createPaymentMethod } from '@idealyst/payments';
8276
+
8277
+ const result = await createPaymentMethod({
8278
+ amount: { amount: 1099, currencyCode: 'usd' },
8279
+ });
8280
+
8281
+ console.log(result.paymentMethodId); // 'pm_xxx'
8282
+ // Send to your server for processing
8283
+ \`\`\`
8284
+
8285
+ ### getPaymentStatus
8286
+
8287
+ Get the current payment provider status.
8288
+
8289
+ \`\`\`tsx
8290
+ import { getPaymentStatus } from '@idealyst/payments';
8291
+
8292
+ const status = getPaymentStatus();
8293
+ // { state: 'ready', availablePaymentMethods: [...], isPaymentAvailable: true }
8294
+ \`\`\`
8295
+
8296
+ ---
8297
+
8298
+ ## usePayments Hook
8299
+
8300
+ Convenience hook that wraps the flat functions with React state management.
8301
+
8302
+ \`\`\`tsx
8303
+ import { usePayments } from '@idealyst/payments';
8304
+
8305
+ const {
8306
+ // State
8307
+ status, // PaymentProviderStatus
8308
+ isReady, // boolean
8309
+ isProcessing, // boolean
8310
+ availablePaymentMethods, // PaymentMethodAvailability[]
8311
+ isApplePayAvailable, // boolean
8312
+ isGooglePayAvailable,// boolean
8313
+ isPaymentAvailable, // boolean
8314
+ error, // PaymentError | null
8315
+
8316
+ // Actions
8317
+ initialize, // (config: PaymentConfig) => Promise<void>
8318
+ checkAvailability, // () => Promise<PaymentMethodAvailability[]>
8319
+ confirmPayment, // (request: PaymentSheetRequest) => Promise<PaymentResult>
8320
+ createPaymentMethod, // (request: PaymentSheetRequest) => Promise<PaymentResult>
8321
+ clearError, // () => void
8322
+ } = usePayments(options?);
8323
+ \`\`\`
8324
+
8325
+ **UsePaymentsOptions:**
8326
+
8327
+ | Option | Type | Default | Description |
8328
+ |--------|------|---------|-------------|
8329
+ | config | PaymentConfig | \u2014 | Auto-initialize with this config |
8330
+ | autoCheckAvailability | boolean | true | Check availability after init |
8331
+
8332
+ ---
8333
+
8334
+ ## Types
8335
+
8336
+ ### PaymentMethodType
8337
+
8338
+ \`\`\`tsx
8339
+ type PaymentMethodType = 'apple_pay' | 'google_pay' | 'card';
8340
+ \`\`\`
8341
+
8342
+ ### PaymentAmount
8343
+
8344
+ \`\`\`tsx
8345
+ interface PaymentAmount {
8346
+ amount: number; // smallest currency unit (cents)
8347
+ currencyCode: string; // ISO 4217 (e.g., 'usd')
8348
+ }
8349
+ \`\`\`
8350
+
8351
+ ### PaymentSheetRequest
8352
+
8353
+ \`\`\`tsx
8354
+ interface PaymentSheetRequest {
8355
+ clientSecret?: string; // required for confirmPayment
8356
+ amount: PaymentAmount;
8357
+ lineItems?: PaymentLineItem[];
8358
+ billingAddress?: BillingAddressConfig;
8359
+ allowedPaymentMethods?: PaymentMethodType[];
8360
+ }
8361
+ \`\`\`
8362
+
8363
+ ### PaymentResult
8364
+
8365
+ \`\`\`tsx
8366
+ interface PaymentResult {
8367
+ paymentMethodType: PaymentMethodType;
8368
+ paymentMethodId?: string; // for createPaymentMethod
8369
+ paymentIntentId?: string; // for confirmPayment
8370
+ status?: string; // 'succeeded', 'requires_capture', etc.
8371
+ }
8372
+ \`\`\`
8373
+
8374
+ ### PaymentError
6654
8375
 
6655
8376
  \`\`\`tsx
6656
- import React from 'react';
6657
- import { LineChart } from '@idealyst/charts';
6658
- import type { ChartDataSeries } from '@idealyst/charts';
8377
+ interface PaymentError {
8378
+ code: PaymentErrorCode;
8379
+ message: string;
8380
+ originalError?: unknown;
8381
+ }
8382
+
8383
+ type PaymentErrorCode =
8384
+ | 'not_initialized'
8385
+ | 'not_available'
8386
+ | 'not_supported'
8387
+ | 'user_cancelled'
8388
+ | 'payment_failed'
8389
+ | 'network_error'
8390
+ | 'invalid_config'
8391
+ | 'invalid_request'
8392
+ | 'provider_error'
8393
+ | 'unknown';
8394
+ \`\`\`
8395
+ `,
8396
+ "idealyst://payments/examples": `# Payments Examples
6659
8397
 
6660
- // Each series has: id, name, data, color? \u2014 NO 'label' property
6661
- const series: ChartDataSeries[] = [
6662
- {
6663
- id: 'product-a',
6664
- name: 'Product A', // Use 'name' \u2014 NOT 'label'
6665
- data: [
6666
- { x: 'Q1', y: 120 },
6667
- { x: 'Q2', y: 150 },
6668
- { x: 'Q3', y: 180 },
6669
- { x: 'Q4', y: 210 },
6670
- ],
6671
- color: '#2196F3',
6672
- },
6673
- {
6674
- id: 'product-b',
6675
- name: 'Product B', // Use 'name' \u2014 NOT 'label'
6676
- data: [
6677
- { x: 'Q1', y: 80 },
6678
- { x: 'Q2', y: 110 },
6679
- { x: 'Q3', y: 95 },
6680
- { x: 'Q4', y: 140 },
6681
- ],
6682
- color: '#FF9800',
6683
- },
6684
- ];
8398
+ Complete code examples for common @idealyst/payments patterns.
8399
+
8400
+ ## Basic Checkout with Hook
8401
+
8402
+ \`\`\`tsx
8403
+ import { usePayments } from '@idealyst/payments';
8404
+ import { Button, View, Text } from '@idealyst/components';
8405
+
8406
+ function CheckoutScreen({ clientSecret }: { clientSecret: string }) {
8407
+ const {
8408
+ isReady,
8409
+ isApplePayAvailable,
8410
+ isGooglePayAvailable,
8411
+ isProcessing,
8412
+ error,
8413
+ confirmPayment,
8414
+ clearError,
8415
+ } = usePayments({
8416
+ config: {
8417
+ publishableKey: 'pk_test_xxx',
8418
+ merchantIdentifier: 'merchant.com.myapp',
8419
+ merchantName: 'My App',
8420
+ merchantCountryCode: 'US',
8421
+ testEnvironment: __DEV__,
8422
+ },
8423
+ });
8424
+
8425
+ const handlePay = async () => {
8426
+ try {
8427
+ const result = await confirmPayment({
8428
+ clientSecret,
8429
+ amount: { amount: 1099, currencyCode: 'usd' },
8430
+ lineItems: [
8431
+ { label: 'Widget', amount: 999 },
8432
+ { label: 'Tax', amount: 100 },
8433
+ ],
8434
+ });
8435
+
8436
+ // Payment succeeded
8437
+ console.log('Payment confirmed:', result.paymentIntentId);
8438
+ } catch (err) {
8439
+ // Error is automatically set in hook state
8440
+ console.error('Payment failed:', err);
8441
+ }
8442
+ };
8443
+
8444
+ if (!isReady) {
8445
+ return <Text>Loading payment methods...</Text>;
8446
+ }
8447
+
8448
+ const canPay = isApplePayAvailable || isGooglePayAvailable;
6685
8449
 
6686
- function ComparisonChart() {
6687
8450
  return (
6688
- <LineChart
6689
- data={series}
6690
- height={350}
6691
- curve="monotone"
6692
- showDots
6693
- animate
6694
- />
8451
+ <View>
8452
+ {canPay ? (
8453
+ <Button
8454
+ onPress={handlePay}
8455
+ disabled={isProcessing}
8456
+ intent="primary"
8457
+ >
8458
+ {isApplePayAvailable ? 'Pay with Apple Pay' : 'Pay with Google Pay'}
8459
+ </Button>
8460
+ ) : (
8461
+ <Text>No payment methods available</Text>
8462
+ )}
8463
+
8464
+ {error && (
8465
+ <View>
8466
+ <Text intent="danger">{error.message}</Text>
8467
+ <Button onPress={clearError}>Dismiss</Button>
8468
+ </View>
8469
+ )}
8470
+ </View>
6695
8471
  );
6696
8472
  }
6697
8473
  \`\`\`
6698
8474
 
6699
- ## Bar Chart
8475
+ ## Create Payment Method (Server-Side Confirm)
6700
8476
 
6701
8477
  \`\`\`tsx
6702
- import React from 'react';
6703
- import { View, Text } from '@idealyst/components';
6704
- import { BarChart } from '@idealyst/charts';
8478
+ import { usePayments } from '@idealyst/payments';
8479
+
8480
+ function DonateScreen() {
8481
+ const { isReady, isPaymentAvailable, createPaymentMethod } = usePayments({
8482
+ config: {
8483
+ publishableKey: 'pk_test_xxx',
8484
+ merchantName: 'Charity',
8485
+ merchantCountryCode: 'US',
8486
+ },
8487
+ });
6705
8488
 
6706
- const categories = [
6707
- { x: 'Electronics', y: 45 },
6708
- { x: 'Clothing', y: 32 },
6709
- { x: 'Books', y: 18 },
6710
- { x: 'Food', y: 56 },
6711
- { x: 'Sports', y: 28 },
6712
- ];
8489
+ const handleDonate = async (amountCents: number) => {
8490
+ const result = await createPaymentMethod({
8491
+ amount: { amount: amountCents, currencyCode: 'usd' },
8492
+ });
8493
+
8494
+ // Send payment method to your server
8495
+ await fetch('/api/donate', {
8496
+ method: 'POST',
8497
+ body: JSON.stringify({
8498
+ paymentMethodId: result.paymentMethodId,
8499
+ amount: amountCents,
8500
+ }),
8501
+ });
8502
+ };
6713
8503
 
6714
- function CategoryBreakdown() {
6715
8504
  return (
6716
- <View padding="md" gap="md">
6717
- <Text typography="h6" weight="bold">Sales by Category</Text>
6718
- <BarChart
6719
- data={[{ id: 'units', name: 'Units Sold', data: categories }]}
6720
- height={300}
6721
- barRadius={4}
6722
- animate
6723
- yAxis={{ tickFormat: (value: number | string | Date) => \`\${value} units\` }}
6724
- />
8505
+ <View>
8506
+ <Button onPress={() => handleDonate(500)} disabled={!isPaymentAvailable}>
8507
+ Donate $5
8508
+ </Button>
8509
+ <Button onPress={() => handleDonate(1000)} disabled={!isPaymentAvailable}>
8510
+ Donate $10
8511
+ </Button>
6725
8512
  </View>
6726
8513
  );
6727
8514
  }
6728
8515
  \`\`\`
6729
8516
 
6730
- ## Stacked Bar Chart
8517
+ ## Flat Functions (No Hook)
6731
8518
 
6732
8519
  \`\`\`tsx
6733
- import React from 'react';
6734
- import { BarChart } from '@idealyst/charts';
8520
+ import {
8521
+ initializePayments,
8522
+ checkPaymentAvailability,
8523
+ confirmPayment,
8524
+ getPaymentStatus,
8525
+ } from '@idealyst/payments';
8526
+ import type { PaymentError } from '@idealyst/payments';
8527
+
8528
+ // Initialize once at app startup
8529
+ async function setupPayments() {
8530
+ await initializePayments({
8531
+ publishableKey: 'pk_test_xxx',
8532
+ merchantIdentifier: 'merchant.com.myapp',
8533
+ merchantName: 'My App',
8534
+ merchantCountryCode: 'US',
8535
+ });
6735
8536
 
6736
- function StackedBarExample() {
6737
- return (
6738
- <BarChart
6739
- data={[
6740
- {
6741
- id: 'online',
6742
- name: 'Online',
6743
- data: [
6744
- { x: 'Q1', y: 100 },
6745
- { x: 'Q2', y: 120 },
6746
- { x: 'Q3', y: 90 },
6747
- ],
6748
- color: '#4CAF50',
6749
- },
6750
- {
6751
- id: 'in-store',
6752
- name: 'In-Store',
6753
- data: [
6754
- { x: 'Q1', y: 60 },
6755
- { x: 'Q2', y: 80 },
6756
- { x: 'Q3', y: 70 },
6757
- ],
6758
- color: '#2196F3',
6759
- },
6760
- ]}
6761
- height={300}
6762
- stacked
6763
- animate
6764
- />
6765
- );
8537
+ const status = getPaymentStatus();
8538
+ console.log('Payment ready:', status.state === 'ready');
8539
+ }
8540
+
8541
+ // Check availability
8542
+ async function canPay() {
8543
+ const methods = await checkPaymentAvailability();
8544
+ return methods.some(m => m.isAvailable);
8545
+ }
8546
+
8547
+ // Process payment
8548
+ async function processPayment(clientSecret: string, totalCents: number) {
8549
+ try {
8550
+ const result = await confirmPayment({
8551
+ clientSecret,
8552
+ amount: { amount: totalCents, currencyCode: 'usd' },
8553
+ });
8554
+ return result;
8555
+ } catch (err) {
8556
+ const paymentErr = err as PaymentError;
8557
+ if (paymentErr.code === 'user_cancelled') {
8558
+ return null; // User cancelled \u2014 not an error
8559
+ }
8560
+ throw paymentErr;
8561
+ }
6766
8562
  }
6767
8563
  \`\`\`
6768
8564
 
6769
- ## Horizontal Bar Chart
8565
+ ## Platform-Conditional UI
6770
8566
 
6771
8567
  \`\`\`tsx
6772
- import React from 'react';
6773
- import { BarChart } from '@idealyst/charts';
8568
+ import { usePayments } from '@idealyst/payments';
8569
+ import { Platform } from 'react-native';
6774
8570
 
6775
- function HorizontalBarExample() {
6776
- const data = [
6777
- { x: 'React', y: 85 },
6778
- { x: 'Vue', y: 62 },
6779
- { x: 'Angular', y: 45 },
6780
- { x: 'Svelte', y: 38 },
6781
- ];
8571
+ function PaymentButtons({ clientSecret }: { clientSecret: string }) {
8572
+ const {
8573
+ isApplePayAvailable,
8574
+ isGooglePayAvailable,
8575
+ confirmPayment,
8576
+ isProcessing,
8577
+ } = usePayments({
8578
+ config: {
8579
+ publishableKey: 'pk_test_xxx',
8580
+ merchantName: 'My Store',
8581
+ merchantCountryCode: 'US',
8582
+ },
8583
+ });
8584
+
8585
+ const handlePlatformPay = () =>
8586
+ confirmPayment({
8587
+ clientSecret,
8588
+ amount: { amount: 2499, currencyCode: 'usd' },
8589
+ });
6782
8590
 
6783
8591
  return (
6784
- <BarChart
6785
- data={[{ id: 'popularity', name: 'Popularity', data }]}
6786
- height={250}
6787
- orientation="horizontal"
6788
- animate
6789
- />
8592
+ <View>
8593
+ {isApplePayAvailable && (
8594
+ <Button
8595
+ onPress={handlePlatformPay}
8596
+ disabled={isProcessing}
8597
+ iconName="apple"
8598
+ >
8599
+ Apple Pay
8600
+ </Button>
8601
+ )}
8602
+
8603
+ {isGooglePayAvailable && (
8604
+ <Button
8605
+ onPress={handlePlatformPay}
8606
+ disabled={isProcessing}
8607
+ iconName="google"
8608
+ >
8609
+ Google Pay
8610
+ </Button>
8611
+ )}
8612
+
8613
+ {/* Always show a card fallback */}
8614
+ <Button
8615
+ onPress={() => navigateToCardForm()}
8616
+ intent="secondary"
8617
+ >
8618
+ Pay with Card
8619
+ </Button>
8620
+ </View>
6790
8621
  );
6791
8622
  }
6792
8623
  \`\`\`
8624
+
8625
+ ## Best Practices
8626
+
8627
+ 1. **Server-side PaymentIntent** \u2014 Always create PaymentIntents on your server, never on the client
8628
+ 2. **Handle cancellation** \u2014 \`user_cancelled\` is not an error, don't show error UI for it
8629
+ 3. **Test environment** \u2014 Set \`testEnvironment: true\` during development for Google Pay
8630
+ 4. **Apple Pay merchant ID** \u2014 Requires Apple Developer Program and Xcode capability setup
8631
+ 5. **Amounts in cents** \u2014 All amounts are in the smallest currency unit (1099 = $10.99)
8632
+ 6. **Web fallback** \u2014 On web, use \`@stripe/react-stripe-js\` (Stripe Elements) directly
8633
+ 7. **3D Secure** \u2014 Set \`urlScheme\` in config for 3D Secure / bank redirect flows on native
8634
+ 8. **Error handling** \u2014 Always wrap \`confirmPayment\` / \`createPaymentMethod\` in try/catch
6793
8635
  `
6794
8636
  };
6795
8637
 
@@ -7710,6 +9552,156 @@ function UploadForm() {
7710
9552
  "Use customMimeTypes for specific formats like 'application/pdf'"
7711
9553
  ],
7712
9554
  relatedPackages: ["components", "camera", "storage"]
9555
+ },
9556
+ clipboard: {
9557
+ name: "Clipboard",
9558
+ npmName: "@idealyst/clipboard",
9559
+ description: "Cross-platform clipboard and OTP autofill for React and React Native. Copy/paste text and auto-detect SMS verification codes on mobile.",
9560
+ category: "utility",
9561
+ platforms: ["web", "native"],
9562
+ documentationStatus: "full",
9563
+ installation: "yarn add @idealyst/clipboard",
9564
+ peerDependencies: [
9565
+ "@react-native-clipboard/clipboard (native)",
9566
+ "react-native-otp-verify (Android OTP, optional)"
9567
+ ],
9568
+ features: [
9569
+ "Cross-platform copy/paste with async API",
9570
+ "Android SMS OTP auto-read via SMS Retriever API (no permissions)",
9571
+ "iOS OTP keyboard autofill via TextInput props",
9572
+ "Clipboard change listeners",
9573
+ "Graceful degradation when native modules missing"
9574
+ ],
9575
+ quickStart: `import { clipboard } from '@idealyst/clipboard';
9576
+
9577
+ // Copy text
9578
+ await clipboard.copy('Hello, world!');
9579
+
9580
+ // Paste text
9581
+ const text = await clipboard.paste();
9582
+
9583
+ // OTP auto-fill (mobile)
9584
+ import { useOTPAutoFill, OTP_INPUT_PROPS } from '@idealyst/clipboard';
9585
+
9586
+ function OTPScreen() {
9587
+ const { code, startListening } = useOTPAutoFill({
9588
+ codeLength: 6,
9589
+ onCodeReceived: (otp) => verifyOTP(otp),
9590
+ });
9591
+
9592
+ useEffect(() => { startListening(); }, []);
9593
+
9594
+ return <TextInput value={code ?? ''} {...OTP_INPUT_PROPS} />;
9595
+ }`,
9596
+ apiHighlights: [
9597
+ "clipboard.copy(text) - Copy text to clipboard",
9598
+ "clipboard.paste() - Read text from clipboard",
9599
+ "clipboard.hasText() - Check if clipboard has text",
9600
+ "clipboard.addListener(fn) - Listen for copy events",
9601
+ "useOTPAutoFill({ codeLength, onCodeReceived }) - Auto-detect SMS OTP codes (Android)",
9602
+ "OTP_INPUT_PROPS - TextInput props for iOS OTP keyboard autofill"
9603
+ ],
9604
+ relatedPackages: ["storage", "components"]
9605
+ },
9606
+ biometrics: {
9607
+ name: "Biometrics",
9608
+ npmName: "@idealyst/biometrics",
9609
+ description: "Cross-platform biometric authentication and passkeys (WebAuthn/FIDO2) for React and React Native. FaceID, TouchID, fingerprint, iris, and passwordless login.",
9610
+ category: "auth",
9611
+ platforms: ["web", "native"],
9612
+ documentationStatus: "full",
9613
+ installation: "yarn add @idealyst/biometrics",
9614
+ peerDependencies: [
9615
+ "expo-local-authentication (native biometrics)",
9616
+ "react-native-passkeys (native passkeys, optional)"
9617
+ ],
9618
+ features: [
9619
+ "Local biometric auth \u2014 FaceID, TouchID, fingerprint, iris",
9620
+ "Passkeys (WebAuthn/FIDO2) \u2014 passwordless login with cryptographic credentials",
9621
+ "Cross-platform \u2014 single API for web and React Native",
9622
+ "Web biometrics via WebAuthn userVerification",
9623
+ "Web passkeys via navigator.credentials",
9624
+ "Graceful degradation when native modules missing",
9625
+ "Base64url encoding helpers for WebAuthn data"
9626
+ ],
9627
+ quickStart: `import { isBiometricAvailable, authenticate } from '@idealyst/biometrics';
9628
+
9629
+ // Check biometric availability
9630
+ const available = await isBiometricAvailable();
9631
+
9632
+ // Authenticate
9633
+ if (available) {
9634
+ const result = await authenticate({ promptMessage: 'Verify your identity' });
9635
+ if (result.success) {
9636
+ // Authenticated!
9637
+ }
9638
+ }
9639
+
9640
+ // Passkeys
9641
+ import { isPasskeySupported, createPasskey, getPasskey } from '@idealyst/biometrics';
9642
+
9643
+ const credential = await createPasskey({
9644
+ challenge: serverChallenge,
9645
+ rp: { id: 'example.com', name: 'My App' },
9646
+ user: { id: userId, name: email, displayName: name },
9647
+ });`,
9648
+ apiHighlights: [
9649
+ "isBiometricAvailable() - Check biometric hardware and enrollment",
9650
+ "getBiometricTypes() - Get available biometric types",
9651
+ "getSecurityLevel() - Get device security level",
9652
+ "authenticate(options) - Prompt for biometric auth",
9653
+ "cancelAuthentication() - Cancel auth (Android)",
9654
+ "isPasskeySupported() - Check passkey support",
9655
+ "createPasskey(options) - Register a new passkey",
9656
+ "getPasskey(options) - Authenticate with passkey"
9657
+ ],
9658
+ relatedPackages: ["oauth-client", "storage"]
9659
+ },
9660
+ payments: {
9661
+ name: "Payments",
9662
+ npmName: "@idealyst/payments",
9663
+ 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.",
9664
+ category: "utility",
9665
+ platforms: ["web", "native"],
9666
+ documentationStatus: "full",
9667
+ installation: "yarn add @idealyst/payments @stripe/stripe-react-native",
9668
+ peerDependencies: [
9669
+ "@stripe/stripe-react-native (native)"
9670
+ ],
9671
+ features: [
9672
+ "Apple Pay via Stripe Platform Pay (iOS)",
9673
+ "Google Pay via Stripe Platform Pay (Android)",
9674
+ "Flat function API + usePayments convenience hook",
9675
+ "PaymentIntent confirmation flow",
9676
+ "Payment method creation flow",
9677
+ "Normalized error handling across platforms",
9678
+ "Graceful degradation when Stripe SDK missing",
9679
+ "Web stub with guidance to Stripe Elements"
9680
+ ],
9681
+ quickStart: `import { usePayments } from '@idealyst/payments';
9682
+
9683
+ const { isReady, isApplePayAvailable, confirmPayment } = usePayments({
9684
+ config: {
9685
+ publishableKey: 'pk_test_xxx',
9686
+ merchantIdentifier: 'merchant.com.myapp',
9687
+ merchantName: 'My App',
9688
+ merchantCountryCode: 'US',
9689
+ },
9690
+ });
9691
+
9692
+ const result = await confirmPayment({
9693
+ clientSecret: 'pi_xxx_secret_xxx',
9694
+ amount: { amount: 1099, currencyCode: 'usd' },
9695
+ });`,
9696
+ apiHighlights: [
9697
+ "initializePayments(config) - Initialize Stripe SDK",
9698
+ "checkPaymentAvailability() - Check Apple Pay / Google Pay / card",
9699
+ "confirmPayment(request) - Present sheet and confirm PaymentIntent",
9700
+ "createPaymentMethod(request) - Present sheet and create payment method",
9701
+ "getPaymentStatus() - Get current provider status",
9702
+ "usePayments(options?) - Convenience hook with state management"
9703
+ ],
9704
+ relatedPackages: ["biometrics", "oauth-client"]
7713
9705
  }
7714
9706
  };
7715
9707
  function getPackagesByCategory() {
@@ -19818,7 +21810,7 @@ function getStorageGuide(args) {
19818
21810
  const guide = storageGuides[uri];
19819
21811
  if (!guide) {
19820
21812
  return textResponse(
19821
- `Topic "${topic}" not found. Available topics: overview, api, examples`
21813
+ `Topic "${topic}" not found. Available topics: overview, api, examples, secure`
19822
21814
  );
19823
21815
  }
19824
21816
  return textResponse(guide);
@@ -19944,6 +21936,39 @@ function getChartsGuide(args) {
19944
21936
  }
19945
21937
  return textResponse(guide);
19946
21938
  }
21939
+ function getClipboardGuide(args) {
21940
+ const topic = args.topic;
21941
+ const uri = `idealyst://clipboard/${topic}`;
21942
+ const guide = clipboardGuides[uri];
21943
+ if (!guide) {
21944
+ return textResponse(
21945
+ `Topic "${topic}" not found. Available topics: overview, api, examples`
21946
+ );
21947
+ }
21948
+ return textResponse(guide);
21949
+ }
21950
+ function getBiometricsGuide(args) {
21951
+ const topic = args.topic;
21952
+ const uri = `idealyst://biometrics/${topic}`;
21953
+ const guide = biometricsGuides[uri];
21954
+ if (!guide) {
21955
+ return textResponse(
21956
+ `Topic "${topic}" not found. Available topics: overview, api, examples`
21957
+ );
21958
+ }
21959
+ return textResponse(guide);
21960
+ }
21961
+ function getPaymentsGuide(args) {
21962
+ const topic = args.topic;
21963
+ const uri = `idealyst://payments/${topic}`;
21964
+ const guide = paymentsGuides[uri];
21965
+ if (!guide) {
21966
+ return textResponse(
21967
+ `Topic "${topic}" not found. Available topics: overview, api, examples`
21968
+ );
21969
+ }
21970
+ return textResponse(guide);
21971
+ }
19947
21972
  function listPackages(args = {}) {
19948
21973
  const category = args.category;
19949
21974
  if (category) {
@@ -20189,6 +22214,9 @@ var toolHandlers = {
20189
22214
  get_markdown_guide: getMarkdownGuide,
20190
22215
  get_config_guide: getConfigGuide,
20191
22216
  get_charts_guide: getChartsGuide,
22217
+ get_clipboard_guide: getClipboardGuide,
22218
+ get_biometrics_guide: getBiometricsGuide,
22219
+ get_payments_guide: getPaymentsGuide,
20192
22220
  list_packages: listPackages,
20193
22221
  get_package_docs: getPackageDocs,
20194
22222
  search_packages: searchPackages2,
@@ -20235,6 +22263,9 @@ export {
20235
22263
  getMarkdownGuideDefinition,
20236
22264
  getConfigGuideDefinition,
20237
22265
  getChartsGuideDefinition,
22266
+ getClipboardGuideDefinition,
22267
+ getBiometricsGuideDefinition,
22268
+ getPaymentsGuideDefinition,
20238
22269
  listPackagesDefinition,
20239
22270
  getPackageDocsDefinition,
20240
22271
  searchPackagesDefinition,
@@ -20271,6 +22302,9 @@ export {
20271
22302
  getMarkdownGuide,
20272
22303
  getConfigGuide,
20273
22304
  getChartsGuide,
22305
+ getClipboardGuide,
22306
+ getBiometricsGuide,
22307
+ getPaymentsGuide,
20274
22308
  listPackages,
20275
22309
  getPackageDocs,
20276
22310
  searchPackages2 as searchPackages,
@@ -20282,4 +22316,4 @@ export {
20282
22316
  toolHandlers,
20283
22317
  callTool
20284
22318
  };
20285
- //# sourceMappingURL=chunk-NX77GGPR.js.map
22319
+ //# sourceMappingURL=chunk-7WPOVADU.js.map