openuispec 0.2.9 → 0.2.11

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 (232) hide show
  1. package/README.md +3 -1
  2. package/cli/index.ts +80 -2
  3. package/cli/init.ts +23 -8
  4. package/docs/cli.md +42 -7
  5. package/drift/index.ts +41 -15
  6. package/mcp-server/index.ts +247 -117
  7. package/mcp-server/screenshot-android.ts +185 -44
  8. package/mcp-server/screenshot-ios.ts +242 -30
  9. package/mcp-server/screenshot.ts +96 -1
  10. package/package.json +5 -2
  11. package/prepare/index.ts +16 -0
  12. package/scripts/take-all-screenshots.ts +507 -0
  13. package/status/index.ts +2 -2
  14. package/examples/social-app/.mcp.json +0 -10
  15. package/examples/social-app/AGENTS.md +0 -124
  16. package/examples/social-app/CLAUDE.md +0 -124
  17. package/examples/social-app/backend/.gitkeep +0 -1
  18. package/examples/social-app/generated/android/social-app/app/.paparazzi-hashes.json +0 -3
  19. package/examples/social-app/generated/android/social-app/app/build.gradle.kts +0 -94
  20. package/examples/social-app/generated/android/social-app/app/src/main/AndroidManifest.xml +0 -26
  21. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/AppContainer.kt +0 -20
  22. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/MainActivity.kt +0 -35
  23. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/SocialAppApplication.kt +0 -13
  24. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/MockData.kt +0 -98
  25. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/preferences/AppPreferences.kt +0 -19
  26. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/preferences/DataStorePreferencesRepository.kt +0 -68
  27. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/preferences/PreferencesRepository.kt +0 -15
  28. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/model/Models.kt +0 -34
  29. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/MainShell.kt +0 -390
  30. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/components/Components.kt +0 -234
  31. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/components/ContractPrimitives.kt +0 -641
  32. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/navigation/RootComponent.kt +0 -113
  33. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/ChatDetailScreen.kt +0 -212
  34. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/CreatePostScreen.kt +0 -113
  35. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/DiscoverScreen.kt +0 -137
  36. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/EditProfileScreen.kt +0 -180
  37. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/HomeFeedScreen.kt +0 -169
  38. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/MessagesInboxScreen.kt +0 -85
  39. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/NotificationsScreen.kt +0 -74
  40. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/PostDetailScreen.kt +0 -293
  41. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/ProfileSelfScreen.kt +0 -116
  42. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/SearchResultsScreen.kt +0 -161
  43. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/SettingsScreen.kt +0 -164
  44. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/SettingsStore.kt +0 -95
  45. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/UserProfileScreen.kt +0 -123
  46. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Color.kt +0 -33
  47. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Shape.kt +0 -41
  48. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Spacing.kt +0 -20
  49. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Theme.kt +0 -82
  50. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Type.kt +0 -60
  51. package/examples/social-app/generated/android/social-app/app/src/main/res/drawable/ic_launcher_foreground.xml +0 -9
  52. package/examples/social-app/generated/android/social-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +0 -5
  53. package/examples/social-app/generated/android/social-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +0 -5
  54. package/examples/social-app/generated/android/social-app/app/src/main/res/values/strings.xml +0 -91
  55. package/examples/social-app/generated/android/social-app/app/src/main/res/values/themes.xml +0 -10
  56. package/examples/social-app/generated/android/social-app/app/src/main/res/values-ru/strings.xml +0 -79
  57. package/examples/social-app/generated/android/social-app/app/src/main/res/values-uz/strings.xml +0 -79
  58. package/examples/social-app/generated/android/social-app/app/src/main/xml/AndroidManifest.xml +0 -23
  59. package/examples/social-app/generated/android/social-app/app/src/test/kotlin/com/social/app/screenshots/HomeFeedScreenshotTest.kt +0 -34
  60. package/examples/social-app/generated/android/social-app/build.gradle.kts +0 -7
  61. package/examples/social-app/generated/android/social-app/gradle/libs.versions.toml +0 -50
  62. package/examples/social-app/generated/android/social-app/gradle/wrapper/gradle-wrapper.jar +0 -0
  63. package/examples/social-app/generated/android/social-app/gradle/wrapper/gradle-wrapper.properties +0 -8
  64. package/examples/social-app/generated/android/social-app/gradle.properties +0 -11
  65. package/examples/social-app/generated/android/social-app/gradlew +0 -248
  66. package/examples/social-app/generated/android/social-app/settings.gradle.kts +0 -27
  67. package/examples/social-app/generated/web/social-app/index.html +0 -12
  68. package/examples/social-app/generated/web/social-app/package-lock.json +0 -2517
  69. package/examples/social-app/generated/web/social-app/package.json +0 -27
  70. package/examples/social-app/generated/web/social-app/src/app/App.tsx +0 -58
  71. package/examples/social-app/generated/web/social-app/src/components/Shell.tsx +0 -259
  72. package/examples/social-app/generated/web/social-app/src/components/cards.tsx +0 -317
  73. package/examples/social-app/generated/web/social-app/src/components/ui.tsx +0 -340
  74. package/examples/social-app/generated/web/social-app/src/flows/CreatePostFlow.tsx +0 -86
  75. package/examples/social-app/generated/web/social-app/src/i18n.tsx +0 -59
  76. package/examples/social-app/generated/web/social-app/src/lib/icons.tsx +0 -85
  77. package/examples/social-app/generated/web/social-app/src/lib/tokens.ts +0 -70
  78. package/examples/social-app/generated/web/social-app/src/lib/utils.ts +0 -97
  79. package/examples/social-app/generated/web/social-app/src/locales/en.json +0 -67
  80. package/examples/social-app/generated/web/social-app/src/locales/ru.json +0 -67
  81. package/examples/social-app/generated/web/social-app/src/locales/uz.json +0 -67
  82. package/examples/social-app/generated/web/social-app/src/main.tsx +0 -16
  83. package/examples/social-app/generated/web/social-app/src/screens/ChatDetailScreen.tsx +0 -90
  84. package/examples/social-app/generated/web/social-app/src/screens/DiscoverScreen.tsx +0 -86
  85. package/examples/social-app/generated/web/social-app/src/screens/EditProfileScreen.tsx +0 -57
  86. package/examples/social-app/generated/web/social-app/src/screens/HomeFeedScreen.tsx +0 -103
  87. package/examples/social-app/generated/web/social-app/src/screens/MessagesInboxScreen.tsx +0 -52
  88. package/examples/social-app/generated/web/social-app/src/screens/NotificationsScreen.tsx +0 -41
  89. package/examples/social-app/generated/web/social-app/src/screens/PostDetailScreen.tsx +0 -115
  90. package/examples/social-app/generated/web/social-app/src/screens/ProfileSelfScreen.tsx +0 -57
  91. package/examples/social-app/generated/web/social-app/src/screens/ProfileUserScreen.tsx +0 -76
  92. package/examples/social-app/generated/web/social-app/src/screens/SearchResultsScreen.tsx +0 -96
  93. package/examples/social-app/generated/web/social-app/src/screens/SettingsScreen.tsx +0 -79
  94. package/examples/social-app/generated/web/social-app/src/state/store.ts +0 -592
  95. package/examples/social-app/generated/web/social-app/src/styles.css +0 -125
  96. package/examples/social-app/generated/web/social-app/src/vite-env.d.ts +0 -1
  97. package/examples/social-app/generated/web/social-app/tsconfig.json +0 -22
  98. package/examples/social-app/generated/web/social-app/tsconfig.node.json +0 -13
  99. package/examples/social-app/generated/web/social-app/tsconfig.node.tsbuildinfo +0 -1
  100. package/examples/social-app/generated/web/social-app/tsconfig.tsbuildinfo +0 -1
  101. package/examples/social-app/generated/web/social-app/vite.config.d.ts +0 -2
  102. package/examples/social-app/generated/web/social-app/vite.config.js +0 -6
  103. package/examples/social-app/generated/web/social-app/vite.config.ts +0 -7
  104. package/examples/social-app/package.json +0 -13
  105. package/examples/social-app/take-web-screenshots.ts +0 -97
  106. package/examples/taskflow/.codex/config.toml +0 -4
  107. package/examples/taskflow/.mcp.json +0 -10
  108. package/examples/taskflow/AGENTS.md +0 -124
  109. package/examples/taskflow/CLAUDE.md +0 -124
  110. package/examples/taskflow/backend/.gitkeep +0 -1
  111. package/examples/taskflow/generated/android/TaskFlow/README.md +0 -43
  112. package/examples/taskflow/generated/android/TaskFlow/app/build.gradle.kts +0 -76
  113. package/examples/taskflow/generated/android/TaskFlow/app/proguard-rules.pro +0 -1
  114. package/examples/taskflow/generated/android/TaskFlow/app/src/main/AndroidManifest.xml +0 -21
  115. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/MainActivity.kt +0 -19
  116. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/TaskFlowApp.kt +0 -283
  117. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/model/DomainModels.kt +0 -106
  118. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/model/SampleData.kt +0 -57
  119. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/components/Common.kt +0 -109
  120. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/HomeScreen.kt +0 -112
  121. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/ProjectsScreen.kt +0 -61
  122. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/SettingsScreen.kt +0 -82
  123. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/TaskDetailScreen.kt +0 -111
  124. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/sheets/Sheets.kt +0 -77
  125. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/theme/Color.kt +0 -30
  126. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/theme/Theme.kt +0 -86
  127. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/theme/Type.kt +0 -57
  128. package/examples/taskflow/generated/android/TaskFlow/app/src/main/res/values/strings.xml +0 -155
  129. package/examples/taskflow/generated/android/TaskFlow/app/src/main/res/values/themes.xml +0 -4
  130. package/examples/taskflow/generated/android/TaskFlow/build.gradle.kts +0 -5
  131. package/examples/taskflow/generated/android/TaskFlow/gradle/gradle-daemon-jvm.properties +0 -12
  132. package/examples/taskflow/generated/android/TaskFlow/gradle/wrapper/gradle-wrapper.jar +0 -0
  133. package/examples/taskflow/generated/android/TaskFlow/gradle/wrapper/gradle-wrapper.properties +0 -7
  134. package/examples/taskflow/generated/android/TaskFlow/gradle.properties +0 -4
  135. package/examples/taskflow/generated/android/TaskFlow/gradlew +0 -18
  136. package/examples/taskflow/generated/android/TaskFlow/gradlew.bat +0 -12
  137. package/examples/taskflow/generated/android/TaskFlow/settings.gradle.kts +0 -18
  138. package/examples/taskflow/generated/ios/TaskFlow/README.md +0 -21
  139. package/examples/taskflow/generated/ios/TaskFlow/Resources/en.lproj/Localizable.strings +0 -115
  140. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/App/TaskFlowApp.swift +0 -24
  141. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Components/AppChrome.swift +0 -150
  142. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Flows/TaskEditorSheet.swift +0 -220
  143. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Models/DomainModels.swift +0 -122
  144. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/CalendarView.swift +0 -21
  145. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/HomeView.swift +0 -201
  146. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/ProfileEditView.swift +0 -48
  147. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/ProjectDetailView.swift +0 -59
  148. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/ProjectsView.swift +0 -63
  149. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/SettingsView.swift +0 -85
  150. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/TaskDetailView.swift +0 -219
  151. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Support/AppModel.swift +0 -320
  152. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Support/AppSupport.swift +0 -41
  153. package/examples/taskflow/generated/ios/TaskFlow/project.yml +0 -26
  154. package/examples/taskflow/generated/web/TaskFlow/README.md +0 -19
  155. package/examples/taskflow/generated/web/TaskFlow/index.html +0 -12
  156. package/examples/taskflow/generated/web/TaskFlow/package-lock.json +0 -1908
  157. package/examples/taskflow/generated/web/TaskFlow/package.json +0 -24
  158. package/examples/taskflow/generated/web/TaskFlow/src/App.tsx +0 -58
  159. package/examples/taskflow/generated/web/TaskFlow/src/AppShell.tsx +0 -55
  160. package/examples/taskflow/generated/web/TaskFlow/src/components/Common.tsx +0 -82
  161. package/examples/taskflow/generated/web/TaskFlow/src/components/Modals.tsx +0 -191
  162. package/examples/taskflow/generated/web/TaskFlow/src/components/Nav.tsx +0 -41
  163. package/examples/taskflow/generated/web/TaskFlow/src/generated/messages.ts +0 -131
  164. package/examples/taskflow/generated/web/TaskFlow/src/hooks.ts +0 -25
  165. package/examples/taskflow/generated/web/TaskFlow/src/i18n.ts +0 -39
  166. package/examples/taskflow/generated/web/TaskFlow/src/locales.en.json +0 -111
  167. package/examples/taskflow/generated/web/TaskFlow/src/main.tsx +0 -13
  168. package/examples/taskflow/generated/web/TaskFlow/src/screens/HomeScreen.tsx +0 -111
  169. package/examples/taskflow/generated/web/TaskFlow/src/screens/ProjectsScreen.tsx +0 -82
  170. package/examples/taskflow/generated/web/TaskFlow/src/screens/SettingsScreens.tsx +0 -132
  171. package/examples/taskflow/generated/web/TaskFlow/src/screens/TaskDetail.tsx +0 -105
  172. package/examples/taskflow/generated/web/TaskFlow/src/store.ts +0 -216
  173. package/examples/taskflow/generated/web/TaskFlow/src/styles.css +0 -617
  174. package/examples/taskflow/generated/web/TaskFlow/src/types.ts +0 -64
  175. package/examples/taskflow/generated/web/TaskFlow/src/utils.ts +0 -78
  176. package/examples/taskflow/generated/web/TaskFlow/tsconfig.json +0 -21
  177. package/examples/taskflow/generated/web/TaskFlow/vite.config.ts +0 -6
  178. package/examples/todo-orbit/.codex/config.toml +0 -4
  179. package/examples/todo-orbit/.mcp.json +0 -10
  180. package/examples/todo-orbit/AGENTS.md +0 -124
  181. package/examples/todo-orbit/CLAUDE.md +0 -124
  182. package/examples/todo-orbit/backend/.gitkeep +0 -1
  183. package/examples/todo-orbit/generated/android/Todo Orbit/README.md +0 -14
  184. package/examples/todo-orbit/generated/android/Todo Orbit/app/build.gradle.kts +0 -58
  185. package/examples/todo-orbit/generated/android/Todo Orbit/app/proguard-rules.pro +0 -1
  186. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/AndroidManifest.xml +0 -20
  187. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/MainActivity.kt +0 -14
  188. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/TodoOrbitApp.kt +0 -345
  189. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/support/AppLogic.kt +0 -231
  190. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/support/Models.kt +0 -169
  191. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/support/Strings.kt +0 -8
  192. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/components/CommonComponents.kt +0 -236
  193. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/screens/AnalyticsScreen.kt +0 -193
  194. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/screens/SettingsScreen.kt +0 -102
  195. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/screens/TasksScreen.kt +0 -347
  196. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/sheets/EditorSheets.kt +0 -347
  197. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/theme/TodoOrbitTheme.kt +0 -59
  198. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/res/values/strings.xml +0 -149
  199. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/res/values-ru/strings.xml +0 -155
  200. package/examples/todo-orbit/generated/android/Todo Orbit/build.gradle.kts +0 -4
  201. package/examples/todo-orbit/generated/android/Todo Orbit/gradle/wrapper/gradle-wrapper.jar +0 -0
  202. package/examples/todo-orbit/generated/android/Todo Orbit/gradle/wrapper/gradle-wrapper.properties +0 -7
  203. package/examples/todo-orbit/generated/android/Todo Orbit/gradle.properties +0 -4
  204. package/examples/todo-orbit/generated/android/Todo Orbit/gradlew +0 -248
  205. package/examples/todo-orbit/generated/android/Todo Orbit/gradlew.bat +0 -93
  206. package/examples/todo-orbit/generated/android/Todo Orbit/settings.gradle.kts +0 -18
  207. package/examples/todo-orbit/generated/ios/Todo Orbit/.screenshot-uitest/Sources/ScreenshotUITest.swift +0 -36
  208. package/examples/todo-orbit/generated/ios/Todo Orbit/README.md +0 -29
  209. package/examples/todo-orbit/generated/ios/Todo Orbit/Resources/en.lproj/Localizable.strings +0 -119
  210. package/examples/todo-orbit/generated/ios/Todo Orbit/Resources/ru.lproj/Localizable.strings +0 -119
  211. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/App/TodoOrbitApp.swift +0 -50
  212. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Components/OrbitChrome.swift +0 -204
  213. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Components/SchedulePreviewView.swift +0 -126
  214. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Components/TrendChartView.swift +0 -70
  215. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Flows/RecurringRuleSheet.swift +0 -126
  216. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Flows/TaskEditorSheet.swift +0 -61
  217. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Models/DomainModels.swift +0 -238
  218. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Screens/AnalyticsView.swift +0 -94
  219. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Screens/SettingsView.swift +0 -76
  220. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Screens/TasksHomeView.swift +0 -364
  221. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Support/AppModel.swift +0 -324
  222. package/examples/todo-orbit/generated/ios/Todo Orbit/TodoOrbit.xcodeproj/project.pbxproj +0 -400
  223. package/examples/todo-orbit/generated/ios/Todo Orbit/TodoOrbit.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  224. package/examples/todo-orbit/generated/ios/Todo Orbit/project.yml +0 -25
  225. package/examples/todo-orbit/generated/web/Todo Orbit/index.html +0 -16
  226. package/examples/todo-orbit/generated/web/Todo Orbit/package-lock.json +0 -1087
  227. package/examples/todo-orbit/generated/web/Todo Orbit/package.json +0 -24
  228. package/examples/todo-orbit/generated/web/Todo Orbit/src/App.tsx +0 -2167
  229. package/examples/todo-orbit/generated/web/Todo Orbit/src/main.tsx +0 -13
  230. package/examples/todo-orbit/generated/web/Todo Orbit/src/styles.css +0 -926
  231. package/examples/todo-orbit/generated/web/Todo Orbit/tsconfig.json +0 -19
  232. package/examples/todo-orbit/generated/web/Todo Orbit/vite.config.ts +0 -6
@@ -13,21 +13,18 @@
13
13
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
14
14
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
15
15
  import { z } from "zod";
16
- import { readFileSync } from "node:fs";
17
- import { join, dirname } from "node:path";
16
+ import { readFileSync, existsSync, readdirSync } from "node:fs";
17
+ import { join, dirname, relative, resolve } from "node:path";
18
18
  import { fileURLToPath } from "node:url";
19
- import { SUPPORTED_TARGETS, findProjectDir, discoverSpecFiles } from "../drift/index.js";
19
+ import { SUPPORTED_TARGETS, findProjectDir, discoverSpecFiles, readProjectName, resolveOutputDir, stateFilePath, loadTargetDrift, createSnapshot } from "../drift/index.js";
20
20
  import { buildPrepareResult } from "../prepare/index.js";
21
21
  import { buildCheckResult } from "../check/index.js";
22
22
  import { buildStatusResult } from "../status/index.js";
23
23
  import { buildValidateResult } from "../schema/validate.js";
24
- import { loadTargetDrift } from "../drift/index.js";
25
- import { readFileSync as fsReadFileSync, existsSync, readdirSync } from "node:fs";
26
- import { relative, resolve } from "node:path";
27
24
  import YAML from "yaml";
28
- import { takeScreenshot } from "./screenshot.js";
29
- import { takeAndroidScreenshot } from "./screenshot-android.js";
30
- import { takeIOSScreenshot } from "./screenshot-ios.js";
25
+ import { takeScreenshot, takeScreenshotBatch } from "./screenshot.js";
26
+ import { takeAndroidScreenshot, takeAndroidScreenshotBatch } from "./screenshot-android.js";
27
+ import { takeIOSScreenshot, takeIOSScreenshotBatch } from "./screenshot-ios.js";
31
28
 
32
29
  // ── resolve project cwd ──────────────────────────────────────────────
33
30
 
@@ -59,8 +56,14 @@ function formatError(err: unknown): string {
59
56
  return err instanceof Error ? err.message : String(err);
60
57
  }
61
58
 
62
- function toolResult(data: unknown): { content: [{ type: "text"; text: string }] } {
63
- return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
59
+ function toolResult(data: unknown, hint?: string): { content: { type: "text"; text: string }[] } {
60
+ const parts: { type: "text"; text: string }[] = [
61
+ { type: "text" as const, text: JSON.stringify(data) },
62
+ ];
63
+ if (hint) {
64
+ parts.push({ type: "text" as const, text: hint });
65
+ }
66
+ return { content: parts };
64
67
  }
65
68
 
66
69
  function toolError(err: unknown): { content: [{ type: "text"; text: string }]; isError: true } {
@@ -108,58 +111,21 @@ export const server = new McpServer(
108
111
  version: getPackageVersion(),
109
112
  },
110
113
  {
111
- instructions: `This project uses OpenUISpec — a semantic UI specification format.
112
- Spec files (YAML) are the single source of truth for all UI across platforms.
113
-
114
- MANDATORY WORKFLOW for any UI-related request (screens, navigation, layout, tokens, flows, localization):
115
-
116
- PRE-GENERATION:
117
- 1. Call openuispec_prepare with the target platform.
118
- 2. Call openuispec_read_specs to load the spec file contents you need.
119
- Use the returned contents as the AUTHORITATIVE source do NOT paraphrase from memory.
120
- Cross-reference exact token values, contract must_handle lists, and locale keys from the content.
121
- 3. If the request requires spec changes, update the spec files FIRST, then call openuispec_check.
122
- 4. Generate or update the platform UI code based on the spec contents.
123
-
124
- POST-GENERATION (do this EVERY TIME after writing UI code):
125
- 5. Call openuispec_check to validate spec integrity.
126
- 6. Call openuispec_read_specs for the screens/contracts you just generated code for.
127
- 7. Audit your generated code against the spec contents. For each screen, verify:
128
- - Every field/action in the spec has a corresponding UI element
129
- - Token values (colors, spacing, radii) match the spec exactly, not approximations
130
- - Contract must_handle states are all implemented (loading, error, empty, etc.)
131
- - Adaptive breakpoints match the layout size_classes in the spec
132
- - Locale keys match $t: references in the spec
133
- - Navigation targets match flow definitions
134
- Report any real gaps found and fix them before finishing.
135
-
136
- CREATING NEW SPEC FILES:
137
- When you need to create or edit spec files and are unsure of the format:
138
- 1. Call openuispec_spec_types to discover available spec types.
139
- 2. Call openuispec_spec_schema with the specific type to get the full JSON schema.
140
- 3. Write the spec file following the schema exactly.
141
-
142
- FOCUSED GETTERS (prefer these for incremental edits over read_specs):
143
- - openuispec_get_screen(name) — single screen spec
144
- - openuispec_get_contract(name, variant?) — single contract, optionally one variant
145
- - openuispec_get_tokens(category) — single token category (color, typography, spacing, etc.)
146
- - openuispec_get_locale(locale, keys?) — single locale file, optionally filtered keys
147
- - openuispec_check(target, screens?, contracts?) — scoped audit for specific screens/contracts
148
- Use read_specs for full-project generation; use focused getters when editing one screen or contract.
149
-
150
- VISUAL VERIFICATION:
151
- - openuispec_screenshot(route, viewport?, theme?) — screenshot the generated web app at a route.
152
- Starts the dev server automatically. Use after generation to visually verify UI matches the spec.
153
- Requires puppeteer (npm install -g puppeteer).
154
- - openuispec_screenshot_android(screen?, theme?, wait_for?) — screenshot the generated Android app.
155
- Builds APK, installs on emulator, and captures via adb screencap.
156
- Shows the real app with navigation, images, and themes. Requires a running emulator.
157
- - openuispec_screenshot_ios(screen?, device?, nav?, theme?, wait_for?) — screenshot the generated iOS app.
158
- Builds with xcodebuild, installs on simulator, and captures via xcrun simctl.
159
- Shows the real app with navigation, images, and themes. Requires Xcode.
160
-
161
- Skip these tools ONLY when the request is purely non-UI (API logic, database, infrastructure, etc.)
162
- or explicitly platform-specific polish that doesn't affect shared UI semantics.`,
114
+ instructions: `OpenUISpec — semantic UI spec format. Spec files (YAML) are the single source of truth for all UI.
115
+
116
+ WORKFLOW — each tool response includes a next_tool hint, follow it:
117
+ 1. openuispec_prepare(target) get context + platform config (include_specs=true to embed content)
118
+ 2. openuispec_read_specs(paths) → load spec content (omit paths for listing only)
119
+ 3. Generate/update code
120
+ 4. openuispec_check(target) validate spec files (audit=true for review checklist, not code inspection)
121
+ 5. Remind the user to baseline when satisfied: openuispec drift --snapshot --target <t>
122
+ Do not baseline on your own initiativethe user decides when output is accepted.
123
+
124
+ FOCUSED GETTERS (prefer for incremental edits): get_screen, get_contract, get_tokens, get_locale
125
+ SPEC AUTHORING: spec_types spec_schema(type, summary?) write YAML
126
+ SCREENSHOTS: screenshot (web), screenshot_android, screenshot_ios — single + batch variants
127
+
128
+ Skip only for purely non-UI requests.`,
163
129
  }
164
130
  );
165
131
 
@@ -168,12 +134,23 @@ or explicitly platform-specific polish that doesn't affect shared UI semantics.`
168
134
  server.registerTool(
169
135
  "openuispec_prepare",
170
136
  {
171
- description: "Build AI-ready work bundle for a target platform. REQUIRED before any UI code generation. Returns spec context, platform config, semantic changes, and generation constraints. Call openuispec_read_specs afterward to load the actual spec file contents you need for generation.",
172
- inputSchema: { target: targetSchema },
137
+ description: "Build AI-ready work bundle for a target platform. REQUIRED before any UI code generation. Returns spec context, platform config, semantic changes, and generation constraints.",
138
+ inputSchema: {
139
+ target: targetSchema,
140
+ include_specs: z.boolean().optional().default(false).describe("Embed all spec file contents in the response. Saves a separate read_specs call but increases response size."),
141
+ },
173
142
  },
174
- async ({ target }) => {
143
+ async ({ target, include_specs }) => {
175
144
  try {
176
- return toolResult(buildPrepareResult(target, projectCwd));
145
+ const result = buildPrepareResult(target, projectCwd, include_specs);
146
+ const baselinePending = result.baseline_status?.output_exists && !result.baseline_status?.snapshot_exists;
147
+ const baselineReminder = baselinePending
148
+ ? " ⚠ Baseline pending — remind user to run `openuispec drift --snapshot --target " + target + "` when satisfied."
149
+ : "";
150
+ const hint = (include_specs
151
+ ? "next_tool: openuispec_check (after generating code)"
152
+ : "next_tool: openuispec_read_specs (load spec contents for generation)") + baselineReminder;
153
+ return toolResult(result, hint);
177
154
  } catch (err) {
178
155
  return toolError(err);
179
156
  }
@@ -184,23 +161,24 @@ server.registerTool(
184
161
 
185
162
  function buildAuditChecklist(projectDir: string, target: string, screenFilter?: string[], contractFilter?: string[]): string {
186
163
  const lines: string[] = [
187
- "POST-GENERATION AUDITverify your code against these concrete spec requirements:",
164
+ "SPEC-DERIVED CHECKLISTthis is extracted from the spec files, NOT from generated code.",
165
+ "Use it as a guide when you manually review the generated code.",
188
166
  "",
189
- "HOW TO AUDIT: For each item below, READ the generated component/screen file,",
190
- "find the code that implements it, and confirm the values match exactly.",
191
- "If you cannot find the implementation, it is a REAL GAP — fix it.",
167
+ "For each item below, read the generated component/screen file,",
168
+ "find the code that implements it, and confirm the values match.",
169
+ "If you cannot find the implementation, it is a gap — fix it.",
192
170
  "",
193
171
  ];
194
172
 
195
173
  // Extract must_handle from contracts
196
- const manifest = YAML.parse(fsReadFileSync(join(projectDir, "openuispec.yaml"), "utf-8"));
174
+ const manifest = YAML.parse(readFileSync(join(projectDir, "openuispec.yaml"), "utf-8"));
197
175
  const contractsDir = resolveSpecDir(projectDir, manifest, "contracts");
198
176
 
199
177
  if (existsSync(contractsDir)) {
200
178
  lines.push("## Contract must_handle requirements");
201
179
  for (const file of readdirSync(contractsDir).filter(f => f.endsWith(".yaml")).sort()) {
202
180
  try {
203
- const content = YAML.parse(fsReadFileSync(join(contractsDir, file), "utf-8"));
181
+ const content = YAML.parse(readFileSync(join(contractsDir, file), "utf-8"));
204
182
  const contractName = Object.keys(content)[0];
205
183
  if (contractFilter && !contractFilter.includes(contractName)) continue;
206
184
  const contract = content[contractName];
@@ -254,7 +232,7 @@ function buildAuditChecklist(projectDir: string, target: string, screenFilter?:
254
232
  lines.push("## Screens — verify all sections exist in generated code");
255
233
  for (const file of readdirSync(screensDir).filter(f => f.endsWith(".yaml")).sort()) {
256
234
  try {
257
- const content = YAML.parse(fsReadFileSync(join(screensDir, file), "utf-8"));
235
+ const content = YAML.parse(readFileSync(join(screensDir, file), "utf-8"));
258
236
  const screenName = Object.keys(content)[0];
259
237
  if (screenFilter && !screenFilter.includes(screenName)) continue;
260
238
  const screen = content[screenName];
@@ -301,7 +279,7 @@ function buildAuditChecklist(projectDir: string, target: string, screenFilter?:
301
279
  lines.push("## Locales — verify all locale files are wired");
302
280
  for (const file of localeFiles) {
303
281
  try {
304
- const keys = Object.keys(JSON.parse(fsReadFileSync(join(localesDir, file), "utf-8")));
282
+ const keys = Object.keys(JSON.parse(readFileSync(join(localesDir, file), "utf-8")));
305
283
  lines.push(`- [ ] ${file}: ${keys.length} keys loaded at runtime`);
306
284
  } catch { /* skip */ }
307
285
  }
@@ -314,7 +292,7 @@ function buildAuditChecklist(projectDir: string, target: string, screenFilter?:
314
292
  const platformPath = join(platformDir, `${target}.yaml`);
315
293
  if (existsSync(platformPath)) {
316
294
  try {
317
- const platformDoc = YAML.parse(fsReadFileSync(platformPath, "utf-8"));
295
+ const platformDoc = YAML.parse(readFileSync(platformPath, "utf-8"));
318
296
  const platformDef = platformDoc?.[target];
319
297
  if (platformDef?.generation) {
320
298
  lines.push("## Platform generation requirements");
@@ -336,26 +314,49 @@ function buildAuditChecklist(projectDir: string, target: string, screenFilter?:
336
314
  server.registerTool(
337
315
  "openuispec_check",
338
316
  {
339
- description: "Run composite validation + post-generation audit. Returns schema validation results AND a concrete audit checklist derived from your spec files listing every contract must_handle item, every screen section, and every locale file that must exist in your generated code. Verify each item. Use optional screens/contracts params to scope the audit to specific items (validation still runs on all files).",
317
+ description: "Validate spec files (schema + semantic lint) and check prepare readiness. Does NOT inspect generated code. With audit=true, returns a spec-derived checklist of must_handle items, screen sections, and locale files use it as a guide when YOU manually review the generated code.",
340
318
  inputSchema: {
341
319
  target: targetSchema,
342
- screens: z.array(z.string()).optional().describe("Screen names to audit (e.g. ['home_feed', 'settings']). If omitted, audits all screens."),
343
- contracts: z.array(z.string()).optional().describe("Contract names to audit (e.g. ['action_trigger']). If omitted, audits all contracts."),
320
+ audit: z.boolean().optional().default(false).describe("Include the full audit checklist. Omit for a compact pass/fail summary."),
321
+ screens: z.array(z.string()).optional().describe("Screen names to audit (e.g. ['home_feed']). Requires audit=true."),
322
+ contracts: z.array(z.string()).optional().describe("Contract names to audit (e.g. ['action_trigger']). Requires audit=true."),
344
323
  },
345
324
  },
346
- async ({ target, screens, contracts }) => {
325
+ async ({ target, audit: includeAudit, screens, contracts }) => {
347
326
  try {
348
327
  const result = buildCheckResult(target, projectCwd);
328
+ const totalErrors = result.validation.total_errors + result.semantic.total_errors;
329
+ const passing = totalErrors === 0 && result.prepare.ready;
330
+
331
+ // bootstrap mode = no snapshot exists yet
332
+ const baselineHint = result.prepare.mode === "bootstrap"
333
+ ? `⚠ BASELINE PENDING: Remind the user to run \`openuispec drift --snapshot --target ${target}\` when satisfied with the output.`
334
+ : "";
335
+
336
+ if (passing && !includeAudit) {
337
+ const compact = {
338
+ target,
339
+ status: "PASS",
340
+ validation_errors: 0,
341
+ semantic_errors: 0,
342
+ prepare: { mode: result.prepare.mode, ready: true },
343
+ };
344
+ return toolResult(compact, baselineHint || `Remind the user to baseline: openuispec drift --snapshot --target ${target}`);
345
+ }
346
+
349
347
  const projectDir = findProjectDir(projectCwd);
350
- const screenFilter = screens && screens.length > 0 ? screens : undefined;
351
- const contractFilter = contracts && contracts.length > 0 ? contracts : undefined;
352
- const audit = buildAuditChecklist(projectDir, target, screenFilter, contractFilter);
353
- return {
354
- content: [
355
- { type: "text" as const, text: JSON.stringify(result, null, 2) },
356
- { type: "text" as const, text: audit },
357
- ],
358
- };
348
+ const hints: string[] = [JSON.stringify(result)];
349
+
350
+ if (includeAudit) {
351
+ const screenFilter = screens && screens.length > 0 ? screens : undefined;
352
+ const contractFilter = contracts && contracts.length > 0 ? contracts : undefined;
353
+ hints.push(buildAuditChecklist(projectDir, target, screenFilter, contractFilter));
354
+ }
355
+
356
+ if (baselineHint) hints.push(baselineHint);
357
+ hints.push(passing ? "next_tool: openuispec_drift --snapshot (to create/update baseline)" : "Fix validation errors, then re-run openuispec_check.");
358
+
359
+ return { content: hints.map(text => ({ type: "text" as const, text })) };
359
360
  } catch (err) {
360
361
  return toolError(err);
361
362
  }
@@ -371,7 +372,7 @@ server.registerTool(
371
372
  },
372
373
  async () => {
373
374
  try {
374
- return toolResult(buildStatusResult(projectCwd));
375
+ return toolResult(buildStatusResult(projectCwd), "next_tool: openuispec_prepare for any target that is 'behind' or 'needs generation'");
375
376
  } catch (err) {
376
377
  return toolError(err);
377
378
  }
@@ -393,7 +394,8 @@ server.registerTool(
393
394
  },
394
395
  async ({ groups }) => {
395
396
  try {
396
- return toolResult(buildValidateResult(groups, projectCwd));
397
+ const result = buildValidateResult(groups, projectCwd);
398
+ return toolResult(result, "next_tool: openuispec_check (for full validation + prepare readiness)");
397
399
  } catch (err) {
398
400
  return toolError(err);
399
401
  }
@@ -405,31 +407,41 @@ server.registerTool(
405
407
  server.registerTool(
406
408
  "openuispec_read_specs",
407
409
  {
408
- description: "Read the full contents of spec files. Call after openuispec_prepare to load the actual YAML/JSON content. Pass specific file paths from the prepare output, or omit to read all spec files. Use these contents as the authoritative source do NOT paraphrase from memory.",
410
+ description: "Read spec file contents. Pass specific paths to load those files. If no paths given, returns a listing of all spec files (path + category, no content) use that to pick which files to load.",
409
411
  inputSchema: {
410
412
  paths: z
411
413
  .array(z.string())
412
414
  .optional()
413
- .describe("Specific spec file paths to read (relative to spec root, e.g. 'screens/home.yaml'). If omitted, reads all spec files."),
415
+ .describe("Spec file paths to read (relative, e.g. 'screens/home.yaml'). If omitted, returns listing only."),
414
416
  },
415
417
  },
416
418
  async ({ paths }) => {
417
419
  try {
418
420
  const projectDir = findProjectDir(projectCwd);
419
421
  const allFiles = discoverSpecFiles(projectDir);
420
- const filesToRead = paths && paths.length > 0
421
- ? allFiles.filter((f) => {
422
- const rel = relative(projectDir, f);
423
- return paths.some((p) => rel === p || rel.endsWith(p));
424
- })
425
- : allFiles;
422
+
423
+ if (!paths || paths.length === 0) {
424
+ // Listing mode — paths + categories, no content
425
+ const listing = allFiles.map((f) => {
426
+ const rel = relative(projectDir, f);
427
+ const dir = dirname(rel);
428
+ const category = rel === "openuispec.yaml" ? "manifest" : (dir || "other");
429
+ return { path: rel, category };
430
+ });
431
+ return toolResult(listing, "next_tool: openuispec_read_specs with specific paths to load content");
432
+ }
433
+
434
+ const filesToRead = allFiles.filter((f) => {
435
+ const rel = relative(projectDir, f);
436
+ return paths.some((p) => rel === p || rel.endsWith(p));
437
+ });
426
438
 
427
439
  const contents = filesToRead.map((f) => ({
428
440
  path: relative(projectDir, f),
429
- content: fsReadFileSync(f, "utf-8"),
441
+ content: readFileSync(f, "utf-8"),
430
442
  }));
431
443
 
432
- return toolResult(contents);
444
+ return toolResult(contents, "next_tool: generate/update code, then openuispec_check");
433
445
  } catch (err) {
434
446
  return toolError(err);
435
447
  }
@@ -441,16 +453,26 @@ server.registerTool(
441
453
  server.registerTool(
442
454
  "openuispec_drift",
443
455
  {
444
- description: "Detect spec drift since last snapshot. Shows which spec files changed, were added, or removed. Use explain to see property-level changes.",
456
+ description: "Detect spec drift since last snapshot, or create a new snapshot. Shows which spec files changed, were added, or removed. Use explain for property-level changes. Use snapshot=true after generation to create/update the baseline.",
445
457
  inputSchema: {
446
458
  target: targetSchema,
447
459
  explain: z.boolean().optional().default(false).describe("Include semantic explanation of changes"),
460
+ snapshot: z.boolean().optional().default(false).describe("Create a new snapshot (baseline) instead of checking drift. Use after code generation is complete and verified."),
448
461
  },
449
462
  },
450
- async ({ target, explain }) => {
463
+ async ({ target, explain, snapshot: doSnapshot }) => {
451
464
  try {
465
+ if (doSnapshot) {
466
+ const result = createSnapshot(projectCwd, target);
467
+ return toolResult(result, "Baseline created. next_tool: openuispec_status (to verify all targets)");
468
+ }
452
469
  const { result } = loadTargetDrift(projectCwd, target, false, explain);
453
- return toolResult(result);
470
+ const d = result.drift;
471
+ const hasDrift = d.changed.length > 0 || d.added.length > 0 || d.removed.length > 0;
472
+ const hint = hasDrift
473
+ ? "next_tool: openuispec_prepare (to build work bundle for pending changes)"
474
+ : "No drift detected. Target is up to date.";
475
+ return toolResult(result, hint);
454
476
  } catch (err) {
455
477
  return toolError(err);
456
478
  }
@@ -490,7 +512,7 @@ server.registerTool(
490
512
  title: info.title,
491
513
  description: info.description,
492
514
  }));
493
- return toolResult(types);
515
+ return toolResult(types, "next_tool: openuispec_spec_schema(type) for the full schema of a specific type");
494
516
  }
495
517
  );
496
518
 
@@ -499,12 +521,13 @@ server.registerTool(
499
521
  server.registerTool(
500
522
  "openuispec_spec_schema",
501
523
  {
502
- description: "Get the full JSON schema for a specific OpenUISpec spec type. Returns the complete schema definition so you know the exact format when creating or editing spec files. Call openuispec_spec_types first to see available types.",
524
+ description: "Get the JSON schema for a specific OpenUISpec spec type. Returns the complete schema definition so you know the exact format when creating or editing spec files.",
503
525
  inputSchema: {
504
- type: z.string().describe("Spec type to get schema for (e.g. 'screen', 'tokens/color', 'contract'). Use openuispec_spec_types to list all available types."),
526
+ type: z.string().describe("Spec type (e.g. 'screen', 'tokens/color', 'contract'). Use openuispec_spec_types to list all."),
527
+ summary: z.boolean().optional().default(false).describe("Return only top-level property names and types instead of the full schema. Useful for a quick overview."),
505
528
  },
506
529
  },
507
- async ({ type }) => {
530
+ async ({ type, summary }) => {
508
531
  const entry = SCHEMA_CATALOG[type];
509
532
  if (!entry) {
510
533
  return toolError(`Unknown spec type "${type}". Call openuispec_spec_types to see available types.`);
@@ -513,6 +536,19 @@ server.registerTool(
513
536
  const __dirname = dirname(fileURLToPath(import.meta.url));
514
537
  const schemaPath = join(__dirname, "..", "schema", entry.file);
515
538
  const schema = JSON.parse(readFileSync(schemaPath, "utf-8"));
539
+
540
+ if (summary) {
541
+ // Extract top-level properties summary
542
+ const props = schema.properties ?? schema.patternProperties ?? {};
543
+ const topLevel: Record<string, string> = {};
544
+ for (const [key, val] of Object.entries(props)) {
545
+ const v = val as any;
546
+ topLevel[key] = v.type ?? (v.$ref ? `ref:${v.$ref}` : "object");
547
+ }
548
+ return toolResult({ type, title: entry.title, required: schema.required ?? [], properties: topLevel },
549
+ "Use summary=false for the full schema when creating/editing spec files.");
550
+ }
551
+
516
552
  return toolResult({ type, title: entry.title, schema });
517
553
  } catch (err) {
518
554
  return toolError(err);
@@ -533,13 +569,13 @@ server.registerTool(
533
569
  async ({ name }) => {
534
570
  try {
535
571
  const projectDir = findProjectDir(projectCwd);
536
- const manifest = YAML.parse(fsReadFileSync(join(projectDir, "openuispec.yaml"), "utf-8"));
572
+ const manifest = YAML.parse(readFileSync(join(projectDir, "openuispec.yaml"), "utf-8"));
537
573
  const screensDir = resolveSpecDir(projectDir, manifest, "screens");
538
574
  const filePath = join(screensDir, `${name}.yaml`);
539
575
  if (!existsSync(filePath)) {
540
576
  return toolError(`Screen "${name}" not found. Expected file: ${filePath}`);
541
577
  }
542
- const content = fsReadFileSync(filePath, "utf-8");
578
+ const content = readFileSync(filePath, "utf-8");
543
579
  return toolResult({ name, path: relative(projectDir, filePath), content });
544
580
  } catch (err) {
545
581
  return toolError(err);
@@ -561,7 +597,7 @@ server.registerTool(
561
597
  async ({ name, variant }) => {
562
598
  try {
563
599
  const projectDir = findProjectDir(projectCwd);
564
- const manifest = YAML.parse(fsReadFileSync(join(projectDir, "openuispec.yaml"), "utf-8"));
600
+ const manifest = YAML.parse(readFileSync(join(projectDir, "openuispec.yaml"), "utf-8"));
565
601
  const contractsDir = resolveSpecDir(projectDir, manifest, "contracts");
566
602
 
567
603
  if (!existsSync(contractsDir)) {
@@ -571,7 +607,7 @@ server.registerTool(
571
607
  // Scan contract files for the matching contract key
572
608
  for (const file of readdirSync(contractsDir).filter(f => f.endsWith(".yaml")).sort()) {
573
609
  const filePath = join(contractsDir, file);
574
- const raw = fsReadFileSync(filePath, "utf-8");
610
+ const raw = readFileSync(filePath, "utf-8");
575
611
  const content = YAML.parse(raw);
576
612
  const contractName = Object.keys(content)[0];
577
613
  if (contractName !== name) continue;
@@ -608,7 +644,7 @@ server.registerTool(
608
644
  async ({ category }) => {
609
645
  try {
610
646
  const projectDir = findProjectDir(projectCwd);
611
- const manifest = YAML.parse(fsReadFileSync(join(projectDir, "openuispec.yaml"), "utf-8"));
647
+ const manifest = YAML.parse(readFileSync(join(projectDir, "openuispec.yaml"), "utf-8"));
612
648
  const tokensDir = resolveSpecDir(projectDir, manifest, "tokens");
613
649
 
614
650
  if (!existsSync(tokensDir)) {
@@ -624,7 +660,7 @@ server.registerTool(
624
660
  for (const candidate of candidates) {
625
661
  const filePath = join(tokensDir, candidate);
626
662
  if (existsSync(filePath)) {
627
- const content = fsReadFileSync(filePath, "utf-8");
663
+ const content = readFileSync(filePath, "utf-8");
628
664
  return toolResult({ category, path: relative(projectDir, filePath), content });
629
665
  }
630
666
  }
@@ -654,7 +690,7 @@ server.registerTool(
654
690
  async ({ locale, keys }) => {
655
691
  try {
656
692
  const projectDir = findProjectDir(projectCwd);
657
- const manifest = YAML.parse(fsReadFileSync(join(projectDir, "openuispec.yaml"), "utf-8"));
693
+ const manifest = YAML.parse(readFileSync(join(projectDir, "openuispec.yaml"), "utf-8"));
658
694
  const localesDir = resolveSpecDir(projectDir, manifest, "locales");
659
695
  const filePath = join(localesDir, `${locale}.json`);
660
696
 
@@ -668,7 +704,7 @@ server.registerTool(
668
704
  return toolError(`Locales directory not found: ${localesDir}`);
669
705
  }
670
706
 
671
- const raw = fsReadFileSync(filePath, "utf-8");
707
+ const raw = readFileSync(filePath, "utf-8");
672
708
  const content = JSON.parse(raw);
673
709
 
674
710
  if (keys && keys.length > 0) {
@@ -700,6 +736,7 @@ server.registerTool(
700
736
  width: z.number().default(1280),
701
737
  height: z.number().default(800),
702
738
  }).optional().describe("Viewport dimensions. Defaults to 1280x800. Use {width: 375, height: 812} for mobile."),
739
+ scale: z.number().optional().default(2).describe("Device pixel ratio used for capture. Higher values produce sharper screenshots (default 2)."),
703
740
  theme: z.enum(["light", "dark"]).optional().describe("Force a color scheme via prefers-color-scheme emulation"),
704
741
  wait_for: z.number().optional().default(1000).describe("Milliseconds to wait after page load before screenshotting (default 1000)"),
705
742
  full_page: z.boolean().optional().default(false).describe("Capture the full scrollable page instead of just the viewport"),
@@ -707,11 +744,12 @@ server.registerTool(
707
744
  output_dir: z.string().optional().describe("Directory to save the screenshot PNG (relative to web app root). E.g. 'screenshots'. If omitted, only returns base64 in response."),
708
745
  },
709
746
  },
710
- async ({ route, viewport, theme, wait_for, full_page, selector, output_dir }) => {
747
+ async ({ route, viewport, scale, theme, wait_for, full_page, selector, output_dir }) => {
711
748
  try {
712
749
  return await takeScreenshot(projectCwd, {
713
750
  route,
714
751
  viewport,
752
+ scale,
715
753
  theme,
716
754
  wait_for,
717
755
  full_page,
@@ -777,6 +815,98 @@ server.registerTool(
777
815
  }
778
816
  );
779
817
 
818
+ // ── tool: openuispec_screenshot_web_batch ──────────────────────────────
819
+
820
+ const webBatchCaptureSchema = z.object({
821
+ screen: z.string().describe("Screen name for metadata and filename"),
822
+ route: z.string().describe("Route path (e.g. '/home', '/settings')"),
823
+ selector: z.string().optional().describe("CSS selector to screenshot a specific element"),
824
+ full_page: z.boolean().optional().describe("Capture full scrollable page"),
825
+ wait_for: z.number().optional().describe("Per-capture wait time in ms"),
826
+ });
827
+
828
+ server.registerTool(
829
+ "openuispec_screenshot_web_batch",
830
+ {
831
+ description: "Take multiple web screenshots in a single server session. Starts the dev server once, then captures all routes in sequence. Much faster than calling screenshot for each route individually.",
832
+ inputSchema: {
833
+ captures: z.array(webBatchCaptureSchema).describe("Array of captures — each with screen name and route"),
834
+ viewport: z.object({ width: z.number().default(1280), height: z.number().default(800) }).optional().describe("Viewport dimensions for all captures"),
835
+ scale: z.number().optional().default(2).describe("Device pixel ratio for all captures (default 2)"),
836
+ theme: z.enum(["light", "dark"]).optional().describe("Force color scheme for all captures"),
837
+ output_dir: z.string().optional().describe("Directory to save all PNGs (relative to web app root)"),
838
+ },
839
+ },
840
+ async ({ captures, viewport, scale, theme, output_dir }) => {
841
+ try {
842
+ return await takeScreenshotBatch(projectCwd, { captures, viewport, scale, theme, output_dir });
843
+ } catch (err) {
844
+ return toolError(err);
845
+ }
846
+ }
847
+ );
848
+
849
+ // ── tool: openuispec_screenshot_android_batch ─────────────────────────
850
+
851
+ const androidBatchCaptureSchema = z.object({
852
+ screen: z.string().describe("Screen name for metadata and filename"),
853
+ route: z.string().optional().describe("Deep link URI to launch"),
854
+ nav: z.array(z.string()).optional().describe("UI tap steps after launch"),
855
+ wait_for: z.number().optional().describe("Per-capture wait time in ms"),
856
+ });
857
+
858
+ server.registerTool(
859
+ "openuispec_screenshot_android_batch",
860
+ {
861
+ description: "Take multiple Android screenshots in a single build+install cycle. Builds the APK once, installs once, then captures each screen in sequence via deep links or UI navigation. Much faster than calling screenshot_android for each screen individually.",
862
+ inputSchema: {
863
+ captures: z.array(androidBatchCaptureSchema).describe("Array of captures — each with screen name and optional route/nav"),
864
+ theme: z.enum(["light", "dark"]).optional().describe("Force light or dark mode for all captures"),
865
+ output_dir: z.string().optional().describe("Directory to save all PNGs (relative to Android project root)"),
866
+ project_dir: z.string().optional().describe("Direct path to Android project root"),
867
+ module: z.string().optional().describe("App module name (default: auto-detect)"),
868
+ },
869
+ },
870
+ async ({ captures, theme, output_dir, project_dir, module }) => {
871
+ try {
872
+ return await takeAndroidScreenshotBatch(projectCwd, { captures, theme, output_dir, project_dir, module });
873
+ } catch (err) {
874
+ return toolError(err);
875
+ }
876
+ }
877
+ );
878
+
879
+ // ── tool: openuispec_screenshot_ios_batch ──────────────────────────────
880
+
881
+ const iosBatchCaptureSchema = z.object({
882
+ screen: z.string().describe("Screen name for metadata and filename"),
883
+ nav: z.array(z.string()).optional().describe("UI tap steps after launch"),
884
+ wait_for: z.number().optional().describe("Per-capture wait time in ms"),
885
+ });
886
+
887
+ server.registerTool(
888
+ "openuispec_screenshot_ios_batch",
889
+ {
890
+ description: "Take multiple iOS screenshots in a single build+install cycle. Builds the app once, then captures each screen — no-nav screens via simctl, nav screens batched into a single XCUITest run. Much faster than calling screenshot_ios for each screen individually.",
891
+ inputSchema: {
892
+ captures: z.array(iosBatchCaptureSchema).describe("Array of captures — each with screen name and optional nav steps"),
893
+ device: z.string().optional().describe("Simulator device name"),
894
+ theme: z.enum(["light", "dark"]).optional().describe("Force light or dark appearance for all captures"),
895
+ output_dir: z.string().optional().describe("Directory to save all PNGs (relative to iOS project root)"),
896
+ project_dir: z.string().optional().describe("Direct path to iOS project root"),
897
+ scheme: z.string().optional().describe("Xcode scheme name"),
898
+ bundle_id: z.string().optional().describe("App bundle identifier"),
899
+ },
900
+ },
901
+ async ({ captures, device, theme, output_dir, project_dir, scheme, bundle_id }) => {
902
+ try {
903
+ return await takeIOSScreenshotBatch(projectCwd, { captures, device, theme, output_dir, project_dir, scheme, bundle_id });
904
+ } catch (err) {
905
+ return toolError(err);
906
+ }
907
+ }
908
+ );
909
+
780
910
  // ── start server ─────────────────────────────────────────────────────
781
911
 
782
912
  export async function startMcpServer() {