cli-command
/cli-command
Scaffold a `bin/magento` console command (Symfony Command + commandList registration + arguments/options/exit-codes) or a cron job (crontab.xml + job class, fixed or config-path schedule) on an existing module. Use for 'add a CLI command' / 'add a scheduled job'. Business logic belongs in a service the command/job calls. For a new module use `magento2-tools:module-create` first.
- Author
- muon-m2 ↗
- Repository
- magento2-tools ↗
- Licence
- MIT
- Last repository change
- 04.10.2026
Assessment and security
- Files in this package
- 10
- Scripts
- 4
- HTML comments
- 0
- URLs in SKILL.md
- 0
No signals detected. Repository changed on 04.10.2026, without references to retired models or APIs.
MIT, open: use and modify while retaining author attribution.
- 4 scripts in this package. Review them before installing, as Claude may execute them.
Scripts: templates/command-class.php, templates/cron-job-class.php, templates/test-command-unit.php, templates/test-cron-unit.php
What it does and when to use it
Scaffold a `bin/magento` console command (Symfony Command + commandList registration + arguments/options/exit-codes) or a cron job (crontab.xml + job class, fixed or config-path schedule) on an existing module. Use for 'add a CLI command' / 'add a scheduled job'. Business logic belongs in a service the command/job calls. For a new module use `magento2-tools:module-create` first.
Triggers from the description
Original author description
Scaffold a `bin/magento` console command (Symfony Command + commandList registration + arguments/options/exit-codes) or a cron job (crontab.xml + job class, fixed or config-path schedule) on an existing module. Use for 'add a CLI command' / 'add a scheduled job'. Business logic belongs in a service the command/job calls. For a new module use `magento2-tools:module-create` first.
How it works
- Core Rules
- Workflow
- Inputs
- Outputs
- Reference Files
- Templates
- Acceptance Criteria
- Related Skills
SKILL.md preview
---
name: cli-command
version: 1.1.2
description:
Scaffold a `bin/magento` console command (Symfony Command + commandList registration +
arguments/options/exit-codes) or a cron job (crontab.xml + job class, fixed or
config-path schedule) on an existing module. Use for 'add a CLI command' / 'add a
scheduled job'. Business logic belongs in a service the command/job calls. For a new
module use `magento2-tools:module-create` first.
---
# Magento 2 CLI Command / Cron Job
Scaffold a `bin/magento` console command or a cron job onto an **existing** Magento 2
module. Two modes:
- **command** — Symfony `Command` subclass, `CommandList` DI registration,
arguments, options, and `Cli::RETURN_*` exit codes.
- **cron** — `crontab.xml` job declaration plus a job class with a delegate service;
fixed `<schedule>` cron expression or `<config_path>` (pair with
`system-config`).
## Core Rules
- **Command name convention:** `{vendor_lower}:{module_lower}:{action}` (all lowercase,
colon-separated). See `context/references/naming.md`.
- **Return codes:** use `Magento\Framework\Console\Cli::RETURN_SUCCESS` (0) and
`Magento\Framework\Console\Cli::RETURN_FAILURE` (1). Never return bare `0`/`1`
literals.
- **Delegate to a service.** The command or cron class must do no business logic itself —
it constructs the call context and delegates to a constructor-injected `{ServiceName}`.
Tests mock the service, not the command internals.
- **Area code.** When the command or cron job interacts with store models that require a
scope (e.g. store emulation), set the area code via `\Magento\Framework\App\State`.
The command class receives `State` via DI; never call `setAreaCode` twice.
- **Cron idempotency.** Every cron job must be safe to run twice in a row with the same
outcome. Long-running jobs should acquire a mutex (e.g. a flag file or a cache-backed
lock) to prevent overlap. See
`${CLAUDE_SKILL_DIR}/references/cron-anatomy.md`.
- **Schedule variants:** use `<schedule>` for a literal cron expression
(`*/5 * * * *`); use `<config_path>` to read the expression from admin config (pair
with `system-config` to add the field). Default cron group is `default`.
- **Coding style.** Generated PHP follows PER-CS 3.0 as the baseline, with the Magento 2
coding standard taking precedence on any conflict; `--standard=Magento2` PHPCS is the
gate. See `context/references/php-coding-style.md`.
- **Source of truth.** Generate from templates → shared references → baked-in Magento 2 knowledge
→ official Magento/Adobe docs (live-fetched only when uncertain). Do NOT read, grep, or "study"
other modules under `app/code`/`vendor/*`/Magento core to infer conventions, entity shapes,
naming, or wiring. Narrow exceptions: the target module/class of this operation, and the specific
contract of a module this code explicitly depends on. Affirm sources in the final report. See
`context/references/source-of-truth.md`.
## Workflow
### Phase 0 — Context Resolution
Invoke `context` (or run
`${CLAUDE_PLUGIN_ROOT}/skills/context/scripts/resolve-context.sh`); capture the
JSON as `{ctx}`. Abort if `{ctx.magento_root}` is unresolved.
**Hard stop if the target module does not exist.** Offer `module-create` and
do not scaffold into a non-existent module.
### Phase 1 — Resolve Inputs
Ask for any missing values in one batch.
**Command mode**
| Input | Default | Notes |
|-------|---------|-------|
| Module | (ask) | Existing `{Vendor}_{Module}` |
| Command class name | (ask) | PascalCase, e.g. `SyncOrdersCommand`; placed in `Console/Command/` |
| Command name (CLI) | (ask) | `{vendor_lower}:{module_lower}:{action}`, e.g. `acme:orders:sync` |
| Command description | (ask) | Short description shown in `bin/magento list` |
| Arguments | (ask) | Zero or more: name, required (yes/no), description |
| Options | (ask) | Zero or more: name, shortcut, mode (value/flag), description |
| Service FQCN | (ask) | The injected service class this command delegates to |
**Cron mode**
| Input | Default | Notes |
|-------|---------|-------|
| Module | (ask) | Existing `{Vendor}_{Module}` |
| Cron job class name | (ask) | PascalCase, e.g. `SyncOrders`; placed in `Cron/` |
| Cron job name (XML) | (ask) | snake_case, unique across the Magento instance, e.g. `acme_orders_sync` |
| Schedule | (ask) | Literal cron expr (`*/5 * * * *`) OR config path (`acme_orders/cron/schedule`) |
| Cron group | `default` | Group id in `crontab.xml`; `default` covers most use cases |
| Service FQCN | (ask) | The injected service class this cron job delegates to |
See `${CLAUDE_SKILL_DIR}/references/console-command-anatomy.md` and
`${CLAUDE_SKILL_DIR}/references/cron-anatomy.md`.
### Phase 2 — Plan
Present every file to create or modify. Typical file sets per mode:
**Command mode:**
- `Console/Command/{CommandClass}.php`
- `etc/di.xml` (merge — `CommandList` registration)
- `Test/Unit/Console/Command/{CommandClass}Test.php`
**Cron mode:**
- `Cron/{CronJobName}.php`
- `etc/crontab.xml` (merge)
- `Test/Unit/Cron/{CronJobName}Test.php`
Wait for "proceed."
### Phase 3 — Test First, then Generate
**3A — Write the failing tests (RED).** Before generating implementation code, write
tests that express expected behaviour and confirm they fail for the right reason.
- **Command test** (`Test/Unit/Console/Command/{CommandClass}Test.php`): use
`Symfony\Component\Console\Tester\CommandTester`; inject a mock `{ServiceName}`
configured with `expects(self::once())`; run the command via `CommandTester::execute()`;
assert `self::assertSame(0, $tester->getStatusCode())` (or
`Magento\Framework\Console\Cli::RETURN_SUCCESS`); assert the output display contains the
expected confirmation message.
- **Cron test** (`Test/Unit/Cron/{CronJobName}Test.php`): mock `{ServiceName}` with
`expects(self::once())->method('execute')` (or the relevant method); call the cron
`execute()` method; assert the return value (typically `void`/`$this`); call `execute()`
a second time on a fresh instance to demonstrate idempotency-safety of the class itself.
Use `self::assertNull` or equivalent — no `self::assertTrue(true)`.
Follow `context/references/tdd-discipline.md`. Run the 3A tests and confirm
they fail for the right reason (class-not-found, not a setup error).
**3B — Generate implementation (GREEN).** Write the minimal code to make the 3A tests
pass, using the templates:
- `${CLAUDE_SKILL_DIR}/templates/command-class.php`
- `${CLAUDE_SKILL_DIR}/templates/command-di.xml`
- `${CLAUDE_SKILL_DIR}/templates/cron-job-class.php`
- `${CLAUDE_SKILL_DIR}/templates/crontab.xml`
- `${CLAUDE_SKILL_DIR}/templates/test-command-unit.php`
- `${CLAUDE_SKILL_DIR}/templates/test-cron-unit.php`
See `${CLAUDE_SKILL_DIR}/references/console-command-anatomy.md`,
`${CLAUDE_SKILL_DIR}/references/cron-anatomy.md`, and
`${CLAUDE_SKILL_DIR}/references/pitfalls.md`.
### Phase 4 — Verify
- `php -l` on every generated `.php` file.
- `xmllint --noout` on every generated `.xml` file.
- Run the Phase 3A tests with `{ctx.runner} vendor/bin/phpunit` and confirm they now
**pass** (they failed before 3B); run the module suite to confirm nothing else broke.
- **Apply the shared module-hygiene baseline (required).** After generating or modifying
PHP files, run
`${CLAUDE_PLUGIN_ROOT}/skills/context/scripts/add-license-headers.sh {ctx.magento_root}/app/code/{Vendor}/{Module} {Vendor}`
to stamp the standard copyright header onto every new `.php` (idempotent — it skips files
that already carry it). When adding a `composer.json` `require` entry, resolve a
**bounded** constraint via
`${CLAUDE_PLUGIN_ROOT}/skills/context/scripts/resolve-dep-constraint.sh <vendor/package>` —
never `"*"`. See `context/references/module-hygiene.md`.
- Run `review --diff` (gate: zero Critical/High findings).
- Consult `${CLAUDE_SKILL_DIR}/references/pitfalls.md` before declaring Phase 4 done.
### Phase 5 — Report
Write a brief Markdown report to
`{output_root}/cli-commands/{Vendor}_{Module}-{mode}-{slug}-{date}.md` listing:
- Mode (`command` or `cron`)
- Command/job name
- Files generated
- Test path + red→green evidence
- `bin/magento setup:upgrade` + `bin/magento cache:flush` commands
- (command mode) how to invoke: `bin/magento {CommandName} [args] [options]`
- (cron mode) how to trigger manually: `bin/magento cron:run --group={CronGroup}`
> **Docs may now be stale.** This change modified module code. Run
> `docs --module={Vendor}_{Module}` to refresh the module's README,
> CHANGELOG, and `docs/*.md` (technical reference, guides, and API references as
> applicable).
## Inputs
```
/cli-command --mode=command --module=Acme_Orders --class=SyncOrdersCommand \
--name=acme:orders:sync --service=Acme\Orders\Service\OrderSyncer
/cli-command --mode=cron --module=Acme_Orders --class=SyncOrders \
--job=acme_orders_sync --schedule="*/15 * * * *" --service=Acme\Orders\Service\OrderSyncer
/cli-command --mode=command --module=Acme_Orders --class=SyncOrdersCommand \
--name=acme:orders:sync --service=Acme\Orders\Service\OrderSyncer --docs-root=<path>
```
`--docs-root=<path>` — output-root override; see "Output root" below.
## Outputs
```
{ctx.magento_root}/app/code/{Vendor}/{Module}/Console/Command/{CommandClass}.php # command mode
{ctx.magento_root}/app/code/{Vendor}/{Module}/etc/di.xml # command mode (merge)
{ctx.magento_root}/app/code/{Vendor}/{Module}/Test/Unit/Console/Command/{CommandClass}Test.php
{ctx.magento_root}/app/code/{Vendor}/{Module}/Cron/{CronJobName}.php # cron mode
{ctx.magento_root}/app/code/{Vendor}/{Module}/etc/crontab.xml # cron mode (merge)
{ctx.magento_root}/app/code/{Vendor}/{Module}/Test/Unit/Cron/{CronJobName}Test.php # cron mode
{output_root}/cli-commands/{Vendor}_{Module}-{mode}-{slug}-{date}.md
```
`{output_root}` defaults to `.docs` (`{ctx.docs_root}`), anchored at the project root, never
under `{ctx.magento_root}`, `app/code`, or a module dir. See the **Artifact location** rule in
`context/SKILL.md`.
### Output root (`--docs-root`)
This skill accepts `--docs-root=<path>` (see
`context/references/artifact-layout.md`). When set, write the run report (and any
report artifacts) under `<path>/cli-commands/`; otherwise default to
`{ctx.docs_root}/cli-commands/`. `feature` passes this so a feature
run's reports collect under its folder.
## Reference Files
- `${CLAUDE_SKILL_DIR}/references/console-command-anatomy.md` — `configure()`, `execute()`,
return codes, `CommandList` DI registration.
- `${CLAUDE_SKILL_DIR}/references/cron-anatomy.md` — `crontab.xml` groups, `<schedule>` vs
`<config_path>`, the `default` group, `cron:run`.
- `${CLAUDE_SKILL_DIR}/references/pitfalls.md` — delegate-to-service, area-code, cron
idempotency/overlap, locking, long-running progress.
- `context/references/naming.md` — naming conventions.
- `context/references/tdd-discipline.md` — shared test-first RED/GREEN loop.
- `context/references/php-coding-style.md` — PER-CS + Magento coding style.
- `context/references/placeholder-schema.md` — token registry.
- `context/references/source-of-truth.md` — source-of-truth hierarchy + the
no-unrelated-module-scanning rule (allowed reads, live-doc fetch protocol, report affirmation).
## Templates
- `templates/command-class.php` → `Console/Command/{CommandClass}.php`
- `templates/command-di.xml` → `etc/di.xml` (merge)
- `templates/cron-job-class.php` → `Cron/{CronJobName}.php`
- `templates/crontab.xml` → `etc/crontab.xml` (merge)
- `templates/test-command-unit.php` → `Test/Unit/Console/Command/{CommandClass}Test.php`
- `templates/test-cron-unit.php` → `Test/Unit/Cron/{CronJobName}Test.php`
All templates follow the placeholder registry in
`context/references/placeholder-schema.md`. Every token used must be in the
Registry there — `tests/test-placeholder-tokens.sh` enforces it.
## Acceptance Criteria
- All generated files pass `php -l` / `xmllint --noout`.
- Command name follows `{vendor_lower}:{module_lower}:{action}` pattern.
- `execute()` returns `Cli::RETURN_SUCCESS` or `Cli::RETURN_FAILURE` (never bare `0`/`1`).
- No business logic in the command or cron class — delegated to the injected service.
- Command test: `CommandTester` asserts status code 0 and expected output; service mock
uses `expects(self::once())`.
- Cron test: service mock uses `expects(self::once())`; second invocation demonstrates
class-level idempotency-safety; no `markTestIncomplete`, no `self::assertTrue(true)`.
- `review --diff` returns zero Critical/High findings.
## Related Skills
| Phase | Skill |
|-------|-------|
| 0 | `context` |
| Before (if module absent) | `module-create` |
| Cron schedule from admin config | `system-config` |
| After | `review --diff` |
Files in this package (10)
- SKILL.md 13 KB
- references/console-command-anatomy.md 4 KB
- references/cron-anatomy.md 2 KB
- references/pitfalls.md 4 KB
- templates/command-class.php 2 KB
- templates/command-di.xml 1 KB
- templates/cron-job-class.php 1 KB
- templates/crontab.xml 1 KB
- templates/test-command-unit.php 2 KB
- templates/test-cron-unit.php 3 KB
Versions
The database has no other skills named cli-command.
Database snapshot: 13.11.2026 · package version 5480bafea8a4 (SHA-256 digest of the entire package)
Downloads in the last 30 days: 0 · ranking
Report a problem with this skill: harmful content, incorrect descriptions or an author’s removal request.
Similar skills
- 100console-commands
Use when creating or refactoring Symfony console commands — never extends Command. On Symfony 8.1+ put #[AsCommand] on a method of a service class (even for a single command); on 7.x use the invokable attribute class. Also covers typed CLI inputs via value resolvers and… - 100indexer
Scaffold a custom Magento 2 indexer + materialized view (mview) on an existing module — `indexer.xml`, `mview.xml` subscriptions, an `ActionInterface` indexer (executeFull/executeList/executeRow) + Mview `ActionInterface` (execute), delegating to a batched action class. Use for… - 100cron-scheduling
Cron and scheduled task management. node-cron, cron expressions, Spring @Scheduled, APScheduler (Python), and distributed scheduling patterns. USE WHEN: user mentions "cron", "scheduled task", "cron job", "node-cron", "@Scheduled", "APScheduler", "periodic task", "crontab" DO… - 100add-cli-command
Use when a ticket adds or changes a command-line interface — a new subcommand, flag, or output format for the repo's CLI tool — and it must follow the CLI's existing conventions for arguments, exit codes, JSON output, help text, and tests. Invoke for "add a `foo bar` command",… - 100magento-events-cron
Implement Magento 2 events, observers, cron jobs, and message queues. Use when building event-driven logic, scheduled tasks, or asynchronous processing. - 90magento-di
Configure Magento 2 dependency injection — di.xml, types, virtual types, preferences, argument replacement, and Object Manager. Use when wiring dependencies, creating class variations, or configuring module integrations.