diff --git a/README.md b/README.md index 71e9975..f3a82e9 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ managed as a monorepo. | Plugin | Capabilities | What it does | | --- | --- | --- | +| [sample](plugins/sample) | varies | Reference sample demonstrating the plugin API (ping, wallet-read, wallet-submit, reserved capabilities). | | [ens](plugins/ens) | `wallet-read` | Resolve ENS names via the Agent Wallet. | ## Setup diff --git a/plugins/sample/README.md b/plugins/sample/README.md new file mode 100644 index 0000000..bb29349 --- /dev/null +++ b/plugins/sample/README.md @@ -0,0 +1,48 @@ +# sample + +Reference sample plugin for the MetaMask Agent Wallet CLI. Demonstrates the plugin API: input schemas, capability declarations, restricted context, and reserved capabilities. + +## Commands + +| Command | Capability | Notes | +| --- | --- | --- | +| `mm ping [name]` | none | No auth. Input schema + positional args. | +| `mm demo balance` | `wallet-read` | Active address, native balance, tx count via `ctx.publicClient(1)`. | +| `mm demo submit` | `wallet-submit` | Obtains `ctx.walletExecutor`. | +| `mm vault mnemonic` | `mnemonic-read` (reserved) | Declares SRP disclosure; SRP stays host-only. | +| `mm admin config` | `config-write` (reserved) | Declares config-write capability. | +| `mm admin network` | `network-manage` (reserved) | Declares network-manage capability. | + +Command paths map to files under `src/commands/` — e.g. `src/commands/demo/balance.ts` → `mm demo balance` / id `demo:balance`. + +For the full plugin developer guide, see [plugin-system.md](https://github.com/MetaMask/agentic/blob/main/docs/plugin-system.md) in the agentic repo. + +## Install (this plugin only) + +One-time CLI configuration. Plugins are beta and off by default: + +```bash +mm config set experimentalPlugins true +mm config set experimentalAllowUnverifiedInstalls true # allows local (file:) installs +``` + +Build and install from this directory: + +```bash +yarn install # from the repo root, wires up the workspace +yarn workspace sample run build +mm plugins install "file:$PWD" --accept-permissions # run from plugins/sample +``` + +Install from the directory, not a packed tarball — the CLI reads `package.json#mm` from the +directory to persist capability approvals for local installs. + +Verify and remove: + +```bash +mm ping Alice +mm demo balance +mm plugins uninstall sample +``` + +To build and link every plugin in this repo instead, use `yarn setup` at the repo root. diff --git a/plugins/sample/package.json b/plugins/sample/package.json new file mode 100644 index 0000000..b60a273 --- /dev/null +++ b/plugins/sample/package.json @@ -0,0 +1,82 @@ +{ + "name": "sample", + "version": "0.1.0", + "description": "Sample MetaMask Agent Wallet (mm) CLI plugin demonstrating the plugin API", + "license": "MIT", + "type": "module", + "keywords": [ + "oclif-plugin", + "metamask", + "agent-wallet" + ], + "files": [ + "dist", + "oclif.manifest.json" + ], + "scripts": { + "build": "tsc -p tsconfig.json && oclif manifest", + "prepack": "npm run build", + "type-check": "tsc -p tsconfig.json --noEmit" + }, + "oclif": { + "bin": "mm", + "commands": "./dist/commands", + "topicSeparator": " " + }, + "mm": { + "schemaVersion": 1, + "minCliVersion": "^6.1.0", + "capabilities": [], + "commands": [ + { + "id": "ping", + "capabilities": [], + "dataAccess": [] + }, + { + "id": "demo:balance", + "capabilities": [ + "wallet-read" + ], + "dataAccess": [ + "balances", + "accounts" + ] + }, + { + "id": "demo:submit", + "capabilities": [ + "wallet-submit" + ], + "dataAccess": [] + }, + { + "id": "vault:mnemonic", + "capabilities": [], + "dataAccess": [ + "mnemonic" + ] + }, + { + "id": "admin:config", + "capabilities": [], + "dataAccess": [] + }, + { + "id": "admin:network", + "capabilities": [ + "network-manage" + ], + "dataAccess": [] + } + ] + }, + "peerDependencies": { + "@metamask/agent-wallet": "^6" + }, + "devDependencies": { + "@metamask/agent-wallet": "portal:../../../agentic-cli/packages/agentic-cli", + "oclif": "^4", + "typescript": "^5" + } +} diff --git a/plugins/sample/src/commands/admin/config.ts b/plugins/sample/src/commands/admin/config.ts new file mode 100644 index 0000000..f1b5f01 --- /dev/null +++ b/plugins/sample/src/commands/admin/config.ts @@ -0,0 +1,12 @@ +import { type CommandIO, PluginCommand } from "@metamask/agent-wallet/plugin"; + +export default class SampleConfigCommand extends PluginCommand<{ capability: "config-write"; status: "reserved" }> { + static override requiresAuth = false; + static override requiresInit = false; + static override description = "Sample plugin: config-write capability (reserved for future config mutations)"; + protected readonly pluginCommandId = "admin:config"; + + async execute(_io: CommandIO) { + return { capability: "config-write" as const, status: "reserved" as const }; + } +} diff --git a/plugins/sample/src/commands/admin/network.ts b/plugins/sample/src/commands/admin/network.ts new file mode 100644 index 0000000..b9a97db --- /dev/null +++ b/plugins/sample/src/commands/admin/network.ts @@ -0,0 +1,12 @@ +import { type CommandIO, PluginCommand } from "@metamask/agent-wallet/plugin"; + +export default class SampleNetworkCommand extends PluginCommand<{ capability: "network-manage"; status: "reserved" }> { + static override requiresAuth = false; + static override requiresInit = false; + static override description = "Sample plugin: network-manage capability (reserved for future network management)"; + protected readonly pluginCommandId = "admin:network"; + + async execute(_io: CommandIO) { + return { capability: "network-manage" as const, status: "reserved" as const }; + } +} diff --git a/plugins/sample/src/commands/demo/balance.ts b/plugins/sample/src/commands/demo/balance.ts new file mode 100644 index 0000000..9df11b6 --- /dev/null +++ b/plugins/sample/src/commands/demo/balance.ts @@ -0,0 +1,23 @@ +import { type CommandIO, PluginCommand } from "@metamask/agent-wallet/plugin"; + +export default class SampleBalanceCommand extends PluginCommand<{ address: string; balanceWei: string; txCount: number }> { + static override requiresAuth = true; + static override description = "Sample plugin: show the active address, on-chain balance, and tx count"; + protected readonly pluginCommandId = "demo:balance"; + + async execute(_io: CommandIO) { + const state = this.ctx.walletStateManager.read(); + const address = [...state.byokWallets, ...state.remoteWallets][0]?.address ?? ""; + if (!address) { + return { address, balanceWei: "0", txCount: 0 }; + } + + const client = this.ctx.publicClient(1); + const [balanceWei, txCount] = await Promise.all([ + client.getBalance({ address: address as `0x${string}` }), + client.getTransactionCount({ address: address as `0x${string}` }), + ]); + + return { address, balanceWei: balanceWei.toString(), txCount }; + } +} diff --git a/plugins/sample/src/commands/demo/submit.ts b/plugins/sample/src/commands/demo/submit.ts new file mode 100644 index 0000000..82805a8 --- /dev/null +++ b/plugins/sample/src/commands/demo/submit.ts @@ -0,0 +1,12 @@ +import { type CommandIO, PluginCommand } from "@metamask/agent-wallet/plugin"; + +export default class SampleSubmitCommand extends PluginCommand<{ walletSubmit: "granted" }> { + static override requiresAuth = true; + static override description = "Sample plugin: obtain the wallet executor (capability: wallet-submit)"; + protected readonly pluginCommandId = "demo:submit"; + + async execute(io: CommandIO) { + await this.ctx.walletExecutor(io, this.pluginCommandId); + return { walletSubmit: "granted" as const }; + } +} diff --git a/plugins/sample/src/commands/ping.ts b/plugins/sample/src/commands/ping.ts new file mode 100644 index 0000000..9f0e3c0 --- /dev/null +++ b/plugins/sample/src/commands/ping.ts @@ -0,0 +1,26 @@ +import { type CommandIO, InputFieldType, type InputSchema, PluginCommand, schemaToArgs, schemaToFlags } from "@metamask/agent-wallet/plugin"; + +const inputs = { + name: { + type: InputFieldType.Text, + flag: "name", + message: "Name to greet", + required: false, + prompt: false, + index: 0, + }, +} satisfies InputSchema; + +export default class SamplePingCommand extends PluginCommand<{ message: string }> { + static override requiresAuth = false; + static override requiresInit = false; + static override description = "Sample plugin: greet without auth (demonstrates the input schema)"; + static override flags = schemaToFlags(inputs); + static override args = schemaToArgs(inputs); + protected readonly pluginCommandId = "ping"; + + async execute(io: CommandIO) { + const { name } = await io.resolveInputs(inputs); + return { message: name ? `pong, ${name}` : "pong" }; + } +} diff --git a/plugins/sample/src/commands/vault/mnemonic.ts b/plugins/sample/src/commands/vault/mnemonic.ts new file mode 100644 index 0000000..2d370d8 --- /dev/null +++ b/plugins/sample/src/commands/vault/mnemonic.ts @@ -0,0 +1,12 @@ +import { type CommandIO, PluginCommand } from "@metamask/agent-wallet/plugin"; + +export default class SampleMnemonicCommand extends PluginCommand<{ capability: "mnemonic-read"; status: "reserved" }> { + static override requiresAuth = false; + static override requiresInit = false; + static override description = "Sample plugin: mnemonic-read capability (reserved; SRP is host-only)"; + protected readonly pluginCommandId = "vault:mnemonic"; + + async execute(_io: CommandIO) { + return { capability: "mnemonic-read" as const, status: "reserved" as const }; + } +} diff --git a/plugins/sample/tsconfig.json b/plugins/sample/tsconfig.json new file mode 100644 index 0000000..af548cd --- /dev/null +++ b/plugins/sample/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "outDir": "dist", + "rootDir": "src", + "skipLibCheck": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src"] +} diff --git a/yarn.lock b/yarn.lock index cca3fee..437f4d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -654,6 +654,22 @@ __metadata: languageName: node linkType: soft +"@metamask/agent-wallet@portal:../../../agentic-cli/packages/agentic-cli::locator=sample%40workspace%3Aplugins%2Fsample": + version: 0.0.0-use.local + resolution: "@metamask/agent-wallet@portal:../../../agentic-cli/packages/agentic-cli::locator=sample%40workspace%3Aplugins%2Fsample" + dependencies: + "@oclif/core": "npm:^4.11.4" + "@oclif/plugin-help": "npm:^6.2.55" + "@oclif/plugin-plugins": "npm:^5.4.37" + "@oclif/plugin-warn-if-update-available": "npm:^3.1.65" + "@toruslabs/base-controllers": "npm:9.12.0" + qrcode-terminal: "npm:^0.12.0" + semver: "npm:^7.7.2" + bin: + mm: dist/index.js + languageName: node + linkType: soft + "@noble/ciphers@npm:1.3.0, @noble/ciphers@npm:^1.3.0": version: 1.3.0 resolution: "@noble/ciphers@npm:1.3.0" @@ -3822,6 +3838,18 @@ __metadata: languageName: node linkType: hard +"sample@workspace:plugins/sample": + version: 0.0.0-use.local + resolution: "sample@workspace:plugins/sample" + dependencies: + "@metamask/agent-wallet": "portal:../../../agentic-cli/packages/agentic-cli" + oclif: "npm:^4" + typescript: "npm:^5" + peerDependencies: + "@metamask/agent-wallet": ^6 + languageName: unknown + linkType: soft + "semver@npm:^7.1.1, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.5.3, semver@npm:^7.7.2, semver@npm:^7.8.1, semver@npm:^7.8.5": version: 7.8.5 resolution: "semver@npm:7.8.5"