@mobileai/react-native 0.9.17 → 0.9.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (292) hide show
  1. package/LICENSE +28 -20
  2. package/MobileAIFloatingOverlay.podspec +25 -0
  3. package/android/build.gradle +61 -0
  4. package/android/src/main/AndroidManifest.xml +3 -0
  5. package/android/src/main/java/com/mobileai/overlay/FloatingOverlayView.kt +151 -0
  6. package/android/src/main/java/com/mobileai/overlay/MobileAIOverlayPackage.kt +23 -0
  7. package/android/src/newarch/com/mobileai/overlay/FloatingOverlayViewManager.kt +45 -0
  8. package/android/src/oldarch/com/mobileai/overlay/FloatingOverlayViewManager.kt +29 -0
  9. package/ios/MobileAIFloatingOverlayComponentView.mm +73 -0
  10. package/lib/module/components/AIAgent.js +902 -136
  11. package/lib/module/components/AIConsentDialog.js +439 -0
  12. package/lib/module/components/AgentChatBar.js +828 -134
  13. package/lib/module/components/AgentOverlay.js +2 -1
  14. package/lib/module/components/DiscoveryTooltip.js +21 -9
  15. package/lib/module/components/FloatingOverlayWrapper.js +108 -0
  16. package/lib/module/components/Icons.js +123 -0
  17. package/lib/module/config/endpoints.js +12 -2
  18. package/lib/module/core/AgentRuntime.js +373 -27
  19. package/lib/module/core/FiberAdapter.js +56 -0
  20. package/lib/module/core/FiberTreeWalker.js +186 -80
  21. package/lib/module/core/IdleDetector.js +19 -0
  22. package/lib/module/core/NativeAlertInterceptor.js +191 -0
  23. package/lib/module/core/systemPrompt.js +203 -45
  24. package/lib/module/index.js +3 -0
  25. package/lib/module/providers/GeminiProvider.js +72 -56
  26. package/lib/module/providers/ProviderFactory.js +6 -2
  27. package/lib/module/services/AudioInputService.js +3 -12
  28. package/lib/module/services/AudioOutputService.js +1 -13
  29. package/lib/module/services/ConversationService.js +166 -0
  30. package/lib/module/services/MobileAIKnowledgeRetriever.js +41 -0
  31. package/lib/module/services/VoiceService.js +29 -8
  32. package/lib/module/services/telemetry/MobileAI.js +44 -0
  33. package/lib/module/services/telemetry/TelemetryService.js +13 -1
  34. package/lib/module/services/telemetry/TouchAutoCapture.js +44 -18
  35. package/lib/module/specs/FloatingOverlayNativeComponent.ts +19 -0
  36. package/lib/module/support/CSATSurvey.js +95 -12
  37. package/lib/module/support/EscalationSocket.js +70 -1
  38. package/lib/module/support/ReportedIssueEventSource.js +148 -0
  39. package/lib/module/support/escalateTool.js +4 -2
  40. package/lib/module/support/index.js +1 -0
  41. package/lib/module/support/reportIssueTool.js +127 -0
  42. package/lib/module/support/supportPrompt.js +77 -9
  43. package/lib/module/tools/guideTool.js +2 -1
  44. package/lib/module/tools/longPressTool.js +4 -3
  45. package/lib/module/tools/pickerTool.js +6 -4
  46. package/lib/module/tools/tapTool.js +12 -3
  47. package/lib/module/tools/typeTool.js +19 -10
  48. package/lib/module/utils/logger.js +175 -6
  49. package/lib/typescript/react-native.config.d.ts +11 -0
  50. package/lib/typescript/src/components/AIAgent.d.ts +28 -2
  51. package/lib/typescript/src/components/AIConsentDialog.d.ts +153 -0
  52. package/lib/typescript/src/components/AgentChatBar.d.ts +15 -2
  53. package/lib/typescript/src/components/DiscoveryTooltip.d.ts +3 -1
  54. package/lib/typescript/src/components/FloatingOverlayWrapper.d.ts +51 -0
  55. package/lib/typescript/src/components/Icons.d.ts +8 -0
  56. package/lib/typescript/src/config/endpoints.d.ts +5 -3
  57. package/lib/typescript/src/core/AgentRuntime.d.ts +4 -0
  58. package/lib/typescript/src/core/FiberAdapter.d.ts +25 -0
  59. package/lib/typescript/src/core/FiberTreeWalker.d.ts +2 -0
  60. package/lib/typescript/src/core/IdleDetector.d.ts +11 -0
  61. package/lib/typescript/src/core/NativeAlertInterceptor.d.ts +55 -0
  62. package/lib/typescript/src/core/types.d.ts +106 -1
  63. package/lib/typescript/src/index.d.ts +9 -4
  64. package/lib/typescript/src/providers/GeminiProvider.d.ts +6 -5
  65. package/lib/typescript/src/services/ConversationService.d.ts +55 -0
  66. package/lib/typescript/src/services/MobileAIKnowledgeRetriever.d.ts +9 -0
  67. package/lib/typescript/src/services/telemetry/MobileAI.d.ts +7 -0
  68. package/lib/typescript/src/services/telemetry/TelemetryService.d.ts +1 -1
  69. package/lib/typescript/src/services/telemetry/TouchAutoCapture.d.ts +9 -6
  70. package/lib/typescript/src/services/telemetry/types.d.ts +3 -1
  71. package/lib/typescript/src/specs/FloatingOverlayNativeComponent.d.ts +17 -0
  72. package/lib/typescript/src/support/EscalationSocket.d.ts +17 -0
  73. package/lib/typescript/src/support/ReportedIssueEventSource.d.ts +24 -0
  74. package/lib/typescript/src/support/escalateTool.d.ts +5 -0
  75. package/lib/typescript/src/support/index.d.ts +2 -1
  76. package/lib/typescript/src/support/reportIssueTool.d.ts +20 -0
  77. package/lib/typescript/src/support/types.d.ts +56 -1
  78. package/lib/typescript/src/utils/logger.d.ts +15 -0
  79. package/package.json +20 -9
  80. package/react-native.config.js +12 -0
  81. package/lib/module/__cli_tmp__.js.map +0 -1
  82. package/lib/module/components/AIAgent.js.map +0 -1
  83. package/lib/module/components/AIZone.js.map +0 -1
  84. package/lib/module/components/AgentChatBar.js.map +0 -1
  85. package/lib/module/components/AgentErrorBoundary.js.map +0 -1
  86. package/lib/module/components/AgentOverlay.js.map +0 -1
  87. package/lib/module/components/DiscoveryTooltip.js.map +0 -1
  88. package/lib/module/components/HighlightOverlay.js.map +0 -1
  89. package/lib/module/components/Icons.js.map +0 -1
  90. package/lib/module/components/ProactiveHint.js.map +0 -1
  91. package/lib/module/components/cards/InfoCard.js.map +0 -1
  92. package/lib/module/components/cards/ReviewSummary.js.map +0 -1
  93. package/lib/module/config/endpoints.js.map +0 -1
  94. package/lib/module/core/ActionRegistry.js.map +0 -1
  95. package/lib/module/core/AgentRuntime.js.map +0 -1
  96. package/lib/module/core/FiberTreeWalker.js.map +0 -1
  97. package/lib/module/core/IdleDetector.js.map +0 -1
  98. package/lib/module/core/MCPBridge.js.map +0 -1
  99. package/lib/module/core/ScreenDehydrator.js.map +0 -1
  100. package/lib/module/core/ZoneRegistry.js.map +0 -1
  101. package/lib/module/core/systemPrompt.js.map +0 -1
  102. package/lib/module/core/types.js.map +0 -1
  103. package/lib/module/hooks/useAction.js.map +0 -1
  104. package/lib/module/index.js.map +0 -1
  105. package/lib/module/plugin/withAppIntents.js.map +0 -1
  106. package/lib/module/providers/GeminiProvider.js.map +0 -1
  107. package/lib/module/providers/OpenAIProvider.js.map +0 -1
  108. package/lib/module/providers/ProviderFactory.js.map +0 -1
  109. package/lib/module/services/AudioInputService.js.map +0 -1
  110. package/lib/module/services/AudioOutputService.js.map +0 -1
  111. package/lib/module/services/KnowledgeBaseService.js.map +0 -1
  112. package/lib/module/services/VoiceService.js.map +0 -1
  113. package/lib/module/services/flags/FlagService.js.map +0 -1
  114. package/lib/module/services/telemetry/MobileAI.js.map +0 -1
  115. package/lib/module/services/telemetry/PiiScrubber.js.map +0 -1
  116. package/lib/module/services/telemetry/TelemetryService.js.map +0 -1
  117. package/lib/module/services/telemetry/TouchAutoCapture.js.map +0 -1
  118. package/lib/module/services/telemetry/device.js.map +0 -1
  119. package/lib/module/services/telemetry/deviceMetadata.js.map +0 -1
  120. package/lib/module/services/telemetry/index.js.map +0 -1
  121. package/lib/module/services/telemetry/types.js.map +0 -1
  122. package/lib/module/support/CSATSurvey.js.map +0 -1
  123. package/lib/module/support/EscalationEventSource.js.map +0 -1
  124. package/lib/module/support/EscalationSocket.js.map +0 -1
  125. package/lib/module/support/SupportChatModal.js.map +0 -1
  126. package/lib/module/support/SupportGreeting.js.map +0 -1
  127. package/lib/module/support/TicketStore.js.map +0 -1
  128. package/lib/module/support/escalateTool.js.map +0 -1
  129. package/lib/module/support/index.js.map +0 -1
  130. package/lib/module/support/supportPrompt.js.map +0 -1
  131. package/lib/module/support/types.js.map +0 -1
  132. package/lib/module/tools/datePickerTool.js.map +0 -1
  133. package/lib/module/tools/guideTool.js.map +0 -1
  134. package/lib/module/tools/index.js.map +0 -1
  135. package/lib/module/tools/keyboardTool.js.map +0 -1
  136. package/lib/module/tools/longPressTool.js.map +0 -1
  137. package/lib/module/tools/pickerTool.js.map +0 -1
  138. package/lib/module/tools/restoreTool.js.map +0 -1
  139. package/lib/module/tools/scrollTool.js.map +0 -1
  140. package/lib/module/tools/simplifyTool.js.map +0 -1
  141. package/lib/module/tools/sliderTool.js.map +0 -1
  142. package/lib/module/tools/tapTool.js.map +0 -1
  143. package/lib/module/tools/typeTool.js.map +0 -1
  144. package/lib/module/tools/types.js.map +0 -1
  145. package/lib/module/types/jsx.d.js.map +0 -1
  146. package/lib/module/utils/audioUtils.js.map +0 -1
  147. package/lib/module/utils/logger.js.map +0 -1
  148. package/lib/typescript/babel.config.d.ts.map +0 -1
  149. package/lib/typescript/bin/generate-map.d.cts.map +0 -1
  150. package/lib/typescript/eslint.config.d.mts.map +0 -1
  151. package/lib/typescript/generate-map.d.ts.map +0 -1
  152. package/lib/typescript/src/__cli_tmp__.d.ts.map +0 -1
  153. package/lib/typescript/src/components/AIAgent.d.ts.map +0 -1
  154. package/lib/typescript/src/components/AIZone.d.ts.map +0 -1
  155. package/lib/typescript/src/components/AgentChatBar.d.ts.map +0 -1
  156. package/lib/typescript/src/components/AgentErrorBoundary.d.ts.map +0 -1
  157. package/lib/typescript/src/components/AgentOverlay.d.ts.map +0 -1
  158. package/lib/typescript/src/components/DiscoveryTooltip.d.ts.map +0 -1
  159. package/lib/typescript/src/components/HighlightOverlay.d.ts.map +0 -1
  160. package/lib/typescript/src/components/Icons.d.ts.map +0 -1
  161. package/lib/typescript/src/components/ProactiveHint.d.ts.map +0 -1
  162. package/lib/typescript/src/components/cards/InfoCard.d.ts.map +0 -1
  163. package/lib/typescript/src/components/cards/ReviewSummary.d.ts.map +0 -1
  164. package/lib/typescript/src/config/endpoints.d.ts.map +0 -1
  165. package/lib/typescript/src/core/ActionRegistry.d.ts.map +0 -1
  166. package/lib/typescript/src/core/AgentRuntime.d.ts.map +0 -1
  167. package/lib/typescript/src/core/FiberTreeWalker.d.ts.map +0 -1
  168. package/lib/typescript/src/core/IdleDetector.d.ts.map +0 -1
  169. package/lib/typescript/src/core/MCPBridge.d.ts.map +0 -1
  170. package/lib/typescript/src/core/ScreenDehydrator.d.ts.map +0 -1
  171. package/lib/typescript/src/core/ZoneRegistry.d.ts.map +0 -1
  172. package/lib/typescript/src/core/systemPrompt.d.ts.map +0 -1
  173. package/lib/typescript/src/core/types.d.ts.map +0 -1
  174. package/lib/typescript/src/hooks/useAction.d.ts.map +0 -1
  175. package/lib/typescript/src/index.d.ts.map +0 -1
  176. package/lib/typescript/src/plugin/withAppIntents.d.ts.map +0 -1
  177. package/lib/typescript/src/providers/GeminiProvider.d.ts.map +0 -1
  178. package/lib/typescript/src/providers/OpenAIProvider.d.ts.map +0 -1
  179. package/lib/typescript/src/providers/ProviderFactory.d.ts.map +0 -1
  180. package/lib/typescript/src/services/AudioInputService.d.ts.map +0 -1
  181. package/lib/typescript/src/services/AudioOutputService.d.ts.map +0 -1
  182. package/lib/typescript/src/services/KnowledgeBaseService.d.ts.map +0 -1
  183. package/lib/typescript/src/services/VoiceService.d.ts.map +0 -1
  184. package/lib/typescript/src/services/flags/FlagService.d.ts.map +0 -1
  185. package/lib/typescript/src/services/telemetry/MobileAI.d.ts.map +0 -1
  186. package/lib/typescript/src/services/telemetry/PiiScrubber.d.ts.map +0 -1
  187. package/lib/typescript/src/services/telemetry/TelemetryService.d.ts.map +0 -1
  188. package/lib/typescript/src/services/telemetry/TouchAutoCapture.d.ts.map +0 -1
  189. package/lib/typescript/src/services/telemetry/device.d.ts.map +0 -1
  190. package/lib/typescript/src/services/telemetry/deviceMetadata.d.ts.map +0 -1
  191. package/lib/typescript/src/services/telemetry/index.d.ts.map +0 -1
  192. package/lib/typescript/src/services/telemetry/types.d.ts.map +0 -1
  193. package/lib/typescript/src/support/CSATSurvey.d.ts.map +0 -1
  194. package/lib/typescript/src/support/EscalationEventSource.d.ts.map +0 -1
  195. package/lib/typescript/src/support/EscalationSocket.d.ts.map +0 -1
  196. package/lib/typescript/src/support/SupportChatModal.d.ts.map +0 -1
  197. package/lib/typescript/src/support/SupportGreeting.d.ts.map +0 -1
  198. package/lib/typescript/src/support/TicketStore.d.ts.map +0 -1
  199. package/lib/typescript/src/support/escalateTool.d.ts.map +0 -1
  200. package/lib/typescript/src/support/index.d.ts.map +0 -1
  201. package/lib/typescript/src/support/supportPrompt.d.ts.map +0 -1
  202. package/lib/typescript/src/support/types.d.ts.map +0 -1
  203. package/lib/typescript/src/tools/datePickerTool.d.ts.map +0 -1
  204. package/lib/typescript/src/tools/guideTool.d.ts.map +0 -1
  205. package/lib/typescript/src/tools/index.d.ts.map +0 -1
  206. package/lib/typescript/src/tools/keyboardTool.d.ts.map +0 -1
  207. package/lib/typescript/src/tools/longPressTool.d.ts.map +0 -1
  208. package/lib/typescript/src/tools/pickerTool.d.ts.map +0 -1
  209. package/lib/typescript/src/tools/restoreTool.d.ts.map +0 -1
  210. package/lib/typescript/src/tools/scrollTool.d.ts.map +0 -1
  211. package/lib/typescript/src/tools/simplifyTool.d.ts.map +0 -1
  212. package/lib/typescript/src/tools/sliderTool.d.ts.map +0 -1
  213. package/lib/typescript/src/tools/tapTool.d.ts.map +0 -1
  214. package/lib/typescript/src/tools/typeTool.d.ts.map +0 -1
  215. package/lib/typescript/src/tools/types.d.ts.map +0 -1
  216. package/lib/typescript/src/utils/audioUtils.d.ts.map +0 -1
  217. package/lib/typescript/src/utils/logger.d.ts.map +0 -1
  218. package/src/__cli_tmp__.tsx +0 -9
  219. package/src/cli/analyzers/chain-analyzer.ts +0 -183
  220. package/src/cli/extractors/ai-extractor.ts +0 -6
  221. package/src/cli/extractors/ast-extractor.ts +0 -551
  222. package/src/cli/generate-intents.ts +0 -140
  223. package/src/cli/generate-map.ts +0 -121
  224. package/src/cli/generate-swift.ts +0 -116
  225. package/src/cli/scanners/expo-scanner.ts +0 -203
  226. package/src/cli/scanners/rn-scanner.ts +0 -445
  227. package/src/components/AIAgent.tsx +0 -1716
  228. package/src/components/AIZone.tsx +0 -147
  229. package/src/components/AgentChatBar.tsx +0 -1143
  230. package/src/components/AgentErrorBoundary.tsx +0 -78
  231. package/src/components/AgentOverlay.tsx +0 -73
  232. package/src/components/DiscoveryTooltip.tsx +0 -148
  233. package/src/components/HighlightOverlay.tsx +0 -136
  234. package/src/components/Icons.tsx +0 -253
  235. package/src/components/ProactiveHint.tsx +0 -145
  236. package/src/components/cards/InfoCard.tsx +0 -58
  237. package/src/components/cards/ReviewSummary.tsx +0 -76
  238. package/src/config/endpoints.ts +0 -22
  239. package/src/core/ActionRegistry.ts +0 -105
  240. package/src/core/AgentRuntime.ts +0 -1471
  241. package/src/core/FiberTreeWalker.ts +0 -930
  242. package/src/core/IdleDetector.ts +0 -72
  243. package/src/core/MCPBridge.ts +0 -163
  244. package/src/core/ScreenDehydrator.ts +0 -53
  245. package/src/core/ZoneRegistry.ts +0 -44
  246. package/src/core/systemPrompt.ts +0 -431
  247. package/src/core/types.ts +0 -521
  248. package/src/hooks/useAction.ts +0 -182
  249. package/src/index.ts +0 -83
  250. package/src/plugin/withAppIntents.ts +0 -98
  251. package/src/providers/GeminiProvider.ts +0 -357
  252. package/src/providers/OpenAIProvider.ts +0 -379
  253. package/src/providers/ProviderFactory.ts +0 -36
  254. package/src/services/AudioInputService.ts +0 -226
  255. package/src/services/AudioOutputService.ts +0 -236
  256. package/src/services/KnowledgeBaseService.ts +0 -156
  257. package/src/services/VoiceService.ts +0 -451
  258. package/src/services/flags/FlagService.ts +0 -137
  259. package/src/services/telemetry/MobileAI.ts +0 -66
  260. package/src/services/telemetry/PiiScrubber.ts +0 -17
  261. package/src/services/telemetry/TelemetryService.ts +0 -323
  262. package/src/services/telemetry/TouchAutoCapture.ts +0 -165
  263. package/src/services/telemetry/device.ts +0 -93
  264. package/src/services/telemetry/deviceMetadata.ts +0 -13
  265. package/src/services/telemetry/index.ts +0 -13
  266. package/src/services/telemetry/types.ts +0 -75
  267. package/src/support/CSATSurvey.tsx +0 -304
  268. package/src/support/EscalationEventSource.ts +0 -190
  269. package/src/support/EscalationSocket.ts +0 -152
  270. package/src/support/SupportChatModal.tsx +0 -563
  271. package/src/support/SupportGreeting.tsx +0 -161
  272. package/src/support/TicketStore.ts +0 -100
  273. package/src/support/escalateTool.ts +0 -174
  274. package/src/support/index.ts +0 -29
  275. package/src/support/supportPrompt.ts +0 -55
  276. package/src/support/types.ts +0 -155
  277. package/src/tools/datePickerTool.ts +0 -60
  278. package/src/tools/guideTool.ts +0 -76
  279. package/src/tools/index.ts +0 -20
  280. package/src/tools/keyboardTool.ts +0 -30
  281. package/src/tools/longPressTool.ts +0 -61
  282. package/src/tools/pickerTool.ts +0 -115
  283. package/src/tools/restoreTool.ts +0 -33
  284. package/src/tools/scrollTool.ts +0 -156
  285. package/src/tools/simplifyTool.ts +0 -33
  286. package/src/tools/sliderTool.ts +0 -65
  287. package/src/tools/tapTool.ts +0 -93
  288. package/src/tools/typeTool.ts +0 -113
  289. package/src/tools/types.ts +0 -58
  290. package/src/types/jsx.d.ts +0 -20
  291. package/src/utils/audioUtils.ts +0 -54
  292. package/src/utils/logger.ts +0 -38
@@ -1,551 +0,0 @@
1
- /**
2
- * AST-based content extractor for React Native screen files.
3
- * Parses JSX to extract interactive elements, text labels, and navigation links.
4
- *
5
- * Supports comprehensive component detection via category-based classification:
6
- * - Inputs: TextInput, *Input
7
- * - Toggles: Switch, *Toggle
8
- * - Buttons: Button, Pressable, TouchableOpacity, *Button, *Btn
9
- * - Images: Image, FastImage, *Avatar, *Image
10
- * - Lists: FlatList, SectionList, ScrollView, VirtualizedList
11
- * - Modals: Modal, *Modal, *Sheet, *Dialog, *BottomSheet
12
- * - Icons: Ionicons, MaterialIcon, FontAwesome, *_Dark, *_Light (skipped — noise)
13
- * - Custom: Any PascalCase component not matched above
14
- *
15
- * Also captures navigation links from:
16
- * - Expo Router: <Link href="..." />, router.navigate/push/replace
17
- * - React Navigation: navigation.navigate/push/replace, <Link screen="..." />
18
- */
19
-
20
- import { parse } from '@babel/parser';
21
- import * as _traverse from '@babel/traverse';
22
- import * as t from '@babel/types';
23
-
24
- const traverse = (_traverse as any).default || _traverse;
25
-
26
- export interface ExtractedContent {
27
- elements: string[];
28
- navigationLinks: string[];
29
- }
30
-
31
- // ─── Component classification ────────────────────────────────
32
-
33
- /** RN layout primitives we always skip (they carry no semantic meaning) */
34
- const LAYOUT_PRIMITIVES = new Set([
35
- 'View', 'SafeAreaView', 'Fragment', 'KeyboardAvoidingView',
36
- 'StatusBar', 'LinearGradient', 'Animated',
37
- ]);
38
-
39
- /** Exact-match icon component names */
40
- const ICON_EXACT = new Set([
41
- 'Ionicons', 'MaterialIcon', 'MaterialCommunityIcons',
42
- 'FontAwesome', 'FontAwesome5', 'Feather', 'Entypo',
43
- 'AntDesign', 'EvilIcons', 'Foundation', 'Octicons',
44
- 'SimpleLineIcons', 'Zocial', 'MaterialIcons',
45
- ]);
46
-
47
- /** Exact-match pressable component names */
48
- const PRESSABLE_EXACT = new Set([
49
- 'Pressable', 'TouchableOpacity', 'TouchableHighlight',
50
- 'TouchableWithoutFeedback', 'TouchableNativeFeedback',
51
- ]);
52
-
53
- /** Exact-match list component names */
54
- const LIST_EXACT = new Set([
55
- 'FlatList', 'SectionList', 'VirtualizedList',
56
- ]);
57
-
58
- /** Exact-match image component names */
59
- const IMAGE_EXACT = new Set([
60
- 'Image', 'FastImage', 'ImageBackground',
61
- ]);
62
-
63
- type ComponentCategory =
64
- | 'input' | 'toggle' | 'button' | 'image'
65
- | 'list' | 'modal' | 'icon' | 'navigation'
66
- | 'custom' | 'skip';
67
-
68
- /**
69
- * Classify a JSX element name into a semantic category.
70
- * Order matters: more specific checks first, custom catch-all last.
71
- */
72
- function classifyComponent(name: string): ComponentCategory {
73
- // Skip layout primitives
74
- if (LAYOUT_PRIMITIVES.has(name)) return 'skip';
75
- // Skip RN text wrappers (we extract text content, not the wrapper)
76
- if (name === 'Text' || name === 'ScrollView') return 'skip';
77
-
78
- // Exact matches
79
- if (name === 'TextInput') return 'input';
80
- if (name === 'Switch') return 'toggle';
81
- if (name === 'Button') return 'button';
82
- if (name === 'Modal') return 'modal';
83
- if (name === 'Link' || name === 'Redirect') return 'navigation';
84
- if (PRESSABLE_EXACT.has(name)) return 'button';
85
- if (LIST_EXACT.has(name)) return 'list';
86
- if (IMAGE_EXACT.has(name)) return 'image';
87
- if (ICON_EXACT.has(name)) return 'icon';
88
-
89
- // Pattern matches (suffix/contains)
90
- if (name.endsWith('Input') || name.endsWith('Field')) return 'input';
91
- if (name.endsWith('Toggle')) return 'toggle';
92
- if (name.endsWith('Button') || name.endsWith('Btn')) return 'button';
93
- if (name.endsWith('Image') || name.endsWith('Avatar') || name.endsWith('Photo')) return 'image';
94
- if (name.endsWith('List')) return 'list';
95
- if (name.endsWith('Modal') || name.endsWith('Sheet') || name.endsWith('Dialog') || name.includes('BottomSheet')) return 'modal';
96
-
97
- // Icon patterns: ends with Icon, or SVG asset convention (*_Dark, *_Light)
98
- if (name.endsWith('Icon') || name.endsWith('_Dark') || name.endsWith('_Light')) return 'icon';
99
-
100
- // If it's PascalCase (starts with uppercase) and not a known primitive, it's a custom component
101
- if (name[0] === name[0]?.toUpperCase() && name[0] !== name[0]?.toLowerCase()) {
102
- return 'custom';
103
- }
104
-
105
- return 'skip';
106
- }
107
-
108
- // ─── Core extraction ──────────────────────────────────────────
109
-
110
- /**
111
- * Extract interactive elements and navigation links from a screen file's source code.
112
- */
113
- export function extractContentFromAST(sourceCode: string, filePath: string): ExtractedContent {
114
- const elements: string[] = [];
115
- const navigationLinks: string[] = [];
116
-
117
- let ast: ReturnType<typeof parse>;
118
- try {
119
- ast = parse(sourceCode, {
120
- sourceType: 'module',
121
- plugins: ['jsx', 'typescript', 'decorators-legacy'],
122
- });
123
- } catch {
124
- console.warn(`[generate-map] Failed to parse ${filePath}, skipping AST extraction`);
125
- return { elements, navigationLinks };
126
- }
127
-
128
- traverse(ast, {
129
- JSXOpeningElement(path: any) {
130
- const nameNode = path.node.name;
131
- const elementName = getJSXElementName(nameNode);
132
- if (!elementName) return;
133
-
134
- const category = classifyComponent(elementName);
135
-
136
- switch (category) {
137
- case 'input': {
138
- const placeholder = getStringAttribute(path.node, 'placeholder');
139
- elements.push(placeholder ? `${placeholder} (text-input)` : 'text input (text-input)');
140
- break;
141
- }
142
-
143
- case 'toggle': {
144
- const label = findSiblingTextLabel(path);
145
- elements.push(label ? `${label} (switch)` : 'toggle (switch)');
146
- break;
147
- }
148
-
149
- case 'button': {
150
- // RN built-in Button uses `title` prop
151
- if (elementName === 'Button') {
152
- const title = getStringAttribute(path.node, 'title');
153
- elements.push(title ? `${title} (button)` : 'button (button)');
154
-
155
- // React Navigation: <Button screen="Details" />
156
- const screenTarget = getStringAttribute(path.node, 'screen');
157
- if (screenTarget) navigationLinks.push(screenTarget);
158
- } else {
159
- // Pressable/TouchableOpacity — find text label in children
160
- const buttonLabel = findChildTextContentRecursive(path);
161
- if (buttonLabel) {
162
- elements.push(`${buttonLabel} (button)`);
163
- }
164
- }
165
- break;
166
- }
167
-
168
- case 'image': {
169
- const alt = getStringAttribute(path.node, 'alt')
170
- || getStringAttribute(path.node, 'accessibilityLabel');
171
- elements.push(alt ? `${alt} (image)` : `${elementName} (image)`);
172
- break;
173
- }
174
-
175
- case 'list': {
176
- elements.push(`${elementName} (list)`);
177
- break;
178
- }
179
-
180
- case 'modal': {
181
- const title = getStringAttribute(path.node, 'title');
182
- elements.push(title ? `${title} (modal)` : `${elementName} (modal)`);
183
- break;
184
- }
185
-
186
- case 'navigation': {
187
- // Expo Router: <Link href="..." /> or <Redirect href="..." />
188
- const target = extractRouteFromAttribute(path.node, 'href');
189
- if (target) navigationLinks.push(target);
190
- // React Navigation: <Link screen="Details" />
191
- const screenTarget = getStringAttribute(path.node, 'screen');
192
- if (screenTarget) navigationLinks.push(screenTarget);
193
- break;
194
- }
195
-
196
- case 'custom': {
197
- // Extract a meaningful label from common props
198
- const label = getStringAttribute(path.node, 'title')
199
- || getStringAttribute(path.node, 'label')
200
- || getStringAttribute(path.node, 'placeholder')
201
- || getStringAttribute(path.node, 'text');
202
- elements.push(label ? `${label} (${elementName})` : `${elementName} (component)`);
203
- break;
204
- }
205
-
206
- case 'icon':
207
- case 'skip':
208
- // Intentionally ignored
209
- break;
210
- }
211
- },
212
-
213
- // ─── Navigation link extraction from imperative calls ─────
214
- CallExpression(path: any) {
215
- const target = extractRouteFromCall(path.node);
216
- if (Array.isArray(target)) navigationLinks.push(...target);
217
- else if (target) navigationLinks.push(target);
218
- },
219
- });
220
-
221
- return {
222
- elements: deduplicateAndPrioritize(elements),
223
- navigationLinks: [...new Set(navigationLinks)],
224
- };
225
- }
226
-
227
- /**
228
- * Build a description string from extracted content.
229
- */
230
- export function buildDescription(extracted: ExtractedContent): string {
231
- const parts: string[] = [];
232
- if (extracted.elements.length > 0) {
233
- parts.push(extracted.elements.join(', '));
234
- }
235
- return parts.join('. ') || 'Screen content';
236
- }
237
-
238
- // ─── Deduplication & prioritization ──────────────────────────
239
-
240
- /** Priority order for element categories (lower = higher priority) */
241
- const CATEGORY_PRIORITY: Record<string, number> = {
242
- 'text-input': 0,
243
- 'switch': 1,
244
- 'button': 2,
245
- 'modal': 3,
246
- 'list': 4,
247
- 'image': 5,
248
- 'component': 6,
249
- };
250
-
251
- const MAX_ELEMENTS = 8;
252
-
253
- function deduplicateAndPrioritize(elements: string[]): string[] {
254
- // Deduplicate
255
- const unique = [...new Set(elements)];
256
-
257
- // Sort by category priority (interactive first)
258
- unique.sort((a, b) => {
259
- const catA = extractCategoryTag(a);
260
- const catB = extractCategoryTag(b);
261
- return (CATEGORY_PRIORITY[catA] ?? 99) - (CATEGORY_PRIORITY[catB] ?? 99);
262
- });
263
-
264
- // Cap to avoid overly long descriptions
265
- return unique.slice(0, MAX_ELEMENTS);
266
- }
267
-
268
- function extractCategoryTag(element: string): string {
269
- const match = element.match(/\(([^)]+)\)$/);
270
- return match ? match[1]! : 'unknown';
271
- }
272
-
273
- // ─── Route extraction helpers ─────────────────────────────────
274
-
275
- /**
276
- * Extract a route target from a JSX attribute that can be:
277
- * - String literal: href="/path"
278
- * - Template literal: href={`/path/${id}`}
279
- * - Object with pathname: href={{ pathname: '/path/[id]', params: {...} }}
280
- */
281
- function extractRouteFromAttribute(node: t.JSXOpeningElement, attrName: string): string | null {
282
- for (const attr of node.attributes) {
283
- if (!t.isJSXAttribute(attr) || !t.isJSXIdentifier(attr.name) || attr.name.name !== attrName) {
284
- continue;
285
- }
286
-
287
- // href="/about" — plain string
288
- if (t.isStringLiteral(attr.value)) {
289
- return attr.value.value;
290
- }
291
-
292
- // href={expression}
293
- if (t.isJSXExpressionContainer(attr.value)) {
294
- return extractRouteFromExpression(attr.value.expression);
295
- }
296
- }
297
- return null;
298
- }
299
-
300
- /**
301
- * Extract a route target from an imperative call expression:
302
- * router.push('/path') | router.navigate({...})
303
- * navigation.navigate('Screen') | navigation.push('Screen', params)
304
- *
305
- * Methods matched: navigate, push, replace
306
- */
307
- function extractRouteFromCall(node: t.CallExpression): string | string[] | null {
308
- const callee = node.callee;
309
-
310
- if (t.isMemberExpression(callee) && t.isIdentifier(callee.property)) {
311
- const method = callee.property.name;
312
-
313
- // navigation.navigate/push/replace('Screen')
314
- if (['navigate', 'push', 'replace'].includes(method)) {
315
- const firstArg = node.arguments[0];
316
- if (!firstArg) return null;
317
- return extractRouteFromExpression(firstArg);
318
- }
319
-
320
- // navigation.reset({ routes: [{ name: 'Screen' }] })
321
- if (method === 'reset') {
322
- const firstArg = node.arguments[0];
323
- if (t.isObjectExpression(firstArg)) {
324
- for (const prop of firstArg.properties) {
325
- if (
326
- t.isObjectProperty(prop) &&
327
- t.isIdentifier(prop.key) &&
328
- prop.key.name === 'routes' &&
329
- t.isArrayExpression(prop.value)
330
- ) {
331
- const routes: string[] = [];
332
- for (const el of prop.value.elements) {
333
- if (t.isObjectExpression(el)) {
334
- for (const rp of el.properties) {
335
- if (t.isObjectProperty(rp) && t.isIdentifier(rp.key) && rp.key.name === 'name') {
336
- const route = extractRouteFromExpression(rp.value);
337
- if (route) routes.push(route);
338
- }
339
- }
340
- }
341
- }
342
- return routes.length > 0 ? routes : null;
343
- }
344
- }
345
- }
346
- }
347
- }
348
-
349
- return null;
350
- }
351
-
352
- /**
353
- * Extract a route string from any expression type:
354
- * - StringLiteral: '/path'
355
- * - TemplateLiteral: `/path/${id}` → '/path/[param]'
356
- * - ObjectExpression with pathname: { pathname: '/path/[id]' }
357
- */
358
- function extractRouteFromExpression(expr: any): string | null {
359
- if (!expr) return null;
360
-
361
- // String literal: '/about' or 'Details'
362
- if (t.isStringLiteral(expr)) {
363
- return expr.value;
364
- }
365
-
366
- // Template literal: `/item-reviews/${id}` → '/item-reviews/[param]'
367
- if (t.isTemplateLiteral(expr) && expr.quasis.length > 0) {
368
- return expr.quasis.map((q: any) => q.value.raw).join('[param]');
369
- }
370
-
371
- // MemberExpression: StackNav.Register → 'Register'
372
- if (t.isMemberExpression(expr) && t.isIdentifier(expr.property)) {
373
- return expr.property.name;
374
- }
375
-
376
- // Identifier: navigate(screenName) → '{screenName}'
377
- if (t.isIdentifier(expr)) {
378
- return `{${expr.name}}`;
379
- }
380
-
381
- // Object with pathname: { pathname: '/user/[id]', params: {...} }
382
- if (t.isObjectExpression(expr)) {
383
- for (const prop of expr.properties) {
384
- if (
385
- t.isObjectProperty(prop) &&
386
- t.isIdentifier(prop.key) &&
387
- prop.key.name === 'pathname'
388
- ) {
389
- if (t.isStringLiteral(prop.value)) {
390
- return prop.value.value;
391
- }
392
- if (t.isTemplateLiteral(prop.value) && prop.value.quasis.length > 0) {
393
- return prop.value.quasis.map((q: any) => q.value.raw).join('[param]');
394
- }
395
- }
396
- }
397
- }
398
-
399
- return null;
400
- }
401
-
402
- // ─── JSX helpers ──────────────────────────────────────────────
403
-
404
- function getJSXElementName(nameNode: t.JSXOpeningElement['name']): string {
405
- if (t.isJSXIdentifier(nameNode)) return nameNode.name;
406
- if (t.isJSXMemberExpression(nameNode) && t.isJSXIdentifier(nameNode.property)) {
407
- return nameNode.property.name;
408
- }
409
- return '';
410
- }
411
-
412
- function getStringAttribute(node: t.JSXOpeningElement, attrName: string): string | null {
413
- for (const attr of node.attributes) {
414
- if (t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name) && attr.name.name === attrName) {
415
- if (t.isStringLiteral(attr.value)) return attr.value.value;
416
- if (t.isJSXExpressionContainer(attr.value)) {
417
- return extractSemanticHint(attr.value.expression);
418
- }
419
- }
420
- }
421
- return null;
422
- }
423
-
424
- /**
425
- * Recursively unwrap a dynamic JS expression to extract a semantic label.
426
- *
427
- * Handles:
428
- * StringLiteral → "Full Name"
429
- * MemberExpression → strings.auth.fullName → "fullName"
430
- * ConditionalExpression → cond ? a : b → try a, fallback b
431
- * LogicalExpression (||) → a || b → try a, fallback b
432
- * TemplateLiteral → `Hello ${name}` → "Hello ..."
433
- * CallExpression → t('loginBtn') → "loginBtn"
434
- */
435
- function extractSemanticHint(node: any, depth: number = 0): string | null {
436
- if (depth > 5 || !node) return null;
437
-
438
- // Direct string literal
439
- if (t.isStringLiteral(node)) return node.value;
440
-
441
- // Numeric literal — sometimes used as placeholder (e.g. placeholder={0})
442
- if (t.isNumericLiteral(node)) return String(node.value);
443
-
444
- // MemberExpression: strings.fullName, i18n.auth.loginButton → deepest property
445
- if (t.isMemberExpression(node) && t.isIdentifier(node.property)) {
446
- return camelToWords(node.property.name);
447
- }
448
-
449
- // Ternary: condition ? consequent : alternate — try consequent first
450
- if (t.isConditionalExpression(node)) {
451
- return extractSemanticHint(node.consequent, depth + 1)
452
- || extractSemanticHint(node.alternate, depth + 1);
453
- }
454
-
455
- // Logical OR: a || b — try left first
456
- if (t.isLogicalExpression(node) && node.operator === '||') {
457
- return extractSemanticHint(node.left, depth + 1)
458
- || extractSemanticHint(node.right, depth + 1);
459
- }
460
-
461
- // Logical AND: a && b — the right side is the "real" value
462
- if (t.isLogicalExpression(node) && node.operator === '&&') {
463
- return extractSemanticHint(node.right, depth + 1);
464
- }
465
-
466
- // Template literal: `Hello ${name}` → "Hello ..."
467
- if (t.isTemplateLiteral(node) && node.quasis.length > 0) {
468
- const staticParts = node.quasis.map((q: any) => q.value.raw).filter(Boolean);
469
- if (staticParts.length > 0) return staticParts.join('...').trim() || null;
470
- }
471
-
472
- // Call expression: t('key'), i18n.t('loginBtn'), translate('email')
473
- if (t.isCallExpression(node) && node.arguments.length > 0) {
474
- const firstArg = node.arguments[0];
475
- if (t.isStringLiteral(firstArg)) return camelToWords(firstArg.value);
476
- }
477
-
478
- return null;
479
- }
480
-
481
- /**
482
- * Convert camelCase/PascalCase to readable words: "fullName" → "full Name", "loginButton" → "login Button"
483
- * Keeps it simple — just inserts spaces before uppercase letters.
484
- */
485
- function camelToWords(str: string): string {
486
- if (!str) return str;
487
- // Don't transform if it's already sentence-like or a single word
488
- if (str.includes(' ') || str.includes('_') || str.includes('-')) return str;
489
- return str.replace(/([a-z])([A-Z])/g, '$1 $2').toLowerCase();
490
- }
491
-
492
- /**
493
- * Find the nearest sibling <Text> element to determine a label for a Switch/toggle.
494
- */
495
- function findSiblingTextLabel(switchPath: any): string | null {
496
- const parent = switchPath.parentPath?.parentPath; // Go up to the View containing the Switch
497
- if (!parent?.node || !t.isJSXElement(parent.node)) return null;
498
-
499
- for (const child of parent.node.children) {
500
- if (t.isJSXElement(child)) {
501
- const text = extractTextRecursive(child);
502
- if (text) return text;
503
- }
504
- }
505
- return null;
506
- }
507
-
508
- /**
509
- * Recursively find text content inside a Pressable/TouchableOpacity.
510
- * Searches through nested Views and custom text-like components.
511
- */
512
- function findChildTextContentRecursive(pressablePath: any): string | null {
513
- const jsxElement = pressablePath.parent;
514
- if (!t.isJSXElement(jsxElement)) return null;
515
- return extractTextRecursive(jsxElement);
516
- }
517
-
518
- /**
519
- * Recursively extract human-readable text from a JSX tree.
520
- * Stops at the first meaningful text found to avoid noise.
521
- */
522
- function extractTextRecursive(element: t.JSXElement, depth: number = 0): string | null {
523
- if (depth > 4) return null; // Safety: don't recurse too deep
524
-
525
- for (const child of element.children) {
526
- // Direct text node
527
- if (t.isJSXText(child)) {
528
- const text = child.value.trim();
529
- if (text) return text;
530
- }
531
-
532
- // String or dynamic expression: {"Sign In"}, {strings.editProfile}, {t('key')}
533
- if (t.isJSXExpressionContainer(child) && !t.isJSXEmptyExpression(child.expression)) {
534
- const hint = extractSemanticHint(child.expression);
535
- if (hint) return hint;
536
- }
537
-
538
- // Recurse into child JSX elements (e.g. <View><Text>Sign In</Text></View>)
539
- if (t.isJSXElement(child)) {
540
- const childName = getJSXElementName(child.openingElement.name);
541
- // Skip icons inside buttons
542
- if (ICON_EXACT.has(childName) || childName.endsWith('Icon') ||
543
- childName.endsWith('_Dark') || childName.endsWith('_Light')) {
544
- continue;
545
- }
546
- const text = extractTextRecursive(child, depth + 1);
547
- if (text) return text;
548
- }
549
- }
550
- return null;
551
- }
@@ -1,140 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import * as fs from 'fs';
4
- import * as path from 'path';
5
- import { parse } from '@babel/parser';
6
- import traverse from '@babel/traverse';
7
- // @ts-ignore - no types installed
8
- import glob from 'glob';
9
-
10
- // Define the schema format for our extracted intents
11
- export interface ExtractedIntent {
12
- name: string;
13
- description: string;
14
- parameters: Record<string, any>;
15
- sourceFile: string;
16
- }
17
-
18
- /**
19
- * Validates and statically extracts `useAction` and `registerAction` definitions
20
- * from a target directory by parsing the AST of all TS/JS files.
21
- */
22
- export function extractIntentsFromAST(sourceDir: string): ExtractedIntent[] {
23
- const files = glob.sync(`${sourceDir}/**/*.{ts,tsx,js,jsx}`, {
24
- ignore: ['**/node_modules/**', '**/*.d.ts', '**/__tests__/**']
25
- });
26
-
27
- const intents: ExtractedIntent[] = [];
28
-
29
- for (const file of files) {
30
- const code = fs.readFileSync(file, 'utf-8');
31
-
32
- // Quick heuristic: ignore files that don't even talk about useAction
33
- if (!code.includes('useAction') && !code.includes('registerAction')) {
34
- continue;
35
- }
36
-
37
- try {
38
- const ast = parse(code, {
39
- sourceType: 'module',
40
- plugins: ['jsx', 'typescript'],
41
- });
42
-
43
- traverse(ast, {
44
- CallExpression(pathNode: any) {
45
- const callee = pathNode.node.callee;
46
- if (
47
- callee.type === 'Identifier' &&
48
- (callee.name === 'useAction' || callee.name === 'registerAction')
49
- ) {
50
- const args = pathNode.node.arguments;
51
- if (args.length >= 2) {
52
- const nameArg = args[0];
53
- const descArg = args[1];
54
- const schemaArg = args[2];
55
-
56
- // We only process if name and desc are static string literals
57
- if (nameArg.type === 'StringLiteral' && descArg.type === 'StringLiteral') {
58
- const name = nameArg.value;
59
- const description = descArg.value;
60
- let parameters: Record<string, any> = {};
61
-
62
- // Parse schema object if provided
63
- if (schemaArg && schemaArg.type === 'ObjectExpression') {
64
- parameters = parseObjectExpression(schemaArg);
65
- }
66
-
67
- intents.push({
68
- name,
69
- description,
70
- parameters,
71
- sourceFile: path.relative(process.cwd(), file)
72
- });
73
- }
74
- }
75
- }
76
- }
77
- });
78
- } catch (error: any) {
79
- console.warn(`[WARN] Skipping file ${file} due to parse error: ${error.message}`);
80
- }
81
- }
82
-
83
- return intents;
84
- }
85
-
86
- /**
87
- * Naively converts an AST ObjectExpression back to a JS Runtime object.
88
- * Assumes the schema is statically defined (no variables/computed keys).
89
- */
90
- function parseObjectExpression(node: any): any {
91
- const result: any = {};
92
- for (const prop of node.properties) {
93
- if (prop.type === 'ObjectProperty' && prop.key.type === 'Identifier') {
94
- const key = prop.key.name;
95
-
96
- if (prop.value.type === 'StringLiteral') {
97
- result[key] = prop.value.value;
98
- } else if (prop.value.type === 'NumericLiteral') {
99
- result[key] = prop.value.value;
100
- } else if (prop.value.type === 'BooleanLiteral') {
101
- result[key] = prop.value.value;
102
- } else if (prop.value.type === 'ObjectExpression') {
103
- result[key] = parseObjectExpression(prop.value);
104
- } else if (prop.value.type === 'ArrayExpression') {
105
- result[key] = prop.value.elements.map((el: any) => {
106
- if (el.type === 'StringLiteral') return el.value;
107
- if (el.type === 'NumericLiteral') return el.value;
108
- return null;
109
- }).filter(Boolean);
110
- }
111
- }
112
- }
113
- return result;
114
- }
115
-
116
- async function main() {
117
- const sourceDir = process.argv[2] || 'src';
118
- const outPath = process.argv[3] || 'intent-manifest.json';
119
-
120
- const absoluteSource = path.resolve(process.cwd(), sourceDir);
121
- const absoluteOut = path.resolve(process.cwd(), outPath);
122
-
123
- console.log(`Scanning ${absoluteSource} for AI Actions...`);
124
-
125
- const intents = extractIntentsFromAST(absoluteSource);
126
-
127
- console.log(`Found ${intents.length} actions.`);
128
- intents.forEach(i => console.log(` - ${i.name} (from ${i.sourceFile})`));
129
-
130
- fs.writeFileSync(absoluteOut, JSON.stringify(intents, null, 2));
131
- console.log(`\n✅ Wrote intent manifest to ${outPath}`);
132
- }
133
-
134
- // Run if called directly
135
- if (require.main === module) {
136
- main().catch(err => {
137
- console.error(err);
138
- process.exit(1);
139
- });
140
- }