1414use DrevOps \VortexInstaller \Runner \ExecutableFinderAwareInterface ;
1515use DrevOps \VortexInstaller \Runner \ExecutableFinderAwareTrait ;
1616use DrevOps \VortexInstaller \Runner \RunnerInterface ;
17+ use DrevOps \VortexInstaller \Schema \ConfigValidator ;
18+ use DrevOps \VortexInstaller \Schema \SchemaGenerator ;
1719use DrevOps \VortexInstaller \Task \Task ;
1820use DrevOps \VortexInstaller \Utils \Config ;
1921use DrevOps \VortexInstaller \Utils \Env ;
@@ -53,6 +55,12 @@ class InstallCommand extends Command implements CommandRunnerAwareInterface, Exe
5355
5456 const OPTION_BUILD = 'build ' ;
5557
58+ const OPTION_SCHEMA = 'schema ' ;
59+
60+ const OPTION_VALIDATE = 'validate ' ;
61+
62+ const OPTION_AGENT_HELP = 'agent-help ' ;
63+
5664 const BUILD_RESULT_SUCCESS = 'success ' ;
5765
5866 const BUILD_RESULT_SKIPPED = 'skipped ' ;
@@ -127,6 +135,9 @@ protected function configure(): void {
127135 $ this ->addOption (static ::OPTION_URI , 'l ' , InputOption::VALUE_REQUIRED , 'Remote or local repository URI with an optional git ref set after @. ' );
128136 $ this ->addOption (static ::OPTION_NO_CLEANUP , NULL , InputOption::VALUE_NONE , 'Do not remove installer after successful installation. ' );
129137 $ this ->addOption (static ::OPTION_BUILD , 'b ' , InputOption::VALUE_NONE , 'Run auto-build after installation without prompting. ' );
138+ $ this ->addOption (static ::OPTION_SCHEMA , NULL , InputOption::VALUE_NONE , 'Output prompt schema as JSON. ' );
139+ $ this ->addOption (static ::OPTION_VALIDATE , NULL , InputOption::VALUE_NONE , 'Validate config without installing. ' );
140+ $ this ->addOption (static ::OPTION_AGENT_HELP , NULL , InputOption::VALUE_NONE , 'Output instructions for AI agents on how to use the installer. ' );
130141 }
131142
132143 /**
@@ -139,6 +150,18 @@ protected function execute(InputInterface $input, OutputInterface $output): int
139150 return Command::SUCCESS ;
140151 }
141152
153+ if ($ input ->getOption (static ::OPTION_AGENT_HELP )) {
154+ return $ this ->handleAgentHelp ($ output );
155+ }
156+
157+ if ($ input ->getOption (static ::OPTION_SCHEMA )) {
158+ return $ this ->handleSchema ($ input , $ output );
159+ }
160+
161+ if ($ input ->getOption (static ::OPTION_VALIDATE )) {
162+ return $ this ->handleValidate ($ input , $ output );
163+ }
164+
142165 Tui::init ($ output );
143166
144167 try {
@@ -266,6 +289,162 @@ protected function execute(InputInterface $input, OutputInterface $output): int
266289 return Command::SUCCESS ;
267290 }
268291
292+ /**
293+ * Handle --schema option.
294+ */
295+ protected function handleSchema (InputInterface $ input , OutputInterface $ output ): int {
296+ $ config = Config::fromString ('{} ' );
297+ $ prompt_manager = new PromptManager ($ config );
298+
299+ $ generator = new SchemaGenerator ();
300+ $ schema = $ generator ->generate ($ prompt_manager ->getHandlers ());
301+
302+ $ output ->write ((string ) json_encode ($ schema , JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ));
303+
304+ return Command::SUCCESS ;
305+ }
306+
307+ /**
308+ * Handle --validate option.
309+ */
310+ protected function handleValidate (InputInterface $ input , OutputInterface $ output ): int {
311+ $ config_option = $ input ->getOption (static ::OPTION_CONFIG );
312+
313+ if (empty ($ config_option ) || !is_string ($ config_option )) {
314+ $ output ->writeln ('The --validate option requires --config. ' );
315+
316+ return Command::FAILURE ;
317+ }
318+
319+ $ config_json = is_file ($ config_option ) ? (string ) file_get_contents ($ config_option ) : $ config_option ;
320+ $ user_config = json_decode ($ config_json , TRUE );
321+
322+ if (!is_array ($ user_config )) {
323+ $ output ->writeln ('Invalid JSON in --config. ' );
324+
325+ return Command::FAILURE ;
326+ }
327+
328+ $ config = Config::fromString ('{} ' );
329+ $ prompt_manager = new PromptManager ($ config );
330+
331+ $ validator = new ConfigValidator ();
332+ $ result = $ validator ->validate ($ user_config , $ prompt_manager ->getHandlers ());
333+
334+ $ output ->write ((string ) json_encode ($ result , JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ));
335+
336+ return $ result ['valid ' ] ? Command::SUCCESS : Command::FAILURE ;
337+ }
338+
339+ /**
340+ * Handle --agent-help option.
341+ *
342+ * Outputs instructions for AI agents on how to use the installer
343+ * programmatically via --schema and --validate.
344+ */
345+ protected function handleAgentHelp (OutputInterface $ output ): int {
346+ $ text = <<<'AGENT_HELP'
347+ # Vortex Installer - AI Agent Instructions
348+
349+ You are interacting with the Vortex installer, a CLI tool that sets up Drupal
350+ projects from the Vortex template. This guide explains how to use the installer
351+ programmatically.
352+
353+ ## Workflow
354+
355+ 1. **Discover prompts**: Run with `--schema` to get a JSON manifest of all
356+ available configuration prompts, their types, valid values, defaults, and
357+ dependencies.
358+
359+ 2. **Build a config**: Using the schema, construct a JSON object where keys are
360+ either prompt IDs (e.g., `hosting_provider`) or environment variable names
361+ (e.g., `VORTEX_INSTALLER_PROMPT_HOSTING_PROVIDER`). Set values according to
362+ the prompt types and allowed options from the schema.
363+
364+ 3. **Validate the config**: Run with `--validate --config='<json>'` to check
365+ your config without performing an installation. The output is a JSON object
366+ with `valid`, `errors`, `warnings`, and `resolved` fields.
367+
368+ 4. **Install**: Run with `--no-interaction --config='<json>' --destination=<dir>`
369+ to perform the actual installation using your validated config.
370+
371+ ## Commands
372+
373+ ```bash
374+ # Get the prompt schema
375+ php installer.php --schema
376+
377+ # Validate a config (JSON string)
378+ php installer.php --validate --config='{"name":"My Project","hosting_provider":"lagoon"}'
379+
380+ # Validate a config (JSON file)
381+ php installer.php --validate --config=config.json
382+
383+ # Install non-interactively
384+ php installer.php --no-interaction --config='<json>' --destination=./my-project
385+ ```
386+
387+ ## Schema Format
388+
389+ The `--schema` output contains a `prompts` array. Each prompt has:
390+
391+ - `id`: The prompt identifier (use as config key).
392+ - `env`: The environment variable name (alternative config key).
393+ - `type`: One of `text`, `select`, `multiselect`, `confirm`, `suggest`.
394+ - `label`: Human-readable label.
395+ - `description`: Optional description text.
396+ - `options`: For `select`/`multiselect`, an array of `{value, label}` objects
397+ representing the allowed values.
398+ - `default`: The default value if not provided.
399+ - `required`: Whether the prompt requires a value.
400+ - `depends_on`: Dependency conditions. If set, this prompt only applies when
401+ the referenced prompt has one of the specified values. A `_system` key
402+ indicates a system-state dependency (not config-based).
403+
404+ ## Value Types by Prompt Type
405+
406+ - `text` / `suggest`: string value.
407+ - `select`: string value matching one of the option values.
408+ - `multiselect`: array of strings, each matching an option value.
409+ - `confirm`: boolean (`true` or `false`).
410+
411+ ## Dependencies
412+
413+ Some prompts depend on other prompts. For example, `hosting_project_name`
414+ depends on `hosting_provider` being `lagoon` or `acquia`. If you set
415+ `hosting_provider` to `none`, you do not need to provide `hosting_project_name`.
416+
417+ When a dependency is not met:
418+ - Omitting the dependent value is OK (it will be skipped).
419+ - Providing a value triggers a warning (it will be ignored).
420+
421+ When a dependency is met:
422+ - Required prompts must have a value or they produce an error.
423+
424+ ## Validation Output
425+
426+ The `--validate` output contains:
427+
428+ - `valid`: boolean - whether the config is valid.
429+ - `errors`: array of `{prompt, message}` objects for invalid values.
430+ - `warnings`: array of `{prompt, message}` objects for ignored values.
431+ - `resolved`: object with the final merged config (your values + defaults).
432+
433+ ## Tips
434+
435+ - Start with `--schema` to understand what prompts exist.
436+ - Provide values only for prompts you want to customize; defaults will be
437+ used for the rest.
438+ - Use `--validate` to check your config before installing.
439+ - The `resolved` field in validation output shows the complete config that
440+ would be used, including defaults.
441+ AGENT_HELP;
442+
443+ $ output ->write ($ text );
444+
445+ return Command::SUCCESS ;
446+ }
447+
269448 protected function checkRequirements (): void {
270449 $ required_commands = [
271450 'git ' ,
0 commit comments