cmdbox-cli 1.0.0__py3-none-any.whl

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.
Files changed (112) hide show
  1. cmdbox/__init__.py +0 -0
  2. cmdbox/cli/__init__.py +0 -0
  3. cmdbox/cli/app.py +125 -0
  4. cmdbox/cli/commands/__init__.py +0 -0
  5. cmdbox/cli/commands/alias_fallback.py +102 -0
  6. cmdbox/cli/commands/command_crud.py +429 -0
  7. cmdbox/cli/commands/command_run.py +255 -0
  8. cmdbox/cli/commands/history.py +109 -0
  9. cmdbox/cli/commands/init.py +54 -0
  10. cmdbox/cli/commands/settings.py +62 -0
  11. cmdbox/cli/commands/tag_crud.py +277 -0
  12. cmdbox/cli/commands/variable_crud.py +349 -0
  13. cmdbox/cli/common/__init__.py +0 -0
  14. cmdbox/cli/common/errors.py +58 -0
  15. cmdbox/cli/common/update_fields.py +88 -0
  16. cmdbox/cli/completions/__init__.py +0 -0
  17. cmdbox/cli/completions/commands.py +26 -0
  18. cmdbox/cli/completions/fields.py +31 -0
  19. cmdbox/cli/completions/tags.py +24 -0
  20. cmdbox/cli/completions/variables.py +26 -0
  21. cmdbox/cli/handlers/__init__.py +0 -0
  22. cmdbox/cli/handlers/command_handlers.py +357 -0
  23. cmdbox/cli/handlers/common_handlers.py +15 -0
  24. cmdbox/cli/handlers/history_handlers.py +94 -0
  25. cmdbox/cli/handlers/init_handler.py +127 -0
  26. cmdbox/cli/handlers/run_handler.py +178 -0
  27. cmdbox/cli/handlers/settings_handler.py +59 -0
  28. cmdbox/cli/handlers/tag_handlers.py +220 -0
  29. cmdbox/cli/handlers/variable_handlers.py +272 -0
  30. cmdbox/cli/prompts/__init__.py +0 -0
  31. cmdbox/cli/prompts/completers.py +161 -0
  32. cmdbox/cli/prompts/prompts.py +108 -0
  33. cmdbox/cli/prompts/validators.py +46 -0
  34. cmdbox/cli/ui/__init__.py +0 -0
  35. cmdbox/cli/ui/console.py +31 -0
  36. cmdbox/cli/ui/editor.py +141 -0
  37. cmdbox/cli/ui/presenters/__init__.py +0 -0
  38. cmdbox/cli/ui/presenters/app_presenter.py +8 -0
  39. cmdbox/cli/ui/presenters/command_presenter.py +168 -0
  40. cmdbox/cli/ui/presenters/history_presenter.py +83 -0
  41. cmdbox/cli/ui/presenters/init_instructions.py +52 -0
  42. cmdbox/cli/ui/presenters/init_presenter.py +57 -0
  43. cmdbox/cli/ui/presenters/result_presenter.py +144 -0
  44. cmdbox/cli/ui/presenters/settings_presenter.py +130 -0
  45. cmdbox/cli/ui/presenters/tag_presenter.py +97 -0
  46. cmdbox/cli/ui/presenters/variable_presenter.py +103 -0
  47. cmdbox/cli/ui/primitives.py +410 -0
  48. cmdbox/cli/ui/theme.py +43 -0
  49. cmdbox/cli/ui/theme_builder.py +49 -0
  50. cmdbox/common/__init__.py +0 -0
  51. cmdbox/common/io.py +34 -0
  52. cmdbox/container.py +156 -0
  53. cmdbox/core/__init__.py +0 -0
  54. cmdbox/core/fields.py +48 -0
  55. cmdbox/core/paths.py +52 -0
  56. cmdbox/database.py +65 -0
  57. cmdbox/exceptions.py +10 -0
  58. cmdbox/init/__init__.py +0 -0
  59. cmdbox/init/detect.py +82 -0
  60. cmdbox/init/integrations/bash.sh +10 -0
  61. cmdbox/init/integrations/cmd.bat +14 -0
  62. cmdbox/init/integrations/fish.fish +11 -0
  63. cmdbox/init/integrations/powershell.ps1 +14 -0
  64. cmdbox/init/integrations/zsh.sh +10 -0
  65. cmdbox/init/io.py +68 -0
  66. cmdbox/init/specs.py +54 -0
  67. cmdbox/logging_setup/__init__.py +0 -0
  68. cmdbox/logging_setup/log_config.py +123 -0
  69. cmdbox/logging_setup/log_decorators.py +40 -0
  70. cmdbox/logging_setup/log_handlers.py +94 -0
  71. cmdbox/migrations/__init__.py +1 -0
  72. cmdbox/migrations/errors.py +10 -0
  73. cmdbox/migrations/runner.py +127 -0
  74. cmdbox/migrations/versions/__init__.py +0 -0
  75. cmdbox/models.py +165 -0
  76. cmdbox/repositories/__init__.py +0 -0
  77. cmdbox/repositories/base_repository.py +181 -0
  78. cmdbox/repositories/command_repository.py +391 -0
  79. cmdbox/repositories/errors.py +120 -0
  80. cmdbox/repositories/history_repository.py +155 -0
  81. cmdbox/repositories/results.py +37 -0
  82. cmdbox/repositories/tag_repository.py +91 -0
  83. cmdbox/repositories/validators.py +256 -0
  84. cmdbox/repositories/variable_repository.py +324 -0
  85. cmdbox/resolve/__init__.py +0 -0
  86. cmdbox/resolve/errors.py +65 -0
  87. cmdbox/resolve/lookup.py +137 -0
  88. cmdbox/resolve/resolver.py +402 -0
  89. cmdbox/resolve/type_defs.py +96 -0
  90. cmdbox/runtime/__init__.py +0 -0
  91. cmdbox/runtime/executor.py +454 -0
  92. cmdbox/runtime/results.py +25 -0
  93. cmdbox/runtime/shell.py +90 -0
  94. cmdbox/services/__init__.py +0 -0
  95. cmdbox/services/command_services.py +261 -0
  96. cmdbox/services/errors.py +37 -0
  97. cmdbox/services/field_selection.py +162 -0
  98. cmdbox/services/history_service.py +68 -0
  99. cmdbox/services/run_service.py +204 -0
  100. cmdbox/services/tag_services.py +134 -0
  101. cmdbox/services/variable_services.py +224 -0
  102. cmdbox/settings/__init__.py +0 -0
  103. cmdbox/settings/models.py +129 -0
  104. cmdbox/settings/settings_repository.py +36 -0
  105. cmdbox/settings/settings_service.py +144 -0
  106. cmdbox/version.py +1 -0
  107. cmdbox_cli-1.0.0.dist-info/METADATA +125 -0
  108. cmdbox_cli-1.0.0.dist-info/RECORD +112 -0
  109. cmdbox_cli-1.0.0.dist-info/WHEEL +5 -0
  110. cmdbox_cli-1.0.0.dist-info/entry_points.txt +2 -0
  111. cmdbox_cli-1.0.0.dist-info/licenses/LICENSE +21 -0
  112. cmdbox_cli-1.0.0.dist-info/top_level.txt +1 -0
cmdbox/__init__.py ADDED
File without changes
cmdbox/cli/__init__.py ADDED
File without changes
cmdbox/cli/app.py ADDED
@@ -0,0 +1,125 @@
1
+ import uuid
2
+ from typing import Annotated
3
+
4
+ import typer
5
+
6
+ from cmdbox import container
7
+ from cmdbox.cli.commands.alias_fallback import AliasFallbackGroup
8
+ from cmdbox.cli.ui.presenters.app_presenter import render_version
9
+ from cmdbox.logging_setup.log_handlers import configure_logging
10
+ from cmdbox.logging_setup.log_config import build_log_config, get_logger
11
+ from cmdbox.version import __version__
12
+ from cmdbox.database import ensure_schema, get_db
13
+ from .commands.command_crud import app as command_crud_app
14
+ from .commands.command_run import app as command_run_app
15
+ from .commands.variable_crud import app as variable_crud_app
16
+ from .commands.tag_crud import app as tag_crud_app
17
+ from .commands.init import app as init_app
18
+ from .commands.settings import app as settings_app
19
+ from .commands.history import app as history_app
20
+
21
+ app = typer.Typer(
22
+ name="cb",
23
+ cls=AliasFallbackGroup,
24
+ help="CmdBox is a CLI tool for storing and recalling commands with many helpful quality of life features.",
25
+ no_args_is_help=True,
26
+ )
27
+
28
+ app.add_typer(
29
+ command_crud_app,
30
+ name="cmd",
31
+ help="CRUD (Create, Read, Update, Delete) operations for commands.",
32
+ )
33
+ app.add_typer(variable_crud_app, name="var", help="CRUD operations for variables.")
34
+ app.add_typer(tag_crud_app, name="tag", help="CRUD operations for tags.")
35
+ app.add_typer(command_run_app)
36
+ app.add_typer(init_app)
37
+ app.add_typer(settings_app, name="settings", help="Manage CmdBox settings.")
38
+ app.add_typer(
39
+ history_app, name="history", help="View and re-run command execution history."
40
+ )
41
+
42
+
43
+ def version_callback(value: bool):
44
+ if value:
45
+ console = container.get_console()
46
+ rendered_version = render_version(__version__)
47
+ console.print(rendered_version)
48
+ raise typer.Exit()
49
+
50
+
51
+ @app.callback()
52
+ def common(
53
+ test: Annotated[
54
+ bool,
55
+ typer.Option(
56
+ "--test",
57
+ "-t",
58
+ help="Enables testing mode. Database will be created in memory and will not affect the "
59
+ "applications persistent database.",
60
+ ),
61
+ ] = False,
62
+ verbose: Annotated[
63
+ bool,
64
+ typer.Option(
65
+ "--verbose",
66
+ "-v",
67
+ help="Enable additional diagnostic output in the terminal. Sets console log level to INFO.",
68
+ ),
69
+ ] = False,
70
+ debug: Annotated[
71
+ bool,
72
+ typer.Option(
73
+ "--debug",
74
+ "-d",
75
+ help="Enable full diagnostic output in the terminal. Sets console log level to DEBUG.",
76
+ ),
77
+ ] = False,
78
+ file_logs: Annotated[
79
+ bool | None,
80
+ typer.Option(
81
+ "--file-logs/--no-file-logs",
82
+ help="Enable/disables writing diagnostic logs to a file. Defaults to settings.",
83
+ ),
84
+ ] = None,
85
+ version: Annotated[
86
+ bool,
87
+ typer.Option(
88
+ "--version",
89
+ "-V",
90
+ callback=version_callback,
91
+ is_eager=True,
92
+ help="Print the app version and exit.",
93
+ ),
94
+ ] = None,
95
+ ) -> None:
96
+ if test:
97
+ get_db(testing=True)
98
+
99
+ settings = container.get_settings()
100
+
101
+ run_id = uuid.uuid4().hex[:6]
102
+ log_config = build_log_config(
103
+ settings=settings, verbose=verbose, debug=debug, file_logs=file_logs
104
+ )
105
+ configure_logging(log_config, run_id=run_id)
106
+
107
+ log = get_logger()
108
+ log.debug(
109
+ f"startup: test={test}, verbose={verbose}, debug={debug}, file_logs={file_logs}"
110
+ )
111
+ log.debug(f"file_logging={log_config.file_enabled} path={log_config.file_path}")
112
+
113
+ ensure_schema()
114
+
115
+ if test:
116
+ console = container.get_console()
117
+ console.info("Testing mode is active, database is in memory.")
118
+
119
+
120
+ def main() -> None:
121
+ app()
122
+
123
+
124
+ if __name__ == "__main__":
125
+ main()
File without changes
@@ -0,0 +1,102 @@
1
+ import click
2
+ from typer.core import TyperGroup
3
+
4
+
5
+ class AliasFallbackGroup(TyperGroup):
6
+ """
7
+ Handles command grouping with support for alias-based fallback.
8
+
9
+ This class extends the functionality of TyperGroup to include command alias
10
+ resolution. When a command is not directly found, it attempts to resolve the
11
+ command name using a predefined alias mapping and dynamically generates an
12
+ alias command that forwards its arguments to a core `run` command. This allows
13
+ users to define shorter or alternate names for commands while maintaining
14
+ flexibility.
15
+
16
+ Attributes:
17
+ _command_aliases (dict[str, str]): A mapping of alias names to their
18
+ corresponding actual command names. Used for resolving commands
19
+ through aliases.
20
+ """
21
+
22
+ _command_aliases: dict[str, str] = {
23
+ "cmds": "cmd",
24
+ "vars": "var",
25
+ "tags": "tag",
26
+ "hist": "history",
27
+ }
28
+
29
+ """
30
+ The second item in the list is the name of the command as configured by
31
+ `@app.command("command_name")`, not the method name. Further items in the list
32
+ will be supplied to that sub-command as args.
33
+ """
34
+ _shortcut_commands: dict[str, list[str]] = {"!!": ["history", "last"]}
35
+
36
+ def get_command(self, ctx: click.Context, cmd_name: str):
37
+ """
38
+ Retrieves a command based on the command name, creating an alias command if
39
+ the command was not directly found.
40
+
41
+ If the command corresponding to `cmd_name` doesn't exist, a new alias
42
+ command is generated using the `run` command, forwarding all its parameters
43
+ except the `alias` parameter.
44
+
45
+ Args:
46
+ ctx (click.Context): The Click context in which the command is being
47
+ invoked.
48
+ cmd_name (str): The name of the command to retrieve.
49
+
50
+ Returns:
51
+ click.Command: The command object corresponding to the given
52
+ `cmd_name`, or an alias command if the original command does not exist.
53
+ Returns None if `run` command is also unavailable.
54
+ """
55
+ cmd_name = self._command_aliases.get(cmd_name, cmd_name)
56
+
57
+ rv = super().get_command(ctx, cmd_name)
58
+ if rv is not None:
59
+ return rv
60
+
61
+ run_cmd = super().get_command(ctx, "run")
62
+ if run_cmd is None:
63
+ return None
64
+
65
+ forwarded_params = [p for p in run_cmd.params if p.name != "alias"]
66
+
67
+ @click.command(
68
+ cmd_name,
69
+ params=forwarded_params,
70
+ help=f"Run stored command '{cmd_name}'.",
71
+ context_settings=run_cmd.context_settings,
72
+ )
73
+ @click.pass_context
74
+ def _alias_cmd(inner_ctx: click.Context, **kwargs):
75
+ inner_ctx.meta["_extra_args"] = inner_ctx.args[:]
76
+ inner_ctx.invoke(run_cmd, alias=cmd_name, **kwargs)
77
+
78
+ return _alias_cmd
79
+
80
+ def resolve_command(self, ctx: click.Context, args: list):
81
+ """
82
+ Resolves the command by expanding shortcut commands if applicable.
83
+
84
+ This method checks whether the provided command arguments include a shortcut
85
+ command. If a shortcut command is detected, it is expanded into its full form
86
+ and the resulting extended argument list is passed to the parent class's
87
+ `resolve_command` method. If no shortcut is found, the original argument list
88
+ is passed directly.
89
+
90
+ Args:
91
+ ctx (click.Context): The Click context containing information about the
92
+ execution of the command.
93
+ args (list): A list of command-line arguments passed to the command.
94
+
95
+ Returns:
96
+ tuple: A tuple containing the command name, the command object, and a list
97
+ of remaining arguments.
98
+ """
99
+ if args and args[0] in self._shortcut_commands:
100
+ expanded = self._shortcut_commands[args[0]]
101
+ return super().resolve_command(ctx, expanded + list(args[1:]))
102
+ return super().resolve_command(ctx, args)
@@ -0,0 +1,429 @@
1
+ from typing import Annotated
2
+ import logging
3
+
4
+ import typer
5
+
6
+ from cmdbox import container
7
+ from cmdbox.cli.common.errors import make_cli_guard
8
+ from cmdbox.cli.completions.commands import complete_command_aliases
9
+ from cmdbox.cli.completions.fields import (
10
+ command_field_options,
11
+ command_editable_field_options,
12
+ )
13
+ from cmdbox.cli.completions.tags import complete_tag_names
14
+ from cmdbox.cli.handlers import command_handlers
15
+
16
+ app = typer.Typer(no_args_is_help=True)
17
+
18
+ cli_guard = make_cli_guard(container.get_console)
19
+
20
+ log = logging.getLogger(__name__)
21
+
22
+
23
+ @app.command("add")
24
+ @cli_guard
25
+ def add(
26
+ alias: Annotated[
27
+ str,
28
+ typer.Argument(
29
+ help="The name of the command. Will be used to recall the command."
30
+ ),
31
+ ] = None,
32
+ template: Annotated[
33
+ str,
34
+ typer.Argument(
35
+ help="The actual command value that will be executed when the command is recalled using the alias."
36
+ ),
37
+ ] = None,
38
+ description: Annotated[
39
+ str, typer.Option("--description", "-d", help="A description of the command.")
40
+ ] = None,
41
+ tags: Annotated[
42
+ list[str],
43
+ typer.Option(
44
+ "--tags",
45
+ "-t",
46
+ help="A list of tags to associate with the command, separated by commas.",
47
+ autocompletion=complete_tag_names,
48
+ ),
49
+ ] = None,
50
+ cwd: Annotated[
51
+ str,
52
+ typer.Option("--cwd", "-c", help="Working directory to run the command from."),
53
+ ] = None,
54
+ shell: Annotated[
55
+ str,
56
+ typer.Option("--shell", "-s", help="Shell to use when running the command."),
57
+ ] = None,
58
+ env: Annotated[
59
+ list[str],
60
+ typer.Option(
61
+ "--env",
62
+ "-e",
63
+ help="Environment variable to set when running the command, in KEY=VALUE format.",
64
+ ),
65
+ ] = None,
66
+ timeout: Annotated[
67
+ int,
68
+ typer.Option(
69
+ "--timeout",
70
+ "-o",
71
+ help="Maximum number of seconds before the process is killed.",
72
+ ),
73
+ ] = None,
74
+ interactive: Annotated[
75
+ bool,
76
+ typer.Option("--interactive", "-i", is_flag=True, help="Interactive mode."),
77
+ ] = False,
78
+ ) -> None:
79
+ """
80
+ Adds a new command with an alias, a template, description, and tags. The command
81
+ can be created in interactive if no options are provided or the `--interactive`
82
+ flag is used.
83
+ """
84
+ log.debug("cmd.add called. alias=%s, interactive=%s", alias, interactive)
85
+ add_cmd_args = command_handlers.AddCommandArgs(
86
+ alias=alias,
87
+ template=template,
88
+ description=description,
89
+ tags=tags,
90
+ cwd=cwd,
91
+ shell=shell,
92
+ env=env,
93
+ timeout=timeout,
94
+ interactive=interactive,
95
+ )
96
+ command_handlers.run_add_command(
97
+ args=add_cmd_args,
98
+ get_cmd_services=container.get_command_services,
99
+ get_tag_services=container.get_tag_services,
100
+ get_console=container.get_console,
101
+ )
102
+
103
+
104
+ @app.command("get")
105
+ @cli_guard
106
+ def get(
107
+ alias: Annotated[
108
+ str,
109
+ typer.Argument(
110
+ help="The alias of the command to retrieve.",
111
+ autocompletion=complete_command_aliases,
112
+ ),
113
+ ],
114
+ ) -> None:
115
+ """
116
+ Retrieves and displays a saved command stored under the provided alias.
117
+ """
118
+ log.debug("cmd.get called. alias=%s", alias)
119
+ command_handlers.run_get_command(
120
+ alias=alias,
121
+ get_cmd_services=container.get_command_services,
122
+ get_console=container.get_console,
123
+ )
124
+
125
+
126
+ @app.command("update")
127
+ @cli_guard
128
+ def update(
129
+ alias: Annotated[
130
+ str,
131
+ typer.Argument(
132
+ help="The alias of the command to update.",
133
+ autocompletion=complete_command_aliases,
134
+ ),
135
+ ],
136
+ template: Annotated[
137
+ str, typer.Option("--template", "-t", help="The new template.")
138
+ ] = None,
139
+ description: Annotated[
140
+ str, typer.Option("--description", "-d", help="The new description.")
141
+ ] = None,
142
+ new_alias: Annotated[
143
+ str, typer.Option("--alias", "-a", help="The new alias.")
144
+ ] = None,
145
+ cwd: Annotated[
146
+ str,
147
+ typer.Option("--cwd", "-c", help="Working directory to run the command from."),
148
+ ] = None,
149
+ shell: Annotated[
150
+ str,
151
+ typer.Option("--shell", "-s", help="Shell to use when running the command."),
152
+ ] = None,
153
+ env: Annotated[
154
+ list[str],
155
+ typer.Option(
156
+ "--env",
157
+ "-e",
158
+ help="Environment variable to set when running the command, in KEY=VALUE format.",
159
+ ),
160
+ ] = None,
161
+ timeout: Annotated[
162
+ int,
163
+ typer.Option(
164
+ "--timeout",
165
+ "-o",
166
+ help="Maximum number of seconds before the process is killed.",
167
+ ),
168
+ ] = None,
169
+ set_: Annotated[
170
+ list[str],
171
+ typer.Option(
172
+ "--set",
173
+ "-s",
174
+ help="A list of key=value pairs to update.",
175
+ autocompletion=command_editable_field_options,
176
+ ),
177
+ ] = None,
178
+ edit_mode: Annotated[
179
+ bool,
180
+ typer.Option("--edit", "-e", help="Edit mode."),
181
+ ] = False,
182
+ edit_fields: Annotated[
183
+ str,
184
+ typer.Option(
185
+ "--edit-fields",
186
+ help="A list of fields to be edited in edit mode, separated by commas. Defaults to all fields.",
187
+ ),
188
+ ] = None,
189
+ ) -> None:
190
+ """
191
+ Updates an existing command with the provided options. Each field can be updated
192
+ individually or in bulk using the `--set` option. Using the `--edit` option enables
193
+ editing the already stored values in an interactive mode.
194
+ """
195
+ log.debug(
196
+ "cmd.update called. alias=%s template_provided=%s description_provided=%s new_alias_provided=%s "
197
+ "set_used=%s, edit_mode=%s, edit_fields=%s",
198
+ alias,
199
+ template is not None,
200
+ description is not None,
201
+ new_alias is not None,
202
+ set_ is not None,
203
+ edit_mode,
204
+ edit_fields,
205
+ )
206
+ command_handlers.run_update_command(
207
+ alias=alias,
208
+ template=template,
209
+ description=description,
210
+ new_alias=new_alias,
211
+ cwd=cwd,
212
+ shell=shell,
213
+ env=env,
214
+ timeout=timeout,
215
+ set_pairs=set_,
216
+ edit_mode=edit_mode,
217
+ edit_fields=edit_fields,
218
+ get_cmd_services=container.get_command_services,
219
+ get_settings=container.get_settings,
220
+ get_console=container.get_console,
221
+ )
222
+
223
+
224
+ app.command("edit", hidden=True)(update)
225
+
226
+
227
+ @app.command("list")
228
+ @cli_guard
229
+ def list_cmds(
230
+ order: Annotated[
231
+ str,
232
+ typer.Option(
233
+ "--order",
234
+ "-o",
235
+ help="The field to order the results by.",
236
+ autocompletion=command_field_options,
237
+ ),
238
+ ] = "alias",
239
+ tags: Annotated[
240
+ list[str],
241
+ typer.Option(
242
+ "--tag",
243
+ "-t",
244
+ help="The tag to filter by.",
245
+ autocompletion=complete_tag_names,
246
+ ),
247
+ ] = None,
248
+ limit: Annotated[
249
+ int,
250
+ typer.Option("--limit", "-l", help="The maximum number of results to return."),
251
+ ] = 10,
252
+ fields: Annotated[
253
+ list[str] | None,
254
+ typer.Option(
255
+ "--field",
256
+ "-f",
257
+ help="The field(s) to display in the results list. Defaults fields in settings.",
258
+ autocompletion=command_field_options,
259
+ ),
260
+ ] = None,
261
+ ) -> None:
262
+ """
263
+ Displays all stored commands in a list format. The number of results can be limited
264
+ with the `--limit` option. The output fields can be customized with the `--field` option.
265
+ """
266
+ log.debug(
267
+ "cmd.list called. order=%s, tags=%s, limit=%s, fields=%s",
268
+ order,
269
+ tags,
270
+ limit,
271
+ fields,
272
+ )
273
+ command_handlers.run_list_command(
274
+ limit=limit,
275
+ order=order,
276
+ tags=tags,
277
+ fields=fields,
278
+ get_cmd_services=container.get_command_services,
279
+ get_settings=container.get_settings,
280
+ get_console=container.get_console,
281
+ get_display_field_resolver=container.get_command_display_field_resolver,
282
+ )
283
+
284
+
285
+ app.command("ls", hidden=True)(list_cmds)
286
+
287
+
288
+ @app.command("search")
289
+ @cli_guard
290
+ def search(
291
+ term: Annotated[str, typer.Argument(help="The search term to use.")],
292
+ limit: Annotated[
293
+ int, typer.Option(help="The maximum number of results to return.")
294
+ ] = 10,
295
+ search_fields: Annotated[
296
+ list[str],
297
+ typer.Option(
298
+ "--in",
299
+ "-i",
300
+ help="The fields to search within.",
301
+ autocompletion=command_field_options,
302
+ ),
303
+ ] = None,
304
+ fields: Annotated[
305
+ list[str] | None,
306
+ typer.Option(
307
+ "--field",
308
+ "-f",
309
+ help="The field(s) to display in the results list. Defaults to all fields.",
310
+ autocompletion=command_field_options,
311
+ ),
312
+ ] = None,
313
+ ) -> None:
314
+ """
315
+ Searches the database for commands matching the provided search term. The search fields
316
+ can be customized with the `--in` option. The output fields can be customized with the
317
+ `--field` option.
318
+ """
319
+ log.debug(
320
+ "cmd.search called. term=%s, limit=%s, search_fields=%s, fields=%s",
321
+ term,
322
+ limit,
323
+ search_fields,
324
+ fields,
325
+ )
326
+ command_handlers.run_search_command(
327
+ term=term,
328
+ limit=limit,
329
+ search_fields=search_fields,
330
+ fields=fields,
331
+ get_cmd_services=container.get_command_services,
332
+ get_settings=container.get_settings,
333
+ get_console=container.get_console,
334
+ get_display_field_resolver=container.get_command_display_field_resolver,
335
+ get_search_field_resolver=container.get_command_search_field_resolver,
336
+ )
337
+
338
+
339
+ app.command("find", hidden=True)(search)
340
+
341
+
342
+ @app.command("delete")
343
+ @cli_guard
344
+ def delete(
345
+ alias: Annotated[
346
+ str,
347
+ typer.Argument(
348
+ help="The alias of the command to delete.",
349
+ autocompletion=complete_command_aliases,
350
+ ),
351
+ ],
352
+ ) -> None:
353
+ """
354
+ Deletes the command stored under the provided alias.
355
+ """
356
+ log.debug("cmd.delete called. alias=%s", alias)
357
+ command_handlers.run_delete_command(
358
+ alias=alias,
359
+ get_cmd_services=container.get_command_services,
360
+ get_console=container.get_console,
361
+ )
362
+
363
+
364
+ app.command("del", hidden=True)(delete)
365
+ app.command("rm", hidden=True)(delete)
366
+ app.command("remove", hidden=True)(delete)
367
+
368
+
369
+ @app.command("tag")
370
+ @cli_guard
371
+ def add_tags(
372
+ alias: Annotated[
373
+ str,
374
+ typer.Argument(
375
+ help="The alias of the command to tag.",
376
+ autocompletion=complete_command_aliases,
377
+ ),
378
+ ] = None,
379
+ tags: Annotated[
380
+ list[str],
381
+ typer.Argument(
382
+ help="The tags to add to the command, separated by commas.",
383
+ autocompletion=complete_tag_names,
384
+ ),
385
+ ] = None,
386
+ ) -> None:
387
+ """
388
+ Adds the provided tags to the command stored under the provided alias. Tags must
389
+ be existing.
390
+ """
391
+ log.debug("cmd.tag.add called. alias=%s, tags=%s", alias, tags)
392
+ command_handlers.run_attach_tags(
393
+ alias=alias,
394
+ tag_names=tags,
395
+ get_cmd_services=container.get_command_services,
396
+ get_tag_services=container.get_tag_services,
397
+ get_console=container.get_console,
398
+ )
399
+
400
+
401
+ @app.command("untag")
402
+ @cli_guard
403
+ def remove_tags(
404
+ alias: Annotated[
405
+ str,
406
+ typer.Argument(
407
+ help="The alias of the command to untag.",
408
+ autocompletion=complete_command_aliases,
409
+ ),
410
+ ] = None,
411
+ tags: Annotated[
412
+ list[str],
413
+ typer.Argument(
414
+ help="The tags to remove from the command, separated by commas.",
415
+ autocompletion=complete_tag_names,
416
+ ),
417
+ ] = None,
418
+ ) -> None:
419
+ """
420
+ Removes the provided tags from the command stored under the provided alias.
421
+ """
422
+ log.debug("cmd.tag.remove called. alias=%s, tags=%s", alias, tags)
423
+ command_handlers.run_detach_tags(
424
+ alias=alias,
425
+ tag_names=tags,
426
+ get_cmd_services=container.get_command_services,
427
+ get_tag_services=container.get_tag_services,
428
+ get_console=container.get_console,
429
+ )