@mobileai/react-native 0.9.16 → 0.9.18

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 (214) hide show
  1. package/README.md +2 -2
  2. package/package.json +5 -8
  3. package/lib/module/__cli_tmp__.js.map +0 -1
  4. package/lib/module/components/AIAgent.js.map +0 -1
  5. package/lib/module/components/AIZone.js.map +0 -1
  6. package/lib/module/components/AgentChatBar.js.map +0 -1
  7. package/lib/module/components/AgentErrorBoundary.js.map +0 -1
  8. package/lib/module/components/AgentOverlay.js.map +0 -1
  9. package/lib/module/components/DiscoveryTooltip.js.map +0 -1
  10. package/lib/module/components/HighlightOverlay.js.map +0 -1
  11. package/lib/module/components/Icons.js.map +0 -1
  12. package/lib/module/components/ProactiveHint.js.map +0 -1
  13. package/lib/module/components/cards/InfoCard.js.map +0 -1
  14. package/lib/module/components/cards/ReviewSummary.js.map +0 -1
  15. package/lib/module/config/endpoints.js.map +0 -1
  16. package/lib/module/core/ActionRegistry.js.map +0 -1
  17. package/lib/module/core/AgentRuntime.js.map +0 -1
  18. package/lib/module/core/FiberTreeWalker.js.map +0 -1
  19. package/lib/module/core/IdleDetector.js.map +0 -1
  20. package/lib/module/core/MCPBridge.js.map +0 -1
  21. package/lib/module/core/ScreenDehydrator.js.map +0 -1
  22. package/lib/module/core/ZoneRegistry.js.map +0 -1
  23. package/lib/module/core/systemPrompt.js.map +0 -1
  24. package/lib/module/core/types.js.map +0 -1
  25. package/lib/module/hooks/useAction.js.map +0 -1
  26. package/lib/module/index.js.map +0 -1
  27. package/lib/module/plugin/withAppIntents.js.map +0 -1
  28. package/lib/module/providers/GeminiProvider.js.map +0 -1
  29. package/lib/module/providers/OpenAIProvider.js.map +0 -1
  30. package/lib/module/providers/ProviderFactory.js.map +0 -1
  31. package/lib/module/services/AudioInputService.js.map +0 -1
  32. package/lib/module/services/AudioOutputService.js.map +0 -1
  33. package/lib/module/services/KnowledgeBaseService.js.map +0 -1
  34. package/lib/module/services/VoiceService.js.map +0 -1
  35. package/lib/module/services/flags/FlagService.js.map +0 -1
  36. package/lib/module/services/telemetry/MobileAI.js.map +0 -1
  37. package/lib/module/services/telemetry/PiiScrubber.js.map +0 -1
  38. package/lib/module/services/telemetry/TelemetryService.js.map +0 -1
  39. package/lib/module/services/telemetry/TouchAutoCapture.js.map +0 -1
  40. package/lib/module/services/telemetry/device.js.map +0 -1
  41. package/lib/module/services/telemetry/deviceMetadata.js.map +0 -1
  42. package/lib/module/services/telemetry/index.js.map +0 -1
  43. package/lib/module/services/telemetry/types.js.map +0 -1
  44. package/lib/module/support/CSATSurvey.js.map +0 -1
  45. package/lib/module/support/EscalationEventSource.js.map +0 -1
  46. package/lib/module/support/EscalationSocket.js.map +0 -1
  47. package/lib/module/support/SupportChatModal.js.map +0 -1
  48. package/lib/module/support/SupportGreeting.js.map +0 -1
  49. package/lib/module/support/TicketStore.js.map +0 -1
  50. package/lib/module/support/escalateTool.js.map +0 -1
  51. package/lib/module/support/index.js.map +0 -1
  52. package/lib/module/support/supportPrompt.js.map +0 -1
  53. package/lib/module/support/types.js.map +0 -1
  54. package/lib/module/tools/datePickerTool.js.map +0 -1
  55. package/lib/module/tools/guideTool.js.map +0 -1
  56. package/lib/module/tools/index.js.map +0 -1
  57. package/lib/module/tools/keyboardTool.js.map +0 -1
  58. package/lib/module/tools/longPressTool.js.map +0 -1
  59. package/lib/module/tools/pickerTool.js.map +0 -1
  60. package/lib/module/tools/restoreTool.js.map +0 -1
  61. package/lib/module/tools/scrollTool.js.map +0 -1
  62. package/lib/module/tools/simplifyTool.js.map +0 -1
  63. package/lib/module/tools/sliderTool.js.map +0 -1
  64. package/lib/module/tools/tapTool.js.map +0 -1
  65. package/lib/module/tools/typeTool.js.map +0 -1
  66. package/lib/module/tools/types.js.map +0 -1
  67. package/lib/module/types/jsx.d.js.map +0 -1
  68. package/lib/module/utils/audioUtils.js.map +0 -1
  69. package/lib/module/utils/logger.js.map +0 -1
  70. package/lib/typescript/babel.config.d.ts.map +0 -1
  71. package/lib/typescript/bin/generate-map.d.cts.map +0 -1
  72. package/lib/typescript/eslint.config.d.mts.map +0 -1
  73. package/lib/typescript/generate-map.d.ts.map +0 -1
  74. package/lib/typescript/src/__cli_tmp__.d.ts.map +0 -1
  75. package/lib/typescript/src/components/AIAgent.d.ts.map +0 -1
  76. package/lib/typescript/src/components/AIZone.d.ts.map +0 -1
  77. package/lib/typescript/src/components/AgentChatBar.d.ts.map +0 -1
  78. package/lib/typescript/src/components/AgentErrorBoundary.d.ts.map +0 -1
  79. package/lib/typescript/src/components/AgentOverlay.d.ts.map +0 -1
  80. package/lib/typescript/src/components/DiscoveryTooltip.d.ts.map +0 -1
  81. package/lib/typescript/src/components/HighlightOverlay.d.ts.map +0 -1
  82. package/lib/typescript/src/components/Icons.d.ts.map +0 -1
  83. package/lib/typescript/src/components/ProactiveHint.d.ts.map +0 -1
  84. package/lib/typescript/src/components/cards/InfoCard.d.ts.map +0 -1
  85. package/lib/typescript/src/components/cards/ReviewSummary.d.ts.map +0 -1
  86. package/lib/typescript/src/config/endpoints.d.ts.map +0 -1
  87. package/lib/typescript/src/core/ActionRegistry.d.ts.map +0 -1
  88. package/lib/typescript/src/core/AgentRuntime.d.ts.map +0 -1
  89. package/lib/typescript/src/core/FiberTreeWalker.d.ts.map +0 -1
  90. package/lib/typescript/src/core/IdleDetector.d.ts.map +0 -1
  91. package/lib/typescript/src/core/MCPBridge.d.ts.map +0 -1
  92. package/lib/typescript/src/core/ScreenDehydrator.d.ts.map +0 -1
  93. package/lib/typescript/src/core/ZoneRegistry.d.ts.map +0 -1
  94. package/lib/typescript/src/core/systemPrompt.d.ts.map +0 -1
  95. package/lib/typescript/src/core/types.d.ts.map +0 -1
  96. package/lib/typescript/src/hooks/useAction.d.ts.map +0 -1
  97. package/lib/typescript/src/index.d.ts.map +0 -1
  98. package/lib/typescript/src/plugin/withAppIntents.d.ts.map +0 -1
  99. package/lib/typescript/src/providers/GeminiProvider.d.ts.map +0 -1
  100. package/lib/typescript/src/providers/OpenAIProvider.d.ts.map +0 -1
  101. package/lib/typescript/src/providers/ProviderFactory.d.ts.map +0 -1
  102. package/lib/typescript/src/services/AudioInputService.d.ts.map +0 -1
  103. package/lib/typescript/src/services/AudioOutputService.d.ts.map +0 -1
  104. package/lib/typescript/src/services/KnowledgeBaseService.d.ts.map +0 -1
  105. package/lib/typescript/src/services/VoiceService.d.ts.map +0 -1
  106. package/lib/typescript/src/services/flags/FlagService.d.ts.map +0 -1
  107. package/lib/typescript/src/services/telemetry/MobileAI.d.ts.map +0 -1
  108. package/lib/typescript/src/services/telemetry/PiiScrubber.d.ts.map +0 -1
  109. package/lib/typescript/src/services/telemetry/TelemetryService.d.ts.map +0 -1
  110. package/lib/typescript/src/services/telemetry/TouchAutoCapture.d.ts.map +0 -1
  111. package/lib/typescript/src/services/telemetry/device.d.ts.map +0 -1
  112. package/lib/typescript/src/services/telemetry/deviceMetadata.d.ts.map +0 -1
  113. package/lib/typescript/src/services/telemetry/index.d.ts.map +0 -1
  114. package/lib/typescript/src/services/telemetry/types.d.ts.map +0 -1
  115. package/lib/typescript/src/support/CSATSurvey.d.ts.map +0 -1
  116. package/lib/typescript/src/support/EscalationEventSource.d.ts.map +0 -1
  117. package/lib/typescript/src/support/EscalationSocket.d.ts.map +0 -1
  118. package/lib/typescript/src/support/SupportChatModal.d.ts.map +0 -1
  119. package/lib/typescript/src/support/SupportGreeting.d.ts.map +0 -1
  120. package/lib/typescript/src/support/TicketStore.d.ts.map +0 -1
  121. package/lib/typescript/src/support/escalateTool.d.ts.map +0 -1
  122. package/lib/typescript/src/support/index.d.ts.map +0 -1
  123. package/lib/typescript/src/support/supportPrompt.d.ts.map +0 -1
  124. package/lib/typescript/src/support/types.d.ts.map +0 -1
  125. package/lib/typescript/src/tools/datePickerTool.d.ts.map +0 -1
  126. package/lib/typescript/src/tools/guideTool.d.ts.map +0 -1
  127. package/lib/typescript/src/tools/index.d.ts.map +0 -1
  128. package/lib/typescript/src/tools/keyboardTool.d.ts.map +0 -1
  129. package/lib/typescript/src/tools/longPressTool.d.ts.map +0 -1
  130. package/lib/typescript/src/tools/pickerTool.d.ts.map +0 -1
  131. package/lib/typescript/src/tools/restoreTool.d.ts.map +0 -1
  132. package/lib/typescript/src/tools/scrollTool.d.ts.map +0 -1
  133. package/lib/typescript/src/tools/simplifyTool.d.ts.map +0 -1
  134. package/lib/typescript/src/tools/sliderTool.d.ts.map +0 -1
  135. package/lib/typescript/src/tools/tapTool.d.ts.map +0 -1
  136. package/lib/typescript/src/tools/typeTool.d.ts.map +0 -1
  137. package/lib/typescript/src/tools/types.d.ts.map +0 -1
  138. package/lib/typescript/src/utils/audioUtils.d.ts.map +0 -1
  139. package/lib/typescript/src/utils/logger.d.ts.map +0 -1
  140. package/src/__cli_tmp__.tsx +0 -9
  141. package/src/cli/analyzers/chain-analyzer.ts +0 -183
  142. package/src/cli/extractors/ai-extractor.ts +0 -6
  143. package/src/cli/extractors/ast-extractor.ts +0 -551
  144. package/src/cli/generate-intents.ts +0 -140
  145. package/src/cli/generate-map.ts +0 -121
  146. package/src/cli/generate-swift.ts +0 -116
  147. package/src/cli/scanners/expo-scanner.ts +0 -203
  148. package/src/cli/scanners/rn-scanner.ts +0 -445
  149. package/src/components/AIAgent.tsx +0 -1716
  150. package/src/components/AIZone.tsx +0 -147
  151. package/src/components/AgentChatBar.tsx +0 -1143
  152. package/src/components/AgentErrorBoundary.tsx +0 -78
  153. package/src/components/AgentOverlay.tsx +0 -73
  154. package/src/components/DiscoveryTooltip.tsx +0 -148
  155. package/src/components/HighlightOverlay.tsx +0 -136
  156. package/src/components/Icons.tsx +0 -253
  157. package/src/components/ProactiveHint.tsx +0 -145
  158. package/src/components/cards/InfoCard.tsx +0 -58
  159. package/src/components/cards/ReviewSummary.tsx +0 -76
  160. package/src/config/endpoints.ts +0 -22
  161. package/src/core/ActionRegistry.ts +0 -105
  162. package/src/core/AgentRuntime.ts +0 -1471
  163. package/src/core/FiberTreeWalker.ts +0 -930
  164. package/src/core/IdleDetector.ts +0 -72
  165. package/src/core/MCPBridge.ts +0 -163
  166. package/src/core/ScreenDehydrator.ts +0 -53
  167. package/src/core/ZoneRegistry.ts +0 -44
  168. package/src/core/systemPrompt.ts +0 -431
  169. package/src/core/types.ts +0 -521
  170. package/src/hooks/useAction.ts +0 -182
  171. package/src/index.ts +0 -83
  172. package/src/plugin/withAppIntents.ts +0 -98
  173. package/src/providers/GeminiProvider.ts +0 -357
  174. package/src/providers/OpenAIProvider.ts +0 -379
  175. package/src/providers/ProviderFactory.ts +0 -36
  176. package/src/services/AudioInputService.ts +0 -226
  177. package/src/services/AudioOutputService.ts +0 -236
  178. package/src/services/KnowledgeBaseService.ts +0 -156
  179. package/src/services/VoiceService.ts +0 -451
  180. package/src/services/flags/FlagService.ts +0 -137
  181. package/src/services/telemetry/MobileAI.ts +0 -66
  182. package/src/services/telemetry/PiiScrubber.ts +0 -17
  183. package/src/services/telemetry/TelemetryService.ts +0 -323
  184. package/src/services/telemetry/TouchAutoCapture.ts +0 -165
  185. package/src/services/telemetry/device.ts +0 -93
  186. package/src/services/telemetry/deviceMetadata.ts +0 -13
  187. package/src/services/telemetry/index.ts +0 -13
  188. package/src/services/telemetry/types.ts +0 -75
  189. package/src/support/CSATSurvey.tsx +0 -304
  190. package/src/support/EscalationEventSource.ts +0 -190
  191. package/src/support/EscalationSocket.ts +0 -152
  192. package/src/support/SupportChatModal.tsx +0 -563
  193. package/src/support/SupportGreeting.tsx +0 -161
  194. package/src/support/TicketStore.ts +0 -100
  195. package/src/support/escalateTool.ts +0 -174
  196. package/src/support/index.ts +0 -29
  197. package/src/support/supportPrompt.ts +0 -55
  198. package/src/support/types.ts +0 -155
  199. package/src/tools/datePickerTool.ts +0 -60
  200. package/src/tools/guideTool.ts +0 -76
  201. package/src/tools/index.ts +0 -20
  202. package/src/tools/keyboardTool.ts +0 -30
  203. package/src/tools/longPressTool.ts +0 -61
  204. package/src/tools/pickerTool.ts +0 -115
  205. package/src/tools/restoreTool.ts +0 -33
  206. package/src/tools/scrollTool.ts +0 -156
  207. package/src/tools/simplifyTool.ts +0 -33
  208. package/src/tools/sliderTool.ts +0 -65
  209. package/src/tools/tapTool.ts +0 -93
  210. package/src/tools/typeTool.ts +0 -113
  211. package/src/tools/types.ts +0 -58
  212. package/src/types/jsx.d.ts +0 -20
  213. package/src/utils/audioUtils.ts +0 -54
  214. package/src/utils/logger.ts +0 -38
@@ -1,6 +0,0 @@
1
- /**
2
- * AI extractor — removed.
3
- *
4
- * Screen mapping is now pure AST (no LLM calls needed).
5
- * This file is kept as a placeholder for potential future use.
6
- */
@@ -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
- }