Skip to content

feat: add @metamask/wallet-cli package scaffold#9065

Merged
sirtimid merged 5 commits into
mainfrom
sirtimid/wallet-cli-scaffold
Jun 11, 2026
Merged

feat: add @metamask/wallet-cli package scaffold#9065
sirtimid merged 5 commits into
mainfrom
sirtimid/wallet-cli-scaffold

Conversation

@sirtimid

@sirtimid sirtimid commented Jun 9, 2026

Copy link
Copy Markdown
Member

Explanation

This adds @metamask/wallet-cli — an oclif-based mm CLI for @metamask/wallet, intended to run the wallet as a background daemon and dispatch messenger actions to it.

This first PR is the package scaffold only: a buildable, lintable, and testable package with the mm binary launcher (bin/run.mjs) and a couple of pure daemon path helpers, but no subcommands yet. oclif renders mm --help / mm --version even with no commands registered, so the package is exercisable end to end. Functionality is added incrementally in follow-up PRs (SQLite persistence, the Unix-socket daemon transport, the Wallet factory, and the daemon subcommands) so each stays small and self-contained.

Because the package is a CLI tool rather than a published library, it is registered in the TOOLS list in yarn.config.cjs — which exempts it from the library exports / main / types and build:docs constraints — matching how @metamask/messenger-cli is handled. The remaining changes outside packages/wallet-cli/ are the standard new-package registration: ESLint overrides, root tsconfig project references, CODEOWNERS, teams.json, and the generated root README dependency graph.

References

N/A

Checklist

  • I've updated the test suite for new or updated code as appropriate (the getDaemonPaths helper is covered to 100%)
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

Note

Low Risk
New CLI package and lint/yarn config only; no wallet runtime, daemon, or messenger integration in this diff.

Overview
Introduces @metamask/wallet-cli, an oclif-based mm CLI aimed at running @metamask/wallet as a background daemon. This PR is scaffold only: bin/run.mjs / run.cmd, oclif metadata pointing at ./dist/commands, and @oclif/core as a runtime dependency—no subcommands yet (help/version still work via oclif).

The package is reshaped as a tool, not a library: library main / types / full exports and typedoc tooling are removed; published files include bin/; yarn.config.cjs adds it to TOOLS (same pattern as messenger-cli). Adds getDaemonPaths plus DaemonPaths types for socket, PID, log, and DB paths under a data dir, with a unit test. Placeholder greeter index is deleted.

Repo wiring: ESLint overrides for wallet-cli src, tests, and bin; tsconfig includes ./bin; changelog Unreleased entry.

Reviewed by Cursor Bugbot for commit 5ef15ec. Bugbot is set up for automated code reviews on this repo. Configure here.

@socket-security

socket-security Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatedsemver@​7.8.1 ⏵ 7.8.2100 +1100100 +195100
Added@​oclif/​core@​4.11.49810010095100

View full report

@socket-security

socket-security Bot commented Jun 9, 2026

Copy link
Copy Markdown

All alerts resolved. Learn more about Socket for GitHub.

This PR previously contained dependency changes with security issues that have been resolved, removed, or ignored.

Ignoring alerts on:

  • ejs@3.1.10
  • jake@10.9.4
  • filelist@1.0.6

View full report

sirtimid added a commit that referenced this pull request Jun 9, 2026
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sirtimid

sirtimid commented Jun 9, 2026

Copy link
Copy Markdown
Member Author

@SocketSecurity ignore npm/jake@10.9.4
@SocketSecurity ignore npm/ejs@3.1.10
@SocketSecurity ignore npm/filelist@1.0.6

All three are transitive dependencies of @oclif/core (@oclif/core → ejs → jake → filelist) and are not imported by our code — this package only imports @oclif/core (in bin/run.mjs) and node:path.

Research:

  • The jake "system shell access" flag is its legitimate task-runner use of child_process — that's the entire purpose of "JavaScript make." Not a compromise signal.
  • The AI code-anomaly notes for all three confirm no malware: ejs is the "standard EJS template engine… no indicators of intentional malware (no network exfiltration, no reverse shells, no obfuscated backdoors)"; filelist is a "legitimate file-listing utility… no evidence of malicious activity"; jake is a "conventional CLI bootstrap for a build tool… no clear signs of malicious activity."
  • No CVEs (Socket Vulnerability score 100/100 on @oclif/core).
  • ejs's only real risk surface (template compilation via the Function constructor) is not reachable here: we only call oclif's execute() and never render untrusted templates. jake/filelist are ejs's build tooling, not on its render path.
  • These are mature, widely-used packages (@oclif/core supply-chain score 98), and this exact dependency subtree already shipped via the prior CI-green @oclif/core@^4.10.5 work.

@sirtimid sirtimid marked this pull request as ready for review June 9, 2026 15:36
@sirtimid sirtimid requested a review from a team as a code owner June 9, 2026 15:36
@sirtimid sirtimid temporarily deployed to default-branch June 9, 2026 15:36 — with GitHub Actions Inactive

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit c85b8d2. Configure here.

Comment thread packages/wallet-cli/bin/run.cmd Outdated
sirtimid added a commit that referenced this pull request Jun 11, 2026
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sirtimid sirtimid force-pushed the sirtimid/wallet-cli-scaffold branch from c8b4c43 to f5ed2fa Compare June 11, 2026 12:52
@sirtimid sirtimid changed the base branch from main to sirtimid/wallet-cli-init June 11, 2026 12:52
@sirtimid sirtimid requested a review from a team as a code owner June 11, 2026 12:52
Base automatically changed from sirtimid/wallet-cli-init to main June 11, 2026 15:14
sirtimid and others added 5 commits June 11, 2026 16:21
Add an oclif-based `mm` CLI package (`@metamask/wallet-cli`) for
`@metamask/wallet`, plus its monorepo registration. This is the initial
scaffold: a buildable/lintable/testable package with the `bin/run.mjs`
launcher and daemon path helpers, but no subcommands yet.

- `packages/wallet-cli/`: package manifest, tsconfigs, jest config (100%
  coverage thresholds), README, CHANGELOG, MIT LICENSE, the `mm` bin
  launcher, and `src/daemon/paths.ts` (+test) / `types.ts` (`DaemonPaths`).
- Register the workspace: add to `TOOLS` in `yarn.config.cjs` (library
  `exports`/`build:docs`-exempt), ESLint overrides for `src`/`bin`, root
  `tsconfig.json`/`tsconfig.build.json` project references, `CODEOWNERS`,
  `teams.json`, and the generated root README dependency graph.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The oclif ESM scaffold ships `bin/run.mjs`, but `bin/run.cmd` invoked
`node "%~dp0\run"` (an extensionless path inherited from the older oclif
template that shipped a `bin/run` file). Node's CJS entry resolution does
not append `.mjs`, so `run.cmd` failed with a missing-module error on
Windows while the `mm` bin shim (which targets `run.mjs`) worked.

Point the launcher at `run.mjs` so the Windows entrypoint matches the
file the package actually ships.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Restore the LICENSE pointer file, LICENSE.MIT, and LICENSE.APACHE2
introduced by the package-initialization PR (#9079) and set the
manifest license back to (MIT OR Apache-2.0); the CLI scaffold should
not change the package's licensing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Collapse duplicate ranges introduced by the @oclif/core addition so
`yarn dedupe --check` (lint:dependencies CI task) passes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sirtimid sirtimid force-pushed the sirtimid/wallet-cli-scaffold branch from 94090fe to 5ef15ec Compare June 11, 2026 15:25

@grypez grypez left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@sirtimid sirtimid added this pull request to the merge queue Jun 11, 2026
Merged via the queue into main with commit b5eb432 Jun 11, 2026
376 checks passed
@sirtimid sirtimid deleted the sirtimid/wallet-cli-scaffold branch June 11, 2026 16:44
pull Bot pushed a commit to Reality2byte/core that referenced this pull request Jun 16, 2026
…aMask#9067)

## Explanation

`@metamask/wallet-cli` runs a background daemon that hosts a
`@metamask/wallet` instance. For controller state to survive daemon
restarts, it needs durable local storage. This PR adds that persistence
layer. It is purely additive and not yet wired into a daemon process —
the daemon entry point that consumes it lands in a follow-up.

- **`KeyValueStore`** — a synchronous `better-sqlite3`-backed key-value
store (a single `kv` table with a TEXT key and a JSON-serialized TEXT
value). Corrupt rows fail loud (a descriptive, key-scoped error) rather
than returning garbage.
- **`loadState(store)`** — rehydrates persisted state into the `{
[controllerName]: { [property]: value } }` shape accepted by the
`Wallet` constructor's `state` option.
- **`subscribeToChanges(messenger, controllerMetadata, store, log?)`** —
subscribes to every `<Controller>:stateChanged` event and writes
persist-flagged top-level properties through to the store (applying
`StateDeriver` persist functions, and deleting a key when its property
is removed from state). It returns an unsubscribe handle; teardown is
owned by the caller (a `dispose()` handle in a later PR), so there is no
package-level teardown event.

### `better-sqlite3` native addon

`better-sqlite3` ships a native C addon. The monorepo runs Yarn with
`enableScripts: false`, so the addon is not built during `yarn install`.
To handle this:

- a `test:prepare` step (`scripts/install-binaries.sh`) fetches a
matching prebuild via `prebuild-install` before tests run;
- `engines.node` is set to `>=20` (better-sqlite3 v12 only ships
prebuilt binaries for Node 20+);
- `@metamask/wallet-cli` is excluded from the Node 18.x CI test matrix;
- `yarn.config.cjs` exempts the package from the standard `test`-script
and `engines.node` constraints accordingly.

## References

- Closes MetaMask#8682
- Builds on MetaMask#9065 (the `@metamask/wallet-cli` package scaffold). This PR
targets that branch and should merge after it.

## Checklist

- [x] I've updated the test suite for new or updated code as appropriate
- [x] I've updated documentation (JSDoc, Markdown, etc.) for new or
updated code as appropriate
- [x] I've communicated my changes to consumers by [updating changelogs
for packages I've
changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md)
- [ ] I've introduced [breaking
changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md)
in this PR and have prepared draft pull requests for clients and
consumer packages to resolve them

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> New durable state path can diverge from in-memory state on disk write
failures (logged only today); native addon and Node 20+ requirement
affect install and CI.
> 
> **Overview**
> Adds **SQLite-backed persistence** in `@metamask/wallet-cli` so wallet
controller state can survive restarts (not yet wired into the daemon).
> 
> A synchronous **`KeyValueStore`** (`better-sqlite3`, JSON values in a
`kv` table) is the storage backend. **`loadState`** rebuilds `{
controllerName: { property: value } }` for `Wallet` construction,
honoring only properties still marked `persist` in controller metadata
so stale rows are not resurrected. **`subscribeToChanges`** listens on
`<Controller>:stateChanged`, writes persist-flagged top-level fields
from Immer patches (including `StateDeriver` transforms and deletes when
properties are removed), logs write failures without stopping the
process, and returns an unsubscribe handle.
> 
> **`better-sqlite3`** is added with **`engines.node` `>=20`**, a
**`test:prepare`** / **`install-binaries.sh`** prebuild step (monorepo
`enableScripts: false`), CI matrix exclusion for Node 18 on this
package, and **`yarn.config.cjs`** exceptions for the custom test script
and engines field. Dependencies on `@metamask/wallet`,
`@metamask/base-controller`, and `immer` are added with matching
tsconfig project references.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
b9854de. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pull Bot pushed a commit to dmrazzy/core that referenced this pull request Jun 25, 2026
…#9226)

## Explanation

Adds the wallet factory and daemon entry point for
`@metamask/wallet-cli`, after the scaffold (MetaMask#9065), persistence (MetaMask#9067),
and transport (MetaMask#9108) slices:

- **`wallet-factory.ts`** — `createWallet()` builds a `@metamask/wallet`
`Wallet` over the SQLite `KeyValueStore`, hydrates it from persisted
state, imports the SRP on first run, and returns `{ wallet, dispose }`.
`dispose` (idempotent, resilient) runs unsubscribe → `wallet.destroy()`
→ `store.close()`.
- **`daemon-entry.ts`** — the daemon process: claims the PID slot,
builds the wallet, serves the messenger over the owner-only Unix socket,
and tears down via `dispose` on shutdown.

It uses the current `@metamask/wallet` `instanceOptions` API —
`storageService` (in-memory adapter), `connectivityController`
(`AlwaysOnlineAdapter`), and `remoteFeatureFlagController`.
`NetworkController` and `TransactionController` slots are left as TODOs
until they're wired on `main`.

Supersedes draft MetaMask#8847 (the `{ wallet, dispose }` refactor), folded in
here on top of `main`.

Closes MetaMask#8779.

## Checklist

- [x] `build`, `test` (100% coverage on both new files), `lint`,
`changelog:validate`, and `constraints` pass.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **High Risk**
> Touches keyring/SRP import, encrypted vault persistence, and exposes
the full wallet messenger over a Unix socket with only filesystem
permissions as the access boundary.
> 
> **Overview**
> Adds **`createWallet`** and **`daemon-entry`** so the CLI can run a
long-lived background wallet process backed by SQLite persistence.
> 
> **`createWallet`** opens a `KeyValueStore`, hydrates controller state
(via a short-lived metadata probe `Wallet`), wires headless
`instanceOptions` (in-memory `storageService`, `AlwaysOnlineAdapter`,
Infura + remote feature flags), subscribes persistence, runs
**`wallet.init()`** (startup aborts if any step fails), imports the SRP
on first run when no vault exists, and returns **`{ wallet, dispose }`**
with ordered, idempotent teardown. First-run failures can delete on-disk
DB files.
> 
> **`daemon-entry`** validates required env vars, **removes password/SRP
from `process.env`** after capture, locks the data dir to **`0o700`**,
runs **`claimDaemonSlot`** (stale socket/PID cleanup, refuse live
siblings), claims the daemon with an **exclusive PID write before
opening the DB**, builds the wallet, and serves **`getStatus`** plus a
**`call`** RPC that forwards arbitrary messenger actions. Shutdown/SIG
handlers coalesce teardown and only remove PID files the process owns.
> 
> Also adds **`remote-feature-flag-controller`** and
**`storage-service`** dependencies and a small **`Readonly`** typing
tweak in persistence subscriptions.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
180ffc1. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pull Bot pushed a commit to dmrazzy/core that referenced this pull request Jun 30, 2026
…oke test (MetaMask#9255)

## Explanation

The final landing slice for `@metamask/wallet-cli`, after the scaffold
(MetaMask#9065), persistence (MetaMask#9067), transport (MetaMask#9108), and factory (MetaMask#9226)
slices. It adds the user-facing command surface so the package is
runnable:

- **`src/commands/daemon/{start,stop,status,purge,call}.ts`** (+ tests)
— the `mm daemon` command suite over the daemon layers already on
`main`: `start` spawns the daemon and reports its socket,
`status`/`stop` ping and tear it down, `purge` deletes the daemon state
files, and `call` dispatches an arbitrary `Controller:method` messenger
action.
- **`bin/dev.mjs` + `bin/dev.cmd`** — the dev-mode launchers deferred
from the scaffold slice, run via the `tsx` loader (`knip`
`ignoreDependencies: ['tsx']`).
- **`wallet-factory.e2e.test.ts`** — a smoke test that feeds the wired
`instanceOptions` into a **real** `Wallet` (no mock) against `:memory:`,
imports the SRP, and dispatches a messenger action — closing the gap
flagged in MetaMask#9226 review. Offline/CI-safe (neither construction nor
`wallet.init()` reaches the network).
- **`src/test/run-command.ts`** + a README **Usage** section.

## Checklist

- [x] `build`, `test` (100% coverage), `lint`, and `changelog:validate`
pass.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> New CLI paths accept wallet secrets and can invoke arbitrary messenger
actions over a local socket; `purge` deletes persisted wallet state,
though safeguards limit deletion when the daemon is still responsive.
> 
> **Overview**
> Adds the runnable **`mm daemon`** command surface on top of the
existing spawn/client/stop layers: **`start`** (Infura/password/SRP via
flags or env), **`stop`**, **`status`** (orphan-PID and stale-PID
warnings), **`purge`** (confirm/`--force`, whitelist-only file deletion,
refuses purge while the daemon still responds), and **`call`** (JSON-RPC
`call` with optional JSON-array args and timeout, TTY vs piped JSON
output).
> 
> Also ships **`bin/dev.mjs`/`dev.cmd`** (oclif dev mode via **`tsx`**),
a **`runCommand`** oclif test harness, a **real `Wallet` `:memory:`
e2e** for `createWallet`, Jest **Web Crypto polyfill** environment for
KeyringController tests, README **Usage**, and knip/tsconfig tweaks for
**`wallet-cli`**.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
15fbe92. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants