aclif

Embedding

The CLI is also a library that can be embedded in a host process. There, it runs the same command classes, supplying credentials, identity, and policy per invocation, with no new process spawned between calls.

Why the runtime exists

Typical CLIs were designed for one person running one command: one process, arguments from the shell, output to the terminal, exit when done. An agent runs the same commands repeatedly, often inside a process serving many agents at once, with identity and credentials arriving per request. A process per command means a login per command.

The alternative is a CLI that is also a library. With aclif, every command is a class. Each execution is an invocation that supplies the credentials, identity, and policy for that one call. Inside a host process, the process stays up, so the login and the connection made on the first call are reused by every call after it.

Three ways to run it

1. Run by the agent

The agent process spawns the binary, executes the command, and reads the JSON it returns. Credentials come from flags, environment variables, or a profile in the agent's own environment. Policy comes from the same profile file, and the audit line writes to stderr. Use this when one agent, one operator, and one set of credentials share a trust boundary.

2. Run by a host application, the design-time case

An application sits between the model and aclif and holds the credentials. The model calls a tool the application defines, and the application executes the command, in-process or by passing a command string to the CLI. A person or an authoring tool uses this to let a model discover providers, introspect commands, and validate the exact command it will write into an agent. One tool describing the grammar replaces one tool per operation. Use this when the model must never hold credentials and tool definitions must stay out of its context.

3. Run by a gateway, the runtime case

A deployed agent submits commands, and one long-lived process serves many such agents. The gateway resolves credentials from the enterprise vault per request, checks policy against the acting user, records every call, and keeps connections warm. The agents hold no provider credentials and cannot widen their own scope. Use this when many agents share providers and one place must hold policy and audit.

Each request becomes an Invocation that holds the user's identity and forwarded SSO claims, a credential resolver over the host's secrets store, a capability check that runs on the command's declared metadata before its code loads, a rate-limit hook, a reporter, and the shared connection pool keyed by provider, instance, identity, and auth type.

A gateway deployment requires both an identity provider and a secrets vault. Each request includes the user's SSO token. The gateway verifies it and attaches the identity to the invocation, where every command, hook, and audit event can read it. Credentials come from the vault through a resolver the gateway author must provide. The framework calls it before each command and hands the secret to the provider client and nowhere else. When the resolver returns the acting user's own credential, the target system applies that user's permissions to the call, so the framework holds no second permission model that could drift from the first.

Three properties follow for any gateway built this way, independant of model behavior: the agent holds no credential, the agent cannot widen its scope, and every call is attributed to a person because the identity travels with the invocation.

Minimal host

The samples import from aclif. A CLI scaffolded from aclif installs @aclif/core under that alias. In a project of your own, install it the same way:

npm install aclif@npm:@aclif/core
import {Runtime, EventReporter, StaticCredentialResolver} from 'aclif'

const runtime = await Runtime.start({cliRoot: '/path/to/your/cli'})
const reporter = new EventReporter()
const result = await runtime.run({
  argv: ['salesforce', 'data', 'query', '--query', 'SELECT Id FROM Account LIMIT 1'],
  context: {requestId: 'req-1', user: {id: 'u1', username: 'alice'}},
  credentials: new StaticCredentialResolver(new Map([
    ['salesforce', {instanceUrl, accessToken, authType: 'session'}],
  ])),
  pool: runtime.pool,
  reporter,
  hooks: {
    capabilityGate: async ({aciMetadata}, ctx) =>
      aciMetadata.mutability === 'delete' && !ctx.user
        ? {allowed: false, error: {code: 'DENIED', message: 'Deletes need a user'}}
        : {allowed: true},
  },
})
console.log(result.exitCode, reporter.envelope())

cliRoot is the package root of the CLI whose commands the runtime should load: the framework itself for the reference binary, or your own package for a CLI built on aclif.

What the host supplies

On the invocationPurpose
contextthe per-request record: request id, the acting user, forwarded SSO claims, an abort signal, audit metadata such as the calling agent
credentialsa CredentialResolver. Static, Env, Profile, and Chain resolvers are built in. A vault-backed resolver is not included; the host writes one against the one-method interface and resolves per request
poolthe runtime's connection pool. Two instances never share a connection
reportercollects the envelope and events. EventReporter is the reference implementation
hooks.capabilityGateallow or refuse from the command's declared metadata before its code loads
hooks.rateLimitcalled per provider and operation before execution
tenantCache, aliasStoreoptional: the host's own store for tenant catalogs and alias sets, so learn, --schema, and --canonical read the host's data

Health monitoring, probe timers, and declarative manifests that turn one HTTP endpoint into a command are part of the runtime as well. The reference is EMBEDDING.md.