feat: add visiontest init command for project-level agent setup#33
feat: add visiontest init command for project-level agent setup#33docer1990 wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a first-class CLI surface (alongside the existing MCP stdio server) and introduces visiontest init --agent ... to generate project-local SKILL.md files for supported coding agents using instructions embedded in the JAR.
Changes:
- Add
initsubcommand that writes agent-specific project paths with YAML frontmatter + embedded instructions content. - Add CLI mode (Clikt) with argument-based dispatch in
Main.kt, plus shared registrar “extracted” suspend functions to reuse MCP business logic. - Update installer, docs, and release workflow assumptions; add unit/integration-style tests for CLI and extracted registrars.
Reviewed changes
Copilot reviewed 50 out of 50 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| openspec/changes/add-init-command/tasks.md | Task breakdown for init command + resource embedding work |
| openspec/changes/add-init-command/specs/init-command/spec.md | Spec for visiontest init behavior, paths, output, and errors |
| openspec/changes/add-init-command/proposal.md | Rationale and scope for project-local agent setup via init |
| openspec/changes/add-init-command/design.md | Design decisions for embedded resource + agent mapping + no platform flag |
| openspec/changes/add-init-command/.openspec.yaml | Openspec metadata for init command change |
| openspec/changes/add-cli-mode/tasks.md | Task plan for CLI mode, dispatch, refactor, and tests |
| openspec/changes/add-cli-mode/specs/cli-mode/spec.md | Spec for MCP-vs-CLI dispatch, platform flag, exit codes, and MVP commands |
| openspec/changes/add-cli-mode/proposal.md | Rationale for adding CLI facade and shared implementation |
| openspec/changes/add-cli-mode/design.md | Detailed design decisions for CLI mode and handler extraction |
| openspec/changes/add-cli-mode/.openspec.yaml | Openspec metadata for CLI mode change |
| install.sh | Removes agent setup, adds --local-jar install path, updates summary output |
| docs/installation.md | Documents CLI usage and visiontest init guidance post-install |
| app/src/test/kotlin/com/example/visiontest/tools/IOSDeviceToolRegistrarTest.kt | Tests extracted iOS device registrar functions directly |
| app/src/test/kotlin/com/example/visiontest/tools/AndroidDeviceToolRegistrarTest.kt | Tests extracted Android device registrar functions directly |
| app/src/test/kotlin/com/example/visiontest/tools/AndroidAutomationToolRegistrarTest.kt | Tests extracted Android automation registrar functions with MockWebServer |
| app/src/test/kotlin/com/example/visiontest/cli/VisionTestCliTest.kt | CLI option parsing tests (platform, args, direction choice) |
| app/src/test/kotlin/com/example/visiontest/cli/InitCommandTest.kt | Tests init command parsing, mapping, idempotency, and classpath resource load |
| app/src/test/kotlin/com/example/visiontest/cli/CliErrorHandlerTest.kt | Tests exit-code mapping behavior of CLI error handler |
| app/src/test/kotlin/com/example/visiontest/cli/CliCommandIntegrationTest.kt | Integration-style delegation tests for CLI path using mocked backends |
| app/src/test/kotlin/com/example/visiontest/MainDispatchTest.kt | Tests MCP-vs-CLI routing based on args |
| app/src/main/kotlin/com/example/visiontest/tools/IOSDeviceToolRegistrar.kt | Extracts internal suspend functions for iOS device tools; MCP delegates |
| app/src/main/kotlin/com/example/visiontest/tools/IOSAutomationToolRegistrar.kt | Extracts internal suspend functions for iOS automation tools; MCP delegates |
| app/src/main/kotlin/com/example/visiontest/tools/AndroidDeviceToolRegistrar.kt | Extracts internal suspend functions for Android device tools; MCP delegates |
| app/src/main/kotlin/com/example/visiontest/tools/AndroidAutomationToolRegistrar.kt | Extracts internal suspend functions for Android automation tools; MCP delegates |
| app/src/main/kotlin/com/example/visiontest/cli/commands/TapByCoordinatesCommand.kt | Adds CLI tap command |
| app/src/main/kotlin/com/example/visiontest/cli/commands/SwipeDirectionCommand.kt | Adds CLI swipe-by-direction command |
| app/src/main/kotlin/com/example/visiontest/cli/commands/StartAutomationServerCommand.kt | Adds CLI start server command |
| app/src/main/kotlin/com/example/visiontest/cli/commands/ScreenshotCommand.kt | Adds CLI screenshot command |
| app/src/main/kotlin/com/example/visiontest/cli/commands/PressHomeCommand.kt | Adds CLI press-home command |
| app/src/main/kotlin/com/example/visiontest/cli/commands/PressBackCommand.kt | Adds CLI press-back (Android-only) command |
| app/src/main/kotlin/com/example/visiontest/cli/commands/LaunchAppCommand.kt | Adds CLI launch-app command |
| app/src/main/kotlin/com/example/visiontest/cli/commands/InstallAutomationServerCommand.kt | Adds CLI install automation server (Android-only) command |
| app/src/main/kotlin/com/example/visiontest/cli/commands/InputTextCommand.kt | Adds CLI input-text command |
| app/src/main/kotlin/com/example/visiontest/cli/commands/InitCommand.kt | Adds visiontest init command to write SKILL.md files from embedded instructions |
| app/src/main/kotlin/com/example/visiontest/cli/commands/GetUiHierarchyCommand.kt | Adds CLI UI-hierarchy command |
| app/src/main/kotlin/com/example/visiontest/cli/commands/GetInteractiveElementsCommand.kt | Adds CLI interactive-elements command |
| app/src/main/kotlin/com/example/visiontest/cli/commands/GetDeviceInfoCommand.kt | Adds CLI device-info command |
| app/src/main/kotlin/com/example/visiontest/cli/commands/AutomationServerStatusCommand.kt | Adds CLI server-status command |
| app/src/main/kotlin/com/example/visiontest/cli/VisionTestCli.kt | Root CLI command wiring for subcommands (lazy ComponentHolder) |
| app/src/main/kotlin/com/example/visiontest/cli/PlatformOption.kt | Adds Platform enum and reusable --platform option helpers |
| app/src/main/kotlin/com/example/visiontest/cli/ComponentHolder.kt | CLI object graph wiring mirroring MCP path + shutdown hook |
| app/src/main/kotlin/com/example/visiontest/cli/CliExit.kt | Adds typed CLI exit code enum + CliExit exception |
| app/src/main/kotlin/com/example/visiontest/cli/CliErrorHandler.kt | Adds exit-code gateway and server-running guard helper |
| app/src/main/kotlin/com/example/visiontest/Main.kt | Dispatches between MCP server and CLI based on args; handles CLI errors/help |
| app/build.gradle.kts | Adds Clikt dependency and embeds AGENT_INSTRUCTIONS.md into resources |
| README.md | Documents CLI usage, init agent setup, and exit codes |
| LEARNING.md | Documents dual-facade pattern and handler extraction rationale |
| CONTRIBUTING.md | Updates repo structure and testing notes; adds installer testing section |
| CLAUDE.md | Adds CLI usage and exit code documentation for agents |
| AGENT_INSTRUCTIONS.md | New shared instruction content embedded into JAR for init |
Implement 'visiontest init --agent claude,opencode,codex' that writes SKILL.md files to each agent's project-level directory. Instructions are embedded as a JAR resource via Gradle processResources — no network needed. - InitCommand with comma-separated --agent validation and YAML frontmatter - 7 unit tests covering single/multi agent, errors, idempotency - Remove all agent setup code from install.sh (~120 lines) - Remove AGENT_INSTRUCTIONS.md from release workflow assets - Update CLAUDE.md, README.md, docs/installation.md
- Close InputStream with .use {} to prevent resource leak
- Validate blank agent names in comma-separated list
- Use CliktError instead of error() for missing JAR resource
- Use JUnit @tempdir for automatic test cleanup
- Add stricter YAML frontmatter assertions in tests
- Add test for blank agent name edge case
- Mark all tasks.md checkboxes as complete
17712a8 to
dc56d3c
Compare
| override fun run() { | ||
| // Validate agent names | ||
| val invalid = agents.filter { it.isBlank() || it !in AGENT_PATHS } | ||
| if (invalid.isNotEmpty()) { | ||
| throw UsageError( | ||
| "Unknown agent(s): ${invalid.joinToString()}. Valid agents: ${AGENT_PATHS.keys.joinToString()}" | ||
| ) | ||
| } | ||
|
|
||
| val instructions = resourceLoader(RESOURCE_PATH) | ||
| ?: throw CliktError("Internal error: embedded resource '$RESOURCE_PATH' not found in JAR") | ||
|
|
||
| val content = YAML_FRONTMATTER + instructions | ||
|
|
||
| for (agent in agents) { | ||
| val relativePath = AGENT_PATHS.getValue(agent) | ||
| val target = workingDir.resolve(relativePath) | ||
| target.parent.createDirectories() | ||
| target.writeText(content) | ||
| echo(" wrote $target") | ||
| } | ||
|
|
||
| echo("Initialized ${agents.size} agent skill file(s).") | ||
| } |
There was a problem hiding this comment.
InitCommand is the only CLI subcommand that doesn’t route its work through runCliCommand like the rest of the commands (e.g., AutomationServerStatusCommand), so it bypasses the project’s standard stdout/stderr + exit-code mapping layer. To keep CLI behavior consistent and make future error handling/testing uniform, consider wrapping the command body with runCliCommand and returning the combined output string (instead of calling echo directly).
| private val agents by option("--agent", help = "Comma-separated agent names: claude, opencode, codex") | ||
| .split(",") | ||
| .required() | ||
|
|
There was a problem hiding this comment.
--agent values are split on commas but not trimmed, so inputs like --agent claude, opencode will produce an unexpected “unknown agent” error because of the leading space. Consider trimming each entry after splitting (and validating on the trimmed values) to make the flag more robust.
| // Validate agent names | ||
| val invalid = agents.filter { it.isBlank() || it !in AGENT_PATHS } | ||
| if (invalid.isNotEmpty()) { | ||
| throw UsageError( | ||
| "Unknown agent(s): ${invalid.joinToString()}. Valid agents: ${AGENT_PATHS.keys.joinToString()}" | ||
| ) | ||
| } |
There was a problem hiding this comment.
When the --agent list contains a blank entry (e.g., --agent ,claude or claude,), the error message currently renders as Unknown agent(s): . ... because the invalid value is an empty string. Handling blank entries separately (with an explicit message like “blank agent name in --agent list”) would make the failure actionable.
Summary
Adds
visiontest init --agent claude,opencode,codex— a CLI command that writes project-level SKILL.md files so AI coding agents discover VisionTest instructions automatically. Replaces the global home-directory agent setup previously done byinstall.sh.Motivation
install.shmixed two concerns: binary installation and agent configuration. Global home-directory config files can't be shared via version control, so teammates had to run the installer independently. Project-level SKILL.md files are committable and shareable.What changed
New
InitCommand.kt— Clikt command with--agentoption (comma-separated, validated). Writes SKILL.md with YAML frontmatter to each agent's project-level path.processResourcestask — copiesAGENT_INSTRUCTIONS.mdinto the JAR as a classpath resource. No network needed at init time.Modified
install.sh— removed ~120 lines of agent setup code (download_agent_instructions,install_agent_instructions,append_with_markers,--skip-agent-setupflag). Summary now points users tovisiontest init.release.yaml— removedAGENT_INSTRUCTIONS.mdfrom release assets.CLAUDE.md,README.md,docs/installation.md— document theinitcommand.Agent path mapping
claude.claude/skills/visiontest/SKILL.mdopencode.opencode/skills/visiontest/SKILL.mdcodex.agents/skills/visiontest/SKILL.mdUsage
# In any project directory visiontest init --agent claude,opencode,codex Testing - ./gradlew :app:test — all passing - bash -n install.sh — syntax check passing