kash-shell 0.3.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.
- kash/__init__.py +2 -0
- kash/__main__.py +4 -0
- kash/actions/__init__.py +55 -0
- kash/actions/core/assistant_chat.py +45 -0
- kash/actions/core/chat.py +90 -0
- kash/actions/core/format_markdown_template.py +92 -0
- kash/actions/core/markdownify.py +29 -0
- kash/actions/core/readability.py +27 -0
- kash/actions/core/show_webpage.py +28 -0
- kash/actions/core/strip_html.py +28 -0
- kash/actions/core/summarize_as_bullets.py +53 -0
- kash/actions/core/webpage_config.py +21 -0
- kash/actions/core/webpage_generate.py +29 -0
- kash/actions/meta/write_instructions.py +39 -0
- kash/actions/meta/write_new_action.py +157 -0
- kash/commands/__init__.py +21 -0
- kash/commands/base/basic_file_commands.py +183 -0
- kash/commands/base/browser_commands.py +62 -0
- kash/commands/base/debug_commands.py +214 -0
- kash/commands/base/diff_commands.py +90 -0
- kash/commands/base/files_command.py +408 -0
- kash/commands/base/general_commands.py +104 -0
- kash/commands/base/global_state_commands.py +41 -0
- kash/commands/base/logs_commands.py +92 -0
- kash/commands/base/reformat_command.py +54 -0
- kash/commands/base/search_command.py +65 -0
- kash/commands/base/show_command.py +69 -0
- kash/commands/extras/utils_commands.py +27 -0
- kash/commands/help/assistant_commands.py +97 -0
- kash/commands/help/doc_commands.py +226 -0
- kash/commands/help/help_commands.py +133 -0
- kash/commands/workspace/selection_commands.py +200 -0
- kash/commands/workspace/workspace_commands.py +640 -0
- kash/concepts/concept_formats.py +23 -0
- kash/concepts/embeddings.py +130 -0
- kash/concepts/text_similarity.py +108 -0
- kash/config/__init__.py +4 -0
- kash/config/api_keys.py +84 -0
- kash/config/capture_output.py +77 -0
- kash/config/colors.py +279 -0
- kash/config/init.py +18 -0
- kash/config/lazy_imports.py +22 -0
- kash/config/logger.py +355 -0
- kash/config/logger_basic.py +35 -0
- kash/config/logo.txt +4 -0
- kash/config/logo_fancy.txt +4 -0
- kash/config/server_config.py +51 -0
- kash/config/settings.py +196 -0
- kash/config/setup.py +51 -0
- kash/config/suppress_warnings.py +27 -0
- kash/config/text_styles.py +426 -0
- kash/docs/__init__.py +0 -0
- kash/docs/all_docs.py +58 -0
- kash/docs/load_actions_info.py +28 -0
- kash/docs/load_api_docs.py +13 -0
- kash/docs/load_help_topics.py +47 -0
- kash/docs/load_source_code.py +125 -0
- kash/docs/markdown/api_docs_template.md +42 -0
- kash/docs/markdown/assistant_instructions_template.md +114 -0
- kash/docs/markdown/readme_template.md +26 -0
- kash/docs/markdown/topics/a1_what_is_kash.md +76 -0
- kash/docs/markdown/topics/a2_progress.md +96 -0
- kash/docs/markdown/topics/a3_installation.md +119 -0
- kash/docs/markdown/topics/a4_getting_started.md +300 -0
- kash/docs/markdown/topics/a5_tips_for_use_with_other_tools.md +83 -0
- kash/docs/markdown/topics/b0_philosophy_of_kash.md +177 -0
- kash/docs/markdown/topics/b1_kash_overview.md +124 -0
- kash/docs/markdown/topics/b2_workspace_and_file_formats.md +61 -0
- kash/docs/markdown/topics/b3_modern_shell_tool_recommendations.md +83 -0
- kash/docs/markdown/topics/b4_faq.md +166 -0
- kash/docs/markdown/warning.md +7 -0
- kash/docs/markdown/welcome.md +9 -0
- kash/docs_base/docs_base.py +85 -0
- kash/docs_base/load_custom_command_info.py +27 -0
- kash/docs_base/load_faqs.py +48 -0
- kash/docs_base/load_recipe_snippets.py +48 -0
- kash/docs_base/recipes/general_system_commands.ksh +10 -0
- kash/docs_base/recipes/python_dev_commands.ksh +7 -0
- kash/docs_base/recipes/tldr_standard_commands.ksh +2144 -0
- kash/errors.py +176 -0
- kash/exec/__init__.py +16 -0
- kash/exec/action_decorators.py +412 -0
- kash/exec/action_exec.py +457 -0
- kash/exec/action_registry.py +123 -0
- kash/exec/combiners.py +127 -0
- kash/exec/command_exec.py +34 -0
- kash/exec/command_registry.py +72 -0
- kash/exec/fetch_url_metadata.py +71 -0
- kash/exec/history.py +44 -0
- kash/exec/llm_transforms.py +121 -0
- kash/exec/precondition_checks.py +71 -0
- kash/exec/precondition_registry.py +43 -0
- kash/exec/preconditions.py +152 -0
- kash/exec/resolve_args.py +123 -0
- kash/exec/shell_callable_action.py +90 -0
- kash/exec_model/__init__.py +0 -0
- kash/exec_model/args_model.py +93 -0
- kash/exec_model/commands_model.py +163 -0
- kash/exec_model/script_model.py +161 -0
- kash/exec_model/shell_model.py +21 -0
- kash/file_storage/__init__.py +0 -0
- kash/file_storage/file_store.py +642 -0
- kash/file_storage/item_file_format.py +152 -0
- kash/file_storage/metadata_dirs.py +108 -0
- kash/file_storage/mtime_cache.py +108 -0
- kash/file_storage/persisted_yaml.py +37 -0
- kash/file_storage/store_cache_warmer.py +37 -0
- kash/file_storage/store_filenames.py +53 -0
- kash/form_input/__init__.py +0 -0
- kash/form_input/prompt_input.py +44 -0
- kash/help/__init__.py +0 -0
- kash/help/assistant.py +324 -0
- kash/help/assistant_instructions.py +68 -0
- kash/help/assistant_output.py +43 -0
- kash/help/docstring_utils.py +111 -0
- kash/help/function_param_info.py +44 -0
- kash/help/help_embeddings.py +85 -0
- kash/help/help_lookups.py +60 -0
- kash/help/help_pages.py +122 -0
- kash/help/help_printing.py +169 -0
- kash/help/help_types.py +247 -0
- kash/help/recommended_commands.py +143 -0
- kash/help/tldr_help.py +296 -0
- kash/llm_utils/__init__.py +0 -0
- kash/llm_utils/chat_format.py +413 -0
- kash/llm_utils/clean_headings.py +65 -0
- kash/llm_utils/fuzzy_parsing.py +119 -0
- kash/llm_utils/language_models.py +178 -0
- kash/llm_utils/llm_completion.py +172 -0
- kash/llm_utils/llm_messages.py +36 -0
- kash/local_server/__init__.py +2 -0
- kash/local_server/local_server.py +183 -0
- kash/local_server/local_server_commands.py +55 -0
- kash/local_server/local_server_routes.py +306 -0
- kash/local_server/local_url_formatters.py +169 -0
- kash/local_server/port_tools.py +67 -0
- kash/local_server/rich_html_template.py +12 -0
- kash/mcp/__init__.py +2 -0
- kash/mcp/mcp_main.py +67 -0
- kash/mcp/mcp_server_commands.py +57 -0
- kash/mcp/mcp_server_routes.py +256 -0
- kash/mcp/mcp_server_sse.py +143 -0
- kash/mcp/mcp_server_stdio.py +45 -0
- kash/media_base/__init__.py +0 -0
- kash/media_base/audio_processing.py +27 -0
- kash/media_base/media_cache.py +178 -0
- kash/media_base/media_services.py +112 -0
- kash/media_base/media_tools.py +48 -0
- kash/media_base/services/local_file_media.py +165 -0
- kash/media_base/speech_transcription.py +224 -0
- kash/media_base/timestamp_citations.py +80 -0
- kash/model/__init__.py +73 -0
- kash/model/actions_model.py +633 -0
- kash/model/assistant_response_model.py +87 -0
- kash/model/compound_actions_model.py +188 -0
- kash/model/graph_model.py +92 -0
- kash/model/items_model.py +821 -0
- kash/model/language_list.py +39 -0
- kash/model/llm_actions_model.py +63 -0
- kash/model/media_model.py +124 -0
- kash/model/operations_model.py +176 -0
- kash/model/params_model.py +435 -0
- kash/model/paths_model.py +458 -0
- kash/model/preconditions_model.py +98 -0
- kash/shell/__init__.py +0 -0
- kash/shell/completions/completion_scoring.py +280 -0
- kash/shell/completions/completion_types.py +154 -0
- kash/shell/completions/shell_completions.py +277 -0
- kash/shell/file_icons/color_for_format.py +70 -0
- kash/shell/file_icons/nerd_icons.py +946 -0
- kash/shell/output/__init__.py +0 -0
- kash/shell/output/kerm_code_utils.py +59 -0
- kash/shell/output/kerm_codes.py +588 -0
- kash/shell/output/kmarkdown.py +117 -0
- kash/shell/output/shell_output.py +477 -0
- kash/shell/ui/__init__.py +0 -0
- kash/shell/ui/shell_results.py +118 -0
- kash/shell/ui/shell_syntax.py +26 -0
- kash/shell/utils/exception_printing.py +50 -0
- kash/shell/utils/native_utils.py +240 -0
- kash/shell/utils/osc_utils.py +95 -0
- kash/shell/utils/shell_function_wrapper.py +204 -0
- kash/shell/utils/sys_tool_deps.py +289 -0
- kash/shell/utils/terminal_images.py +133 -0
- kash/shell_main.py +67 -0
- kash/text_handling/custom_sliding_transforms.py +266 -0
- kash/text_handling/doc_normalization.py +64 -0
- kash/text_handling/markdown_util.py +167 -0
- kash/text_handling/unified_diffs.py +138 -0
- kash/utils/__init__.py +4 -0
- kash/utils/common/__init__.py +4 -0
- kash/utils/common/atomic_var.py +147 -0
- kash/utils/common/format_utils.py +81 -0
- kash/utils/common/function_inspect.py +178 -0
- kash/utils/common/import_utils.py +89 -0
- kash/utils/common/lazyobject.py +144 -0
- kash/utils/common/obj_replace.py +78 -0
- kash/utils/common/parse_key_vals.py +85 -0
- kash/utils/common/parse_shell_args.py +348 -0
- kash/utils/common/stack_traces.py +49 -0
- kash/utils/common/string_replace.py +93 -0
- kash/utils/common/string_template.py +101 -0
- kash/utils/common/task_stack.py +162 -0
- kash/utils/common/type_utils.py +137 -0
- kash/utils/common/uniquifier.py +95 -0
- kash/utils/common/url.py +155 -0
- kash/utils/file_utils/__init__.py +3 -0
- kash/utils/file_utils/dir_size.py +48 -0
- kash/utils/file_utils/file_ext.py +86 -0
- kash/utils/file_utils/file_formats.py +134 -0
- kash/utils/file_utils/file_formats_model.py +408 -0
- kash/utils/file_utils/file_sort_filter.py +235 -0
- kash/utils/file_utils/file_walk.py +163 -0
- kash/utils/file_utils/filename_parsing.py +99 -0
- kash/utils/file_utils/git_tools.py +19 -0
- kash/utils/file_utils/ignore_files.py +166 -0
- kash/utils/file_utils/path_utils.py +36 -0
- kash/utils/lang_utils/__init__.py +0 -0
- kash/utils/lang_utils/capitalization.py +128 -0
- kash/utils/lang_utils/inflection.py +18 -0
- kash/utils/rich_custom/__init__.py +3 -0
- kash/utils/rich_custom/ansi_cell_len.py +72 -0
- kash/utils/rich_custom/rich_char_transform.py +89 -0
- kash/utils/rich_custom/rich_indent.py +69 -0
- kash/utils/rich_custom/rich_markdown_fork.py +771 -0
- kash/version.py +31 -0
- kash/web_content/canon_url.py +24 -0
- kash/web_content/dir_store.py +103 -0
- kash/web_content/file_cache_utils.py +117 -0
- kash/web_content/local_file_cache.py +247 -0
- kash/web_content/web_extract.py +55 -0
- kash/web_content/web_extract_justext.py +86 -0
- kash/web_content/web_extract_readabilipy.py +23 -0
- kash/web_content/web_fetch.py +101 -0
- kash/web_content/web_page_model.py +28 -0
- kash/web_gen/__init__.py +4 -0
- kash/web_gen/tabbed_webpage.py +149 -0
- kash/web_gen/template_render.py +29 -0
- kash/web_gen/templates/base_styles.css.jinja +192 -0
- kash/web_gen/templates/base_webpage.html.jinja +124 -0
- kash/web_gen/templates/content_styles.css.jinja +194 -0
- kash/web_gen/templates/explain_view.html.jinja +49 -0
- kash/web_gen/templates/item_view.html.jinja +294 -0
- kash/web_gen/templates/tabbed_webpage.html.jinja +49 -0
- kash/workspaces/__init__.py +13 -0
- kash/workspaces/param_state.py +24 -0
- kash/workspaces/selections.py +333 -0
- kash/workspaces/source_items.py +88 -0
- kash/workspaces/workspace_importing.py +56 -0
- kash/workspaces/workspace_names.py +33 -0
- kash/workspaces/workspace_output.py +154 -0
- kash/workspaces/workspace_registry.py +78 -0
- kash/workspaces/workspaces.py +197 -0
- kash/xonsh_custom/custom_shell.py +366 -0
- kash/xonsh_custom/customize_prompt.py +197 -0
- kash/xonsh_custom/customize_xonsh.py +112 -0
- kash/xonsh_custom/shell_load_commands.py +152 -0
- kash/xonsh_custom/shell_which.py +64 -0
- kash/xonsh_custom/xonsh_completers.py +715 -0
- kash/xonsh_custom/xonsh_env.py +28 -0
- kash/xonsh_custom/xonsh_modern_tools.py +56 -0
- kash/xonsh_custom/xonsh_ranking_completer.py +152 -0
- kash/xontrib/fnm.py +120 -0
- kash/xontrib/kash_extension.py +61 -0
- kash_shell-0.3.0.dist-info/METADATA +757 -0
- kash_shell-0.3.0.dist-info/RECORD +269 -0
- kash_shell-0.3.0.dist-info/WHEEL +4 -0
- kash_shell-0.3.0.dist-info/entry_points.txt +3 -0
- kash_shell-0.3.0.dist-info/licenses/LICENSE +664 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from prettyfmt import fmt_lines
|
|
4
|
+
|
|
5
|
+
from kash.commands.base.basic_file_commands import trash
|
|
6
|
+
from kash.config.logger import get_log_settings, get_logger, reload_rich_logging_setup
|
|
7
|
+
from kash.config.settings import (
|
|
8
|
+
LogLevel,
|
|
9
|
+
atomic_global_settings,
|
|
10
|
+
global_settings,
|
|
11
|
+
server_log_file_path,
|
|
12
|
+
)
|
|
13
|
+
from kash.exec import kash_command
|
|
14
|
+
from kash.shell.output.shell_output import cprint, format_name_and_value, print_status
|
|
15
|
+
from kash.shell.utils.native_utils import tail_file
|
|
16
|
+
from kash.utils.common.format_utils import fmt_loc
|
|
17
|
+
|
|
18
|
+
log = get_logger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@kash_command
|
|
22
|
+
def logs(follow: bool = False) -> None:
|
|
23
|
+
"""
|
|
24
|
+
Page through the logs for the current workspace.
|
|
25
|
+
|
|
26
|
+
:param follow: Follow the file as it grows.
|
|
27
|
+
"""
|
|
28
|
+
tail_file(get_log_settings().log_file_path, follow=follow)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@kash_command
|
|
32
|
+
def clear_logs() -> None:
|
|
33
|
+
"""
|
|
34
|
+
Clear the logs for the current workspace. Logs for the current workspace will be lost
|
|
35
|
+
permanently!
|
|
36
|
+
"""
|
|
37
|
+
log_path = get_log_settings().log_file_path
|
|
38
|
+
if log_path.exists():
|
|
39
|
+
with open(log_path, "w"):
|
|
40
|
+
pass
|
|
41
|
+
obj_dir = get_log_settings().log_objects_dir
|
|
42
|
+
if obj_dir.exists():
|
|
43
|
+
trash(obj_dir)
|
|
44
|
+
os.makedirs(obj_dir, exist_ok=True)
|
|
45
|
+
|
|
46
|
+
print_status("Logs cleared:\n%s", fmt_lines([fmt_loc(log_path)]))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@kash_command
|
|
50
|
+
def log_level(level: str | None = None, console: bool = False, file: bool = False) -> None:
|
|
51
|
+
"""
|
|
52
|
+
Set or show the log level. Applies to both console and file log levels unless specified.
|
|
53
|
+
|
|
54
|
+
:param level: The log level to set. If not specified, will show current level.
|
|
55
|
+
:param console: Set console log level only.
|
|
56
|
+
:param file: Set file log level only.
|
|
57
|
+
"""
|
|
58
|
+
if not console and not file:
|
|
59
|
+
console = True
|
|
60
|
+
file = True
|
|
61
|
+
|
|
62
|
+
if level:
|
|
63
|
+
level_parsed = LogLevel.parse(level)
|
|
64
|
+
with atomic_global_settings().updates() as settings:
|
|
65
|
+
if console:
|
|
66
|
+
settings.console_log_level = level_parsed
|
|
67
|
+
if file:
|
|
68
|
+
settings.file_log_level = level_parsed
|
|
69
|
+
|
|
70
|
+
reload_rich_logging_setup()
|
|
71
|
+
|
|
72
|
+
settings = get_log_settings()
|
|
73
|
+
cprint(format_name_and_value("file_log_level", settings.log_file_level.name))
|
|
74
|
+
cprint(format_name_and_value("console_log_level", settings.log_console_level.name))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@kash_command
|
|
78
|
+
def log_settings() -> None:
|
|
79
|
+
"""
|
|
80
|
+
Show the current log settings.
|
|
81
|
+
"""
|
|
82
|
+
settings = get_log_settings()
|
|
83
|
+
cprint(format_name_and_value("log_dir", str(settings.log_dir)))
|
|
84
|
+
cprint(format_name_and_value("log_file_path", str(settings.log_file_path)))
|
|
85
|
+
cprint(format_name_and_value("log_objects_dir", str(settings.log_objects_dir)))
|
|
86
|
+
cprint(format_name_and_value("log_file_level", settings.log_file_level.name))
|
|
87
|
+
cprint(format_name_and_value("log_console_level", settings.log_console_level.name))
|
|
88
|
+
cprint(
|
|
89
|
+
format_name_and_value(
|
|
90
|
+
"server_log_file", str(server_log_file_path(global_settings().local_server_port))
|
|
91
|
+
)
|
|
92
|
+
)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from prettyfmt import fmt_lines
|
|
4
|
+
|
|
5
|
+
from kash.commands.base.basic_file_commands import trash
|
|
6
|
+
from kash.commands.workspace.selection_commands import select
|
|
7
|
+
from kash.config.logger import get_logger
|
|
8
|
+
from kash.exec import assemble_path_args, kash_command, resolvable_paths
|
|
9
|
+
from kash.exec_model.shell_model import ShellResult
|
|
10
|
+
from kash.shell.output.shell_output import print_status
|
|
11
|
+
from kash.text_handling.doc_normalization import normalize_text_file
|
|
12
|
+
from kash.utils.common.format_utils import fmt_loc
|
|
13
|
+
from kash.utils.file_utils.filename_parsing import join_filename, split_filename
|
|
14
|
+
|
|
15
|
+
log = get_logger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@kash_command
|
|
19
|
+
def reformat(*paths: str, inplace: bool = False) -> ShellResult:
|
|
20
|
+
"""
|
|
21
|
+
Format text, Markdown, or HTML according to kash conventions.
|
|
22
|
+
|
|
23
|
+
TODO: Also handle JSON and YAML.
|
|
24
|
+
|
|
25
|
+
:param inplace: Overwrite the original file. Otherwise save to a new
|
|
26
|
+
file with `_formatted` appended to the original name.
|
|
27
|
+
"""
|
|
28
|
+
resolved_paths = assemble_path_args(*paths)
|
|
29
|
+
final_paths = []
|
|
30
|
+
|
|
31
|
+
for path in resolved_paths:
|
|
32
|
+
target_path = None
|
|
33
|
+
dirname, name, item_type, ext = split_filename(path)
|
|
34
|
+
new_name = f"{name}_formatted"
|
|
35
|
+
target_path = join_filename(dirname, new_name, item_type, ext)
|
|
36
|
+
|
|
37
|
+
normalize_text_file(path, target_path=target_path)
|
|
38
|
+
if inplace:
|
|
39
|
+
trash(path)
|
|
40
|
+
os.rename(target_path, path)
|
|
41
|
+
print_status("Formatted:\n%s", fmt_lines([fmt_loc(path)]))
|
|
42
|
+
final_paths.append(path)
|
|
43
|
+
else:
|
|
44
|
+
print_status(
|
|
45
|
+
"Formatted:\n%s",
|
|
46
|
+
fmt_lines([f"{fmt_loc(path)} -> {fmt_loc(target_path)}"]),
|
|
47
|
+
)
|
|
48
|
+
final_paths.append(target_path)
|
|
49
|
+
|
|
50
|
+
resolvable = resolvable_paths(final_paths)
|
|
51
|
+
if resolvable:
|
|
52
|
+
select(*resolvable)
|
|
53
|
+
|
|
54
|
+
return ShellResult(show_selection=len(resolvable) > 0)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from kash.config.logger import get_logger
|
|
4
|
+
from kash.errors import InvalidState
|
|
5
|
+
from kash.exec import assemble_path_args, kash_command
|
|
6
|
+
from kash.exec_model.shell_model import ShellResult
|
|
7
|
+
from kash.shell.output.shell_output import cprint
|
|
8
|
+
from kash.shell.utils.sys_tool_deps import SysTool, sys_tool_check
|
|
9
|
+
from kash.utils.common.parse_shell_args import shell_quote
|
|
10
|
+
|
|
11
|
+
log = get_logger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@kash_command
|
|
15
|
+
def search(
|
|
16
|
+
query_str: str,
|
|
17
|
+
*paths: str,
|
|
18
|
+
sort: str = "path",
|
|
19
|
+
ignore_case: bool = False,
|
|
20
|
+
verbose: bool = False,
|
|
21
|
+
) -> ShellResult:
|
|
22
|
+
"""
|
|
23
|
+
Search for a string in files at the given paths and return their store paths.
|
|
24
|
+
Useful to find all docs or resources matching a string or regex. This wraps
|
|
25
|
+
ripgrep.
|
|
26
|
+
|
|
27
|
+
Example: Look for all resource files containing the string "youtube.com",
|
|
28
|
+
sorted by date modified:
|
|
29
|
+
|
|
30
|
+
search "youtube.com" resources/ --sort=modified
|
|
31
|
+
|
|
32
|
+
:param sort: How to sort results. Can be `path` or `modified` or `created` (as with `rg`).
|
|
33
|
+
:param ignore_case: Ignore case when searching.
|
|
34
|
+
:param verbose: Also print the ripgrep command line.
|
|
35
|
+
"""
|
|
36
|
+
sys_tool_check().require(SysTool.ripgrep)
|
|
37
|
+
from ripgrepy import RipGrepNotFound, Ripgrepy
|
|
38
|
+
|
|
39
|
+
resolved_paths = assemble_path_args(*paths)
|
|
40
|
+
|
|
41
|
+
strip_prefix = None
|
|
42
|
+
if not resolved_paths:
|
|
43
|
+
resolved_paths = (Path("."),)
|
|
44
|
+
strip_prefix = "./"
|
|
45
|
+
try:
|
|
46
|
+
rg = Ripgrepy(query_str, *[str(p) for p in resolved_paths])
|
|
47
|
+
rg = rg.files_with_matches().sort(sort)
|
|
48
|
+
if ignore_case:
|
|
49
|
+
rg = rg.ignore_case()
|
|
50
|
+
if verbose:
|
|
51
|
+
command = " ".join(
|
|
52
|
+
[shell_quote(arg) for arg in rg.command]
|
|
53
|
+
+ [query_str]
|
|
54
|
+
+ [str(p) for p in resolved_paths]
|
|
55
|
+
)
|
|
56
|
+
cprint(f"{command}")
|
|
57
|
+
rg_output = rg.run().as_string
|
|
58
|
+
results: list[str] = [
|
|
59
|
+
line.lstrip(strip_prefix) if strip_prefix and line.startswith(strip_prefix) else line
|
|
60
|
+
for line in rg_output.splitlines()
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
return ShellResult(results, show_result=True)
|
|
64
|
+
except RipGrepNotFound:
|
|
65
|
+
raise InvalidState("`rg` command not found. Install ripgrep to use the search command.")
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from kash.config.logger import get_logger
|
|
2
|
+
from kash.config.text_styles import STYLE_HINT
|
|
3
|
+
from kash.errors import InvalidInput, InvalidState
|
|
4
|
+
from kash.exec import assemble_path_args, kash_command
|
|
5
|
+
from kash.model.paths_model import StorePath
|
|
6
|
+
from kash.shell.output.shell_output import cprint
|
|
7
|
+
from kash.shell.utils.native_utils import ViewMode, terminal_show_image, view_file_native
|
|
8
|
+
from kash.web_content.file_cache_utils import cache_file
|
|
9
|
+
from kash.workspaces import current_ws
|
|
10
|
+
|
|
11
|
+
log = get_logger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@kash_command
|
|
15
|
+
def show(
|
|
16
|
+
path: str | None = None,
|
|
17
|
+
console: bool = False,
|
|
18
|
+
native: bool = False,
|
|
19
|
+
thumbnail: bool = False,
|
|
20
|
+
browser: bool = False,
|
|
21
|
+
) -> None:
|
|
22
|
+
"""
|
|
23
|
+
Show the contents of a file if one is given, or the first file if multiple files
|
|
24
|
+
are selected. Will try to use native apps or web browser to display the file if
|
|
25
|
+
appropriate, and otherwise display the file in the console.
|
|
26
|
+
|
|
27
|
+
Will use `bat` if available to show files in the console, including syntax
|
|
28
|
+
highlighting and git diffs.
|
|
29
|
+
|
|
30
|
+
:param console: Force display to console (not browser or native apps).
|
|
31
|
+
:param native: Force display with a native app (depending on your system configuration).
|
|
32
|
+
:param thumbnail: If there is a thumbnail image, show it too.
|
|
33
|
+
:param browser: Force display with your default web browser.
|
|
34
|
+
"""
|
|
35
|
+
view_mode = (
|
|
36
|
+
ViewMode.console
|
|
37
|
+
if console
|
|
38
|
+
else ViewMode.browser
|
|
39
|
+
if browser
|
|
40
|
+
else ViewMode.native
|
|
41
|
+
if native
|
|
42
|
+
else ViewMode.auto
|
|
43
|
+
)
|
|
44
|
+
try:
|
|
45
|
+
input_paths = assemble_path_args(path)
|
|
46
|
+
input_path = input_paths[0]
|
|
47
|
+
|
|
48
|
+
if isinstance(input_path, StorePath):
|
|
49
|
+
ws = current_ws()
|
|
50
|
+
if input_path.is_file():
|
|
51
|
+
# Optionally, if we can inline display the image (like in kitty) above the text representation, do that.
|
|
52
|
+
item = ws.load(input_path)
|
|
53
|
+
if thumbnail and item.thumbnail_url:
|
|
54
|
+
try:
|
|
55
|
+
local_path, _was_cached = cache_file(item.thumbnail_url)
|
|
56
|
+
terminal_show_image(local_path)
|
|
57
|
+
except Exception as e:
|
|
58
|
+
log.info("Had trouble showing thumbnail image (will skip): %s", e)
|
|
59
|
+
cprint(f"[Image: {item.thumbnail_url}]", style=STYLE_HINT)
|
|
60
|
+
|
|
61
|
+
view_file_native(ws.base_dir / input_path, view_mode=view_mode)
|
|
62
|
+
else:
|
|
63
|
+
view_file_native(input_path, view_mode=view_mode)
|
|
64
|
+
except (InvalidInput, InvalidState):
|
|
65
|
+
if path:
|
|
66
|
+
# If path is absolute or we couldbn't get a selection, just show the file.
|
|
67
|
+
view_file_native(path, view_mode=view_mode)
|
|
68
|
+
else:
|
|
69
|
+
raise InvalidInput("No selection")
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from flowmark import Wrap
|
|
2
|
+
from rich.text import Text
|
|
3
|
+
|
|
4
|
+
from kash.exec import kash_command
|
|
5
|
+
from kash.shell.output.shell_output import cprint
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@kash_command
|
|
9
|
+
def explain_unicode(text: str) -> None:
|
|
10
|
+
"""
|
|
11
|
+
Explain the Unicode characters in the given text, showing their hex code and their full Unicode name.
|
|
12
|
+
"""
|
|
13
|
+
import unicodedata
|
|
14
|
+
|
|
15
|
+
def unicode_char_name(char: str) -> str:
|
|
16
|
+
try:
|
|
17
|
+
return unicodedata.name(char)
|
|
18
|
+
except ValueError:
|
|
19
|
+
return hex(ord(char))
|
|
20
|
+
|
|
21
|
+
for i, char in enumerate(text):
|
|
22
|
+
cprint(
|
|
23
|
+
Text.from_markup(
|
|
24
|
+
f"[hint]{i:05d}[/hint]: [key]{char}[/key] [hint]([/hint][emph]{unicode_char_name(char)}[/emph][hint])[/hint]"
|
|
25
|
+
),
|
|
26
|
+
text_wrap=Wrap.NONE,
|
|
27
|
+
)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from rich import get_console
|
|
2
|
+
|
|
3
|
+
from kash.commands.base.basic_file_commands import trash
|
|
4
|
+
from kash.commands.workspace.selection_commands import select
|
|
5
|
+
from kash.config.logger import get_logger
|
|
6
|
+
from kash.config.text_styles import PROMPT_ASSIST, SPINNER
|
|
7
|
+
from kash.exec import kash_command
|
|
8
|
+
from kash.exec_model.shell_model import ShellResult
|
|
9
|
+
from kash.form_input.prompt_input import prompt_simple_string
|
|
10
|
+
from kash.help.assistant import (
|
|
11
|
+
AssistanceType,
|
|
12
|
+
assist_system_message_with_state,
|
|
13
|
+
shell_context_assistance,
|
|
14
|
+
)
|
|
15
|
+
from kash.llm_utils.language_models import LLM
|
|
16
|
+
from kash.model.items_model import Item, ItemType
|
|
17
|
+
from kash.shell.utils.native_utils import tail_file
|
|
18
|
+
from kash.utils.file_utils.file_formats_model import Format
|
|
19
|
+
from kash.workspaces import current_ws
|
|
20
|
+
|
|
21
|
+
log = get_logger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@kash_command
|
|
25
|
+
def assist(
|
|
26
|
+
input: str | None = None,
|
|
27
|
+
model: LLM | None = None,
|
|
28
|
+
type: AssistanceType = AssistanceType.basic,
|
|
29
|
+
) -> None:
|
|
30
|
+
"""
|
|
31
|
+
Invoke the kash assistant. You don't normally need this command as it is the same as just
|
|
32
|
+
asking a question (a question ending with ?) on the kash console.
|
|
33
|
+
|
|
34
|
+
:param type: The type of assistance to use.
|
|
35
|
+
:param model: The model to use for the assistant. If not provided, the default model
|
|
36
|
+
for the assistant type is used.
|
|
37
|
+
"""
|
|
38
|
+
if not input:
|
|
39
|
+
input = prompt_simple_string(
|
|
40
|
+
"What do you need help with? (Ask any question or press enter to see main `help` page.)",
|
|
41
|
+
prompt_symbol=PROMPT_ASSIST,
|
|
42
|
+
)
|
|
43
|
+
if not input.strip():
|
|
44
|
+
help()
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
with get_console().status("Thinking…", spinner=SPINNER):
|
|
48
|
+
shell_context_assistance(input, model=model, assistance_type=type)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@kash_command
|
|
52
|
+
def assistant_system_message(
|
|
53
|
+
is_structured: bool = False, skip_api_docs: bool = False
|
|
54
|
+
) -> ShellResult:
|
|
55
|
+
"""
|
|
56
|
+
Save the assistant system message. Useful for debugging.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
item = Item(
|
|
60
|
+
type=ItemType.export,
|
|
61
|
+
title="Assistant System Message",
|
|
62
|
+
format=Format.markdown,
|
|
63
|
+
body=assist_system_message_with_state(
|
|
64
|
+
is_structured=is_structured, skip_api_docs=skip_api_docs
|
|
65
|
+
),
|
|
66
|
+
)
|
|
67
|
+
ws = current_ws()
|
|
68
|
+
store_path = ws.save(item, as_tmp=True)
|
|
69
|
+
|
|
70
|
+
log.message("Saved assistant system message to %s", store_path)
|
|
71
|
+
|
|
72
|
+
select(store_path)
|
|
73
|
+
|
|
74
|
+
return ShellResult(show_selection=True)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@kash_command
|
|
78
|
+
def assistant_history(follow: bool = False) -> None:
|
|
79
|
+
"""
|
|
80
|
+
Show the assistant history for the current workspace.
|
|
81
|
+
|
|
82
|
+
:param follow: Follow the file as it grows.
|
|
83
|
+
"""
|
|
84
|
+
ws = current_ws()
|
|
85
|
+
tail_file(ws.base_dir / ws.dirs.assistant_history_yml, follow=follow)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@kash_command
|
|
89
|
+
def clear_assistant() -> None:
|
|
90
|
+
"""
|
|
91
|
+
Clear the assistant history for the current workspace. Old history file will be
|
|
92
|
+
moved to the trash.
|
|
93
|
+
"""
|
|
94
|
+
ws = current_ws()
|
|
95
|
+
path = ws.base_dir / ws.dirs.assistant_history_yml
|
|
96
|
+
if path.exists():
|
|
97
|
+
trash(path)
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from rich.box import SQUARE
|
|
5
|
+
from rich.console import Group
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
from rich.text import Text
|
|
8
|
+
|
|
9
|
+
from kash.config.colors import rich_terminal_dark
|
|
10
|
+
from kash.config.logger import get_logger, record_console
|
|
11
|
+
from kash.config.text_styles import (
|
|
12
|
+
COLOR_HINT,
|
|
13
|
+
CONSOLE_WRAP_WIDTH,
|
|
14
|
+
LOGO_LARGE,
|
|
15
|
+
STYLE_EMPH,
|
|
16
|
+
STYLE_LOGO,
|
|
17
|
+
TAGLINE_STYLED,
|
|
18
|
+
)
|
|
19
|
+
from kash.docs.all_docs import all_docs
|
|
20
|
+
from kash.exec import kash_command
|
|
21
|
+
from kash.help.help_pages import print_see_also
|
|
22
|
+
from kash.shell.output.shell_output import PrintHooks, console_pager, cprint, print_markdown
|
|
23
|
+
from kash.utils.rich_custom.rich_markdown_fork import Markdown
|
|
24
|
+
from kash.version import get_version_name
|
|
25
|
+
|
|
26
|
+
log = get_logger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Break the line into non-space and space chunks by using a regex.
|
|
30
|
+
# Colorize each chunk and optionally swap lines to spaces.
|
|
31
|
+
def logo_colorize_line(line: str, space_replacement: str = " ", line_offset: int = 0) -> Text:
|
|
32
|
+
line = " " * line_offset + line
|
|
33
|
+
bits = re.findall(r"[^\s]+|\s+", line)
|
|
34
|
+
texts = []
|
|
35
|
+
solid_count = 0
|
|
36
|
+
for i, bit in enumerate(bits):
|
|
37
|
+
if bit.strip():
|
|
38
|
+
texts.append(
|
|
39
|
+
Text(
|
|
40
|
+
bit,
|
|
41
|
+
style=STYLE_LOGO if solid_count > 0 else STYLE_EMPH,
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
solid_count += 1
|
|
45
|
+
else:
|
|
46
|
+
bit = re.sub(r" ", space_replacement, bit)
|
|
47
|
+
if i > 0:
|
|
48
|
+
bit = " " + bit[1:]
|
|
49
|
+
if i < len(bits) - 1:
|
|
50
|
+
bit = bit[:-1] + " "
|
|
51
|
+
texts.append(Text(bit, style=COLOR_HINT))
|
|
52
|
+
return Text.assemble(*texts)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def color_logo() -> Group:
|
|
56
|
+
logo_lines = LOGO_LARGE.split("\n")
|
|
57
|
+
left_margin = 2
|
|
58
|
+
offset = 2
|
|
59
|
+
return Group(
|
|
60
|
+
*[logo_colorize_line(line, " ", left_margin + offset) for line in logo_lines],
|
|
61
|
+
Text.assemble(" " * left_margin, TAGLINE_STYLED),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def branded_box(content: Group | None, version: str | None = None) -> Panel:
|
|
66
|
+
line_char = "─"
|
|
67
|
+
panel_width = CONSOLE_WRAP_WIDTH
|
|
68
|
+
|
|
69
|
+
logo_lines = LOGO_LARGE.split("\n")
|
|
70
|
+
logo_top = logo_colorize_line(logo_lines[0], line_char)
|
|
71
|
+
offset = (panel_width - 4 - len(logo_top)) // 2
|
|
72
|
+
tagline_offset = (panel_width - 4 - len(TAGLINE_STYLED)) // 2 + 1
|
|
73
|
+
|
|
74
|
+
logo_rest = [logo_colorize_line(line, " ", offset) for line in logo_lines[1:]]
|
|
75
|
+
if version:
|
|
76
|
+
header = Text.assemble(*logo_top)
|
|
77
|
+
footer = Text(version, style=COLOR_HINT, justify="right")
|
|
78
|
+
else:
|
|
79
|
+
header = Text.assemble(*logo_top)
|
|
80
|
+
footer = None
|
|
81
|
+
|
|
82
|
+
body = ["", content] if content else []
|
|
83
|
+
|
|
84
|
+
return Panel(
|
|
85
|
+
Group(
|
|
86
|
+
*logo_rest,
|
|
87
|
+
"",
|
|
88
|
+
Text.assemble(" " * tagline_offset, TAGLINE_STYLED),
|
|
89
|
+
# Text(" " * tagline_offset + "🮎" * len(TAGLINE_STYLED), style=STYLE_EMPH),
|
|
90
|
+
*body,
|
|
91
|
+
),
|
|
92
|
+
title=header,
|
|
93
|
+
title_align="center",
|
|
94
|
+
subtitle=footer,
|
|
95
|
+
subtitle_align="right",
|
|
96
|
+
border_style=COLOR_HINT,
|
|
97
|
+
padding=(0, 1),
|
|
98
|
+
width=panel_width,
|
|
99
|
+
box=SQUARE,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@kash_command
|
|
104
|
+
def kash_logo(box: bool = False, svg_out: str | None = None, html_out: str | None = None) -> None:
|
|
105
|
+
"""
|
|
106
|
+
Show the kash logo.
|
|
107
|
+
"""
|
|
108
|
+
logo = branded_box(None) if box else color_logo()
|
|
109
|
+
|
|
110
|
+
cprint(logo)
|
|
111
|
+
|
|
112
|
+
if svg_out:
|
|
113
|
+
with record_console() as console:
|
|
114
|
+
console.print(logo)
|
|
115
|
+
with Path(svg_out).open("w") as f:
|
|
116
|
+
f.write(console.export_svg(theme=rich_terminal_dark))
|
|
117
|
+
log.message(f"Wrote logo: {svg_out}")
|
|
118
|
+
if html_out:
|
|
119
|
+
with record_console() as console:
|
|
120
|
+
console.print(logo)
|
|
121
|
+
with Path(html_out).open("w") as f:
|
|
122
|
+
f.write(console.export_html(theme=rich_terminal_dark))
|
|
123
|
+
log.message(f"Wrote logo: {html_out}")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@kash_command
|
|
127
|
+
def welcome() -> None:
|
|
128
|
+
"""
|
|
129
|
+
Print a welcome message.
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
help_topics = all_docs.help_topics
|
|
133
|
+
version = get_version_name()
|
|
134
|
+
# Create header with logo and right-justified version
|
|
135
|
+
|
|
136
|
+
PrintHooks.before_welcome()
|
|
137
|
+
cprint(
|
|
138
|
+
branded_box(
|
|
139
|
+
Group(Markdown(help_topics.welcome)),
|
|
140
|
+
version,
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
cprint(Panel(Markdown(help_topics.warning), box=SQUARE, border_style=COLOR_HINT))
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@kash_command
|
|
147
|
+
def manual(no_pager: bool = False) -> None:
|
|
148
|
+
"""
|
|
149
|
+
Show the kash full manual with all help pages.
|
|
150
|
+
"""
|
|
151
|
+
# TODO: Take an argument to show help for a specific command or action.
|
|
152
|
+
|
|
153
|
+
from kash.help.help_pages import print_manual
|
|
154
|
+
|
|
155
|
+
with console_pager(use_pager=not no_pager):
|
|
156
|
+
print_manual()
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@kash_command
|
|
160
|
+
def why_kash() -> None:
|
|
161
|
+
"""
|
|
162
|
+
Show help on why kash was created.
|
|
163
|
+
"""
|
|
164
|
+
help_topics = all_docs.help_topics
|
|
165
|
+
with console_pager():
|
|
166
|
+
print_markdown(help_topics.what_is_kash)
|
|
167
|
+
print_markdown(help_topics.philosophy_of_kash)
|
|
168
|
+
print_see_also(["help", "getting_started", "faq", "commands", "actions"])
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@kash_command
|
|
172
|
+
def installation() -> None:
|
|
173
|
+
"""
|
|
174
|
+
Show help on installing kash.
|
|
175
|
+
"""
|
|
176
|
+
help_topics = all_docs.help_topics
|
|
177
|
+
with console_pager():
|
|
178
|
+
print_markdown(help_topics.installation)
|
|
179
|
+
print_see_also(
|
|
180
|
+
[
|
|
181
|
+
"What is kash?",
|
|
182
|
+
"What can I do with kash?",
|
|
183
|
+
"getting_started",
|
|
184
|
+
"What are the most important kash commands?",
|
|
185
|
+
"commands",
|
|
186
|
+
"actions",
|
|
187
|
+
"check_tools",
|
|
188
|
+
"faq",
|
|
189
|
+
]
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@kash_command
|
|
194
|
+
def getting_started() -> None:
|
|
195
|
+
"""
|
|
196
|
+
Show help on getting started using kash.
|
|
197
|
+
"""
|
|
198
|
+
help_topics = all_docs.help_topics
|
|
199
|
+
with console_pager():
|
|
200
|
+
print_markdown(help_topics.getting_started)
|
|
201
|
+
print_see_also(
|
|
202
|
+
[
|
|
203
|
+
"What is kash?",
|
|
204
|
+
"What can I do with kash?",
|
|
205
|
+
"What are the most important kash commands?",
|
|
206
|
+
"commands",
|
|
207
|
+
"actions",
|
|
208
|
+
"check_tools",
|
|
209
|
+
"faq",
|
|
210
|
+
]
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@kash_command
|
|
215
|
+
def faq() -> None:
|
|
216
|
+
"""
|
|
217
|
+
Show the kash FAQ.
|
|
218
|
+
"""
|
|
219
|
+
help_topics = all_docs.help_topics
|
|
220
|
+
with console_pager():
|
|
221
|
+
print_markdown(help_topics.faq)
|
|
222
|
+
|
|
223
|
+
print_see_also(["help", "commands", "actions"])
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
DOC_COMMANDS = [welcome, manual, why_kash, getting_started, faq]
|