Build your own CLI
aclif is a framework. The binary you ship carries your name and your choice of providers, and takes everything else from the framework.
Scaffold
npx --package @aclif/core aclif-scaffold-cli --name mycli --dir ../mycli --providers salesforce,servicenow
cd ../mycli && npm install && npm run build
./bin/run.js discover --json
Here mycli stands for whatever you name yours. The scaffold writes a small package: a package.json with the oclif configuration, a bin/run.js, three hook re-exports, a topic generator, one source file, a dependency allowlist for your own providers, a vitest config, and a conformance test. npm test runs that test.
// src/index.ts
import {defineCli, type DefinedCli} from 'aclif'
import {salesforcePlugin, servicenowPlugin} from 'aclif/providers'
const cli: DefinedCli = defineCli({
bin: 'mycli',
providers: [salesforcePlugin, servicenowPlugin],
})
export const COMMANDS: DefinedCli['COMMANDS'] = cli.COMMANDS
export const registry: DefinedCli['registry'] = cli.registry
What the name changes
| Surface | mycli gets |
|---|---|
| Command strings in examples, hints, and next-command suggestions | mycli salesforce data query ..., rendered at emit time from the configured binary name |
| Config and cache directories | ~/.config/mycli/config.yaml, ~/.cache/mycli/ |
| Framework environment variables | MYCLI_PROFILE, MYCLI_INSTANCE, MYCLI_IDENTITY_TOKEN, MYCLI_IDENTITY_SECRET, MYCLI_NO_MANIFESTS, MYCLI_CONFIG_DIR |
Audit line, auth status, discover, learn | this CLI's providers only |
Everything else is the framework's: the envelope, the error vocabulary, the introspection flags, credential resolution, the connection pool, the session cache, and the embedded runtime.
Add your own provider
A provider that belongs to your CLI goes under your package's src/providers/ and is listed in src/index.ts next to the framework's. It is written exactly as a framework provider is, against the same provider API and the same conformance rules, and it is tier private in your CLI. The guide is PROVIDER_AUTHORING.md. The scaffold is npm run scaffold-provider. Everything a provider needs from the framework, including the schema-inference helpers and withDnsRetry, is importable from aclif.
import {defineCli, type DefinedCli} from 'aclif'
import {salesforcePlugin} from 'aclif/providers'
import {acmePlugin} from './providers/acme/plugin.js'
const cli: DefinedCli = defineCli({bin: 'mycli', providers: [salesforcePlugin, acmePlugin]})
Test your providers
The framework's conformance suite is importable from aclif/testing, and the scaffold writes a test that runs it over the providers your package declares. This needs @aclif/core 1.1.0 or later.
// test/conformance.test.ts
import {conformanceSuite} from 'aclif/testing'
import {registry} from '../src/index.js'
conformanceSuite({
registry,
cliRoot: process.cwd(),
sourceDirs: [{dir: 'src/providers', tier: 'private'}],
dependencyAllowlist: 'src/providers/dependency-allowlist.json',
})
npm run build && npm test
The suite checks every provider whose directory sits under src/providers/: complete safety metadata, --dry-run before any client use, exit 3 with every auth path named when credentials are absent, a SETUP.md per provider under docs/providers/<name>/, examples that parse, and the source rules in PROVIDER_AUTHORING.md. Providers imported from aclif/providers are checked upstream and skipped here. A provider with a tenant walk, an http adapter, or session support needs a fake in the options (tenantFakes, httpFakes, sessionFakes). The failing test names which one.
The same module exports providerHarness for fixture tests over an msw server, captureGoldens for golden introspection files, and runBinary for end-to-end runs of your built binary. vitest, msw, and ajv are optional peer dependencies of the framework. The scaffold adds them to devDependencies.
Upgrade the framework
npm update aclif. Your providers and your name are in your tree. The framework's changes arrive as a dependency bump. Nothing in the framework repository needs to know your CLI exists.
When to fork instead
Build a CLI package unless you also need to change the framework's core. A team that does change core forks the repository and keeps its providers under src/providers/private/, a prefix upstream never commits to and upstream CI refuses in pull requests, so pulling upstream stays conflict-free and core fixes go back from a clean branch. The two routes, and how to contribute a provider or a fix back, are in FORKING.md.
The full guide is BUILDING_A_CLI.md.