openuispec 0.2.10 → 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.
- package/README.md +3 -1
- package/cli/index.ts +4 -2
- package/cli/init.ts +23 -8
- package/docs/cli.md +13 -8
- package/drift/index.ts +41 -15
- package/mcp-server/index.ts +155 -116
- package/mcp-server/screenshot.ts +19 -4
- package/package.json +5 -2
- package/prepare/index.ts +16 -0
- package/scripts/take-all-screenshots.ts +507 -0
- package/status/index.ts +2 -2
- package/examples/social-app/.mcp.json +0 -10
- package/examples/social-app/AGENTS.md +0 -124
- package/examples/social-app/CLAUDE.md +0 -124
- package/examples/social-app/backend/.gitkeep +0 -1
- package/examples/social-app/generated/android/social-app/app/.paparazzi-hashes.json +0 -3
- package/examples/social-app/generated/android/social-app/app/build.gradle.kts +0 -94
- package/examples/social-app/generated/android/social-app/app/src/main/AndroidManifest.xml +0 -26
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/AppContainer.kt +0 -20
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/MainActivity.kt +0 -35
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/SocialAppApplication.kt +0 -13
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/MockData.kt +0 -98
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/preferences/AppPreferences.kt +0 -19
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/preferences/DataStorePreferencesRepository.kt +0 -68
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/preferences/PreferencesRepository.kt +0 -15
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/model/Models.kt +0 -34
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/MainShell.kt +0 -390
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/components/Components.kt +0 -234
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/components/ContractPrimitives.kt +0 -641
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/navigation/RootComponent.kt +0 -113
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/ChatDetailScreen.kt +0 -212
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/CreatePostScreen.kt +0 -113
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/DiscoverScreen.kt +0 -137
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/EditProfileScreen.kt +0 -180
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/HomeFeedScreen.kt +0 -169
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/MessagesInboxScreen.kt +0 -85
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/NotificationsScreen.kt +0 -74
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/PostDetailScreen.kt +0 -293
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/ProfileSelfScreen.kt +0 -116
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/SearchResultsScreen.kt +0 -161
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/SettingsScreen.kt +0 -164
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/SettingsStore.kt +0 -95
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/UserProfileScreen.kt +0 -123
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Color.kt +0 -33
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Shape.kt +0 -41
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Spacing.kt +0 -20
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Theme.kt +0 -82
- package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Type.kt +0 -60
- package/examples/social-app/generated/android/social-app/app/src/main/res/drawable/ic_launcher_foreground.xml +0 -9
- package/examples/social-app/generated/android/social-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +0 -5
- package/examples/social-app/generated/android/social-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +0 -5
- package/examples/social-app/generated/android/social-app/app/src/main/res/values/strings.xml +0 -91
- package/examples/social-app/generated/android/social-app/app/src/main/res/values/themes.xml +0 -10
- package/examples/social-app/generated/android/social-app/app/src/main/res/values-ru/strings.xml +0 -79
- package/examples/social-app/generated/android/social-app/app/src/main/res/values-uz/strings.xml +0 -79
- package/examples/social-app/generated/android/social-app/app/src/main/xml/AndroidManifest.xml +0 -23
- package/examples/social-app/generated/android/social-app/app/src/test/kotlin/com/social/app/screenshots/HomeFeedScreenshotTest.kt +0 -34
- package/examples/social-app/generated/android/social-app/build.gradle.kts +0 -7
- package/examples/social-app/generated/android/social-app/gradle/libs.versions.toml +0 -50
- package/examples/social-app/generated/android/social-app/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/examples/social-app/generated/android/social-app/gradle/wrapper/gradle-wrapper.properties +0 -8
- package/examples/social-app/generated/android/social-app/gradle.properties +0 -11
- package/examples/social-app/generated/android/social-app/gradlew +0 -248
- package/examples/social-app/generated/android/social-app/settings.gradle.kts +0 -27
- package/examples/social-app/generated/web/social-app/index.html +0 -12
- package/examples/social-app/generated/web/social-app/package-lock.json +0 -2517
- package/examples/social-app/generated/web/social-app/package.json +0 -27
- package/examples/social-app/generated/web/social-app/src/app/App.tsx +0 -58
- package/examples/social-app/generated/web/social-app/src/components/Shell.tsx +0 -259
- package/examples/social-app/generated/web/social-app/src/components/cards.tsx +0 -317
- package/examples/social-app/generated/web/social-app/src/components/ui.tsx +0 -340
- package/examples/social-app/generated/web/social-app/src/flows/CreatePostFlow.tsx +0 -86
- package/examples/social-app/generated/web/social-app/src/i18n.tsx +0 -59
- package/examples/social-app/generated/web/social-app/src/lib/icons.tsx +0 -85
- package/examples/social-app/generated/web/social-app/src/lib/tokens.ts +0 -70
- package/examples/social-app/generated/web/social-app/src/lib/utils.ts +0 -97
- package/examples/social-app/generated/web/social-app/src/locales/en.json +0 -67
- package/examples/social-app/generated/web/social-app/src/locales/ru.json +0 -67
- package/examples/social-app/generated/web/social-app/src/locales/uz.json +0 -67
- package/examples/social-app/generated/web/social-app/src/main.tsx +0 -16
- package/examples/social-app/generated/web/social-app/src/screens/ChatDetailScreen.tsx +0 -90
- package/examples/social-app/generated/web/social-app/src/screens/DiscoverScreen.tsx +0 -86
- package/examples/social-app/generated/web/social-app/src/screens/EditProfileScreen.tsx +0 -57
- package/examples/social-app/generated/web/social-app/src/screens/HomeFeedScreen.tsx +0 -103
- package/examples/social-app/generated/web/social-app/src/screens/MessagesInboxScreen.tsx +0 -52
- package/examples/social-app/generated/web/social-app/src/screens/NotificationsScreen.tsx +0 -41
- package/examples/social-app/generated/web/social-app/src/screens/PostDetailScreen.tsx +0 -115
- package/examples/social-app/generated/web/social-app/src/screens/ProfileSelfScreen.tsx +0 -57
- package/examples/social-app/generated/web/social-app/src/screens/ProfileUserScreen.tsx +0 -76
- package/examples/social-app/generated/web/social-app/src/screens/SearchResultsScreen.tsx +0 -96
- package/examples/social-app/generated/web/social-app/src/screens/SettingsScreen.tsx +0 -79
- package/examples/social-app/generated/web/social-app/src/state/store.ts +0 -592
- package/examples/social-app/generated/web/social-app/src/styles.css +0 -125
- package/examples/social-app/generated/web/social-app/src/vite-env.d.ts +0 -1
- package/examples/social-app/generated/web/social-app/tsconfig.json +0 -22
- package/examples/social-app/generated/web/social-app/tsconfig.node.json +0 -13
- package/examples/social-app/generated/web/social-app/tsconfig.node.tsbuildinfo +0 -1
- package/examples/social-app/generated/web/social-app/tsconfig.tsbuildinfo +0 -1
- package/examples/social-app/generated/web/social-app/vite.config.d.ts +0 -2
- package/examples/social-app/generated/web/social-app/vite.config.js +0 -6
- package/examples/social-app/generated/web/social-app/vite.config.ts +0 -7
- package/examples/social-app/package.json +0 -13
- package/examples/social-app/take-web-screenshots.ts +0 -97
- package/examples/taskflow/.codex/config.toml +0 -4
- package/examples/taskflow/.mcp.json +0 -10
- package/examples/taskflow/AGENTS.md +0 -124
- package/examples/taskflow/CLAUDE.md +0 -124
- package/examples/taskflow/backend/.gitkeep +0 -1
- package/examples/taskflow/generated/android/TaskFlow/README.md +0 -43
- package/examples/taskflow/generated/android/TaskFlow/app/build.gradle.kts +0 -76
- package/examples/taskflow/generated/android/TaskFlow/app/proguard-rules.pro +0 -1
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/AndroidManifest.xml +0 -21
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/MainActivity.kt +0 -19
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/TaskFlowApp.kt +0 -283
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/model/DomainModels.kt +0 -106
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/model/SampleData.kt +0 -57
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/components/Common.kt +0 -109
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/HomeScreen.kt +0 -112
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/ProjectsScreen.kt +0 -61
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/SettingsScreen.kt +0 -82
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/TaskDetailScreen.kt +0 -111
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/sheets/Sheets.kt +0 -77
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/theme/Color.kt +0 -30
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/theme/Theme.kt +0 -86
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/theme/Type.kt +0 -57
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/res/values/strings.xml +0 -155
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/res/values/themes.xml +0 -4
- package/examples/taskflow/generated/android/TaskFlow/build.gradle.kts +0 -5
- package/examples/taskflow/generated/android/TaskFlow/gradle/gradle-daemon-jvm.properties +0 -12
- package/examples/taskflow/generated/android/TaskFlow/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/examples/taskflow/generated/android/TaskFlow/gradle/wrapper/gradle-wrapper.properties +0 -7
- package/examples/taskflow/generated/android/TaskFlow/gradle.properties +0 -4
- package/examples/taskflow/generated/android/TaskFlow/gradlew +0 -18
- package/examples/taskflow/generated/android/TaskFlow/gradlew.bat +0 -12
- package/examples/taskflow/generated/android/TaskFlow/settings.gradle.kts +0 -18
- package/examples/taskflow/generated/ios/TaskFlow/README.md +0 -21
- package/examples/taskflow/generated/ios/TaskFlow/Resources/en.lproj/Localizable.strings +0 -115
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/App/TaskFlowApp.swift +0 -24
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Components/AppChrome.swift +0 -150
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Flows/TaskEditorSheet.swift +0 -220
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Models/DomainModels.swift +0 -122
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/CalendarView.swift +0 -21
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/HomeView.swift +0 -201
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/ProfileEditView.swift +0 -48
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/ProjectDetailView.swift +0 -59
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/ProjectsView.swift +0 -63
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/SettingsView.swift +0 -85
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/TaskDetailView.swift +0 -219
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Support/AppModel.swift +0 -320
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Support/AppSupport.swift +0 -41
- package/examples/taskflow/generated/ios/TaskFlow/project.yml +0 -31
- package/examples/taskflow/generated/web/TaskFlow/README.md +0 -19
- package/examples/taskflow/generated/web/TaskFlow/index.html +0 -12
- package/examples/taskflow/generated/web/TaskFlow/package-lock.json +0 -1908
- package/examples/taskflow/generated/web/TaskFlow/package.json +0 -24
- package/examples/taskflow/generated/web/TaskFlow/src/App.tsx +0 -58
- package/examples/taskflow/generated/web/TaskFlow/src/AppShell.tsx +0 -55
- package/examples/taskflow/generated/web/TaskFlow/src/components/Common.tsx +0 -82
- package/examples/taskflow/generated/web/TaskFlow/src/components/Modals.tsx +0 -191
- package/examples/taskflow/generated/web/TaskFlow/src/components/Nav.tsx +0 -41
- package/examples/taskflow/generated/web/TaskFlow/src/generated/messages.ts +0 -131
- package/examples/taskflow/generated/web/TaskFlow/src/hooks.ts +0 -25
- package/examples/taskflow/generated/web/TaskFlow/src/i18n.ts +0 -39
- package/examples/taskflow/generated/web/TaskFlow/src/locales.en.json +0 -111
- package/examples/taskflow/generated/web/TaskFlow/src/main.tsx +0 -13
- package/examples/taskflow/generated/web/TaskFlow/src/screens/HomeScreen.tsx +0 -111
- package/examples/taskflow/generated/web/TaskFlow/src/screens/ProjectsScreen.tsx +0 -82
- package/examples/taskflow/generated/web/TaskFlow/src/screens/SettingsScreens.tsx +0 -132
- package/examples/taskflow/generated/web/TaskFlow/src/screens/TaskDetail.tsx +0 -105
- package/examples/taskflow/generated/web/TaskFlow/src/store.ts +0 -216
- package/examples/taskflow/generated/web/TaskFlow/src/styles.css +0 -617
- package/examples/taskflow/generated/web/TaskFlow/src/types.ts +0 -64
- package/examples/taskflow/generated/web/TaskFlow/src/utils.ts +0 -78
- package/examples/taskflow/generated/web/TaskFlow/tsconfig.json +0 -21
- package/examples/taskflow/generated/web/TaskFlow/vite.config.ts +0 -6
- package/examples/todo-orbit/.codex/config.toml +0 -4
- package/examples/todo-orbit/.mcp.json +0 -10
- package/examples/todo-orbit/AGENTS.md +0 -124
- package/examples/todo-orbit/CLAUDE.md +0 -124
- package/examples/todo-orbit/backend/.gitkeep +0 -1
- package/examples/todo-orbit/generated/android/Todo Orbit/README.md +0 -14
- package/examples/todo-orbit/generated/android/Todo Orbit/app/build.gradle.kts +0 -58
- package/examples/todo-orbit/generated/android/Todo Orbit/app/proguard-rules.pro +0 -1
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/AndroidManifest.xml +0 -20
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/MainActivity.kt +0 -14
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/TodoOrbitApp.kt +0 -345
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/support/AppLogic.kt +0 -231
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/support/Models.kt +0 -169
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/support/Strings.kt +0 -8
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/components/CommonComponents.kt +0 -236
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/screens/AnalyticsScreen.kt +0 -193
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/screens/SettingsScreen.kt +0 -102
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/screens/TasksScreen.kt +0 -347
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/sheets/EditorSheets.kt +0 -347
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/theme/TodoOrbitTheme.kt +0 -59
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/res/values/strings.xml +0 -149
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/res/values-ru/strings.xml +0 -155
- package/examples/todo-orbit/generated/android/Todo Orbit/build.gradle.kts +0 -4
- package/examples/todo-orbit/generated/android/Todo Orbit/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/gradle/wrapper/gradle-wrapper.properties +0 -7
- package/examples/todo-orbit/generated/android/Todo Orbit/gradle.properties +0 -4
- package/examples/todo-orbit/generated/android/Todo Orbit/gradlew +0 -248
- package/examples/todo-orbit/generated/android/Todo Orbit/gradlew.bat +0 -93
- package/examples/todo-orbit/generated/android/Todo Orbit/settings.gradle.kts +0 -18
- package/examples/todo-orbit/generated/ios/Todo Orbit/.screenshot-uitest/Sources/ScreenshotUITest.swift +0 -36
- package/examples/todo-orbit/generated/ios/Todo Orbit/README.md +0 -29
- package/examples/todo-orbit/generated/ios/Todo Orbit/Resources/en.lproj/Localizable.strings +0 -119
- package/examples/todo-orbit/generated/ios/Todo Orbit/Resources/ru.lproj/Localizable.strings +0 -119
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/App/TodoOrbitApp.swift +0 -50
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Components/OrbitChrome.swift +0 -204
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Components/SchedulePreviewView.swift +0 -126
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Components/TrendChartView.swift +0 -70
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Flows/RecurringRuleSheet.swift +0 -126
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Flows/TaskEditorSheet.swift +0 -61
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Models/DomainModels.swift +0 -238
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Screens/AnalyticsView.swift +0 -94
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Screens/SettingsView.swift +0 -76
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Screens/TasksHomeView.swift +0 -364
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Support/AppModel.swift +0 -324
- package/examples/todo-orbit/generated/ios/Todo Orbit/TodoOrbit.xcodeproj/project.pbxproj +0 -439
- package/examples/todo-orbit/generated/ios/Todo Orbit/TodoOrbit.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- package/examples/todo-orbit/generated/ios/Todo Orbit/TodoOrbit.xcodeproj/xcshareddata/xcschemes/TodoOrbit.xcscheme +0 -89
- package/examples/todo-orbit/generated/ios/Todo Orbit/project.yml +0 -32
- package/examples/todo-orbit/generated/web/Todo Orbit/index.html +0 -16
- package/examples/todo-orbit/generated/web/Todo Orbit/package-lock.json +0 -1087
- package/examples/todo-orbit/generated/web/Todo Orbit/package.json +0 -24
- package/examples/todo-orbit/generated/web/Todo Orbit/src/App.tsx +0 -2167
- package/examples/todo-orbit/generated/web/Todo Orbit/src/main.tsx +0 -13
- package/examples/todo-orbit/generated/web/Todo Orbit/src/styles.css +0 -926
- package/examples/todo-orbit/generated/web/Todo Orbit/tsconfig.json +0 -19
- package/examples/todo-orbit/generated/web/Todo Orbit/vite.config.ts +0 -6
package/mcp-server/index.ts
CHANGED
|
@@ -13,17 +13,14 @@
|
|
|
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
25
|
import { takeScreenshot, takeScreenshotBatch } from "./screenshot.js";
|
|
29
26
|
import { takeAndroidScreenshot, takeAndroidScreenshotBatch } from "./screenshot-android.js";
|
|
@@ -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:
|
|
63
|
-
|
|
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: `
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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 initiative — the 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.
|
|
172
|
-
inputSchema: {
|
|
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
|
-
|
|
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
|
-
"
|
|
164
|
+
"SPEC-DERIVED CHECKLIST — this 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
|
-
"
|
|
190
|
-
"find the code that implements it, and confirm the values match
|
|
191
|
-
"If you cannot find the implementation, it is a
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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: "
|
|
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
|
-
|
|
343
|
-
|
|
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
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
|
|
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
|
|
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("
|
|
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
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
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(
|
|
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 =
|
|
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(
|
|
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 =
|
|
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(
|
|
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 =
|
|
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,
|
|
@@ -794,13 +832,14 @@ server.registerTool(
|
|
|
794
832
|
inputSchema: {
|
|
795
833
|
captures: z.array(webBatchCaptureSchema).describe("Array of captures — each with screen name and route"),
|
|
796
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)"),
|
|
797
836
|
theme: z.enum(["light", "dark"]).optional().describe("Force color scheme for all captures"),
|
|
798
837
|
output_dir: z.string().optional().describe("Directory to save all PNGs (relative to web app root)"),
|
|
799
838
|
},
|
|
800
839
|
},
|
|
801
|
-
async ({ captures, viewport, theme, output_dir }) => {
|
|
840
|
+
async ({ captures, viewport, scale, theme, output_dir }) => {
|
|
802
841
|
try {
|
|
803
|
-
return await takeScreenshotBatch(projectCwd, { captures, viewport, theme, output_dir });
|
|
842
|
+
return await takeScreenshotBatch(projectCwd, { captures, viewport, scale, theme, output_dir });
|
|
804
843
|
} catch (err) {
|
|
805
844
|
return toolError(err);
|
|
806
845
|
}
|
package/mcp-server/screenshot.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { findProjectDir } from "../drift/index.js";
|
|
|
17
17
|
export interface ScreenshotOptions {
|
|
18
18
|
route: string;
|
|
19
19
|
viewport?: { width: number; height: number };
|
|
20
|
+
scale?: number;
|
|
20
21
|
theme?: "light" | "dark";
|
|
21
22
|
wait_for?: number;
|
|
22
23
|
full_page?: boolean;
|
|
@@ -177,6 +178,7 @@ export async function takeScreenshot(
|
|
|
177
178
|
const {
|
|
178
179
|
route = "/",
|
|
179
180
|
viewport = { width: 1280, height: 800 },
|
|
181
|
+
scale = 2,
|
|
180
182
|
theme,
|
|
181
183
|
wait_for = 1000,
|
|
182
184
|
full_page = false,
|
|
@@ -192,7 +194,11 @@ export async function takeScreenshot(
|
|
|
192
194
|
// 2. Navigate
|
|
193
195
|
const page = await browser.newPage();
|
|
194
196
|
try {
|
|
195
|
-
await page.setViewport({
|
|
197
|
+
await page.setViewport({
|
|
198
|
+
width: viewport.width,
|
|
199
|
+
height: viewport.height,
|
|
200
|
+
deviceScaleFactor: scale,
|
|
201
|
+
});
|
|
196
202
|
|
|
197
203
|
if (theme) {
|
|
198
204
|
await page.emulateMediaFeatures([
|
|
@@ -245,6 +251,7 @@ export async function takeScreenshot(
|
|
|
245
251
|
route,
|
|
246
252
|
url: targetUrl,
|
|
247
253
|
viewport,
|
|
254
|
+
scale,
|
|
248
255
|
theme: theme ?? "default",
|
|
249
256
|
full_page,
|
|
250
257
|
selector: selector ?? null,
|
|
@@ -271,6 +278,7 @@ export interface WebBatchCapture {
|
|
|
271
278
|
export interface WebScreenshotBatchOptions {
|
|
272
279
|
captures: WebBatchCapture[];
|
|
273
280
|
viewport?: { width: number; height: number };
|
|
281
|
+
scale?: number;
|
|
274
282
|
theme?: "light" | "dark";
|
|
275
283
|
output_dir?: string;
|
|
276
284
|
}
|
|
@@ -281,7 +289,7 @@ export async function takeScreenshotBatch(
|
|
|
281
289
|
projectCwd: string,
|
|
282
290
|
options: WebScreenshotBatchOptions,
|
|
283
291
|
): Promise<ScreenshotResult> {
|
|
284
|
-
const { captures, viewport = { width: 1280, height: 800 }, theme, output_dir } = options;
|
|
292
|
+
const { captures, viewport = { width: 1280, height: 800 }, scale = 2, theme, output_dir } = options;
|
|
285
293
|
|
|
286
294
|
if (captures.length === 0) {
|
|
287
295
|
return { content: [{ type: "text", text: "No web captures specified." }], isError: true };
|
|
@@ -293,7 +301,11 @@ export async function takeScreenshotBatch(
|
|
|
293
301
|
const page = await browser.newPage();
|
|
294
302
|
|
|
295
303
|
try {
|
|
296
|
-
await page.setViewport({
|
|
304
|
+
await page.setViewport({
|
|
305
|
+
width: viewport.width,
|
|
306
|
+
height: viewport.height,
|
|
307
|
+
deviceScaleFactor: scale,
|
|
308
|
+
});
|
|
297
309
|
if (theme) {
|
|
298
310
|
await page.emulateMediaFeatures([{ name: "prefers-color-scheme", value: theme }]);
|
|
299
311
|
}
|
|
@@ -330,7 +342,10 @@ export async function takeScreenshotBatch(
|
|
|
330
342
|
const content: ScreenshotResult["content"] = [];
|
|
331
343
|
for (const s of snapshots) {
|
|
332
344
|
content.push({ type: "image" as const, data: s.data, mimeType: "image/png" });
|
|
333
|
-
content.push({
|
|
345
|
+
content.push({
|
|
346
|
+
type: "text" as const,
|
|
347
|
+
text: JSON.stringify({ screen: s.screen, path: s.path, viewport, scale, theme: themeLabel }, null, 2),
|
|
348
|
+
});
|
|
334
349
|
}
|
|
335
350
|
return { content };
|
|
336
351
|
} finally {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openuispec",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "A semantic UI specification format for AI-native, platform-native app development",
|
|
@@ -19,7 +19,10 @@
|
|
|
19
19
|
"schema/",
|
|
20
20
|
"spec/",
|
|
21
21
|
"docs/",
|
|
22
|
-
"examples
|
|
22
|
+
"examples/*/openuispec/**",
|
|
23
|
+
"examples/*/openuispec.yaml",
|
|
24
|
+
"examples/*/README.md",
|
|
25
|
+
"scripts/",
|
|
23
26
|
"README.md",
|
|
24
27
|
"LICENSE"
|
|
25
28
|
],
|
package/prepare/index.ts
CHANGED
|
@@ -135,6 +135,11 @@ export interface PrepareResult {
|
|
|
135
135
|
commit: string | null;
|
|
136
136
|
branch: string | null;
|
|
137
137
|
};
|
|
138
|
+
baseline_status?: {
|
|
139
|
+
output_exists: boolean;
|
|
140
|
+
snapshot_exists: boolean;
|
|
141
|
+
action_needed: string | null;
|
|
142
|
+
};
|
|
138
143
|
summary: {
|
|
139
144
|
changed: number;
|
|
140
145
|
added: number;
|
|
@@ -1103,6 +1108,10 @@ function buildBootstrapPrepareResult(cwd: string, target: string, includeContent
|
|
|
1103
1108
|
);
|
|
1104
1109
|
}
|
|
1105
1110
|
|
|
1111
|
+
const outputDirExists = existsSync(outputDir);
|
|
1112
|
+
const snapshotPath = join(outputDir, ".openuispec-state.json");
|
|
1113
|
+
const snapshotFileExists = existsSync(snapshotPath);
|
|
1114
|
+
|
|
1106
1115
|
return {
|
|
1107
1116
|
mode: "bootstrap",
|
|
1108
1117
|
project: projectName,
|
|
@@ -1116,6 +1125,13 @@ function buildBootstrapPrepareResult(cwd: string, target: string, includeContent
|
|
|
1116
1125
|
commit: null,
|
|
1117
1126
|
branch: null,
|
|
1118
1127
|
},
|
|
1128
|
+
baseline_status: {
|
|
1129
|
+
output_exists: outputDirExists,
|
|
1130
|
+
snapshot_exists: snapshotFileExists,
|
|
1131
|
+
action_needed: outputDirExists && !snapshotFileExists
|
|
1132
|
+
? `Baseline pending — when satisfied with the generated output, run: openuispec drift --snapshot --target ${target}`
|
|
1133
|
+
: null,
|
|
1134
|
+
},
|
|
1119
1135
|
summary: {
|
|
1120
1136
|
changed: 0,
|
|
1121
1137
|
added: 0,
|