@qodalis/cli-core 2.0.0-beta.17 → 2.0.0-beta.18

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/public-api.d.ts CHANGED
@@ -22,9 +22,18 @@ type CliProcessCommand = {
22
22
  */
23
23
  rawCommand: string;
24
24
  /**
25
- * The value of the command
25
+ * The value of the command: the text after the command (and sub-command)
26
+ * words, flags removed, as typed (quotes included). Prefer `positionals`
27
+ * to read arguments.
26
28
  */
27
29
  value?: string;
30
+ /**
31
+ * The positional arguments, split like a shell: quotes removed
32
+ * (`"my file.txt"` is one argument), backslash escapes applied, flags
33
+ * excluded. `cat "a b.txt" c.txt` has `['a b.txt', 'c.txt']`. Set by the
34
+ * engine for every command; empty when there are none.
35
+ */
36
+ positionals?: string[];
28
37
  /**
29
38
  * The arguments that were entered
30
39
  */
@@ -366,6 +375,11 @@ interface ICliCommandExecutorService {
366
375
  * @param context The context in which the command is executed
367
376
  */
368
377
  executeCommand(command: string, context: ICliExecutionContext): Promise<void>;
378
+ /**
379
+ * Run a command line and return its output as text instead of showing it
380
+ * (as `$(...)` does); its errors and warnings are still shown.
381
+ */
382
+ capture?(command: string, context: ICliExecutionContext): Promise<string>;
369
383
  /**
370
384
  * Register a global parameter handler.
371
385
  * Global parameters (e.g. --help, --version) are evaluated for every
@@ -539,8 +553,22 @@ interface ICliKeyValueStore {
539
553
  *
540
554
  * State is a plain object (`Record<string, any>`). Updates are shallow-merged
541
555
  * (top-level keys are replaced, nested objects are not deep-merged).
542
- * `getState()` returns a snapshot — mutating the returned object has no effect;
543
- * always use `updateState()` to change state.
556
+ * `getState()` returns a shallow copy: assigning its top-level keys has no
557
+ * effect, and nested objects must not be mutated in place either; always use
558
+ * `updateState()` to change state.
559
+ *
560
+ * Saved state is loaded once, before the store's command first runs (or by
561
+ * `initialize()`), and merged over the initial state, so keys added to the
562
+ * initial state later are present. Changes are saved with `persist()`, or
563
+ * after every change with `autoPersist`.
564
+ *
565
+ * A store is global by default: one saved state shared by every terminal and
566
+ * tab of the browser. A save writes only the top-level keys this terminal
567
+ * changed (merged into what is saved, under a cross-tab lock), so terminals
568
+ * changing different keys never overwrite each other; for the same key the
569
+ * last save wins. Changes saved elsewhere are applied here, except keys
570
+ * changed here and not saved yet. A store with `scope: 'session'` belongs to
571
+ * one terminal and is never saved.
544
572
  */
545
573
  interface ICliStateStore {
546
574
  /**
@@ -555,6 +583,17 @@ interface ICliStateStore {
555
583
  * @param newState Partial state to merge with the current state
556
584
  */
557
585
  updateState(newState: Partial<CliState>): void;
586
+ /**
587
+ * Change the state from its latest saved value and save it, atomically
588
+ * (under a lock shared by all tabs): use it for read-modify-write changes
589
+ * such as adding an item to a list, so terminals changing the same key at
590
+ * once never lose each other's change.
591
+ *
592
+ * ```ts
593
+ * await context.state.update((s) => ({ todos: [...s.todos, todo] }));
594
+ * ```
595
+ */
596
+ update(change: (state: CliState) => Partial<CliState>): Promise<void>;
558
597
  /**
559
598
  * Select a specific property or computed value from the state.
560
599
  * @param selector A function to project a slice of the state.
@@ -572,13 +611,16 @@ interface ICliStateStore {
572
611
  */
573
612
  reset(): void;
574
613
  /**
575
- * Persist the state to storage.
614
+ * Persist the state to storage (does nothing when no storage is available).
576
615
  */
577
616
  persist(): Promise<void>;
578
617
  /**
579
- * Initialize the state from storage.
618
+ * Load the saved state, once: later calls resolve when that first load
619
+ * is done and keep changes made since.
580
620
  */
581
621
  initialize(): Promise<void>;
622
+ /** Read the saved state again, replacing the current state. */
623
+ reload?(): Promise<void>;
582
624
  }
583
625
 
584
626
  /** Ownership metadata attached to files, directories, or other resources */
@@ -593,96 +635,159 @@ declare const DEFAULT_FILE_PERMISSIONS = "rw-r--r--";
593
635
  /** Default directory permissions: rwxr-xr-x (755) */
594
636
  declare const DEFAULT_DIR_PERMISSIONS = "rwxr-xr-x";
595
637
 
638
+ /** A single audit log entry */
639
+ interface ICliAuditEntry {
640
+ /** Entry id (server-assigned in server mode) */
641
+ id?: string;
642
+ /** When the event occurred */
643
+ timestamp: number;
644
+ /** Event type (e.g., 'login', 'logout', 'login_failed') */
645
+ event: string;
646
+ /** Username involved */
647
+ username: string;
648
+ /** Optional additional details */
649
+ details?: string;
650
+ /** Id of the user the entry is about, when known */
651
+ userId?: string;
652
+ /** The user who performed the action, when different from `username` */
653
+ actor?: string;
654
+ /** Id of the acting user, when different from the subject */
655
+ actorId?: string;
656
+ /** Extra data (e.g. remote address) */
657
+ metadata?: Record<string, unknown>;
658
+ }
659
+ /** Service for persistent audit logging */
660
+ interface ICliAuditService {
661
+ /** Log an audit entry (timestamp is added automatically) */
662
+ log(entry: Omit<ICliAuditEntry, 'timestamp'>): Promise<void>;
663
+ /** Retrieve audit entries with optional filtering */
664
+ getEntries(options?: {
665
+ limit?: number;
666
+ event?: string;
667
+ username?: string;
668
+ }): Promise<ICliAuditEntry[]>;
669
+ /** Clear all audit entries */
670
+ clear(): Promise<void>;
671
+ }
672
+
596
673
  /**
597
- * Token for the CLI user session service.
598
- */
599
- declare const ICliUserSessionService_TOKEN = "cli-user-session-service";
600
- /**
601
- * Token for the CLI users store service.
602
- */
603
- declare const ICliUsersStoreService_TOKEN = "cli-users-store-service";
604
- /**
605
- * Token for the CLI groups store service.
606
- */
607
- declare const ICliGroupsStoreService_TOKEN = "cli-groups-store-service";
608
- /**
609
- * Token for the CLI auth service.
610
- */
611
- declare const ICliAuthService_TOKEN = "cli-auth-service";
612
- /**
613
- * Token for registering completion providers via module services (multi: true).
614
- */
615
- declare const ICliCompletionProvider_TOKEN = "cli-completion-provider";
616
- /**
617
- * Token for the drag-and-drop service.
618
- */
619
- declare const ICliDragDropService_TOKEN = "cli-drag-drop-service";
620
- /**
621
- * Token for the file transfer service.
622
- */
623
- declare const ICliFileTransferService_TOKEN = "cli-file-transfer-service";
624
- /**
625
- * Token for the translation service.
626
- */
627
- declare const ICliTranslationService_TOKEN = "cli-translation-service";
628
- /**
629
- * Token for the permission service.
630
- */
631
- declare const ICliPermissionService_TOKEN = "cli-permission-service";
632
- /**
633
- * Token for the file picker provider.
634
- */
635
- declare const ICliFilePickerProvider_TOKEN = "cli-file-picker-provider";
636
- /**
637
- * Token for the HTTP client service.
638
- */
639
- declare const ICliHttpClient_TOKEN = "cli-http-client";
640
- /**
641
- * Token for the user event service (lifecycle events).
674
+ * A permission an extension uses, declared so tools (`perms --list`,
675
+ * `groupmod --grant` completion, docs) can show it. Declaring is optional:
676
+ * any `resource:action` string can be granted and checked.
642
677
  */
643
- declare const ICliUserEventService_TOKEN = "cli-user-event-service";
678
+ interface CliPermissionDefinition {
679
+ /** Permission string, `resource:action` (e.g. `files:chown`). */
680
+ name: string;
681
+ /** Human readable description. */
682
+ description?: string;
683
+ /** Module that declared it (filled in at registration). */
684
+ module?: string;
685
+ }
686
+ /** Registry of permissions declared by modules (`ICliModule.permissions`). */
687
+ interface ICliPermissionRegistry {
688
+ register(definitions: CliPermissionDefinition[], module?: string): void;
689
+ list(): CliPermissionDefinition[];
690
+ get(name: string): CliPermissionDefinition | undefined;
691
+ /** Remove the permissions registered by `module` (when it is unloaded). */
692
+ unregister?(module: string): void;
693
+ }
694
+ /** The identity checks are made against. */
695
+ interface ICliPrincipal {
696
+ /** User id */
697
+ id: string;
698
+ /** Login name (used as file owner) */
699
+ name: string;
700
+ /** Group (role) names */
701
+ groups: string[];
702
+ /** Effective permissions (union of the groups' permissions) */
703
+ permissions: string[];
704
+ }
705
+ /** rwx-style protected resource (files, directories, or anything an extension owns). */
706
+ interface ICliProtectedResource {
707
+ ownership?: ICliOwnership;
708
+ /** 9-char mode string, e.g. `rwxr-x---` */
709
+ permissions?: string;
710
+ }
711
+ type CliAccessAction = 'read' | 'write' | 'execute';
644
712
  /**
645
- * Token for the audit service (persistent event logging).
713
+ * Authorization service. Provided by an auth module (the users module provides
714
+ * one in local and in server mode). Extensions should use the helpers
715
+ * {@link cliAuthorize} / {@link cliCanAccess}, which allow everything when no
716
+ * authorization service is registered.
646
717
  */
647
- declare const ICliAuditService_TOKEN = "cli-audit-service";
718
+ interface ICliAuthorizationService {
719
+ /** The signed-in principal, or undefined when nobody is signed in. */
720
+ getPrincipal(): ICliPrincipal | undefined;
721
+ /** Does the current principal hold `permission` (wildcards honoured)? */
722
+ can(permission: string): boolean;
723
+ /**
724
+ * rwx check on a resource for the current principal (owner / group / other).
725
+ * Principals holding `*` or any of `bypassPermissions` are always allowed.
726
+ * Resources without a mode or ownership are allowed.
727
+ */
728
+ canAccess(resource: ICliProtectedResource, action: CliAccessAction, bypassPermissions?: string[]): boolean;
729
+ }
730
+ /** Thrown when an operation is not permitted. The message is user facing. */
731
+ declare class CliAccessDeniedError extends Error {
732
+ readonly code = "EACCES";
733
+ constructor(message?: string);
734
+ }
735
+ /** True for {@link CliAccessDeniedError} (also across bundle copies). */
736
+ declare function isCliAccessDeniedError(error: unknown): error is CliAccessDeniedError;
648
737
  /**
649
- * Token for the authorization service ({@link ICliAuthorizationService}).
650
- * Provided by an auth module (e.g. `@qodalis/cli-users`); absent otherwise.
738
+ * Check a permission for the current principal. Returns `true` when no
739
+ * authorization service is registered (no auth module loaded).
651
740
  */
652
- declare const ICliAuthorizationService_TOKEN = "cli-authorization-service";
741
+ declare function cliAuthorize(context: Pick<ICliExecutionContext, 'services'>, permission: string): boolean;
653
742
  /**
654
- * Token for the permission registry ({@link ICliPermissionRegistry}).
655
- * Always provided by the CLI engine.
743
+ * rwx check for the current principal. Returns `true` when no authorization
744
+ * service is registered.
656
745
  */
657
- declare const ICliPermissionRegistry_TOKEN = "cli-permission-registry";
746
+ declare function cliCanAccess(context: Pick<ICliExecutionContext, 'services'>, resource: ICliProtectedResource, action: CliAccessAction, bypassPermissions?: string[]): boolean;
658
747
  /**
659
- * Token for the sessions service ({@link ICliSessionsService}).
748
+ * Reference rwx evaluation shared by implementations of
749
+ * {@link ICliAuthorizationService.canAccess}.
660
750
  */
661
- declare const ICliSessionsService_TOKEN = "cli-sessions-service";
751
+ declare function cliEvaluateAccess(principal: ICliPrincipal | undefined, resource: ICliProtectedResource, action: CliAccessAction, bypassPermissions?: string[]): boolean;
752
+
662
753
  /**
663
- * Token for the persistent key-value store used by modules (setup state, etc.).
754
+ * Context passed to completion providers describing the current input state.
664
755
  */
665
- declare const ICliKeyValueStore_TOKEN = "cli-key-value-store";
666
-
756
+ interface ICliCompletionContext {
757
+ /** Full current line text */
758
+ input: string;
759
+ /** Cursor position in the line */
760
+ cursor: number;
761
+ /** The token (word) being completed at the cursor position */
762
+ token: string;
763
+ /** Start index of the token in the input string */
764
+ tokenStart: number;
765
+ /** Index of the token among all tokens (0 = command, 1+ = arguments) */
766
+ tokenIndex: number;
767
+ /** All tokens parsed from the input */
768
+ tokens: string[];
769
+ }
667
770
  /**
668
- * Abstract permission service for checking rwx permissions on resources.
669
- * The default implementation lives in @qodalis/cli-users; consumers can
670
- * replace it via the DI container.
771
+ * Provides tab-completion candidates for CLI input.
772
+ * Plugins can register providers to extend completion (e.g. file paths, custom values).
671
773
  */
672
- interface ICliPermissionService {
774
+ interface ICliCompletionProvider {
673
775
  /**
674
- * Check whether a user may perform an action on a resource.
675
- * @param user The acting user.
676
- * @param action The requested operation.
677
- * @param ownership Owner/group of the resource.
678
- * @param permissions Permission string (e.g. "rwxr-xr-x").
679
- * @returns true if allowed.
776
+ * Priority order. Lower values are checked first.
777
+ * Built-in defaults: 100 (commands), 200 (parameters).
778
+ * Plugins should use lower values (e.g. 50) to take precedence for specific commands.
680
779
  */
681
- check(user: ICliUser, action: 'read' | 'write' | 'execute', ownership: ICliOwnership, permissions: string): boolean;
682
- /** Parse octal (e.g. "755") to a permission string (e.g. "rwxr-xr-x"). */
683
- parseOctal(octal: string): string;
684
- /** Convert a permission string (e.g. "rwxr-xr-x") to octal (e.g. "755"). */
685
- toOctal(permissions: string): string;
780
+ priority: number;
781
+ /**
782
+ * Return completion candidates for the current input context.
783
+ * Return an empty array if this provider does not apply.
784
+ */
785
+ getCompletions(context: ICliCompletionContext): string[] | Promise<string[]>;
786
+ }
787
+
788
+ interface ICliDragDropService {
789
+ /** Emits an array of dropped File objects each time files are dropped onto the terminal */
790
+ readonly onFileDrop: Observable<File[]>;
686
791
  }
687
792
 
688
793
  /**
@@ -749,591 +854,733 @@ interface ICliFilePickerProvider {
749
854
  }
750
855
 
751
856
  /**
752
- * Represents an option for the readSelect and readSelectInline prompts.
857
+ * Represents an entry in a file listing.
753
858
  */
754
- interface CliSelectOption {
755
- /** Display text shown to the user */
756
- label: string;
757
- /** Value returned when this option is selected */
758
- value: string;
759
- /** Optional description shown as dimmed italic text after the label */
760
- description?: string;
761
- /** Group name — options sharing the same group are displayed under a group header */
762
- group?: string;
763
- /** When true, the option is visible but cannot be selected (rendered dimmed) */
764
- disabled?: boolean;
859
+ interface ICliFileEntry {
860
+ name: string;
861
+ type: 'file' | 'directory';
862
+ size: number;
863
+ modified: string;
765
864
  }
865
+
766
866
  /**
767
- * Represents an option for the readMultiSelect prompt.
867
+ * Abstraction for file transfer operations.
868
+ * Default implementation uses browser-native APIs (file picker, download dialog).
869
+ * @qodalis/cli-files overrides with virtual FS backed by IndexedDB.
768
870
  */
769
- interface CliMultiSelectOption extends CliSelectOption {
770
- /** Whether the option is pre-selected */
771
- checked?: boolean;
871
+ interface ICliFileTransferService {
872
+ readFile(path: string): Promise<string | null>;
873
+ writeFile(path: string, content: string): Promise<void>;
874
+ listFiles(path: string): Promise<ICliFileEntry[]>;
875
+ exists(path: string): Promise<boolean>;
876
+ downloadToBrowser(filename: string, content: string | Blob): void;
877
+ uploadFromBrowser(accept?: string): Promise<{
878
+ name: string;
879
+ content: string;
880
+ } | null>;
772
881
  }
882
+
773
883
  /**
774
- * Options for readLine input.
884
+ * Options for HTTP client requests.
885
+ * Extends standard RequestInit with convenience fields.
775
886
  */
776
- interface CliLineOptions {
777
- /**
778
- * Pre-filled default value. Shown in the buffer when the prompt opens.
779
- * The cursor starts at the end of the default text.
780
- */
781
- default?: string;
887
+ interface CliHttpRequestOptions extends Omit<RequestInit, 'method' | 'body'> {
782
888
  /**
783
- * Validation function called on Enter.
784
- * Return an error message string to reject the input and display the error.
785
- * Return null to accept the input.
889
+ * Request timeout in milliseconds. When set, the request is automatically
890
+ * aborted after this duration. Combines with any existing abort signal.
786
891
  */
787
- validate?: (value: string) => string | null;
788
- /** Placeholder text shown dimmed when the buffer is empty */
789
- placeholder?: string;
790
- }
791
- /**
792
- * Options for readSelect and readSelectInline.
793
- */
794
- interface CliSelectOptions {
795
- /** Pre-select an option by its value */
796
- default?: string;
797
- /** Enable type-to-filter. When true, printable keystrokes filter the option list. Default: false. */
798
- searchable?: boolean;
799
- /** Callback invoked each time the highlighted option changes */
800
- onChange?: (value: string) => void;
801
- }
802
- /**
803
- * Options for readMultiSelect.
804
- */
805
- interface CliMultiSelectOptions {
806
- /** Enable type-to-filter. When true, printable keystrokes filter the option list. Default: false. */
807
- searchable?: boolean;
808
- /** Callback invoked each time the set of checked options changes */
809
- onChange?: (values: string[]) => void;
810
- }
811
- /**
812
- * Options for readNumber input.
813
- */
814
- interface CliNumberOptions {
815
- /** Minimum allowed value */
816
- min?: number;
817
- /** Maximum allowed value */
818
- max?: number;
819
- /** Pre-filled default value */
820
- default?: number;
821
- }
822
- /**
823
- * Options for readDate input.
824
- */
825
- interface CliDateOptions {
826
- /**
827
- * Date format string. Supported tokens: YYYY (4-digit year), MM (2-digit month),
828
- * DD (2-digit day). Separators can be `-`, `/`, or `.`.
829
- * @default 'YYYY-MM-DD'
830
- */
831
- format?: string;
832
- /** Minimum allowed date in the same format as `format` */
833
- min?: string;
834
- /** Maximum allowed date in the same format as `format` */
835
- max?: string;
836
- /** Pre-filled default value */
837
- default?: string;
892
+ timeout?: number;
838
893
  }
839
894
  /**
840
- * Provides interactive input reading from the terminal.
841
- * All methods resolve with `null` when the user aborts (Ctrl+C or Escape).
895
+ * HTTP client for making requests from command processors.
896
+ *
897
+ * Automatically merges the command's abort signal so that requests are
898
+ * cancelled when the user presses Ctrl+C. Provides typed convenience
899
+ * methods for common HTTP verbs as well as a raw `fetch()` for cases
900
+ * that need full Response access (streaming, progress, etc.).
901
+ *
902
+ * Available on the execution context as `context.http`.
903
+ *
904
+ * @example
905
+ * ```ts
906
+ * // Typed JSON response
907
+ * const users = await context.http.get<User[]>('https://api.example.com/users');
908
+ *
909
+ * // POST with body
910
+ * await context.http.post('https://api.example.com/users', { name: 'John' });
911
+ *
912
+ * // Raw fetch for streaming
913
+ * const response = await context.http.fetch('https://example.com/file.zip');
914
+ * const reader = response.body?.getReader();
915
+ * ```
842
916
  */
843
- interface ICliInputReader {
844
- /**
845
- * Read a line of text input.
846
- * @param prompt - The prompt text displayed to the user
847
- * @param options - Optional configuration for default value, validation, and placeholder
848
- * @returns The entered text, or null if aborted
849
- */
850
- readLine(prompt: string, options?: CliLineOptions): Promise<string | null>;
917
+ interface ICliHttpClient {
851
918
  /**
852
- * Read a password (masked input).
853
- * @param prompt - The prompt text displayed to the user
854
- * @returns The entered password, or null if aborted
919
+ * Make a raw HTTP request. Returns the full Response object for cases
920
+ * that need streaming, headers, or status inspection.
921
+ * The command's abort signal is automatically merged.
855
922
  */
856
- readPassword(prompt: string): Promise<string | null>;
923
+ fetch(url: string, init?: RequestInit): Promise<Response>;
857
924
  /**
858
- * Read a yes/no confirmation.
859
- * @param prompt - The prompt text displayed to the user
860
- * @param defaultValue - Default value when Enter is pressed without input
861
- * @returns true for yes, false for no, or null if aborted
925
+ * HTTP GET request. Returns the parsed JSON response body.
862
926
  */
863
- readConfirm(prompt: string, defaultValue?: boolean): Promise<boolean | null>;
927
+ get<T = any>(url: string, options?: CliHttpRequestOptions): Promise<T>;
864
928
  /**
865
- * Read a selection from a vertical list of options.
866
- * @param prompt - The prompt text displayed to the user
867
- * @param options - Array of options to choose from
868
- * @param selectOptions - Optional configuration for default, search, onChange
869
- * @returns The selected option's value, or null if aborted
929
+ * HTTP POST request. Automatically serializes the body as JSON
930
+ * (unless body is FormData, Blob, string, or ArrayBuffer).
931
+ * Returns the parsed JSON response body.
870
932
  */
871
- readSelect(prompt: string, options: CliSelectOption[], selectOptions?: CliSelectOptions): Promise<string | null>;
933
+ post<T = any>(url: string, body?: any, options?: CliHttpRequestOptions): Promise<T>;
872
934
  /**
873
- * Read a selection from an inline horizontal list.
874
- * @param prompt - The prompt text displayed to the user
875
- * @param options - Array of options to choose from
876
- * @param selectOptions - Optional configuration for default, search, onChange
877
- * @returns The selected option's value, or null if aborted
935
+ * HTTP PUT request. Automatically serializes the body as JSON.
936
+ * Returns the parsed JSON response body.
878
937
  */
879
- readSelectInline(prompt: string, options: CliSelectOption[], selectOptions?: CliSelectOptions): Promise<string | null>;
938
+ put<T = any>(url: string, body?: any, options?: CliHttpRequestOptions): Promise<T>;
880
939
  /**
881
- * Read multiple selections from a checkbox list.
882
- * @param prompt - The prompt text displayed to the user
883
- * @param options - Array of options with optional pre-checked state
884
- * @param selectOptions - Optional configuration for search, onChange
885
- * @returns Array of selected option values, or null if aborted
940
+ * HTTP PATCH request. Automatically serializes the body as JSON.
941
+ * Returns the parsed JSON response body.
886
942
  */
887
- readMultiSelect(prompt: string, options: CliMultiSelectOption[], selectOptions?: CliMultiSelectOptions): Promise<string[] | null>;
943
+ patch<T = any>(url: string, body?: any, options?: CliHttpRequestOptions): Promise<T>;
888
944
  /**
889
- * Read a number input.
890
- * @param prompt - The prompt text displayed to the user
891
- * @param options - Optional configuration for min, max, and default value
892
- * @returns The entered number, or null if aborted
945
+ * HTTP DELETE request. Returns the parsed JSON response body (if any).
893
946
  */
894
- readNumber(prompt: string, options?: CliNumberOptions): Promise<number | null>;
947
+ delete<T = any>(url: string, options?: CliHttpRequestOptions): Promise<T>;
895
948
  /**
896
- * Read a date input with format validation.
897
- * @param prompt - The prompt text displayed to the user
898
- * @param options - Optional configuration for format, min/max range, default
899
- * @returns The date string in the specified format, or null if aborted
949
+ * HTTP HEAD request. Returns the full Response (no body parsing).
900
950
  */
901
- readDate(prompt: string, options?: CliDateOptions): Promise<string | null>;
951
+ head(url: string, options?: CliHttpRequestOptions): Promise<Response>;
902
952
  /**
903
- * Read file(s) via the environment's file picker dialog.
904
- * @param prompt - The prompt text displayed to the user
905
- * @param options - Optional configuration for multiple, accept filter, directory mode
906
- * @returns Array of selected files with content, or null if cancelled/unsupported
953
+ * HTTP OPTIONS request. Returns the parsed JSON response body.
907
954
  */
908
- readFile(prompt: string, options?: CliFilePickerOptions): Promise<CliFileResult[] | null>;
909
- }
910
-
911
- /**
912
- * Context passed to completion providers describing the current input state.
913
- */
914
- interface ICliCompletionContext {
915
- /** Full current line text */
916
- input: string;
917
- /** Cursor position in the line */
918
- cursor: number;
919
- /** The token (word) being completed at the cursor position */
920
- token: string;
921
- /** Start index of the token in the input string */
922
- tokenStart: number;
923
- /** Index of the token among all tokens (0 = command, 1+ = arguments) */
924
- tokenIndex: number;
925
- /** All tokens parsed from the input */
926
- tokens: string[];
955
+ options<T = any>(url: string, options?: CliHttpRequestOptions): Promise<T>;
927
956
  }
928
957
  /**
929
- * Provides tab-completion candidates for CLI input.
930
- * Plugins can register providers to extend completion (e.g. file paths, custom values).
958
+ * Error thrown by `ICliHttpClient` JSON helpers (`get`, `post`, `put`,
959
+ * `patch`, `delete`, `options`) when the server responds with a non-2xx status.
960
+ *
961
+ * Exposes the HTTP status so callers can react to specific responses
962
+ * (for example, clearing a stored session token on `401`).
963
+ *
964
+ * @example
965
+ * ```ts
966
+ * try {
967
+ * await context.http.get(url);
968
+ * } catch (e) {
969
+ * if (e instanceof CliHttpError && e.status === 401) {
970
+ * // token expired or revoked
971
+ * }
972
+ * }
973
+ * ```
931
974
  */
932
- interface ICliCompletionProvider {
933
- /**
934
- * Priority order. Lower values are checked first.
935
- * Built-in defaults: 100 (commands), 200 (parameters).
936
- * Plugins should use lower values (e.g. 50) to take precedence for specific commands.
937
- */
938
- priority: number;
939
- /**
940
- * Return completion candidates for the current input context.
941
- * Return an empty array if this provider does not apply.
942
- */
943
- getCompletions(context: ICliCompletionContext): string[] | Promise<string[]>;
975
+ declare class CliHttpError extends Error {
976
+ /** HTTP status code of the failed response */
977
+ readonly status: number;
978
+ /** HTTP status text of the failed response */
979
+ readonly statusText: string;
980
+ /** Parsed JSON error body, when the server returned one */
981
+ readonly body?: unknown | undefined;
982
+ constructor(message: string,
983
+ /** HTTP status code of the failed response */
984
+ status: number,
985
+ /** HTTP status text of the failed response */
986
+ statusText?: string,
987
+ /** Parsed JSON error body, when the server returned one */
988
+ body?: unknown | undefined);
944
989
  }
945
990
 
946
- interface ICliProgressBar {
947
- /**
948
- * Indicates if the progress bar is running
949
- */
950
- isRunning: boolean;
951
- /**
952
- * Show the progress bar
953
- */
954
- show: (text?: string) => void;
955
- /**
956
- * Hide the progress bar
957
- */
958
- hide: () => void;
991
+ /** A sign-in session as listed by {@link ICliSessionsService}. Never carries a token. */
992
+ interface ICliSessionInfo {
993
+ id: string;
994
+ userId: string;
995
+ userName: string;
996
+ createdAt: number;
997
+ lastActivity: number;
998
+ /** When the session expires if idle; undefined when there is no timeout */
999
+ expiresAt?: number;
1000
+ /** True for the caller's own session */
1001
+ current?: boolean;
1002
+ metadata?: {
1003
+ userAgent?: string;
1004
+ remoteAddress?: string;
1005
+ [key: string]: unknown;
1006
+ };
959
1007
  }
960
1008
  /**
961
- * Represents a spinner for the CLI
1009
+ * Lists and revokes sign-in sessions. Implemented locally (sessions of this
1010
+ * browser) and by servers (all sessions). Used by `sessions` / `session-kick`.
962
1011
  */
963
- interface ICliSpinner extends ICliProgressBar {
964
- /**
965
- * Set the text of the spinner
966
- * @param text The text to set
967
- */
968
- setText: (text: string) => void;
969
- }
970
- type CliPercentageProgressBarUpdateValueOptions = {
971
- /**
972
- * The type of update to perform
973
- * @default 'replace'
974
- */
975
- type?: 'replace' | 'increment';
976
- };
977
- /**
978
- * Represents a progress bar for the CLI
1012
+ interface ICliSessionsService {
1013
+ /** All sessions visible to the caller */
1014
+ list(): Promise<ICliSessionInfo[]>;
1015
+ /** Revoke a session by id */
1016
+ revoke(sessionId: string, reason?: string): Promise<void>;
1017
+ /** Revoke every session of a user (optionally keeping one) */
1018
+ revokeUser(userId: string, options?: {
1019
+ exceptSessionId?: string;
1020
+ reason?: string;
1021
+ }): Promise<void>;
1022
+ }
1023
+
1024
+ /** Represents a user lifecycle event */
1025
+ interface ICliUserEvent {
1026
+ /** The type of event that occurred */
1027
+ type: 'login' | 'logout' | 'login_failed' | 'user_created' | 'user_deleted' | 'user_updated' | 'password_changed' | 'user_switched' | 'user_disabled' | 'user_enabled' | 'group_created' | 'group_updated' | 'group_deleted' | 'group_member_added' | 'group_member_removed' | 'session_revoked' | 'audit_cleared';
1028
+ /** The user involved in the event (for group events: the acting user) */
1029
+ user: ICliUser;
1030
+ /** The acting user's name, when different from `user` (e.g. an admin) */
1031
+ actor?: string;
1032
+ /** When the event occurred */
1033
+ timestamp: number;
1034
+ /** Optional additional details */
1035
+ details?: string;
1036
+ /** `user_updated`: the user's name before a rename (set only when the name changed) */
1037
+ previousName?: string;
1038
+ }
1039
+ /** Service that emits user lifecycle events for plugins to subscribe to */
1040
+ interface ICliUserEventService {
1041
+ /** Observable stream of user events */
1042
+ readonly events$: Observable<ICliUserEvent>;
1043
+ /** Emit a user event */
1044
+ emit(event: ICliUserEvent): void;
1045
+ }
1046
+
1047
+ /**
1048
+ * Represents a service that manages user sessions in the CLI
979
1049
  */
980
- interface ICliPercentageProgressBar extends ICliProgressBar {
981
- /**
982
- * Update the progress of the progress bar.
983
- * @param progress The progress value (0–100). Values outside this range are clamped.
984
- * @param options Optional: update type ('replace' or 'increment')
985
- */
986
- update: (progress: number, options?: CliPercentageProgressBarUpdateValueOptions) => void;
987
- /**
988
- * Complete the progress bar
989
- * @returns void
990
- */
991
- complete: () => void;
992
- /**
993
- * Set the text of the spinner
994
- * @param text The text to set
995
- */
996
- setText: (text: string) => void;
1050
+ interface ICliUserSessionService {
1051
+ /** Gets the current user session as a reactive stream */
1052
+ getUserSession(): Observable<ICliUserSession | undefined>;
1053
+ /** Sets the current user session */
1054
+ setUserSession(session: ICliUserSession): Promise<void>;
1055
+ /** Clears the current session (logout) */
1056
+ clearSession(): Promise<void>;
1057
+ /** Persists the current session to storage */
1058
+ persistSession(): Promise<void>;
1059
+ /** Restores a session from storage (called on boot) */
1060
+ restoreSession(): Promise<ICliUserSession | undefined>;
997
1061
  }
998
- type CliTextAnimatorOptions = {
1062
+ /**
1063
+ * Represents a service that manages users in the CLI
1064
+ */
1065
+ interface ICliUsersStoreService {
1066
+ /** Gets users with optional filtering and pagination */
1067
+ getUsers(options?: {
1068
+ query?: string;
1069
+ skip?: number;
1070
+ take?: number;
1071
+ }): Observable<ICliUser[]>;
1072
+ /** Creates a new user */
1073
+ createUser(user: CliAddUser): Promise<ICliUser>;
1074
+ /** Gets a user by id, name, or email */
1075
+ getUser(id: string): Observable<ICliUser | undefined>;
1076
+ /** Updates an existing user */
1077
+ updateUser(id: string, updates: CliUpdateUser): Promise<ICliUser>;
1078
+ /** Deletes a user by id */
1079
+ deleteUser(id: string): Promise<void>;
1080
+ }
1081
+ /**
1082
+ * Represents a service that manages groups in the CLI
1083
+ */
1084
+ interface ICliGroupsStoreService {
1085
+ /** Gets all groups */
1086
+ getGroups(): Observable<ICliGroup[]>;
1087
+ /** Gets a group by id or name */
1088
+ getGroup(id: string): Observable<ICliGroup | undefined>;
1089
+ /** Creates a new group (role), optionally with permissions */
1090
+ createGroup(name: string, description?: string, permissions?: string[]): Promise<ICliGroup>;
1091
+ /** Updates a group's description and/or permissions (by id or name) */
1092
+ updateGroup(id: string, updates: {
1093
+ description?: string;
1094
+ permissions?: string[];
1095
+ }): Promise<ICliGroup>;
1096
+ /** Adds a user (id or name) to a group (id or name) */
1097
+ addMember(groupId: string, userId: string): Promise<void>;
1098
+ /** Removes a user (id or name) from a group (id or name) */
1099
+ removeMember(groupId: string, userId: string): Promise<void>;
1100
+ /** Deletes a group by id */
1101
+ deleteGroup(id: string): Promise<void>;
1102
+ /** Gets all users that belong to a group */
1103
+ getGroupMembers(groupId: string): Observable<ICliUser[]>;
1104
+ }
1105
+ /**
1106
+ * Represents a service that handles authentication in the CLI
1107
+ */
1108
+ interface ICliAuthService {
999
1109
  /**
1000
- * The speed of the animation
1001
- * @default 100
1110
+ * Authenticates a user and returns a session. `password` may be omitted
1111
+ * only when the implementation allows passwordless sign-in.
1002
1112
  */
1003
- speed?: number;
1113
+ login(username: string, password?: string): Promise<ICliUserSession>;
1114
+ /** Ends the current session (the CLI is left signed out) */
1115
+ logout(): Promise<void>;
1004
1116
  /**
1005
- * The text will be removed after typing
1117
+ * Switch to another user (`su`). Requires the target's password unless
1118
+ * the caller may skip it (implementation-defined, e.g. `users:update`).
1119
+ * Goes through the same lockout, events and audit as `login`.
1006
1120
  */
1007
- removeAfterTyping?: boolean;
1008
- };
1009
- interface ICliTextAnimator extends ICliProgressBar {
1121
+ switchUser?(username: string, password?: string): Promise<ICliUserSession>;
1010
1122
  /**
1011
- * Show animated text in a typing and erasing effect.
1012
- * @param text The text to animate
1013
- * @param options Optional: speed (ms per character), removeAfterTyping
1014
- * @returns A promise that resolves when the animation completes
1123
+ * Sets or updates a user's password.
1124
+ * @param currentPassword The user's current password, when changing one's own
1125
+ * password. Server-backed implementations forward it so the server can
1126
+ * re-check it; local implementations may ignore it.
1015
1127
  */
1016
- showText: (text: string, options?: CliTextAnimatorOptions) => Promise<void>;
1128
+ setPassword(userId: string, password: string, currentPassword?: string): Promise<void>;
1129
+ /** Verifies a password against stored credentials */
1130
+ verifyPassword(userId: string, password: string): Promise<boolean>;
1131
+ /** Hashes a password with a salt using Web Crypto SHA-256 */
1132
+ hashPassword(password: string, salt: string): Promise<string>;
1133
+ /** Validate password against policy. Returns error message or null if valid. */
1134
+ validatePassword?(password: string): string | null;
1017
1135
  }
1018
1136
 
1019
1137
  /**
1020
- * Result of a guard check.
1021
- *
1022
- * - `true` or `undefined`: allow the command.
1023
- * - `false`: block it with the generic message `<command>: permission denied`.
1024
- * - a non-empty `string`: block it and print this message.
1025
- * - `{ allowed, message }`: explicit form; `message` is used when `allowed` is false.
1138
+ * Token for the CLI user session service.
1026
1139
  */
1027
- type CliCommandGuardResult = boolean | string | void | {
1028
- allowed: boolean;
1029
- message?: string;
1030
- };
1140
+ declare const ICliUserSessionService_TOKEN: CliToken<ICliUserSessionService>;
1031
1141
  /**
1032
- * A global command guard: a check that runs before **every** command, across
1033
- * all processors and modules, and can block it.
1034
- *
1035
- * Use guards for cross-cutting policies such as authentication, permission
1036
- * checks, feature flags, maintenance windows or rate limiting, instead of
1037
- * repeating the check inside each processor.
1038
- *
1039
- * ## When guards run
1040
- *
1041
- * For each command the executor:
1042
- * 1. parses the input and resolves the processor (and sub-processor);
1043
- * 2. handles global parameters such as `--help` (help is never guarded);
1044
- * 3. extracts the positional value into `command.value`;
1045
- * 4. **runs the guards**;
1046
- * 5. validates required values/parameters, runs processor hooks and finally
1047
- * calls `processCommand`.
1048
- *
1049
- * Guards run in ascending `priority` (default `0`). For each guard the executor
1050
- * calls `appliesTo` (if defined) and skips the guard when it returns `false`;
1051
- * otherwise it awaits `canActivate`. The **first** guard that blocks stops the
1052
- * chain: its message is written as an error and the command exits with code
1053
- * `-1`. A guard that throws (in `appliesTo` or `canActivate`) also blocks the
1054
- * command, so a broken guard fails closed.
1055
- *
1056
- * ## Access to the context
1057
- *
1058
- * Both `appliesTo` and `canActivate` receive the {@link ICliExecutionContext}
1059
- * of the command about to run, so a guard can:
1060
- * - resolve services with `context.services.get(...)` / `getRequired(...)`
1061
- * (e.g. a policy, feature-flag or permission service registered by any
1062
- * module) to decide **what** should be guarded and **whether** it may run;
1063
- * - inspect `context.userSession` for the signed-in user and their groups;
1064
- * - read the processor's `metadata` (for example `requireAuth` or
1065
- * `requiredGroups`) and the parsed `command` (`command.value`, `command.args`);
1066
- * - prompt the user via `context.reader` (e.g. a confirmation guard).
1067
- *
1068
- * Guards should not write to the terminal to report a denial; return a message
1069
- * instead so the executor prints it consistently.
1070
- *
1071
- * ## Registering guards
1072
- *
1073
- * The preferred way is the module's `guards` array; the boot sequence
1074
- * registers each entry as a multi-provider under {@link ICliCommandGuard_TOKEN}.
1075
- * Several modules can contribute guards; all of them run.
1076
- *
1077
- * ```ts
1078
- * const billingModule: ICliModule = {
1079
- * apiVersion: 2,
1080
- * name: 'billing',
1081
- * processors: [new InvoiceProcessor()],
1082
- * guards: [billingGuard],
1083
- * };
1084
- * ```
1085
- *
1086
- * Guards can also be added at runtime (e.g. from `onInit`):
1087
- *
1088
- * ```ts
1089
- * context.services.set({ provide: ICliCommandGuard_TOKEN, useValue: myGuard, multi: true });
1090
- * ```
1091
- *
1092
- * ## Example: policy resolved from a service
1093
- *
1094
- * ```ts
1095
- * const featureFlagGuard: ICliCommandGuard = {
1096
- * name: 'feature-flags',
1097
- * // Only guard commands that are behind a flag; the flag list comes from a service
1098
- * appliesTo: (processor, _command, context) =>
1099
- * context.services.get<FeatureFlags>(FeatureFlags_TOKEN)?.isFlagged(processor.command) ?? false,
1100
- * canActivate: async (_command, processor, context) => {
1101
- * const flags = context.services.getRequired<FeatureFlags>(FeatureFlags_TOKEN);
1102
- * return (await flags.isEnabled(processor.command, context.userSession?.user))
1103
- * ? true
1104
- * : `${processor.command}: not available for your account`;
1105
- * },
1106
- * };
1107
- * ```
1108
- *
1109
- * ## Without a guard-providing module
1110
- *
1111
- * Guards are opt-in. When no guard is registered every command runs as before.
1112
- * The engine registers three built-in guards that enforce the processor access
1113
- * metadata (`requireAuth`, `requiredGroups`, `requiredPermissions`); they stay
1114
- * inactive until an auth module (e.g. `@qodalis/cli-users`) registers an
1115
- * `ICliAuthorizationService`, so without one that metadata is informational.
1142
+ * Token for the CLI users store service.
1116
1143
  */
1117
- interface ICliCommandGuard {
1118
- /** Unique name, shown in diagnostics when the guard throws. */
1119
- name: string;
1120
- /** Execution order; lower runs first. Defaults to `0`. */
1121
- priority?: number;
1122
- /**
1123
- * Optional filter deciding whether this guard is relevant for the command.
1124
- * Return `false` to skip the guard. When omitted the guard applies to
1125
- * every command. May be async, and may use `context` to resolve services.
1126
- */
1127
- appliesTo?(processor: ICliCommandProcessor, command: CliProcessCommand, context: ICliExecutionContext): boolean | Promise<boolean>;
1144
+ declare const ICliUsersStoreService_TOKEN: CliToken<ICliUsersStoreService>;
1145
+ /**
1146
+ * Token for the CLI groups store service.
1147
+ */
1148
+ declare const ICliGroupsStoreService_TOKEN: CliToken<ICliGroupsStoreService>;
1149
+ /**
1150
+ * Token for the CLI auth service.
1151
+ */
1152
+ declare const ICliAuthService_TOKEN: CliToken<ICliAuthService>;
1153
+ /**
1154
+ * Token for registering completion providers via module services (multi: true).
1155
+ */
1156
+ declare const ICliCompletionProvider_TOKEN: CliToken<ICliCompletionProvider>;
1157
+ /**
1158
+ * Token for the drag-and-drop service.
1159
+ */
1160
+ declare const ICliDragDropService_TOKEN: CliToken<ICliDragDropService>;
1161
+ /**
1162
+ * Token for the file transfer service.
1163
+ */
1164
+ declare const ICliFileTransferService_TOKEN: CliToken<ICliFileTransferService>;
1165
+ /**
1166
+ * Token for the translation service.
1167
+ */
1168
+ declare const ICliTranslationService_TOKEN: CliToken<ICliTranslationService>;
1169
+ /**
1170
+ * Token for the permission service.
1171
+ */
1172
+ declare const ICliPermissionService_TOKEN: CliToken<ICliPermissionService>;
1173
+ /**
1174
+ * Token for the file picker provider.
1175
+ */
1176
+ declare const ICliFilePickerProvider_TOKEN: CliToken<ICliFilePickerProvider>;
1177
+ /**
1178
+ * Token for the HTTP client service.
1179
+ */
1180
+ declare const ICliHttpClient_TOKEN: CliToken<ICliHttpClient>;
1181
+ /**
1182
+ * Token for the user event service (lifecycle events).
1183
+ */
1184
+ declare const ICliUserEventService_TOKEN: CliToken<ICliUserEventService>;
1185
+ /**
1186
+ * Token for the audit service (persistent event logging).
1187
+ */
1188
+ declare const ICliAuditService_TOKEN: CliToken<ICliAuditService>;
1189
+ /**
1190
+ * Token for the authorization service ({@link ICliAuthorizationService}).
1191
+ * Provided by an auth module (e.g. `@qodalis/cli-users`); absent otherwise.
1192
+ */
1193
+ declare const ICliAuthorizationService_TOKEN: CliToken<ICliAuthorizationService>;
1194
+ /**
1195
+ * Token for the permission registry ({@link ICliPermissionRegistry}).
1196
+ * Always provided by the CLI engine.
1197
+ */
1198
+ declare const ICliPermissionRegistry_TOKEN: CliToken<ICliPermissionRegistry>;
1199
+ /**
1200
+ * Token for the sessions service ({@link ICliSessionsService}).
1201
+ */
1202
+ declare const ICliSessionsService_TOKEN: CliToken<ICliSessionsService>;
1203
+ /**
1204
+ * Token for the persistent key-value store used by modules (setup state, etc.).
1205
+ */
1206
+ declare const ICliKeyValueStore_TOKEN: CliToken<ICliKeyValueStore>;
1207
+
1208
+ /**
1209
+ * Abstract permission service for checking rwx permissions on resources.
1210
+ * The default implementation lives in @qodalis/cli-users; consumers can
1211
+ * replace it via the DI container.
1212
+ */
1213
+ interface ICliPermissionService {
1128
1214
  /**
1129
- * Decide whether the command may run. See {@link CliCommandGuardResult}
1130
- * for the accepted return values.
1215
+ * Check whether a user may perform an action on a resource.
1216
+ * @param user The acting user.
1217
+ * @param action The requested operation.
1218
+ * @param ownership Owner/group of the resource.
1219
+ * @param permissions Permission string (e.g. "rwxr-xr-x").
1220
+ * @returns true if allowed.
1131
1221
  */
1132
- canActivate(command: CliProcessCommand, processor: ICliCommandProcessor, context: ICliExecutionContext): CliCommandGuardResult | Promise<CliCommandGuardResult>;
1222
+ check(user: ICliUser, action: 'read' | 'write' | 'execute', ownership: ICliOwnership, permissions: string): boolean;
1223
+ /** Parse octal (e.g. "755") to a permission string (e.g. "rwxr-xr-x"). */
1224
+ parseOctal(octal: string): string;
1225
+ /** Convert a permission string (e.g. "rwxr-xr-x") to octal (e.g. "755"). */
1226
+ toOctal(permissions: string): string;
1133
1227
  }
1134
- /** DI token for {@link ICliCommandGuard}; register guards as multi-providers. */
1135
- declare const ICliCommandGuard_TOKEN = "cli-command-guard";
1136
1228
 
1137
1229
  /**
1138
- * A permission an extension uses, declared so tools (`perms --list`,
1139
- * `groupmod --grant` completion, docs) can show it. Declaring is optional:
1140
- * any `resource:action` string can be granted and checked.
1230
+ * Represents an option for the readSelect and readSelectInline prompts.
1141
1231
  */
1142
- interface CliPermissionDefinition {
1143
- /** Permission string, `resource:action` (e.g. `files:chown`). */
1144
- name: string;
1145
- /** Human readable description. */
1232
+ interface CliSelectOption {
1233
+ /** Display text shown to the user */
1234
+ label: string;
1235
+ /** Value returned when this option is selected */
1236
+ value: string;
1237
+ /** Optional description shown as dimmed italic text after the label */
1146
1238
  description?: string;
1147
- /** Module that declared it (filled in at registration). */
1148
- module?: string;
1149
- }
1150
- /** Registry of permissions declared by modules (`ICliModule.permissions`). */
1151
- interface ICliPermissionRegistry {
1152
- register(definitions: CliPermissionDefinition[], module?: string): void;
1153
- list(): CliPermissionDefinition[];
1154
- get(name: string): CliPermissionDefinition | undefined;
1155
- }
1156
- /** The identity checks are made against. */
1157
- interface ICliPrincipal {
1158
- /** User id */
1159
- id: string;
1160
- /** Login name (used as file owner) */
1161
- name: string;
1162
- /** Group (role) names */
1163
- groups: string[];
1164
- /** Effective permissions (union of the groups' permissions) */
1165
- permissions: string[];
1239
+ /** Group name — options sharing the same group are displayed under a group header */
1240
+ group?: string;
1241
+ /** When true, the option is visible but cannot be selected (rendered dimmed) */
1242
+ disabled?: boolean;
1166
1243
  }
1167
- /** rwx-style protected resource (files, directories, or anything an extension owns). */
1168
- interface ICliProtectedResource {
1169
- ownership?: ICliOwnership;
1170
- /** 9-char mode string, e.g. `rwxr-x---` */
1171
- permissions?: string;
1244
+ /**
1245
+ * Represents an option for the readMultiSelect prompt.
1246
+ */
1247
+ interface CliMultiSelectOption extends CliSelectOption {
1248
+ /** Whether the option is pre-selected */
1249
+ checked?: boolean;
1172
1250
  }
1173
- type CliAccessAction = 'read' | 'write' | 'execute';
1174
1251
  /**
1175
- * Authorization service. Provided by an auth module (the users module provides
1176
- * one in local and in server mode). Extensions should use the helpers
1177
- * {@link cliAuthorize} / {@link cliCanAccess}, which allow everything when no
1178
- * authorization service is registered.
1252
+ * Options for readLine input.
1179
1253
  */
1180
- interface ICliAuthorizationService {
1181
- /** The signed-in principal, or undefined when nobody is signed in. */
1182
- getPrincipal(): ICliPrincipal | undefined;
1183
- /** Does the current principal hold `permission` (wildcards honoured)? */
1184
- can(permission: string): boolean;
1254
+ interface CliLineOptions {
1185
1255
  /**
1186
- * rwx check on a resource for the current principal (owner / group / other).
1187
- * Principals holding `*` or any of `bypassPermissions` are always allowed.
1188
- * Resources without a mode or ownership are allowed.
1256
+ * Pre-filled default value. Shown in the buffer when the prompt opens.
1257
+ * The cursor starts at the end of the default text.
1189
1258
  */
1190
- canAccess(resource: ICliProtectedResource, action: CliAccessAction, bypassPermissions?: string[]): boolean;
1191
- }
1192
- /** Thrown when an operation is not permitted. The message is user facing. */
1193
- declare class CliAccessDeniedError extends Error {
1194
- readonly code = "EACCES";
1195
- constructor(message?: string);
1259
+ default?: string;
1260
+ /**
1261
+ * Validation function called on Enter.
1262
+ * Return an error message string to reject the input and display the error.
1263
+ * Return null to accept the input.
1264
+ */
1265
+ validate?: (value: string) => string | null;
1266
+ /** Placeholder text shown dimmed when the buffer is empty */
1267
+ placeholder?: string;
1196
1268
  }
1197
- /** True for {@link CliAccessDeniedError} (also across bundle copies). */
1198
- declare function isCliAccessDeniedError(error: unknown): error is CliAccessDeniedError;
1199
1269
  /**
1200
- * Check a permission for the current principal. Returns `true` when no
1201
- * authorization service is registered (no auth module loaded).
1202
- */
1203
- declare function cliAuthorize(context: Pick<ICliExecutionContext, 'services'>, permission: string): boolean;
1204
- /**
1205
- * rwx check for the current principal. Returns `true` when no authorization
1206
- * service is registered.
1270
+ * Options for readSelect and readSelectInline.
1207
1271
  */
1208
- declare function cliCanAccess(context: Pick<ICliExecutionContext, 'services'>, resource: ICliProtectedResource, action: CliAccessAction, bypassPermissions?: string[]): boolean;
1272
+ interface CliSelectOptions {
1273
+ /** Pre-select an option by its value */
1274
+ default?: string;
1275
+ /** Enable type-to-filter. When true, printable keystrokes filter the option list. Default: false. */
1276
+ searchable?: boolean;
1277
+ /** Callback invoked each time the highlighted option changes */
1278
+ onChange?: (value: string) => void;
1279
+ }
1209
1280
  /**
1210
- * Reference rwx evaluation shared by implementations of
1211
- * {@link ICliAuthorizationService.canAccess}.
1281
+ * Options for readMultiSelect.
1212
1282
  */
1213
- declare function cliEvaluateAccess(principal: ICliPrincipal | undefined, resource: ICliProtectedResource, action: CliAccessAction, bypassPermissions?: string[]): boolean;
1214
-
1215
- /** A sign-in session as listed by {@link ICliSessionsService}. Never carries a token. */
1216
- interface ICliSessionInfo {
1217
- id: string;
1218
- userId: string;
1219
- userName: string;
1220
- createdAt: number;
1221
- lastActivity: number;
1222
- /** When the session expires if idle; undefined when there is no timeout */
1223
- expiresAt?: number;
1224
- /** True for the caller's own session */
1225
- current?: boolean;
1226
- metadata?: {
1227
- userAgent?: string;
1228
- remoteAddress?: string;
1229
- [key: string]: unknown;
1230
- };
1283
+ interface CliMultiSelectOptions {
1284
+ /** Enable type-to-filter. When true, printable keystrokes filter the option list. Default: false. */
1285
+ searchable?: boolean;
1286
+ /** Callback invoked each time the set of checked options changes */
1287
+ onChange?: (values: string[]) => void;
1231
1288
  }
1232
1289
  /**
1233
- * Lists and revokes sign-in sessions. Implemented locally (sessions of this
1234
- * browser) and by servers (all sessions). Used by `sessions` / `session-kick`.
1290
+ * Options for readNumber input.
1235
1291
  */
1236
- interface ICliSessionsService {
1237
- /** All sessions visible to the caller */
1238
- list(): Promise<ICliSessionInfo[]>;
1239
- /** Revoke a session by id */
1240
- revoke(sessionId: string, reason?: string): Promise<void>;
1241
- /** Revoke every session of a user (optionally keeping one) */
1242
- revokeUser(userId: string, options?: {
1243
- exceptSessionId?: string;
1244
- reason?: string;
1245
- }): Promise<void>;
1292
+ interface CliNumberOptions {
1293
+ /** Minimum allowed value */
1294
+ min?: number;
1295
+ /** Maximum allowed value */
1296
+ max?: number;
1297
+ /** Pre-filled default value */
1298
+ default?: number;
1246
1299
  }
1247
-
1248
1300
  /**
1249
- * Represents a service that manages user sessions in the CLI
1301
+ * Options for readDate input.
1250
1302
  */
1251
- interface ICliUserSessionService {
1252
- /** Gets the current user session as a reactive stream */
1253
- getUserSession(): Observable<ICliUserSession | undefined>;
1254
- /** Sets the current user session */
1255
- setUserSession(session: ICliUserSession): Promise<void>;
1256
- /** Clears the current session (logout) */
1257
- clearSession(): Promise<void>;
1258
- /** Persists the current session to storage */
1259
- persistSession(): Promise<void>;
1260
- /** Restores a session from storage (called on boot) */
1261
- restoreSession(): Promise<ICliUserSession | undefined>;
1303
+ interface CliDateOptions {
1304
+ /**
1305
+ * Date format string. Supported tokens: YYYY (4-digit year), MM (2-digit month),
1306
+ * DD (2-digit day). Separators can be `-`, `/`, or `.`.
1307
+ * @default 'YYYY-MM-DD'
1308
+ */
1309
+ format?: string;
1310
+ /** Minimum allowed date in the same format as `format` */
1311
+ min?: string;
1312
+ /** Maximum allowed date in the same format as `format` */
1313
+ max?: string;
1314
+ /** Pre-filled default value */
1315
+ default?: string;
1262
1316
  }
1263
1317
  /**
1264
- * Represents a service that manages users in the CLI
1318
+ * Provides interactive input reading from the terminal.
1319
+ * All methods resolve with `null` when the user aborts (Ctrl+C or Escape).
1265
1320
  */
1266
- interface ICliUsersStoreService {
1267
- /** Gets users with optional filtering and pagination */
1268
- getUsers(options?: {
1269
- query?: string;
1270
- skip?: number;
1271
- take?: number;
1272
- }): Observable<ICliUser[]>;
1273
- /** Creates a new user */
1274
- createUser(user: CliAddUser): Promise<ICliUser>;
1275
- /** Gets a user by id, name, or email */
1276
- getUser(id: string): Observable<ICliUser | undefined>;
1277
- /** Updates an existing user */
1278
- updateUser(id: string, updates: CliUpdateUser): Promise<ICliUser>;
1279
- /** Deletes a user by id */
1280
- deleteUser(id: string): Promise<void>;
1321
+ interface ICliInputReader {
1322
+ /**
1323
+ * Read a line of text input.
1324
+ * @param prompt - The prompt text displayed to the user
1325
+ * @param options - Optional configuration for default value, validation, and placeholder
1326
+ * @returns The entered text, or null if aborted
1327
+ */
1328
+ readLine(prompt: string, options?: CliLineOptions): Promise<string | null>;
1329
+ /**
1330
+ * Read a password (masked input).
1331
+ * @param prompt - The prompt text displayed to the user
1332
+ * @returns The entered password, or null if aborted
1333
+ */
1334
+ readPassword(prompt: string): Promise<string | null>;
1335
+ /**
1336
+ * Read a yes/no confirmation.
1337
+ * @param prompt - The prompt text displayed to the user
1338
+ * @param defaultValue - Default value when Enter is pressed without input
1339
+ * @returns true for yes, false for no, or null if aborted
1340
+ */
1341
+ readConfirm(prompt: string, defaultValue?: boolean): Promise<boolean | null>;
1342
+ /**
1343
+ * Read a selection from a vertical list of options.
1344
+ * @param prompt - The prompt text displayed to the user
1345
+ * @param options - Array of options to choose from
1346
+ * @param selectOptions - Optional configuration for default, search, onChange
1347
+ * @returns The selected option's value, or null if aborted
1348
+ */
1349
+ readSelect(prompt: string, options: CliSelectOption[], selectOptions?: CliSelectOptions): Promise<string | null>;
1350
+ /**
1351
+ * Read a selection from an inline horizontal list.
1352
+ * @param prompt - The prompt text displayed to the user
1353
+ * @param options - Array of options to choose from
1354
+ * @param selectOptions - Optional configuration for default, search, onChange
1355
+ * @returns The selected option's value, or null if aborted
1356
+ */
1357
+ readSelectInline(prompt: string, options: CliSelectOption[], selectOptions?: CliSelectOptions): Promise<string | null>;
1358
+ /**
1359
+ * Read multiple selections from a checkbox list.
1360
+ * @param prompt - The prompt text displayed to the user
1361
+ * @param options - Array of options with optional pre-checked state
1362
+ * @param selectOptions - Optional configuration for search, onChange
1363
+ * @returns Array of selected option values, or null if aborted
1364
+ */
1365
+ readMultiSelect(prompt: string, options: CliMultiSelectOption[], selectOptions?: CliMultiSelectOptions): Promise<string[] | null>;
1366
+ /**
1367
+ * Read a number input.
1368
+ * @param prompt - The prompt text displayed to the user
1369
+ * @param options - Optional configuration for min, max, and default value
1370
+ * @returns The entered number, or null if aborted
1371
+ */
1372
+ readNumber(prompt: string, options?: CliNumberOptions): Promise<number | null>;
1373
+ /**
1374
+ * Read a date input with format validation.
1375
+ * @param prompt - The prompt text displayed to the user
1376
+ * @param options - Optional configuration for format, min/max range, default
1377
+ * @returns The date string in the specified format, or null if aborted
1378
+ */
1379
+ readDate(prompt: string, options?: CliDateOptions): Promise<string | null>;
1380
+ /**
1381
+ * Read file(s) via the environment's file picker dialog.
1382
+ * @param prompt - The prompt text displayed to the user
1383
+ * @param options - Optional configuration for multiple, accept filter, directory mode
1384
+ * @returns Array of selected files with content, or null if cancelled/unsupported
1385
+ */
1386
+ readFile(prompt: string, options?: CliFilePickerOptions): Promise<CliFileResult[] | null>;
1387
+ }
1388
+
1389
+ interface ICliProgressBar {
1390
+ /**
1391
+ * Indicates if the progress bar is running
1392
+ */
1393
+ isRunning: boolean;
1394
+ /**
1395
+ * Show the progress bar
1396
+ */
1397
+ show: (text?: string) => void;
1398
+ /**
1399
+ * Hide the progress bar
1400
+ */
1401
+ hide: () => void;
1281
1402
  }
1282
1403
  /**
1283
- * Represents a service that manages groups in the CLI
1404
+ * Represents a spinner for the CLI
1284
1405
  */
1285
- interface ICliGroupsStoreService {
1286
- /** Gets all groups */
1287
- getGroups(): Observable<ICliGroup[]>;
1288
- /** Gets a group by id or name */
1289
- getGroup(id: string): Observable<ICliGroup | undefined>;
1290
- /** Creates a new group (role), optionally with permissions */
1291
- createGroup(name: string, description?: string, permissions?: string[]): Promise<ICliGroup>;
1292
- /** Updates a group's description and/or permissions (by id or name) */
1293
- updateGroup(id: string, updates: {
1294
- description?: string;
1295
- permissions?: string[];
1296
- }): Promise<ICliGroup>;
1297
- /** Adds a user (id or name) to a group (id or name) */
1298
- addMember(groupId: string, userId: string): Promise<void>;
1299
- /** Removes a user (id or name) from a group (id or name) */
1300
- removeMember(groupId: string, userId: string): Promise<void>;
1301
- /** Deletes a group by id */
1302
- deleteGroup(id: string): Promise<void>;
1303
- /** Gets all users that belong to a group */
1304
- getGroupMembers(groupId: string): Observable<ICliUser[]>;
1406
+ interface ICliSpinner extends ICliProgressBar {
1407
+ /**
1408
+ * Set the text of the spinner
1409
+ * @param text The text to set
1410
+ */
1411
+ setText: (text: string) => void;
1305
1412
  }
1413
+ type CliPercentageProgressBarUpdateValueOptions = {
1414
+ /**
1415
+ * The type of update to perform
1416
+ * @default 'replace'
1417
+ */
1418
+ type?: 'replace' | 'increment';
1419
+ };
1306
1420
  /**
1307
- * Represents a service that handles authentication in the CLI
1421
+ * Represents a progress bar for the CLI
1308
1422
  */
1309
- interface ICliAuthService {
1423
+ interface ICliPercentageProgressBar extends ICliProgressBar {
1310
1424
  /**
1311
- * Authenticates a user and returns a session. `password` may be omitted
1312
- * only when the implementation allows passwordless sign-in.
1425
+ * Update the progress of the progress bar.
1426
+ * @param progress The progress value (0–100). Values outside this range are clamped.
1427
+ * @param options Optional: update type ('replace' or 'increment')
1313
1428
  */
1314
- login(username: string, password?: string): Promise<ICliUserSession>;
1315
- /** Ends the current session (the CLI is left signed out) */
1316
- logout(): Promise<void>;
1429
+ update: (progress: number, options?: CliPercentageProgressBarUpdateValueOptions) => void;
1317
1430
  /**
1318
- * Switch to another user (`su`). Requires the target's password unless
1319
- * the caller may skip it (implementation-defined, e.g. `users:update`).
1320
- * Goes through the same lockout, events and audit as `login`.
1431
+ * Complete the progress bar
1432
+ * @returns void
1321
1433
  */
1322
- switchUser?(username: string, password?: string): Promise<ICliUserSession>;
1434
+ complete: () => void;
1323
1435
  /**
1324
- * Sets or updates a user's password.
1325
- * @param currentPassword The user's current password, when changing one's own
1326
- * password. Server-backed implementations forward it so the server can
1327
- * re-check it; local implementations may ignore it.
1436
+ * Set the text of the spinner
1437
+ * @param text The text to set
1328
1438
  */
1329
- setPassword(userId: string, password: string, currentPassword?: string): Promise<void>;
1330
- /** Verifies a password against stored credentials */
1331
- verifyPassword(userId: string, password: string): Promise<boolean>;
1332
- /** Hashes a password with a salt using Web Crypto SHA-256 */
1333
- hashPassword(password: string, salt: string): Promise<string>;
1334
- /** Validate password against policy. Returns error message or null if valid. */
1335
- validatePassword?(password: string): string | null;
1439
+ setText: (text: string) => void;
1440
+ }
1441
+ type CliTextAnimatorOptions = {
1442
+ /**
1443
+ * The speed of the animation
1444
+ * @default 100
1445
+ */
1446
+ speed?: number;
1447
+ /**
1448
+ * The text will be removed after typing
1449
+ */
1450
+ removeAfterTyping?: boolean;
1451
+ };
1452
+ interface ICliTextAnimator extends ICliProgressBar {
1453
+ /**
1454
+ * Show animated text in a typing and erasing effect.
1455
+ * @param text The text to animate
1456
+ * @param options Optional: speed (ms per character), removeAfterTyping
1457
+ * @returns A promise that resolves when the animation completes
1458
+ */
1459
+ showText: (text: string, options?: CliTextAnimatorOptions) => Promise<void>;
1460
+ /**
1461
+ * Stop the running animation, erase what it wrote and resolve the
1462
+ * `showText` promise. Optional for backward compatibility.
1463
+ */
1464
+ cancel?: () => void;
1465
+ }
1466
+
1467
+ /**
1468
+ * Result of a guard check.
1469
+ *
1470
+ * - `true` or `undefined`: allow the command.
1471
+ * - `false`: block it with the generic message `<command>: permission denied`.
1472
+ * - a non-empty `string`: block it and print this message.
1473
+ * - `{ allowed, message }`: explicit form; `message` is used when `allowed` is false.
1474
+ */
1475
+ type CliCommandGuardResult = boolean | string | void | {
1476
+ allowed: boolean;
1477
+ message?: string;
1478
+ };
1479
+ /**
1480
+ * A global command guard: a check that runs before **every** command, across
1481
+ * all processors and modules, and can block it.
1482
+ *
1483
+ * Use guards for cross-cutting policies such as authentication, permission
1484
+ * checks, feature flags, maintenance windows or rate limiting, instead of
1485
+ * repeating the check inside each processor.
1486
+ *
1487
+ * ## When guards run
1488
+ *
1489
+ * For each command the executor:
1490
+ * 1. parses the input and resolves the processor (and sub-processor);
1491
+ * 2. handles global parameters such as `--help` (help is never guarded);
1492
+ * 3. extracts the positional value into `command.value`;
1493
+ * 4. **runs the guards**;
1494
+ * 5. validates required values/parameters, runs processor hooks and finally
1495
+ * calls `processCommand`.
1496
+ *
1497
+ * Guards run in ascending `priority` (default `0`). For each guard the executor
1498
+ * calls `appliesTo` (if defined) and skips the guard when it returns `false`;
1499
+ * otherwise it awaits `canActivate`. The **first** guard that blocks stops the
1500
+ * chain: its message is written as an error and the command exits with code
1501
+ * `-1`. A guard that throws (in `appliesTo` or `canActivate`) also blocks the
1502
+ * command, so a broken guard fails closed.
1503
+ *
1504
+ * ## Access to the context
1505
+ *
1506
+ * Both `appliesTo` and `canActivate` receive the {@link ICliExecutionContext}
1507
+ * of the command about to run, so a guard can:
1508
+ * - resolve services with `context.services.get(...)` / `getRequired(...)`
1509
+ * (e.g. a policy, feature-flag or permission service registered by any
1510
+ * module) to decide **what** should be guarded and **whether** it may run;
1511
+ * - inspect `context.userSession` for the signed-in user and their groups;
1512
+ * - read the processor's `metadata` (for example `requireAuth` or
1513
+ * `requiredGroups`) and the parsed `command` (`command.value`, `command.args`);
1514
+ * - prompt the user via `context.reader` (e.g. a confirmation guard).
1515
+ *
1516
+ * Guards should not write to the terminal to report a denial; return a message
1517
+ * instead so the executor prints it consistently.
1518
+ *
1519
+ * ## Registering guards
1520
+ *
1521
+ * The preferred way is the module's `guards` array; the boot sequence
1522
+ * registers each entry as a multi-provider under {@link ICliCommandGuard_TOKEN}.
1523
+ * Several modules can contribute guards; all of them run.
1524
+ *
1525
+ * ```ts
1526
+ * const billingModule: ICliModule = {
1527
+ * apiVersion: 2,
1528
+ * name: 'billing',
1529
+ * processors: [new InvoiceProcessor()],
1530
+ * guards: [billingGuard],
1531
+ * };
1532
+ * ```
1533
+ *
1534
+ * Guards can also be added at runtime (e.g. from `onInit`):
1535
+ *
1536
+ * ```ts
1537
+ * context.services.set({ provide: ICliCommandGuard_TOKEN, useValue: myGuard, multi: true });
1538
+ * ```
1539
+ *
1540
+ * ## Example: policy resolved from a service
1541
+ *
1542
+ * ```ts
1543
+ * const featureFlagGuard: ICliCommandGuard = {
1544
+ * name: 'feature-flags',
1545
+ * // Only guard commands that are behind a flag; the flag list comes from a service
1546
+ * appliesTo: (processor, _command, context) =>
1547
+ * context.services.get<FeatureFlags>(FeatureFlags_TOKEN)?.isFlagged(processor.command) ?? false,
1548
+ * canActivate: async (_command, processor, context) => {
1549
+ * const flags = context.services.getRequired<FeatureFlags>(FeatureFlags_TOKEN);
1550
+ * return (await flags.isEnabled(processor.command, context.userSession?.user))
1551
+ * ? true
1552
+ * : `${processor.command}: not available for your account`;
1553
+ * },
1554
+ * };
1555
+ * ```
1556
+ *
1557
+ * ## Without a guard-providing module
1558
+ *
1559
+ * Guards are opt-in. When no guard is registered every command runs as before.
1560
+ * The engine registers three built-in guards that enforce the processor access
1561
+ * metadata (`requireAuth`, `requiredGroups`, `requiredPermissions`); they stay
1562
+ * inactive until an auth module (e.g. `@qodalis/cli-users`) registers an
1563
+ * `ICliAuthorizationService`, so without one that metadata is informational.
1564
+ */
1565
+ interface ICliCommandGuard {
1566
+ /** Unique name, shown in diagnostics when the guard throws. */
1567
+ name: string;
1568
+ /** Execution order; lower runs first. Defaults to `0`. */
1569
+ priority?: number;
1570
+ /**
1571
+ * Optional filter deciding whether this guard is relevant for the command.
1572
+ * Return `false` to skip the guard. When omitted the guard applies to
1573
+ * every command. May be async, and may use `context` to resolve services.
1574
+ */
1575
+ appliesTo?(processor: ICliCommandProcessor, command: CliProcessCommand, context: ICliExecutionContext): boolean | Promise<boolean>;
1576
+ /**
1577
+ * Decide whether the command may run. See {@link CliCommandGuardResult}
1578
+ * for the accepted return values.
1579
+ */
1580
+ canActivate(command: CliProcessCommand, processor: ICliCommandProcessor, context: ICliExecutionContext): CliCommandGuardResult | Promise<CliCommandGuardResult>;
1336
1581
  }
1582
+ /** DI token for {@link ICliCommandGuard}; register guards as multi-providers. */
1583
+ declare const ICliCommandGuard_TOKEN: CliToken<ICliCommandGuard>;
1337
1584
 
1338
1585
  /**
1339
1586
  * Represents a serialized snapshot of a CLI engine's state.
@@ -1496,6 +1743,210 @@ interface ICliBackgroundServiceRegistry {
1496
1743
  destroyAll(): Promise<void>;
1497
1744
  }
1498
1745
 
1746
+ /**
1747
+ * One event on the CLI event bus.
1748
+ */
1749
+ interface CliEvent<T = unknown> {
1750
+ /** Namespaced type, e.g. `package:installed` or `todo:added`. */
1751
+ type: string;
1752
+ /** Event payload. */
1753
+ data: T;
1754
+ /** Who emitted it (module or command name), when known. */
1755
+ source?: string;
1756
+ /** When it was emitted (ms since epoch). */
1757
+ timestamp: number;
1758
+ }
1759
+ type CliEventHandler<T = unknown> = (event: CliEvent<T>) => void;
1760
+ /**
1761
+ * Payload types of the known events, keyed by event type. The built-in
1762
+ * events are listed here; plugins add their own with declaration merging:
1763
+ *
1764
+ * ```ts
1765
+ * declare module '@qodalis/cli-core' {
1766
+ * interface CliEventMap {
1767
+ * 'todo:added': { id: number; text: string };
1768
+ * }
1769
+ * }
1770
+ * ```
1771
+ *
1772
+ * Events not listed can still be emitted and received; their payload is
1773
+ * then `unknown`.
1774
+ */
1775
+ interface CliEventMap {
1776
+ /** Boot finished: modules are set up and the terminal accepts input. */
1777
+ 'boot:completed': {
1778
+ durationMs: number;
1779
+ modules: number;
1780
+ commands: number;
1781
+ };
1782
+ /** A command line finished running (typed, or run with `engine.execute`). */
1783
+ 'command:executed': {
1784
+ command: string;
1785
+ exitCode: number;
1786
+ durationMs: number;
1787
+ };
1788
+ /** A module (plugin) was booted, at startup or later (e.g. `pkg add`). */
1789
+ 'module:loaded': {
1790
+ name: string;
1791
+ version?: string;
1792
+ commands: number;
1793
+ };
1794
+ /** A module was unloaded (e.g. `pkg remove`, or before `pkg update`). */
1795
+ 'module:unloaded': {
1796
+ name: string;
1797
+ };
1798
+ /** `pkg add` installed a package. */
1799
+ 'package:installed': {
1800
+ name: string;
1801
+ version: string;
1802
+ };
1803
+ /** `pkg update` changed a package's version. */
1804
+ 'package:updated': {
1805
+ name: string;
1806
+ version: string;
1807
+ previousVersion: string;
1808
+ };
1809
+ /** `pkg remove` removed a package. */
1810
+ 'package:removed': {
1811
+ name: string;
1812
+ version: string;
1813
+ };
1814
+ /** A server connected (at boot, or after a reconnect). */
1815
+ 'server:connected': {
1816
+ name: string;
1817
+ url: string;
1818
+ commands: number;
1819
+ reconnected: boolean;
1820
+ };
1821
+ /** A server could not be reached at boot. */
1822
+ 'server:unavailable': {
1823
+ name: string;
1824
+ url: string;
1825
+ };
1826
+ /** A connected server went away. */
1827
+ 'server:disconnected': {
1828
+ name: string;
1829
+ };
1830
+ /** An event pushed by a server over its event socket. */
1831
+ 'server:event': {
1832
+ server: string;
1833
+ type: string;
1834
+ data: unknown;
1835
+ };
1836
+ /** Background service lifecycle (`service:registered`, `service:started`, ...). */
1837
+ 'service:registered': {
1838
+ name: string;
1839
+ [key: string]: unknown;
1840
+ };
1841
+ 'service:started': {
1842
+ name: string;
1843
+ [key: string]: unknown;
1844
+ };
1845
+ 'service:stopped': {
1846
+ name: string;
1847
+ [key: string]: unknown;
1848
+ };
1849
+ 'service:completed': {
1850
+ name: string;
1851
+ [key: string]: unknown;
1852
+ };
1853
+ 'service:failed': {
1854
+ name: string;
1855
+ error?: string;
1856
+ [key: string]: unknown;
1857
+ };
1858
+ 'service:status': {
1859
+ name: string;
1860
+ [key: string]: unknown;
1861
+ };
1862
+ /** A custom event emitted by a background service with `context.emit()`. */
1863
+ 'service:event': {
1864
+ service: string;
1865
+ type: string;
1866
+ data: unknown;
1867
+ };
1868
+ /** A setting changed with `configure` (`category` is `system` or a plugin key). */
1869
+ 'config:changed': {
1870
+ category: string;
1871
+ key: string;
1872
+ value: unknown;
1873
+ };
1874
+ /** A configuration category was reset to its defaults. */
1875
+ 'config:reset': {
1876
+ category: string;
1877
+ };
1878
+ /** The signed-in user changed (sign-in, sign-out, `su`). */
1879
+ 'session:changed': {
1880
+ user?: string;
1881
+ };
1882
+ }
1883
+ /** Event types with a known payload. */
1884
+ type CliEventType = keyof CliEventMap;
1885
+ /** Payload of event `K`: from {@link CliEventMap}, or `unknown`. */
1886
+ type CliEventData<K extends string> = K extends CliEventType ? CliEventMap[K] : unknown;
1887
+ /**
1888
+ * Publish/subscribe bus shared by the system, modules and commands of one
1889
+ * terminal. Handlers run synchronously in subscription order; a failing
1890
+ * handler is logged and does not affect the others or the emitter.
1891
+ *
1892
+ * Patterns: an exact type (`package:installed`), a namespace wildcard
1893
+ * (`package:*`) or `*` for everything.
1894
+ *
1895
+ * Events live in memory: each terminal has its own bus for its lifetime,
1896
+ * with a short recent history, and nothing is persisted.
1897
+ *
1898
+ * Get it from a command or module with `context.events`, or from the
1899
+ * services container with {@link ICliEventBus_TOKEN}.
1900
+ */
1901
+ interface ICliEventBus {
1902
+ /**
1903
+ * Emit an event to every matching handler.
1904
+ *
1905
+ * ```ts
1906
+ * bus.emit('package:installed', { name, version }); // checked against CliEventMap
1907
+ * bus.emit<TodoAdded>('todo:added', { id, text }); // explicit payload type
1908
+ * bus.emit('todo:refresh'); // custom event, no payload
1909
+ * ```
1910
+ */
1911
+ emit<K extends CliEventType>(type: K, data: CliEventMap[K], options?: CliEmitOptions): void;
1912
+ emit<T = unknown, K extends string = string>(type: K & CustomEventType<K>, data?: T, options?: CliEmitOptions): void;
1913
+ /**
1914
+ * Handle events matching `pattern`. Returns a function that unsubscribes.
1915
+ *
1916
+ * ```ts
1917
+ * bus.on('package:installed', (e) => e.data.version); // typed from CliEventMap
1918
+ * bus.on<TodoAdded>('todo:added', (e) => e.data.text); // explicit payload type
1919
+ * bus.on('server:*', (e) => console.log(e.type)); // anonymous: data is unknown
1920
+ * ```
1921
+ */
1922
+ on<K extends CliEventType>(pattern: K, handler: CliEventHandler<CliEventMap[K]>): () => void;
1923
+ on<T = unknown>(pattern: string, handler: CliEventHandler<T>): () => void;
1924
+ /** Like `on`, but only for the next matching event. */
1925
+ once<K extends CliEventType>(pattern: K, handler: CliEventHandler<CliEventMap[K]>): () => void;
1926
+ once<T = unknown>(pattern: string, handler: CliEventHandler<T>): () => void;
1927
+ /**
1928
+ * Recent events matching `pattern`, oldest first. The bus keeps the last
1929
+ * events in memory only: events are not persisted across reloads (state
1930
+ * that must survive a reload belongs in a state store).
1931
+ */
1932
+ history<K extends CliEventType>(pattern: K, limit?: number): CliEvent<CliEventMap[K]>[];
1933
+ history<T = unknown>(pattern?: string, limit?: number): CliEvent<T>[];
1934
+ /** Matching events as an observable (completes when the bus is disposed). */
1935
+ events$<K extends CliEventType>(pattern: K): Observable<CliEvent<CliEventMap[K]>>;
1936
+ events$<T = unknown>(pattern?: string): Observable<CliEvent<T>>;
1937
+ }
1938
+ interface CliEmitOptions {
1939
+ /** Who emits the event (module or command name). */
1940
+ source?: string;
1941
+ }
1942
+ /**
1943
+ * A type that is not a key of {@link CliEventMap}. Keeps `emit` from accepting
1944
+ * a known event with an unchecked payload.
1945
+ */
1946
+ type CustomEventType<K extends string> = K extends CliEventType ? never : K;
1947
+ /** Service token of the terminal's {@link ICliEventBus}. */
1948
+ declare const ICliEventBus_TOKEN: CliToken<ICliEventBus>;
1949
+
1499
1950
  /**
1500
1951
  * Messages sent from the main thread to a background service Worker.
1501
1952
  */
@@ -1529,38 +1980,6 @@ type CliWorkerOutboundMessage = {
1529
1980
  stack?: string;
1530
1981
  };
1531
1982
 
1532
- /**
1533
- * Represents an entry in a file listing.
1534
- */
1535
- interface ICliFileEntry {
1536
- name: string;
1537
- type: 'file' | 'directory';
1538
- size: number;
1539
- modified: string;
1540
- }
1541
-
1542
- /**
1543
- * Abstraction for file transfer operations.
1544
- * Default implementation uses browser-native APIs (file picker, download dialog).
1545
- * @qodalis/cli-files overrides with virtual FS backed by IndexedDB.
1546
- */
1547
- interface ICliFileTransferService {
1548
- readFile(path: string): Promise<string | null>;
1549
- writeFile(path: string, content: string): Promise<void>;
1550
- listFiles(path: string): Promise<ICliFileEntry[]>;
1551
- exists(path: string): Promise<boolean>;
1552
- downloadToBrowser(filename: string, content: string | Blob): void;
1553
- uploadFromBrowser(accept?: string): Promise<{
1554
- name: string;
1555
- content: string;
1556
- } | null>;
1557
- }
1558
-
1559
- interface ICliDragDropService {
1560
- /** Emits an array of dropped File objects each time files are dropped onto the terminal */
1561
- readonly onFileDrop: Observable<File[]>;
1562
- }
1563
-
1564
1983
  /**
1565
1984
  * Opaque state value carried between lines during syntax highlighting.
1566
1985
  * Encodes context such as "inside a multi-line string" or "inside a block comment"
@@ -1618,10 +2037,37 @@ declare enum CliLogLevel {
1618
2037
  WARN = 4,
1619
2038
  ERROR = 5
1620
2039
  }
2040
+ /**
2041
+ * How the CLI reports its startup.
2042
+ */
2043
+ interface CliBootOptions {
2044
+ /**
2045
+ * Report each boot step (terminal, services, servers, modules) in the
2046
+ * terminal while the CLI starts. When `false`, a spinner is shown instead.
2047
+ * Users can override it with `configure set system.bootLogs false`.
2048
+ * @default true
2049
+ */
2050
+ showLogs?: boolean;
2051
+ /**
2052
+ * Keep the boot report on screen after startup instead of clearing it
2053
+ * when the terminal is ready. Warnings and errors are kept either way.
2054
+ * @default false
2055
+ */
2056
+ keepLogs?: boolean;
2057
+ /**
2058
+ * Steps taking at least this many milliseconds are highlighted as slow.
2059
+ * @default 500
2060
+ */
2061
+ slowStepMs?: number;
2062
+ }
1621
2063
  /**
1622
2064
  * Options for the CLI
1623
2065
  */
1624
2066
  type CliOptions = Record<string, any> & {
2067
+ /**
2068
+ * How the CLI reports its startup (boot log, spinner).
2069
+ */
2070
+ boot?: CliBootOptions;
1625
2071
  /**
1626
2072
  * Custom terminal options
1627
2073
  */
@@ -1692,6 +2138,13 @@ type CliProcessorMetadata = Record<string, any> & {
1692
2138
  * Sealed processors can still be extended (wrapped) by processors with `extendsProcessor = true`.
1693
2139
  */
1694
2140
  sealed?: boolean;
2141
+ /**
2142
+ * The command's text is code or other literal input (`eval`): the engine
2143
+ * passes the whole line as typed, without operators (`|`, `;`, `&&`,
2144
+ * `>`) or expansion of `$(...)`, `$VAR`, braces and wildcards, so
2145
+ * `eval [1,2].map(x => x * 2)` keeps its `=>`, `*` and `${...}`.
2146
+ */
2147
+ literalInput?: boolean;
1695
2148
  /**
1696
2149
  * If true, the processor requires the server to be running
1697
2150
  */
@@ -1751,7 +2204,45 @@ type CliStateConfiguration = {
1751
2204
  * @remarks If the store identifier is not set, the processor command name is used
1752
2205
  */
1753
2206
  storeName?: string;
2207
+ /**
2208
+ * Save the state after every change, so `persist()` does not have to be
2209
+ * called. Changes made in the same tick are saved together.
2210
+ * @default false
2211
+ */
2212
+ autoPersist?: boolean;
2213
+ /**
2214
+ * Version of the state's shape. When the saved state has an older
2215
+ * version, `migrate` converts it while it is loaded. Saved state without
2216
+ * a version counts as version 0.
2217
+ */
2218
+ version?: number;
2219
+ /**
2220
+ * Convert saved state of version `fromVersion` to the current
2221
+ * {@link version}. The result is merged over `initialState`.
2222
+ */
2223
+ migrate?: (state: CliState, fromVersion: number) => CliState;
2224
+ /**
2225
+ * Apply changes saved by other tabs and terminals of the same browser.
2226
+ * @default true
2227
+ */
2228
+ syncAcrossTabs?: boolean;
2229
+ /**
2230
+ * Where the state lives:
2231
+ * - `'global'`: saved in the browser and shared by every terminal and tab
2232
+ * (each save merges the keys it changed, so terminals changing
2233
+ * different keys never overwrite each other).
2234
+ * - `'user'`: like `'global'`, but saved separately for each signed-in
2235
+ * user (signed out, the state is shared); it switches when the user
2236
+ * signs in or out.
2237
+ * - `'session'`: this terminal only, in memory; never saved or shared.
2238
+ * @default 'global'
2239
+ */
2240
+ scope?: CliStateScope;
1754
2241
  };
2242
+ /** See {@link CliStateConfiguration.scope}. */
2243
+ type CliStateScope = 'global' | 'user' | 'session';
2244
+ /** Options of a state store (see {@link CliStateConfiguration}). */
2245
+ type CliStateStoreOptions = Pick<CliStateConfiguration, 'autoPersist' | 'version' | 'migrate' | 'syncAcrossTabs' | 'scope'>;
1755
2246
  type CliState = Record<string, any>;
1756
2247
  /**
1757
2248
  * Position of the CLI panel relative to the viewport edge.
@@ -1894,114 +2385,6 @@ interface CliPanelPaneState {
1894
2385
  widthPercent: number;
1895
2386
  }
1896
2387
 
1897
- /**
1898
- * Options for HTTP client requests.
1899
- * Extends standard RequestInit with convenience fields.
1900
- */
1901
- interface CliHttpRequestOptions extends Omit<RequestInit, 'method' | 'body'> {
1902
- /**
1903
- * Request timeout in milliseconds. When set, the request is automatically
1904
- * aborted after this duration. Combines with any existing abort signal.
1905
- */
1906
- timeout?: number;
1907
- }
1908
- /**
1909
- * HTTP client for making requests from command processors.
1910
- *
1911
- * Automatically merges the command's abort signal so that requests are
1912
- * cancelled when the user presses Ctrl+C. Provides typed convenience
1913
- * methods for common HTTP verbs as well as a raw `fetch()` for cases
1914
- * that need full Response access (streaming, progress, etc.).
1915
- *
1916
- * Available on the execution context as `context.http`.
1917
- *
1918
- * @example
1919
- * ```ts
1920
- * // Typed JSON response
1921
- * const users = await context.http.get<User[]>('https://api.example.com/users');
1922
- *
1923
- * // POST with body
1924
- * await context.http.post('https://api.example.com/users', { name: 'John' });
1925
- *
1926
- * // Raw fetch for streaming
1927
- * const response = await context.http.fetch('https://example.com/file.zip');
1928
- * const reader = response.body?.getReader();
1929
- * ```
1930
- */
1931
- interface ICliHttpClient {
1932
- /**
1933
- * Make a raw HTTP request. Returns the full Response object for cases
1934
- * that need streaming, headers, or status inspection.
1935
- * The command's abort signal is automatically merged.
1936
- */
1937
- fetch(url: string, init?: RequestInit): Promise<Response>;
1938
- /**
1939
- * HTTP GET request. Returns the parsed JSON response body.
1940
- */
1941
- get<T = any>(url: string, options?: CliHttpRequestOptions): Promise<T>;
1942
- /**
1943
- * HTTP POST request. Automatically serializes the body as JSON
1944
- * (unless body is FormData, Blob, string, or ArrayBuffer).
1945
- * Returns the parsed JSON response body.
1946
- */
1947
- post<T = any>(url: string, body?: any, options?: CliHttpRequestOptions): Promise<T>;
1948
- /**
1949
- * HTTP PUT request. Automatically serializes the body as JSON.
1950
- * Returns the parsed JSON response body.
1951
- */
1952
- put<T = any>(url: string, body?: any, options?: CliHttpRequestOptions): Promise<T>;
1953
- /**
1954
- * HTTP PATCH request. Automatically serializes the body as JSON.
1955
- * Returns the parsed JSON response body.
1956
- */
1957
- patch<T = any>(url: string, body?: any, options?: CliHttpRequestOptions): Promise<T>;
1958
- /**
1959
- * HTTP DELETE request. Returns the parsed JSON response body (if any).
1960
- */
1961
- delete<T = any>(url: string, options?: CliHttpRequestOptions): Promise<T>;
1962
- /**
1963
- * HTTP HEAD request. Returns the full Response (no body parsing).
1964
- */
1965
- head(url: string, options?: CliHttpRequestOptions): Promise<Response>;
1966
- /**
1967
- * HTTP OPTIONS request. Returns the parsed JSON response body.
1968
- */
1969
- options<T = any>(url: string, options?: CliHttpRequestOptions): Promise<T>;
1970
- }
1971
- /**
1972
- * Error thrown by `ICliHttpClient` JSON helpers (`get`, `post`, `put`,
1973
- * `patch`, `delete`, `options`) when the server responds with a non-2xx status.
1974
- *
1975
- * Exposes the HTTP status so callers can react to specific responses
1976
- * (for example, clearing a stored session token on `401`).
1977
- *
1978
- * @example
1979
- * ```ts
1980
- * try {
1981
- * await context.http.get(url);
1982
- * } catch (e) {
1983
- * if (e instanceof CliHttpError && e.status === 401) {
1984
- * // token expired or revoked
1985
- * }
1986
- * }
1987
- * ```
1988
- */
1989
- declare class CliHttpError extends Error {
1990
- /** HTTP status code of the failed response */
1991
- readonly status: number;
1992
- /** HTTP status text of the failed response */
1993
- readonly statusText: string;
1994
- /** Parsed JSON error body, when the server returned one */
1995
- readonly body?: unknown | undefined;
1996
- constructor(message: string,
1997
- /** HTTP status code of the failed response */
1998
- status: number,
1999
- /** HTTP status text of the failed response */
2000
- statusText?: string,
2001
- /** Parsed JSON error body, when the server returned one */
2002
- body?: unknown | undefined);
2003
- }
2004
-
2005
2388
  type CliNotificationLevel = 'info' | 'success' | 'warn' | 'error';
2006
2389
  interface CliNotification {
2007
2390
  level: CliNotificationLevel;
@@ -2026,64 +2409,6 @@ interface ICliNotifier {
2026
2409
  readonly change$: Observable<CliNotification | undefined>;
2027
2410
  }
2028
2411
 
2029
- /** Represents a user lifecycle event */
2030
- interface ICliUserEvent {
2031
- /** The type of event that occurred */
2032
- type: 'login' | 'logout' | 'login_failed' | 'user_created' | 'user_deleted' | 'user_updated' | 'password_changed' | 'user_switched' | 'user_disabled' | 'user_enabled' | 'group_created' | 'group_updated' | 'group_deleted' | 'group_member_added' | 'group_member_removed' | 'session_revoked' | 'audit_cleared';
2033
- /** The user involved in the event (for group events: the acting user) */
2034
- user: ICliUser;
2035
- /** The acting user's name, when different from `user` (e.g. an admin) */
2036
- actor?: string;
2037
- /** When the event occurred */
2038
- timestamp: number;
2039
- /** Optional additional details */
2040
- details?: string;
2041
- /** `user_updated`: the user's name before a rename (set only when the name changed) */
2042
- previousName?: string;
2043
- }
2044
- /** Service that emits user lifecycle events for plugins to subscribe to */
2045
- interface ICliUserEventService {
2046
- /** Observable stream of user events */
2047
- readonly events$: Observable<ICliUserEvent>;
2048
- /** Emit a user event */
2049
- emit(event: ICliUserEvent): void;
2050
- }
2051
-
2052
- /** A single audit log entry */
2053
- interface ICliAuditEntry {
2054
- /** Entry id (server-assigned in server mode) */
2055
- id?: string;
2056
- /** When the event occurred */
2057
- timestamp: number;
2058
- /** Event type (e.g., 'login', 'logout', 'login_failed') */
2059
- event: string;
2060
- /** Username involved */
2061
- username: string;
2062
- /** Optional additional details */
2063
- details?: string;
2064
- /** Id of the user the entry is about, when known */
2065
- userId?: string;
2066
- /** The user who performed the action, when different from `username` */
2067
- actor?: string;
2068
- /** Id of the acting user, when different from the subject */
2069
- actorId?: string;
2070
- /** Extra data (e.g. remote address) */
2071
- metadata?: Record<string, unknown>;
2072
- }
2073
- /** Service for persistent audit logging */
2074
- interface ICliAuditService {
2075
- /** Log an audit entry (timestamp is added automatically) */
2076
- log(entry: Omit<ICliAuditEntry, 'timestamp'>): Promise<void>;
2077
- /** Retrieve audit entries with optional filtering */
2078
- getEntries(options?: {
2079
- limit?: number;
2080
- event?: string;
2081
- username?: string;
2082
- }): Promise<ICliAuditEntry[]>;
2083
- /** Clear all audit entries */
2084
- clear(): Promise<void>;
2085
- }
2086
-
2087
2412
  /**
2088
2413
  * A managed timer handle returned by createInterval/createTimeout.
2089
2414
  * Managed timers are automatically cleared when full-screen mode exits
@@ -2130,6 +2455,16 @@ interface ICliExecutionContext {
2130
2455
  * The progress bar to use for showing progress
2131
2456
  */
2132
2457
  progressBar: ICliPercentageProgressBar;
2458
+ /**
2459
+ * The terminal's event bus: emit and subscribe to events shared by the
2460
+ * system, modules and commands. Optional for backward compatibility.
2461
+ */
2462
+ readonly events?: ICliEventBus;
2463
+ /**
2464
+ * `true` once the CLI has finished booting (modules set up, terminal
2465
+ * ready). Optional for backward compatibility; `undefined` means unknown.
2466
+ */
2467
+ readonly isBooted?: boolean;
2133
2468
  /**
2134
2469
  * A subject that emits when the command is aborted
2135
2470
  */
@@ -2768,6 +3103,7 @@ interface ICliServiceProvider {
2768
3103
  * @param service The token (typically a `*_TOKEN` constant) identifying the service
2769
3104
  * @returns The service instance cast to `T`, or `undefined` if not registered
2770
3105
  */
3106
+ get<T>(service: CliToken<T>): T | undefined;
2771
3107
  get<T = unknown>(service: any): T | undefined;
2772
3108
  /**
2773
3109
  * Retrieve all registered services for a multi-provider token.
@@ -2775,6 +3111,7 @@ interface ICliServiceProvider {
2775
3111
  * @param service The token (typically a `*_TOKEN` constant) identifying the service
2776
3112
  * @returns Array of all resolved service instances
2777
3113
  */
3114
+ getAll<T>(service: CliToken<T>): T[];
2778
3115
  getAll<T = unknown>(service: any): T[];
2779
3116
  /**
2780
3117
  * Retrieve a service by its injection token, throwing if not registered.
@@ -2784,6 +3121,7 @@ interface ICliServiceProvider {
2784
3121
  * @returns The service instance, cast to `T`
2785
3122
  * @throws If no service is registered for the given token
2786
3123
  */
3124
+ getRequired<T>(service: CliToken<T>): T;
2787
3125
  getRequired<T = unknown>(service: any): T;
2788
3126
  /**
2789
3127
  * Check whether a service is registered for the given token.
@@ -2796,11 +3134,20 @@ interface ICliServiceProvider {
2796
3134
  * @param definition A single provider or an array of providers to register
2797
3135
  */
2798
3136
  set(definition: CliProvider | CliProvider[]): void;
3137
+ /**
3138
+ * Remove a registration. With `provider` (the provider object passed to
3139
+ * `set`, or the registered value), only that registration is removed: for
3140
+ * a single token the previous registration becomes active again, for a
3141
+ * multi token the others remain. Without it, every registration of the
3142
+ * token is removed. Sealed tokens cannot be removed.
3143
+ * @returns Whether something was removed
3144
+ */
3145
+ remove?(service: any, provider?: unknown): boolean;
2799
3146
  }
2800
3147
  /** DI token for `ICliServerAuthTokenProvider` (multi-provider) */
2801
- declare const ICliServerAuthTokenProvider_TOKEN = "cli-server-auth-token-provider";
3148
+ declare const ICliServerAuthTokenProvider_TOKEN: CliToken<ICliServerAuthTokenProvider>;
2802
3149
  /** DI token for `ICliServerAuthService` */
2803
- declare const ICliServerAuthService_TOKEN = "cli-server-auth-service";
3150
+ declare const ICliServerAuthService_TOKEN: CliToken<ICliServerAuthService>;
2804
3151
  /**
2805
3152
  * Integrators implement this interface to supply auth credentials
2806
3153
  * for server requests. Multiple providers can be registered — the
@@ -2863,7 +3210,7 @@ interface ICliServerManager {
2863
3210
  preferredServer?(): string | null;
2864
3211
  }
2865
3212
  /** Token for {@link ICliServerManager} */
2866
- declare const ICliServerManager_TOKEN = "cli-server-manager";
3213
+ declare const ICliServerManager_TOKEN: CliToken<ICliServerManager>;
2867
3214
  /**
2868
3215
  * Internal service that resolves auth headers for a server by merging:
2869
3216
  * 1. Static/dynamic headers from `CliServerConfig.headers`
@@ -3001,6 +3348,22 @@ type CliServerCapabilities = {
3001
3348
  streaming?: boolean;
3002
3349
  };
3003
3350
 
3351
+ /**
3352
+ * A DI token that carries the type of the service it identifies, so
3353
+ * `services.get(TOKEN)` is typed without a type argument:
3354
+ *
3355
+ * ```ts
3356
+ * export const ITodoStore_TOKEN = createCliToken<ITodoStore>('todo-store');
3357
+ * const store = context.services.get(ITodoStore_TOKEN); // ITodoStore | undefined
3358
+ * ```
3359
+ *
3360
+ * At runtime it is the plain string, so it works anywhere a string token does.
3361
+ */
3362
+ type CliToken<T> = string & {
3363
+ readonly __cliTokenType?: T;
3364
+ };
3365
+ /** Create a typed {@link CliToken}. */
3366
+ declare function createCliToken<T>(name: string): CliToken<T>;
3004
3367
  type MultiServices = {
3005
3368
  /**
3006
3369
  * When true, injector returns an array of instances. This is useful to allow multiple
@@ -3017,10 +3380,15 @@ type CliTypeProvider = MultiServices & {
3017
3380
  deps?: any[];
3018
3381
  };
3019
3382
  type CliFactoryProvider = MultiServices & {
3020
- useFactory: Function;
3383
+ useFactory: (...args: any[]) => any;
3021
3384
  /** DI tokens resolved from the container and passed as factory arguments. */
3022
3385
  deps?: any[];
3023
3386
  };
3387
+ /**
3388
+ * Service registration. `useFactory` and `useClass` providers are created
3389
+ * lazily, on the first `get()` of their token (or of a token depending on
3390
+ * them), so their `deps` may be registered after them.
3391
+ */
3024
3392
  type CliProvider = {
3025
3393
  provide: any;
3026
3394
  /** When true, this token is sealed after registration and cannot be overwritten. */
@@ -3135,6 +3503,9 @@ declare const enums: {
3135
3503
  declare class CliModuleRegistry {
3136
3504
  private readonly modules;
3137
3505
  private readonly bootHandlers;
3506
+ private readonly unregisterHandlers;
3507
+ /** Registrations whose boot handlers are still running. */
3508
+ private readonly inFlight;
3138
3509
  /** Optional warning callback — set by the engine to route through ICliLogger */
3139
3510
  onWarn?: (message: string) => void;
3140
3511
  /**
@@ -3147,6 +3518,23 @@ declare class CliModuleRegistry {
3147
3518
  * skipped with a warning — this ensures only compatible frontend plugins load.
3148
3519
  */
3149
3520
  register(module: ICliModule): Promise<void>;
3521
+ /**
3522
+ * Resolve once every registration in progress (including ones started
3523
+ * meanwhile, e.g. a plugin loading another) has finished booting.
3524
+ */
3525
+ whenIdle(): Promise<void>;
3526
+ private registerInternal;
3527
+ /**
3528
+ * Register a handler that is called when a module is unregistered,
3529
+ * e.g. so its processors can be removed.
3530
+ */
3531
+ onModuleUnregister(handler: (module: ICliModule) => void): void;
3532
+ /**
3533
+ * Unregister a module and notify all unregister handlers. A module
3534
+ * registered again afterwards (e.g. a newer version) boots again.
3535
+ * Returns the removed module, or `undefined` when it was not registered.
3536
+ */
3537
+ unregister(name: string): ICliModule | undefined;
3150
3538
  /**
3151
3539
  * Get a module by name.
3152
3540
  */
@@ -3286,7 +3674,10 @@ declare class ServerVersionNegotiator {
3286
3674
  * Returns the negotiated API version and the base path for all subsequent calls,
3287
3675
  * or null if the server is unreachable or incompatible.
3288
3676
  */
3289
- static discover(baseUrl: string, headers?: CliHeadersProvider): Promise<{
3677
+ static discover(baseUrl: string, headers?: CliHeadersProvider, options?: {
3678
+ /** Rethrow when the server cannot be reached (instead of `null`, "no discovery") */
3679
+ rethrowNetworkErrors?: boolean;
3680
+ }): Promise<{
3290
3681
  apiVersion: number;
3291
3682
  basePath: string;
3292
3683
  } | null>;
@@ -3462,6 +3853,45 @@ declare const cliAccessMetadataGuards: ICliCommandGuard[];
3462
3853
  */
3463
3854
  declare function cliDefaultServer(context: Pick<ICliExecutionContext, 'services' | 'options'>): CliServerConfig | undefined;
3464
3855
 
3856
+ /** A help row: the left column and its description. */
3857
+ type CliHelpRow = readonly [string, string];
3858
+ /** Write a `📋 Usage:` block with aligned, highlighted commands. */
3859
+ declare function writeUsage(context: ICliExecutionContext, rows: readonly CliHelpRow[]): void;
3860
+ /** Write a `📝 Examples:` block with aligned `# comments`. */
3861
+ declare function writeExamples(context: ICliExecutionContext, rows: readonly CliHelpRow[]): void;
3862
+ /**
3863
+ * Write a paragraph wrapped at word boundaries to the terminal width (at
3864
+ * most 100 columns), so long help text never breaks inside a word.
3865
+ */
3866
+ declare function writeWrapped(context: ICliExecutionContext, text: string, indent?: number): void;
3867
+ /** `💡 Use <command> to <action>` hint. */
3868
+ declare function writeHint(context: ICliExecutionContext, command: string, action: string): void;
3869
+
3870
+ /**
3871
+ * Split text into arguments like a POSIX shell: spaces separate words,
3872
+ * quotes are removed (`"a b"` is one word), single quotes keep everything
3873
+ * literally, and a backslash escapes the next character outside quotes
3874
+ * (`my\ file`) or `"`, `\`, `$` and `` ` `` inside double quotes. A quote
3875
+ * without a closing one is ordinary text (`it's`).
3876
+ */
3877
+ declare function splitShellWords(text: string): string[];
3878
+ /**
3879
+ * The arguments as a shell's echo prints them: {@link splitShellWords}
3880
+ * joined by one space. Inside double quotes, a backslash before other
3881
+ * characters stays (`"a\nb"`) for echo's escape step.
3882
+ */
3883
+ declare function joinShellWords(text: string): string;
3884
+ /** Quote a value so {@link splitShellWords} reads it back as one word. */
3885
+ declare function quoteShellWord(value: string): string;
3886
+
3887
+ /**
3888
+ * Text given as one free-form argument (JSON, code, a query) without one
3889
+ * pair of quotes wrapping all of it: `'{"a": 1}'` -> `{"a": 1}`, and
3890
+ * `"say \"hi\""` -> `say "hi"`. Anything else is kept as typed, so quotes
3891
+ * and backslashes inside the text are never touched.
3892
+ */
3893
+ declare function unquoteRawArgument(text: string): string;
3894
+
3465
3895
  /**
3466
3896
  * Retrieve a parameter's value from the parsed arguments, checking aliases.
3467
3897
  * @param p The parameter descriptor
@@ -3614,7 +4044,7 @@ interface CliThemeInfo {
3614
4044
 
3615
4045
  declare const DefaultThemeInfos: Record<string, CliThemeInfo>;
3616
4046
 
3617
- declare const LIBRARY_VERSION = "2.0.0-beta.17";
4047
+ declare const LIBRARY_VERSION = "2.0.0-beta.18";
3618
4048
  declare const API_VERSION = 2;
3619
4049
 
3620
4050
  /**
@@ -3675,4 +4105,5 @@ declare class BrowserFileTransferService implements ICliFileTransferService {
3675
4105
  private _pickFile;
3676
4106
  }
3677
4107
 
3678
- export { API_VERSION, BrowserFileTransferService, CANCELLATION_ERROR_MESSAGE, CLI_CONFIGURE_STORE_NAME, CancellablePromise, type CliAccessAction, CliAccessDeniedError, type CliAddUser, CliBackgroundColor, type CliBackgroundServiceStatus, type CliBackgroundServiceType, type CliCommandGuardResult, type CliDateOptions, type CliEngineSnapshot, type CliFactoryProvider, type CliFilePickerOptions, type CliFileResult, CliForegroundColor, type CliFullScreenOptions, type CliHeadersProvider, CliHttpError, type CliHttpRequestOptions, CliIcon, type CliLineOptions, CliLogLevel, CliModuleRegistry, type CliMultiSelectOption, type CliMultiSelectOptions, type CliNotification, type CliNotificationLevel, type CliNumberOptions, type CliOptions, type CliPackageSource, type CliPanelConfig, type CliPanelHideAlignment, type CliPanelPaneState, type CliPanelPosition, type CliPanelState, type CliPanelTabState, type CliPercentageProgressBarUpdateValueOptions, type CliPermissionDefinition, type CliProcessCommand, type CliProcessorMetadata, type CliProvider, type CliSelectOption, type CliSelectOptions, type CliServerCapabilities, type CliServerCommandDescriptor, type CliServerConfig, type CliServerOutput, CliServerPermissions, type CliServerResponse, type CliServiceEventHandler, type CliServiceExecutionMode, type CliServiceLogLevel, type CliState, type CliStateConfiguration, type CliTableOptions, type CliTextAnimatorOptions, type CliTheme, type CliThemeCategory, type CliThemeInfo, type CliTypeProvider, type CliUpdateUser, CliUsersPermissions, type CliValueProvider, type CliWorkerInboundMessage, type CliWorkerOutboundMessage, DEFAULT_DIR_PERMISSIONS, DEFAULT_FILE_PERMISSIONS, DefaultLibraryAuthor, DefaultThemeInfos, DefaultThemes, type DefaultThemesType, type ICliAuditEntry, type ICliAuditService, ICliAuditService_TOKEN, type ICliAuthService, ICliAuthService_TOKEN, type ICliAuthorizationService, ICliAuthorizationService_TOKEN, type ICliBackgroundService, type ICliBackgroundServiceInfo, type ICliBackgroundServiceRegistry, type ICliClipboard, type ICliCommandAuthor, type ICliCommandChildProcessor, type ICliCommandExecutorService, type ICliCommandGuard, ICliCommandGuard_TOKEN, type ICliCommandParameterDescriptor, type ICliCommandProcessor, type ICliCommandProcessorRegistry, type ICliCompletionContext, type ICliCompletionProvider, ICliCompletionProvider_TOKEN, type ICliConfigurableModule, type ICliConfigurationOption, type ICliDragDropService, ICliDragDropService_TOKEN, type ICliExecutionContext, type ICliExecutionProcess, type ICliFileEntry, type ICliFilePickerProvider, ICliFilePickerProvider_TOKEN, type ICliFileTransferService, ICliFileTransferService_TOKEN, type ICliGlobalParameterHandler, type ICliGroup, type ICliGroupsStoreService, ICliGroupsStoreService_TOKEN, type ICliHttpClient, ICliHttpClient_TOKEN, type ICliInputReader, type ICliKeyValueStore, ICliKeyValueStore_TOKEN, type ICliLogger, type ICliManagedInterval, type ICliManagedTimer, type ICliModule, type ICliNotifier, type ICliOwnership, type ICliPanelRef, type ICliPercentageProgressBar, type ICliPermissionRegistry, ICliPermissionRegistry_TOKEN, type ICliPermissionService, ICliPermissionService_TOKEN, type ICliPingServerService, type ICliPrincipal, type ICliProcessEntry, type ICliProcessRegisterOptions, type ICliProcessRegistry, type ICliProcessorHook, type ICliProgressBar, type ICliProtectedResource, type ICliServerAuthService, ICliServerAuthService_TOKEN, type ICliServerAuthTokenProvider, ICliServerAuthTokenProvider_TOKEN, type ICliServerManager, ICliServerManager_TOKEN, type ICliServiceContext, type ICliServiceEvent, type ICliServiceLogEntry, type ICliServiceProvider, type ICliSessionInfo, type ICliSessionsService, ICliSessionsService_TOKEN, type ICliSpinner, type ICliStateStore, type ICliTerminalWriter, type ICliTextAnimator, type ICliTranslationService, ICliTranslationService_TOKEN, type ICliUmdModule, type ICliUser, type ICliUserCredentials, type ICliUserEvent, type ICliUserEventService, ICliUserEventService_TOKEN, type ICliUserSession, type ICliUserSessionService, ICliUserSessionService_TOKEN, type ICliUsersStoreService, ICliUsersStoreService_TOKEN, type ISyntaxHighlightRule, type ISyntaxHighlighter, type ISyntaxTheme, LIBRARY_VERSION, type LineState, ObjectDescriber, type Package, type ServerVersionInfo, ServerVersionNegotiator, type WorkerServiceContext, appendQueryParams, bootCliModule, bootUmdModule, buildAuthenticatedWebSocketUrl, clearTerminalLine, cliAccessMetadataGuards, cliAuthorize, cliCanAccess, cliDefaultServer, cliEvaluateAccess, cliHasPermission, cliMergePermissions, cliMissingPermissions, cliPermissionMatches, cliRequireAuthGuard, cliRequiredGroupsGuard, cliRequiredPermissionsGuard, colorFirstWord, colorizeJson, compareVersions, constants, createWorkerService, delay, derivePanelThemeStyles, enums, formatJson, getConfigValue, getParameterValue, getPluginConfigValue, getRightOfWord, highlightTextWithBg, initializeBrowserEnvironment, isCliAccessDeniedError, loadPanelPosition, nextPanelPosition, resolveConfigurationCategories, resolveHeaders, resolveServerHeaders, resolveServerWebSocketUrl, satisfiesMinVersion, satisfiesVersionRange, savePanelPosition, stripAnsi, toQueryString, truncateToWidth, utils, visibleLength };
4108
+ export { API_VERSION, BrowserFileTransferService, CANCELLATION_ERROR_MESSAGE, CLI_CONFIGURE_STORE_NAME, CancellablePromise, CliAccessDeniedError, CliBackgroundColor, CliForegroundColor, CliHttpError, CliIcon, CliLogLevel, CliModuleRegistry, CliServerPermissions, CliUsersPermissions, DEFAULT_DIR_PERMISSIONS, DEFAULT_FILE_PERMISSIONS, DefaultLibraryAuthor, DefaultThemeInfos, DefaultThemes, ICliAuditService_TOKEN, ICliAuthService_TOKEN, ICliAuthorizationService_TOKEN, ICliCommandGuard_TOKEN, ICliCompletionProvider_TOKEN, ICliDragDropService_TOKEN, ICliEventBus_TOKEN, ICliFilePickerProvider_TOKEN, ICliFileTransferService_TOKEN, ICliGroupsStoreService_TOKEN, ICliHttpClient_TOKEN, ICliKeyValueStore_TOKEN, ICliPermissionRegistry_TOKEN, ICliPermissionService_TOKEN, ICliServerAuthService_TOKEN, ICliServerAuthTokenProvider_TOKEN, ICliServerManager_TOKEN, ICliSessionsService_TOKEN, ICliTranslationService_TOKEN, ICliUserEventService_TOKEN, ICliUserSessionService_TOKEN, ICliUsersStoreService_TOKEN, LIBRARY_VERSION, ObjectDescriber, ServerVersionNegotiator, appendQueryParams, bootCliModule, bootUmdModule, buildAuthenticatedWebSocketUrl, clearTerminalLine, cliAccessMetadataGuards, cliAuthorize, cliCanAccess, cliDefaultServer, cliEvaluateAccess, cliHasPermission, cliMergePermissions, cliMissingPermissions, cliPermissionMatches, cliRequireAuthGuard, cliRequiredGroupsGuard, cliRequiredPermissionsGuard, colorFirstWord, colorizeJson, compareVersions, constants, createCliToken, createWorkerService, delay, derivePanelThemeStyles, enums, formatJson, getConfigValue, getParameterValue, getPluginConfigValue, getRightOfWord, highlightTextWithBg, initializeBrowserEnvironment, isCliAccessDeniedError, joinShellWords, loadPanelPosition, nextPanelPosition, quoteShellWord, resolveConfigurationCategories, resolveHeaders, resolveServerHeaders, resolveServerWebSocketUrl, satisfiesMinVersion, satisfiesVersionRange, savePanelPosition, splitShellWords, stripAnsi, toQueryString, truncateToWidth, unquoteRawArgument, utils, visibleLength, writeExamples, writeHint, writeUsage, writeWrapped };
4109
+ export type { CliAccessAction, CliAddUser, CliBackgroundServiceStatus, CliBackgroundServiceType, CliBootOptions, CliCommandGuardResult, CliDateOptions, CliEmitOptions, CliEngineSnapshot, CliEvent, CliEventData, CliEventHandler, CliEventMap, CliEventType, CliFactoryProvider, CliFilePickerOptions, CliFileResult, CliFullScreenOptions, CliHeadersProvider, CliHelpRow, CliHttpRequestOptions, CliLineOptions, CliMultiSelectOption, CliMultiSelectOptions, CliNotification, CliNotificationLevel, CliNumberOptions, CliOptions, CliPackageSource, CliPanelConfig, CliPanelHideAlignment, CliPanelPaneState, CliPanelPosition, CliPanelState, CliPanelTabState, CliPercentageProgressBarUpdateValueOptions, CliPermissionDefinition, CliProcessCommand, CliProcessorMetadata, CliProvider, CliSelectOption, CliSelectOptions, CliServerCapabilities, CliServerCommandDescriptor, CliServerConfig, CliServerOutput, CliServerResponse, CliServiceEventHandler, CliServiceExecutionMode, CliServiceLogLevel, CliState, CliStateConfiguration, CliStateScope, CliStateStoreOptions, CliTableOptions, CliTextAnimatorOptions, CliTheme, CliThemeCategory, CliThemeInfo, CliToken, CliTypeProvider, CliUpdateUser, CliValueProvider, CliWorkerInboundMessage, CliWorkerOutboundMessage, CustomEventType, DefaultThemesType, ICliAuditEntry, ICliAuditService, ICliAuthService, ICliAuthorizationService, ICliBackgroundService, ICliBackgroundServiceInfo, ICliBackgroundServiceRegistry, ICliClipboard, ICliCommandAuthor, ICliCommandChildProcessor, ICliCommandExecutorService, ICliCommandGuard, ICliCommandParameterDescriptor, ICliCommandProcessor, ICliCommandProcessorRegistry, ICliCompletionContext, ICliCompletionProvider, ICliConfigurableModule, ICliConfigurationOption, ICliDragDropService, ICliEventBus, ICliExecutionContext, ICliExecutionProcess, ICliFileEntry, ICliFilePickerProvider, ICliFileTransferService, ICliGlobalParameterHandler, ICliGroup, ICliGroupsStoreService, ICliHttpClient, ICliInputReader, ICliKeyValueStore, ICliLogger, ICliManagedInterval, ICliManagedTimer, ICliModule, ICliNotifier, ICliOwnership, ICliPanelRef, ICliPercentageProgressBar, ICliPermissionRegistry, ICliPermissionService, ICliPingServerService, ICliPrincipal, ICliProcessEntry, ICliProcessRegisterOptions, ICliProcessRegistry, ICliProcessorHook, ICliProgressBar, ICliProtectedResource, ICliServerAuthService, ICliServerAuthTokenProvider, ICliServerManager, ICliServiceContext, ICliServiceEvent, ICliServiceLogEntry, ICliServiceProvider, ICliSessionInfo, ICliSessionsService, ICliSpinner, ICliStateStore, ICliTerminalWriter, ICliTextAnimator, ICliTranslationService, ICliUmdModule, ICliUser, ICliUserCredentials, ICliUserEvent, ICliUserEventService, ICliUserSession, ICliUserSessionService, ICliUsersStoreService, ISyntaxHighlightRule, ISyntaxHighlighter, ISyntaxTheme, LineState, Package, ServerVersionInfo, WorkerServiceContext };