Package Exports
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@metamask/agentic-cli) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
@metamask/agentic-cli
MetaMask Agentic CLI (mm) — a dual-mode CLI for interacting with the MetaMask Agentic platform. Supports both standard single-command execution and an interactive REPL shell.
Prerequisites
- Node.js >= 22.x
- npm >= 10.x
Running Locally
1. Install dependencies
From the repo root:
npm install2. Build
Build all packages (from repo root):
npm run buildOr build only the CLI package:
npm run build:local -w packages/agentic-cli3. Run the CLI
After building, run directly with Node:
node packages/agentic-cli/dist/lib.esm/index.jsOr link globally for the mm command:
npm link -w packages/agentic-cli
mm --help4. Development mode (watch)
Start a dev watcher that rebuilds on file changes:
npm run dev -w packages/agentic-cliOr start all packages in parallel from the repo root:
npm run devAvailable Commands
| Command | Description |
|---|---|
mm init |
Initialize project settings |
mm login |
Sign in (QR, browser/social, or token) |
mm auth status |
Check authentication status |
mm reset |
Reset session / configuration |
Global Flags
| Flag | Description |
|---|---|
-f, --format <fmt> |
stdout format for headless runs: json, text, yaml, toml, toon |
--json |
shorthand for --format=json (also forces headless on a TTY, like -f json) |
--toon |
shorthand for --format=toon (also forces headless on a TTY, like -f toon) |
When multiple format flags are combined (e.g. --json --format toon), the latest token on the command line wins.
| -v, --verbose | Enables ctx.logger and stderr output from io.log / io.progress (see Logging and progress). |
REPL vs headless
- REPL — If stdin and stdout are both a TTY and you run
mmwith no arguments, ormm initwithout-f/--format,--json, or--toon, the CLI starts the interactive Ink shell (history,ReplCommandRunnerper line). - Headless — All other invocations run a single oclif command. The headless renderer drives
execute: there is no interactiveask. Use flags, env vars, and schemadefaultvalues soio.resolveInputsnever needs a prompt; otherwise required fields raiseMISSING_FLAG. For prompts, use the REPL or pass everything explicitly.
Init Flow
mm init configures your local project settings. It walks you through a series of prompts and persists choices to ~/.metamask/session.json. By default the CLI uses a server wallet — pass --with-private-key to bring your own keys instead.
Step by step
Run the command
mm init
On a TTY, plain
mm init(no--format/-f) starts the REPL and runsinitas the first input line. Runmmalone to open the shell with an empty first line.To force headless
initwith prompts disabled, pass a format, e.g.mm init -f jsonormm init --toon(then provide any required values via flags or env).Done — settings are saved with
server-walletmode and the CLI prints✔ Project initialized
Bring your own keys (BYOK)
Use the REPL (mm on a TTY, then init --with-private-key) so io.resolveInputs can prompt for keys (EVM / Solana are optional — press Enter to skip), or pass everything on the CLI for headless runs:
mm init --with-private-keyThis sets wallet mode to byok and stores the provided keys in the session.
Optional mnemonic password (BYOK)
Encrypt the stored mnemonic with a password at init:
mm init --wallet byo --mnemonic "<phrase>" --password "<pw>"
# or: MM_PASSWORD=<pw> mm init --wallet byo --mnemonic "<phrase>"Manage the password later:
mm wallet password set --new=<pw>
mm wallet password change --current=<old> --new=<new>
mm wallet password remove --current=<pw>Unlock for a single headless BYOK command that signs or derives keys with --password or MM_PASSWORD. Commands that do not unlock the stored mnemonic do not accept this flag. Plaintext mnemonics written before this feature keep working unchanged.
Non-interactive usage
Pass BYOK values as flags to skip prompts:
mm init \
--with-private-key \
--evm-private-key 0xYOUR_KEYView current settings
mm init showLogin and auth status
mm login
On a TTY, run mm login (or open the REPL with mm and type login). Choose QR (MetaMask Mobile), browser (Google or email via dashboard), or token (paste an existing auth token). Session fields used by authenticated commands are updated when sign-in completes.
mm login social # open dashboard + MWP pairing code
mm login --mode social # sameSet MM_DASHBOARD_URL to override the dashboard base URL (default https://test-dashboard.web3auth.io for mm-dev, https://developer.metamask.io for mm). See mm login --help for other flags.
mm auth status
mm auth statusThis reports whether you are authenticated, your project ID, wallet address, and token expiry status.
Session Storage
All session data is persisted to:
~/.metamask/session.jsonThe file is created with restricted permissions (0600). Use mm reset to clear it.
Adding a new command
Commands are oclif classes that extend Command from src/runtime/Command.ts. The base class wires init() (context, session, flags), run() (headless output), and optional auth in beforeExecute. You implement execute(io) and return plain data; renderers turn that into stdout (headless) or Ink (REPL).
Create a command module under
src/commands/, e.g.src/commands/foo/FooCommand.ts. Optionally addview.tsxfor the success Ink tree.Set static metadata on the class:
static override requiresAuth = true | false— whentrue,beforeExecutechecks / refreshes MM auth (seeCommand.beforeExecute).static override description = "..."— short help text.static override flags— usually{ ...Command.baseFlags }or{ ...Command.flagsWithInputs(yourSchema) }(see Configuring input fields).static View— optional React component for REPL success UI:({ data }: FinalProps<TFinal>) => …. Omit it if you only care about serialized headless output.
Optional streaming UI
- Set
static LiveViewto a component receivingLiveProps(items,progress,emitted,signal) whileexecuteruns. - From
execute, callio.yield(item)to append typed rows (NDJSON on stdout in headless mode when any item was yielded). Useio.emit(<Ink node>)for one-off lines in the REPL history.
- Set
Implement
async execute(io: CommandIO): Promise<TFinal>- Read
this.ctx.flags,this.ctx.session,this.ctx.authServiceas needed. - Resolve inputs with
await io.resolveInputs(schema)(neverthis.resolveInputs). - Throw
ValidationErrorfor invalid flags/inputs (new ValidationError("CODE", "message", "hint")), orCommandErrorfor other expected failures.
- Read
Register the command in
src/commands/index.ts:export const commands = { // ... foo: FooCommand, // → `mm foo` "topic:bar": BarCommand, // → `mm topic bar` };
Build —
npm run build -w packages/agentic-cli(or rootnpm run build). oclif loads commands from this registry.
Minimal example (no extra input schema, no auth, with a simple View):
import { Text } from "ink";
import { Command } from "../../runtime/Command";
import type { CommandIO, FinalProps } from "../../runtime/CommandIO";
type FooData = { ok: true };
function FooView({ data }: FinalProps<FooData>) {
return <Text>{data.ok ? "ok" : ""}</Text>;
}
export class FooCommand extends Command<FooData> {
static override requiresAuth = false;
static override description = "Example";
static override flags = { ...Command.baseFlags };
static override View = FooView;
async execute(_io: CommandIO): Promise<FooData> {
return { ok: true };
}
}Logging and progress
There are two related surfaces: session-wide logger on the context, and CommandIO methods used inside execute.
this.ctx.logger
- Type:
Logger(debug,info,warn,error). - Disabled by default (no-ops).
Command.initcallslogger.enable(this)when the user passes--verbose, wiring methods to oclif’s stderr logging. - Use
this.ctx.loggerinbeforeExecute,execute, orafterExecutefor implementation diagnostics (auth checks, HTTP traces, etc.) that should respect the global verbose flag once.
io.log and io.progress
Defined on CommandIO and passed into execute by the active renderer:
| Method | Purpose |
|---|---|
io.progress(label?) |
Single transient status line while work runs. In the REPL, updates the spinner area in ReplCommandRunner. In headless mode, a line is written to stderr only when --verbose: [progress] …. Pass undefined to clear. |
io.log(level, msg) |
Structured lines to stderr when --verbose: [level] …. |
The default beforeExecute (when requiresAuth is true) already uses io.progress and logger.debug around token validation; follow the same pattern for long steps instead of a separate “loader” type.
Note: There is no getLoader() / Loader interface anymore — use io.progress (and optional io.emit / LiveView) for user-visible activity.
Configuring input fields
Structured CLI flags and prompts are driven by an InputSchema: a plain object map of field name → InputField, consumed by schemaToFlags and resolveInputs in src/helpers/input.ts. Merge into command flags with Command.flagsWithInputs(schema), then in execute:
const inputs = await io.resolveInputs(mySchema);resolveInputs reads this.ctx.flags (via the flags object the renderer passes into io; same values oclif parsed). In the REPL, the interactive renderer supplies io.ask so missing values can be prompted. In headless mode the asker is null, so only flags, env, and defaults apply.
Common properties (all field types)
| Property | Purpose |
|---|---|
flag |
oclif flag name (kebab-case), e.g. with-private-key → --with-private-key. |
message |
Prompt text and help description. |
env |
Optional env var fallback. |
required |
If not false, missing value (no flag, no env, no prompt) throws MISSING_FLAG. |
prompt |
If false, never prompt; value must come from flag or env. |
index |
Sort order for resolution. Lower runs first — required when using when so dependencies exist before conditional fields. |
when |
(resolved) => boolean — if false, field is skipped (not read, not prompted). |
parse / validate |
Normalize and validate after flag or prompt. |
Field types
type |
CLI flag | Interactive prompt |
|---|---|---|
select |
string (constrained options) | list select |
text |
string | text input |
password |
string | masked input |
confirm |
string | yes/no confirm |
boolean |
Flags.boolean (no value after flag) |
confirm when prompt is not false and flag omitted |
Boolean quirks
- Fields with
prompt: false(e.g.--with-private-key) use an oclifdefaultso omitted flags have a defined value. - Fields that should prompt when omitted must not use an oclif default on the flag; the schema omits
defaultonFlags.booleanin that case so “not passed” stays undefined andresolveInputscan show the confirm. The schema’s owndefaultis still used when there is no prompter (fully non-interactive run).
For a full example, see src/commands/init/inputs.ts (when + index for BYOK keys after withPrivateKey).
Tests
npm test -w packages/agentic-cliTests use Vitest. Unit specs live under test/unit-test/; integration tests under test/integration-test/.