@zhin.js/command 1.1.14 → 1.1.16

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.
@@ -1,6 +1,6 @@
1
1
  import type { CommandDefinition, CommandParams, CommandOptions, CommandParameterValue, CommandSegment, CommandSession } from './definition.js';
2
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';
3
+ export type CommandValidationIssueCode = 'expectedType' | 'expectedArray' | 'minimum' | 'maximum' | 'choices' | 'invalidFormat' | 'invalidValue' | 'unknownOption' | 'duplicateOption' | 'defaultUndefined' | 'parameterUndefined' | 'missingOption' | 'missingRequiredParam' | 'unexpectedArguments' | 'invalidParameter' | 'custom';
4
4
  export interface CommandValidationIssue {
5
5
  readonly field: string;
6
6
  readonly message: string;
package/lib/arguments.js CHANGED
@@ -105,7 +105,7 @@ export function requestsCommandHelp(segments) {
105
105
  continue;
106
106
  if (token.value === '--')
107
107
  return false;
108
- if (token.value === '--help')
108
+ if (token.value === '--help' || token.value === '-h')
109
109
  return true;
110
110
  }
111
111
  return false;
@@ -300,7 +300,7 @@ export function parseCommandArguments(definition, remaining, initial, session, l
300
300
  else if (schema.default !== undefined)
301
301
  params[name] = typeof schema.default === 'function' ? schema.default(session) : schema.default;
302
302
  else if (!schema.optional && !(definition.$parameter?.name === name && definition.$parameter.optional)) {
303
- issues.push(commandValidationIssue('expectedType', name, locale, { type: schema.type }));
303
+ issues.push(commandValidationIssue('missingRequiredParam', name, locale, { type: schema.type }));
304
304
  }
305
305
  }
306
306
  if (Object.hasOwn(params, name) && params[name] === undefined)
@@ -1,5 +1,5 @@
1
1
  import { type CapabilityId, type CapabilitySlot, type PluginId, type RuntimeSnapshot, type Locale } from '@zhin.js/plugin-runtime';
2
- import type { UserInteractionFactory } from '@zhin.js/interaction';
2
+ import type { UserInteractionFactory } from '@zhin.js/im-contract';
3
3
  import { type CapabilityOperationFactory } from '@zhin.js/feature-kit';
4
4
  import { type CommandValidationIssue } from './arguments.js';
5
5
  import { type CommandMenuEntry, type CommandMenuAppearance } from './command-menu.js';
@@ -21,6 +21,7 @@ const segmentFields = {
21
21
  dice: 'result',
22
22
  rps: 'result',
23
23
  };
24
+ const DEFAULT_MENU_KEYWORDS = new Set(['菜单', '帮助', 'help', 'menu']);
24
25
  export class CommandIndex {
25
26
  snapshot;
26
27
  $projection = 'zhin.command-index/1';
@@ -384,10 +385,19 @@ export class CommandIndex {
384
385
  const text = typeof input === 'string' ? input.trim() : exactMessageText(input);
385
386
  if (text === undefined)
386
387
  return undefined;
387
- if (text === keyword)
388
+ const spaceIndex = text.search(/\s/u);
389
+ const firstToken = spaceIndex === -1 ? text : text.slice(0, spaceIndex);
390
+ const normalizedFirst = !commandPrefix && firstToken.startsWith('/')
391
+ ? firstToken.slice(1)
392
+ : firstToken;
393
+ const matchedDefaultAlias = this.#menu?.keyword === undefined
394
+ && DEFAULT_MENU_KEYWORDS.has(normalizedFirst.toLowerCase());
395
+ if (text === keyword || (matchedDefaultAlias && spaceIndex === -1))
388
396
  return this.#buildMenu({}, context);
389
- if (text.startsWith(keyword + ' ')) {
390
- const key = text.slice(keyword.length + 1).trim();
397
+ if (text.startsWith(keyword + ' ') || (matchedDefaultAlias && spaceIndex !== -1)) {
398
+ const key = (text.startsWith(keyword + ' ')
399
+ ? text.slice(keyword.length + 1)
400
+ : text.slice(spaceIndex + 1)).trim();
391
401
  const args = key.split(/\s+/u);
392
402
  const flag = args.indexOf('--page');
393
403
  if (flag !== -1) {
@@ -416,17 +426,23 @@ export class CommandIndex {
416
426
  const children = node.children
417
427
  .map((childId) => snapshot.tree.get(childId))
418
428
  .filter((child) => !!child);
419
- const pages = Math.max(1, Math.ceil((directCommands.length + children.length) / MENU_PAGE_SIZE));
429
+ const effectiveCommands = targetId === root
430
+ && directCommands.length === 0
431
+ && this.#commands.length > 0
432
+ && this.#commands.length <= MENU_PAGE_SIZE
433
+ ? this.#commands
434
+ : directCommands;
435
+ const pages = Math.max(1, Math.ceil((effectiveCommands.length + children.length) / MENU_PAGE_SIZE));
420
436
  const invocation = `${commandPrefix}${keyword}${pluginKey ? ` ${pluginKey}` : ''}`;
421
437
  if (page > pages)
422
438
  return { value: commandMessages(locale, 'pageOutOfRange', { pages, command: invocation }) };
423
439
  const offset = (page - 1) * MENU_PAGE_SIZE;
424
- const pageCommands = directCommands.slice(offset, offset + MENU_PAGE_SIZE);
425
- const pageChildren = children.slice(Math.max(0, offset - directCommands.length), Math.max(0, offset + MENU_PAGE_SIZE - directCommands.length));
440
+ const pageCommands = effectiveCommands.slice(offset, offset + MENU_PAGE_SIZE);
441
+ const pageChildren = children.slice(Math.max(0, offset - effectiveCommands.length), Math.max(0, offset + MENU_PAGE_SIZE - effectiveCommands.length));
426
442
  const displayKey = pluginKey ?? node.instanceKey;
427
443
  const lines = [commandMessages(locale, 'menuTitle', { plugin: displayKey })];
428
444
  if (pages > 1)
429
- lines.push(commandMessages(locale, 'menuSummary', { page, pages, commands: directCommands.length, plugins: children.length }));
445
+ lines.push(commandMessages(locale, 'menuSummary', { page, pages, commands: effectiveCommands.length, plugins: children.length }));
430
446
  if (pageCommands.length > 0) {
431
447
  lines.push('');
432
448
  for (const cmd of pageCommands) {
@@ -444,7 +460,7 @@ export class CommandIndex {
444
460
  lines.push(` ${childKey} (${label})`);
445
461
  }
446
462
  }
447
- if (directCommands.length === 0 && children.length === 0) {
463
+ if (effectiveCommands.length === 0 && children.length === 0) {
448
464
  lines.push('');
449
465
  lines.push(commandMessages(locale, 'emptyMenu'));
450
466
  }
@@ -471,7 +487,7 @@ export class CommandIndex {
471
487
  name: pluginKey ? `${pluginKey}.${child.instanceKey}` : child.instanceKey,
472
488
  description: child.metadata?.displayName ?? child.instanceKey,
473
489
  }))),
474
- commandTotal: directCommands.length, pluginTotal: children.length, hint, text,
490
+ commandTotal: effectiveCommands.length, pluginTotal: children.length, hint, text,
475
491
  appearance: Object.freeze(readMenuAppearance(config?.menuStyle)),
476
492
  });
477
493
  if (!snapshot.resources.get(root)?.has(htmlRendererToken.id))
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { type Locale, type PluginId, type RuntimeSnapshot } from '@zhin.js/plugin-runtime';
6
6
  import { type CapabilityOperation, type OperationSession, type AdapterClient, type RegisteredAdapterName, type CapabilityContext } from '@zhin.js/feature-kit';
7
- import type { UserInteraction } from '@zhin.js/interaction';
7
+ import type { UserInteraction } from '@zhin.js/im-contract';
8
8
  import { type CommandExecutionPolicy } from './execution-policy.js';
9
9
  export type { CommandExecutionPolicy, CommandExecutionLimit } from './execution-policy.js';
10
10
  declare const commandBrand: "zhin.command/1";
package/lib/messages.d.ts CHANGED
@@ -5,4 +5,4 @@ export declare function commandDescription(value: Readonly<{
5
5
  descriptionI18n?: Readonly<Partial<Record<Locale, string>>>;
6
6
  }>, locale: Locale): string | undefined;
7
7
  /** Static dictionaries; the caller supplies the operation's language. */
8
- export declare const commandMessages: (locale: Locale | undefined, key: "choices" | "defaultValue" | "menuKeyword" | "invalidPage" | "missingPlugin" | "pageOutOfRange" | "menuTitle" | "menuSummary" | "childPlugins" | "emptyMenu" | "pluginHint" | "previousPage" | "nextPage" | "guideTitle" | "guideSubtitle" | "commandCount" | "pluginCount" | "commandIndex" | "pluginIndex" | "guideEmpty" | "helpHint" | "genericHelpHint" | "expectedType" | "expectedArray" | "minimum" | "maximum" | "invalidFormat" | "invalidValue" | "unknownOption" | "duplicateOption" | "defaultUndefined" | "parameterUndefined" | "missingOption" | "unexpectedArguments" | "invalidParameter" | "dynamicValue" | "noAdapter" | "missingEndpoint" | "adapterComparison" | "conditionMismatch" | "missingPermissionHost" | "missingEvidence" | "noPermit" | "uncheckedConditions" | "conditionsMatch" | "unconstrained" | "simulatedIdentity" | "unevaluatedCode" | "defaultedRole", values?: import("@zhin.js/plugin-runtime").MessageValues) => string;
8
+ export declare const commandMessages: (locale: Locale | undefined, key: "choices" | "defaultValue" | "menuKeyword" | "invalidPage" | "missingPlugin" | "pageOutOfRange" | "menuTitle" | "menuSummary" | "childPlugins" | "emptyMenu" | "pluginHint" | "previousPage" | "nextPage" | "guideTitle" | "guideSubtitle" | "commandCount" | "pluginCount" | "commandIndex" | "pluginIndex" | "guideEmpty" | "helpHint" | "genericHelpHint" | "expectedType" | "expectedArray" | "minimum" | "maximum" | "invalidFormat" | "invalidValue" | "unknownOption" | "duplicateOption" | "defaultUndefined" | "parameterUndefined" | "missingOption" | "missingRequiredParam" | "unexpectedArguments" | "invalidParameter" | "dynamicValue" | "noAdapter" | "missingEndpoint" | "adapterComparison" | "conditionMismatch" | "missingPermissionHost" | "missingEvidence" | "noPermit" | "uncheckedConditions" | "conditionsMatch" | "unconstrained" | "simulatedIdentity" | "unevaluatedCode" | "defaultedRole", values?: import("@zhin.js/plugin-runtime").MessageValues) => string;
package/lib/messages.js CHANGED
@@ -37,6 +37,7 @@ export const commandMessages = createMessages({
37
37
  defaultUndefined: { 'zh-CN': '{field}:默认值返回了 undefined', en: '{field}: default returned undefined' },
38
38
  parameterUndefined: { 'zh-CN': '{field}:默认值或快捷指令返回了 undefined', en: '{field}: default or shortcut returned undefined' },
39
39
  missingOption: { 'zh-CN': '缺少选项 --{field}', en: 'Missing option --{field}' },
40
+ missingRequiredParam: { 'zh-CN': '缺少必填参数 {field}(类型:{type})', en: 'Missing required parameter {field} (expected {type})' },
40
41
  unexpectedArguments: { 'zh-CN': '存在多余参数', en: 'Unexpected arguments' },
41
42
  invalidParameter: { 'zh-CN': '缺少指令参数或参数不正确', en: 'Missing or invalid command parameter' },
42
43
  defaultValue: { 'zh-CN': '默认值:{value}', en: 'default: {value}' },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/command",
3
- "version": "1.1.14",
3
+ "version": "1.1.16",
4
4
  "description": "Convention-based Command Feature for Zhin Plugin Runtime",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -21,10 +21,10 @@
21
21
  ],
22
22
  "dependencies": {
23
23
  "segment-matcher": "^1.0.5",
24
- "@zhin.js/feature-kit": "1.1.12",
25
- "@zhin.js/interaction": "1.1.2",
26
- "@zhin.js/permission": "1.1.12",
27
- "@zhin.js/plugin-runtime": "1.1.20"
24
+ "@zhin.js/feature-kit": "1.1.14",
25
+ "@zhin.js/im-contract": "1.1.13",
26
+ "@zhin.js/permission": "1.1.14",
27
+ "@zhin.js/plugin-runtime": "1.1.22"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@types/node": "^26.1.2",
package/src/arguments.ts CHANGED
@@ -7,7 +7,7 @@ import { commandDescription, commandMessages } from './messages.js';
7
7
 
8
8
  export type CommandValidationIssueCode = 'expectedType' | 'expectedArray' | 'minimum' | 'maximum'
9
9
  | 'choices' | 'invalidFormat' | 'invalidValue' | 'unknownOption' | 'duplicateOption'
10
- | 'defaultUndefined' | 'parameterUndefined' | 'missingOption' | 'unexpectedArguments'
10
+ | 'defaultUndefined' | 'parameterUndefined' | 'missingOption' | 'missingRequiredParam' | 'unexpectedArguments'
11
11
  | 'invalidParameter' | 'custom';
12
12
 
13
13
  export interface CommandValidationIssue {
@@ -136,7 +136,7 @@ export function requestsCommandHelp(segments: readonly Readonly<CommandSegment>[
136
136
  for (const token of tokenize(segments)) {
137
137
  if (!isTextToken(token) || token.quoted) continue;
138
138
  if (token.value === '--') return false;
139
- if (token.value === '--help') return true;
139
+ if (token.value === '--help' || token.value === '-h') return true;
140
140
  }
141
141
  return false;
142
142
  }
@@ -280,7 +280,7 @@ export function parseCommandArguments(
280
280
  if (value !== undefined) params[name] = value;
281
281
  else if (schema.default !== undefined) params[name] = typeof schema.default === 'function' ? schema.default(session) : schema.default;
282
282
  else if (!schema.optional && !(definition.$parameter?.name === name && definition.$parameter.optional)) {
283
- issues.push(commandValidationIssue('expectedType', name, locale, { type: schema.type }));
283
+ issues.push(commandValidationIssue('missingRequiredParam', name, locale, { type: schema.type }));
284
284
  }
285
285
  }
286
286
  if (Object.hasOwn(params, name) && params[name] === undefined) issues.push(commandValidationIssue('parameterUndefined', name, locale));
@@ -12,7 +12,7 @@ import {
12
12
  type RuntimeSnapshot,
13
13
  type Locale,
14
14
  } from '@zhin.js/plugin-runtime';
15
- import type { UserInteractionFactory } from '@zhin.js/interaction';
15
+ import type { UserInteractionFactory } from '@zhin.js/im-contract';
16
16
  import { bindOperationInput, type CapabilityOperationFactory } from '@zhin.js/feature-kit';
17
17
  import {
18
18
  permissionHostToken,
@@ -162,6 +162,8 @@ export interface CommandMenuConfig {
162
162
  readonly keyword?: string;
163
163
  }
164
164
 
165
+ const DEFAULT_MENU_KEYWORDS = new Set(['菜单', '帮助', 'help', 'menu']);
166
+
165
167
  export class CommandIndex {
166
168
  readonly $projection = 'zhin.command-index/1' as const;
167
169
  readonly #commands: readonly CommandRecord[];
@@ -523,9 +525,18 @@ export class CommandIndex {
523
525
  const { commandPrefix, keyword, locale } = context;
524
526
  const text = typeof input === 'string' ? input.trim() : exactMessageText(input);
525
527
  if (text === undefined) return undefined;
526
- if (text === keyword) return this.#buildMenu({}, context);
527
- if (text.startsWith(keyword + ' ')) {
528
- const key = text.slice(keyword.length + 1).trim();
528
+ const spaceIndex = text.search(/\s/u);
529
+ const firstToken = spaceIndex === -1 ? text : text.slice(0, spaceIndex);
530
+ const normalizedFirst = !commandPrefix && firstToken.startsWith('/')
531
+ ? firstToken.slice(1)
532
+ : firstToken;
533
+ const matchedDefaultAlias = this.#menu?.keyword === undefined
534
+ && DEFAULT_MENU_KEYWORDS.has(normalizedFirst.toLowerCase());
535
+ if (text === keyword || (matchedDefaultAlias && spaceIndex === -1)) return this.#buildMenu({}, context);
536
+ if (text.startsWith(keyword + ' ') || (matchedDefaultAlias && spaceIndex !== -1)) {
537
+ const key = (text.startsWith(keyword + ' ')
538
+ ? text.slice(keyword.length + 1)
539
+ : text.slice(spaceIndex + 1)).trim();
529
540
  const args = key.split(/\s+/u);
530
541
  const flag = args.indexOf('--page');
531
542
  if (flag !== -1) {
@@ -555,17 +566,23 @@ export class CommandIndex {
555
566
  const children = node.children
556
567
  .map((childId) => snapshot.tree.get(childId))
557
568
  .filter((child): child is NonNullable<typeof child> => !!child);
558
-
559
- const pages = Math.max(1, Math.ceil((directCommands.length + children.length) / MENU_PAGE_SIZE));
569
+ const effectiveCommands = targetId === root
570
+ && directCommands.length === 0
571
+ && this.#commands.length > 0
572
+ && this.#commands.length <= MENU_PAGE_SIZE
573
+ ? this.#commands
574
+ : directCommands;
575
+
576
+ const pages = Math.max(1, Math.ceil((effectiveCommands.length + children.length) / MENU_PAGE_SIZE));
560
577
  const invocation = `${commandPrefix}${keyword}${pluginKey ? ` ${pluginKey}` : ''}`;
561
578
  if (page > pages) return { value: commandMessages(locale, 'pageOutOfRange', { pages, command: invocation }) };
562
579
  const offset = (page - 1) * MENU_PAGE_SIZE;
563
- const pageCommands = directCommands.slice(offset, offset + MENU_PAGE_SIZE);
564
- const pageChildren = children.slice(Math.max(0, offset - directCommands.length), Math.max(0, offset + MENU_PAGE_SIZE - directCommands.length));
580
+ const pageCommands = effectiveCommands.slice(offset, offset + MENU_PAGE_SIZE);
581
+ const pageChildren = children.slice(Math.max(0, offset - effectiveCommands.length), Math.max(0, offset + MENU_PAGE_SIZE - effectiveCommands.length));
565
582
 
566
583
  const displayKey = pluginKey ?? node.instanceKey;
567
584
  const lines: string[] = [commandMessages(locale, 'menuTitle', { plugin: displayKey })];
568
- if (pages > 1) lines.push(commandMessages(locale, 'menuSummary', { page, pages, commands: directCommands.length, plugins: children.length }));
585
+ if (pages > 1) lines.push(commandMessages(locale, 'menuSummary', { page, pages, commands: effectiveCommands.length, plugins: children.length }));
569
586
 
570
587
  if (pageCommands.length > 0) {
571
588
  lines.push('');
@@ -586,7 +603,7 @@ export class CommandIndex {
586
603
  }
587
604
  }
588
605
 
589
- if (directCommands.length === 0 && children.length === 0) {
606
+ if (effectiveCommands.length === 0 && children.length === 0) {
590
607
  lines.push('');
591
608
  lines.push(commandMessages(locale, 'emptyMenu'));
592
609
  }
@@ -612,7 +629,7 @@ export class CommandIndex {
612
629
  name: pluginKey ? `${pluginKey}.${child.instanceKey}` : child.instanceKey,
613
630
  description: child.metadata?.displayName ?? child.instanceKey,
614
631
  }))),
615
- commandTotal: directCommands.length, pluginTotal: children.length, hint, text,
632
+ commandTotal: effectiveCommands.length, pluginTotal: children.length, hint, text,
616
633
  appearance: Object.freeze(readMenuAppearance(config?.menuStyle)),
617
634
  });
618
635
  if (!snapshot.resources.get(root)?.has(htmlRendererToken.id)) return { value: text, menu };
package/src/definition.ts CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  } from '@zhin.js/feature-kit';
17
17
  import { assertPermitSyntax } from '@zhin.js/permission';
18
18
  import { validateArgumentSchemas } from './arguments.js';
19
- import type { UserInteraction } from '@zhin.js/interaction';
19
+ import type { UserInteraction } from '@zhin.js/im-contract';
20
20
  import { normalizeExecutionPolicy, type CommandExecutionPolicy } from './execution-policy.js';
21
21
  export type { CommandExecutionPolicy, CommandExecutionLimit } from './execution-policy.js';
22
22
 
package/src/messages.ts CHANGED
@@ -42,6 +42,7 @@ export const commandMessages = createMessages({
42
42
  defaultUndefined: { 'zh-CN': '{field}:默认值返回了 undefined', en: '{field}: default returned undefined' },
43
43
  parameterUndefined: { 'zh-CN': '{field}:默认值或快捷指令返回了 undefined', en: '{field}: default or shortcut returned undefined' },
44
44
  missingOption: { 'zh-CN': '缺少选项 --{field}', en: 'Missing option --{field}' },
45
+ missingRequiredParam: { 'zh-CN': '缺少必填参数 {field}(类型:{type})', en: 'Missing required parameter {field} (expected {type})' },
45
46
  unexpectedArguments: { 'zh-CN': '存在多余参数', en: 'Unexpected arguments' },
46
47
  invalidParameter: { 'zh-CN': '缺少指令参数或参数不正确', en: 'Missing or invalid command parameter' },
47
48
  defaultValue: { 'zh-CN': '默认值:{value}', en: 'default: {value}' },