Evoo CLI, a powerful, job-based tool for automating project tasks. It can be used for both one-off scaffolding (like initializing a new project with create-react-app.json) and stateful, incremental updates (like adding a themed component with add-material-ui-button.json).
Define a series of tasks in a single JSON file—creating files, asking questions, installing dependencies, and more. Evoo executes them sequentially with support for conditional logic, user prompts, and persistent state (evoo.store.json) to create highly adaptable and context-aware automations.
- Job-Based System: Every action is a "job"—create a file, ask a question, install a package, or group other jobs.
- Conditional Logic: Use powerful
whenclauses to run jobs only when specific conditions are met. - Interactive Prompts: Engage with the user through text inputs, confirmations (
y/n), or a list of options. - State Management: Save user answers in-memory for the current session (
#id) or persistently across runs (@id). - Dynamic Paths & Content: Use stored answers as variables in file paths, file content, and conditional checks.
- Plugin-Driven: The CLI's functionality is extended through plugins, making it highly modular and adaptable.
Install Evoo globally to use the evoo command anywhere:
npm install -g evooOr run it directly without a global installation using npx:
npx evoo@latest <jsonPath>evoo <source> [options]<source>: The path to a local.jsonfile or a URL to a remote one.
| Option | Short | Description |
|---|---|---|
--dir |
-d |
Set the working directory for the scaffold. |
--force |
-f |
Overwrite existing files without prompting. |
--extend-path |
-e |
Adds a prefix to the name property of all file jobs. |
The power of Evoo comes from its JSON structure. At its core, it's a list of jobs to be executed in order.
| Property | Type | Required | Description |
|---|---|---|---|
name |
string |
The internal name of the scaffold. | |
title |
string |
A user-friendly title for the scaffold. | |
version |
string |
The semantic version of the scaffold. | |
description |
string |
A brief summary of what the scaffold does. | |
dependencies |
string[] |
A list of npm packages to install at the very beginning of the process. | |
jobs |
Job[] |
The primary array of jobs to be executed sequentially. | |
definitions |
Record<string, Job> |
A map of reusable job definitions that can be executed by a run job. |
|
plugins |
string[] |
A list of plugins to load, which can provide custom job types. | |
sharedContext |
object |
A shared data object that is passed to all plugin job executors. |
Every object in the jobs array is a Job. All jobs share these common properties:
| Property | Type | Required | Description |
|---|---|---|---|
type |
string |
✔️ | The type of job to run. |
id |
string |
A unique identifier, used to reference the job's result in when conditions. Required for question jobs. |
|
when |
string |
A conditional expression. The job only runs if the expression evaluates to true. | |
confirm |
string |
A yes/no question to ask before running the job. |
Performs a file operation (write, append, or replace). This is the default job type if type is omitted.
| Property | Type | Required | Description |
|---|---|---|---|
name |
string |
✔️ | The path of the file, relative to the project or a base directory from a group job. |
method |
"w", "a", or "replace" |
The file operation method. Defaults to "w". |
|
content |
string or Record<string, string> |
✔️ | The content for the operation. If method is "replace", this must be an object of search/replace pairs. |
Example:
{
"jobs": [
{
"name": "src/components/<#componentName>.tsx",
"content": "export const Button = () => <button>Click Me</button>;"
}
]
}Prompts the user for input and stores the answer.
| Property | Type | Required | Description |
|---|---|---|---|
id |
string |
✔️ | The key used to store the answer. Use #id for session storage and @id for persistent storage. |
question |
string |
✔️ | The question text displayed to the user. |
questionType |
"ask", "confirm", or "options" |
✔️ | The type of prompt to display. |
defaultValue |
string |
An optional default value for the prompt. | |
options |
Record<string, string> |
A map of options for the options questionType. |
Example:
{
"type": "question",
"id": "@project.linter",
"questionType": "confirm",
"question": "Do you want to use ESLint?",
"defaultValue": true
}Displays a custom message in the terminal.
| Property | Type | Required | Description |
|---|---|---|---|
message |
string |
✔️ | The message to display. |
logLevel |
"error", "warn", "info", "success", "log" |
The style of the log message. Defaults to "log". |
Example:
{
"type": "log",
"logLevel": "success",
"message": "✅ Project has been successfully created!"
}Installs npm packages.
| Property | Type | Required | Description |
|---|---|---|---|
dependencies |
string[] or string |
✔️ | The package(s) to install. |
when |
string |
✔️ | This job must be conditional to prevent accidental installations. |
Example:
{
"type": "dependencies",
"when": "@project.linter == true",
"dependencies": ["eslint", "prettier"]
}A container for a nested sequence of jobs.
| Property | Type | Required | Description |
|---|---|---|---|
jobs |
Job[] |
✔️ | An array of Job objects to be executed sequentially. |
base |
string |
An optional base path that prefixes all file paths within this group. |
Example:
{
"type": "group",
"when": "#useTypescript == true",
"base": "src/",
"jobs": [
{ "name": "../tsconfig.json", "content": "{}" },
{ "name": "index.ts", "content": "// TS entry file" }
]
}Executes a reusable job from the top-level definitions map.
| Property | Type | Required | Description |
|---|---|---|---|
target |
string |
✔️ | The key of the job to execute from the definitions object. |
Example:
{
"definitions": {
"createHook": {
"type": "file",
"name": "src/hooks/useMouse.ts",
"content": "..."
}
},
"jobs": [
{
"type": "run",
"target": "createHook"
}
]
}A when expression determines if a job should run. It can access any stored variable, including nested values using dot notation (e.g., #project.linter).
-
Existence Check: Checks if a job has run (i.e., its result is
defined), not whether the value is "truthy"."#useAuth": Runs if theuseAuthjob was executed."!#useAuth": Runs if theuseAuthjob was skipped.
-
Value Comparison: Compares a job's result to a specific value.
- Note: Use loose equality operators (
==,!=) in the JSON. Evoo's engine will execute them as strict (===,!==) comparisons at runtime. "#framework == 'react'": Runs if theframeworkresult is strictly'react'."@project.linter == true": Runs if the nestedlintervalue is the booleantrue.
- Note: Use loose equality operators (
The name property of a file job is highly dynamic. You can construct paths using a combination of:
- Variables: Inject answers from questions.
"name": "src/components/<#componentName>/index.tsx"
- Ask Placeholders: Prompt the user directly for a path segment.
<-ask->: Prompts the user (e.g.,src/<-ask->/index.js).<-ask|defaultName->: Prompts with a default value.
- Directory Shortcuts: Use special keywords that resolve to common paths.
%SRC%,%COMPONENTS%(e.g.,src,src/components).
Ask if they want TypeScript and only create tsconfig.json if they say yes.
{
"jobs": [
{
"type": "question",
"questionType": "confirm",
"id": "@project.useTypescript",
"question": "Use TypeScript?",
"defaultValue": true
},
{
"name": "tsconfig.json",
"content": "{\n \"compilerOptions\": {}\n}",
"when": "@project.useTypescript == true"
}
]
}Ask for a component name and use it to generate a file with the correct name and content.
{
"jobs": [
{
"type": "question",
"questionType": "ask",
"id": "#componentName",
"question": "What is the name of your component?",
"defaultValue": "Button"
},
{
"name": "src/components/<#componentName>.tsx",
"content": "export const %%COMPONENT_NAME%% = () => <></>;"
},
{
"name": "src/components/<#componentName>.tsx",
"method": "replace",
"content": {
"%%COMPONENT_NAME%%": "<#componentName>"
}
}
]
}The true power of the Evoo CLI lies in its plugin system. If you are interested in creating your own plugins, please refer to the Plugin Development Guide in the @evoo/core package for detailed documentation and best practices.
In addition to loading plugins from node_modules, Evoo supports "direct plugin loading," which allows you to load a plugin directly from a URL without needing to install it as a package. This is particularly useful for testing or using community-provided plugins without adding them to your project's dependencies.
To use a direct plugin, prefix the plugin identifier with an exclamation mark (!) in your evoo.json file:
{
"plugins": ["!@evoo/plugin-shadcn"]
}When a direct plugin is used, the following happens:
- URL Resolution: The plugin is resolved to a
unpkg.comURL. For example,!@evoo/plugin-shadcnbecomeshttps://unpkg.com/@evoo/plugin-shadcn/dist/index.js. - Versioning: You can specify a version (e.g.,
!@evoo/plugin-shadcn@1.0.0-alpha.6). If no version is provided, thelatestversion is automatically fetched from the npm registry. - Local Caching: The downloaded plugin is cached in your home directory (
~/.evoo/direct-plugins). On subsequent runs, the cached version is used, avoiding unnecessary downloads.
You can also load a plugin directly from any URL. This is useful for loading plugins from private repositories or other sources.
{
"plugins": ["https://raw.githubusercontent.com/user/repo/refs/heads/master/setup.js"]
}When a URL is used, the plugin is downloaded and cached locally in the ~/.evoo/direct-plugins/.locals directory. The plugin is re-downloaded on every run to ensure the latest version is always used.
This feature provides a more flexible way to use and distribute plugins, making it easier to share and experiment with new automations.
Evoo can store a GitHub personal access token to fetch configurations from private repositories.
# Set your GitHub token
evoo set githubToken ghp_abcdef123456# View the currently stored token
evoo get githubTokenContributions are welcome! Please feel free to open an issue or submit a pull request on GitHub.
This project is licensed under the MIT License.