Plugins
The sf CLI is extensible through plugins. A plugin is an npm package that exports a cliPlugin object, which registers additional commands and aliases with the CLI.
How plugins work
When sf starts, the plugin loader:
- Discovers plugins from two sources:
- Known first-party packages —
@stoneforge/smithyis auto-detected - User-configured packages — listed in
config.yamlunderplugins.packages
- Known first-party packages —
- Imports each package and looks for a named
cliPluginexport - Validates the plugin structure (name, version, commands array)
- Registers commands and aliases with the CLI
Resolution rules
- Built-in commands always take precedence over plugin commands
- Plugin commands can merge subcommands into existing command groups
- Aliases cannot conflict with existing commands
- If a package is not found, it’s silently skipped (no error)
Configuring plugins
Add third-party plugins in .stoneforge/config.yaml:
plugins: packages: - "@custom/my-plugin" - "another-stoneforge-plugin"Authoring a plugin
Each package must export a cliPlugin conforming to the CLIPlugin interface:
import type { CLIPlugin } from '@stoneforge/quarry/cli';
export const cliPlugin: CLIPlugin = { name: 'my-plugin', version: '1.0.0', commands: [/* Command objects */], aliases: { 'shortcut': 'my-command subcommand', }, init: async () => { /* optional async setup */ },};Plugin structure
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Unique plugin identifier |
version | string | Yes | Semver version string |
commands | Command[] | Yes | Array of command definitions to register |
aliases | Record<string, string> | No | Map of alias → target command |
init | () => Promise<void> | No | Async initialization hook, called once at startup |
Command objects
Each command in the commands array registers a CLI subcommand:
{ name: 'my-command', description: 'Does something useful', options: [ { flags: '--input <path>', description: 'Input file path' }, { flags: '--dry-run', description: 'Preview without executing' }, ], action: async (options, quarry) => { // options contains parsed flags // quarry is the initialized Quarry instance },}Commands receive the initialized Quarry instance, giving full access to the data layer — create elements, run queries, manage dependencies, and more.
Subcommand merging
Plugins can add subcommands to existing command groups. For example, a plugin could add new task operations:
export const cliPlugin: CLIPlugin = { name: 'task-extras', version: '1.0.0', commands: [ { name: 'task', subcommands: [ { name: 'estimate', description: 'Estimate task complexity', action: async (options, quarry) => { /* ... */ }, }, ], }, ],};This adds sf task estimate without affecting any existing task commands.
The Smithy plugin
The @stoneforge/smithy package ships a built-in plugin named orchestrator that adds all agent orchestration commands to the CLI. It registers 6 command groups and 2 aliases:
| Command group | Description |
|---|---|
sf agent | Register, start, stop, and inspect agents |
sf daemon | Control the background dispatch daemon |
sf dispatch | Manually trigger task dispatch |
sf merge | Run merge operations |
sf pool | Manage agent pools |
sf task (orchestration) | Handoff, complete, merge, reject, and sync tasks |
| Alias | Maps to |
|---|---|
agents | agent list |
pools | pool list |
These commands are documented in the Orchestration commands section of the CLI Reference.