@zhin.js/command 1.1.9 → 1.1.11
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 +21 -1
- package/lib/arguments.d.ts +8 -2
- package/lib/arguments.js +40 -27
- package/lib/command-index.d.ts +24 -5
- package/lib/command-index.js +87 -43
- package/lib/command-menu.d.ts +7 -6
- package/lib/command-menu.js +7 -5
- package/lib/definition.d.ts +25 -2
- package/lib/definition.js +34 -1
- package/lib/index.d.ts +1 -1
- package/lib/messages.d.ts +8 -0
- package/lib/messages.js +57 -0
- package/lib/permission-explanation.d.ts +2 -0
- package/lib/permission-explanation.js +15 -12
- package/lib/provider.js +1 -1
- package/package.json +4 -4
- package/src/arguments.ts +54 -26
- package/src/command-index.ts +114 -46
- package/src/command-menu.ts +17 -13
- package/src/definition.ts +48 -1
- package/src/index.ts +1 -1
- package/src/messages.ts +62 -0
- package/src/permission-explanation.ts +16 -13
- package/src/provider.ts +1 -1
package/README.md
CHANGED
|
@@ -42,6 +42,8 @@ commands/search/[...kw]/index.ts -> search <...kw> (params: { kw: { type:
|
|
|
42
42
|
- `config` / `use()` / `owner` / `generation`:owner-scoped Runtime 上下文。
|
|
43
43
|
- `signal`:真实 IM 入站操作的运行代退休时同步取消。补问、确认及其他需要随代结束的等待应观察此信号;它不会强制取消已经开始的发送,也不禁止持有旧 lease 的普通命令继续排空。没有携带运行代生命周期的输入(如普通 Host 调用)不会凭空获得 IM 生命周期。
|
|
44
44
|
- `input`:派发来源;IM 中为满足 `CommandMessage` 的 Runtime `Message`。
|
|
45
|
+
- `locale`:本次操作已经解析的 `zh-CN` 或 `en`,群聊也按发送者偏好;无个人偏好时沿用项目默认语言。
|
|
46
|
+
- `formatCommand(localPath, owner?)`:从当前索引取得静态命令调用头,默认本插件,也可显式传入已知的插件 owner,自动拼上实际 Endpoint 前缀和显式 `commandNamespace`。例如 `formatCommand('remind list')`;参数在返回的调用头后显式附加。命令不存在、被禁用或当前没有已知聊天命令环境时返回 `undefined`,不要以硬编码 `/` 或命令名掩盖该结果。
|
|
45
47
|
- `adapter` / `endpoint`:适配器实例 id 与 endpoint 名。
|
|
46
48
|
- `scene`:`{ id, type, name? }` 场景对象。
|
|
47
49
|
- `sender`:`{ id, name?, role: string[] }` 发送者对象。
|
|
@@ -78,7 +80,9 @@ PermissionHost 或缺少身份依据均不能被展示为“已授权”或“
|
|
|
78
80
|
|
|
79
81
|
## 集中声明参数、选项与帮助
|
|
80
82
|
|
|
81
|
-
|
|
83
|
+
**列出指令**:中文操作使用 `菜单`,英文操作使用 `menu`,每次只接受该操作语言的默认关键词。列出根插件的指令和直接子插件;`菜单 <插件实例键>` 查看子插件,嵌套实例用 `菜单 foo.bar`(英文对应 `menu foo.bar`)。菜单逐层浏览,不一次展开全树。账号设有命令前缀时同样要加前缀,如 `/菜单`。项目根配置的顶层 `menuKeyword` 可显式修改关键词,不随语言翻译;`false` 或空字符串关闭。详见[指令菜单](../../../docs/authoring/commands.md#查看已加载的指令菜单)。
|
|
84
|
+
|
|
85
|
+
命令、参数和选项保留 `description` 原文,可额外声明 `descriptionI18n: { en: 'English description' }`。菜单、`--help` 与 `CommandIndex.list(locale)` 使用对应说明,缺失时保留作者原文,不自动翻译机器名称、用户内容或参数值。参数错误带稳定的 `code` 与 `parameters`,`message` 是当前语言的展示;自定义 `validate` 返回的文本仍由作者负责。
|
|
82
86
|
|
|
83
87
|
安装 `@zhin.js/html-renderer` 后,菜单发出带原始文本备用内容的 HTML 段,复用统一发送链的图片渲染和平台策略。渲染失败或平台无法消费图片时保留原文本;无渲染器时直接返回文本。Command Feature 不调用渲染器,也不新增 Core 或样式组件依赖。
|
|
84
88
|
|
|
@@ -170,3 +174,19 @@ pnpm --filter @zhin.js/command test
|
|
|
170
174
|
|
|
171
175
|
完整用户契约见[命令创作指南](../../../docs/authoring/commands.md),架构迁移背景见
|
|
172
176
|
[Plugin Runtime 原位迁移](../../../docs/architecture/target-implementation/in-place-migration.md)。
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
命令可声明 `descriptionI18n: { en: 'Create a reminder' }` 翻译描述。若静态目录名是中文,可沿用 `alias` 提供英文入口,并用 `preferredAliasI18n` 显式选择英文展示:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
export default defineCommand({
|
|
183
|
+
alias: ['reminder subscribe'],
|
|
184
|
+
preferredAliasI18n: { en: 'reminder subscribe' },
|
|
185
|
+
execute({ locale, formatCommand }) {
|
|
186
|
+
// 参数仍由调用方明确附加;不存在或没有真实聊天环境时返回 undefined。
|
|
187
|
+
return formatCommand('提醒 订阅');
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
`preferredAliasI18n` 必须引用该命令已声明的别名;它只选择菜单、帮助与指令引用的展示入口,不创建新路由或停用既有别名。实际插件挂载命名空间与当前消息前缀由同一个 `CommandIndex` 拼接。
|
package/lib/arguments.d.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import type { CommandDefinition, CommandParams, CommandOptions, CommandParameterValue, CommandSegment, CommandSession } from './definition.js';
|
|
2
|
+
import type { Locale } from '@zhin.js/plugin-runtime';
|
|
3
|
+
export type CommandValidationIssueCode = 'expectedType' | 'expectedArray' | 'minimum' | 'maximum' | 'choices' | 'invalidFormat' | 'invalidValue' | 'unknownOption' | 'duplicateOption' | 'defaultUndefined' | 'parameterUndefined' | 'missingOption' | 'unexpectedArguments' | 'invalidParameter' | 'custom';
|
|
2
4
|
export interface CommandValidationIssue {
|
|
3
5
|
readonly field: string;
|
|
4
6
|
readonly message: string;
|
|
7
|
+
readonly code?: CommandValidationIssueCode;
|
|
8
|
+
readonly parameters?: Readonly<Record<string, string | number>>;
|
|
5
9
|
}
|
|
10
|
+
/** Stable validation reason and values; never reverse-parse an English message. */
|
|
11
|
+
export declare function commandValidationIssue(code: Exclude<CommandValidationIssueCode, 'custom'>, field: string, locale: Locale, parameters?: Readonly<Record<string, string | number>>): CommandValidationIssue;
|
|
6
12
|
export interface ParsedCommandArguments {
|
|
7
13
|
readonly params: Readonly<Record<string, CommandParameterValue>>;
|
|
8
14
|
readonly options: Readonly<Record<string, CommandParameterValue>>;
|
|
@@ -14,6 +20,6 @@ export declare function validateArgumentSchemas(params?: CommandParams, options?
|
|
|
14
20
|
/** Reserved flags use the same quoted-token and option-terminator rules as arguments. */
|
|
15
21
|
export declare function requestsCommandHelp(segments: readonly Readonly<CommandSegment>[]): boolean;
|
|
16
22
|
/** Parse arguments after a route match; the CommandIndex remains the only dispatcher. */
|
|
17
|
-
export declare function parseCommandArguments(definition: CommandDefinition, remaining: readonly Readonly<CommandSegment>[], initial: Readonly<Record<string, CommandParameterValue>>, session: CommandSession): ParsedCommandArguments;
|
|
23
|
+
export declare function parseCommandArguments(definition: CommandDefinition, remaining: readonly Readonly<CommandSegment>[], initial: Readonly<Record<string, CommandParameterValue>>, session: CommandSession, locale?: Locale): ParsedCommandArguments;
|
|
18
24
|
export declare function commandUsage(name: string, definition: CommandDefinition): string;
|
|
19
|
-
export declare function commandHelp(name: string, definition: CommandDefinition, prefix?: string): string;
|
|
25
|
+
export declare function commandHelp(name: string, definition: CommandDefinition, prefix?: string, locale?: Locale): string;
|
package/lib/arguments.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import { commandDescription, commandMessages } from './messages.js';
|
|
2
|
+
/** Stable validation reason and values; never reverse-parse an English message. */
|
|
3
|
+
export function commandValidationIssue(code, field, locale, parameters = {}) {
|
|
4
|
+
const values = Object.freeze({ field, ...parameters });
|
|
5
|
+
return Object.freeze({ field, code, parameters: values, message: commandMessages(locale, code, values) });
|
|
6
|
+
}
|
|
1
7
|
const scalarTypes = new Set(['string', 'word', 'text', 'number', 'integer', 'float', 'boolean']);
|
|
2
8
|
/** Validate the single declaration used by parsing, help and execution. */
|
|
3
9
|
export function validateArgumentSchemas(params, options) {
|
|
@@ -51,6 +57,11 @@ function validateSchema(name, schema) {
|
|
|
51
57
|
if (schema.choices !== undefined && !Array.isArray(schema.choices)) {
|
|
52
58
|
throw new TypeError(`Command argument ${name}.choices must be an array`);
|
|
53
59
|
}
|
|
60
|
+
if (schema.descriptionI18n !== undefined && (!schema.descriptionI18n || typeof schema.descriptionI18n !== 'object'
|
|
61
|
+
|| Array.isArray(schema.descriptionI18n)
|
|
62
|
+
|| Object.entries(schema.descriptionI18n).some(([locale, text]) => !['zh-CN', 'en'].includes(locale) || typeof text !== 'string'))) {
|
|
63
|
+
throw new TypeError(`Command argument ${name}.descriptionI18n must map zh-CN/en to strings`);
|
|
64
|
+
}
|
|
54
65
|
if (schema.default !== undefined && !scalarTypes.has(schema.type))
|
|
55
66
|
throw new TypeError(`Structured Command argument ${name} cannot declare a default`);
|
|
56
67
|
if (schema.default !== undefined && typeof schema.default !== 'function') {
|
|
@@ -158,31 +169,32 @@ function valueMatches(schema, value) {
|
|
|
158
169
|
return typeof value === 'string' || !!value && typeof value === 'object';
|
|
159
170
|
return typeof value === 'string';
|
|
160
171
|
}
|
|
161
|
-
function validateValue(field, schema, value, session) {
|
|
172
|
+
function validateValue(field, schema, value, session, locale = 'zh-CN') {
|
|
162
173
|
if (schema.rest && !Array.isArray(value))
|
|
163
|
-
return
|
|
174
|
+
return commandValidationIssue('expectedArray', field, locale, { type: schema.type });
|
|
164
175
|
const values = schema.rest ? (Array.isArray(value) ? value : [value]) : [value];
|
|
165
176
|
for (const item of values) {
|
|
166
177
|
if (!valueMatches(schema, item))
|
|
167
|
-
return
|
|
178
|
+
return commandValidationIssue('expectedType', field, locale, { type: schema.type });
|
|
168
179
|
const size = typeof item === 'number' ? item : typeof item === 'string' ? item.length : undefined;
|
|
169
180
|
if (schema.min !== undefined && (size === undefined || size < schema.min))
|
|
170
|
-
return
|
|
181
|
+
return commandValidationIssue('minimum', field, locale, { minimum: schema.min });
|
|
171
182
|
if (schema.max !== undefined && (size === undefined || size > schema.max))
|
|
172
|
-
return
|
|
183
|
+
return commandValidationIssue('maximum', field, locale, { maximum: schema.max });
|
|
173
184
|
if (schema.choices && !schema.choices.includes(item))
|
|
174
|
-
return
|
|
185
|
+
return commandValidationIssue('choices', field, locale, { choices: schema.choices.join(', ') });
|
|
175
186
|
if (schema.pattern) {
|
|
176
187
|
const pattern = new RegExp(schema.pattern.source, schema.pattern.flags.replace(/[gy]/gu, ''));
|
|
177
188
|
if (typeof item !== 'string' || !pattern.test(item))
|
|
178
|
-
return
|
|
189
|
+
return commandValidationIssue('invalidFormat', field, locale);
|
|
179
190
|
}
|
|
180
191
|
}
|
|
181
192
|
const result = schema.validate?.(value, session);
|
|
182
|
-
return
|
|
193
|
+
return typeof result === 'string' ? Object.freeze({ field, code: 'custom', message: result })
|
|
194
|
+
: result === false ? commandValidationIssue('invalidValue', field, locale) : undefined;
|
|
183
195
|
}
|
|
184
196
|
/** Parse arguments after a route match; the CommandIndex remains the only dispatcher. */
|
|
185
|
-
export function parseCommandArguments(definition, remaining, initial, session) {
|
|
197
|
+
export function parseCommandArguments(definition, remaining, initial, session, locale = 'zh-CN') {
|
|
186
198
|
const params = { __proto__: null, ...initial };
|
|
187
199
|
const options = { __proto__: null };
|
|
188
200
|
const issues = [];
|
|
@@ -211,14 +223,14 @@ export function parseCommandArguments(definition, remaining, initial, session) {
|
|
|
211
223
|
const flag = rawFlag ?? token.value;
|
|
212
224
|
const name = optionNames.get(flag);
|
|
213
225
|
if (!name) {
|
|
214
|
-
issues.push(
|
|
226
|
+
issues.push(commandValidationIssue('unknownOption', flag, locale));
|
|
215
227
|
continue;
|
|
216
228
|
}
|
|
217
229
|
const schema = schemas[name];
|
|
218
230
|
if (!schema)
|
|
219
231
|
continue;
|
|
220
232
|
if (Object.hasOwn(options, name)) {
|
|
221
|
-
issues.push(
|
|
233
|
+
issues.push(commandValidationIssue('duplicateOption', name, locale));
|
|
222
234
|
continue;
|
|
223
235
|
}
|
|
224
236
|
let value;
|
|
@@ -234,7 +246,7 @@ export function parseCommandArguments(definition, remaining, initial, session) {
|
|
|
234
246
|
}
|
|
235
247
|
}
|
|
236
248
|
if (value === undefined)
|
|
237
|
-
issues.push(
|
|
249
|
+
issues.push(commandValidationIssue('expectedType', name, locale, { field: `--${name}`, type: schema.type }));
|
|
238
250
|
else
|
|
239
251
|
options[name] = value;
|
|
240
252
|
}
|
|
@@ -242,14 +254,14 @@ export function parseCommandArguments(definition, remaining, initial, session) {
|
|
|
242
254
|
if (!Object.hasOwn(options, name) && schema.default !== undefined)
|
|
243
255
|
options[name] = typeof schema.default === 'function' ? schema.default(session) : schema.default;
|
|
244
256
|
if (Object.hasOwn(options, name) && options[name] === undefined)
|
|
245
|
-
issues.push(
|
|
257
|
+
issues.push(commandValidationIssue('defaultUndefined', name, locale, { field: `--${name}` }));
|
|
246
258
|
else if (options[name] !== undefined) {
|
|
247
|
-
const issue = validateValue(`--${name}`, schema, options[name], session);
|
|
259
|
+
const issue = validateValue(`--${name}`, schema, options[name], session, locale);
|
|
248
260
|
if (issue)
|
|
249
261
|
issues.push(issue);
|
|
250
262
|
}
|
|
251
263
|
else if (schema.optional === false)
|
|
252
|
-
issues.push(
|
|
264
|
+
issues.push(commandValidationIssue('missingOption', name, locale));
|
|
253
265
|
}
|
|
254
266
|
const declared = Object.entries(definition.params ?? {});
|
|
255
267
|
for (const [name, declaredSchema] of declared) {
|
|
@@ -262,7 +274,7 @@ export function parseCommandArguments(definition, remaining, initial, session) {
|
|
|
262
274
|
? segmentsFromTokens(raw).map((segment) => segment.type === 'text' && typeof segment.data.text === 'string' ? segment.data.text : undefined)
|
|
263
275
|
: raw.map((token) => convert(schema, token));
|
|
264
276
|
if (converted.some((item) => item === undefined))
|
|
265
|
-
issues.push(
|
|
277
|
+
issues.push(commandValidationIssue('expectedType', name, locale, { type: schema.type }));
|
|
266
278
|
else if (converted.length > 0 || schema.optional)
|
|
267
279
|
value = converted;
|
|
268
280
|
}
|
|
@@ -279,7 +291,7 @@ export function parseCommandArguments(definition, remaining, initial, session) {
|
|
|
279
291
|
value = convert(schema, positional.shift());
|
|
280
292
|
// Defaults apply to omitted input, never to a supplied value of the wrong type.
|
|
281
293
|
if (value === undefined) {
|
|
282
|
-
issues.push(
|
|
294
|
+
issues.push(commandValidationIssue('expectedType', name, locale, { type: schema.type }));
|
|
283
295
|
continue;
|
|
284
296
|
}
|
|
285
297
|
}
|
|
@@ -288,20 +300,20 @@ export function parseCommandArguments(definition, remaining, initial, session) {
|
|
|
288
300
|
else if (schema.default !== undefined)
|
|
289
301
|
params[name] = typeof schema.default === 'function' ? schema.default(session) : schema.default;
|
|
290
302
|
else if (!schema.optional && !(definition.$parameter?.name === name && definition.$parameter.optional)) {
|
|
291
|
-
issues.push(
|
|
303
|
+
issues.push(commandValidationIssue('expectedType', name, locale, { type: schema.type }));
|
|
292
304
|
}
|
|
293
305
|
}
|
|
294
306
|
if (Object.hasOwn(params, name) && params[name] === undefined)
|
|
295
|
-
issues.push(
|
|
307
|
+
issues.push(commandValidationIssue('parameterUndefined', name, locale));
|
|
296
308
|
else if (params[name] !== undefined) {
|
|
297
309
|
const effective = definition.$parameter?.name === name && definition.$parameter.rest ? { ...schema, rest: true } : schema;
|
|
298
|
-
const issue = validateValue(name, effective, params[name], session);
|
|
310
|
+
const issue = validateValue(name, effective, params[name], session, locale);
|
|
299
311
|
if (issue)
|
|
300
312
|
issues.push(issue);
|
|
301
313
|
}
|
|
302
314
|
}
|
|
303
315
|
if (declared.length > 0 && !definition.$parameter && positional.length > 0)
|
|
304
|
-
issues.push(
|
|
316
|
+
issues.push(commandValidationIssue('unexpectedArguments', 'arguments', locale));
|
|
305
317
|
return Object.freeze({ params: Object.freeze(params), options: Object.freeze(options), remaining: schemas === undefined && declared.every(([name]) => Object.hasOwn(initial, name)) ? remaining : segmentsFromTokens(positional), issues: Object.freeze(issues) });
|
|
306
318
|
}
|
|
307
319
|
export function commandUsage(name, definition) {
|
|
@@ -310,12 +322,13 @@ export function commandUsage(name, definition) {
|
|
|
310
322
|
const options = Object.entries(definition.options ?? {}).map(([key, schema]) => `[--${key}${schema.type === 'boolean' ? '' : ` <${schema.type}>`}]`);
|
|
311
323
|
return [name, ...parameters, ...options].join(' ');
|
|
312
324
|
}
|
|
313
|
-
export function commandHelp(name, definition, prefix = '') {
|
|
314
|
-
return [prefix + commandUsage(name, definition), definition
|
|
315
|
-
...Object.entries(definition.params ?? {}).map(([key, schema]) => argumentHelp(key, schema)),
|
|
316
|
-
...Object.entries(definition.options ?? {}).map(([key, schema]) => argumentHelp(`--${key}${schema.alias ? ` (-${schema.alias})` : ''}`, schema)),
|
|
325
|
+
export function commandHelp(name, definition, prefix = '', locale = 'zh-CN') {
|
|
326
|
+
return [prefix + commandUsage(name, definition), commandDescription(definition, locale),
|
|
327
|
+
...Object.entries(definition.params ?? {}).map(([key, schema]) => argumentHelp(key, schema, locale)),
|
|
328
|
+
...Object.entries(definition.options ?? {}).map(([key, schema]) => argumentHelp(`--${key}${schema.alias ? ` (-${schema.alias})` : ''}`, schema, locale)),
|
|
317
329
|
].filter(Boolean).join('\n');
|
|
318
330
|
}
|
|
319
|
-
function argumentHelp(name, schema) {
|
|
320
|
-
|
|
331
|
+
function argumentHelp(name, schema, locale) {
|
|
332
|
+
const description = commandDescription(schema, locale);
|
|
333
|
+
return ` ${name}: ${schema.type}${description ? ` — ${description}` : ''}${schema.default !== undefined ? ` (${commandMessages(locale, 'defaultValue', { value: typeof schema.default === 'function' ? commandMessages(locale, 'dynamicValue') : JSON.stringify(schema.default) })})` : ''}${schema.choices ? ` (${schema.choices.join(', ')})` : ''}${schema.min !== undefined ? ` min=${schema.min}` : ''}${schema.max !== undefined ? ` max=${schema.max}` : ''}`;
|
|
321
334
|
}
|
package/lib/command-index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { type CapabilityId, type CapabilitySlot, type PluginId, type RuntimeSnapshot } from '@zhin.js/plugin-runtime';
|
|
1
|
+
import { type CapabilityId, type CapabilitySlot, type PluginId, type RuntimeSnapshot, type Locale } from '@zhin.js/plugin-runtime';
|
|
2
2
|
import type { UserInteractionFactory } from '@zhin.js/interaction';
|
|
3
3
|
import { type CommandValidationIssue } from './arguments.js';
|
|
4
|
+
import { type CommandMenuEntry, type CommandMenuAppearance } from './command-menu.js';
|
|
5
|
+
export type { CommandMenuEntry, CommandMenuAppearance } from './command-menu.js';
|
|
4
6
|
import { type CommandExecutionLimit } from './execution-policy.js';
|
|
5
7
|
export { CommandExecutionLimitedError } from './execution-policy.js';
|
|
6
8
|
import { type CommandDefinition, type CommandParameterDefinition, type CommandParameterType, type CommandSegment } from './definition.js';
|
|
@@ -32,7 +34,7 @@ export interface CommandDispatchResult {
|
|
|
32
34
|
readonly command?: string;
|
|
33
35
|
readonly owner?: PluginId;
|
|
34
36
|
readonly value?: unknown;
|
|
35
|
-
readonly menu?:
|
|
37
|
+
readonly menu?: CommandMenuPage;
|
|
36
38
|
}
|
|
37
39
|
/** Semantic menu navigation; the IM composition layer supplies platform controls. */
|
|
38
40
|
export interface CommandMenuNavigation {
|
|
@@ -41,19 +43,36 @@ export interface CommandMenuNavigation {
|
|
|
41
43
|
readonly page: number;
|
|
42
44
|
readonly pages: number;
|
|
43
45
|
}
|
|
46
|
+
/** Immutable page data; presentation cannot change the catalog or execute commands. */
|
|
47
|
+
export interface CommandMenuPage extends CommandMenuNavigation {
|
|
48
|
+
readonly locale: Locale;
|
|
49
|
+
readonly title: string;
|
|
50
|
+
readonly commands: readonly CommandMenuEntry[];
|
|
51
|
+
readonly plugins: readonly CommandMenuEntry[];
|
|
52
|
+
readonly commandTotal: number;
|
|
53
|
+
readonly pluginTotal: number;
|
|
54
|
+
readonly hint?: string;
|
|
55
|
+
readonly text: string;
|
|
56
|
+
readonly appearance: CommandMenuAppearance;
|
|
57
|
+
}
|
|
44
58
|
export type CommandMatchInput = string | readonly Readonly<CommandSegment>[];
|
|
45
59
|
export interface CommandMenuConfig {
|
|
46
|
-
|
|
60
|
+
/** Omission selects the built-in keyword for each operation's language. */
|
|
61
|
+
readonly keyword?: string;
|
|
47
62
|
}
|
|
48
63
|
export declare class CommandIndex {
|
|
49
64
|
#private;
|
|
50
65
|
private readonly snapshot;
|
|
51
66
|
readonly $projection: "zhin.command-index/1";
|
|
52
67
|
constructor(slots: readonly Readonly<CapabilitySlot<CommandDefinition>>[], snapshot: RuntimeSnapshot, menu?: CommandMenuConfig);
|
|
53
|
-
list(): readonly CommandDescriptor[];
|
|
68
|
+
list(locale?: Locale): readonly CommandDescriptor[];
|
|
69
|
+
/** Resolve a local static route without executing parameters, permission checks or commands. */
|
|
70
|
+
formatCommand(owner: PluginId, localPath: string, prefix: string | undefined, locale?: Locale): string | undefined;
|
|
71
|
+
/** Return the configured menu entry only when the calling environment is known. */
|
|
72
|
+
formatMenu(prefix: string | undefined, locale: Locale): string | undefined;
|
|
54
73
|
has(name: string): boolean;
|
|
55
74
|
execute(name: string, args?: readonly string[], operationSnapshot?: RuntimeSnapshot): Promise<unknown>;
|
|
56
|
-
dispatch(input: CommandMatchInput, source?: unknown, interactionFactory?: UserInteractionFactory, commandPrefix?: string, operationSnapshot?: RuntimeSnapshot): Promise<CommandDispatchResult>;
|
|
75
|
+
dispatch(input: CommandMatchInput, source?: unknown, interactionFactory?: UserInteractionFactory, commandPrefix?: string, operationSnapshot?: RuntimeSnapshot, localeOverride?: Locale): Promise<CommandDispatchResult>;
|
|
57
76
|
}
|
|
58
77
|
export declare function isCommandIndex(value: unknown): value is CommandIndex;
|
|
59
78
|
/**
|
package/lib/command-index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { SegmentMatcher, TypeMatcherRegistry, } from 'segment-matcher';
|
|
2
|
-
import { htmlRendererToken, } from '@zhin.js/plugin-runtime';
|
|
2
|
+
import { htmlRendererToken, resolveLocale, } from '@zhin.js/plugin-runtime';
|
|
3
3
|
import { bindOperationInput } from '@zhin.js/feature-kit';
|
|
4
4
|
import { permissionHostToken, } from '@zhin.js/permission';
|
|
5
5
|
import { commandAdapterMatches, toCommandPermissionSubject } from './permission-explanation.js';
|
|
6
|
-
import { commandUsage, commandHelp, parseCommandArguments, requestsCommandHelp } from './arguments.js';
|
|
6
|
+
import { commandUsage, commandHelp, parseCommandArguments, requestsCommandHelp, commandValidationIssue } from './arguments.js';
|
|
7
|
+
import { commandDescription, commandMessages } from './messages.js';
|
|
7
8
|
import { MENU_PAGE_SIZE, readMenuAppearance, renderCommandMenu } from './command-menu.js';
|
|
8
9
|
import { admitCommandExecution, admitDirectCommandExecution } from './execution-runtime.js';
|
|
9
10
|
import { CommandExecutionLimitedError, normalizeExecutionPolicy } from './execution-policy.js';
|
|
@@ -90,6 +91,7 @@ export class CommandIndex {
|
|
|
90
91
|
...(shortcutKeys && shortcutKeys.length > 0 ? { shortcut: shortcutKeys } : {}),
|
|
91
92
|
slot,
|
|
92
93
|
segments: Object.freeze(primarySegments),
|
|
94
|
+
preferredSegments: Object.freeze(Object.fromEntries(Object.entries(slot.definition.preferredAliasI18n ?? {}).map(([locale, entry]) => [locale, Object.freeze([...namespace, ...aliasRuntimeSegments(entry, primarySegments.slice(namespace.length))])]))),
|
|
93
95
|
parameter,
|
|
94
96
|
});
|
|
95
97
|
claim([...new Set([occupancyKey(primarySegments, parameter), ...declaredRouteShapes(primarySegments, slot.definition)])], slot.source);
|
|
@@ -130,8 +132,30 @@ export class CommandIndex {
|
|
|
130
132
|
this.#routes = Object.freeze(routes.sort(compareRoutes));
|
|
131
133
|
this.#shortcuts = shortcuts;
|
|
132
134
|
}
|
|
133
|
-
list() {
|
|
134
|
-
return this.#commands.map(toDescriptor)
|
|
135
|
+
list(locale = 'zh-CN') {
|
|
136
|
+
return this.#commands.map(record => ({ ...toDescriptor(record),
|
|
137
|
+
name: commandDisplayName(record, locale), usage: commandUsage(commandDisplayName(record, locale), record.slot.definition),
|
|
138
|
+
description: commandDescription(record.slot.definition, locale),
|
|
139
|
+
parameters: record.parameters.map(parameter => ({ ...parameter, description: commandDescription(parameter, locale) })),
|
|
140
|
+
options: Object.freeze(Object.fromEntries(Object.entries(record.options).map(([key, schema]) => [key, { ...schema, description: commandDescription(schema, locale) }]))),
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
143
|
+
/** Resolve a local static route without executing parameters, permission checks or commands. */
|
|
144
|
+
formatCommand(owner, localPath, prefix, locale = 'zh-CN') {
|
|
145
|
+
if (prefix === undefined || typeof localPath !== 'string')
|
|
146
|
+
return undefined;
|
|
147
|
+
const path = localPath.trim().split(/\s+/u).filter(Boolean).join(' ');
|
|
148
|
+
if (!path)
|
|
149
|
+
return undefined;
|
|
150
|
+
const command = this.#commands.find(record => record.owner === owner
|
|
151
|
+
&& record.slot.localName.split('/').filter(segment => !segment.startsWith('$')).join(' ') === path);
|
|
152
|
+
return command ? prefix + commandDisplaySegments(command, locale).filter(segment => !segment.startsWith('$')).join(' ') : undefined;
|
|
153
|
+
}
|
|
154
|
+
/** Return the configured menu entry only when the calling environment is known. */
|
|
155
|
+
formatMenu(prefix, locale) {
|
|
156
|
+
if (prefix === undefined || !this.#menu)
|
|
157
|
+
return undefined;
|
|
158
|
+
return prefix + (this.#menu.keyword ?? commandMessages(locale, 'menuKeyword'));
|
|
135
159
|
}
|
|
136
160
|
has(name) {
|
|
137
161
|
try {
|
|
@@ -144,6 +168,7 @@ export class CommandIndex {
|
|
|
144
168
|
}
|
|
145
169
|
}
|
|
146
170
|
async execute(name, args = [], operationSnapshot = this.snapshot) {
|
|
171
|
+
const locale = commandLocale(undefined, operationSnapshot);
|
|
147
172
|
const match = this.#match(name, false);
|
|
148
173
|
if (!match) {
|
|
149
174
|
this.#diagnoseParameter(name);
|
|
@@ -153,28 +178,31 @@ export class CommandIndex {
|
|
|
153
178
|
throw new Error(`Permission denied for Command: ${match.command.name}`);
|
|
154
179
|
const definition = match.command.slot.definition;
|
|
155
180
|
const supplied = [...match.remaining, ...(args.length > 0 ? [{ type: 'text', data: { text: args.join(' ') } }] : [])];
|
|
156
|
-
const parsed = parseCommandArguments(definition, supplied, resolveDynamicParams(match.params, undefined), {});
|
|
181
|
+
const parsed = parseCommandArguments(definition, supplied, resolveDynamicParams(match.params, undefined), {}, locale);
|
|
157
182
|
if (parsed.issues.length > 0)
|
|
158
|
-
throw new CommandArgumentValidationError(parsed.issues, commandHelp(match.command
|
|
183
|
+
throw new CommandArgumentValidationError(parsed.issues, commandHelp(commandDisplayName(match.command, locale), definition, '', locale));
|
|
159
184
|
// No inbound source: execution policy applies per command, never as a blanket refusal.
|
|
160
185
|
const admission = admitDirectCommandExecution(match.command.slot.id, match.command.execution, operationSnapshot);
|
|
161
186
|
if (admission.limit)
|
|
162
187
|
throw new CommandExecutionLimitedError(admission.limit);
|
|
163
188
|
try {
|
|
164
|
-
return await definition.execute(createCommandContext(operationSnapshot, match.command.slot.owner, textArgs(parsed.remaining), parsed.params, undefined, parsed.remaining, undefined, definition.adapter, parsed.options));
|
|
189
|
+
return await definition.execute(createCommandContext(operationSnapshot, match.command.slot.owner, textArgs(parsed.remaining), parsed.params, undefined, parsed.remaining, undefined, definition.adapter, parsed.options, { locale }));
|
|
165
190
|
}
|
|
166
191
|
finally {
|
|
167
192
|
admission.release();
|
|
168
193
|
}
|
|
169
194
|
}
|
|
170
|
-
async dispatch(input, source = undefined, interactionFactory, commandPrefix
|
|
195
|
+
async dispatch(input, source = undefined, interactionFactory, commandPrefix, operationSnapshot = this.snapshot, localeOverride) {
|
|
196
|
+
const locale = localeOverride ?? commandLocale(source, operationSnapshot);
|
|
197
|
+
const prefix = commandPrefix ?? '';
|
|
171
198
|
if (this.#menu) {
|
|
172
|
-
const
|
|
199
|
+
const keyword = this.#menu.keyword ?? commandMessages(locale, 'menuKeyword');
|
|
200
|
+
const menuResult = this.#dispatchMenu(input, prefix, operationSnapshot, locale, keyword);
|
|
173
201
|
if (menuResult !== undefined) {
|
|
174
202
|
return Object.freeze({
|
|
175
203
|
matched: true,
|
|
176
204
|
status: 'executed',
|
|
177
|
-
command:
|
|
205
|
+
command: keyword,
|
|
178
206
|
owner: operationSnapshot.root,
|
|
179
207
|
...menuResult,
|
|
180
208
|
});
|
|
@@ -192,12 +220,12 @@ export class CommandIndex {
|
|
|
192
220
|
return Object.freeze({ matched: false, status: 'unmatched' });
|
|
193
221
|
if (!(await this.#permitAllows(candidate, source, operationSnapshot)))
|
|
194
222
|
return this.#denied(candidate);
|
|
195
|
-
const help = commandHelp(candidate
|
|
223
|
+
const help = commandHelp(commandDisplayName(candidate, locale), candidate.slot.definition, prefix, locale);
|
|
196
224
|
const segments = normalizeSegments(typeof input === 'string' ? [{ type: 'text', data: { text: input } }] : input);
|
|
197
225
|
if (requestsCommandHelp(segments))
|
|
198
226
|
return Object.freeze({ matched: true, status: 'executed', command: candidate.name, owner: candidate.slot.owner, value: help });
|
|
199
227
|
return Object.freeze({ matched: true, status: 'invalid', command: candidate.name, owner: candidate.slot.owner,
|
|
200
|
-
issues: Object.freeze([
|
|
228
|
+
issues: Object.freeze([commandValidationIssue('invalidParameter', candidate.parameter?.name ?? 'arguments', locale)]), help });
|
|
201
229
|
}
|
|
202
230
|
if (!commandAdapterMatches(match.command, source))
|
|
203
231
|
return Object.freeze({ matched: false, status: 'unmatched' });
|
|
@@ -205,18 +233,18 @@ export class CommandIndex {
|
|
|
205
233
|
return this.#denied(match.command);
|
|
206
234
|
const definition = match.command.slot.definition;
|
|
207
235
|
const authorSource = bindOperationInput(source, match.command.slot.owner, operationSnapshot);
|
|
208
|
-
const help = commandHelp(match.command
|
|
236
|
+
const help = commandHelp(commandDisplayName(match.command, locale), definition, prefix, locale);
|
|
209
237
|
if (match.helpRequested) {
|
|
210
238
|
return Object.freeze({ matched: true, status: 'executed', command: match.command.name, owner: match.command.slot.owner, value: help });
|
|
211
239
|
}
|
|
212
|
-
const parsed = parseCommandArguments(definition, match.remaining, resolveDynamicParams(match.params, authorSource), resolveCommandSession(authorSource));
|
|
240
|
+
const parsed = parseCommandArguments(definition, match.remaining, resolveDynamicParams(match.params, authorSource), resolveCommandSession(authorSource), locale);
|
|
213
241
|
if (parsed.issues.length > 0)
|
|
214
242
|
return Object.freeze({ matched: true, status: 'invalid', command: match.command.name,
|
|
215
243
|
owner: match.command.slot.owner, issues: parsed.issues, help });
|
|
216
244
|
const limit = admitCommandExecution(authorSource, match.command.slot.id, match.command.slot.owner, match.command.execution, operationSnapshot);
|
|
217
245
|
if (limit)
|
|
218
246
|
return Object.freeze({ matched: true, status: 'limited', command: match.command.name, owner: match.command.slot.owner, limit });
|
|
219
|
-
const value = await definition.execute(createCommandContext(operationSnapshot, match.command.slot.owner, textArgs(parsed.remaining), parsed.params, authorSource, parsed.remaining, interactionFactory?.(authorSource), definition.adapter, parsed.options));
|
|
247
|
+
const value = await definition.execute(createCommandContext(operationSnapshot, match.command.slot.owner, textArgs(parsed.remaining), parsed.params, authorSource, parsed.remaining, interactionFactory?.(authorSource), definition.adapter, parsed.options, { locale, formatCommand: (localPath, owner = match.command.owner) => this.formatCommand(owner, localPath, source && typeof source === 'object' && 'conversation' in source ? commandPrefix : undefined, locale) }));
|
|
220
248
|
return Object.freeze({ matched: true, status: 'executed', command: match.command.name, owner: match.command.slot.owner, value });
|
|
221
249
|
}
|
|
222
250
|
#denied(record) {
|
|
@@ -334,16 +362,12 @@ export class CommandIndex {
|
|
|
334
362
|
// ==========================================================================
|
|
335
363
|
// 内置菜单命令
|
|
336
364
|
// ==========================================================================
|
|
337
|
-
#dispatchMenu(input, commandPrefix, snapshot) {
|
|
365
|
+
#dispatchMenu(input, commandPrefix, snapshot, locale, keyword) {
|
|
338
366
|
const text = typeof input === 'string' ? input.trim() : exactMessageText(input);
|
|
339
367
|
if (text === undefined)
|
|
340
368
|
return undefined;
|
|
341
|
-
const menu = this.#menu;
|
|
342
|
-
if (!menu)
|
|
343
|
-
return undefined;
|
|
344
|
-
const keyword = menu.keyword;
|
|
345
369
|
if (text === keyword)
|
|
346
|
-
return this.#buildMenu(undefined, commandPrefix, keyword, snapshot);
|
|
370
|
+
return this.#buildMenu(undefined, commandPrefix, keyword, snapshot, locale);
|
|
347
371
|
if (text.startsWith(keyword + ' ')) {
|
|
348
372
|
const key = text.slice(keyword.length + 1).trim();
|
|
349
373
|
const args = key.split(/\s+/u);
|
|
@@ -352,24 +376,24 @@ export class CommandIndex {
|
|
|
352
376
|
const pageText = args[flag + 1] ?? '';
|
|
353
377
|
if (flag > 1 || args.length !== flag + 2 || !/^[1-9]\d*$/u.test(pageText)
|
|
354
378
|
|| !Number.isSafeInteger(Number(pageText))) {
|
|
355
|
-
return { value:
|
|
379
|
+
return { value: commandMessages(locale, 'invalidPage', { command: `${commandPrefix}${keyword}` }) };
|
|
356
380
|
}
|
|
357
|
-
return this.#buildMenu(flag === 1 ? args[0] : undefined, commandPrefix, keyword, snapshot, Number(pageText));
|
|
381
|
+
return this.#buildMenu(flag === 1 ? args[0] : undefined, commandPrefix, keyword, snapshot, locale, Number(pageText));
|
|
358
382
|
}
|
|
359
383
|
if (key)
|
|
360
|
-
return this.#buildMenu(key, commandPrefix, keyword, snapshot);
|
|
361
|
-
return this.#buildMenu(undefined, commandPrefix, keyword, snapshot);
|
|
384
|
+
return this.#buildMenu(key, commandPrefix, keyword, snapshot, locale);
|
|
385
|
+
return this.#buildMenu(undefined, commandPrefix, keyword, snapshot, locale);
|
|
362
386
|
}
|
|
363
387
|
return undefined;
|
|
364
388
|
}
|
|
365
|
-
#buildMenu(pluginKey, commandPrefix, keyword, snapshot, page = 1) {
|
|
389
|
+
#buildMenu(pluginKey, commandPrefix, keyword, snapshot, locale, page = 1) {
|
|
366
390
|
const root = snapshot.root;
|
|
367
391
|
const targetId = pluginKey
|
|
368
392
|
? `${root}/${pluginKey.split('.').join('/')}`
|
|
369
393
|
: root;
|
|
370
394
|
const node = snapshot.tree.get(targetId);
|
|
371
395
|
if (!node)
|
|
372
|
-
return { value:
|
|
396
|
+
return { value: commandMessages(locale, 'missingPlugin', { plugin: pluginKey ?? '' }) };
|
|
373
397
|
const directCommands = this.#commands.filter((cmd) => cmd.slot.owner === targetId);
|
|
374
398
|
const children = node.children
|
|
375
399
|
.map((childId) => snapshot.tree.get(childId))
|
|
@@ -377,24 +401,25 @@ export class CommandIndex {
|
|
|
377
401
|
const pages = Math.max(1, Math.ceil((directCommands.length + children.length) / MENU_PAGE_SIZE));
|
|
378
402
|
const invocation = `${commandPrefix}${keyword}${pluginKey ? ` ${pluginKey}` : ''}`;
|
|
379
403
|
if (page > pages)
|
|
380
|
-
return { value:
|
|
404
|
+
return { value: commandMessages(locale, 'pageOutOfRange', { pages, command: invocation }) };
|
|
381
405
|
const offset = (page - 1) * MENU_PAGE_SIZE;
|
|
382
406
|
const pageCommands = directCommands.slice(offset, offset + MENU_PAGE_SIZE);
|
|
383
407
|
const pageChildren = children.slice(Math.max(0, offset - directCommands.length), Math.max(0, offset + MENU_PAGE_SIZE - directCommands.length));
|
|
384
408
|
const displayKey = pluginKey ?? node.instanceKey;
|
|
385
|
-
const lines = [
|
|
409
|
+
const lines = [commandMessages(locale, 'menuTitle', { plugin: displayKey })];
|
|
386
410
|
if (pages > 1)
|
|
387
|
-
lines.push(
|
|
411
|
+
lines.push(commandMessages(locale, 'menuSummary', { page, pages, commands: directCommands.length, plugins: children.length }));
|
|
388
412
|
if (pageCommands.length > 0) {
|
|
389
413
|
lines.push('');
|
|
390
414
|
for (const cmd of pageCommands) {
|
|
391
|
-
const
|
|
392
|
-
|
|
415
|
+
const description = commandDescription(cmd.slot.definition, locale);
|
|
416
|
+
const desc = description ? ` - ${description}` : '';
|
|
417
|
+
lines.push(` ${commandPrefix}${commandDisplayName(cmd, locale)}${desc}`);
|
|
393
418
|
}
|
|
394
419
|
}
|
|
395
420
|
if (pageChildren.length > 0) {
|
|
396
421
|
lines.push('');
|
|
397
|
-
lines.push('
|
|
422
|
+
lines.push(commandMessages(locale, 'childPlugins'));
|
|
398
423
|
for (const child of pageChildren) {
|
|
399
424
|
const childKey = pluginKey ? `${pluginKey}.${child.instanceKey}` : child.instanceKey;
|
|
400
425
|
const label = child.metadata?.displayName ?? child.instanceKey;
|
|
@@ -403,32 +428,51 @@ export class CommandIndex {
|
|
|
403
428
|
}
|
|
404
429
|
if (directCommands.length === 0 && children.length === 0) {
|
|
405
430
|
lines.push('');
|
|
406
|
-
lines.push('
|
|
431
|
+
lines.push(commandMessages(locale, 'emptyMenu'));
|
|
407
432
|
}
|
|
408
433
|
const hints = [];
|
|
409
434
|
if (children.length > 0)
|
|
410
|
-
hints.push(
|
|
435
|
+
hints.push(commandMessages(locale, 'pluginHint', { command: `${commandPrefix}${keyword}` }));
|
|
411
436
|
if (page > 1)
|
|
412
|
-
hints.push(
|
|
437
|
+
hints.push(commandMessages(locale, 'previousPage', { command: `${invocation} --page ${page - 1}` }));
|
|
413
438
|
if (page < pages)
|
|
414
|
-
hints.push(
|
|
439
|
+
hints.push(commandMessages(locale, 'nextPage', { command: `${invocation} --page ${page + 1}` }));
|
|
415
440
|
const hint = hints.length ? hints.join('\n') : undefined;
|
|
416
441
|
if (hint) {
|
|
417
442
|
lines.push('');
|
|
418
443
|
lines.push(hint);
|
|
419
444
|
}
|
|
420
445
|
const text = lines.join('\n');
|
|
421
|
-
const menu = Object.freeze({ keyword, ...(pluginKey ? { pluginKey } : {}), page, pages });
|
|
422
|
-
if (!snapshot.resources.get(root)?.has(htmlRendererToken.id))
|
|
423
|
-
return { value: text, menu };
|
|
424
446
|
const config = snapshot.config.get(root);
|
|
425
|
-
|
|
447
|
+
const menu = Object.freeze({
|
|
448
|
+
keyword, ...(pluginKey ? { pluginKey } : {}), page, pages, locale, title: displayKey,
|
|
449
|
+
commands: Object.freeze(pageCommands.map(cmd => Object.freeze({
|
|
450
|
+
name: `${commandPrefix}${commandDisplayName(cmd, locale)}`, description: commandDescription(cmd.slot.definition, locale),
|
|
451
|
+
}))),
|
|
452
|
+
plugins: Object.freeze(pageChildren.map(child => Object.freeze({
|
|
426
453
|
name: pluginKey ? `${pluginKey}.${child.instanceKey}` : child.instanceKey,
|
|
427
454
|
description: child.metadata?.displayName ?? child.instanceKey,
|
|
428
|
-
})),
|
|
429
|
-
|
|
455
|
+
}))),
|
|
456
|
+
commandTotal: directCommands.length, pluginTotal: children.length, hint, text,
|
|
457
|
+
appearance: Object.freeze(readMenuAppearance(config?.menuStyle)),
|
|
458
|
+
});
|
|
459
|
+
if (!snapshot.resources.get(root)?.has(htmlRendererToken.id))
|
|
460
|
+
return { value: text, menu };
|
|
461
|
+
return { menu, value: renderCommandMenu(displayKey, menu.commands, menu.plugins, hint, text, {
|
|
462
|
+
page, pages, locale, commandTotal: menu.commandTotal, pluginTotal: menu.pluginTotal, appearance: menu.appearance,
|
|
463
|
+
}) };
|
|
430
464
|
}
|
|
431
465
|
}
|
|
466
|
+
function commandDisplaySegments(record, locale) {
|
|
467
|
+
return record.preferredSegments[locale] ?? record.segments;
|
|
468
|
+
}
|
|
469
|
+
function commandDisplayName(record, locale) {
|
|
470
|
+
return displayName(commandDisplaySegments(record, locale), record.parameter);
|
|
471
|
+
}
|
|
472
|
+
function commandLocale(source, snapshot) {
|
|
473
|
+
const locale = source && typeof source === 'object' && 'locale' in source ? source.locale : undefined;
|
|
474
|
+
return resolveLocale(locale ?? snapshot.config.get(snapshot.root)?.locale);
|
|
475
|
+
}
|
|
432
476
|
export function isCommandIndex(value) {
|
|
433
477
|
return !!value && typeof value === 'object'
|
|
434
478
|
&& value.$projection === 'zhin.command-index/1';
|
package/lib/command-menu.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import type { Locale } from '@zhin.js/plugin-runtime';
|
|
2
|
+
export interface CommandMenuEntry {
|
|
2
3
|
readonly name: string;
|
|
3
4
|
readonly description?: string;
|
|
4
5
|
}
|
|
5
|
-
interface
|
|
6
|
+
export interface CommandMenuAppearance {
|
|
6
7
|
readonly title?: string;
|
|
7
8
|
readonly subtitle?: string;
|
|
8
9
|
readonly accentColor?: string;
|
|
@@ -19,13 +20,13 @@ export interface CommandMenuSegment {
|
|
|
19
20
|
}
|
|
20
21
|
export declare const MENU_PAGE_SIZE = 30;
|
|
21
22
|
/** Configuration is text/data, never an interpolated CSS or HTML fragment. */
|
|
22
|
-
export declare function readMenuAppearance(value: unknown):
|
|
23
|
+
export declare function readMenuAppearance(value: unknown): CommandMenuAppearance;
|
|
23
24
|
/** Presentation only: the unified outbound pipeline owns image rendering and fallback. */
|
|
24
|
-
export declare function renderCommandMenu(title: string, commands: readonly
|
|
25
|
+
export declare function renderCommandMenu(title: string, commands: readonly CommandMenuEntry[], children: readonly CommandMenuEntry[], hint: string | undefined, text: string, options?: {
|
|
25
26
|
readonly page?: number;
|
|
26
27
|
readonly pages?: number;
|
|
28
|
+
readonly locale?: Locale;
|
|
27
29
|
readonly commandTotal?: number;
|
|
28
30
|
readonly pluginTotal?: number;
|
|
29
|
-
readonly appearance?:
|
|
31
|
+
readonly appearance?: CommandMenuAppearance;
|
|
30
32
|
}): CommandMenuSegment;
|
|
31
|
-
export {};
|