From c23c590330af83afb6e146bcd9b0a274b3689d22 Mon Sep 17 00:00:00 2001
From: 魏曹先生 <1992414357@qq.com>
Date: Mon, 17 Aug 2026 03:27:46 +0800
Subject: refactor!: remove Node type and simplify dispatcher macro syntax
The `dispatcher!` macro no longer requires a `CMD*` dispatcher type
argument; the dispatcher struct is now generated internally as
`__Dispatcher{Pascal}`. The `Node` type, `node!` macro, and
`Dispatcher::node()` / `clone_dispatcher()` methods are removed.
---
docs/_zh_CN/pages/10-help.md | 4 ++--
docs/_zh_CN/pages/11-resource-system.md | 6 ++---
docs/_zh_CN/pages/13-hook.md | 2 +-
docs/_zh_CN/pages/14-testing.md | 6 ++---
docs/_zh_CN/pages/2-define-a-dispatcher.md | 10 ++++----
docs/_zh_CN/pages/3-define-a-chain.md | 8 +++----
docs/_zh_CN/pages/4-render-result.md | 2 +-
docs/_zh_CN/pages/5-multiple-commands.md | 8 +++----
docs/_zh_CN/pages/6-argument-parse-picker.md | 24 +++++++++----------
docs/_zh_CN/pages/7-argument-parse-clap.md | 4 ++--
docs/_zh_CN/pages/9-error-handling.md | 6 ++---
docs/_zh_CN/pages/advanced/1-completion.md | 2 +-
.../_zh_CN/pages/advanced/2-structural-renderer.md | 4 ++--
docs/_zh_CN/pages/other/features.md | 16 ++++++-------
docs/_zh_CN/pages/other/naming_rule.md | 28 ++++++++++------------
15 files changed, 64 insertions(+), 66 deletions(-)
(limited to 'docs/_zh_CN')
diff --git a/docs/_zh_CN/pages/10-help.md b/docs/_zh_CN/pages/10-help.md
index 99a81fd..6043abc 100644
--- a/docs/_zh_CN/pages/10-help.md
+++ b/docs/_zh_CN/pages/10-help.md
@@ -14,7 +14,7 @@ Mingling 里用 `#[help]` 宏给命令添加帮助文本。
```rust
@@@use mingling::macros::help;
@@@use mingling::macros::buffer;
-@@@dispatcher!("greet", CMDGreet => EntryGreet);
+@@@dispatcher!("greet", EntryGreet);
#[help(buffer)]
fn help_greet(_entry: EntryGreet) {
r_println!("Usage: greet [name]");
@@ -51,7 +51,7 @@ fn help_root(entry: EntryFallback) {
```rust
@@@use mingling::macros::help;
@@@use mingling::setup::BasicProgramSetup;
-@@@dispatcher!("greet", CMDGreet => EntryGreet);
+@@@dispatcher!("greet", EntryGreet);
fn main() {
let mut program = ThisProgram::new();
program.with_setup(BasicProgramSetup);
diff --git a/docs/_zh_CN/pages/11-resource-system.md b/docs/_zh_CN/pages/11-resource-system.md
index b127a3a..40d8cd9 100644
--- a/docs/_zh_CN/pages/11-resource-system.md
+++ b/docs/_zh_CN/pages/11-resource-system.md
@@ -30,7 +30,7 @@ fn main() {
@@@use mingling::macros::buffer;
@@@#[derive(Default, Clone)]
@@@struct ResCurrentDir(String);
-@@@dispatcher!("pwd", CMDPrintWorkingDir => EntryPrintWorkingDir);
+@@@dispatcher!("pwd", EntryPrintWorkingDir);
@@@pack!(ResultPath = String);
// 通过 &T 注入只读资源
#[chain]
@@ -52,7 +52,7 @@ fn render_path(result: ResultPath) {
@@@use mingling::macros::buffer;
@@@#[derive(Default, Clone)]
@@@struct ResVisitCount(u32);
-@@@dispatcher!("visit", CMDVisit => EntryVisit);
+@@@dispatcher!("visit", EntryVisit);
@@@pack!(ResultDone = ());
#[chain]
fn handle_visit(_args: EntryVisit, counter: &mut ResVisitCount) -> Next {
@@ -73,7 +73,7 @@ Chain 可以同时注入任意多个资源,框架按类型自动匹配:
```rust
@@@#[derive(Default, Clone)] struct ResConfig(String);
@@@#[derive(Default, Clone)] struct ResCounter(u32);
-@@@dispatcher!("test", CMDTest => EntryTest);
+@@@dispatcher!("test", EntryTest);
@@@pack!(ResultDone = ());
// 同时注入只读 + 可修改
#[chain]
diff --git a/docs/_zh_CN/pages/13-hook.md b/docs/_zh_CN/pages/13-hook.md
index 2aa3ef7..811957c 100644
--- a/docs/_zh_CN/pages/13-hook.md
+++ b/docs/_zh_CN/pages/13-hook.md
@@ -54,7 +54,7 @@ Hook 覆盖了管线的完整生命周期:
@@@use mingling::prelude::*;
@@@use mingling::hook::ProgramHook;
@@@
-@@@dispatcher!("greet", CMDGreet => EntryGreet);
+@@@dispatcher!("greet", EntryGreet);
@@@pack!(ResultName = String);
@@@
@@@#[chain] fn handle_greet(args: EntryGreet) -> Next {
diff --git a/docs/_zh_CN/pages/14-testing.md b/docs/_zh_CN/pages/14-testing.md
index 3567aef..30f3fc4 100644
--- a/docs/_zh_CN/pages/14-testing.md
+++ b/docs/_zh_CN/pages/14-testing.md
@@ -35,7 +35,7 @@ fn test_render_name() {
```rust
@@@use mingling::{assert_member_id, assert_render_result, unpack_chain_process};
-@@@dispatcher!("hello", CMDHello => EntryHello);
+@@@dispatcher!("hello", EntryHello);
@@@pack!(ResultName = String);
@@@pack!(ErrorNoName = ());
@@@#[chain]
@@ -77,7 +77,7 @@ fn test_handle_hello_with_name() {
@@@use mingling::{assert_member_id, unpack_chain_process};
@@@use mingling::macros::entry;
-@@@dispatcher!("hello", CMDHello => EntryHello);
+@@@dispatcher!("hello", EntryHello);
@@@pack!(ResultName = String);
@@@#[chain]
@@@fn handle_hello(args: EntryHello) -> Next {
@@ -102,7 +102,7 @@ fn test_with_entry_macro() {
@@@use mingling::{assert_render_result, unpack_chain_process};
@@@#[derive(Default, Clone)]
@@@struct ResPrefix(String);
-@@@dispatcher!("hello", CMDHello => EntryHello);
+@@@dispatcher!("hello", EntryHello);
@@@pack!(ResultGreeting = String);
@@@
#[chain]
diff --git a/docs/_zh_CN/pages/2-define-a-dispatcher.md b/docs/_zh_CN/pages/2-define-a-dispatcher.md
index 4ec7c05..7ae26f1 100644
--- a/docs/_zh_CN/pages/2-define-a-dispatcher.md
+++ b/docs/_zh_CN/pages/2-define-a-dispatcher.md
@@ -19,13 +19,13 @@ Mingling 的管线从 Dispatcher 开始。
写法是固定的三个部分:
```rust
-dispatcher!("命令路径", 分发器类型 => 入口类型);
+dispatcher!("命令路径", 入口类型);
```
看一个具体的例子:
```rust
-dispatcher!("greet", CMDGreet => EntryGreet);
+dispatcher!("greet", EntryGreet);
```
> [!NOTE]
@@ -36,8 +36,8 @@ dispatcher!("greet", CMDGreet => EntryGreet);
如果你的程序有层级结构——比如 `remote add`、`remote rm`——只需要在命令名里加点号分隔:
```rust
-dispatcher!("remote.add", CMDRemoteAdd => EntryRemoteAdd);
-dispatcher!("remote.rm", CMDRemoteRm => EntryRemoteRm);
+dispatcher!("remote.add", EntryRemoteAdd);
+dispatcher!("remote.rm", EntryRemoteRm);
```
用户在终端输入 `remote add` 时,Mingling 会依次匹配 `remote` 和 `add` 两个层级。
@@ -68,7 +68,7 @@ pub struct EntryGreet {
// Features: ["extras"]
// 省略 CMDType 和 EntryType,名字自动推导
dispatcher!("greet");
-// dispatcher!("greet", CMDGreet => EntryGreet);
+// dispatcher!("greet", EntryGreet);
```
这种写法会自动生成 `CMDGreet` 和 `EntryGreet`,效果跟显式声明完全一样。
diff --git a/docs/_zh_CN/pages/3-define-a-chain.md b/docs/_zh_CN/pages/3-define-a-chain.md
index b1becc9..bf30b91 100644
--- a/docs/_zh_CN/pages/3-define-a-chain.md
+++ b/docs/_zh_CN/pages/3-define-a-chain.md
@@ -3,7 +3,7 @@
使用 chain 宏声明链,并承接 Entry 输入
-上一节我们声明了 `dispatcher!("greet", CMDGreet => EntryGreet)`
+上一节我们声明了 `dispatcher!("greet", EntryGreet)`
现在用户输入 `greet` 时会被匹配并包装成 `EntryGreet`。
@@ -16,7 +16,7 @@
`#[chain]` 用来标记一个处理函数,格式非常直接:
```rust
-@@@dispatcher!("greet", CMDGreet => EntryGreet);
+@@@dispatcher!("greet", EntryGreet);
pack!(ResultName = String);
#[chain]
@@ -77,7 +77,7 @@ pub struct ResultName {
`EntryGreet` 的 `inner` 是一个 `Vec`,你可以在 Chain 里自由地处理它:
```rust
-@@@dispatcher!("greet", CMDGreet => EntryGreet);
+@@@dispatcher!("greet", EntryGreet);
@@@pack!(ResultName = String);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
@@ -100,7 +100,7 @@ fn handle_greet(args: EntryGreet) -> Next {
```rust
// 1. 声明命令
-dispatcher!("greet", CMDGreet => EntryGreet);
+dispatcher!("greet", EntryGreet);
// 2. 声明管线中的数据类型
pack!(ResultName = String);
diff --git a/docs/_zh_CN/pages/4-render-result.md b/docs/_zh_CN/pages/4-render-result.md
index 2dbb617..e3e49f5 100644
--- a/docs/_zh_CN/pages/4-render-result.md
+++ b/docs/_zh_CN/pages/4-render-result.md
@@ -52,7 +52,7 @@ fn render_name(name: ResultName) {
use mingling::macros::buffer;
// 1. 用 Dispatcher 声明命令
-dispatcher!("greet", CMDGreet => EntryGreet);
+dispatcher!("greet", EntryGreet);
// 2. 用 pack! 声明结果数据
pack!(ResultName = String);
diff --git a/docs/_zh_CN/pages/5-multiple-commands.md b/docs/_zh_CN/pages/5-multiple-commands.md
index b251232..e0a58e7 100644
--- a/docs/_zh_CN/pages/5-multiple-commands.md
+++ b/docs/_zh_CN/pages/5-multiple-commands.md
@@ -12,8 +12,8 @@
```rust
@@@use mingling::macros::buffer;
// 声明两个命令
-dispatcher!("greet", CMDGreet => EntryGreet);
-dispatcher!("add", CMDAdd => EntryAdd);
+dispatcher!("greet", EntryGreet);
+dispatcher!("add", EntryAdd);
pack!(ResultGreeting = String);
pack!(ResultSum = i32);
@@ -62,8 +62,8 @@ Sum: 6
多层级的命令也是同理——每个点号分隔的层级都只是名字的一部分:
```rust
-dispatcher!("remote.add", CMDRemoteAdd => EntryRemoteAdd);
-dispatcher!("remote.rm", CMDRemoteRm => EntryRemoteRm);
+dispatcher!("remote.add", EntryRemoteAdd);
+dispatcher!("remote.rm", EntryRemoteRm);
```
每个子命令的 Entry、Chain、Renderer 完全独立,互不干扰。
diff --git a/docs/_zh_CN/pages/6-argument-parse-picker.md b/docs/_zh_CN/pages/6-argument-parse-picker.md
index 462a912..33f1f0d 100644
--- a/docs/_zh_CN/pages/6-argument-parse-picker.md
+++ b/docs/_zh_CN/pages/6-argument-parse-picker.md
@@ -26,7 +26,7 @@ features = ["parser"]
```rust
// Features: ["parser"]
-@@@dispatcher!("greet", CMDGreet => EntryGreet);
+@@@dispatcher!("greet", EntryGreet);
@@@pack!(ResultName = String);
#[chain]
@@ -42,7 +42,7 @@ fn handle_greet_entry(prev: EntryGreet) -> Next {
```rust
// Features: ["parser"]
-@@@dispatcher!("greet", CMDGreet => EntryGreet);
+@@@dispatcher!("greet", EntryGreet);
@@@pack!(ResultName = String);
@@@#[chain]
@@@fn handle_greet_entry(prev: EntryGreet) -> Next {
@@ -55,7 +55,7 @@ let name = prev.pick_or((), "World").unpack();
```rust
// Features: ["parser"]
-@@@dispatcher!("greet", CMDGreet => EntryGreet);
+@@@dispatcher!("greet", EntryGreet);
@@@pack!(ResultName = String);
@@@#[chain]
@@@fn handle_greet_entry(prev: EntryGreet) {
@@ -76,7 +76,7 @@ let name = prev.pick_or((), "World").unpack();
```rust
// Features: ["parser"]
-@@@dispatcher!("greet", CMDGreet => EntryGreet);
+@@@dispatcher!("greet", EntryGreet);
@@@pack!(ResultName = String);
#[chain]
@@ -90,7 +90,7 @@ fn handle_greet_entry(prev: EntryGreet) -> Next {
```rust
// Features: ["parser"]
-@@@dispatcher!("greet", CMDGreet => EntryGreet);
+@@@dispatcher!("greet", EntryGreet);
@@@pack!(ResultName = String);
@@@#[chain]
@@@fn handle_greet_entry(prev: EntryGreet) {
@@ -113,7 +113,7 @@ fn handle_greet_entry(prev: EntryGreet) -> Next {
```rust
// Features: ["parser"]
-@@@dispatcher!("test", CMDTest => EntryTest);
+@@@dispatcher!("test", EntryTest);
@@@pack!(ResultInfo = (String, u8, u32));
#[chain]
@@ -141,7 +141,7 @@ fn handle_test_entry(prev: EntryTest) -> Next {
// Features: ["parser", "extras"]
@@@use mingling::macros::buffer;
@@@use mingling::macros::route;
-@@@dispatcher!("greet", CMDGreet => EntryGreet);
+@@@dispatcher!("greet", EntryGreet);
@@@pack!(ResultName = String);
@@@pack!(ErrorNoName = ());
@@ -200,7 +200,7 @@ let name = match pick_result {
````rust
// Features: ["parser"]
-@@@dispatcher!("greet", CMDGreet => EntryGreet);
+@@@dispatcher!("greet", EntryGreet);
@@@pack!(ResultName = String);
#[chain]
@@ -226,7 +226,7 @@ fn handle_greet_entry(prev: EntryGreet) -> Next {
// Features: ["parser", "extras"]
@@@use mingling::macros::buffer;
@@@use mingling::macros::route;
-@@@dispatcher!("greet", CMDGreet => EntryGreet);
+@@@dispatcher!("greet", EntryGreet);
@@@pack!(ResultName = String);
@@@pack!(ErrorNameTooLong = usize);
@@ -276,7 +276,7 @@ fn render_name(prev: ResultName) {
```rust
// Features: ["parser"]
@@@use mingling::parser::Yes;
-@@@dispatcher!("test", CMDTest => EntryTest);
+@@@dispatcher!("test", EntryTest);
@@@pack!(ResultDone = ());
#[chain]
@@ -329,7 +329,7 @@ impl Pickable for Address {
Some(Address { ip, port })
}
}
-@@@dispatcher!("connect", CMDConnect => EntryConnect);
+@@@dispatcher!("connect", EntryConnect);
@@@pack!(ResultConnected = Address);
#[chain]
@@ -369,7 +369,7 @@ pub enum Fruits {
}
impl PickableEnum for Fruits {}
-@@@dispatcher!("eat", CMDEat => EntryEat);
+@@@dispatcher!("eat", EntryEat);
@@@pack!(ResultFruit = Fruits);
#[chain]
diff --git a/docs/_zh_CN/pages/7-argument-parse-clap.md b/docs/_zh_CN/pages/7-argument-parse-clap.md
index 83dddc3..e9475ae 100644
--- a/docs/_zh_CN/pages/7-argument-parse-clap.md
+++ b/docs/_zh_CN/pages/7-argument-parse-clap.md
@@ -27,7 +27,7 @@ features = ["derive", "color"]
@@@ use mingling::macros::dispatcher_clap;
@@@ use mingling::macros::buffer;
#[derive(Default, clap::Parser, Grouped)]
-#[dispatcher_clap("greet", CMDGreet, help = true, error = ErrorGreetParsed)]
+#[dispatcher_clap("greet", help = true, error = ErrorGreetParsed)]
pub struct EntryGreet {
#[clap(default_value = "World")]
name: String,
@@ -63,7 +63,7 @@ fn render_greet_parse_failed(err: ErrorGreetParsed) {
@@@use mingling::setup::BasicProgramSetup;
@@@use mingling::macros::dispatcher_clap;
@@@#[derive(Default, clap::Parser, Grouped)]
-@@@#[dispatcher_clap("greet", CMDGreet)]
+@@@#[dispatcher_clap("greet", )]
@@@pub struct EntryGreet {
@@@ name: String,
@@@}
diff --git a/docs/_zh_CN/pages/9-error-handling.md b/docs/_zh_CN/pages/9-error-handling.md
index 222ef44..dce48d1 100644
--- a/docs/_zh_CN/pages/9-error-handling.md
+++ b/docs/_zh_CN/pages/9-error-handling.md
@@ -19,7 +19,7 @@
## 用独立类型区分错误
```rust
-@@@dispatcher!("greet", CMDGreet => EntryGreet);
+@@@dispatcher!("greet", EntryGreet);
pack!(ResultGreeting = String);
pack!(ErrorNameEmpty = String);
@@ -39,7 +39,7 @@ fn handle_greet(args: EntryGreet) -> Next {
```rust
@@@use mingling::macros::buffer;
-@@@dispatcher!("greet", CMDGreet => EntryGreet);
+@@@dispatcher!("greet", EntryGreet);
@@@pack!(ResultGreeting = String);
@@@pack!(ErrorNameEmpty = String);
@@@#[chain] fn handle_greet(args: EntryGreet) -> Next { ResultGreeting::new(args.inner.first().cloned().unwrap_or_default()).to_render() }
@@ -61,7 +61,7 @@ fn render_error_name_empty(err: ErrorNameEmpty) {
```rust
@@@use mingling::macros::buffer;
-dispatcher!("greet", CMDGreet => EntryGreet);
+dispatcher!("greet", EntryGreet);
pack!(ResultGreeting = String);
pack!(ErrorNameEmpty = String);
diff --git a/docs/_zh_CN/pages/advanced/1-completion.md b/docs/_zh_CN/pages/advanced/1-completion.md
index c7dd678..28587b3 100644
--- a/docs/_zh_CN/pages/advanced/1-completion.md
+++ b/docs/_zh_CN/pages/advanced/1-completion.md
@@ -40,7 +40,7 @@ features = [
@@@use mingling::prelude::*;
@@@use mingling::{ShellContext, Suggest, SuggestItem};
@@@use std::collections::BTreeSet;
-@@@dispatcher!("greet", CMDGreet => EntryGreet);
+@@@dispatcher!("greet", EntryGreet);
#[completion(EntryGreet)]
fn complete_greet(ctx: &ShellContext) -> Suggest {
diff --git a/docs/_zh_CN/pages/advanced/2-structural-renderer.md b/docs/_zh_CN/pages/advanced/2-structural-renderer.md
index 9723247..f2e1717 100644
--- a/docs/_zh_CN/pages/advanced/2-structural-renderer.md
+++ b/docs/_zh_CN/pages/advanced/2-structural-renderer.md
@@ -29,7 +29,7 @@ features = ["structural_renderer"]
// serde = "1"
@@@use mingling::macros::buffer;
@@@use mingling::setup::StructuralRendererSetup;
-@@@dispatcher!("render", CMDRender => EntryRender);
+@@@dispatcher!("render", EntryRender);
// pack_structural! 等价于 pack! + StructuralData
pack_structural!(ResultInfo = (String, i32));
@@ -72,7 +72,7 @@ fn render_info(r: ResultInfo) {
@@@use mingling::setup::StructuralRendererSetup;
@@@use mingling::StructuralData;
@@@use serde::Serialize;
-@@@dispatcher!("render", CMDRender => EntryRender);
+@@@dispatcher!("render", EntryRender);
#[derive(Serialize, StructuralData, Grouped)]
struct Info {
diff --git a/docs/_zh_CN/pages/other/features.md b/docs/_zh_CN/pages/other/features.md
index 7ed3f5f..4913945 100644
--- a/docs/_zh_CN/pages/other/features.md
+++ b/docs/_zh_CN/pages/other/features.md
@@ -151,14 +151,14 @@ build_comp_scripts("myprogram").unwrap();
例如,允许 `dispatcher!("greet")` 的缩写形式,自动生成 `CMDGreet` / `EntryGreet`。
-| 宏 | 说明 |
-| ------------------------------------------------------- | --------------------------------------------- |
-| `empty_result!()` | 链中提前返回空结果的简写 |
-| `entry!(Type, ["a", "b"])` | 构造入口类型的测试数据 |
-| `group!(Type)` | 将外部类型注册为组成员,无需修改其定义 |
-| `pack_err!(ErrorType)` / `pack_err!(ErrorType = Inner)` | 创建带自动 `name` 字段的错误类型 |
-| `#[program_setup]` | 声明程序初始化函数 |
-| `dispatcher!("cmd.path")` **缩写形式** | 省略 `CMDStruct => EntryStruct`,名字自动推导 |
+| 宏 | 说明 |
+| ------------------------------------------------------- | -------------------------------------- |
+| `empty_result!()` | 链中提前返回空结果的简写 |
+| `entry!(Type, ["a", "b"])` | 构造入口类型的测试数据 |
+| `group!(Type)` | 将外部类型注册为组成员,无需修改其定义 |
+| `pack_err!(ErrorType)` / `pack_err!(ErrorType = Inner)` | 创建带自动 `name` 字段的错误类型 |
+| `#[program_setup]` | 声明程序初始化函数 |
+| `dispatcher!("cmd.path")` **缩写形式** | 省略 `EntryStruct`,入口类型名自动推导 |
Details
diff --git a/docs/_zh_CN/pages/other/naming_rule.md b/docs/_zh_CN/pages/other/naming_rule.md
index 6e65ee4..1bca8f6 100644
--- a/docs/_zh_CN/pages/other/naming_rule.md
+++ b/docs/_zh_CN/pages/other/naming_rule.md
@@ -40,19 +40,17 @@ Res + 名称
### 分发器
-分发器是命令的入口点,与 `Node` 名称一一对应。节点名用 `.` 分隔层级,分发器名用 `CMD` 前缀加 PascalCase。
+分发器是命令的入口点。命令名用 `.` 分隔层级,与用户输入的命令参数一一对应。
```
-CMD + 命令层级
+命令名
```
-| 节点 | 分发器 |
-| ------------ | ----------------- |
-| `greet` | `CMDGreet` |
-| `remote.add` | `CMDRemoteAdd` |
-| `remote.rm` | `CMDRemoteRemove` |
-
-即使节点是缩写,分发器的名称也要写全名。例如节点是 `remote.rm`,分发器是 `CMDRemoteRemove`,不是 `CMDRemoteRm`。
+| 命令 |
+| ------------ |
+| `greet` |
+| `remote.add` |
+| `remote.rm` |
### 入口
@@ -62,11 +60,11 @@ CMD + 命令层级
Entry + 命令层级
```
-| 分发器 | 入口 |
-| ----------------- | ------------------- |
-| `CMDGreet` | `EntryGreet` |
-| `CMDRemoteAdd` | `EntryRemoteAdd` |
-| `CMDRemoteRemove` | `EntryRemoteRemove` |
+| 命令 | 入口 |
+| ------------ | ------------------- |
+| `greet` | `EntryGreet` |
+| `remote.add` | `EntryRemoteAdd` |
+| `remote.rm` | `EntryRemoteRemove` |
### 状态
@@ -174,7 +172,7 @@ fn handle_remote_add(args: EntryRemoteAdd, cwd: &ResCurrentDir, db: &mut ResData
@@@ pack!(ResultRemoteAdded = String);
@@@ pack!(ErrorRepositoryNotFound = String);
// 分发器
-dispatcher!("remote.add", CMDRemoteAdd => EntryRemoteAdd);
+dispatcher!("remote.add", EntryRemoteAdd);
// 入口 → 状态
#[chain]
--
cgit