-
Notifications
You must be signed in to change notification settings - Fork 0
feat: non-interactive CLI mode for AI agents and CI #2
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 17 commits
Commits
Show all changes
39 commits
Select commit
Hold shift + click to select a range
1d85809
refactor: extract operations layer and expand feature definitions
fernandomg 900e8e6
feat: add non-interactive CLI with meow arg parsing
fernandomg 189036b
refactor: migrate TUI components to operations layer and feature defi…
fernandomg 140a01d
fix: include all features in full-mode success JSON
fernandomg 72122c9
fix: use execFile in cloneRepo to avoid shell interpolation of user i…
fernandomg eef5a61
fix: handle empty and malformed --features values
fernandomg fb2b88a
test: add vitest with coverage for non-interactive agentic flow
fernandomg 675349b
chore: update AGENTS.md for non-interactive mode and operations layer
fernandomg 4baea82
docs: add architecture.md with full agent guide
fernandomg b714974
chore: add pull request template
fernandomg 6288b80
test: add error propagation test for createEnvFile
fernandomg 69c84de
fix: catch unhandled promise rejection in non-interactive CLI path
fernandomg 3642f3d
fix: use execFile for pnpm remove to avoid shell interpolation
fernandomg 7d81baf
fix: clean up entry point error handling and redundant cast
fernandomg 28a2cfd
fix: differentiate empty --features error from missing --features
fernandomg f1f9890
refactor: use execFile exclusively in installPackages
fernandomg 5a8a646
chore: update architecture.md and add test helper guards
fernandomg 73d35d3
refactor: use fs/promises.copyFile instead of shell exec in createEnv…
fernandomg b4eea77
fix: catch unknown error type in TUI component error handlers
fernandomg 976b2b2
fix: replace buffered exec/execFile with spawn to eliminate maxBuffer…
fernandomg 7797726
fix: align CLI help text bin name with package.json
fernandomg 5322d74
fix: update architecture.md exec.ts docs to match spawn implementation
fernandomg cc34435
fix: deduplicate feature names in parseFeatures
fernandomg 52d4361
fix: replace shell exec with execFile in cleanupFiles
fernandomg 461ea7a
fix: include signal info in exec error when process is killed
fernandomg bcb2d44
fix: remove incorrect --help text claim from featureDefinitions docs
fernandomg b1870e9
fix: restore per-step progress display in TUI components
fernandomg 1719338
fix: add exec.test.ts to architecture.md file listing
fernandomg 106c6f0
fix: prevent stdout truncation on piped non-interactive errors
fernandomg c8dc7fb
fix: remove last process.exit call from --info path
fernandomg d0e4592
fix: use rm -f for single-file deletes in cleanupFiles
fernandomg 687f8d5
fix: show failed step in TUI progress display on error
fernandomg a2b7c92
test: add missing coverage and fix mock return types
fernandomg 66468bd
fix: clarify that CLI --help text is not derived from featureDefinitions
fernandomg f471b2f
fix: add missing unknown type annotation on catch parameters
fernandomg bf6067d
fix: correct doc-code inconsistencies found by cross-reference review
fernandomg 04eaaa0
refactor: replace filesystem shell commands with node:fs/promises
fernandomg 4f33846
fix: use fallback default in Install title when installationType is u…
fernandomg 1fd205b
fix: correct postInstall comment and expand How to Add a Feature guide
fernandomg 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
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,34 @@ | ||
| ## Summary | ||
|
|
||
| <!-- Why this change? What problem does it solve? Link motivation, not just mechanics. --> | ||
|
|
||
| Closes # | ||
|
|
||
| ## Changes | ||
|
|
||
| <!-- Brief description of what was changed. Bullet points work well. --> | ||
|
|
||
| - | ||
|
|
||
| ## Acceptance criteria | ||
|
|
||
| <!-- Mirror the criteria from the linked issue. Check them off as you go. --> | ||
|
|
||
| - [ ] | ||
|
|
||
| ## Test plan | ||
|
|
||
| <!-- How was this tested? Commands, screenshots, recordings — show your work. --> | ||
|
|
||
| ## Breaking changes | ||
|
|
||
| <!-- If none, delete this section. If any, describe what breaks and migration steps. --> | ||
|
|
||
| None. | ||
|
|
||
| ## Checklist | ||
|
|
||
| - [ ] Self-reviewed my own diff | ||
| - [ ] Tests added or updated | ||
| - [ ] Docs updated (if applicable) | ||
| - [ ] No unrelated changes bundled in |
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,92 @@ | ||
| # Agent Configuration | ||
|
|
||
| > `CLAUDE.md` points to this file so Claude Code picks it up automatically. Other agents (Cursor, Windsurf, etc.) read `AGENTS.md` natively. | ||
|
|
||
| --- | ||
|
|
||
| ## What This Is | ||
|
|
||
| A CLI installer tool for dAppBooster projects. It supports two modes: | ||
|
|
||
| - **Interactive** (default): React + Ink TUI that walks users through project naming, repo cloning, installation mode selection, optional packages, and post-install steps. | ||
| - **Non-interactive**: Flag-driven mode (`--ni` or auto-detected when not a TTY) for AI agents and CI. Outputs JSON to stdout. Run `--info` for feature discovery, then `--name` + `--mode` [+ `--features`] to install. | ||
|
|
||
| ## Stack & Conventions | ||
|
|
||
| | Category | Technology | Notes | | ||
| |----------|-----------|-------| | ||
| | Language | TypeScript (strict mode) | Extends `@sindresorhus/tsconfig` | | ||
| | Framework | React + Ink | Terminal UI framework | | ||
| | Arg parsing | meow | CLI flag parsing for non-interactive mode | | ||
| | Package manager | pnpm | Never npm or yarn | | ||
| | Linting/formatting | Biome | Run `pnpm lint` before committing | | ||
| | Testing | Vitest + @vitest/coverage-v8 | | | ||
| | Node | v20+ | See `.nvmrc` | | ||
| | Naming | camelCase vars/functions, PascalCase components/types | | | ||
|
|
||
| ## Code Style | ||
|
|
||
| - **Semicolons:** no | ||
| - **Quotes:** single | ||
| - **Print width:** 100 | ||
| - **Trailing commas:** none (Biome default) | ||
| - **Indent:** spaces, width 2 | ||
| - **Imports:** explicit `.js` extensions (ESM, `"type": "module"`) | ||
|
|
||
| ## Working Rules | ||
|
|
||
| - Use **pnpm** only (never npm or yarn) | ||
| - Treat `dist/` as build output — never edit directly | ||
| - User input (`projectName`) must never be interpolated into shell command strings — use `execFile` (args array) instead | ||
| - `source/constants/config.ts` is the single source of truth for feature metadata — all consumers read from it | ||
| - Components are presentation-only — business logic lives in `source/operations/` | ||
|
|
||
| ## Architecture | ||
|
|
||
| See [architecture.md](./architecture.md) for the full architecture guide, including data flow, how to add features, and security patterns. | ||
|
|
||
| Entry: `source/cli.tsx` — parses args with `meow`, routes between interactive and non-interactive paths. | ||
|
|
||
| - **Interactive path**: `source/app.tsx` — step-based state machine that renders each installer step in sequence via React + Ink | ||
| - **Non-interactive path**: `source/nonInteractive.ts` — validates flags, runs operations sequentially, outputs JSON | ||
|
|
||
| Key directories: | ||
|
|
||
| - `source/operations/` — business logic as plain async functions, shared by both paths | ||
| - `source/components/steps/` — TUI step components, presentation-only | ||
| - `source/components/` — reusable UI components (Ask, Divider, MainTitle, Multiselect) | ||
| - `source/__tests__/` — vitest test suite | ||
|
|
||
| ## Testing | ||
|
|
||
| - **Framework:** Vitest + V8 coverage | ||
| - **Run tests:** `pnpm test` / `pnpm test:coverage` | ||
| - **Structure:** `source/__tests__/` mirrors `source/` layout. Operations tests live in `source/__tests__/operations/` | ||
| - **What to test:** Non-interactive agentic flow (validation, JSON output), operations (correct shell commands), config, utils | ||
| - **What not to test:** React/Ink components, `exec.ts` internals (mocked in all consumers) | ||
| - **Mocking pattern:** Operations tests mock `exec`/`execFile` from `source/operations/exec.js`. Non-interactive tests mock the entire operations layer | ||
| - **Coverage:** Focus on the agentic interface. Test files and `source/components/` are excluded from coverage | ||
|
|
||
| ## Guardrails | ||
|
|
||
| - Do not commit secrets, API keys, or credentials | ||
| - Do not modify CI/CD pipelines without team review | ||
| - Do not skip tests or linting to make a build pass | ||
| - When in doubt, ask — don't assume | ||
|
|
||
| ## Change Strategy | ||
|
|
||
| - Prefer small, focused diffs over broad refactors | ||
| - Preserve existing UX unless the task explicitly changes it | ||
| - Avoid introducing new patterns when a project pattern already exists | ||
| - Update docs only when behavior or workflow changes | ||
|
|
||
| ## Validation Checklist | ||
|
|
||
| - `pnpm build` | ||
| - `pnpm lint` | ||
| - `pnpm test` | ||
|
|
||
| ## Release | ||
|
|
||
| GitHub Actions workflow (`.github/workflows/release.yml`) triggers on GitHub release events. Pre-releases do a dry-run; full releases publish to npm. | ||
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,3 @@ | ||
| # CLAUDE.md | ||
|
|
||
| See [AGENTS.md](./AGENTS.md) for all project guidance. |
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,190 @@ | ||
| # Architecture Overview | ||
|
|
||
| ## Tech Stack | ||
|
|
||
| | Category | Technology | Notes | | ||
| |----------|-----------|-------| | ||
| | Framework | React + Ink | Terminal UI for interactive mode | | ||
| | Language | TypeScript (strict mode) | Extends `@sindresorhus/tsconfig` | | ||
| | Arg parsing | meow | CLI flag parsing, non-interactive mode | | ||
| | Styling | Ink primitives | `<Box>`, `<Text>`, ink-gradient, ink-big-text | | ||
| | Testing | Vitest + @vitest/coverage-v8 | | | ||
| | Node | v20+ | See `.nvmrc` | | ||
|
|
||
| ## Project Structure | ||
|
|
||
| ``` | ||
| source/ | ||
| cli.tsx Entry point: meow arg parsing, mode routing | ||
| app.tsx Interactive TUI: step-based state machine | ||
| nonInteractive.ts Non-interactive: validate flags → run operations → JSON | ||
| info.ts --info JSON output for agent discovery | ||
| constants/ | ||
| config.ts Single source of truth: feature definitions, repo URL | ||
| operations/ | ||
| exec.ts exec (shell) and execFile (no shell) helpers | ||
| cloneRepo.ts Shallow clone, checkout latest tag, reinit git | ||
| createEnvFile.ts Copy .env.example → .env.local | ||
| installPackages.ts pnpm install / remove based on mode and features | ||
| cleanupFiles.ts Remove files for deselected features, patch package.json | ||
| index.ts Barrel export | ||
| components/ | ||
| steps/ TUI step components (presentation-only) | ||
| ProjectName.tsx Prompt for project name | ||
| CloneRepo/CloneRepo.tsx Clone progress display | ||
| InstallationMode.tsx Full / Custom selection | ||
| OptionalPackages.tsx Feature multiselect | ||
| Install/Install.tsx Install progress display | ||
| FileCleanup.tsx Cleanup progress display | ||
| PostInstall.tsx Post-install instructions | ||
| Ask.tsx Text input with validation | ||
| Divider.tsx Section divider | ||
| MainTitle.tsx Gradient title banner | ||
| Multiselect/ Checkbox multiselect component | ||
| types/ | ||
| types.ts Shared TypeScript types | ||
| utils/ | ||
| utils.ts Validation, path helpers, package resolution | ||
| __tests__/ Mirrors source/ layout | ||
| nonInteractive.test.ts | ||
| info.test.ts | ||
| utils.test.ts | ||
| operations/ | ||
| cloneRepo.test.ts | ||
| createEnvFile.test.ts | ||
| installPackages.test.ts | ||
| cleanupFiles.test.ts | ||
| ``` | ||
|
|
||
| ## Key Abstractions | ||
|
|
||
| ### Feature Definitions (`source/constants/config.ts`) | ||
|
|
||
| Single source of truth for all feature metadata. Every consumer reads from here: | ||
fernandomg marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ```ts | ||
| featureDefinitions: Record<FeatureName, { | ||
| description: string // --info output, --help text | ||
fernandomg marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| label: string // TUI multiselect display | ||
| packages: string[] // pnpm packages to remove when deselected | ||
| default: boolean // --info output | ||
| postInstall?: string[] // post-install instructions for agents and TUI | ||
| }> | ||
| ``` | ||
|
|
||
| `featureNames` is derived as `Object.keys(featureDefinitions)`. | ||
|
|
||
| When adding a new feature, add it here. All other code (validation, cleanup, info output, TUI selection) picks it up automatically — except `cleanupFiles.ts` which needs explicit cleanup rules. | ||
|
|
||
| ### Operations Layer (`source/operations/`) | ||
|
|
||
| Plain async functions with no UI dependencies. Each operation receives explicit arguments (project folder, mode, features) and performs file system or shell work. | ||
|
|
||
| | Function | What it does | | ||
| |---|---| | ||
| | `cloneRepo(projectName)` | Shallow clone, checkout latest tag, rm .git, git init. Uses `execFile` (no shell) for all commands except `git checkout $(...)` which needs shell substitution. | | ||
| | `createEnvFile(projectFolder)` | Copy .env.example to .env.local | | ||
| | `installPackages(projectFolder, mode, features)` | Full: `pnpm i`. Custom: `pnpm remove` deselected packages + postinstall. Uses `execFile` exclusively (no shell). | | ||
| | `cleanupFiles(projectFolder, mode, features)` | Remove files/folders for deselected features, patch package.json scripts, remove .install-files | | ||
|
|
||
| ### Shell Execution (`source/operations/exec.ts`) | ||
|
|
||
| Two helpers with different security profiles: | ||
|
|
||
| - **`execFile(file, args, options)`** — uses `child_process.execFile` (no shell). Arguments are passed as an array, so user input cannot be interpreted as shell metacharacters. Use this whenever user-provided values (e.g., `projectName`) appear in the command. | ||
| - **`exec(command, options)`** — uses `child_process.exec` (spawns a shell). Only for commands that require shell features like `$(...)` substitution. Never interpolate user input into the command string. | ||
|
|
||
| Both helpers capture stdout/stderr (no leaking to parent process) and throw on failure with the stderr message. | ||
fernandomg marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ## Data Flow | ||
|
|
||
| ### Non-interactive (agent) | ||
|
|
||
| ``` | ||
| CLI flags (string) | ||
| → meow parses to typed flags | ||
| → validate() converts to { name, mode, features: FeatureName[] } | ||
| → operations receive typed args | ||
| → JSON output to stdout | ||
| ``` | ||
|
|
||
| **Routing:** `source/cli.tsx` | ||
|
|
||
| ``` | ||
| --info → source/info.ts → print JSON → exit 0 | ||
| --ni / !isTTY → source/nonInteractive.ts → validate → operations → JSON | ||
| default → dynamic import ink + App → TUI | ||
| ``` | ||
|
|
||
| **Non-interactive validation order:** | ||
| 1. `--name` required | ||
| 2. `--mode` required | ||
| 3. `--name` matches `/^[a-zA-Z0-9_]+$/` | ||
| 4. `--mode` is `full` or `custom` | ||
| 5. Full mode: skip to step 8 (features ignored, all installed) | ||
| 6. `--features` required for custom mode | ||
| 7. All feature names are valid keys in `featureDefinitions` | ||
| 8. Project directory does not already exist | ||
|
|
||
| **Non-interactive execution order:** | ||
| `cloneRepo` → `createEnvFile` → `installPackages` → `cleanupFiles` → success JSON | ||
|
|
||
| Any error produces `{ "success": false, "error": "..." }` and exit code 1. | ||
|
|
||
| **Success output:** | ||
| ```json | ||
| { | ||
| "success": true, | ||
| "projectName": "...", | ||
| "mode": "full|custom", | ||
| "features": ["..."], | ||
| "path": "/absolute/path", | ||
| "postInstall": ["..."] | ||
| } | ||
| ``` | ||
|
|
||
| For full mode, `features` lists all feature names. For custom mode, only the selected ones. | ||
|
|
||
| ### Interactive (human) | ||
|
|
||
| ``` | ||
| User input via Ink components | ||
| → useState in App.tsx | ||
| → passed as props to step components | ||
| → components convert MultiSelectItem[] → FeatureName[] | ||
| → operations receive typed args | ||
| → Ink renders progress/status | ||
| ``` | ||
|
|
||
| Steps: ProjectName → CloneRepo → InstallationMode → OptionalPackages → Install → FileCleanup → PostInstall | ||
|
|
||
| Components are presentation-only — they call operations via `useEffect` and render status. Components receive `MultiSelectItem[]` for feature selection (TUI concern) and convert to `FeatureName[]` before calling operations. | ||
|
|
||
| ## How to Add a New Feature | ||
|
|
||
| 1. **`source/constants/config.ts`** — add entry to `featureDefinitions` with description, label, packages, default, and optional postInstall. Add the name to the `FeatureName` union type. | ||
|
|
||
| 2. **`source/operations/cleanupFiles.ts`** — add a cleanup function and call it from `cleanupFiles()` when the feature is deselected. If the feature has scripts in package.json, add removal to `patchPackageJson`. | ||
|
|
||
| 3. **Tests** — add test cases in `source/__tests__/operations/cleanupFiles.test.ts` for the new cleanup rules. The nonInteractive, info, installPackages, and utils tests pick up new features automatically since they read from `featureDefinitions`. | ||
|
|
||
| 4. **Verify** — `pnpm build && pnpm lint && pnpm test` | ||
|
|
||
| Steps 1 and 4 are always required. Steps 2-3 only apply if the feature has files/folders to clean up. | ||
|
|
||
| ## How to Add a New Operation | ||
|
|
||
| 1. Create `source/operations/newOperation.ts` — export an async function. Use `execFile` for commands with user input, `exec` only when shell features are needed. | ||
|
|
||
| 2. Export from `source/operations/index.ts`. | ||
|
|
||
| 3. Call from `source/nonInteractive.ts` (in the execution sequence) and from the relevant TUI component. | ||
|
|
||
| 4. Add tests in `source/__tests__/operations/newOperation.test.ts` — mock `exec`/`execFile` to verify correct commands. | ||
|
|
||
| ## Security | ||
|
|
||
| - User input (`projectName`) is validated against `/^[a-zA-Z0-9_]+$/` before any use | ||
| - Operations use `execFile` (no shell) for commands that include user input | ||
| - `exec` (shell) is reserved for commands needing shell substitution, and never receives user input in the command string | ||
| - Non-interactive output suppresses child process stdout/stderr to guarantee clean JSON on stdout | ||
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
Oops, something went wrong.
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.