> **本文回答**：写一条自定义命令分四步——① `class { async main() {...} }`；② `new CommandOptions()` 配置；③ `CommandRegistry.regist(new CommandDefinition('MYCMD', '描述', MyCommand, options))`；④ `await Engine.editor.executerWithOp('MYCMD')` 触发；命令体内必须用撤销分组包裹，并在 `finally` 中清理预览。

## 一、最小完整命令

```js
const { CommandRegistry, CommandDefinition, CommandOptions, writeMessage,
        Engine, CircleEnt, message } = vjcad;

class HelloCommand {
  async main() {
    writeMessage('<br/>Hello WebCAD! 这是一个简单的自定义命令。');
    const circle = new CircleEnt([50, 50], 30);
    circle.setDefaults();
    circle.color = 1;
    Engine.addEntities(circle);
    Engine.zoomExtents();
    writeMessage('<br/>已在画布中心创建一个红色圆形。');
  }
}

const options = new CommandOptions();
options.useAutoComplete = true;

CommandRegistry.regist(new CommandDefinition(
  'HELLO',
  '简单示例命令',
  HelloCommand,
  options
));

await Engine.editor.executerWithOp('HELLO');
```

MCP 参考：`vjcad/example/command/01simple.js`。

## 二、批量注册（典型工程做法）

```js
const commands = [
  { name: 'MYINFO',  desc: '显示文档信息', cls: InfoCommand    },
  { name: 'MYCLEAR', desc: '清空画布',    cls: ClearAllCommand },
  { name: 'MYZOOM',  desc: '缩放全图',    cls: ZoomAllCommand  }
];

commands.forEach(cmd => {
  const o = new CommandOptions();
  o.useAutoComplete = true;
  CommandRegistry.regist(new CommandDefinition(cmd.name, cmd.desc, cmd.cls, o));
});

// 查询已注册命令
const infoCmd = CommandRegistry.item('MYINFO');
console.log(infoCmd.name, infoCmd.description);
```

MCP 参考：`vjcad/example/command/02register.js`。

`CommandRegistry` 关键 API（来源 `vjcad/doc/command/api-reference.md`）：

```js
CommandRegistry.regist(definition)        // 注册命令
CommandRegistry.item('MYCMD')             // 查询单个
CommandRegistry.items                     // 所有命令
```

## 三、带撤销分组的命令体

```js
class SafeCommand {
  async main() {
    Engine.undoManager.start_undoMark('我的命令');
    try {
      // 1. 取输入
      // 2. 创建/修改实体（每个 Engine.addEntities 已在该 mark 内合并）
    } finally {
      Engine.undoManager.end_undoMark();
      Engine.clearPreview();
    }
  }
}
```

MCP 参考：`vjcad/example/command/03withpreview.js`、`vjcad/example/command/04statemachine.js`。

也可以用快捷形式：

```js
Engine.startUndoRecord('我的命令');
try { ... } finally { Engine.endUndoRecord(); }
```

MCP 参考：`vjcad/doc/command/api-reference.md`。

## 四、在插件里注册

```js
// onActivate(context)
context.registerCommand('MYCMD', '命令描述', MyCommand);

// onDeactivate(context)
context.unregisterCommand('MYCMD');
```

MCP 参考：`vjcad/doc/command/api-reference.md`。详见 [plugin/context-api](../plugin/context-api.md)。

## 反例

- 没 `setDefaults()` 直接 `Engine.addEntities(ent)`：颜色 / 图层 / 线型 等不会被默认填充。
- 把 `await Engine.editor.executerWithOp('LINE')` 写在 `await cadView.onLoad()` 之前：编辑器还没就绪。
- 在 `main()` 体内 throw 了异常但没 `finally`：撤销组没关、预览图没清。

## 参考（官方文档 MCP）

- 文档：`vjcad/doc/command/index.md`、`vjcad/doc/command/api-reference.md`
- 示例：`vjcad/example/command/01simple.js`、`02register.js`、`03withpreview.js`、`04statemachine.js`
