@koishi-ce/plugin-help 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # @koishi-ce/plugin-help
2
+
3
+ **简体中文** | [English](#english)
4
+
5
+ 帮助指令插件,移植自上游 [koishijs/koishi](https://github.com/koishijs/koishi) 的 `plugins/common/help`。列出当前可用的指令清单,或输出某条指令的详细帮助(描述、别名、用法、选项、示例与子指令),并按权限与隐藏标记过滤展示内容。
6
+
7
+ ## 指令
8
+
9
+ | 指令 | 权限 | 说明 |
10
+ | --- | --- | --- |
11
+ | `help [command:string]` | 0 | 显示帮助信息,选项 `-H, --showHidden` 查看隐藏的选项和指令 |
12
+
13
+ - 不带参数时列出当前可用的全部指令(按显示名排序),带参数时输出目标指令的详细帮助。
14
+ - 注册全局快捷调用「帮助」(fuzzy 匹配)。
15
+ - 指令参数除指令名外,也可以直接输入某条指令的快捷调用文本来定位指令。
16
+ - 未找到指令时,按相似度给出「您要找的是不是…」建议。
17
+
18
+ ## 配置项
19
+
20
+ | 字段 | 类型 | 默认值 | 说明 |
21
+ | --- | --- | --- | --- |
22
+ | `shortcut` | boolean | `true` | 是否启用快捷调用 |
23
+ | `options` | boolean | `true` | 是否为每个指令添加 `-h, --help` 选项 |
24
+
25
+ ### 指令与选项配置扩展
26
+
27
+ 本插件经 `ctx.schema.extend` 为每条指令及指令选项追加以下配置(出现在各指令的配置中,而非本插件的配置):
28
+
29
+ | 位置 | 字段 | 类型 | 默认值 | 说明 |
30
+ | --- | --- | --- | --- | --- |
31
+ | 指令 | `hidden` | 计算属性 boolean | `false` | 在帮助菜单中隐藏指令 |
32
+ | 指令 | `hideOptions` | boolean | `false` | 是否隐藏所有选项 |
33
+ | 指令 | `params` | any | — | 帮助信息的本地化参数 |
34
+ | 选项 | `hidden` | 计算属性 boolean | `false` | 在帮助菜单中隐藏选项 |
35
+ | 选项 | `params` | any | — | 帮助信息的本地化参数 |
36
+
37
+ ## 用法
38
+
39
+ ```bash
40
+ bun add @koishi-ce/plugin-help
41
+ ```
42
+
43
+ 也可以在控制台的插件市场中直接安装。随后在配置文件中启用:
44
+
45
+ ```yaml
46
+ plugins:
47
+ help: {}
48
+ ```
49
+
50
+ ## 备注
51
+
52
+ - 为所有指令注入隐藏的 `-h, --help` 选项;带 `-h` 调用、或调用本身没有 action 的指令时,自动转而输出该指令的帮助。
53
+ - 详细帮助依次输出:指令标题、描述、别名、用法、可用选项、使用示例与子指令列表。
54
+ - 选项的权限要求高于当前用户、或被 `hidden` 标记隐藏时不展示,`-H` 可查看。
55
+ - 指令列表与详情均按 `command:<name>` 权限过滤,无权限的指令不展示。
56
+ - 提供 `help/command` 与 `help/option` 事件,供其他插件改写帮助输出。
57
+
58
+ ## 许可证
59
+
60
+ [MIT](https://github.com/Koishi-CE/koishi/blob/main/LICENSE)。本包是上游 [koishijs/koishi](https://github.com/koishijs/koishi) 的社区再分发,版权归属见 [NOTICE](https://github.com/Koishi-CE/koishi/blob/main/NOTICE)。
61
+
62
+ ---
63
+
64
+ ## English
65
+
66
+ A help command plugin, ported from `plugins/common/help` of the upstream [koishijs/koishi](https://github.com/koishijs/koishi) repository. It lists the available commands, or shows detailed help for a specific command (description, aliases, usage, options, examples and subcommands), filtered by authority and hidden flags.
67
+
68
+ ## Commands
69
+
70
+ | Command | Authority | Description |
71
+ | --- | --- | --- |
72
+ | `help [command:string]` | 0 | Show help; the `-H, --showHidden` option reveals hidden options and commands |
73
+
74
+ - Without arguments it lists all available commands; with an argument it shows detailed help for the target command.
75
+ - Registers the global shortcut "帮助" (Chinese, fuzzy matching).
76
+ - Besides command names, the argument also accepts a command's shortcut text to locate it.
77
+ - When a command is not found, it suggests similar ones.
78
+
79
+ ## Configuration
80
+
81
+ | Field | Type | Default | Description |
82
+ | --- | --- | --- | --- |
83
+ | `shortcut` | boolean | `true` | Whether to enable the shortcut |
84
+ | `options` | boolean | `true` | Whether to add a `-h, --help` option to every command |
85
+
86
+ ### Command and Option Config Extensions
87
+
88
+ Via `ctx.schema.extend`, the following fields are appended to every command and option config (on each command, not on this plugin):
89
+
90
+ | Scope | Field | Type | Default | Description |
91
+ | --- | --- | --- | --- | --- |
92
+ | Command | `hidden` | computed boolean | `false` | Hide the command from help menus |
93
+ | Command | `hideOptions` | boolean | `false` | Hide all options |
94
+ | Command | `params` | any | — | Localization params for help output |
95
+ | Option | `hidden` | computed boolean | `false` | Hide the option from help menus |
96
+ | Option | `params` | any | — | Localization params for help output |
97
+
98
+ ## Usage
99
+
100
+ ```bash
101
+ bun add @koishi-ce/plugin-help
102
+ ```
103
+
104
+ The plugin can also be installed from the console plugin market, then enabled in the config file:
105
+
106
+ ```yaml
107
+ plugins:
108
+ help: {}
109
+ ```
110
+
111
+ ## Notes
112
+
113
+ - Injects a hidden `-h, --help` option into every command; calling with `-h`, or calling a command without an action, shows its help instead.
114
+ - Options whose authority exceeds the user or marked as `hidden` are not shown unless `-H` is used.
115
+ - Command lists and details are filtered by the `command:<name>` permission.
116
+ - Provides `help/command` and `help/option` events for other plugins to customize the output.
117
+
118
+ ## License
119
+
120
+ [MIT](https://github.com/Koishi-CE/koishi/blob/main/LICENSE). This package is a community redistribution of upstream koishijs/koishi; see [NOTICE](https://github.com/Koishi-CE/koishi/blob/main/NOTICE) for attribution.
@@ -1,3 +1,6 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2019-present Shigma and Koishijs contributors.
3
+
1
4
  commands:
2
5
  help:
3
6
  description: Show help
@@ -1,3 +1,6 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2019-present Shigma and Koishijs contributors.
3
+
1
4
  commands:
2
5
  help:
3
6
  description: 显示帮助信息
package/lib/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Command, Computed, Context, Schema, Session } from "@koishi-ce/koishi";
1
+ import { Computed, Context, Schema, Session } from "@koishi-ce/koishi";
2
2
  //#region src/index.d.ts
3
3
  declare module "@koishi-ce/koishi" {
4
4
  interface Events {
@@ -25,14 +25,13 @@ declare module "@koishi-ce/koishi" {
25
25
  }
26
26
  }
27
27
  /** 配置项 */
28
- interface Config {
28
+ export interface Config {
29
29
  /** 是否启用“帮助”快捷调用 */
30
30
  shortcut?: boolean;
31
31
  /** 是否为每个指令注入 `-h, --help` 选项 */
32
32
  options?: boolean;
33
33
  }
34
- declare const Config: Schema<Config>;
35
- declare const name = "help";
36
- declare function apply(ctx: Context, config: Config): void;
37
- //#endregion
38
- export { Config, apply, name };
34
+ export declare const Config: Schema<Config>;
35
+ export declare const name = "help";
36
+ export declare function apply(ctx: Context, config: Config): void;
37
+ //#endregion
package/lib/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Context, Schema, h } from "@koishi-ce/koishi";
2
- import enUS from "./assets/en-US-f1RJaaO6.yml";
3
- import zhCN from "./assets/zh-CN-DmsdBxOO.yml";
2
+ import enUS from "./assets/en-US-DRtqxx3I.yml";
3
+ import zhCN from "./assets/zh-CN-DCysH3jk.yml";
4
4
  //#region src/index.ts
5
5
  /**
6
6
  * 帮助指令插件(help)。
package/locales/de-DE.yml CHANGED
@@ -1,22 +1,25 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2019-present Shigma and Koishijs contributors.
3
+
1
4
  commands:
2
5
  help:
3
- description: 显示帮助信息
6
+ description: Hilfe anzeigen
4
7
  shortcuts:
5
8
  help: Hilfe
6
9
  options:
7
- help: 显示此信息
8
- authority: 显示权限设置
9
- showHidden: 查看隐藏的选项和指令
10
+ help: Diese Meldung anzeigen
11
+ authority: Berechtigungsanforderungen anzeigen
12
+ showHidden: Versteckte Optionen und Befehle anzeigen
10
13
  messages:
11
- not-found: 指令未找到。
12
- hint-authority: 括号内为对应的最低权限等级
13
- hint-subcommand: 标有星号的表示含有子指令
14
- command-title: 指令:{0}
15
- command-aliases: 别名:{0}
16
- command-examples: 使用示例:
17
- command-authority: 最低权限:{0} 级。
18
- subcommand-prolog: 可用的子指令有{0}
19
- global-prolog: 当前可用的指令有{0}
20
- global-epilog: 输入“{0}help 指令名”查看特定指令的语法和使用示例。
21
- available-options: 可用的选项有:
22
- available-options-with-authority: 可用的选项有(括号内为额外要求的权限等级):
14
+ not-found: Befehl nicht gefunden.
15
+ hint-authority: die minimale Berechtigung ist in Klammern angegeben
16
+ hint-subcommand: mit einem Sternchen markierte Befehle besitzen Unterbefehle
17
+ command-title: 'Befehl: {0}'
18
+ command-aliases: 'Aliase: {0}.'
19
+ command-examples: 'Beispiele:'
20
+ command-authority: 'Minimale Berechtigung: {0}.'
21
+ subcommand-prolog: 'Verfügbare Unterbefehle{0}:'
22
+ global-prolog: 'Verfügbare Befehle{0}:'
23
+ global-epilog: Geben Sie „{0}help <Befehl>“ ein, um Syntax und Beispiele für einen bestimmten Befehl anzuzeigen.
24
+ available-options: 'Verfügbare Optionen:'
25
+ available-options-with-authority: 'Verfügbare Optionen (in Klammern angegebene zusätzlich erforderliche Berechtigung):'
package/locales/en-US.yml CHANGED
@@ -1,3 +1,6 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2019-present Shigma and Koishijs contributors.
3
+
1
4
  commands:
2
5
  help:
3
6
  description: Show help
package/locales/fr-FR.yml CHANGED
@@ -1,3 +1,6 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2019-present Shigma and Koishijs contributors.
3
+
1
4
  commands:
2
5
  help:
3
6
  description: Afficher l'aide
package/locales/ja-JP.yml CHANGED
@@ -1,3 +1,6 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2019-present Shigma and Koishijs contributors.
3
+
1
4
  commands:
2
5
  help:
3
6
  description: ヘルプを表示
package/locales/ru-RU.yml CHANGED
@@ -1,22 +1,25 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2019-present Shigma and Koishijs contributors.
3
+
1
4
  commands:
2
5
  help:
3
- description: 显示帮助信息
6
+ description: Показать справку
4
7
  shortcuts:
5
8
  help: Справка
6
9
  options:
7
- help: 显示此信息
8
- authority: 显示权限设置
9
- showHidden: 查看隐藏的选项和指令
10
+ help: показать это сообщение
11
+ authority: показать требования к правам
12
+ showHidden: показать скрытые опции и команды
10
13
  messages:
11
- not-found: 指令未找到。
12
- hint-authority: 括号内为对应的最低权限等级
13
- hint-subcommand: 标有星号的表示含有子指令
14
- command-title: 指令:{0}
15
- command-aliases: 别名:{0}
16
- command-examples: 使用示例:
17
- command-authority: 最低权限:{0} 级。
18
- subcommand-prolog: 可用的子指令有{0}
19
- global-prolog: 当前可用的指令有{0}
20
- global-epilog: 输入“{0}help 指令名”查看特定指令的语法和使用示例。
21
- available-options: 可用的选项有:
22
- available-options-with-authority: 可用的选项有(括号内为额外要求的权限等级):
14
+ not-found: Команда не найдена.
15
+ hint-authority: минимальный уровень прав указан в скобках
16
+ hint-subcommand: команды, отмеченные звёздочкой, имеют подкоманды
17
+ command-title: 'Команда: {0}'
18
+ command-aliases: 'Псевдонимы: {0}.'
19
+ command-examples: 'Примеры:'
20
+ command-authority: 'Минимальный уровень прав: {0}.'
21
+ subcommand-prolog: 'Доступные подкоманды{0}:'
22
+ global-prolog: 'Доступные команды{0}:'
23
+ global-epilog: Введите "{0}help <команда>", чтобы увидеть синтаксис и примеры для конкретной команды.
24
+ available-options: 'Доступные опции:'
25
+ available-options-with-authority: 'Доступные опции (в скобках указано дополнительное требование к правам):'
package/locales/zh-CN.yml CHANGED
@@ -1,3 +1,6 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2019-present Shigma and Koishijs contributors.
3
+
1
4
  commands:
2
5
  help:
3
6
  description: 显示帮助信息
package/locales/zh-TW.yml CHANGED
@@ -1,3 +1,6 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2019-present Shigma and Koishijs contributors.
3
+
1
4
  commands:
2
5
  help:
3
6
  description: 顯示幫助信息
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@koishi-ce/plugin-help",
3
3
  "description": "Help plugin for Koishi",
4
- "version": "1.0.0",
4
+ "version": "1.1.0",
5
5
  "type": "module",
6
6
  "main": "lib/index.mjs",
7
7
  "typings": "lib/index.d.ts",
@@ -47,13 +47,12 @@
47
47
  ]
48
48
  },
49
49
  "peerDependencies": {
50
- "koishi": "^4.18.11"
50
+ "@koishi-ce/koishi": "^1.0.0"
51
51
  },
52
52
  "devDependencies": {
53
+ "@koishi-ce/koishi": "^1.0.8",
53
54
  "@koishi-ce/plugin-mock": "^1.0.0",
54
- "@minatojs/driver-memory": "^3.7.0",
55
- "@koishi-ce/koishi": "^1.0.0",
56
- "minato": "^3.7.0"
55
+ "@koishijs/plugin-database-memory": "^3.7.0"
57
56
  },
58
57
  "exports": {
59
58
  ".": {
@@ -0,0 +1,98 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2026-present Koishi-CE contributors.
3
+
4
+ /**
5
+ * help 插件补充用例(bun:test 断言):
6
+ * 父指令对当前会话不可见时下钻子指令(getCommands else 分支)、
7
+ * 选项权限不足时从帮助中隐藏、i18n 定义的示例文本逐行展示、
8
+ * 以及快捷调用的模糊命中给出的纠错建议(候选字段收集循环)。
9
+ *
10
+ * 备注:inferCommand 里 session.suggest 的 filter 回调(index.ts 223-225)
11
+ * 经 help 插件路径不可达——suggest 仅在传入 actual 时才调用 filter,
12
+ * 而 help 的 suggest 调用不传 actual(见 core/src/session/interact.ts)。
13
+ */
14
+ import { beforeAll, describe, expect, it } from "bun:test";
15
+ import { App } from "@koishi-ce/koishi";
16
+ import * as help from "@koishi-ce/plugin-help";
17
+ import mock from "@koishi-ce/plugin-mock";
18
+ import * as memoryModule from "@koishijs/plugin-database-memory";
19
+
20
+ // CJS 实现配 ESM 声明,Bun 互操作视图多包一层 default,穿透取真实驱动
21
+ const memory =
22
+ memoryModule.default as unknown as typeof memoryModule.default.default;
23
+
24
+ const app = new App({
25
+ minSimilarity: 0.64,
26
+ });
27
+
28
+ app.plugin(mock);
29
+ app.plugin(help);
30
+ app.plugin(memory);
31
+
32
+ app.i18n.define(
33
+ "$zh-CN",
34
+ "commands.help.messages.global-epilog",
35
+ "",
36
+ );
37
+
38
+ const client = app.mock.client("123", "456");
39
+
40
+ beforeAll(async () => {
41
+ await app.start();
42
+ await app.mock.initUser("123", 2);
43
+ await app.mock.initChannel("456");
44
+ });
45
+
46
+ describe("@koishi-ce/plugin-help 补充用例", () => {
47
+ it("父指令对当前会话不可见时不进入全局列表", async () => {
48
+ // 限定 telegram 平台的指令对 mock 会话 match 失败,全局列表不展示
49
+ const parent = app
50
+ .platform("telegram")
51
+ .command("tg", "T");
52
+ parent.subcommand("tgc", "C");
53
+ const [reply] = await client.receive("help");
54
+ expect(reply).toContain("help");
55
+ expect(reply).not.toContain("tg");
56
+ });
57
+
58
+ it("选项权限不足时从帮助中隐藏", async () => {
59
+ app
60
+ .command("adv", "D")
61
+ .option("top", "-t", { authority: 3 });
62
+ const [reply] = await client.receive("help adv");
63
+ expect(reply).toBe("指令:adv\nD");
64
+ });
65
+
66
+ it("i18n 定义的示例文本逐行展示", async () => {
67
+ app.command("demo", "D");
68
+ app.i18n.define(
69
+ "$zh-CN",
70
+ "commands.demo.examples",
71
+ "first\nsecond",
72
+ );
73
+ const [reply] = await client.receive("help demo");
74
+ expect(reply).toBe(
75
+ "指令:demo\nD\n使用示例:\n first\n second",
76
+ );
77
+ });
78
+
79
+ it("快捷调用的模糊命中给出纠错建议", async () => {
80
+ const probe = app.command("probe", "DESCRIPTION");
81
+ probe.shortcut("ask", { i18n: true });
82
+ app.i18n.define(
83
+ "$zh-CN",
84
+ "commands.probe.shortcuts.ask",
85
+ "quickbrownfox",
86
+ );
87
+ // 与快捷调用文本相差一字母:非精确命中,走候选列表与建议流程
88
+ await client.shouldReply(
89
+ "help quickbrownfo",
90
+ "指令未找到。您要找的是不是“probe”?回复句号以使用推测的指令。",
91
+ );
92
+ // 回复句号确认后展示推测指令的帮助
93
+ await client.shouldReply(
94
+ ".",
95
+ "指令:probe\nDESCRIPTION",
96
+ );
97
+ });
98
+ });
package/src/index.test.ts CHANGED
@@ -1,3 +1,6 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2026-present Koishi-CE contributors.
3
+
1
4
  /**
2
5
  * help 插件测试:覆盖帮助列表、指令属性(别名 / 用法 / 示例 / 权限)、
3
6
  * 选项展示、子指令、无数据库场景与 shortcut / options 配置开关。
@@ -6,7 +9,11 @@ import { beforeAll, describe, it } from "bun:test";
6
9
  import { App } from "@koishi-ce/koishi";
7
10
  import * as help from "@koishi-ce/plugin-help";
8
11
  import mock from "@koishi-ce/plugin-mock";
9
- import memory from "@minatojs/driver-memory";
12
+ import * as memoryModule from "@koishijs/plugin-database-memory";
13
+
14
+ // CJS 实现配 ESM 声明,Bun 互操作视图多包一层 default,穿透取真实驱动
15
+ const memory =
16
+ memoryModule.default as unknown as typeof memoryModule.default.default;
10
17
 
11
18
  const app = new App({
12
19
  minSimilarity: 0.64,
@@ -16,7 +23,11 @@ app.plugin(mock);
16
23
  app.plugin(help);
17
24
  app.plugin(memory);
18
25
 
19
- app.i18n.define("$zh-CN", "commands.help.messages.global-epilog", "EPILOG");
26
+ app.i18n.define(
27
+ "$zh-CN",
28
+ "commands.help.messages.global-epilog",
29
+ "EPILOG",
30
+ );
20
31
 
21
32
  const client = app.mock.client("123", "456");
22
33
 
@@ -68,20 +79,30 @@ describe("@koishi-ce/plugin-help", () => {
68
79
  app.command("foo1", "DESCRIPTION").alias("foo");
69
80
  app.command("foo3", "DESCRIPTION").shortcut(/foobar/);
70
81
  app.command("foo4", "DESCRIPTION").usage("USAGE TEXT");
71
- app.command("foo5", "DESCRIPTION").usage(({ userId }) => `${userId}`);
72
- app.command("foo6", "DESCRIPTION").example("EXAMPLE TEXT");
82
+ app
83
+ .command("foo5", "DESCRIPTION")
84
+ .usage(({ userId }) => `${userId}`);
85
+ app
86
+ .command("foo6", "DESCRIPTION")
87
+ .example("EXAMPLE TEXT");
73
88
  app.command("foo7", "DESCRIPTION", { authority: 3 });
74
89
 
75
90
  await client.shouldReply(
76
91
  "help foo1",
77
92
  "指令:foo1\nDESCRIPTION\n别名:foo。",
78
93
  );
79
- await client.shouldReply("help foobar", "指令:foo3\nDESCRIPTION");
94
+ await client.shouldReply(
95
+ "help foobar",
96
+ "指令:foo3\nDESCRIPTION",
97
+ );
80
98
  await client.shouldReply(
81
99
  "help foo4",
82
100
  "指令:foo4\nDESCRIPTION\nUSAGE TEXT",
83
101
  );
84
- await client.shouldReply("help foo5", "指令:foo5\nDESCRIPTION\n123");
102
+ await client.shouldReply(
103
+ "help foo5",
104
+ "指令:foo5\nDESCRIPTION\n123",
105
+ );
85
106
  await client.shouldReply(
86
107
  "help foo6",
87
108
  "指令:foo6\nDESCRIPTION\n使用示例:\n EXAMPLE TEXT",
@@ -92,7 +113,9 @@ describe("@koishi-ce/plugin-help", () => {
92
113
  // 验证 hideOptions、选项权限与 hidden 选项的过滤,以及 -H 的全量展示
93
114
  it("command options", async () => {
94
115
  const bar = app
95
- .command("bar <arg:number>", "DESCRIPTION", { hideOptions: true })
116
+ .command("bar <arg:number>", "DESCRIPTION", {
117
+ hideOptions: true,
118
+ })
96
119
  .option("opt1", "选项1", { authority: 2 })
97
120
  .option("opt1", "-n 选项2", { value: false })
98
121
  .option("opt2", "[arg:boolean] 选项3")
@@ -132,7 +155,9 @@ describe("@koishi-ce/plugin-help", () => {
132
155
 
133
156
  // 验证多级子指令在父指令帮助中的逐层呈现
134
157
  it("subcommand", async () => {
135
- const foo2 = app.command("foo2", "DESCRIPTION", { authority: 0 });
158
+ const foo2 = app.command("foo2", "DESCRIPTION", {
159
+ authority: 0,
160
+ });
136
161
  const foo1 = foo2.subcommand("foo1");
137
162
  foo1.subcommand("foo3");
138
163
 
@@ -163,7 +188,11 @@ describe("@koishi-ce/plugin-help", () => {
163
188
  const app = new App();
164
189
  app.plugin(help);
165
190
  app.plugin(mock);
166
- app.i18n.define("$zh-CN", "commands.help.messages.global-epilog", "");
191
+ app.i18n.define(
192
+ "$zh-CN",
193
+ "commands.help.messages.global-epilog",
194
+ "",
195
+ );
167
196
  await app.start();
168
197
 
169
198
  const client = app.mock.client("123");
@@ -203,7 +232,9 @@ describe("@koishi-ce/plugin-help", () => {
203
232
  const app = new App();
204
233
  app.plugin(help);
205
234
  app.plugin(mock);
206
- app.command("test <arg>", { checkArgCount: true }).action(() => "pass");
235
+ app
236
+ .command("test <arg>", { checkArgCount: true })
237
+ .action(() => "pass");
207
238
  await app.start();
208
239
 
209
240
  const client = app.mock.client("123");
package/src/index.ts CHANGED
@@ -1,3 +1,7 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2019-present Shigma and Koishijs contributors.
3
+ // Copyright (c) 2026-present Koishi-CE contributors.
4
+
1
5
  /**
2
6
  * 帮助指令插件(help)。
3
7
  *
@@ -48,7 +52,9 @@ declare module "@koishi-ce/koishi" {
48
52
  }
49
53
 
50
54
  namespace Argv {
51
- interface OptionConfig<T extends Argv.Type = Argv.Type> {
55
+ interface OptionConfig<
56
+ T extends Argv.Type = Argv.Type,
57
+ > {
52
58
  /** 在帮助中隐藏此选项 */
53
59
  hidden?: Computed<boolean>;
54
60
  /** 本地化参数 */
@@ -72,14 +78,19 @@ export interface Config {
72
78
  }
73
79
 
74
80
  export const Config: Schema<Config> = Schema.object({
75
- shortcut: Schema.boolean().default(true).description("是否启用快捷调用。"),
81
+ shortcut: Schema.boolean()
82
+ .default(true)
83
+ .description("是否启用快捷调用。"),
76
84
  options: Schema.boolean()
77
85
  .default(true)
78
86
  .description("是否为每个指令添加 `-h, --help` 选项。"),
79
87
  });
80
88
 
81
89
  /** 在当前会话中转执行 help 指令(供 -h 选项与无 action 的指令复用) */
82
- function executeHelp(session: Session<never, never>, name: string) {
90
+ function executeHelp(
91
+ session: Session<never, never>,
92
+ name: string,
93
+ ) {
83
94
  if (!session.app.$commander.get("help")) return;
84
95
  return session.execute({
85
96
  name: "help",
@@ -114,7 +125,9 @@ export function apply(ctx: Context, config: Config) {
114
125
  hidden: Schema.computed(Schema.boolean())
115
126
  .description("在帮助菜单中隐藏指令。")
116
127
  .default(false),
117
- params: Schema.any().description("帮助信息的本地化参数。").hidden(),
128
+ params: Schema.any()
129
+ .description("帮助信息的本地化参数。")
130
+ .hidden(),
118
131
  }),
119
132
  900,
120
133
  );
@@ -125,7 +138,9 @@ export function apply(ctx: Context, config: Config) {
125
138
  hidden: Schema.computed(Schema.boolean())
126
139
  .description("在帮助菜单中隐藏选项。")
127
140
  .default(false),
128
- params: Schema.any().description("帮助信息的本地化参数。").hidden(),
141
+ params: Schema.any()
142
+ .description("帮助信息的本地化参数。")
143
+ .hidden(),
129
144
  }),
130
145
  900,
131
146
  );
@@ -139,7 +154,14 @@ export function apply(ctx: Context, config: Config) {
139
154
  // 指令执行前的拦截:带 -h 或指令本身没有 action 时,转而输出帮助
140
155
  ctx.before(
141
156
  "command/execute",
142
- (argv: Argv<never, never, unknown[], { help?: boolean }>) => {
157
+ (
158
+ argv: Argv<
159
+ never,
160
+ never,
161
+ unknown[],
162
+ { help?: boolean }
163
+ >,
164
+ ) => {
143
165
  const { command, options, session } = argv;
144
166
  if (!command || !session || !options) return;
145
167
  if (options["help"] && command._options["help"]) {
@@ -158,16 +180,24 @@ export function apply(ctx: Context, config: Config) {
158
180
  * @param target 用户输入的指令名或快捷调用文本
159
181
  * @returns 指令对象;仅有模糊命中时返回候选列表
160
182
  */
161
- function findCommand(target: string, session: Session<never, never>) {
183
+ function findCommand(
184
+ target: string,
185
+ session: Session<never, never>,
186
+ ) {
162
187
  const command = $.resolve(target, session);
163
188
  if (command?.ctx.filter(session)) return command;
164
189
 
165
190
  // 指令名未命中:转为在各语言的指令快捷调用文本中检索
166
191
  const data = ctx.i18n
167
192
  .find("commands.(name).shortcuts.(variant)", target)
168
- .map((item) => ({ ...item, command: $.resolve(item.data.name, session) }))
193
+ .map((item) => ({
194
+ ...item,
195
+ command: $.resolve(item.data.name, session),
196
+ }))
169
197
  .filter((item) => item.command?.match(session));
170
- const perfect = data.filter((item) => item.similarity === 1);
198
+ const perfect = data.filter(
199
+ (item) => item.similarity === 1,
200
+ );
171
201
  if (!perfect.length) return data;
172
202
  return perfect[0]?.command;
173
203
  }
@@ -175,7 +205,9 @@ export function apply(ctx: Context, config: Config) {
175
205
  // 字段收集器:help 指令自身只用 authority,
176
206
  // 但被查询的目标指令可能声明了额外的 user / channel 观察字段
177
207
  const createCollector =
178
- <T extends "user" | "channel">(key: T): FieldCollector<T> =>
208
+ <T extends "user" | "channel">(
209
+ key: T,
210
+ ): FieldCollector<T> =>
179
211
  (argv, fields) => {
180
212
  const { args, session } = argv;
181
213
  const [target] = args ?? [];
@@ -186,7 +218,12 @@ export function apply(ctx: Context, config: Config) {
186
218
  if (result) {
187
219
  session.collect(
188
220
  key,
189
- { ...argv, command: result, args: [], options: { help: true } },
221
+ {
222
+ ...argv,
223
+ command: result,
224
+ args: [],
225
+ options: { help: true },
226
+ },
190
227
  fields,
191
228
  );
192
229
  }
@@ -196,14 +233,22 @@ export function apply(ctx: Context, config: Config) {
196
233
  if (!command) continue;
197
234
  session.collect(
198
235
  key,
199
- { ...argv, command, args: [], options: { help: true } },
236
+ {
237
+ ...argv,
238
+ command,
239
+ args: [],
240
+ options: { help: true },
241
+ },
200
242
  fields,
201
243
  );
202
244
  }
203
245
  };
204
246
 
205
247
  /** 推断用户输入对应的指令;仅有模糊命中时发起相似度建议(“您要找的是不是…”) */
206
- async function inferCommand(target: string, session: Session) {
248
+ async function inferCommand(
249
+ target: string,
250
+ session: Session,
251
+ ) {
207
252
  const result = findCommand(target, session);
208
253
  if (!Array.isArray(result)) return result;
209
254
 
@@ -223,7 +268,11 @@ export function apply(ctx: Context, config: Config) {
223
268
  filter: (name) => {
224
269
  const command = $.resolve(name, session);
225
270
  if (!command) return false;
226
- return ctx.permissions.test(`command:${command.name}`, session, cache);
271
+ return ctx.permissions.test(
272
+ `command:${command.name}`,
273
+ session,
274
+ cache,
275
+ );
227
276
  },
228
277
  });
229
278
  if (!name) return;
@@ -232,7 +281,10 @@ export function apply(ctx: Context, config: Config) {
232
281
 
233
282
  // 主指令:无参数时列出全局指令清单,带参数时输出目标指令的详细帮助
234
283
  const cmd = ctx
235
- .command("help [command:string]", { authority: 0, ...config })
284
+ .command("help [command:string]", {
285
+ authority: 0,
286
+ ...config,
287
+ })
236
288
  .userFields(["authority"])
237
289
  .userFields(createCollector("user"))
238
290
  .channelFields(createCollector("channel"))
@@ -241,25 +293,40 @@ export function apply(ctx: Context, config: Config) {
241
293
  if (!session || !options) return;
242
294
  if (!target) {
243
295
  const prefix =
244
- session.resolve(session.app.koishi.config.prefix)?.[0] ?? "";
245
- const commands = $._commandList.filter((cmd) => cmd.parent === null);
296
+ session.resolve(
297
+ session.app.koishi.config.prefix,
298
+ )?.[0] ?? "";
299
+ const commands = $._commandList.filter(
300
+ (cmd) => cmd.parent === null,
301
+ );
246
302
  const output = await formatCommands(
247
303
  ".global-prolog",
248
304
  session,
249
305
  commands,
250
306
  options as HelpOptions,
251
307
  );
252
- const epilog = session.text(".global-epilog", [prefix]);
308
+ const epilog = session.text(".global-epilog", [
309
+ prefix,
310
+ ]);
253
311
  if (epilog) output.push(epilog);
254
312
  return output.filter(Boolean).join("\n");
255
313
  }
256
314
 
257
315
  const command = await inferCommand(target, session);
258
316
  if (!command) return;
259
- if (!(await ctx.permissions.test(`command:${command.name}`, session))) {
317
+ if (
318
+ !(await ctx.permissions.test(
319
+ `command:${command.name}`,
320
+ session,
321
+ ))
322
+ ) {
260
323
  return session.text("internal.low-authority");
261
324
  }
262
- return showHelp(command, session, options as HelpOptions);
325
+ return showHelp(
326
+ command,
327
+ session,
328
+ options as HelpOptions,
329
+ );
263
330
  });
264
331
 
265
332
  // 注册全局快捷调用“帮助”(具体文本由各语言的 i18n 文本提供)
@@ -274,12 +341,23 @@ function* getCommands(
274
341
  showHidden = false,
275
342
  ): Generator<Command> {
276
343
  for (const command of commands) {
277
- if (!showHidden && session.resolve(command.config.hidden)) continue;
344
+ if (
345
+ !showHidden &&
346
+ session.resolve(command.config.hidden)
347
+ )
348
+ continue;
278
349
  // 自身可用则产出,否则下钻子指令(子指令可能单独可用)
279
- if (command.match(session) && Object.keys(command._aliases).length) {
350
+ if (
351
+ command.match(session) &&
352
+ Object.keys(command._aliases).length
353
+ ) {
280
354
  yield command;
281
355
  } else {
282
- yield* getCommands(session, command.children, showHidden);
356
+ yield* getCommands(
357
+ session,
358
+ command.children,
359
+ showHidden,
360
+ );
283
361
  }
284
362
  }
285
363
  }
@@ -293,7 +371,9 @@ async function formatCommands(
293
371
  ) {
294
372
  const cache = new Map<string, Promise<boolean>>();
295
373
  // 第一步:按可见性过滤
296
- children = Array.from(getCommands(session, children, options.showHidden));
374
+ children = Array.from(
375
+ getCommands(session, children, options.showHidden),
376
+ );
297
377
  // 第二步:按权限过滤(并行检测并缓存结果)
298
378
  children = (
299
379
  await Promise.all(
@@ -312,18 +392,27 @@ async function formatCommands(
312
392
  .filter(([, result]) => result)
313
393
  .map(([command]) => command);
314
394
  // 第三步:按显示名排序
315
- children.sort((a, b) => (a.displayName > b.displayName ? 1 : -1));
395
+ children.sort((a, b) =>
396
+ a.displayName > b.displayName ? 1 : -1,
397
+ );
316
398
  if (!children.length) return [];
317
399
 
318
- const prefix = session.resolve(session.app.koishi.config.prefix)?.[0] ?? "";
319
- const output = children.map(({ name, displayName, config }) => {
320
- let output = ` ${prefix}${displayName.replace(/\./g, " ")}`;
321
- output += ` ${session.text([`commands.${name}.description`, ""], config.params)}`;
322
- return output;
323
- });
400
+ const prefix =
401
+ session.resolve(
402
+ session.app.koishi.config.prefix,
403
+ )?.[0] ?? "";
404
+ const output = children.map(
405
+ ({ name, displayName, config }) => {
406
+ let output = ` ${prefix}${displayName.replace(/\./g, " ")}`;
407
+ output += ` ${session.text([`commands.${name}.description`, ""], config.params)}`;
408
+ return output;
409
+ },
410
+ );
324
411
  const hints: string[] = [];
325
412
  const hintText = hints.length
326
- ? session.text("general.paren", [hints.join(session.text("general.comma"))])
413
+ ? session.text("general.paren", [
414
+ hints.join(session.text("general.comma")),
415
+ ])
327
416
  : "";
328
417
  output.unshift(session.text(path, [hintText]));
329
418
  return output;
@@ -334,7 +423,10 @@ function getOptionVisibility(
334
423
  option: Argv.OptionConfig,
335
424
  session: Session<"authority">,
336
425
  ) {
337
- if (session.user && (option.authority ?? 0) > session.user.authority) {
426
+ if (
427
+ session.user &&
428
+ (option.authority ?? 0) > session.user.authority
429
+ ) {
338
430
  return false;
339
431
  }
340
432
  return !session.resolve(option.hidden);
@@ -346,7 +438,8 @@ function getOptions(
346
438
  session: Session<"authority">,
347
439
  config: HelpOptions,
348
440
  ) {
349
- if (command.config.hideOptions && !config.showHidden) return [];
441
+ if (command.config.hideOptions && !config.showHidden)
442
+ return [];
350
443
  const options = config.showHidden
351
444
  ? Object.values(command._options)
352
445
  : Object.values(command._options).filter((option) =>
@@ -356,20 +449,37 @@ function getOptions(
356
449
 
357
450
  const output: string[] = [];
358
451
  Object.values(command._options).forEach((option) => {
359
- function pushOption(option: Argv.OptionVariant, name: string) {
360
- if (!config.showHidden && !getOptionVisibility(option, session)) return;
452
+ function pushOption(
453
+ option: Argv.OptionVariant,
454
+ name: string,
455
+ ) {
456
+ if (
457
+ !config.showHidden &&
458
+ !getOptionVisibility(option, session)
459
+ )
460
+ return;
361
461
  let line = `${h.escape(option.syntax)}`;
362
462
  const description = session.text(
363
- option.descPath ?? [`commands.${command.name}.options.${name}`, ""],
463
+ option.descPath ?? [
464
+ `commands.${command.name}.options.${name}`,
465
+ "",
466
+ ],
364
467
  option.params,
365
468
  );
366
469
  if (description) line += ` ${description}`;
367
- line = command.ctx.chain("help/option", line, option, command, session);
470
+ line = command.ctx.chain(
471
+ "help/option",
472
+ line,
473
+ option,
474
+ command,
475
+ session,
476
+ );
368
477
  output.push(` ${line}`);
369
478
  }
370
479
 
371
480
  // 无值选项直接输出;带值选项再逐个输出其语法变体
372
- if (!("value" in option)) pushOption(option, option.name ?? "");
481
+ if (!("value" in option))
482
+ pushOption(option, option.name ?? "");
373
483
  for (const value in option.variants) {
374
484
  const variant = option.variants[value];
375
485
  if (!variant) continue;
@@ -390,7 +500,8 @@ async function showHelp(
390
500
  ) {
391
501
  const output = [
392
502
  session.text(".command-title", [
393
- command.displayName.replace(/\./g, " ") + command.declaration,
503
+ command.displayName.replace(/\./g, " ") +
504
+ command.declaration,
394
505
  ]),
395
506
  ];
396
507
 
@@ -402,11 +513,18 @@ async function showHelp(
402
513
 
403
514
  // 有数据库时按目标指令的声明预取 user / channel 字段(usage 等钩子可能用到)
404
515
  if (session.app.database) {
405
- const argv: Argv = { command, args: [], options: { help: true } };
516
+ const argv: Argv = {
517
+ command,
518
+ args: [],
519
+ options: { help: true },
520
+ };
406
521
  const userFields = session.collect("user", argv);
407
522
  await session.observeUser(userFields);
408
523
  if (!session.isDirect) {
409
- const channelFields = session.collect("channel", argv);
524
+ const channelFields = session.collect(
525
+ "channel",
526
+ argv,
527
+ );
410
528
  await session.observeChannel(channelFields);
411
529
  }
412
530
  }
@@ -414,12 +532,20 @@ async function showHelp(
414
532
  if (Object.keys(command._aliases).length > 1) {
415
533
  output.push(
416
534
  session.text(".command-aliases", [
417
- Array.from(Object.keys(command._aliases).slice(1)).join(","),
535
+ Array.from(
536
+ Object.keys(command._aliases).slice(1),
537
+ ).join(","),
418
538
  ]),
419
539
  );
420
540
  }
421
541
 
422
- session.app.emit(session, "help/command", output, command, session);
542
+ session.app.emit(
543
+ session,
544
+ "help/command",
545
+ output,
546
+ command,
547
+ session,
548
+ );
423
549
 
424
550
  if (command._usage) {
425
551
  output.push(
@@ -441,7 +567,9 @@ async function showHelp(
441
567
  if (command._examples.length) {
442
568
  output.push(
443
569
  session.text(".command-examples"),
444
- ...command._examples.map((example) => ` ${example}`),
570
+ ...command._examples.map(
571
+ (example) => ` ${example}`,
572
+ ),
445
573
  );
446
574
  } else {
447
575
  const text = session.text(