feat: non-interactive CLI mode for AI agents and CI#2
feat: non-interactive CLI mode for AI agents and CI#2fernandomg wants to merge 34 commits intomainfrom
Conversation
Add meow dependency for CLI arg parsing and remove ink-spawn. Expand config.ts with full feature definitions and update utils for the new config structure. Extract reusable operations (cloneRepo, createEnvFile, installPackages, cleanupFiles) with a shared exec helper.
Rewrite cli.tsx with meow for arg parsing and routing between interactive and non-interactive modes. Add --info output builder and non-interactive execution path that runs the full setup (clone, install, env, cleanup) without the TUI.
…nitions Update CloneRepo, Install, FileCleanup, OptionalPackages, and PostInstall to use the extracted operations and featureDefinitions. Remove now-unused components (Commands, CustomInstallation, FullInstallation, InstallAllPackages). Clean up app.tsx by removing hybrid flag pre-population.
Full mode installs everything, so the success output should list all feature names instead of an empty array. This makes the features and postInstall fields consistent for agents parsing the JSON.
…nput cloneRepo previously interpolated projectName into shell command strings via exec(). While isValidName restricts to [a-zA-Z0-9_], this switches to execFile (no shell) for defense-in-depth. Only the git checkout with $() substitution still uses shell exec since it has no user input.
Filter empty strings from parsed features so that --features "", --features "," and trailing commas don't produce confusing error messages with blank feature names. An effectively empty features list after filtering is treated as missing --features.
Test suite (91 tests) focused on the agent-facing interface: - nonInteractive: flag validation (missing/invalid name, mode, features), full-mode execution (operation order, all features in output, postInstall), custom-mode execution (feature selection, partial postInstall), edge cases (empty features, trailing commas, whitespace trimming), error handling (operation failures produce JSON errors), JSON output format (parseable, required fields, absolute path) - info: JSON structure, all features present with description/default, postInstall conditional, no internal fields leaked, modes present - operations: cloneRepo (5-step sequence, execFile for user input, exec only for shell substitution, no projectName in shell strings), createEnvFile (correct cp command and cwd), installPackages (full/custom/all-selected modes, pnpm remove with correct packages, postinstall after remove, selected packages excluded), cleanupFiles (full-mode no-op, per-feature file removal, subgraph compound condition, package.json script patching, .install-files last) - utils: isValidName, isFeatureSelected, getPackagesToRemove, getPostInstallMessages Coverage: nonInteractive 95%, info/config/all operations 100%.
Reflect the current architecture: dual-mode entry point, operations layer, non-interactive path, info output, test suite, and updated config.ts role as single source of truth.
Covers tech stack, project structure, key abstractions (feature definitions, operations layer, shell execution), data flow for both interactive and non-interactive paths, how to add features, how to add operations, and security patterns. Link from AGENTS.md.
runNonInteractive() is async but was called fire-and-forget. If an unexpected error escapes the internal try/catch, Node.js would emit an unhandled rejection instead of clean JSON. Add .catch() to ensure errors always produce valid JSON output with exit code 1.
installPackages interpolated package names from featureDefinitions into a shell command string via exec(). While the data is developer- controlled, this contradicts the stated security principle. Switch to execFile with an args array for consistency with cloneRepo.
- Add .catch() to interactive path for unhandled import failures - Match JSON formatting (null, 2) in cli.tsx catch with fail() - Remove redundant InstallationType cast in app.tsx PostInstall props
Agents couldn't tell whether they forgot --features or passed a garbage value. Now: missing flag says "requires --features", while a present but empty-after-parsing value says "value is empty".
All pnpm commands (i, remove, run postinstall) now use execFile instead of exec, eliminating shell interpretation entirely. No command in installPackages needs shell features.
- Document execFile usage in cloneRepo and installPackages operations - Add null guards to getLastJsonOutput and getWrittenPackageJson test helpers so failures produce clear messages instead of JSON parse errors
There was a problem hiding this comment.
Pull request overview
Adds a non-interactive CLI path (for AI agents/CI) alongside the existing Ink TUI by extracting installation side effects into a shared source/operations/ layer, plus structured --info JSON and a new Vitest test suite.
Changes:
- Introduces
meow-based CLI parsing with--ni/--non-interactive,--name,--mode,--features, and--info. - Extracts cloning/env/package-install/cleanup logic into
source/operations/*shared by interactive + non-interactive flows. - Adds Vitest + coverage config and a comprehensive test suite for the agentic interface and operations.
Reviewed changes
Copilot reviewed 34 out of 36 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| vitest.config.ts | Adds Vitest + V8 coverage configuration. |
| tsconfig.json | Excludes source/__tests__ from tsc builds. |
| source/utils/utils.ts | Refactors utilities around typed feature definitions; adds post-install messaging + directory existence helper. |
| source/operations/installPackages.ts | Implements install logic using operations layer (full vs custom). |
| source/operations/index.ts | Barrel exports for operations. |
| source/operations/exec.ts | Adds exec/execFile wrappers for running commands. |
| source/operations/createEnvFile.ts | Operation to create .env.local. |
| source/operations/cloneRepo.ts | Operation to clone repo + checkout latest tag + re-init git. |
| source/operations/cleanupFiles.ts | Operation to remove deselected feature files and patch package.json. |
| source/nonInteractive.ts | Implements non-interactive flag validation, sequential operations, JSON output/error handling. |
| source/info.ts | Implements --info JSON metadata output for feature discovery. |
| source/constants/config.ts | Centralizes feature metadata into featureDefinitions + typed FeatureName. |
| source/components/steps/PostInstall.tsx | Adapts feature selection checks to new typed feature model. |
| source/components/steps/OptionalPackages.tsx | Builds multiselect options from featureDefinitions (no hardcoded list). |
| source/components/steps/Install/InstallAllPackages.tsx | Removes old Ink Spawn-based installer step. |
| source/components/steps/Install/Install.tsx | Replaces Ink Spawn scripting with operations-layer calls and basic status rendering. |
| source/components/steps/Install/FullInstallation.tsx | Removes old full-install Ink Spawn wrapper. |
| source/components/steps/Install/CustomInstallation.tsx | Removes old custom-install Ink Spawn implementation. |
| source/components/steps/FileCleanup.tsx | Replaces inline cleanup scripting with operations-layer calls and basic status rendering. |
| source/components/steps/CloneRepo/Commands.tsx | Removes old Ink Spawn command runner. |
| source/components/steps/CloneRepo/CloneRepo.tsx | Replaces Ink Spawn clone UI with operations-layer call and basic status rendering. |
| source/cli.tsx | Adds meow routing between --info, non-interactive JSON mode, and interactive Ink mode. |
| source/app.tsx | Minor refactor for skipFeatures derived state. |
| source/tests/utils.test.ts | Adds unit tests for new utils/feature logic. |
| source/tests/operations/installPackages.test.ts | Adds tests asserting correct pnpm command usage and no-shell behavior. |
| source/tests/operations/createEnvFile.test.ts | Adds tests for env file creation operation. |
| source/tests/operations/cloneRepo.test.ts | Adds tests verifying clone/checkout/init command sequence. |
| source/tests/operations/cleanupFiles.test.ts | Adds tests for cleanup behavior and package.json patching. |
| source/tests/nonInteractive.test.ts | Adds comprehensive validation/execution/JSON-output tests for non-interactive mode. |
| source/tests/info.test.ts | Adds tests for --info JSON schema/content. |
| package.json | Adds meow, vitest, and coverage scripts/deps; removes ink-spawn. |
| pnpm-lock.yaml | Locks new dependencies (meow, vitest, coverage) and removes ink-spawn. |
| architecture.md | Documents the new architecture, operations layer, data flow, and security model. |
| CLAUDE.md | Points Claude agents to AGENTS.md. |
| AGENTS.md | Adds agent-focused repo guidance, conventions, and test instructions. |
| .github/PULL_REQUEST_TEMPLATE.md | Adds a PR template aligning with the repo’s workflow. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…File No child process needed for a simple file copy. Uses Node.js native copyFile which is faster and avoids shell buffering/quoting pitfalls.
|
createEnvFile.ts — Fixed in 73d35d3. Replaced shell |
CloneRepo, Install, and FileCleanup all assumed .catch received an Error instance. If a non-Error value is rejected, error.message would be undefined. Now catches unknown and derives a safe message string.
|
CloneRepo.tsx, Install.tsx, FileCleanup.tsx — Fixed in b4eea77. All three component error handlers now catch |
… limit The previous implementation used child_process.exec/execFile which buffer all stdout in memory (default 1MB). pnpm i output can exceed this. Since no caller uses stdout, switch to spawn with stdio ignore for stdout and pipe for stderr (to preserve error diagnostics).
|
exec.ts maxBuffer — Fixed in 976b2b2. Replaced |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 34 out of 36 changed files in this pull request and generated 3 comments.
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 35 out of 37 changed files in this pull request and generated 3 comments.
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The description field comment said "--info output, --help text" but the help text is hardcoded in cli.tsx, not generated from this field.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 35 out of 37 changed files in this pull request and generated 2 comments.
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The refactored TUI components showed a single flat status line instead of the original per-operation stepper. Added an onProgress callback to cloneRepo, installPackages, and cleanupFiles so the TUI renders each step with its own status: green checkmark for completed, dimmed circle with Working... for in-progress, red cross for errors. Non-interactive path omits the callback with zero overhead.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 35 out of 37 changed files in this pull request and generated 2 comments.
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
process.exit(1) can terminate before stdout flushes when piped, truncating JSON error output. Replaced with process.exitCode = 1 and throw, letting the event loop drain and flush stdout naturally before the process exits. Ref: https://nodejs.org/api/process.html#processexitcode
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 35 out of 37 changed files in this pull request and generated 2 comments.
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Restructured cli.tsx routing as if/else-if/else to eliminate the process.exit(0) after --info output. No process.exit() calls remain in the source — all paths exit naturally via event loop drain. Ref: https://nodejs.org/api/process.html#processexitcode
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 35 out of 37 changed files in this pull request and generated 5 comments.
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Single-file rm calls without -f would fail if the file is missing, e.g. if the upstream template changes between tags. Added -f to all single-file deletes for idempotent cleanup. Directory deletes already use -rf.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 35 out of 37 changed files in this pull request and generated 3 comments.
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
When an operation errored, the step that failed was hidden — the rendering logic sliced it off as incomplete and only showed it during the running state. Extracted deriveStepDisplay() to compute completed, current, and failed steps from the steps array and status. All three TUI components now show the failed step with a red cross marker.
Add test for projectDirectoryExists check in custom mode validation path, which was only covered for full mode. Fix all exec/execFile mock return values from '' to undefined to match the actual Promise<void> signatures.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 35 out of 37 changed files in this pull request and generated 2 comments.
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
AGENTS.md and architecture.md claimed all consumers read from featureDefinitions, but CLI --help text hardcodes its own feature list. Updated docs to explicitly note the exception.
Summary
Adds a non-interactive mode so AI agents and CI pipelines can install dAppBooster projects via flags instead of the interactive TUI.
Changes
--name,--mode,--features,--ni,--infoflags via meow--infoflag for agent feature discovery (structured JSON metadata)source/operations/) extracted from TUI components — shared by both pathsexecFile(no shell) for all commands that touch user inputink-spawnreplaced bychild_processvia the operations layerAGENTS.md,architecture.md, PR templateReview feedback addressed
createEnvFile.ts— replaced shellexec('cp ...')withfs/promises.copyFile(73d35d3)CloneRepo.tsx,Install.tsx,FileCleanup.tsx— catchunknownerror type in handlers (b4eea77)exec.ts— replaced bufferedexec/execFilewithspawnto eliminate maxBuffer limit (976b2b2)cli.tsx— aligned help text bin name withpackage.json(dappbooster, notdappbooster-starter) (7797726)architecture.md— updated exec.ts docs to match spawn-based implementation (5322d74)nonInteractive.ts— deduplicate--featuresflag values preserving order (cc34435)cleanupFiles.ts— replaced shellexec()withexecFile()for rm/mkdir/cp commands that don't need shell features (52d4361)exec.ts— include signal name in error when process is killed instead of misleading "exit code null" (461ea7a)architecture.md— removed incorrect "--help text" claim fromfeatureDefinitions.descriptioncomment (bcb2d44)CloneRepo.tsx,Install.tsx,FileCleanup.tsx— restored per-step progress display withonProgresscallbacks matching the original ink-spawn stepper UX (b1870e9)nonInteractive.ts,cli.tsx— eliminated allprocess.exit()calls, replaced withprocess.exitCode+ throw/branching to prevent stdout truncation when piped (106c6f0, c8dc7fb)cleanupFiles.ts— added-fflag to all single-filermcalls for idempotent cleanup (d0e4592)CloneRepo.tsx,Install.tsx,FileCleanup.tsx— show failed step with red cross marker on error instead of hiding it (687f8d5)projectDirectoryExistscoverage for custom mode, fixed mock return types from''toundefinedto matchPromise<void>signatures (a2b7c92)Acceptance criteria
--ni --name my_app --mode fullinstalls everything and outputs success JSON--ni --name my_app --mode custom --features demo,subgraphinstalls selected features only--infooutputs parseable feature metadataTest plan
pnpm build && pnpm lint && pnpm test— all greenpnpm test:coverage— nonInteractive 95%, info/config/operations 100%--infooutput and edge cases (empty features, trailing commas, duplicates, shell injection)Breaking changes
None.
Checklist