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,157 @@
1
+ from flowmark import Wrap, fill_text
2
+
3
+ from kash.actions.meta.write_instructions import write_instructions
4
+ from kash.config.logger import get_logger
5
+ from kash.errors import ApiResultError, InvalidInput
6
+ from kash.exec import kash_action
7
+ from kash.exec.preconditions import is_instructions
8
+ from kash.help.assistant import assist_preamble, assistance_structured
9
+ from kash.help.assistant_output import print_assistant_response
10
+ from kash.llm_utils.chat_format import ChatHistory, ChatMessage, ChatRole
11
+ from kash.llm_utils.language_models import LLMName
12
+ from kash.model import (
13
+ LLM,
14
+ ONE_OR_NO_ARGS,
15
+ ActionInput,
16
+ ActionResult,
17
+ Format,
18
+ ItemType,
19
+ Message,
20
+ TitleTemplate,
21
+ common_params,
22
+ )
23
+ from kash.utils.common.lazyobject import lazyobject
24
+ from kash.utils.common.type_utils import not_none
25
+
26
+ log = get_logger(__name__)
27
+
28
+
29
+ @lazyobject
30
+ def write_action_instructions() -> str:
31
+ from kash.docs.load_source_code import load_source_code
32
+
33
+ return (
34
+ """
35
+ You are a senior software engineer who writes clean code exactly
36
+ conforming with existing practice using the kash framework, which you understand
37
+ clearly. You always want to write actions that are helpful and reusable
38
+ as command-line operations or as Python libraries, since kash makes
39
+ commands and actions available to use both ways.
40
+
41
+ You need to write Python code to implement an action or a command in kash.
42
+
43
+ Guidelines:
44
+
45
+ - Provide Python code in the python_code field.
46
+
47
+ - Choose a command implementation (with @kash_command decorating a function) if the
48
+ operation is very simple and does not have files (Items) to output. To just read
49
+ a file and show information, or print something, use a command. Choose an
50
+ action implementation (with @kash_action decorating a class) if the operation
51
+ has files (Items) to output.
52
+
53
+ - Add non-code commentary in the response_text field explaining any issues or
54
+ assumptions you made while writing the code.
55
+
56
+ - If desired behavior of the code is not clear from the description, add
57
+ comment placeholders in the code so it can be filled in later.
58
+
59
+ - If the user gives other random context, like a previous conversation, consider
60
+ how you would implement a command or action that would generalize the operation
61
+ being discussed. Add parameters to the command or action if it makes sense
62
+ a user would want them, and use defaults as appropriate.
63
+
64
+ - Look at the example below. Commonly, you will subclass PerItemAction
65
+ for simple actions that work on one item at a time. Subclass LLMAction
66
+ if it is simply a transformation of the input using an LLM.
67
+ .
68
+ To illustrate, here are a couple examples of the correct format for an action that
69
+ strips HTML tags:
70
+ """
71
+ + load_source_code().example_action_src.replace("{", "{{").replace("}", "}}")
72
+ + """
73
+
74
+ And here are a couple simple command implementations:
75
+ """
76
+ + load_source_code().example_command_src.replace("{", "{{").replace("}", "}}")
77
+ + """
78
+
79
+ Next you will get context or a request from the user on a problem, or
80
+ simply a previous discussion with an assistant describing a partial solution
81
+ but from which you can understand what action or command would help solve
82
+ the problem.
83
+
84
+ After thinking about the context below, describe a useful command or action
85
+ that is appropriate for the problem, and then give the Python code for it.
86
+ """
87
+ )
88
+
89
+
90
+ @kash_action(
91
+ expected_args=ONE_OR_NO_ARGS,
92
+ precondition=is_instructions,
93
+ cacheable=False,
94
+ interactive_input=True,
95
+ params=common_params("model"),
96
+ title_template=TitleTemplate("Action: {title}"),
97
+ )
98
+ def write_new_action(input: ActionInput, model: LLMName = LLM.default_structured) -> ActionResult:
99
+ """
100
+ Create a new kash action or command in Python, based some problem or context or
101
+ a description of the features.
102
+
103
+ If no input is provided, will start an interactive chat to get
104
+
105
+ Or `write_instructions` to create a chat to use as input for this action.
106
+ """
107
+ if not input.items:
108
+ # Start a chat to collect the action description.
109
+ # FIXME: Consider generalizing this so an action can declare an input action to collect its input.
110
+ chat_result = write_instructions(ActionInput.empty())
111
+ if not chat_result.items:
112
+ raise InvalidInput("No chat input provided")
113
+
114
+ action_description_item = chat_result.items[0]
115
+ else:
116
+ action_description_item = input.items[0]
117
+
118
+ # Manually check precondition since we might have created the item
119
+ is_instructions.check(action_description_item, "action `write_new_action`")
120
+
121
+ chat_history = ChatHistory()
122
+
123
+ instructions_message = ChatHistory.from_yaml(not_none(action_description_item.body)).messages[0]
124
+
125
+ # Give the LLM full context on kash APIs.
126
+ # But we do this here lazily to prevent circular dependencies.
127
+ system_message = Message(assist_preamble(is_structured=True, skip_api_docs=False))
128
+ chat_history.extend(
129
+ [
130
+ ChatMessage(ChatRole.system, system_message),
131
+ ChatMessage(ChatRole.user, str(write_action_instructions)),
132
+ instructions_message,
133
+ ]
134
+ )
135
+
136
+ assistant_response = assistance_structured(chat_history.as_chat_completion(), model)
137
+
138
+ print_assistant_response(assistant_response, model)
139
+
140
+ if not assistant_response.python_code:
141
+ raise ApiResultError("No Python code provided in the response.")
142
+
143
+ body = assistant_response.python_code
144
+ # Put the instructions in an actual comment at the top of the file.
145
+ action_comments = "(This action was written by kash `write_new_action`.)\n\n" + str(
146
+ instructions_message.content
147
+ )
148
+ comment = fill_text(action_comments, text_wrap=Wrap.WRAP_FULL, extra_indent="# ")
149
+ commented_body = "\n\n".join(filter(None, [comment, body]))
150
+
151
+ result_item = action_description_item.derived_copy(
152
+ type=ItemType.extension,
153
+ format=Format.python,
154
+ body=commented_body,
155
+ )
156
+
157
+ return ActionResult([result_item])
@@ -0,0 +1,21 @@
1
+ # Import all command modules to ensure commands are registered.
2
+
3
+ import kash.commands.base.basic_file_commands # noqa: F401
4
+ import kash.commands.base.browser_commands # noqa: F401
5
+ import kash.commands.base.debug_commands # noqa: F401
6
+ import kash.commands.base.diff_commands # noqa: F401
7
+ import kash.commands.base.files_command # noqa: F401
8
+ import kash.commands.base.general_commands # noqa: F401
9
+ import kash.commands.base.global_state_commands # noqa: F401
10
+ import kash.commands.base.logs_commands # noqa: F401
11
+ import kash.commands.base.reformat_command # noqa: F401
12
+ import kash.commands.base.search_command # noqa: F401
13
+ import kash.commands.base.show_command # noqa: F401
14
+ import kash.commands.extras.utils_commands # noqa: F401
15
+ import kash.commands.help.assistant_commands # noqa: F401 # noqa: F401
16
+ import kash.commands.help.doc_commands # noqa: F401
17
+ import kash.commands.help.help_commands # noqa: F401
18
+ import kash.commands.workspace.selection_commands # noqa: F401
19
+ import kash.commands.workspace.workspace_commands # noqa: F401
20
+ import kash.local_server.local_server_commands # noqa: F401
21
+ import kash.mcp.mcp_server_commands # noqa: F401
@@ -0,0 +1,183 @@
1
+ import os
2
+
3
+ from frontmatter_format import fmf_read_raw, fmf_strip_frontmatter
4
+ from prettyfmt import fmt_lines
5
+ from strif import copyfile_atomic
6
+
7
+ from kash.config.logger import get_logger
8
+ from kash.config.text_styles import STYLE_EMPH
9
+ from kash.errors import InvalidInput
10
+ from kash.exec import assemble_path_args, kash_command, resolve_path_arg
11
+ from kash.shell.output.shell_output import (
12
+ PadStyle,
13
+ PrintHooks,
14
+ Wrap,
15
+ cprint,
16
+ print_status,
17
+ print_style,
18
+ )
19
+ from kash.shell.utils.native_utils import edit_files, native_trash
20
+ from kash.shell.utils.native_utils import tail_file as native_tail_file
21
+ from kash.utils.common.format_utils import fmt_loc
22
+ from kash.utils.file_utils.file_formats_model import detect_file_format
23
+ from kash.workspaces.workspace_output import print_file_info
24
+
25
+ log = get_logger(__name__)
26
+
27
+
28
+ @kash_command
29
+ def cbcopy(path: str | None = None, raw: bool = False) -> None:
30
+ """
31
+ Copy the contents of a file (or the first file in the selection) to the OS-native
32
+ clipboard.
33
+
34
+ :param raw: Copy the full exact contents of the file. Otherwise frontmatter is omitted.
35
+ """
36
+ # TODO: Get this to work for images!
37
+ import pyperclip
38
+
39
+ input_paths = assemble_path_args(path)
40
+ input_path = input_paths[0]
41
+
42
+ format = detect_file_format(input_path)
43
+ if not format or not format.is_text:
44
+ raise InvalidInput(f"Cannot copy non-text files to clipboard: {fmt_loc(input_path)}")
45
+
46
+ if raw:
47
+ with open(input_path) as f:
48
+ content = f.read()
49
+
50
+ pyperclip.copy(content)
51
+ print_status(
52
+ "Copied raw contents of file to clipboard (%s chars):\n%s",
53
+ len(content),
54
+ fmt_lines([fmt_loc(input_path)]),
55
+ )
56
+ else:
57
+ content, metadata_str = fmf_read_raw(input_path)
58
+ pyperclip.copy(content)
59
+ skip_msg = ""
60
+ if metadata_str:
61
+ skip_msg = f", skipping {len(metadata_str)} chars of frontmatter"
62
+ print_status(
63
+ "Copied contents of file to clipboard (%s chars%s):\n%s",
64
+ len(content),
65
+ skip_msg,
66
+ fmt_lines([fmt_loc(input_path)]),
67
+ )
68
+
69
+
70
+ @kash_command
71
+ def edit(path: str | None = None, all: bool = False) -> None:
72
+ """
73
+ Edit the contents of a file using the user's default editor (or defaulting to nano).
74
+
75
+ :param all: Normally edits only the first file given. This passes all files to the editor.
76
+ """
77
+ input_paths = assemble_path_args(path)
78
+ if not all:
79
+ input_paths = [input_paths[0]]
80
+
81
+ edit_files(*input_paths)
82
+
83
+
84
+ @kash_command
85
+ def file_info(*paths: str, size_summary: bool = False, format: bool = False) -> None:
86
+ """
87
+ Show info about a file. By default this includes a summary of the size and HTML
88
+ structure of the items at the given paths (for text documents) and the detected
89
+ mime type.
90
+
91
+ :param size_summary: Only show size summary (words, sentences, paragraphs for a text document).
92
+ :param format: Only show detected file format.
93
+ """
94
+ if not size_summary and not format:
95
+ size_summary = format = True
96
+
97
+ # FIXME: Ensure this yields absolute paths for global workspace store paths
98
+ input_paths = assemble_path_args(*paths)
99
+ for input_path in input_paths:
100
+ cprint(f"{fmt_loc(input_path)}:", style=STYLE_EMPH, text_wrap=Wrap.NONE)
101
+ with print_style(PadStyle.INDENT):
102
+ print_file_info(input_path, show_size_details=size_summary, show_format=format)
103
+ PrintHooks.spacer()
104
+
105
+
106
+ @kash_command
107
+ def rename(path: str, new_path: str) -> None:
108
+ """
109
+ Rename a file or item. Creates any new parent paths as needed.
110
+ Note this may invalidate relations that point to the old store path.
111
+
112
+ TODO: Add an option here to update all relations in the workspace.
113
+ """
114
+ from_path, to_path = assemble_path_args(path, new_path)
115
+ to_path.parent.mkdir(parents=True, exist_ok=True)
116
+ os.rename(from_path, to_path)
117
+
118
+ print_status(f"Renamed: {fmt_loc(from_path)} -> {fmt_loc(to_path)}")
119
+
120
+
121
+ @kash_command
122
+ def copy(*paths: str) -> None:
123
+ """
124
+ Copy the items at the given paths to the target path.
125
+ """
126
+ if len(paths) < 2:
127
+ raise InvalidInput("Must provide at least one source path and a target path")
128
+
129
+ src_paths = [resolve_path_arg(path) for path in paths[:-1] if path]
130
+ dest_path = resolve_path_arg(paths[-1])
131
+
132
+ if len(src_paths) == 1 and dest_path.is_dir():
133
+ dest_path = dest_path / src_paths[0].name
134
+ elif len(src_paths) > 1 and not dest_path.is_dir():
135
+ raise InvalidInput(f"Cannot copy multiple files to a file target: {dest_path}")
136
+
137
+ for src_path in src_paths:
138
+ copyfile_atomic(src_path, dest_path, make_parents=True)
139
+
140
+ print_status(
141
+ f"Copied:\n{fmt_lines(fmt_loc(p) for p in src_paths)}\n->\n{fmt_lines([fmt_loc(dest_path)])}",
142
+ )
143
+
144
+
145
+ @kash_command
146
+ def trash(*paths: str) -> None:
147
+ """
148
+ Trash the items at the given paths. Uses OS-native trash or recycle bin on Mac, Windows, or Linux.
149
+ """
150
+
151
+ resolved_paths = assemble_path_args(*paths)
152
+ native_trash(*resolved_paths)
153
+ print_status(f"Deleted (check trash or recycling bin to recover):\n{fmt_lines(resolved_paths)}")
154
+
155
+
156
+ @kash_command
157
+ def strip_frontmatter(*paths: str) -> None:
158
+ """
159
+ Strip the frontmatter from the given files.
160
+ """
161
+ input_paths = assemble_path_args(*paths)
162
+
163
+ for path in input_paths:
164
+ log.message("Stripping frontmatter from: %s", fmt_loc(path))
165
+ fmf_strip_frontmatter(path)
166
+
167
+
168
+ @kash_command
169
+ def tail_file(path: str, follow: bool = False) -> None:
170
+ """
171
+ Tail a file. With colorization using bat if available, otherwise using less.
172
+ If `follow` is True, follows the file as it grows.
173
+ """
174
+
175
+ native_tail_file(path, follow=follow)
176
+
177
+
178
+ @kash_command
179
+ def follow_file(path: str) -> None:
180
+ """
181
+ Same as `tail_file --follow`.
182
+ """
183
+ native_tail_file(path, follow=True)
@@ -0,0 +1,62 @@
1
+ import urllib.parse
2
+
3
+ from kash.exec import kash_command
4
+ from kash.shell.utils.native_utils import native_open_url
5
+
6
+
7
+ @kash_command
8
+ def browser(url_or_query: str) -> None:
9
+ """
10
+ Open a URL or query in the browser.
11
+ """
12
+ native_open_url(url_or_query)
13
+
14
+
15
+ @kash_command
16
+ def web_search_google(query: str) -> None:
17
+ """
18
+ Open browser with a Google for a query.
19
+ """
20
+ native_open_url(f"https://www.google.com/search?q={urllib.parse.quote(query)}")
21
+
22
+
23
+ @kash_command
24
+ def web_search_duckduckgo(query: str) -> None:
25
+ """
26
+ Open browser with a DuckDuckGo search for a query.
27
+ """
28
+ native_open_url(f"https://duckduckgo.com/?q={urllib.parse.quote(query)}")
29
+
30
+
31
+ @kash_command
32
+ def web_search_bing(query: str) -> None:
33
+ """
34
+ Open browser with a Bing search for a query.
35
+ """
36
+ native_open_url(f"https://www.bing.com/search?q={urllib.parse.quote(query)}")
37
+
38
+
39
+ @kash_command
40
+ def web_search_youtube(query: str) -> None:
41
+ """
42
+ Open browser with a YouTube search for a query.
43
+ """
44
+ params = {"search_query": query}
45
+ url = f"https://www.youtube.com/results?{urllib.parse.urlencode(params)}"
46
+ native_open_url(url)
47
+
48
+
49
+ @kash_command
50
+ def web_search_amazon(query: str) -> None:
51
+ """
52
+ Open browser with an Amazon search for a query.
53
+ """
54
+ native_open_url(f"https://www.amazon.com/s?k={urllib.parse.quote(query)}")
55
+
56
+
57
+ @kash_command
58
+ def web_search_perplexity(query: str) -> None:
59
+ """
60
+ Open browser with a Perplexity search for a query.
61
+ """
62
+ native_open_url(f"https://www.perplexity.ai/search?q={urllib.parse.quote(query)}")
@@ -0,0 +1,214 @@
1
+ from collections.abc import Callable
2
+
3
+ import rich
4
+ from flowmark import Wrap
5
+ from strif import single_line
6
+
7
+ from kash.config.logger import get_logger
8
+ from kash.config.text_styles import STYLE_HINT, STYLE_KEY
9
+ from kash.errors import InvalidInput
10
+ from kash.exec import kash_command
11
+ from kash.help import tldr_help
12
+ from kash.help.function_param_info import annotate_param_info
13
+ from kash.help.recommended_commands import RECOMMENDED_TLDR_COMMANDS
14
+ from kash.model.params_model import Param
15
+ from kash.shell.output.kerm_codes import IframePopover, TextTooltip
16
+ from kash.shell.output.shell_output import PrintHooks, console_pager, cprint, format_name_and_value
17
+
18
+ log = get_logger(__name__)
19
+
20
+
21
+ @kash_command
22
+ def kerm_text_tooltip(text: str) -> None:
23
+ """
24
+ Show a tooltip in the Kerm terminal.
25
+ """
26
+ tooltip = TextTooltip(text=text)
27
+ print(tooltip.as_osc(), end="")
28
+
29
+
30
+ @kash_command
31
+ def kerm_iframe_popover(url: str) -> None:
32
+ """
33
+ Show an iframe popover in the Kerm terminal.
34
+ """
35
+ popover = IframePopover(url=url)
36
+ print(popover.as_osc(), end="")
37
+
38
+
39
+ @kash_command
40
+ def dump_tldr_snippets() -> None:
41
+ """
42
+ Dev task to run occasionally: Dump a few hundred TLDR snippet examples to a
43
+ standard script file for use in kash tab completion.
44
+ """
45
+ tldr_help.dump_all_tldr_snippets()
46
+
47
+
48
+ @kash_command
49
+ def debug_tldr() -> None:
50
+ """
51
+ Debug task to dump TLDR snippets for a few commands.
52
+ """
53
+ log.message("TLDR cache dir: %s", tldr_help._cache_dir)
54
+ log.message("TLDR cache timestamp file: %s", tldr_help._timestamp_file)
55
+ log.message("TLDR cache location: %s", tldr_help._cache_location("en"))
56
+ log.message("Should update TLDR cache: %s", tldr_help._should_update_cache())
57
+
58
+ with console_pager():
59
+ for command in RECOMMENDED_TLDR_COMMANDS:
60
+ cprint(f"{command}", style=STYLE_KEY)
61
+ cprint(f"description: {tldr_help.tldr_description(command)}")
62
+ cprint(f"help: {tldr_help.tldr_help(command)}")
63
+ cprint()
64
+ cprint("parsed snippets:")
65
+ for snippet in tldr_help.tldr_snippets(command):
66
+ cprint(f"# {snippet.comment}")
67
+ cprint(f"{snippet.command_line}")
68
+ cprint()
69
+ cprint()
70
+
71
+
72
+ @kash_command
73
+ def debug_completions() -> None:
74
+ """
75
+ Dump info on all xonsh completers.
76
+ """
77
+ from xonsh.built_ins import XSH
78
+
79
+ def name_of(completer: Callable) -> str:
80
+ module_name = completer.__module__ if hasattr(completer, "__module__") else "?"
81
+ if hasattr(completer, "__name__"):
82
+ return f"{module_name}.{completer.__name__}"
83
+ elif hasattr(completer, "__class__"):
84
+ return f"{module_name}.{completer.__class__.__name__}"
85
+ else:
86
+ return str(completer)
87
+
88
+ def to_str(completer: Callable) -> str:
89
+ return f"{name_of(completer)} (non_exclusive={getattr(completer, 'non_exclusive', False)})"
90
+
91
+ cprint(f"{len(XSH.completers)} completers registered:")
92
+ PrintHooks.spacer()
93
+
94
+ for completer in XSH.completers.values():
95
+ cprint(to_str(completer), text_wrap=Wrap.NONE, style=STYLE_KEY)
96
+ if completer.__doc__:
97
+ cprint(
98
+ f"{single_line(completer.__doc__)}", text_wrap=Wrap.WRAP_INDENT, style=STYLE_HINT
99
+ )
100
+
101
+
102
+ @kash_command
103
+ def debug_help() -> None:
104
+ """
105
+ Debug task to dump help for all commands and actions.
106
+ """
107
+ from kash.docs.all_docs import all_docs
108
+
109
+ cprint(f"all_docs: {all_docs}", style=STYLE_KEY)
110
+
111
+ cprint("\nhelp_topics:", style=STYLE_KEY)
112
+
113
+ for topic in all_docs.help_topics.__dict__.keys():
114
+ cprint(f"{topic}", text_wrap=Wrap.NONE)
115
+
116
+ cprint("\nfaqs:")
117
+ for faq in all_docs.faqs:
118
+ cprint(f"{faq}", text_wrap=Wrap.NONE)
119
+
120
+ cprint("\ncustom_command_infos:", style=STYLE_KEY)
121
+ for cmd in all_docs.custom_command_infos:
122
+ cprint(f"{cmd}", text_wrap=Wrap.NONE)
123
+
124
+ cprint("\nstd_command_infos:", style=STYLE_KEY)
125
+ for cmd in all_docs.std_command_infos:
126
+ cprint(f"{cmd}", text_wrap=Wrap.NONE)
127
+
128
+ cprint("\naction_infos:", style=STYLE_KEY)
129
+ for action in all_docs.action_infos:
130
+ cprint(f"{action}", text_wrap=Wrap.NONE)
131
+
132
+ # cprint("\nrecipe_snippets:", style=STYLE_KEY)
133
+ # for snippet in all_docs.recipe_snippets:
134
+ # cprint(f"{snippet}", text_wrap=Wrap.NONE)
135
+
136
+
137
+ @kash_command
138
+ def debug_command(command_or_action: str) -> None:
139
+ """
140
+ Debug task to dump info on a command or action.
141
+ """
142
+ from kash.exec.action_registry import get_all_actions_defaults
143
+ from kash.exec.command_registry import get_all_commands
144
+
145
+ def dump_params(param_info: list[Param]) -> None:
146
+ for param in param_info:
147
+ cprint(format_name_and_value(param.display, str(param)))
148
+ cprint()
149
+
150
+ try:
151
+ command = get_all_commands()[command_or_action]
152
+ param_info = annotate_param_info(command)
153
+ cprint(format_name_and_value("Command", f"`{command_or_action}`"))
154
+ cprint(format_name_and_value("Docstring", command.__doc__ or ""))
155
+ cprint()
156
+ dump_params(param_info)
157
+ cprint()
158
+ rich.inspect(command)
159
+ except KeyError:
160
+ try:
161
+ action = get_all_actions_defaults()[command_or_action]
162
+ cprint(format_name_and_value("Action", f"`{command_or_action}`"))
163
+ cprint(format_name_and_value("Description", action.description or ""))
164
+ cprint()
165
+ dump_params(list(action.params))
166
+ cprint()
167
+ rich.inspect(action.tool_json_schema(), title="JSON Schema", docs=False)
168
+ cprint()
169
+ rich.inspect(action, title="Action Instance", docs=False)
170
+ except KeyError:
171
+ raise InvalidInput(f"No info found for {command_or_action}")
172
+
173
+
174
+ @kash_command
175
+ def reload_system() -> None:
176
+ """
177
+ Experimental! Reload the kash package and all its submodules. Also restarts
178
+ the local the local server. Not perfect! But sometimes useful for development.
179
+ """
180
+ import kash
181
+ from kash.local_server.local_server import restart_local_server
182
+ from kash.utils.common.import_utils import recursive_reload
183
+
184
+ module = kash
185
+ exclude = ["kash.xontrib.kash_extension"] # Don't reload the kash initialization.
186
+
187
+ def filter_func(name: str) -> bool:
188
+ if exclude:
189
+ for excluded_module in exclude:
190
+ if name == excluded_module or name.startswith(excluded_module + "."):
191
+ log.info("Excluding reloading module: %s", name)
192
+ return False
193
+ return True
194
+
195
+ package_names = recursive_reload(module, filter_func=filter_func)
196
+ log.info("Reloaded modules: %s", ", ".join(package_names))
197
+ log.message("Reloaded %s modules from %s.", len(package_names), module.__name__)
198
+
199
+ restart_local_server()
200
+
201
+ # TODO Re-register commands and actions.
202
+
203
+
204
+ @kash_command
205
+ def reload_commands_and_actions() -> None:
206
+ """
207
+ Reload all commands and actions. This can be needed to register newly imported
208
+ Python files that define commands or actions with the shell.
209
+ """
210
+ from kash.xonsh_custom.customize_xonsh import reload_shell_commands_and_actions
211
+ from kash.xonsh_custom.shell_load_commands import log_command_action_info
212
+
213
+ reload_shell_commands_and_actions()
214
+ log_command_action_info()