@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/src/command-index.ts
CHANGED
|
@@ -5,10 +5,12 @@ import {
|
|
|
5
5
|
} from 'segment-matcher';
|
|
6
6
|
import {
|
|
7
7
|
htmlRendererToken,
|
|
8
|
+
resolveLocale,
|
|
8
9
|
type CapabilityId,
|
|
9
10
|
type CapabilitySlot,
|
|
10
11
|
type PluginId,
|
|
11
12
|
type RuntimeSnapshot,
|
|
13
|
+
type Locale,
|
|
12
14
|
} from '@zhin.js/plugin-runtime';
|
|
13
15
|
import type { UserInteractionFactory } from '@zhin.js/interaction';
|
|
14
16
|
import { bindOperationInput } from '@zhin.js/feature-kit';
|
|
@@ -17,8 +19,10 @@ import {
|
|
|
17
19
|
type PermissionHost,
|
|
18
20
|
} from '@zhin.js/permission';
|
|
19
21
|
import { commandAdapterMatches, toCommandPermissionSubject } from './permission-explanation.js';
|
|
20
|
-
import { commandUsage, commandHelp, parseCommandArguments, requestsCommandHelp, type CommandValidationIssue } from './arguments.js';
|
|
21
|
-
import {
|
|
22
|
+
import { commandUsage, commandHelp, parseCommandArguments, requestsCommandHelp, commandValidationIssue, type CommandValidationIssue } from './arguments.js';
|
|
23
|
+
import { commandDescription, commandMessages } from './messages.js';
|
|
24
|
+
import { MENU_PAGE_SIZE, readMenuAppearance, renderCommandMenu, type CommandMenuSegment, type CommandMenuEntry, type CommandMenuAppearance } from './command-menu.js';
|
|
25
|
+
export type { CommandMenuEntry, CommandMenuAppearance } from './command-menu.js';
|
|
22
26
|
import { admitCommandExecution, admitDirectCommandExecution } from './execution-runtime.js';
|
|
23
27
|
import { CommandExecutionLimitedError, normalizeExecutionPolicy, type CommandExecutionLimit } from './execution-policy.js';
|
|
24
28
|
export { CommandExecutionLimitedError } from './execution-policy.js';
|
|
@@ -63,7 +67,7 @@ export interface CommandDispatchResult {
|
|
|
63
67
|
readonly command?: string;
|
|
64
68
|
readonly owner?: PluginId;
|
|
65
69
|
readonly value?: unknown;
|
|
66
|
-
readonly menu?:
|
|
70
|
+
readonly menu?: CommandMenuPage;
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
/** Semantic menu navigation; the IM composition layer supplies platform controls. */
|
|
@@ -74,14 +78,28 @@ export interface CommandMenuNavigation {
|
|
|
74
78
|
readonly pages: number;
|
|
75
79
|
}
|
|
76
80
|
|
|
81
|
+
/** Immutable page data; presentation cannot change the catalog or execute commands. */
|
|
82
|
+
export interface CommandMenuPage extends CommandMenuNavigation {
|
|
83
|
+
readonly locale: Locale;
|
|
84
|
+
readonly title: string;
|
|
85
|
+
readonly commands: readonly CommandMenuEntry[];
|
|
86
|
+
readonly plugins: readonly CommandMenuEntry[];
|
|
87
|
+
readonly commandTotal: number;
|
|
88
|
+
readonly pluginTotal: number;
|
|
89
|
+
readonly hint?: string;
|
|
90
|
+
readonly text: string;
|
|
91
|
+
readonly appearance: CommandMenuAppearance;
|
|
92
|
+
}
|
|
93
|
+
|
|
77
94
|
interface CommandMenuResult {
|
|
78
95
|
readonly value: string | CommandMenuSegment;
|
|
79
|
-
readonly menu?:
|
|
96
|
+
readonly menu?: CommandMenuPage;
|
|
80
97
|
}
|
|
81
98
|
|
|
82
99
|
interface CommandRecord extends CommandDescriptor {
|
|
83
100
|
readonly slot: Readonly<CapabilitySlot<CommandDefinition>>;
|
|
84
101
|
readonly segments: readonly string[];
|
|
102
|
+
readonly preferredSegments: Readonly<Partial<Record<Locale, readonly string[]>>>;
|
|
85
103
|
readonly parameter?: CommandParameterDefinition;
|
|
86
104
|
}
|
|
87
105
|
|
|
@@ -121,7 +139,8 @@ const segmentFields = {
|
|
|
121
139
|
};
|
|
122
140
|
|
|
123
141
|
export interface CommandMenuConfig {
|
|
124
|
-
|
|
142
|
+
/** Omission selects the built-in keyword for each operation's language. */
|
|
143
|
+
readonly keyword?: string;
|
|
125
144
|
}
|
|
126
145
|
|
|
127
146
|
export class CommandIndex {
|
|
@@ -198,6 +217,8 @@ export class CommandIndex {
|
|
|
198
217
|
...(shortcutKeys && shortcutKeys.length > 0 ? { shortcut: shortcutKeys } : {}),
|
|
199
218
|
slot,
|
|
200
219
|
segments: Object.freeze(primarySegments),
|
|
220
|
+
preferredSegments: Object.freeze(Object.fromEntries(Object.entries(slot.definition.preferredAliasI18n ?? {}).map(([locale, entry]) =>
|
|
221
|
+
[locale, Object.freeze([...namespace, ...aliasRuntimeSegments(entry, primarySegments.slice(namespace.length))])]))),
|
|
201
222
|
parameter,
|
|
202
223
|
});
|
|
203
224
|
|
|
@@ -251,8 +272,30 @@ export class CommandIndex {
|
|
|
251
272
|
this.#shortcuts = shortcuts;
|
|
252
273
|
}
|
|
253
274
|
|
|
254
|
-
list(): readonly CommandDescriptor[] {
|
|
255
|
-
return this.#commands.map(toDescriptor)
|
|
275
|
+
list(locale: Locale = 'zh-CN'): readonly CommandDescriptor[] {
|
|
276
|
+
return this.#commands.map(record => ({ ...toDescriptor(record),
|
|
277
|
+
name: commandDisplayName(record, locale), usage: commandUsage(commandDisplayName(record, locale), record.slot.definition),
|
|
278
|
+
description: commandDescription(record.slot.definition, locale),
|
|
279
|
+
parameters: record.parameters.map(parameter => ({ ...parameter, description: commandDescription(parameter, locale) })),
|
|
280
|
+
options: Object.freeze(Object.fromEntries(Object.entries(record.options).map(([key, schema]) =>
|
|
281
|
+
[key, { ...schema, description: commandDescription(schema, locale) }]))),
|
|
282
|
+
}));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Resolve a local static route without executing parameters, permission checks or commands. */
|
|
286
|
+
formatCommand(owner: PluginId, localPath: string, prefix: string | undefined, locale: Locale = 'zh-CN'): string | undefined {
|
|
287
|
+
if (prefix === undefined || typeof localPath !== 'string') return undefined;
|
|
288
|
+
const path = localPath.trim().split(/\s+/u).filter(Boolean).join(' ');
|
|
289
|
+
if (!path) return undefined;
|
|
290
|
+
const command = this.#commands.find(record => record.owner === owner
|
|
291
|
+
&& record.slot.localName.split('/').filter(segment => !segment.startsWith('$')).join(' ') === path);
|
|
292
|
+
return command ? prefix + commandDisplaySegments(command, locale).filter(segment => !segment.startsWith('$')).join(' ') : undefined;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** Return the configured menu entry only when the calling environment is known. */
|
|
296
|
+
formatMenu(prefix: string | undefined, locale: Locale): string | undefined {
|
|
297
|
+
if (prefix === undefined || !this.#menu) return undefined;
|
|
298
|
+
return prefix + (this.#menu.keyword ?? commandMessages(locale, 'menuKeyword'));
|
|
256
299
|
}
|
|
257
300
|
|
|
258
301
|
has(name: string): boolean {
|
|
@@ -265,6 +308,7 @@ export class CommandIndex {
|
|
|
265
308
|
}
|
|
266
309
|
|
|
267
310
|
async execute(name: string, args: readonly string[] = [], operationSnapshot: RuntimeSnapshot = this.snapshot): Promise<unknown> {
|
|
311
|
+
const locale = commandLocale(undefined, operationSnapshot);
|
|
268
312
|
const match = this.#match(name, false);
|
|
269
313
|
if (!match) {
|
|
270
314
|
this.#diagnoseParameter(name);
|
|
@@ -273,14 +317,14 @@ export class CommandIndex {
|
|
|
273
317
|
if (!(await this.#permitAllows(match.command, undefined, operationSnapshot))) throw new Error(`Permission denied for Command: ${match.command.name}`);
|
|
274
318
|
const definition = match.command.slot.definition;
|
|
275
319
|
const supplied = [...match.remaining, ...(args.length > 0 ? [{ type: 'text', data: { text: args.join(' ') } }] : [])];
|
|
276
|
-
const parsed = parseCommandArguments(definition, supplied, resolveDynamicParams(match.params, undefined), {});
|
|
277
|
-
if (parsed.issues.length > 0) throw new CommandArgumentValidationError(parsed.issues, commandHelp(match.command
|
|
320
|
+
const parsed = parseCommandArguments(definition, supplied, resolveDynamicParams(match.params, undefined), {}, locale);
|
|
321
|
+
if (parsed.issues.length > 0) throw new CommandArgumentValidationError(parsed.issues, commandHelp(commandDisplayName(match.command, locale), definition, '', locale));
|
|
278
322
|
// No inbound source: execution policy applies per command, never as a blanket refusal.
|
|
279
323
|
const admission = admitDirectCommandExecution(match.command.slot.id, match.command.execution, operationSnapshot);
|
|
280
324
|
if (admission.limit) throw new CommandExecutionLimitedError(admission.limit);
|
|
281
325
|
try {
|
|
282
326
|
return await definition.execute(createCommandContext(operationSnapshot, match.command.slot.owner,
|
|
283
|
-
textArgs(parsed.remaining), parsed.params, undefined, parsed.remaining, undefined, definition.adapter, parsed.options));
|
|
327
|
+
textArgs(parsed.remaining), parsed.params, undefined, parsed.remaining, undefined, definition.adapter, parsed.options, { locale }));
|
|
284
328
|
} finally {
|
|
285
329
|
admission.release();
|
|
286
330
|
}
|
|
@@ -290,16 +334,20 @@ export class CommandIndex {
|
|
|
290
334
|
input: CommandMatchInput,
|
|
291
335
|
source: unknown = undefined,
|
|
292
336
|
interactionFactory?: UserInteractionFactory,
|
|
293
|
-
commandPrefix
|
|
337
|
+
commandPrefix?: string,
|
|
294
338
|
operationSnapshot: RuntimeSnapshot = this.snapshot,
|
|
339
|
+
localeOverride?: Locale,
|
|
295
340
|
): Promise<CommandDispatchResult> {
|
|
341
|
+
const locale = localeOverride ?? commandLocale(source, operationSnapshot);
|
|
342
|
+
const prefix = commandPrefix ?? '';
|
|
296
343
|
if (this.#menu) {
|
|
297
|
-
const
|
|
344
|
+
const keyword = this.#menu.keyword ?? commandMessages(locale, 'menuKeyword');
|
|
345
|
+
const menuResult = this.#dispatchMenu(input, prefix, operationSnapshot, locale, keyword);
|
|
298
346
|
if (menuResult !== undefined) {
|
|
299
347
|
return Object.freeze({
|
|
300
348
|
matched: true,
|
|
301
349
|
status: 'executed',
|
|
302
|
-
command:
|
|
350
|
+
command: keyword,
|
|
303
351
|
owner: operationSnapshot.root,
|
|
304
352
|
...menuResult,
|
|
305
353
|
});
|
|
@@ -314,27 +362,29 @@ export class CommandIndex {
|
|
|
314
362
|
if (!candidate) return Object.freeze({ matched: false, status: 'unmatched' });
|
|
315
363
|
if (!commandAdapterMatches(candidate, source)) return Object.freeze({ matched: false, status: 'unmatched' });
|
|
316
364
|
if (!(await this.#permitAllows(candidate, source, operationSnapshot))) return this.#denied(candidate);
|
|
317
|
-
const help = commandHelp(candidate
|
|
365
|
+
const help = commandHelp(commandDisplayName(candidate, locale), candidate.slot.definition, prefix, locale);
|
|
318
366
|
const segments = normalizeSegments(typeof input === 'string' ? [{ type: 'text', data: { text: input } }] : input);
|
|
319
367
|
if (requestsCommandHelp(segments)) return Object.freeze({ matched: true, status: 'executed', command: candidate.name, owner: candidate.slot.owner, value: help });
|
|
320
368
|
return Object.freeze({ matched: true, status: 'invalid', command: candidate.name, owner: candidate.slot.owner,
|
|
321
|
-
issues: Object.freeze([
|
|
369
|
+
issues: Object.freeze([commandValidationIssue('invalidParameter', candidate.parameter?.name ?? 'arguments', locale)]), help });
|
|
322
370
|
}
|
|
323
371
|
if (!commandAdapterMatches(match.command, source)) return Object.freeze({ matched: false, status: 'unmatched' });
|
|
324
372
|
if (!(await this.#permitAllows(match.command, source, operationSnapshot))) return this.#denied(match.command);
|
|
325
373
|
const definition = match.command.slot.definition;
|
|
326
374
|
const authorSource = bindOperationInput(source, match.command.slot.owner, operationSnapshot);
|
|
327
|
-
const help = commandHelp(match.command
|
|
375
|
+
const help = commandHelp(commandDisplayName(match.command, locale), definition, prefix, locale);
|
|
328
376
|
if (match.helpRequested) {
|
|
329
377
|
return Object.freeze({ matched: true, status: 'executed', command: match.command.name, owner: match.command.slot.owner, value: help });
|
|
330
378
|
}
|
|
331
|
-
const parsed = parseCommandArguments(definition, match.remaining, resolveDynamicParams(match.params, authorSource), resolveCommandSession(authorSource));
|
|
379
|
+
const parsed = parseCommandArguments(definition, match.remaining, resolveDynamicParams(match.params, authorSource), resolveCommandSession(authorSource), locale);
|
|
332
380
|
if (parsed.issues.length > 0) return Object.freeze({ matched: true, status: 'invalid', command: match.command.name,
|
|
333
381
|
owner: match.command.slot.owner, issues: parsed.issues, help });
|
|
334
382
|
const limit = admitCommandExecution(authorSource, match.command.slot.id, match.command.slot.owner, match.command.execution, operationSnapshot);
|
|
335
383
|
if (limit) return Object.freeze({ matched: true, status: 'limited', command: match.command.name, owner: match.command.slot.owner, limit });
|
|
336
384
|
const value = await definition.execute(createCommandContext(operationSnapshot, match.command.slot.owner,
|
|
337
|
-
textArgs(parsed.remaining), parsed.params, authorSource, parsed.remaining, interactionFactory?.(authorSource), definition.adapter, parsed.options
|
|
385
|
+
textArgs(parsed.remaining), parsed.params, authorSource, parsed.remaining, interactionFactory?.(authorSource), definition.adapter, parsed.options,
|
|
386
|
+
{ locale, formatCommand: (localPath, owner = match.command.owner) => this.formatCommand(owner, localPath,
|
|
387
|
+
source && typeof source === 'object' && 'conversation' in source ? commandPrefix : undefined, locale) }));
|
|
338
388
|
return Object.freeze({ matched: true, status: 'executed', command: match.command.name, owner: match.command.slot.owner, value });
|
|
339
389
|
}
|
|
340
390
|
|
|
@@ -446,13 +496,10 @@ export class CommandIndex {
|
|
|
446
496
|
// 内置菜单命令
|
|
447
497
|
// ==========================================================================
|
|
448
498
|
|
|
449
|
-
#dispatchMenu(input: CommandMatchInput, commandPrefix: string, snapshot: RuntimeSnapshot): CommandMenuResult | undefined {
|
|
499
|
+
#dispatchMenu(input: CommandMatchInput, commandPrefix: string, snapshot: RuntimeSnapshot, locale: Locale, keyword: string): CommandMenuResult | undefined {
|
|
450
500
|
const text = typeof input === 'string' ? input.trim() : exactMessageText(input);
|
|
451
501
|
if (text === undefined) return undefined;
|
|
452
|
-
|
|
453
|
-
if (!menu) return undefined;
|
|
454
|
-
const keyword = menu.keyword;
|
|
455
|
-
if (text === keyword) return this.#buildMenu(undefined, commandPrefix, keyword, snapshot);
|
|
502
|
+
if (text === keyword) return this.#buildMenu(undefined, commandPrefix, keyword, snapshot, locale);
|
|
456
503
|
if (text.startsWith(keyword + ' ')) {
|
|
457
504
|
const key = text.slice(keyword.length + 1).trim();
|
|
458
505
|
const args = key.split(/\s+/u);
|
|
@@ -461,24 +508,24 @@ export class CommandIndex {
|
|
|
461
508
|
const pageText = args[flag + 1] ?? '';
|
|
462
509
|
if (flag > 1 || args.length !== flag + 2 || !/^[1-9]\d*$/u.test(pageText)
|
|
463
510
|
|| !Number.isSafeInteger(Number(pageText))) {
|
|
464
|
-
return { value:
|
|
511
|
+
return { value: commandMessages(locale, 'invalidPage', { command: `${commandPrefix}${keyword}` }) };
|
|
465
512
|
}
|
|
466
|
-
return this.#buildMenu(flag === 1 ? args[0] : undefined, commandPrefix, keyword, snapshot, Number(pageText));
|
|
513
|
+
return this.#buildMenu(flag === 1 ? args[0] : undefined, commandPrefix, keyword, snapshot, locale, Number(pageText));
|
|
467
514
|
}
|
|
468
|
-
if (key) return this.#buildMenu(key, commandPrefix, keyword, snapshot);
|
|
469
|
-
return this.#buildMenu(undefined, commandPrefix, keyword, snapshot);
|
|
515
|
+
if (key) return this.#buildMenu(key, commandPrefix, keyword, snapshot, locale);
|
|
516
|
+
return this.#buildMenu(undefined, commandPrefix, keyword, snapshot, locale);
|
|
470
517
|
}
|
|
471
518
|
return undefined;
|
|
472
519
|
}
|
|
473
520
|
|
|
474
|
-
#buildMenu(pluginKey: string | undefined, commandPrefix: string, keyword: string, snapshot: RuntimeSnapshot, page = 1): CommandMenuResult {
|
|
521
|
+
#buildMenu(pluginKey: string | undefined, commandPrefix: string, keyword: string, snapshot: RuntimeSnapshot, locale: Locale, page = 1): CommandMenuResult {
|
|
475
522
|
const root = snapshot.root;
|
|
476
523
|
const targetId = pluginKey
|
|
477
524
|
? `${root}/${pluginKey.split('.').join('/')}` as PluginId
|
|
478
525
|
: root;
|
|
479
526
|
|
|
480
527
|
const node = snapshot.tree.get(targetId);
|
|
481
|
-
if (!node) return { value:
|
|
528
|
+
if (!node) return { value: commandMessages(locale, 'missingPlugin', { plugin: pluginKey ?? '' }) };
|
|
482
529
|
|
|
483
530
|
const directCommands = this.#commands.filter((cmd) => cmd.slot.owner === targetId);
|
|
484
531
|
const children = node.children
|
|
@@ -487,26 +534,27 @@ export class CommandIndex {
|
|
|
487
534
|
|
|
488
535
|
const pages = Math.max(1, Math.ceil((directCommands.length + children.length) / MENU_PAGE_SIZE));
|
|
489
536
|
const invocation = `${commandPrefix}${keyword}${pluginKey ? ` ${pluginKey}` : ''}`;
|
|
490
|
-
if (page > pages) return { value:
|
|
537
|
+
if (page > pages) return { value: commandMessages(locale, 'pageOutOfRange', { pages, command: invocation }) };
|
|
491
538
|
const offset = (page - 1) * MENU_PAGE_SIZE;
|
|
492
539
|
const pageCommands = directCommands.slice(offset, offset + MENU_PAGE_SIZE);
|
|
493
540
|
const pageChildren = children.slice(Math.max(0, offset - directCommands.length), Math.max(0, offset + MENU_PAGE_SIZE - directCommands.length));
|
|
494
541
|
|
|
495
542
|
const displayKey = pluginKey ?? node.instanceKey;
|
|
496
|
-
const lines: string[] = [
|
|
497
|
-
if (pages > 1) lines.push(
|
|
543
|
+
const lines: string[] = [commandMessages(locale, 'menuTitle', { plugin: displayKey })];
|
|
544
|
+
if (pages > 1) lines.push(commandMessages(locale, 'menuSummary', { page, pages, commands: directCommands.length, plugins: children.length }));
|
|
498
545
|
|
|
499
546
|
if (pageCommands.length > 0) {
|
|
500
547
|
lines.push('');
|
|
501
548
|
for (const cmd of pageCommands) {
|
|
502
|
-
const
|
|
503
|
-
|
|
549
|
+
const description = commandDescription(cmd.slot.definition, locale);
|
|
550
|
+
const desc = description ? ` - ${description}` : '';
|
|
551
|
+
lines.push(` ${commandPrefix}${commandDisplayName(cmd, locale)}${desc}`);
|
|
504
552
|
}
|
|
505
553
|
}
|
|
506
554
|
|
|
507
555
|
if (pageChildren.length > 0) {
|
|
508
556
|
lines.push('');
|
|
509
|
-
lines.push('
|
|
557
|
+
lines.push(commandMessages(locale, 'childPlugins'));
|
|
510
558
|
for (const child of pageChildren) {
|
|
511
559
|
const childKey = pluginKey ? `${pluginKey}.${child.instanceKey}` : child.instanceKey;
|
|
512
560
|
const label = child.metadata?.displayName ?? child.instanceKey;
|
|
@@ -516,13 +564,13 @@ export class CommandIndex {
|
|
|
516
564
|
|
|
517
565
|
if (directCommands.length === 0 && children.length === 0) {
|
|
518
566
|
lines.push('');
|
|
519
|
-
lines.push('
|
|
567
|
+
lines.push(commandMessages(locale, 'emptyMenu'));
|
|
520
568
|
}
|
|
521
569
|
|
|
522
570
|
const hints: string[] = [];
|
|
523
|
-
if (children.length > 0) hints.push(
|
|
524
|
-
if (page > 1) hints.push(
|
|
525
|
-
if (page < pages) hints.push(
|
|
571
|
+
if (children.length > 0) hints.push(commandMessages(locale, 'pluginHint', { command: `${commandPrefix}${keyword}` }));
|
|
572
|
+
if (page > 1) hints.push(commandMessages(locale, 'previousPage', { command: `${invocation} --page ${page - 1}` }));
|
|
573
|
+
if (page < pages) hints.push(commandMessages(locale, 'nextPage', { command: `${invocation} --page ${page + 1}` }));
|
|
526
574
|
const hint = hints.length ? hints.join('\n') : undefined;
|
|
527
575
|
if (hint) {
|
|
528
576
|
lines.push('');
|
|
@@ -530,19 +578,39 @@ export class CommandIndex {
|
|
|
530
578
|
}
|
|
531
579
|
|
|
532
580
|
const text = lines.join('\n');
|
|
533
|
-
const menu = Object.freeze({ keyword, ...(pluginKey ? { pluginKey } : {}), page, pages });
|
|
534
|
-
if (!snapshot.resources.get(root)?.has(htmlRendererToken.id)) return { value: text, menu };
|
|
535
581
|
const config = snapshot.config.get(root) as Record<string, unknown> | undefined;
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
582
|
+
const menu: CommandMenuPage = Object.freeze({
|
|
583
|
+
keyword, ...(pluginKey ? { pluginKey } : {}), page, pages, locale, title: displayKey,
|
|
584
|
+
commands: Object.freeze(pageCommands.map(cmd => Object.freeze({
|
|
585
|
+
name: `${commandPrefix}${commandDisplayName(cmd, locale)}`, description: commandDescription(cmd.slot.definition, locale),
|
|
586
|
+
}))),
|
|
587
|
+
plugins: Object.freeze(pageChildren.map(child => Object.freeze({
|
|
539
588
|
name: pluginKey ? `${pluginKey}.${child.instanceKey}` : child.instanceKey,
|
|
540
589
|
description: child.metadata?.displayName ?? child.instanceKey,
|
|
541
|
-
})),
|
|
542
|
-
|
|
590
|
+
}))),
|
|
591
|
+
commandTotal: directCommands.length, pluginTotal: children.length, hint, text,
|
|
592
|
+
appearance: Object.freeze(readMenuAppearance(config?.menuStyle)),
|
|
593
|
+
});
|
|
594
|
+
if (!snapshot.resources.get(root)?.has(htmlRendererToken.id)) return { value: text, menu };
|
|
595
|
+
return { menu, value: renderCommandMenu(displayKey, menu.commands, menu.plugins, hint, text, {
|
|
596
|
+
page, pages, locale, commandTotal: menu.commandTotal, pluginTotal: menu.pluginTotal, appearance: menu.appearance,
|
|
597
|
+
}) };
|
|
543
598
|
}
|
|
544
599
|
}
|
|
545
600
|
|
|
601
|
+
function commandDisplaySegments(record: CommandRecord, locale: Locale): readonly string[] {
|
|
602
|
+
return record.preferredSegments[locale] ?? record.segments;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function commandDisplayName(record: CommandRecord, locale: Locale): string {
|
|
606
|
+
return displayName(commandDisplaySegments(record, locale), record.parameter);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function commandLocale(source: unknown, snapshot: RuntimeSnapshot): Locale {
|
|
610
|
+
const locale = source && typeof source === 'object' && 'locale' in source ? source.locale : undefined;
|
|
611
|
+
return resolveLocale(locale ?? (snapshot.config.get(snapshot.root) as { locale?: unknown } | undefined)?.locale);
|
|
612
|
+
}
|
|
613
|
+
|
|
546
614
|
export function isCommandIndex(value: unknown): value is CommandIndex {
|
|
547
615
|
return !!value && typeof value === 'object'
|
|
548
616
|
&& (value as { readonly $projection?: unknown }).$projection === 'zhin.command-index/1';
|
package/src/command-menu.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
import type { Locale } from '@zhin.js/plugin-runtime';
|
|
2
|
+
import { commandMessages } from './messages.js';
|
|
3
|
+
|
|
4
|
+
export interface CommandMenuEntry {
|
|
2
5
|
readonly name: string;
|
|
3
6
|
readonly description?: string;
|
|
4
7
|
}
|
|
5
8
|
|
|
6
|
-
interface
|
|
9
|
+
export interface CommandMenuAppearance {
|
|
7
10
|
readonly title?: string;
|
|
8
11
|
readonly subtitle?: string;
|
|
9
12
|
readonly accentColor?: string;
|
|
@@ -27,7 +30,7 @@ const escape = (value: string) => value.replace(/[&<>"']/gu, char => ({
|
|
|
27
30
|
})[char] ?? char);
|
|
28
31
|
|
|
29
32
|
/** Configuration is text/data, never an interpolated CSS or HTML fragment. */
|
|
30
|
-
export function readMenuAppearance(value: unknown):
|
|
33
|
+
export function readMenuAppearance(value: unknown): CommandMenuAppearance {
|
|
31
34
|
if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
|
|
32
35
|
const input = value as Record<string, unknown>;
|
|
33
36
|
const result: { title?: string; subtitle?: string; accentColor?: string; backgroundImage?: string } = {};
|
|
@@ -69,20 +72,21 @@ function icon(name: string, plugin = false): string {
|
|
|
69
72
|
/** Presentation only: the unified outbound pipeline owns image rendering and fallback. */
|
|
70
73
|
export function renderCommandMenu(
|
|
71
74
|
title: string,
|
|
72
|
-
commands: readonly
|
|
73
|
-
children: readonly
|
|
75
|
+
commands: readonly CommandMenuEntry[],
|
|
76
|
+
children: readonly CommandMenuEntry[],
|
|
74
77
|
hint: string | undefined,
|
|
75
78
|
text: string,
|
|
76
|
-
options: { readonly page?: number; readonly pages?: number; readonly commandTotal?: number; readonly pluginTotal?: number; readonly appearance?:
|
|
79
|
+
options: { readonly page?: number; readonly pages?: number; readonly locale?: Locale; readonly commandTotal?: number; readonly pluginTotal?: number; readonly appearance?: CommandMenuAppearance } = {},
|
|
77
80
|
): CommandMenuSegment {
|
|
81
|
+
const locale = options.locale ?? 'zh-CN';
|
|
78
82
|
const appearance = options.appearance ?? {};
|
|
79
83
|
const background = appearance.backgroundImage ?? `data:image/svg+xml,${encodeURIComponent(landscape)}`;
|
|
80
84
|
// CSS string escaping additionally prevents an artwork URL from closing <style>.
|
|
81
85
|
const cssUrl = JSON.stringify(background).replace(/</gu, '\\3c ').replace(/>/gu, '\\3e ');
|
|
82
|
-
const entries = (items: readonly
|
|
86
|
+
const entries = (items: readonly CommandMenuEntry[], plugin: boolean) => items.map(item =>
|
|
83
87
|
`<div class="entry"><div class="icon">${icon(item.name, plugin)}</div><div class="copy"><div class="name">${escape(item.name)}</div>${item.description
|
|
84
88
|
? `<div class="description">${escape(item.description)}</div>` : ''}</div></div>`).join('');
|
|
85
|
-
const section = (label: string, items: readonly
|
|
89
|
+
const section = (label: string, items: readonly CommandMenuEntry[], total: number, plugin = false) => items.length === 0 ? ''
|
|
86
90
|
: `<section><div class="section-head"><h2>${label}</h2><span>${items.length} / ${total}</span></div><div class="grid">${entries(items, plugin)}</div></section>`;
|
|
87
91
|
const page = options.page ?? 1;
|
|
88
92
|
const pages = options.pages ?? 1;
|
|
@@ -91,7 +95,7 @@ export function renderCommandMenu(
|
|
|
91
95
|
type: 'html',
|
|
92
96
|
data: {
|
|
93
97
|
width: 1080, fileName: 'command-menu.png', text,
|
|
94
|
-
html: `<!doctype html><html lang="
|
|
98
|
+
html: `<!doctype html><html lang="${locale}"><head><meta charset="utf-8"><style>
|
|
95
99
|
*{box-sizing:border-box}body{margin:0;background:#253b46;color:#f4f0e6;font-family:"PingFang SC","Microsoft YaHei",sans-serif;font-size:18px;line-height:1.5;--accent:${appearance.accentColor ?? '#e7c993'}}
|
|
96
100
|
main{padding:42px 28px 24px;background-image:linear-gradient(180deg,#0c202a0a,#0c202a55),url(${cssUrl});background-size:cover;background-position:center top}
|
|
97
101
|
header{min-height:210px;padding:10px 22px 30px;position:relative}.eyebrow{font-size:12px;letter-spacing:5px;color:#f4e4c4;margin:0 0 17px}
|
|
@@ -102,11 +106,11 @@ h2{margin:0;font-size:20px;font-weight:700;letter-spacing:1px;color:var(--accent
|
|
|
102
106
|
.grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));background:#20363fdb;border-radius:0 0 13px 13px;overflow:hidden}
|
|
103
107
|
.entry{display:flex;align-items:center;gap:12px;padding:19px 17px;min-width:0;min-height:106px;border-top:1px solid #e7e6d614;border-right:1px solid #e7e6d614;background:linear-gradient(120deg,#ffffff08,#ffffff00)}.entry:nth-child(3n){border-right:0}
|
|
104
108
|
.icon{width:35px;height:35px;flex:0 0 35px;color:var(--accent);filter:drop-shadow(0 1px 6px #d9ba7c22)}.icon svg{display:block;width:100%;height:100%}.copy{min-width:0}.name{font-size:18px;font-weight:700;color:var(--accent)}.description{margin:4px 0 0;color:#e1e8e5;font-size:15px;line-height:1.45}
|
|
105
|
-
.name
|
|
109
|
+
.name{overflow-wrap:anywhere;white-space:pre-wrap}.description,h1{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere;white-space:pre-wrap}
|
|
106
110
|
footer{margin:6px 5px 0;padding:15px 18px;display:flex;align-items:center;justify-content:space-between;gap:22px;background:#142a34da;border:1px solid #f0e2c71c;border-radius:13px}.hint{font-size:14px;color:#cfdbd7;white-space:pre-wrap;overflow-wrap:anywhere}.page{font-size:20px;color:var(--accent);white-space:nowrap;font-family:Georgia,serif}.signature{margin:16px 0 0;text-align:center;font-size:10px;letter-spacing:4px;color:#c4d0c899}.empty{margin:0 0 20px;padding:28px;background:#142a34da;border-radius:16px;color:#dbe4de}
|
|
107
|
-
</style></head><body><main><header><div class="eyebrow">ZHIN / COMMAND ATLAS</div><h1>${escape(appearance.title ||
|
|
108
|
-
${section('
|
|
109
|
-
<footer><div class="hint">${escape(hint ??
|
|
111
|
+
</style></head><body><main><header><div class="eyebrow">ZHIN / COMMAND ATLAS</div><h1>${escape(appearance.title || commandMessages(locale, 'guideTitle', { title }))}</h1><div class="subtitle">${escape(appearance.subtitle ?? commandMessages(locale, 'guideSubtitle'))}</div><div class="summary"><i></i>${escape(commandMessages(locale, 'commandCount', { count: options.commandTotal ?? commands.length }))}<span>·</span>${escape(commandMessages(locale, 'pluginCount', { count: options.pluginTotal ?? children.length }))}<span>·</span>${escape(title)}</div></header>
|
|
112
|
+
${section(commandMessages(locale, 'commandIndex'), commands, options.commandTotal ?? commands.length)}${section(commandMessages(locale, 'pluginIndex'), children, options.pluginTotal ?? children.length, true)}${commands.length || children.length ? '' : `<p class="empty">${commandMessages(locale, 'guideEmpty')}</p>`}
|
|
113
|
+
<footer><div class="hint">${escape(hint ?? commandMessages(locale, 'genericHelpHint'))}</div><div class="page">${String(page).padStart(2, '0')} / ${String(pages).padStart(2, '0')}</div></footer><div class="signature">ZHIN · MAKE EVERY MESSAGE MATTER</div></main></body></html>`,
|
|
110
114
|
},
|
|
111
115
|
};
|
|
112
116
|
}
|
package/src/definition.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Command authoring API consumed from `zhin.js/command`.
|
|
3
3
|
* @module zhin.js/command
|
|
4
4
|
*/
|
|
5
|
-
import type
|
|
5
|
+
import { resolveLocale, type Locale, type PluginId, type RuntimeSnapshot } from '@zhin.js/plugin-runtime';
|
|
6
6
|
import {
|
|
7
7
|
createCapabilityContext,
|
|
8
8
|
readOperationClient,
|
|
@@ -92,6 +92,8 @@ export interface CommandParamSchema {
|
|
|
92
92
|
readonly type: CommandParameterType;
|
|
93
93
|
readonly default?: CommandDynamicValue;
|
|
94
94
|
readonly description?: string;
|
|
95
|
+
/** Optional translated display text; machine keys and values remain unchanged. */
|
|
96
|
+
readonly descriptionI18n?: Readonly<Partial<Record<Locale, string>>>;
|
|
95
97
|
readonly optional?: boolean;
|
|
96
98
|
readonly rest?: boolean;
|
|
97
99
|
readonly min?: number;
|
|
@@ -145,6 +147,7 @@ export interface CommandParameterDefinition {
|
|
|
145
147
|
/** `[...name]` / `[[...name]]` 捕获所有段,运行时值为 `string[]`。 */
|
|
146
148
|
readonly rest?: boolean;
|
|
147
149
|
readonly description?: string;
|
|
150
|
+
readonly descriptionI18n?: Readonly<Partial<Record<Locale, string>>>;
|
|
148
151
|
}
|
|
149
152
|
|
|
150
153
|
/** 场景:群 / 私聊 / 频道等。 */
|
|
@@ -192,6 +195,8 @@ export interface CommandConversation {
|
|
|
192
195
|
export interface CommandMessage {
|
|
193
196
|
readonly conversation: CommandConversation;
|
|
194
197
|
readonly content: string;
|
|
198
|
+
/** Display preference resolved by the IM runtime, when this is an IM operation. */
|
|
199
|
+
readonly locale?: Locale;
|
|
195
200
|
/** 发送者(结构化视图见 CommandContext.sender)。 */
|
|
196
201
|
readonly sender?: { readonly id: string; readonly name?: string; readonly roles?: readonly string[] };
|
|
197
202
|
readonly id?: string;
|
|
@@ -264,6 +269,16 @@ export interface CommandContext<
|
|
|
264
269
|
TInput extends CommandMessage = CommandMessage,
|
|
265
270
|
TAdapter extends string | undefined = undefined,
|
|
266
271
|
> extends CapabilityContext<TConfig>, CommandSession {
|
|
272
|
+
/** Language resolved once for the current operation. */
|
|
273
|
+
readonly locale: Locale;
|
|
274
|
+
/**
|
|
275
|
+
* Resolve a static local command path to its current chat invocation. Defaults to this
|
|
276
|
+
* plugin; an explicit owner may reference another indexed plugin.
|
|
277
|
+
* Includes the actual Endpoint prefix and configured commandNamespace. Arguments
|
|
278
|
+
* are appended explicitly by the caller. Returns undefined for unknown/disabled
|
|
279
|
+
* paths or calls without a known chat command environment.
|
|
280
|
+
*/
|
|
281
|
+
formatCommand(localPath: string, owner?: PluginId): string | undefined;
|
|
267
282
|
readonly args: readonly string[];
|
|
268
283
|
readonly params: Readonly<Record<string, CommandParameterValue>>;
|
|
269
284
|
readonly options: Readonly<Record<string, CommandParameterValue>>;
|
|
@@ -294,6 +309,7 @@ export interface CommandDefinition<
|
|
|
294
309
|
/** @internal Convention-derived parameter metadata. */
|
|
295
310
|
readonly $parameter?: CommandParameterDefinition;
|
|
296
311
|
readonly description?: string;
|
|
312
|
+
readonly descriptionI18n?: Readonly<Partial<Record<Locale, string>>>;
|
|
297
313
|
/** Restrict this command to one adapter and infer `context.$client`. */
|
|
298
314
|
readonly adapter?: TAdapter;
|
|
299
315
|
/**
|
|
@@ -307,6 +323,8 @@ export interface CommandDefinition<
|
|
|
307
323
|
* 用户路由;alias 只替换文件路径提供的静态段。
|
|
308
324
|
*/
|
|
309
325
|
readonly alias?: readonly string[];
|
|
326
|
+
/** Display an explicitly declared alias for this locale; every value must also occur in alias. */
|
|
327
|
+
readonly preferredAliasI18n?: Readonly<Partial<Record<Locale, string>>>;
|
|
310
328
|
/**
|
|
311
329
|
* 内置 permit DSL(AND)。单项内逗号为 OR。
|
|
312
330
|
* 例:`adapter(icqq)`、`role(master)`、`group(123,456)`。
|
|
@@ -398,7 +416,9 @@ export function defineCommand<
|
|
|
398
416
|
}
|
|
399
417
|
}
|
|
400
418
|
validateArgumentSchemas(definition.params, definition.options);
|
|
419
|
+
validateDescriptionI18n(definition.descriptionI18n);
|
|
401
420
|
validateCommandAlias(definition.alias);
|
|
421
|
+
validatePreferredAliases(definition);
|
|
402
422
|
validateCommandPermit(definition.permit);
|
|
403
423
|
validateCommandShortcutShape(definition.shortcut);
|
|
404
424
|
const execution = normalizeExecutionPolicy(definition.execution);
|
|
@@ -407,6 +427,15 @@ export function defineCommand<
|
|
|
407
427
|
>;
|
|
408
428
|
}
|
|
409
429
|
|
|
430
|
+
/** @internal Validate display-only translations without changing their fallback text. */
|
|
431
|
+
export function validateDescriptionI18n(value: CommandDefinition['descriptionI18n']): void {
|
|
432
|
+
if (value === undefined) return;
|
|
433
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)
|
|
434
|
+
|| Object.entries(value).some(([locale, text]) => !['zh-CN', 'en'].includes(locale) || typeof text !== 'string')) {
|
|
435
|
+
throw new TypeError('Command descriptionI18n must map zh-CN/en to strings');
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
410
439
|
/**
|
|
411
440
|
* 先固定插件 config 类型,再定义命令;params / options / 返回值仍从定义推断。
|
|
412
441
|
*
|
|
@@ -435,6 +464,19 @@ function validateAdapterName(adapter: string | undefined): void {
|
|
|
435
464
|
}
|
|
436
465
|
}
|
|
437
466
|
|
|
467
|
+
function validatePreferredAliases(definition: Pick<CommandDefinition, 'alias' | 'preferredAliasI18n'>): void {
|
|
468
|
+
const preferred = definition.preferredAliasI18n;
|
|
469
|
+
if (preferred === undefined) return;
|
|
470
|
+
validateDescriptionI18n(preferred);
|
|
471
|
+
const normalize = (value: string) => value.trim().split(/\s+/u).filter(Boolean).join(' ');
|
|
472
|
+
const aliases = new Set(definition.alias?.map(normalize));
|
|
473
|
+
for (const [locale, alias] of Object.entries(preferred)) {
|
|
474
|
+
if (!aliases.has(normalize(alias))) {
|
|
475
|
+
throw new TypeError(`Command preferredAliasI18n.${locale} must reference a declared alias`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
438
480
|
function validateCommandAlias(alias: readonly string[] | undefined): void {
|
|
439
481
|
if (alias === undefined) return;
|
|
440
482
|
if (!Array.isArray(alias)) {
|
|
@@ -506,6 +548,8 @@ export function parseCommandDefinition(value: unknown): CommandDefinition {
|
|
|
506
548
|
}
|
|
507
549
|
validateAdapterName(definition.adapter);
|
|
508
550
|
normalizeExecutionPolicy(definition.execution);
|
|
551
|
+
validateCommandAlias(definition.alias);
|
|
552
|
+
validatePreferredAliases(definition);
|
|
509
553
|
return definition as CommandDefinition;
|
|
510
554
|
}
|
|
511
555
|
|
|
@@ -537,6 +581,7 @@ export function createCommandContext(
|
|
|
537
581
|
interaction?: UserInteraction,
|
|
538
582
|
adapter?: string,
|
|
539
583
|
options: Readonly<Record<string, CommandParameterValue>> = Object.freeze({}),
|
|
584
|
+
presentation?: Readonly<{ locale: Locale; formatCommand?: (localPath: string, owner?: PluginId) => string | undefined }>,
|
|
540
585
|
): CommandContext {
|
|
541
586
|
const context = createCapabilityContext(snapshot, ownerId, undefined, readOperationInputSignal(input));
|
|
542
587
|
const session = resolveCommandSession(input);
|
|
@@ -545,6 +590,8 @@ export function createCommandContext(
|
|
|
545
590
|
const result = {
|
|
546
591
|
...context,
|
|
547
592
|
...session,
|
|
593
|
+
locale: presentation?.locale ?? resolveLocale((snapshot.config.get(snapshot.root) as { locale?: unknown } | undefined)?.locale),
|
|
594
|
+
formatCommand: presentation?.formatCommand ?? (() => undefined),
|
|
548
595
|
args: Object.freeze([...args]),
|
|
549
596
|
params: Object.freeze(parameterValues),
|
|
550
597
|
options: Object.freeze(optionValues),
|
package/src/index.ts
CHANGED
package/src/messages.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { createMessages, type Locale } from '@zhin.js/plugin-runtime';
|
|
2
|
+
|
|
3
|
+
/** Translates only display metadata explicitly supplied by its author. */
|
|
4
|
+
export function commandDescription(
|
|
5
|
+
value: Readonly<{ description?: string; descriptionI18n?: Readonly<Partial<Record<Locale, string>>> }>,
|
|
6
|
+
locale: Locale,
|
|
7
|
+
): string | undefined {
|
|
8
|
+
return value.descriptionI18n?.[locale] ?? value.description;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Static dictionaries; the caller supplies the operation's language. */
|
|
12
|
+
export const commandMessages = createMessages({
|
|
13
|
+
menuKeyword: { 'zh-CN': '菜单', en: 'menu' },
|
|
14
|
+
invalidPage: { 'zh-CN': '页码必须是正整数。用法:{command} [插件名] --page <页码>', en: 'The page must be a positive integer. Usage: {command} [plugin] --page <page>' },
|
|
15
|
+
missingPlugin: { 'zh-CN': '未找到插件: {plugin}', en: 'Plugin not found: {plugin}' },
|
|
16
|
+
pageOutOfRange: { 'zh-CN': '页码超出范围:共 {pages} 页。请使用「{command} --page <页码>」。', en: 'Page out of range: {pages} pages available. Use {command} --page <page>.' },
|
|
17
|
+
menuTitle: { 'zh-CN': '=== {plugin} 指令菜单 ===', en: '=== {plugin} command menu ===' },
|
|
18
|
+
menuSummary: { 'zh-CN': '第 {page}/{pages} 页 · {commands} 条指令 · {plugins} 个子插件', en: 'Page {page}/{pages} · Commands: {commands} · Plugins: {plugins}' },
|
|
19
|
+
childPlugins: { 'zh-CN': '子插件:', en: 'Plugins:' },
|
|
20
|
+
emptyMenu: { 'zh-CN': '(暂无指令和子插件)', en: '(No commands or plugins)' },
|
|
21
|
+
pluginHint: { 'zh-CN': '提示:使用「{command} <插件名>」查看子插件的指令', en: 'Use {command} <plugin> to view a plugin’s commands.' },
|
|
22
|
+
previousPage: { 'zh-CN': '上一页:{command}', en: 'Previous page: {command}' },
|
|
23
|
+
nextPage: { 'zh-CN': '下一页:{command}', en: 'Next page: {command}' },
|
|
24
|
+
guideTitle: { 'zh-CN': '{title} · 指令指南', en: '{title} · Command guide' },
|
|
25
|
+
guideSubtitle: { 'zh-CN': '每一个灵感,都有回应。', en: 'An answer to every idea.' },
|
|
26
|
+
commandCount: { 'zh-CN': '{count} 条指令', en: 'Commands: {count}' },
|
|
27
|
+
pluginCount: { 'zh-CN': '{count} 个子插件', en: 'Plugins: {count}' },
|
|
28
|
+
commandIndex: { 'zh-CN': '指令索引', en: 'Commands' },
|
|
29
|
+
pluginIndex: { 'zh-CN': '插件导航', en: 'Plugins' },
|
|
30
|
+
guideEmpty: { 'zh-CN': '暂无指令和子插件', en: 'No commands or plugins' },
|
|
31
|
+
helpHint: { 'zh-CN': '查看参数与用法:{command} --help', en: 'View arguments and usage: {command} --help' },
|
|
32
|
+
genericHelpHint: { 'zh-CN': '在当前环境的指令后追加 --help 查看用法', en: 'Append --help to a command in this environment to view its usage' },
|
|
33
|
+
expectedType: { 'zh-CN': '{field}:应为 {type}', en: '{field}: expected {type}' },
|
|
34
|
+
expectedArray: { 'zh-CN': '{field}:应为 {type} 数组', en: '{field}: expected an array of {type}' },
|
|
35
|
+
minimum: { 'zh-CN': '{field}:最小值为 {minimum}', en: '{field}: minimum {minimum}' },
|
|
36
|
+
maximum: { 'zh-CN': '{field}:最大值为 {maximum}', en: '{field}: maximum {maximum}' },
|
|
37
|
+
choices: { 'zh-CN': '{field}:可选值为 {choices}', en: '{field}: expected one of {choices}' },
|
|
38
|
+
invalidFormat: { 'zh-CN': '{field}:格式不正确', en: '{field}: invalid format' },
|
|
39
|
+
invalidValue: { 'zh-CN': '{field}:值不正确', en: '{field}: invalid value' },
|
|
40
|
+
unknownOption: { 'zh-CN': '未知选项 {field}', en: 'Unknown option {field}' },
|
|
41
|
+
duplicateOption: { 'zh-CN': '重复选项 --{field}', en: 'Duplicate option --{field}' },
|
|
42
|
+
defaultUndefined: { 'zh-CN': '{field}:默认值返回了 undefined', en: '{field}: default returned undefined' },
|
|
43
|
+
parameterUndefined: { 'zh-CN': '{field}:默认值或快捷指令返回了 undefined', en: '{field}: default or shortcut returned undefined' },
|
|
44
|
+
missingOption: { 'zh-CN': '缺少选项 --{field}', en: 'Missing option --{field}' },
|
|
45
|
+
unexpectedArguments: { 'zh-CN': '存在多余参数', en: 'Unexpected arguments' },
|
|
46
|
+
invalidParameter: { 'zh-CN': '缺少指令参数或参数不正确', en: 'Missing or invalid command parameter' },
|
|
47
|
+
defaultValue: { 'zh-CN': '默认值:{value}', en: 'default: {value}' },
|
|
48
|
+
dynamicValue: { 'zh-CN': '动态计算', en: 'dynamic' },
|
|
49
|
+
noAdapter: { 'zh-CN': '命令未声明 adapter 约束', en: 'The command does not declare an adapter restriction' },
|
|
50
|
+
missingEndpoint: { 'zh-CN': '未选择当前 Endpoint,无法判断命令的精确 adapter 约束', en: 'Select an Endpoint to evaluate the command’s exact adapter restriction' },
|
|
51
|
+
adapterComparison: { 'zh-CN': '命令 adapter 与当前 Endpoint 声明的原生适配器精确比较;未探测在线状态', en: 'Exact comparison with the Endpoint’s declared native adapter; online status was not checked' },
|
|
52
|
+
conditionMismatch: { 'zh-CN': '至少一个声明条件与假设输入不符;这不是实际准入检查', en: 'At least one declared condition does not match the hypothetical input; this is not a live admission check' },
|
|
53
|
+
missingPermissionHost: { 'zh-CN': 'PermissionHost 缺失或无法确认;不能推断命令可执行', en: 'PermissionHost is missing or unverified; command execution cannot be inferred' },
|
|
54
|
+
missingEvidence: { 'zh-CN': '存在缺少依据的条件,不能推断命令可执行', en: 'Some conditions lack evidence; command execution cannot be inferred' },
|
|
55
|
+
noPermit: { 'zh-CN': '命令未声明 permit 限制;这不表示已获得执行权限', en: 'The command does not declare permit restrictions; this does not establish authorization' },
|
|
56
|
+
uncheckedConditions: { 'zh-CN': '存在未执行的平台或自定义条件,不能推断命令可执行', en: 'Platform or custom conditions have not been evaluated; command execution cannot be inferred' },
|
|
57
|
+
conditionsMatch: { 'zh-CN': '已解释的 builtin 条件与假设输入匹配;不代表真实授权或命令可执行', en: 'The explained builtin conditions match the hypothetical input; this does not establish authorization or executability' },
|
|
58
|
+
unconstrained: { 'zh-CN': '已解释的 builtin 规则不检查具体 Endpoint、parent 或 thread;保留完整目标不等于这些维度受规则限制', en: 'The explained builtin rules do not constrain a specific Endpoint, parent or thread; retaining the full target does not add such restrictions' },
|
|
59
|
+
simulatedIdentity: { 'zh-CN': '身份与角色仅用于模拟,未向平台核实', en: 'Identity and roles are hypothetical and have not been verified with the platform' },
|
|
60
|
+
unevaluatedCode: { 'zh-CN': '未执行命令、动态默认值、平台或自定义 checker', en: 'Commands, dynamic defaults, platform checks and custom checkers have not been executed' },
|
|
61
|
+
defaultedRole: { 'zh-CN': 'canonical 输入未附角色时按命令运行时默认 user 解释;这不是已核验的平台角色', en: 'Canonical input without roles uses the command runtime’s default user role; this is not a verified platform role' },
|
|
62
|
+
});
|