@whanext/core 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+
4
+ ## 0.11.0 - Command discovery
5
+
6
+ - `CommandRouter.load()` carrega diretórios de comandos diretamente pelo router.
7
+ - `loadCommands()` agora aceita `URL` além de caminhos em string.
8
+ - Autoload reconhece `.ts`, `.mts` e `.cts` por padrão, além de JavaScript.
9
+ - `CommandContext` agora expõe `ctx.commands` (catálogo somente-leitura) e `ctx.prefix`.
10
+ - Menus e comandos de ajuda podem consultar o catálogo sem factory ou referência global ao app.
11
+ - Mantém compatibilidade com `defineCommand`, `defineCommands`, `defineSubcommand` e `defineCommandGroup`.
12
+
3
13
  Todas as mudanças relevantes do projeto serão registradas neste arquivo.
4
14
 
5
15
  ## 0.10.0
package/README.md CHANGED
@@ -566,81 +566,83 @@ Restrições disponíveis:
566
566
 
567
567
  `ArgsParser` possui `string`, `number`, `boolean`, `enum`, `user`, `duration`, `peek`, `skip` e `rest`. `args.user()` retorna `User`, nunca uma string crua.
568
568
 
569
- ### Carregando comandos de uma pasta
569
+ ### Descoberta automática de comandos
570
570
 
571
- `loadCommands` importa e registra todos os comandos de um diretório, sem precisar listar cada arquivo manualmente. Um arquivo pode conter um ou vários comandos:
571
+ A forma recomendada agora é deixar cada arquivo de comando autossuficiente e pedir ao próprio router para descobrir a árvore inteira. Não é necessário manter `index.ts` por pasta nem um arquivo central de imports:
572
572
 
573
573
  ```ts
574
- import { loadCommands } from '@whanext/core';
574
+ const app = await create({ prefix: '&' });
575
575
 
576
- await loadCommands(app.router(), './commands');
576
+ await app.commands.load(new URL('./commands/', import.meta.url));
577
577
  ```
578
578
 
579
- Para um comando por arquivo, continue usando `defineCommand` normalmente:
579
+ O mesmo código funciona em desenvolvimento TypeScript (com um runtime/loader como `tsx`) e depois do build: o loader reconhece `.ts`, `.mts`, `.cts`, `.js`, `.mjs` e `.cjs` por padrão. O diretório é percorrido recursivamente.
580
580
 
581
- ```ts
582
- // commands/ping.js
583
- export default defineCommand({
584
- name: 'ping',
585
- description: 'Responde pong.',
586
- async execute(message) {
587
- await app.message.reply(message, 'pong');
588
- },
589
- });
581
+ ```text
582
+ src/commands/
583
+ ├── admin/
584
+ │ ├── ban.ts
585
+ │ ├── mute.ts
586
+ │ └── warn.ts
587
+ ├── group/
588
+ │ ├── access.ts
589
+ │ └── pin.ts
590
+ └── general/
591
+ ├── menu.ts
592
+ └── profile.ts
590
593
  ```
591
594
 
592
- Para manter comandos relacionados juntos, use `defineCommands`. Esse é o formato recomendado para módulos com vários comandos:
595
+ Um arquivo pode exportar um comando:
593
596
 
594
597
  ```ts
595
- // commands/moderation.ts
596
- import { defineCommand, defineCommands } from '@whanext/core';
597
-
598
- const mute = defineCommand({
599
- name: 'mute',
600
- description: 'Silencia um membro.',
601
- async execute(message, args) {
602
- // ...
603
- },
604
- });
605
-
606
- const unmute = defineCommand({
607
- name: 'unmute',
608
- description: 'Remove o silêncio de um membro.',
609
- async execute(message, args) {
610
- // ...
598
+ export default defineCommand({
599
+ name: 'ping',
600
+ description: 'Responde pong.',
601
+ async execute(ctx) {
602
+ await ctx.reply('pong');
611
603
  },
612
604
  });
613
-
614
- export default defineCommands(mute, unmute);
615
605
  ```
616
606
 
617
- Vários exports nomeados também são descobertos automaticamente:
607
+ Ou vários comandos relacionados no mesmo módulo:
618
608
 
619
609
  ```ts
620
610
  export const mute = defineCommand({ /* ... */ });
621
611
  export const unmute = defineCommand({ /* ... */ });
622
612
  ```
623
613
 
624
- Exports auxiliares, como constantes e metadados, são ignorados quando o módulo contém ao menos um comando válido. Se o mesmo objeto de comando aparecer em uma coleção e em um export nomeado, ele será registrado apenas uma vez.
614
+ Também é possível exportar uma coleção com `defineCommands(...)`. Exports auxiliares são ignorados quando o módulo contém pelo menos um comando válido, e o mesmo objeto não é registrado duas vezes.
625
615
 
626
- Por padrão, `loadCommands` procura arquivos `.js`, `.mjs` e `.cjs`, incluindo subpastas. Extensões como `.ts` não são carregadas por padrão, já que o `import()` dinâmico depende de um loader do TypeScript estar ativo no processo (`tsx`, `ts-node` ou similar); habilite explicitamente quando esse loader existir:
616
+ Para menus dinâmicos, `CommandContext` expõe uma visão somente-leitura do catálogo e o prefixo atual:
627
617
 
628
618
  ```ts
629
- await loadCommands(app.router(), './commands', { extensions: ['.ts'] });
619
+ export default defineCommand({
620
+ name: 'menu',
621
+ description: 'Mostra os comandos.',
622
+ async execute(ctx) {
623
+ const commands = ctx.commands.catalog({ category: 'administração' });
624
+ const lines = commands.map((command) =>
625
+ `${ctx.prefix}${command.path.join(' ')} — ${command.definition.description}`,
626
+ );
627
+
628
+ await ctx.reply(lines.join('\n'));
629
+ },
630
+ });
630
631
  ```
631
632
 
632
- `loadCommands` também aceita qualquer objeto com um método `command()`, não apenas o router retornado por `app.router()`.
633
+ `ctx.commands` fornece `catalog()`, `categories()`, `find()`, `has()`, `size` e `prefix`; ele não expõe detalhes internos do provider.
633
634
 
634
- O retorno informa os arquivos carregados e ignorados, além de cada comando registrado:
635
+ Se for necessário controlar extensões ou recursão:
635
636
 
636
637
  ```ts
637
- const result = await loadCommands(app.router(), './commands');
638
-
639
- console.log(result.loaded); // arquivos importados
640
- console.log(result.skipped); // extensões não habilitadas
641
- console.log(result.commands); // { name, filePath }[]
638
+ await app.commands.load(new URL('./commands/', import.meta.url), {
639
+ recursive: true,
640
+ extensions: ['.ts'],
641
+ });
642
642
  ```
643
643
 
644
+ `loadCommands(registrar, directory, options)` continua disponível como API de baixo nível para registradores customizados. O retorno contém arquivos carregados/ignorados e os comandos descobertos.
645
+
644
646
  ## Cache externo
645
647
 
646
648
  ```ts
package/dist/index.d.ts CHANGED
@@ -438,6 +438,17 @@ declare class MessageService {
438
438
  text(chatId: string, text: string, mentions?: MentionTarget[]): Promise<SentMessage>;
439
439
  }
440
440
 
441
+ interface CommandCatalogView {
442
+ readonly prefix: string;
443
+ readonly size: number;
444
+ catalog(options?: {
445
+ category?: string;
446
+ includeHidden?: boolean;
447
+ }): readonly RegisteredCommand[];
448
+ categories(): readonly string[];
449
+ has(path: string): boolean;
450
+ find(path: string): RegisteredCommand | undefined;
451
+ }
441
452
  interface CommandRuntimeServices {
442
453
  messages: MessageService;
443
454
  media: MediaService;
@@ -466,6 +477,8 @@ interface CommandContext<Schema extends CommandOptionSchema = CommandOptionSchem
466
477
  readonly chat: CommandChatContext;
467
478
  readonly group: CommandGroupContext | undefined;
468
479
  readonly command: RegisteredCommand;
480
+ readonly commands: CommandCatalogView;
481
+ readonly prefix: string;
469
482
  readonly options: ParsedCommandOptions<Schema>;
470
483
  readonly args: ArgsParser;
471
484
  readonly locale: string | undefined;
@@ -584,6 +597,24 @@ declare function defineCommandGroup<const Group extends CommandGroupDefinition>(
584
597
  declare function defineCommands<const Commands extends readonly CommandDefinition[]>(...commands: Commands): Commands;
585
598
  declare function isCommandGroup(definition: CommandDefinition): definition is CommandGroupDefinition;
586
599
 
600
+ interface CommandRegistrar {
601
+ command(definition: CommandDefinition): unknown;
602
+ }
603
+ interface LoadCommandsOptions {
604
+ extensions?: readonly string[];
605
+ recursive?: boolean;
606
+ }
607
+ interface LoadCommandsResult {
608
+ loaded: readonly string[];
609
+ skipped: readonly string[];
610
+ commands: readonly LoadedCommand[];
611
+ }
612
+ interface LoadedCommand {
613
+ name: string;
614
+ filePath: string;
615
+ }
616
+ declare function loadCommands(registrar: CommandRegistrar, dirPath: string | URL, options?: LoadCommandsOptions): Promise<LoadCommandsResult>;
617
+
587
618
  interface RouterOptions {
588
619
  prefix?: string;
589
620
  onError?: (error: WhaNextError, message: Message) => void | Promise<void>;
@@ -606,6 +637,7 @@ declare class CommandRouter {
606
637
  get prefix(): string;
607
638
  get size(): number;
608
639
  command(definition: CommandDefinition): this;
640
+ load(dirPath: string | URL, options?: LoadCommandsOptions): Promise<LoadCommandsResult>;
609
641
  use(middleware: CommandMiddleware): this;
610
642
  onError(handler: CommandErrorHandler): () => void;
611
643
  catalog(options?: CommandCatalogOptions): readonly RegisteredCommand[];
@@ -749,24 +781,6 @@ declare class MemoryCache implements CacheStore {
749
781
  stats(): Readonly<MemoryCacheStats>;
750
782
  }
751
783
 
752
- interface CommandRegistrar {
753
- command(definition: CommandDefinition): unknown;
754
- }
755
- interface LoadCommandsOptions {
756
- extensions?: readonly string[];
757
- recursive?: boolean;
758
- }
759
- interface LoadCommandsResult {
760
- loaded: readonly string[];
761
- skipped: readonly string[];
762
- commands: readonly LoadedCommand[];
763
- }
764
- interface LoadedCommand {
765
- name: string;
766
- filePath: string;
767
- }
768
- declare function loadCommands(registrar: CommandRegistrar, dirPath: string, options?: LoadCommandsOptions): Promise<LoadCommandsResult>;
769
-
770
784
  declare class SqliteMuteStore implements MuteStore {
771
785
  #private;
772
786
  constructor(path?: string);
@@ -777,4 +791,4 @@ declare class SqliteMuteStore implements MuteStore {
777
791
  close(): void;
778
792
  }
779
793
 
780
- export { type AddMuteOptions, type AddMuteResult, type AppEvents, type AppHealth, type AppHealthStatus, ArgsParser, type AudioContent, type BooleanOption, Browser, type CacheOptions, type CacheStore, type CallEvent, type CallStatus, type ChangeResult, type CommandCatalogOptions, type CommandChatContext, type CommandConcurrency, type CommandContext, type CommandCooldown, type CommandDefinition, type CommandErrorHandler, type CommandGroupContext, type CommandGroupDefinition, type CommandGuard, type CommandHelpOptions, type CommandHooks, type CommandLocalization, type CommandMetadata, type CommandMiddleware, type CommandOptionDefinition, type CommandOptionSchema, type CommandOptionValue, type CommandOptionValues, type CommandRegistrar, CommandRouter, type CommandRuntimeServices, type CommandScope, type ConcurrencyStrategy, type ConnectionState, type ConnectionUpdate, type CreateOptions, DeferredReply, type DownloadedMedia, type DurationOption, type EnumOption, type ExecutableCommandDefinition, type GroupAccess, type GroupAddressingMode, type GroupParticipant, type GroupParticipantAction, type GroupParticipantsChanged, type GroupRole, type GroupSnapshot, type GuardResult, type ImageContent, type InviteResult, type LoadCommandsOptions, type LoadCommandsResult, type LoadedCommand, type LogContext, type LogEntry, type LogFormat, type LogLevel, type LogWriter, Logger, type LoggerConfig, type LoggerOptions, type LoginOptions, type MediaKind, type MediaSource, type MemberActionState, MemoryCache, type MemoryCacheStats, type MentionTarget, type Message, type MessageContent, type MessageKey, type MessageMedia, type MuteChangeResult, type MuteEnforcement, type MuteOptions, type MuteRecord, MuteService, type MuteStore, type NumberOption, ParsedCommandOptions, type ParticipantUpdateResult, type PresenceState, type QuotedMessage, type ReconnectOptions, type RegisteredCommand, type RemoveMuteResult, type ReplyOptions, type RouterOptions, type SentMessage, SqliteMuteStore, type StickerContent, type StoredMute, type StringOption, type TextContent, User, type UserData, type UserOption, type VideoContent, WhaNextApp, WhaNextError, type WhaNextErrorCode, type WhatsAppProvider, create, defineCommand, defineCommandGroup, defineCommands, defineSubcommand, guards, isCommandGroup, loadCommands, option, toWhaNextError };
794
+ export { type AddMuteOptions, type AddMuteResult, type AppEvents, type AppHealth, type AppHealthStatus, ArgsParser, type AudioContent, type BooleanOption, Browser, type CacheOptions, type CacheStore, type CallEvent, type CallStatus, type ChangeResult, type CommandCatalogOptions, type CommandCatalogView, type CommandChatContext, type CommandConcurrency, type CommandContext, type CommandCooldown, type CommandDefinition, type CommandErrorHandler, type CommandGroupContext, type CommandGroupDefinition, type CommandGuard, type CommandHelpOptions, type CommandHooks, type CommandLocalization, type CommandMetadata, type CommandMiddleware, type CommandOptionDefinition, type CommandOptionSchema, type CommandOptionValue, type CommandOptionValues, type CommandRegistrar, CommandRouter, type CommandRuntimeServices, type CommandScope, type ConcurrencyStrategy, type ConnectionState, type ConnectionUpdate, type CreateOptions, DeferredReply, type DownloadedMedia, type DurationOption, type EnumOption, type ExecutableCommandDefinition, type GroupAccess, type GroupAddressingMode, type GroupParticipant, type GroupParticipantAction, type GroupParticipantsChanged, type GroupRole, type GroupSnapshot, type GuardResult, type ImageContent, type InviteResult, type LoadCommandsOptions, type LoadCommandsResult, type LoadedCommand, type LogContext, type LogEntry, type LogFormat, type LogLevel, type LogWriter, Logger, type LoggerConfig, type LoggerOptions, type LoginOptions, type MediaKind, type MediaSource, type MemberActionState, MemoryCache, type MemoryCacheStats, type MentionTarget, type Message, type MessageContent, type MessageKey, type MessageMedia, type MuteChangeResult, type MuteEnforcement, type MuteOptions, type MuteRecord, MuteService, type MuteStore, type NumberOption, ParsedCommandOptions, type ParticipantUpdateResult, type PresenceState, type QuotedMessage, type ReconnectOptions, type RegisteredCommand, type RemoveMuteResult, type ReplyOptions, type RouterOptions, type SentMessage, SqliteMuteStore, type StickerContent, type StoredMute, type StringOption, type TextContent, User, type UserData, type UserOption, type VideoContent, WhaNextApp, WhaNextError, type WhaNextErrorCode, type WhatsAppProvider, create, defineCommand, defineCommandGroup, defineCommands, defineSubcommand, guards, isCommandGroup, loadCommands, option, toWhaNextError };
package/dist/index.js CHANGED
@@ -395,6 +395,8 @@ var CommandContextImplementation = class {
395
395
  chat;
396
396
  group;
397
397
  command;
398
+ commands;
399
+ prefix;
398
400
  options;
399
401
  args;
400
402
  locale;
@@ -413,6 +415,8 @@ var CommandContextImplementation = class {
413
415
  this.user = options.message.sender;
414
416
  this.chat = { id: options.message.chatId, isGroup: options.message.isGroup };
415
417
  this.command = options.command;
418
+ this.commands = options.commands;
419
+ this.prefix = options.commands.prefix;
416
420
  this.options = options.options;
417
421
  this.args = options.args;
418
422
  this.locale = options.locale;
@@ -510,6 +514,85 @@ function normalizeContent(content) {
510
514
  return typeof content === "string" ? { text: content } : content;
511
515
  }
512
516
 
517
+ // src/commands/load-commands.ts
518
+ import { readdir } from "fs/promises";
519
+ import path from "path";
520
+ import { fileURLToPath, pathToFileURL } from "url";
521
+ var DEFAULT_EXTENSIONS = [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"];
522
+ async function loadCommands(registrar, dirPath, options = {}) {
523
+ const extensions = options.extensions ?? DEFAULT_EXTENSIONS;
524
+ const recursive = options.recursive ?? true;
525
+ const resolvedDirPath = dirPath instanceof URL ? fileURLToPath(dirPath) : dirPath;
526
+ const entries = await readEntries(resolvedDirPath, recursive);
527
+ const loaded = [];
528
+ const skipped = [];
529
+ const commands = [];
530
+ for (const filePath of entries) {
531
+ if (isDeclarationFile(filePath) || !extensions.includes(path.extname(filePath))) {
532
+ skipped.push(filePath);
533
+ continue;
534
+ }
535
+ const definitions = await importCommands(filePath);
536
+ for (const definition of definitions) {
537
+ registrar.command(definition);
538
+ commands.push({ name: definition.name, filePath });
539
+ }
540
+ loaded.push(filePath);
541
+ }
542
+ return { loaded, skipped, commands };
543
+ }
544
+ async function readEntries(dirPath, recursive) {
545
+ let dirents;
546
+ try {
547
+ dirents = await readdir(dirPath, { recursive, withFileTypes: true });
548
+ } catch (error) {
549
+ throw new WhaNextError("COMMAND_LOAD_FAILED", `Could not read the commands directory "${dirPath}".`, {
550
+ cause: error,
551
+ context: { dirPath }
552
+ });
553
+ }
554
+ return dirents.filter((dirent) => dirent.isFile()).map((dirent) => path.join(dirent.parentPath, dirent.name)).sort((left, right) => left.localeCompare(right));
555
+ }
556
+ async function importCommands(filePath) {
557
+ let module;
558
+ try {
559
+ module = await import(pathToFileURL(filePath).href);
560
+ } catch (error) {
561
+ throw new WhaNextError("COMMAND_LOAD_FAILED", `Could not import the command file "${filePath}".`, {
562
+ cause: error,
563
+ context: { filePath }
564
+ });
565
+ }
566
+ const definitions = [];
567
+ const seen = /* @__PURE__ */ new Set();
568
+ const exports = [
569
+ module.default,
570
+ ...Object.entries(module).filter(([name]) => name !== "default").map(([, value]) => value)
571
+ ];
572
+ for (const value of exports) {
573
+ const candidates = Array.isArray(value) ? value : [value];
574
+ for (const candidate of candidates) {
575
+ if (isCommandDefinition(candidate) && !seen.has(candidate)) {
576
+ seen.add(candidate);
577
+ definitions.push(candidate);
578
+ }
579
+ }
580
+ }
581
+ if (definitions.length === 0) {
582
+ throw new WhaNextError("COMMAND_LOAD_FAILED", `The file "${filePath}" does not export any valid commands.`, {
583
+ context: { filePath }
584
+ });
585
+ }
586
+ return definitions;
587
+ }
588
+ function isCommandDefinition(value) {
589
+ const candidate = value;
590
+ return typeof value === "object" && value !== null && typeof candidate.name === "string" && (typeof candidate.execute === "function" || Array.isArray(candidate.subcommands));
591
+ }
592
+ function isDeclarationFile(filePath) {
593
+ return /\.d\.(?:ts|mts|cts)$/.test(filePath);
594
+ }
595
+
513
596
  // src/commands/options.ts
514
597
  var option = {
515
598
  string(definition) {
@@ -705,6 +788,9 @@ var CommandRouter = class {
705
788
  this.#definitions.add(definition);
706
789
  return this;
707
790
  }
791
+ load(dirPath, options = {}) {
792
+ return loadCommands(this, dirPath, options);
793
+ }
708
794
  use(middleware) {
709
795
  this.#globalMiddleware.push(middleware);
710
796
  return this;
@@ -778,6 +864,7 @@ _Nenhum comando dispon\xEDvel._`;
778
864
  options: new ParsedCommandOptions({}),
779
865
  args: new ArgsParser(tokens),
780
866
  services: this.#services,
867
+ commands: this,
781
868
  signal: new AbortController().signal,
782
869
  ...root.locale ? { locale: root.locale } : {}
783
870
  });
@@ -820,6 +907,7 @@ _Nenhum comando dispon\xEDvel._`;
820
907
  options: parsedOptions,
821
908
  args: legacyArgs,
822
909
  services: this.#services,
910
+ commands: this,
823
911
  signal,
824
912
  ...resolved.locale ? { locale: resolved.locale } : {}
825
913
  });
@@ -840,6 +928,7 @@ _Nenhum comando dispon\xEDvel._`;
840
928
  options: new ParsedCommandOptions({}),
841
929
  args: legacyArgs,
842
930
  services: this.#services,
931
+ commands: this,
843
932
  signal: new AbortController().signal,
844
933
  ...resolved.locale ? { locale: resolved.locale } : {}
845
934
  });
@@ -2890,81 +2979,6 @@ var guards = {
2890
2979
  return guard;
2891
2980
  }
2892
2981
  };
2893
-
2894
- // src/commands/load-commands.ts
2895
- import { readdir } from "fs/promises";
2896
- import path from "path";
2897
- import { pathToFileURL } from "url";
2898
- var DEFAULT_EXTENSIONS = [".js", ".mjs", ".cjs"];
2899
- async function loadCommands(registrar, dirPath, options = {}) {
2900
- const extensions = options.extensions ?? DEFAULT_EXTENSIONS;
2901
- const recursive = options.recursive ?? true;
2902
- const entries = await readEntries(dirPath, recursive);
2903
- const loaded = [];
2904
- const skipped = [];
2905
- const commands = [];
2906
- for (const filePath of entries) {
2907
- if (!extensions.includes(path.extname(filePath))) {
2908
- skipped.push(filePath);
2909
- continue;
2910
- }
2911
- const definitions = await importCommands(filePath);
2912
- for (const definition of definitions) {
2913
- registrar.command(definition);
2914
- commands.push({ name: definition.name, filePath });
2915
- }
2916
- loaded.push(filePath);
2917
- }
2918
- return { loaded, skipped, commands };
2919
- }
2920
- async function readEntries(dirPath, recursive) {
2921
- let dirents;
2922
- try {
2923
- dirents = await readdir(dirPath, { recursive, withFileTypes: true });
2924
- } catch (error) {
2925
- throw new WhaNextError("COMMAND_LOAD_FAILED", `Could not read the commands directory "${dirPath}".`, {
2926
- cause: error,
2927
- context: { dirPath }
2928
- });
2929
- }
2930
- return dirents.filter((dirent) => dirent.isFile()).map((dirent) => path.join(dirent.parentPath, dirent.name)).sort((left, right) => left.localeCompare(right));
2931
- }
2932
- async function importCommands(filePath) {
2933
- let module;
2934
- try {
2935
- module = await import(pathToFileURL(filePath).href);
2936
- } catch (error) {
2937
- throw new WhaNextError("COMMAND_LOAD_FAILED", `Could not import the command file "${filePath}".`, {
2938
- cause: error,
2939
- context: { filePath }
2940
- });
2941
- }
2942
- const definitions = [];
2943
- const seen = /* @__PURE__ */ new Set();
2944
- const exports = [
2945
- module.default,
2946
- ...Object.entries(module).filter(([name]) => name !== "default").map(([, value]) => value)
2947
- ];
2948
- for (const value of exports) {
2949
- const candidates = Array.isArray(value) ? value : [value];
2950
- for (const candidate of candidates) {
2951
- if (isCommandDefinition(candidate) && !seen.has(candidate)) {
2952
- seen.add(candidate);
2953
- definitions.push(candidate);
2954
- }
2955
- }
2956
- }
2957
- if (definitions.length === 0) {
2958
- throw new WhaNextError("COMMAND_LOAD_FAILED", `The file "${filePath}" does not export any valid commands.`, {
2959
- context: { filePath }
2960
- });
2961
- }
2962
- return definitions;
2963
- }
2964
- function isCommandDefinition(value) {
2965
- const candidate = value;
2966
- return typeof value === "object" && value !== null && typeof candidate.name === "string" && (typeof candidate.execute === "function" || Array.isArray(candidate.subcommands));
2967
- }
2968
2982
  export {
2969
2983
  ArgsParser,
2970
2984
  Browser,