-
Notifications
You must be signed in to change notification settings - Fork 0
docs: extract installation details from CLAUDE.md and reduce duplication #29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| # Installation & Distribution | ||
|
|
||
| ## One-Command Installer (`install.sh`) | ||
|
|
||
| Users install with `curl -fsSL https://github.com/docer1990/visiontest/releases/latest/download/install.sh | bash`. The script: | ||
| 1. Detects OS (macOS/Linux) and arch (arm64/x86_64) | ||
| 2. Validates Java 17+ with platform-specific install suggestions | ||
| 3. Fetches latest release tag from GitHub API, validates format (`v[0-9][0-9A-Za-z._-]*`) and rejects dangerous characters | ||
| 4. Downloads `visiontest.jar` + SHA-256 checksum, verifies integrity | ||
| 5. Downloads Android APKs (`automation-server.apk`, `automation-server-test.apk`) + checksums, verifies integrity | ||
| 6. On macOS arm64: downloads `ios-automation-server.tar.gz` + checksum, extracts pre-built iOS XCUITest bundle to `ios-automation-server/` subdirectory (skipped on Linux and macOS x86_64) | ||
| 7. Installs JAR, APKs, and iOS bundle to `~/.local/share/visiontest/` (customizable via `VISIONTEST_DIR` env var, must be under `$HOME`) | ||
| 8. Creates wrapper script at `~/.local/bin/visiontest`, ensures PATH | ||
| 9. Does not modify Claude Desktop configuration; use `run-visiontest.sh` or manual setup for Claude integration. | ||
|
|
||
| **Security hardening:** `umask 077`, explicit `chmod` on all files/dirs, tag validation, checksum verification, install path restricted to `$HOME`. | ||
|
|
||
| ## Release Workflow (`.github/workflows/release.yaml`) | ||
|
|
||
| Triggered by git tags matching `v*`. The workflow runs the test suite, builds the fat JAR via `shadowJar`, Android APKs, and the pre-built iOS XCUITest bundle (on a macOS runner), generates SHA-256 checksums, and creates a GitHub Release with the following assets: `visiontest.jar`, `visiontest.jar.sha256`, `automation-server.apk`, `automation-server.apk.sha256`, `automation-server-test.apk`, `automation-server-test.apk.sha256`, `ios-automation-server.tar.gz`, `ios-automation-server.tar.gz.sha256`, `install.sh`, `run-visiontest.sh`. | ||
|
|
||
| All GitHub Actions in both workflows are pinned to commit SHAs for supply-chain security. When updating or adding actions, always use SHA-pinned references instead of floating version tags. | ||
|
|
||
| ## Launcher Script (`run-visiontest.sh`) | ||
|
|
||
| Used for development and Claude Desktop config. JAR resolution order: | ||
|
|
||
| 1. Repo build: `app/build/libs/visiontest.jar` (sets up `ANDROID_HOME`, APK path, `cd` to project root) | ||
| 2. Installed JAR: `~/.local/share/visiontest/visiontest.jar` (skips Android SDK setup) | ||
| 3. Error with build/install instructions | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - JDK 17+ | ||
| - macOS or Linux (arm64 or x86_64) | ||
| - Android Platform Tools (ADB) in PATH — for Android automation | ||
| - Xcode Command Line Tools — for iOS simulator support (macOS only). Pre-built iOS bundle requires the same Xcode major version used in CI (see release notes). For source builds or Intel Macs, the full Xcode IDE is needed. | ||
| - Android SDK — only needed for building the automation-server module from source | ||
|
|
||
| > **Quick start:** Users who just need the MCP server can run `curl -fsSL https://github.com/docer1990/visiontest/releases/latest/download/install.sh | bash` — only Java 17+ is required. |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| ## ADDED Requirements | ||
|
|
||
| ### Requirement: ToolDiscovery class encapsulates all path resolution | ||
| A `ToolDiscovery` class in the `discovery/` package SHALL encapsulate all asset discovery logic previously embedded in `ToolFactory`. It SHALL accept only a `Logger` as a constructor parameter. | ||
|
|
||
| #### Scenario: ToolDiscovery is independently constructable | ||
| - **WHEN** `ToolDiscovery(logger)` is constructed | ||
| - **THEN** it SHALL be ready to use without requiring `DeviceConfig`, `AutomationClient`, or any other `ToolFactory` dependency | ||
|
|
||
| ### Requirement: Android APK discovery | ||
| `ToolDiscovery` SHALL provide `findAutomationServerApk()` and its testable overload `findAutomationServerApk(envApkPath, searchRoots, installDir)` with identical behavior to the current `ToolFactory` implementation. | ||
|
|
||
| #### Scenario: APK found via environment variable | ||
| - **WHEN** `VISION_TEST_APK_PATH` environment variable points to an existing file | ||
| - **THEN** `findAutomationServerApk()` SHALL return that file's absolute path | ||
|
|
||
| #### Scenario: APK found via search roots | ||
| - **WHEN** no environment variable is set and the APK exists at `<searchRoot>/automation-server/build/outputs/apk/androidTest/debug/automation-server-debug-androidTest.apk` | ||
| - **THEN** `findAutomationServerApk()` SHALL return the first match found across search roots | ||
|
|
||
| #### Scenario: APK found in install directory | ||
| - **WHEN** no environment variable is set and no search root contains the APK, but `<installDir>/automation-server-test.apk` exists | ||
| - **THEN** `findAutomationServerApk()` SHALL return the install directory APK path | ||
|
|
||
| #### Scenario: No APK found | ||
| - **WHEN** no APK is found in any location | ||
| - **THEN** `findAutomationServerApk()` SHALL return null | ||
|
|
||
| ### Requirement: Main APK resolution from test APK path | ||
| `ToolDiscovery` SHALL provide `resolveMainApkPath(testApkPath)` with identical behavior to the current `ToolFactory` implementation. | ||
|
|
||
| #### Scenario: Gradle layout derivation | ||
| - **WHEN** test APK path contains `androidTest/` and `-androidTest` substrings and the derived main APK exists | ||
| - **THEN** `resolveMainApkPath()` SHALL return the derived path | ||
|
|
||
| #### Scenario: Install directory sibling lookup | ||
| - **WHEN** the test APK is named `automation-server-test.apk` and `automation-server.apk` exists in the same directory | ||
| - **THEN** `resolveMainApkPath()` SHALL return the sibling APK path | ||
|
|
||
| #### Scenario: No main APK found | ||
| - **WHEN** no derivation or sibling lookup succeeds | ||
| - **THEN** `resolveMainApkPath()` SHALL return null | ||
|
|
||
| ### Requirement: Xcode project discovery | ||
| `ToolDiscovery` SHALL provide `findXcodeProject()` with identical cascading search behavior: environment variable → CWD → project root → code source root. | ||
|
|
||
| #### Scenario: Xcode project from environment variable | ||
| - **WHEN** `VISION_TEST_IOS_PROJECT_PATH` environment variable points to a valid `.xcodeproj` directory | ||
| - **THEN** `findXcodeProject()` SHALL return its absolute path | ||
|
|
||
| #### Scenario: Xcode project from project root | ||
| - **WHEN** no environment variable is set and the `.xcodeproj` exists relative to the detected project root | ||
| - **THEN** `findXcodeProject()` SHALL return its absolute path | ||
|
|
||
| #### Scenario: No Xcode project found | ||
| - **WHEN** no `.xcodeproj` is found in any location | ||
| - **THEN** `findXcodeProject()` SHALL return null | ||
|
|
||
| ### Requirement: xctestrun bundle discovery | ||
| `ToolDiscovery` SHALL provide `findXctestrun()` and its testable overload `findXctestrun(installDir)` with identical behavior. | ||
|
|
||
| #### Scenario: xctestrun found in install directory | ||
| - **WHEN** `<installDir>/ios-automation-server/` contains `.xctestrun` files | ||
| - **THEN** `findXctestrun()` SHALL return the absolute path of the first file alphabetically | ||
|
|
||
| #### Scenario: No xctestrun found | ||
| - **WHEN** the bundle directory does not exist or contains no `.xctestrun` files | ||
| - **THEN** `findXctestrun()` SHALL return null | ||
|
|
||
| ### Requirement: Project root discovery | ||
| `ToolDiscovery` SHALL provide `findProjectRoot(startFrom)` that walks up the directory tree (max 10 levels) looking for `settings.gradle.kts` or `settings.gradle`. | ||
|
|
||
| #### Scenario: Project root found | ||
| - **WHEN** a `settings.gradle.kts` or `settings.gradle` file exists within 10 parent directories of `startFrom` | ||
| - **THEN** `findProjectRoot()` SHALL return the directory containing it | ||
|
|
||
| #### Scenario: Project root not found within depth limit | ||
| - **WHEN** no settings file exists within 10 levels up | ||
| - **THEN** `findProjectRoot()` SHALL return null | ||
|
|
||
| #### Scenario: Trailing dot in path handled | ||
| - **WHEN** `startFrom` path ends with `.` | ||
| - **THEN** `findProjectRoot()` SHALL resolve the parent correctly and search normally | ||
|
|
||
| ### Requirement: Install directory resolution | ||
| `ToolDiscovery` SHALL provide `resolveInstallDir()` with the cascading resolution: `VISIONTEST_DIR` env var → JAR directory → `~/.local/share/visiontest` default. | ||
|
|
||
| #### Scenario: Install dir from environment variable | ||
| - **WHEN** `VISIONTEST_DIR` environment variable is set and non-empty | ||
| - **THEN** `resolveInstallDir()` SHALL return a `File` pointing to that path | ||
|
|
||
| #### Scenario: Install dir default fallback | ||
| - **WHEN** no environment variable is set and not running from a JAR | ||
| - **THEN** `resolveInstallDir()` SHALL return `~/.local/share/visiontest` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| ## ADDED Requirements | ||
|
|
||
| ### Requirement: ToolScope absorbs tool registration boilerplate | ||
| The `ToolScope` class SHALL wrap `Server.addTool()` with automatic timeout enforcement, error handling via `ErrorHandler.handleToolError()`, and `CallToolResult` wrapping. Tool handlers SHALL only provide the business logic as a `suspend (CallToolRequest?) -> String` lambda. | ||
|
|
||
| #### Scenario: Tool registered via ToolScope executes within timeout | ||
| - **WHEN** a tool is registered via `ToolScope.tool()` with a 10s timeout and the handler returns in 5s | ||
| - **THEN** the tool SHALL return a `CallToolResult` containing a single `TextContent` with the handler's return value | ||
|
|
||
| #### Scenario: Tool registered via ToolScope times out | ||
| - **WHEN** a tool is registered via `ToolScope.tool()` with a 10s timeout and the handler takes longer than 10s | ||
| - **THEN** the tool SHALL return an error `CallToolResult` produced by `ErrorHandler.handleToolError()` with a `TimeoutCancellationException` | ||
|
docer1990 marked this conversation as resolved.
|
||
|
|
||
| #### Scenario: Tool registered via ToolScope handles exceptions | ||
| - **WHEN** a tool handler throws any `Exception` | ||
| - **THEN** the tool SHALL return an error `CallToolResult` produced by `ErrorHandler.handleToolError()` with the thrown exception and the tool name as context | ||
|
|
||
| #### Scenario: Tool registered with custom timeout | ||
| - **WHEN** a tool is registered with `timeoutMs = 30000` | ||
| - **THEN** the tool SHALL use 30s as its timeout instead of the default | ||
|
|
||
| ### Requirement: ToolRegistrar interface for modular registration | ||
| Each platform tool group SHALL implement the `ToolRegistrar` interface with a single `registerTools(scope: ToolScope)` method. `ToolFactory.registerAllTools()` SHALL iterate over all registrars and delegate to each. | ||
|
|
||
| #### Scenario: All tools registered via registrars | ||
| - **WHEN** `ToolFactory.registerAllTools(server)` is called | ||
| - **THEN** all 36 MCP tools SHALL be registered on the server with identical names, descriptions, and input schemas as the current monolithic implementation | ||
|
docer1990 marked this conversation as resolved.
|
||
|
|
||
| #### Scenario: Registrar receives ToolScope | ||
| - **WHEN** a `ToolRegistrar.registerTools(scope)` is called | ||
| - **THEN** the scope SHALL provide the server, logger, and default timeout configured in `ToolFactory` | ||
|
|
||
| ### Requirement: CallToolRequest parameter extraction helpers | ||
| Extension functions on `CallToolRequest?` SHALL provide type-safe parameter extraction: `requireString(key)`, `requireInt(key)`, `optionalString(key)`, `optionalInt(key)`. | ||
|
|
||
| #### Scenario: requireString returns value when present | ||
| - **WHEN** `request.requireString("packageName")` is called and `packageName` exists in the request arguments | ||
| - **THEN** it SHALL return the string value | ||
|
|
||
| #### Scenario: requireString throws when missing | ||
| - **WHEN** `request.requireString("packageName")` is called and `packageName` is not in the request arguments | ||
| - **THEN** it SHALL throw `IllegalArgumentException` with a message containing the key name | ||
|
|
||
| #### Scenario: requireInt parses integer from string | ||
| - **WHEN** `request.requireInt("x")` is called and the argument value is `"100"` | ||
| - **THEN** it SHALL return `100` as an `Int` | ||
|
|
||
| #### Scenario: requireInt throws on non-integer | ||
| - **WHEN** `request.requireInt("x")` is called and the argument value is `"abc"` | ||
| - **THEN** it SHALL throw `IllegalArgumentException` with a message indicating the key must be an integer | ||
|
|
||
| #### Scenario: optionalString returns null when missing | ||
| - **WHEN** `request.optionalString("text")` is called and `text` is not in the request arguments | ||
| - **THEN** it SHALL return `null` | ||
|
|
||
| ### Requirement: ToolHelpers object for pure utility functions | ||
| The functions `extractProperty`, `extractPattern`, and `formatAppInfo` SHALL be moved to a `ToolHelpers` object in the `tools/` package with identical behavior. | ||
|
|
||
| #### Scenario: extractProperty finds property value | ||
| - **WHEN** `ToolHelpers.extractProperty("[ro.product.model]: [Pixel 6]", "ro.product.model")` is called | ||
| - **THEN** it SHALL return `"Pixel 6"` | ||
|
|
||
| #### Scenario: extractProperty returns Unknown for missing property | ||
| - **WHEN** `ToolHelpers.extractProperty("", "ro.product.model")` is called | ||
| - **THEN** it SHALL return `"Unknown"` | ||
|
|
||
| #### Scenario: formatAppInfo extracts and formats app information | ||
| - **WHEN** `ToolHelpers.formatAppInfo(rawDumpsysOutput, "com.example.app")` is called with valid dumpsys output | ||
| - **THEN** it SHALL return a formatted string containing version name, version code, SDK targets, install dates, and up to 10 permissions | ||
|
|
||
| ### Requirement: Four platform-specific registrars | ||
| The system SHALL provide exactly four `ToolRegistrar` implementations: | ||
| - `AndroidDeviceToolRegistrar` — registers 4 Android device management tools | ||
| - `AndroidAutomationToolRegistrar` — registers 14 Android UI automation tools | ||
| - `IOSDeviceToolRegistrar` — registers 4 iOS device management tools | ||
| - `IOSAutomationToolRegistrar` — registers 10 iOS UI automation tools plus server lifecycle management | ||
|
docer1990 marked this conversation as resolved.
|
||
|
|
||
| #### Scenario: Android device tools registered | ||
| - **WHEN** `AndroidDeviceToolRegistrar.registerTools(scope)` is called | ||
| - **THEN** tools `available_device_android`, `list_apps_android`, `info_app_android`, `launch_app_android` SHALL be registered | ||
|
|
||
| #### Scenario: Android automation tools registered | ||
| - **WHEN** `AndroidAutomationToolRegistrar.registerTools(scope)` is called | ||
| - **THEN** all 14 Android automation tools SHALL be registered including `install_automation_server`, `start_automation_server`, `get_ui_hierarchy`, `find_element`, `android_tap_by_coordinates`, `android_swipe`, `android_swipe_direction`, `android_swipe_on_element`, `android_press_back`, `android_press_home`, `android_input_text`, `android_get_device_info`, `get_interactive_elements`, and `automation_server_status` | ||
|
|
||
| #### Scenario: iOS device tools registered | ||
| - **WHEN** `IOSDeviceToolRegistrar.registerTools(scope)` is called | ||
| - **THEN** tools `ios_available_device`, `ios_list_apps`, `ios_info_app`, `ios_launch_app` SHALL be registered | ||
|
|
||
| #### Scenario: iOS automation tools registered | ||
| - **WHEN** `IOSAutomationToolRegistrar.registerTools(scope)` is called | ||
| - **THEN** all 10 iOS automation tools SHALL be registered including `ios_start_automation_server`, `ios_automation_server_status`, `ios_get_ui_hierarchy`, `ios_get_interactive_elements`, `ios_tap_by_coordinates`, `ios_swipe`, `ios_swipe_direction`, `ios_find_element`, `ios_get_device_info`, `ios_press_home`, `ios_input_text`, and `ios_stop_automation_server` | ||
|
docer1990 marked this conversation as resolved.
|
||
|
|
||
| ### Requirement: ToolFactory remains the public entry point | ||
| `ToolFactory` SHALL maintain its existing constructor signature and `registerAllTools(server: Server)` method. `Main.kt` SHALL require zero changes. | ||
|
|
||
| #### Scenario: ToolFactory constructor compatibility | ||
| - **WHEN** `ToolFactory(android, ios, logger)` is constructed (using defaults for optional params) | ||
| - **THEN** it SHALL compile and function identically to the pre-refactor version | ||
|
|
||
| #### Scenario: registerAllTools delegates to registrars | ||
| - **WHEN** `toolFactory.registerAllTools(server)` is called | ||
| - **THEN** it SHALL create a `ToolScope` and pass it to each of the four registrars | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.