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
kash/__init__.py ADDED
@@ -0,0 +1,2 @@
1
+ # This file is part of the namespace package
2
+ __path__ = __import__("pkgutil").extend_path(__path__, __name__)
kash/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from kash.shell_main import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -0,0 +1,55 @@
1
+ from dataclasses import dataclass
2
+ from pathlib import Path
3
+
4
+ from kash.config.logger import get_logger
5
+ from kash.config.settings import APP_NAME
6
+ from kash.exec import import_action_subdirs
7
+ from kash.utils.common.atomic_var import AtomicVar
8
+ from kash.utils.common.import_utils import import_namespace_modules
9
+
10
+ log = get_logger(__name__)
11
+
12
+ # This hook can be used for auto-registering actions from any module.
13
+ import_action_subdirs(["core", "meta"], __package__, Path(__file__).parent)
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class Kit:
18
+ name: str
19
+ full_module_name: str
20
+ path: Path | None
21
+
22
+
23
+ _kits: AtomicVar[dict[str, Kit]] = AtomicVar(initial_value={})
24
+
25
+
26
+ def get_loaded_kits() -> dict[str, Kit]:
27
+ """
28
+ Get all kits (modules within `kash.kits`) that have been loaded.
29
+ """
30
+ return _kits.copy()
31
+
32
+
33
+ def load_kits() -> dict[str, Kit]:
34
+ """
35
+ Import all kits (modules within `kash.kits`) by inspecting the namespace.
36
+ """
37
+ kits_namespace = f"{APP_NAME}.kits"
38
+ new_kits = {}
39
+ try:
40
+ imported = import_namespace_modules(kits_namespace)
41
+ for name, module in imported.items():
42
+ new_kits[name] = Kit(
43
+ name,
44
+ module.__name__,
45
+ Path(module.__file__) if module.__file__ else None,
46
+ )
47
+ except ImportError:
48
+ log.info("No kits found in namespace `%s`", kits_namespace)
49
+
50
+ _kits.update(lambda kits: {**kits, **new_kits})
51
+
52
+ return new_kits
53
+
54
+
55
+ load_kits()
@@ -0,0 +1,45 @@
1
+ from kash.exec import kash_action
2
+ from kash.exec.preconditions import is_chat
3
+ from kash.form_input.prompt_input import prompt_simple_string
4
+ from kash.help.assistant import assistant_chat_history, shell_context_assistance
5
+ from kash.help.assistant_output import print_assistant_heading
6
+ from kash.llm_utils.language_models import LLM, LLMName
7
+ from kash.model import NO_ARGS, ActionInput, ActionResult, common_params
8
+ from kash.shell.output.shell_output import PrintHooks, Wrap, print_response
9
+
10
+
11
+ @kash_action(
12
+ expected_args=NO_ARGS,
13
+ interactive_input=True,
14
+ cacheable=False,
15
+ precondition=is_chat,
16
+ params=common_params("model"),
17
+ )
18
+ def assistant_chat(input: ActionInput, model: LLMName = LLM.default_careful) -> ActionResult:
19
+ """
20
+ Chat with the kash assistant. This is just the same as typing on the command line,
21
+ but with a chat session.
22
+ """
23
+ chat_history = assistant_chat_history(
24
+ include_system_message=True, is_structured=False, skip_api_docs=False
25
+ )
26
+ PrintHooks.spacer()
27
+ print_assistant_heading(model)
28
+ print_response(
29
+ f"History of {chat_history.size_summary()}.\nPress enter (or type `exit`) to end chat.",
30
+ text_wrap=Wrap.NONE,
31
+ )
32
+
33
+ while True:
34
+ try:
35
+ user_message = prompt_simple_string(f"assistant/{model.litellm_name}")
36
+ except KeyboardInterrupt:
37
+ break
38
+
39
+ user_message = user_message.strip()
40
+ if not user_message or user_message.lower() == "exit" or user_message.lower() == "quit":
41
+ break
42
+
43
+ shell_context_assistance(user_message, silent=True, model=model)
44
+
45
+ return ActionResult([])
@@ -0,0 +1,90 @@
1
+ from kash.config.logger import get_logger
2
+ from kash.exec import kash_action
3
+ from kash.exec.preconditions import is_chat
4
+ from kash.form_input.prompt_input import prompt_simple_string
5
+ from kash.llm_utils.chat_format import ChatHistory, ChatMessage, ChatRole
6
+ from kash.llm_utils.language_models import LLMName
7
+ from kash.llm_utils.llm_completion import llm_completion
8
+ from kash.model import (
9
+ LLM,
10
+ ONE_OR_NO_ARGS,
11
+ ActionInput,
12
+ ActionResult,
13
+ Format,
14
+ Item,
15
+ ItemType,
16
+ ShellResult,
17
+ common_params,
18
+ )
19
+ from kash.shell.output.shell_output import (
20
+ PadStyle,
21
+ Wrap,
22
+ print_markdown,
23
+ print_response,
24
+ print_style,
25
+ )
26
+
27
+ log = get_logger(__name__)
28
+
29
+
30
+ @kash_action(
31
+ expected_args=ONE_OR_NO_ARGS,
32
+ precondition=is_chat,
33
+ uses_selection=False,
34
+ interactive_input=True,
35
+ cacheable=False,
36
+ params=common_params("model"),
37
+ )
38
+ def chat(input: ActionInput, model: LLMName = LLM.default_careful) -> ActionResult:
39
+ """
40
+ Chat with an LLM. By default, starts a new chat session. If provided a chat
41
+ history item, will continue an existing chat.
42
+ """
43
+ if input.items:
44
+ chat_history = input.items[0].as_chat_history()
45
+ size_desc = f"{chat_history.size_summary()} in chat history"
46
+ else:
47
+ chat_history = ChatHistory()
48
+ size_desc = "empty chat history"
49
+
50
+ print_response(
51
+ f"Beginning chat with {size_desc}. Press enter (or type `exit`) to end chat.",
52
+ text_wrap=Wrap.WRAP_FULL,
53
+ )
54
+
55
+ while True:
56
+ try:
57
+ user_message = prompt_simple_string(model.litellm_name)
58
+ except KeyboardInterrupt:
59
+ break
60
+
61
+ user_message = user_message.strip()
62
+ if not user_message or user_message.lower() == "exit" or user_message.lower() == "quit":
63
+ break
64
+
65
+ chat_history.append(ChatMessage(ChatRole.user, user_message))
66
+
67
+ llm_response = llm_completion(
68
+ model,
69
+ messages=chat_history.as_chat_completion(),
70
+ )
71
+
72
+ with print_style(PadStyle.PAD):
73
+ print_markdown(llm_response.content)
74
+
75
+ # XXX: Why does the response have trailing whitespace on lines? Makes the YAML ugly.
76
+ stripped_response = "\n".join(line.rstrip() for line in llm_response.content.splitlines())
77
+
78
+ chat_history.append(ChatMessage(ChatRole.assistant, stripped_response))
79
+
80
+ if chat_history.messages:
81
+ item = Item(
82
+ ItemType.chat,
83
+ body=chat_history.to_yaml(),
84
+ format=Format.yaml,
85
+ )
86
+
87
+ return ActionResult([item])
88
+ else:
89
+ log.warning("Empty chat! Not saving anything.")
90
+ return ActionResult([], shell_result=ShellResult(show_selection=False))
@@ -0,0 +1,92 @@
1
+ import re
2
+ from pathlib import Path
3
+
4
+ from kash.config.logger import get_logger
5
+ from kash.errors import InvalidInput
6
+ from kash.exec import kash_action
7
+ from kash.exec.preconditions import is_markdown
8
+ from kash.model import ONE_OR_MORE_ARGS, ActionInput, ActionResult, ItemType, Param
9
+ from kash.utils.common.type_utils import not_none
10
+
11
+ log = get_logger(__name__)
12
+
13
+
14
+ @kash_action(
15
+ expected_args=ONE_OR_MORE_ARGS,
16
+ precondition=is_markdown,
17
+ params=(
18
+ Param(
19
+ "md_template",
20
+ "Path for the markdown template to use for formatting. This is plain Markdown "
21
+ "with curly-brace {name} variables for values to insert.",
22
+ type=Path,
23
+ default_value=Path("template.md"),
24
+ ),
25
+ ),
26
+ )
27
+ def format_markdown_template(
28
+ input: ActionInput, md_template: Path = Path("template.md")
29
+ ) -> ActionResult:
30
+ """
31
+ Format the given text documents into a single document using the given
32
+ template. This is a simple way to generate combined docs from individual
33
+ pieces or sections, e.g. assembling a readme from a few docs.
34
+
35
+ As a convenience, the variables in the template need only be unique
36
+ matching prefixes of the filename of each item, e.g. {body} for a file
37
+ named `body.md` or `body_new_01.md`.
38
+ """
39
+ template_path = md_template if md_template else Path("template.md")
40
+ items = input.items
41
+
42
+ with open(template_path) as f:
43
+ template = f.read()
44
+
45
+ # Identify variables in the template.
46
+ variables: list[str] = re.findall(r"\{(\w+)\}", template)
47
+
48
+ if len(variables) != len(items):
49
+ raise InvalidInput(
50
+ f"Number of inputs ({len(items)} items) does not match the"
51
+ f" number of variables ({len(variables)}) in the template"
52
+ )
53
+
54
+ # Create a dictionary to map variable names to item bodies.
55
+ item_map: dict[str, str] = {}
56
+ unmatched_items = set(range(len(items)))
57
+
58
+ for var in variables:
59
+ matches = []
60
+ for i, item in enumerate(items):
61
+ store_path = not_none(item.store_path)
62
+ filename = Path(store_path).stem
63
+
64
+ if not item.body:
65
+ raise InvalidInput(f"Item has no body: {store_path}")
66
+
67
+ if filename.startswith(var):
68
+ matches.append((i, item))
69
+
70
+ if len(matches) == 0:
71
+ raise InvalidInput(f"No matching item found for variable: `{var}`")
72
+ elif len(matches) > 1:
73
+ raise InvalidInput(
74
+ f"Multiple items match variable `{var}`: {[items[i].store_path for i, _ in matches]}"
75
+ )
76
+
77
+ index, matched_item = matches[0]
78
+ item_map[var] = matched_item.body
79
+ unmatched_items.remove(index)
80
+
81
+ if unmatched_items:
82
+ raise InvalidInput(f"Unmatched items: {[items[i].store_path for i in unmatched_items]}")
83
+
84
+ # Format the body using the mapped items.
85
+ body = template.format(**item_map)
86
+
87
+ result_item = items[0].derived_copy(
88
+ type=ItemType.doc,
89
+ body=body,
90
+ )
91
+
92
+ return ActionResult([result_item])
@@ -0,0 +1,29 @@
1
+ from kash.config.logger import get_logger
2
+ from kash.exec import kash_action
3
+ from kash.exec.preconditions import has_html_body, is_url_item
4
+ from kash.model import Format, Item, ItemType
5
+ from kash.web_content.file_cache_utils import get_url_html
6
+ from kash.web_content.web_extract_readabilipy import extract_text_readabilipy
7
+
8
+ log = get_logger(__name__)
9
+
10
+
11
+ @kash_action(
12
+ precondition=is_url_item | has_html_body,
13
+ mcp_tool=True,
14
+ )
15
+ def markdownify(item: Item) -> Item:
16
+ """
17
+ Converts a URL or raw HTML item to Markdown, fetching with the content
18
+ cache if needed. Also uses readability to clean up the HTML.
19
+ """
20
+ from markdownify import markdownify as markdownify_convert
21
+
22
+ url, html_content = get_url_html(item)
23
+ page_data = extract_text_readabilipy(url, html_content)
24
+ markdown_content = markdownify_convert(page_data.clean_html)
25
+
26
+ output_item = item.derived_copy(
27
+ type=ItemType.doc, format=Format.markdown, body=markdown_content
28
+ )
29
+ return output_item
@@ -0,0 +1,27 @@
1
+ from kash.config.logger import get_logger
2
+ from kash.exec import kash_action
3
+ from kash.exec.preconditions import has_html_body, is_url_item
4
+ from kash.model import Format, Item, ItemType
5
+ from kash.web_content.file_cache_utils import get_url_html
6
+ from kash.web_content.web_extract_readabilipy import extract_text_readabilipy
7
+
8
+ log = get_logger(__name__)
9
+
10
+
11
+ @kash_action(
12
+ precondition=is_url_item | has_html_body,
13
+ mcp_tool=True,
14
+ )
15
+ def readability(item: Item) -> Item:
16
+ """
17
+ Extracts clean HTML from a raw HTML item.
18
+ See `markdownify` to also convert to Markdown.
19
+ """
20
+ url, html_content = get_url_html(item)
21
+ page_data = extract_text_readabilipy(url, html_content)
22
+
23
+ output_item = item.derived_copy(
24
+ type=ItemType.doc, format=Format.html, body=page_data.clean_html
25
+ )
26
+
27
+ return output_item
@@ -0,0 +1,28 @@
1
+ from kash.actions.core.webpage_config import webpage_config
2
+ from kash.actions.core.webpage_generate import webpage_generate
3
+ from kash.commands.base.show_command import show
4
+ from kash.config.logger import get_logger
5
+ from kash.exec import kash_action
6
+ from kash.exec.preconditions import has_text_body, is_html
7
+ from kash.exec_model.commands_model import Command
8
+ from kash.exec_model.shell_model import ShellResult
9
+ from kash.model import ActionInput, ActionResult
10
+
11
+ log = get_logger(__name__)
12
+
13
+
14
+ @kash_action(
15
+ precondition=is_html | has_text_body,
16
+ )
17
+ def show_webpage(input: ActionInput) -> ActionResult:
18
+ """
19
+ Show text, Markdown, or HTML as a nicely formatted webpage.
20
+ """
21
+ config_result = webpage_config(input)
22
+
23
+ log.message("Configured web page: %s", config_result)
24
+ result = webpage_generate(ActionInput(items=config_result.items))
25
+
26
+ # Automatically show the result.
27
+ result.shell_result = ShellResult(display_command=Command.assemble(show))
28
+ return result
@@ -0,0 +1,28 @@
1
+ from kash.config.logger import get_logger
2
+ from kash.errors import InvalidInput
3
+ from kash.exec import kash_action
4
+ from kash.exec.preconditions import has_html_body, has_text_body
5
+ from kash.model import Format, Item, ItemType
6
+ from kash.utils.common.format_utils import html_to_plaintext
7
+
8
+ log = get_logger(__name__)
9
+
10
+
11
+ @kash_action(precondition=has_html_body | has_text_body)
12
+ def strip_html(item: Item) -> Item:
13
+ """
14
+ Strip HTML tags from HTML or Markdown. This is a very dumb filter, simply searching
15
+ for and removing tags by regex. This works well for clean and simple HTML; use
16
+ `markdownify` for complex HTML.
17
+ """
18
+ if not item.body:
19
+ raise InvalidInput("Item must have a body")
20
+
21
+ clean_body = html_to_plaintext(item.body)
22
+ output_item = item.derived_copy(
23
+ type=ItemType.doc,
24
+ format=Format.markdown,
25
+ body=clean_body,
26
+ )
27
+
28
+ return output_item
@@ -0,0 +1,53 @@
1
+ from kash.exec import kash_action, llm_transform_item
2
+ from kash.llm_utils.language_models import LLMName
3
+ from kash.model import LLM, Item, LLMOptions, Message, MessageTemplate, common_params
4
+
5
+ llm_options = LLMOptions(
6
+ system_message=Message(
7
+ """
8
+ You are a careful and precise editor.
9
+ You give exactly the results requested without additional commentary.
10
+ """
11
+ ),
12
+ body_template=MessageTemplate(
13
+ """
14
+ Summarize the following text as a list of concise bullet points:
15
+
16
+ - Each point should be one sentence long.
17
+
18
+ - Format your response as a list of bullet points in Markdown format.
19
+
20
+ - Do NOT use nested bullet points. Give a single list, not a list of lists.
21
+
22
+ - Include all key numbers or facts, without omitting any claims or important details.
23
+
24
+ - Use simple and precise language.
25
+
26
+ - Simply state the facts or claims without referencing the text or the author. For example, if the
27
+ text is about cheese being nutritious, you can say "Cheese is nutritious." But do NOT
28
+ say "The author says cheese is nutritious" or "According to the text, cheese is nutritious."
29
+
30
+ - It is very important you do not add any details that are not directly stated in the original text.
31
+ Do not change any numbers or alter its meaning in any way.
32
+
33
+ - Do NOT give any additional response at the beginning, such as "Here are the concise bullet points".
34
+ Simply give the summary.
35
+
36
+ - If the input is very short or so unclear you can't summarize it, simply output "(No results)".
37
+
38
+ Input text:
39
+
40
+ {body}
41
+
42
+ Bullet points:
43
+ """
44
+ ),
45
+ )
46
+
47
+
48
+ @kash_action(llm_options=llm_options, params=common_params("model"), mcp_tool=True)
49
+ def summarize_as_bullets(item: Item, model: LLMName = LLM.default_basic) -> Item:
50
+ """
51
+ Summarize text as bullet points.
52
+ """
53
+ return llm_transform_item(item, model=model)
@@ -0,0 +1,21 @@
1
+ from kash.config.logger import get_logger
2
+ from kash.errors import InvalidInput
3
+ from kash.exec import kash_action
4
+ from kash.model import ActionInput, ActionResult
5
+ from kash.web_gen import tabbed_webpage
6
+
7
+ log = get_logger(__name__)
8
+
9
+
10
+ @kash_action()
11
+ def webpage_config(input: ActionInput) -> ActionResult:
12
+ """
13
+ Set up a web page config with optional tabs for each page of content. Uses first item as the page title.
14
+ """
15
+ for item in input.items:
16
+ if not item.body:
17
+ raise InvalidInput(f"Item must have a body: {item}")
18
+
19
+ config_item = tabbed_webpage.webpage_config(input.items)
20
+
21
+ return ActionResult([config_item])
@@ -0,0 +1,29 @@
1
+ from kash.config.logger import get_logger
2
+ from kash.exec import kash_action
3
+ from kash.exec.preconditions import is_config
4
+ from kash.model import ONE_ARG, ActionInput, ActionResult, FileExt, Format, Item, ItemType
5
+ from kash.web_gen import tabbed_webpage
6
+
7
+ log = get_logger(__name__)
8
+
9
+
10
+ @kash_action(
11
+ expected_args=ONE_ARG,
12
+ precondition=is_config,
13
+ )
14
+ def webpage_generate(input: ActionInput) -> ActionResult:
15
+ """
16
+ Generate a web page from a configured web page item.
17
+ """
18
+ config_item = input.items[0]
19
+ html = tabbed_webpage.webpage_generate(config_item)
20
+
21
+ webpage_item = Item(
22
+ title=config_item.title,
23
+ type=ItemType.export,
24
+ format=Format.html,
25
+ file_ext=FileExt.html,
26
+ body=html,
27
+ )
28
+
29
+ return ActionResult([webpage_item])
@@ -0,0 +1,39 @@
1
+ from kash.exec import kash_action
2
+ from kash.form_input.prompt_input import prompt_simple_string
3
+ from kash.llm_utils.chat_format import ChatHistory, ChatMessage, ChatRole
4
+ from kash.model import NO_ARGS, ActionInput, ActionResult, Format, Item, ItemType
5
+
6
+
7
+ @kash_action(
8
+ expected_args=NO_ARGS,
9
+ interactive_input=True,
10
+ cacheable=False,
11
+ )
12
+ def write_instructions(input: ActionInput) -> ActionResult:
13
+ """
14
+ Write a chat item with system and user instructions.
15
+ """
16
+ chat_history = ChatHistory()
17
+
18
+ system_instructions = prompt_simple_string(
19
+ "Enter the system instructions (or enter for none): "
20
+ )
21
+ system_instructions = system_instructions.strip()
22
+ if system_instructions:
23
+ chat_history.append(ChatMessage(ChatRole.system, system_instructions))
24
+
25
+ user_instructions = prompt_simple_string("Enter the user instructions: ")
26
+ user_instructions = user_instructions.strip()
27
+ if user_instructions:
28
+ chat_history.append(ChatMessage(ChatRole.user, user_instructions))
29
+
30
+ if chat_history.messages:
31
+ item = Item(
32
+ type=ItemType.chat,
33
+ body=chat_history.to_yaml(),
34
+ format=Format.yaml,
35
+ )
36
+
37
+ return ActionResult([item])
38
+ else:
39
+ return ActionResult([])