Authoring Plugins
The sigx CLI is extensible: any package installed in a project can contribute commands. Commands like dev, build, and preview are themselves provided by plugins (@sigx/vite, @sigx/ssg, @sigx/lynx-cli). This guide shows how to write your own.
How discovery works
When the CLI runs, it scans the current project's package.json dependencies and devDependencies. For each dependency it reads node_modules/<dep>/package.json and looks for a top-level sigx-cli field that points at a plugin module:
{
"name": "my-sigx-plugin",
"sigx-cli": { "plugin": "./dist/plugin.js" }
}
The plugin value is a path, relative to the dependency's package root, to a module that default-exports a SigxPlugin. The CLI dynamically imports that module, takes its default export, calls detect(cwd), and registers the plugin's commands only if detect returns true.
Discovery inspects the current working directory's package.json and node_modules. All discovery failures are swallowed silently, so a broken plugin never crashes the CLI.
Defining a plugin
Use definePlugin for type-safe authoring. It is an identity function — it returns the plugin unchanged but gives you full type checking against the SigxPlugin contract. Import it from @sigx/cli/plugin (it is also re-exported from @sigx/cli):
import { definePlugin } from '@sigx/cli/plugin';
export default definePlugin({
name: 'my-plugin',
detect: (cwd) => true,
commands: {
hello: {
description: 'Say hello',
async run({ cwd, args, logger }) {
logger.log(`hello from ${cwd}`);
},
},
},
});
If you prefer, you can use the satisfies operator instead of definePlugin for the same type safety:
import type { SigxPlugin } from '@sigx/cli/plugin';
export default {
name: 'my-plugin',
detect: (cwd) => true,
commands: {
hello: {
description: 'Say hello',
async run({ cwd, args, logger }) {
logger.log(`hello from ${cwd}`);
},
},
},
} satisfies SigxPlugin;
The Logger passed to run has only log, warn, and error methods — there is no info. Messages are prefixed with [sigx] and warn / error are uppercased.
Gating activation with detect
detect(cwd) decides whether your plugin is relevant to the current project. Return true to register your commands, false to stay out of the way. A common pattern is to look for a config file or a marker dependency:
import { existsSync } from 'node:fs';
import { join } from 'node:path';
import { definePlugin } from '@sigx/cli/plugin';
export default definePlugin({
name: 'my-plugin',
detect: (cwd) => existsSync(join(cwd, 'my-plugin.config.ts')),
commands: {
// ...
},
});
Declaring command arguments
Commands can declare named arguments through the args map. Each entry is an ArgDef with a type of 'string' or 'boolean', an optional description, and an optional default. These map onto the underlying argument parser, and the parsed values arrive in ctx.args:
import { definePlugin } from '@sigx/cli/plugin';
export default definePlugin({
name: 'deployer',
detect: () => true,
commands: {
deploy: {
description: 'Deploy the built site',
args: {
target: {
type: 'string',
description: 'Deploy target',
default: 'staging',
},
force: {
type: 'boolean',
description: 'Skip confirmation',
},
},
async run({ args, logger }) {
logger.log(`Deploying to ${String(args.target)}`);
if (args.force) {
logger.warn('Forcing deploy without confirmation');
}
},
},
},
});
The command context
Every command's run handler receives a single CommandContext object:
async run(ctx) {
ctx.cwd; // process.cwd()
ctx.args; // parsed args object (Record<string, unknown>)
ctx.logger; // prefixed Logger with log/warn/error
}
Packaging checklist
To ship a plugin:
- Build a module that default-exports a
SigxPlugin(viadefinePluginorsatisfies SigxPlugin). - Point your package's
package.jsonsigx-cli.pluginfield at that built module. - Publish the package and add it to a project's dependencies.
Once installed, run npx sigx <your-command> in a project where your detect returns true.
Command name conflicts
If two plugins register a command with the same name, the last one discovered wins, and the CLI logs a warning via logger.warn:
[sigx] WARN Plugin "X" overrides command "Y"
See also
- API Reference — the full
SigxPlugin,PluginCommand,CommandContext,ArgDef, andLoggertypes. - Commands — the built-in and plugin-provided commands.
