Clide-JS
is a Command Line Interface (CLI) framework for node designed to
build powerful and flexible command-line applications with ease. It leverages a
modular approach, allowing developers to create commands, use hooks for
lifecycle management, and extend functionality with plugins.
- Key Features
- Installation
- Quick Start
- Ideal Use Cases
- Running
- Creating Commands
- Creating Plugins
- Routing and Command Resolution
- Dynamic Option Handling
- CLI Examples
- Reference
- Contributing
- Easy-to-use API: Develop commands with a simple and intuitive interface.
- Command Resolution: Dynamically resolves commands from a specified directory, supporting nested and parameterized commands.
- Flexible Lifecycle: Control command execution flow with pre-defined hooks.
- Extensible & Customizable: Adapt the framework to your specific needs through plugins and configuration.
- TypeScript Support: Develop robust and predictable CLI applications with type safety.
npm install clide-js
# or
yarn add clide-js
1. Import and call the run
function
// src/cli.ts
import { run } from 'clide-js';
// Use argv (minus the binary name) and the default commands directory.
run();
// Or pass in your own + some options
run({
command: 'deploy dev --watch',
commandsDir: path.join(__dirname, 'modules'),
initialData: { ... }
plugins: [help()]
})
2. Use the command
function to define your commands in separate files:
// src/commands/hello.ts
import { command } from 'clide-js';
export default command({
description: 'Say hello!',
options: {
name: {
description: 'The name to greet',
type: 'string',
alias: ['n'],
default: 'World',
},
},
handler: async ({ context, end, next, options }) => {
// Use the options getter to dynamically retrieve option values.
const name = options.name({
prompt: 'Enter your name',
});
const message = `Hello, ${name}!`;
// Use the client to log messages or show arbitrary prompts
context.client.log(message);
// Send some data to the next command
next(message);
// Or end the command and return some data
end(message);
},
});
3. (Optional) Create plugins to extend the framework:
import { Plugin } from 'clide-js';
export function logger(): Plugin {
return {
name: 'logger',
version: '1.0.0',
description: 'Logs the result of each execution step.',
init: ({ client, commandString, hooks }) => {
client.log('🪵 Received command:', commandString);
hooks.on('beforeNext', ({ data, state }) => {
client.log('🪵 Next:', {
commandName: state.command.commandName,
commandTokens: state.command.commandTokens,
commandPath: state.command.commandPath,
params: state.params,
data: state.data,
});
});
hooks.on('beforeEnd', ({ data, state }) => {
log('🪵 End:', {
commandName: state.command.commandName,
commandTokens: state.command.commandTokens,
commandPath: state.command.commandPath,
params: state.params,
data: state.data,
});
});
// return true to indicate successful initialization
return true;
},
};
}
Clide-JS is ideal for developers looking to create efficient, maintainable CLI applications with minimal fuss. It offers the right balance of functionality and ease-of-use, making it a practical choice for both simple scripts and more elaborate command-line tools.
- Build complex CLI applications with nested commands and subcommands.
- Develop interactive and dynamic CLI experiences.
- Integrate with other tools and services through plugins.
- Create reusable and modular command components.
Clide-JS is designed to be straightforward to run. The primary entry point is
the run
function,
which orchestrates the command execution flow. It parses and executes commands
based on your configuration, handles plugins, and utilizes hooks for lifecycle
management.
The run
function
takes an optional configuration
object
allowing you to specify commands, plugins, and hooks. This level of
customization makes it adaptable to various CLI application requirements.
For detailed API information on the
run
function,
please refer to the Typedoc
reference.
In Clide-JS, commands are the building blocks of your CLI application. Each command can have its description, options, and a handler function where the command's logic resides. Clide-JS allows for dynamic command resolution, meaning your commands can be organized hierarchically, with support for nested and parameterized commands.
To create a command, use the
command
factory
function. This function takes an object with your command's metadata, options,
and the handler function. The handler function, which is where the main logic of
your command lives, receives a
State
object. This
object provides access to parsed options, command parameters, and the ability to
control the command execution flow.
For a comprehensive guide on creating commands, including handling options and parameters, see the Typedoc reference.
Plugins in Clide-JS offer a way to extend and customize the framework's
functionality. A plugin is an object that includes metadata (name, version,
description) and an
init
function. This function is called during the CLI application's initialization
phase and receives the application's
Context
. The
Context
provides
access to hooks, commands, and other critical framework components.
You can create plugins to add new features, integrate with external services,
modify existing behavior, or inject middleware for advanced use cases. The
init
function should return a boolean indicating whether the initialization was
successful.
For more information on developing plugins, including accessing and modifying the application context, refer to the Typedoc reference.
- help: Adds the
--help
/-h
option and manages printing help messages when the option is present or aUsageError
occurs. Included in the core package. - logger: A simple logger that logs the result of each execution step. Included in the core package.
- command-menu: Prompts the user to select a subcommand when required.
If you don't explicitly provide a commands directory when calling the
run
function, the
framework automatically attempts to locate the commands directory in two ways:
- Current Working Directory: It first checks for a directory named "commands" directly in your current working directory. This is useful if you want to keep your "commands" directory at the root of your project.
- Caller Directory: If no "commands" directory is found in the current
working directory, the framework looks for a "commands" folder adjacent to
the file that called the
run
function. This is helpful for scenarios where your CLI script lives in a specific directory within your project (e.g., "cli/bin.js") and the commands are kept in a sibling directory called "cli/commands".
1. Parse Command String:
- The command string is split into tokens separated by spaces.
- The first token is assumed to be the command name.
2. Find Command File:
- Clide-JS attempts to locate a file with the same name as the command in the specified commands directory.
- If the file exists, it imports the module and checks for a default export,
which should be the
CommandModule
object.
3. Handle Non-existent Files:
- If the command file isn't found directly:
- The provided path is checked to ensure it's a directory. If so, it treats it as a pass-through command for further resolution.
- If not, It attempts to resolve a parameterized command file (e.g.,
[param].ts
).
4. Handle Parameterized Commands:
- Parameterized commands use filenames like
[param].ts
or[...param].ts
to capture arguments. - The filename is parsed to identify the parameter name and spread operator (if present).
- It attempts to import the corresponding file and checks for a default export.
- For spread operator commands, all remaining tokens are passed as the parameter value.
5. Prepare Resolved Command:
Once a command file is found:
- All options up to the next command token are parsed and removed from the remaining command string.
- A
resolveNext
function is added if the command isn't the last one in the string for further resolution of subcommands. - If the command won't be executed (e.g.,
isMiddleware
isfalse
), its handler is replaced with a pass-through function.
- The resolution process is flexible, handling various command formats and directory structures.
- Detailed error messages guide users in case of resolution issues.
1. Basic Command:
mycli list
The framework searches for list.js
in the commands directory. If found, it
imports the module and executes its handler.
2. Pass-through Command:
mycli settings ...
The framework identifies settings
as a directory, treats it as a pass-through
command, and expects further command resolution within the settings
directory.
4. Subcommand:
mycli users create
- Either
users.js
or ausers
directory is first resolved, thencreate
is identified as a subcommand. - The framework looks for
create.js
in theusers
directory.
3. Parameterized Command:
mycli deploy prod
- After resolving
deploy
, The framework searches forprod.ts
in thedeploy
directory. - If not found, the framework will look for a parameterized file name and finds
[environment].ts
. - The module is imported and the
environment
is set toprod
in theState.params
.
- Clide-JS prioritizes direct file name matching for each token, followed by parameterized file names.
- Plugin and initial command options are removed before further processing.
- Resolution continues within a directory matching the name of the previously resolved command.
This flexible approach allows for intuitive command structures and efficient execution, making it ideal for building versatile CLI applications.
Clide-JS introduces a dynamic and user-centric approach to handling command
options, distinguishing it from many other CLI frameworks. Instead of validating
options before execution, Clide-JS's
OptionsGetter
allows command handlers to address missing or invalid options dynamically,
enhancing the user experience and offering more flexibility:
- Lazy Evaluation: Options are not immediately validated upon command execution. Instead, they are evaluated dynamically when accessed by the command handler.
- User Prompts: If an option is required but not provided, Clide-JS can prompt the user for input, allowing for interactive CLI experiences. This eliminates the need for remembering all options beforehand and provides a more guided experience.
- Option Getters: Each option is represented by a
getter
function that dynamically retrieves its value. Getters can prompt users for missing values, validate input, and provide default values if needed.
- Enhanced Flexibility: Allows command handlers to deal with options in a context-sensitive manner, enhancing the adaptability of commands.
- Improved User Experience: By deferring validation and potentially prompting for input, Clide-JS makes CLI tools more user-friendly and interactive.
- Robust Error Handling: Dynamic validation allows for more informative and contextual error messages, improving debugging and user guidance.
When creating commands, the
OptionsGetter
provides a straightforward and intuitive interface for accessing and handling
options.
// Example usage in a command
options: {
n: {
type: 'string',
alias: ['name'],
},
a: {
type: 'string',
alias: ['alt-name'],
}
},
handler: async ({ options, next }) => {
// Getter functions
const name = await options.n();
const altName = await options.a();
// Alias getter functions
const name = await options.name();
const altName = await options.altName();
// Get function
const { name, altName } = options.get(['name', 'alt-name']);
// Direct values access
const { name, altName } = options.values;
},
- GH Scripts - A collection of misc GH scripts.
- @delvtech/council-cli - A CLI for building on the Council governance protocol.
Visit the examples directory for some basic setups.
Clide-JS uses Typedoc to autogenerate detailed references for each major component of the framework. See the Typedoc reference for a full breakdown of Clide-JS's APIs.
Clide-JS is a new framework still under development. Contributions are welcome! You can get a brief overview of the code base in /notes/source-code.md. If you're unsure where to start, or if a feature would be welcomed, open an issue and start a conversation.