Skip to content

feat: non-interactive CLI mode for AI agents and CI#2

Draft
fernandomg wants to merge 34 commits intomainfrom
feat/non-interactive-cli
Draft

feat: non-interactive CLI mode for AI agents and CI#2
fernandomg wants to merge 34 commits intomainfrom
feat/non-interactive-cli

Conversation

@fernandomg
Copy link
Member

@fernandomg fernandomg commented Mar 20, 2026

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, --info flags via meow
  • Non-interactive path: validates flags, runs operations, outputs JSON to stdout
  • --info flag for agent feature discovery (structured JSON metadata)
  • Operations layer (source/operations/) extracted from TUI components — shared by both paths
  • execFile (no shell) for all commands that touch user input
  • ink-spawn replaced by child_process via the operations layer
  • 124 vitest tests focused on the agentic interface
  • AGENTS.md, architecture.md, PR template

Review feedback addressed

  • createEnvFile.ts — replaced shell exec('cp ...') with fs/promises.copyFile (73d35d3)
  • CloneRepo.tsx, Install.tsx, FileCleanup.tsx — catch unknown error type in handlers (b4eea77)
  • exec.ts — replaced buffered exec/execFile with spawn to eliminate maxBuffer limit (976b2b2)
  • cli.tsx — aligned help text bin name with package.json (dappbooster, not dappbooster-starter) (7797726)
  • architecture.md — updated exec.ts docs to match spawn-based implementation (5322d74)
  • nonInteractive.ts — deduplicate --features flag values preserving order (cc34435)
  • cleanupFiles.ts — replaced shell exec() with execFile() 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 from featureDefinitions.description comment (bcb2d44)
  • CloneRepo.tsx, Install.tsx, FileCleanup.tsx — restored per-step progress display with onProgress callbacks matching the original ink-spawn stepper UX (b1870e9)
  • nonInteractive.ts, cli.tsx — eliminated all process.exit() calls, replaced with process.exitCode + throw/branching to prevent stdout truncation when piped (106c6f0, c8dc7fb)
  • cleanupFiles.ts — added -f flag to all single-file rm calls for idempotent cleanup (d0e4592)
  • CloneRepo.tsx, Install.tsx, FileCleanup.tsx — show failed step with red cross marker on error instead of hiding it (687f8d5)
  • Tests — added missing projectDirectoryExists coverage for custom mode, fixed mock return types from '' to undefined to match Promise<void> signatures (a2b7c92)

Acceptance criteria

  • --ni --name my_app --mode full installs everything and outputs success JSON
  • --ni --name my_app --mode custom --features demo,subgraph installs selected features only
  • --info outputs parseable feature metadata
  • Invalid flags produce JSON errors with exit code 1
  • Interactive TUI displays per-step progress with checkmarks and error markers
  • All 124 tests pass, build and lint clean

Test plan

  • pnpm build && pnpm lint && pnpm test — all green
  • pnpm test:coverage — nonInteractive 95%, info/config/operations 100%
  • Manual verification of --info output and edge cases (empty features, trailing commas, duplicates, shell injection)

Breaking changes

None.

Checklist

  • Self-reviewed my own diff
  • Tests added or updated
  • Docs updated (if applicable)
  • No unrelated changes bundled in

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
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@fernandomg fernandomg self-assigned this Mar 20, 2026
…File

No child process needed for a simple file copy. Uses Node.js native
copyFile which is faster and avoids shell buffering/quoting pitfalls.
@fernandomg
Copy link
Member Author

createEnvFile.ts — Fixed in 73d35d3. Replaced shell exec('cp ...') with fs/promises.copyFile. No child process needed at all.

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.
@fernandomg
Copy link
Member Author

CloneRepo.tsx, Install.tsx, FileCleanup.tsx — Fixed in b4eea77. All three component error handlers now catch unknown and derive a safe message via error instanceof Error ? error.message : String(error).

… 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).
@fernandomg
Copy link
Member Author

exec.ts maxBuffer — Fixed in 976b2b2. Replaced child_process.exec/execFile (which buffer stdout in memory) with spawn using stdio: ['ignore', 'ignore', 'pipe']. Stdout is discarded (no caller uses it), stderr is piped for error diagnostics. No buffer limit at all.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants