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.
Files changed (269) hide show
  1. kash/__init__.py +2 -0
  2. kash/__main__.py +4 -0
  3. kash/actions/__init__.py +55 -0
  4. kash/actions/core/assistant_chat.py +45 -0
  5. kash/actions/core/chat.py +90 -0
  6. kash/actions/core/format_markdown_template.py +92 -0
  7. kash/actions/core/markdownify.py +29 -0
  8. kash/actions/core/readability.py +27 -0
  9. kash/actions/core/show_webpage.py +28 -0
  10. kash/actions/core/strip_html.py +28 -0
  11. kash/actions/core/summarize_as_bullets.py +53 -0
  12. kash/actions/core/webpage_config.py +21 -0
  13. kash/actions/core/webpage_generate.py +29 -0
  14. kash/actions/meta/write_instructions.py +39 -0
  15. kash/actions/meta/write_new_action.py +157 -0
  16. kash/commands/__init__.py +21 -0
  17. kash/commands/base/basic_file_commands.py +183 -0
  18. kash/commands/base/browser_commands.py +62 -0
  19. kash/commands/base/debug_commands.py +214 -0
  20. kash/commands/base/diff_commands.py +90 -0
  21. kash/commands/base/files_command.py +408 -0
  22. kash/commands/base/general_commands.py +104 -0
  23. kash/commands/base/global_state_commands.py +41 -0
  24. kash/commands/base/logs_commands.py +92 -0
  25. kash/commands/base/reformat_command.py +54 -0
  26. kash/commands/base/search_command.py +65 -0
  27. kash/commands/base/show_command.py +69 -0
  28. kash/commands/extras/utils_commands.py +27 -0
  29. kash/commands/help/assistant_commands.py +97 -0
  30. kash/commands/help/doc_commands.py +226 -0
  31. kash/commands/help/help_commands.py +133 -0
  32. kash/commands/workspace/selection_commands.py +200 -0
  33. kash/commands/workspace/workspace_commands.py +640 -0
  34. kash/concepts/concept_formats.py +23 -0
  35. kash/concepts/embeddings.py +130 -0
  36. kash/concepts/text_similarity.py +108 -0
  37. kash/config/__init__.py +4 -0
  38. kash/config/api_keys.py +84 -0
  39. kash/config/capture_output.py +77 -0
  40. kash/config/colors.py +279 -0
  41. kash/config/init.py +18 -0
  42. kash/config/lazy_imports.py +22 -0
  43. kash/config/logger.py +355 -0
  44. kash/config/logger_basic.py +35 -0
  45. kash/config/logo.txt +4 -0
  46. kash/config/logo_fancy.txt +4 -0
  47. kash/config/server_config.py +51 -0
  48. kash/config/settings.py +196 -0
  49. kash/config/setup.py +51 -0
  50. kash/config/suppress_warnings.py +27 -0
  51. kash/config/text_styles.py +426 -0
  52. kash/docs/__init__.py +0 -0
  53. kash/docs/all_docs.py +58 -0
  54. kash/docs/load_actions_info.py +28 -0
  55. kash/docs/load_api_docs.py +13 -0
  56. kash/docs/load_help_topics.py +47 -0
  57. kash/docs/load_source_code.py +125 -0
  58. kash/docs/markdown/api_docs_template.md +42 -0
  59. kash/docs/markdown/assistant_instructions_template.md +114 -0
  60. kash/docs/markdown/readme_template.md +26 -0
  61. kash/docs/markdown/topics/a1_what_is_kash.md +76 -0
  62. kash/docs/markdown/topics/a2_progress.md +96 -0
  63. kash/docs/markdown/topics/a3_installation.md +119 -0
  64. kash/docs/markdown/topics/a4_getting_started.md +300 -0
  65. kash/docs/markdown/topics/a5_tips_for_use_with_other_tools.md +83 -0
  66. kash/docs/markdown/topics/b0_philosophy_of_kash.md +177 -0
  67. kash/docs/markdown/topics/b1_kash_overview.md +124 -0
  68. kash/docs/markdown/topics/b2_workspace_and_file_formats.md +61 -0
  69. kash/docs/markdown/topics/b3_modern_shell_tool_recommendations.md +83 -0
  70. kash/docs/markdown/topics/b4_faq.md +166 -0
  71. kash/docs/markdown/warning.md +7 -0
  72. kash/docs/markdown/welcome.md +9 -0
  73. kash/docs_base/docs_base.py +85 -0
  74. kash/docs_base/load_custom_command_info.py +27 -0
  75. kash/docs_base/load_faqs.py +48 -0
  76. kash/docs_base/load_recipe_snippets.py +48 -0
  77. kash/docs_base/recipes/general_system_commands.ksh +10 -0
  78. kash/docs_base/recipes/python_dev_commands.ksh +7 -0
  79. kash/docs_base/recipes/tldr_standard_commands.ksh +2144 -0
  80. kash/errors.py +176 -0
  81. kash/exec/__init__.py +16 -0
  82. kash/exec/action_decorators.py +412 -0
  83. kash/exec/action_exec.py +457 -0
  84. kash/exec/action_registry.py +123 -0
  85. kash/exec/combiners.py +127 -0
  86. kash/exec/command_exec.py +34 -0
  87. kash/exec/command_registry.py +72 -0
  88. kash/exec/fetch_url_metadata.py +71 -0
  89. kash/exec/history.py +44 -0
  90. kash/exec/llm_transforms.py +121 -0
  91. kash/exec/precondition_checks.py +71 -0
  92. kash/exec/precondition_registry.py +43 -0
  93. kash/exec/preconditions.py +152 -0
  94. kash/exec/resolve_args.py +123 -0
  95. kash/exec/shell_callable_action.py +90 -0
  96. kash/exec_model/__init__.py +0 -0
  97. kash/exec_model/args_model.py +93 -0
  98. kash/exec_model/commands_model.py +163 -0
  99. kash/exec_model/script_model.py +161 -0
  100. kash/exec_model/shell_model.py +21 -0
  101. kash/file_storage/__init__.py +0 -0
  102. kash/file_storage/file_store.py +642 -0
  103. kash/file_storage/item_file_format.py +152 -0
  104. kash/file_storage/metadata_dirs.py +108 -0
  105. kash/file_storage/mtime_cache.py +108 -0
  106. kash/file_storage/persisted_yaml.py +37 -0
  107. kash/file_storage/store_cache_warmer.py +37 -0
  108. kash/file_storage/store_filenames.py +53 -0
  109. kash/form_input/__init__.py +0 -0
  110. kash/form_input/prompt_input.py +44 -0
  111. kash/help/__init__.py +0 -0
  112. kash/help/assistant.py +324 -0
  113. kash/help/assistant_instructions.py +68 -0
  114. kash/help/assistant_output.py +43 -0
  115. kash/help/docstring_utils.py +111 -0
  116. kash/help/function_param_info.py +44 -0
  117. kash/help/help_embeddings.py +85 -0
  118. kash/help/help_lookups.py +60 -0
  119. kash/help/help_pages.py +122 -0
  120. kash/help/help_printing.py +169 -0
  121. kash/help/help_types.py +247 -0
  122. kash/help/recommended_commands.py +143 -0
  123. kash/help/tldr_help.py +296 -0
  124. kash/llm_utils/__init__.py +0 -0
  125. kash/llm_utils/chat_format.py +413 -0
  126. kash/llm_utils/clean_headings.py +65 -0
  127. kash/llm_utils/fuzzy_parsing.py +119 -0
  128. kash/llm_utils/language_models.py +178 -0
  129. kash/llm_utils/llm_completion.py +172 -0
  130. kash/llm_utils/llm_messages.py +36 -0
  131. kash/local_server/__init__.py +2 -0
  132. kash/local_server/local_server.py +183 -0
  133. kash/local_server/local_server_commands.py +55 -0
  134. kash/local_server/local_server_routes.py +306 -0
  135. kash/local_server/local_url_formatters.py +169 -0
  136. kash/local_server/port_tools.py +67 -0
  137. kash/local_server/rich_html_template.py +12 -0
  138. kash/mcp/__init__.py +2 -0
  139. kash/mcp/mcp_main.py +67 -0
  140. kash/mcp/mcp_server_commands.py +57 -0
  141. kash/mcp/mcp_server_routes.py +256 -0
  142. kash/mcp/mcp_server_sse.py +143 -0
  143. kash/mcp/mcp_server_stdio.py +45 -0
  144. kash/media_base/__init__.py +0 -0
  145. kash/media_base/audio_processing.py +27 -0
  146. kash/media_base/media_cache.py +178 -0
  147. kash/media_base/media_services.py +112 -0
  148. kash/media_base/media_tools.py +48 -0
  149. kash/media_base/services/local_file_media.py +165 -0
  150. kash/media_base/speech_transcription.py +224 -0
  151. kash/media_base/timestamp_citations.py +80 -0
  152. kash/model/__init__.py +73 -0
  153. kash/model/actions_model.py +633 -0
  154. kash/model/assistant_response_model.py +87 -0
  155. kash/model/compound_actions_model.py +188 -0
  156. kash/model/graph_model.py +92 -0
  157. kash/model/items_model.py +821 -0
  158. kash/model/language_list.py +39 -0
  159. kash/model/llm_actions_model.py +63 -0
  160. kash/model/media_model.py +124 -0
  161. kash/model/operations_model.py +176 -0
  162. kash/model/params_model.py +435 -0
  163. kash/model/paths_model.py +458 -0
  164. kash/model/preconditions_model.py +98 -0
  165. kash/shell/__init__.py +0 -0
  166. kash/shell/completions/completion_scoring.py +280 -0
  167. kash/shell/completions/completion_types.py +154 -0
  168. kash/shell/completions/shell_completions.py +277 -0
  169. kash/shell/file_icons/color_for_format.py +70 -0
  170. kash/shell/file_icons/nerd_icons.py +946 -0
  171. kash/shell/output/__init__.py +0 -0
  172. kash/shell/output/kerm_code_utils.py +59 -0
  173. kash/shell/output/kerm_codes.py +588 -0
  174. kash/shell/output/kmarkdown.py +117 -0
  175. kash/shell/output/shell_output.py +477 -0
  176. kash/shell/ui/__init__.py +0 -0
  177. kash/shell/ui/shell_results.py +118 -0
  178. kash/shell/ui/shell_syntax.py +26 -0
  179. kash/shell/utils/exception_printing.py +50 -0
  180. kash/shell/utils/native_utils.py +240 -0
  181. kash/shell/utils/osc_utils.py +95 -0
  182. kash/shell/utils/shell_function_wrapper.py +204 -0
  183. kash/shell/utils/sys_tool_deps.py +289 -0
  184. kash/shell/utils/terminal_images.py +133 -0
  185. kash/shell_main.py +67 -0
  186. kash/text_handling/custom_sliding_transforms.py +266 -0
  187. kash/text_handling/doc_normalization.py +64 -0
  188. kash/text_handling/markdown_util.py +167 -0
  189. kash/text_handling/unified_diffs.py +138 -0
  190. kash/utils/__init__.py +4 -0
  191. kash/utils/common/__init__.py +4 -0
  192. kash/utils/common/atomic_var.py +147 -0
  193. kash/utils/common/format_utils.py +81 -0
  194. kash/utils/common/function_inspect.py +178 -0
  195. kash/utils/common/import_utils.py +89 -0
  196. kash/utils/common/lazyobject.py +144 -0
  197. kash/utils/common/obj_replace.py +78 -0
  198. kash/utils/common/parse_key_vals.py +85 -0
  199. kash/utils/common/parse_shell_args.py +348 -0
  200. kash/utils/common/stack_traces.py +49 -0
  201. kash/utils/common/string_replace.py +93 -0
  202. kash/utils/common/string_template.py +101 -0
  203. kash/utils/common/task_stack.py +162 -0
  204. kash/utils/common/type_utils.py +137 -0
  205. kash/utils/common/uniquifier.py +95 -0
  206. kash/utils/common/url.py +155 -0
  207. kash/utils/file_utils/__init__.py +3 -0
  208. kash/utils/file_utils/dir_size.py +48 -0
  209. kash/utils/file_utils/file_ext.py +86 -0
  210. kash/utils/file_utils/file_formats.py +134 -0
  211. kash/utils/file_utils/file_formats_model.py +408 -0
  212. kash/utils/file_utils/file_sort_filter.py +235 -0
  213. kash/utils/file_utils/file_walk.py +163 -0
  214. kash/utils/file_utils/filename_parsing.py +99 -0
  215. kash/utils/file_utils/git_tools.py +19 -0
  216. kash/utils/file_utils/ignore_files.py +166 -0
  217. kash/utils/file_utils/path_utils.py +36 -0
  218. kash/utils/lang_utils/__init__.py +0 -0
  219. kash/utils/lang_utils/capitalization.py +128 -0
  220. kash/utils/lang_utils/inflection.py +18 -0
  221. kash/utils/rich_custom/__init__.py +3 -0
  222. kash/utils/rich_custom/ansi_cell_len.py +72 -0
  223. kash/utils/rich_custom/rich_char_transform.py +89 -0
  224. kash/utils/rich_custom/rich_indent.py +69 -0
  225. kash/utils/rich_custom/rich_markdown_fork.py +771 -0
  226. kash/version.py +31 -0
  227. kash/web_content/canon_url.py +24 -0
  228. kash/web_content/dir_store.py +103 -0
  229. kash/web_content/file_cache_utils.py +117 -0
  230. kash/web_content/local_file_cache.py +247 -0
  231. kash/web_content/web_extract.py +55 -0
  232. kash/web_content/web_extract_justext.py +86 -0
  233. kash/web_content/web_extract_readabilipy.py +23 -0
  234. kash/web_content/web_fetch.py +101 -0
  235. kash/web_content/web_page_model.py +28 -0
  236. kash/web_gen/__init__.py +4 -0
  237. kash/web_gen/tabbed_webpage.py +149 -0
  238. kash/web_gen/template_render.py +29 -0
  239. kash/web_gen/templates/base_styles.css.jinja +192 -0
  240. kash/web_gen/templates/base_webpage.html.jinja +124 -0
  241. kash/web_gen/templates/content_styles.css.jinja +194 -0
  242. kash/web_gen/templates/explain_view.html.jinja +49 -0
  243. kash/web_gen/templates/item_view.html.jinja +294 -0
  244. kash/web_gen/templates/tabbed_webpage.html.jinja +49 -0
  245. kash/workspaces/__init__.py +13 -0
  246. kash/workspaces/param_state.py +24 -0
  247. kash/workspaces/selections.py +333 -0
  248. kash/workspaces/source_items.py +88 -0
  249. kash/workspaces/workspace_importing.py +56 -0
  250. kash/workspaces/workspace_names.py +33 -0
  251. kash/workspaces/workspace_output.py +154 -0
  252. kash/workspaces/workspace_registry.py +78 -0
  253. kash/workspaces/workspaces.py +197 -0
  254. kash/xonsh_custom/custom_shell.py +366 -0
  255. kash/xonsh_custom/customize_prompt.py +197 -0
  256. kash/xonsh_custom/customize_xonsh.py +112 -0
  257. kash/xonsh_custom/shell_load_commands.py +152 -0
  258. kash/xonsh_custom/shell_which.py +64 -0
  259. kash/xonsh_custom/xonsh_completers.py +715 -0
  260. kash/xonsh_custom/xonsh_env.py +28 -0
  261. kash/xonsh_custom/xonsh_modern_tools.py +56 -0
  262. kash/xonsh_custom/xonsh_ranking_completer.py +152 -0
  263. kash/xontrib/fnm.py +120 -0
  264. kash/xontrib/kash_extension.py +61 -0
  265. kash_shell-0.3.0.dist-info/METADATA +757 -0
  266. kash_shell-0.3.0.dist-info/RECORD +269 -0
  267. kash_shell-0.3.0.dist-info/WHEEL +4 -0
  268. kash_shell-0.3.0.dist-info/entry_points.txt +3 -0
  269. 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]