kash-shell 0.3.8__py3-none-any.whl → 0.3.10__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 (154) hide show
  1. kash/actions/__init__.py +4 -4
  2. kash/actions/core/markdownify.py +5 -2
  3. kash/actions/core/readability.py +5 -2
  4. kash/actions/core/render_as_html.py +18 -0
  5. kash/actions/core/webpage_config.py +12 -4
  6. kash/commands/__init__.py +8 -20
  7. kash/commands/base/basic_file_commands.py +15 -0
  8. kash/commands/base/debug_commands.py +15 -2
  9. kash/commands/base/general_commands.py +27 -18
  10. kash/commands/base/logs_commands.py +1 -4
  11. kash/commands/base/model_commands.py +8 -8
  12. kash/commands/base/search_command.py +3 -2
  13. kash/commands/base/show_command.py +5 -3
  14. kash/commands/extras/parse_uv_lock.py +186 -0
  15. kash/commands/help/doc_commands.py +2 -31
  16. kash/commands/help/welcome.py +33 -0
  17. kash/commands/workspace/selection_commands.py +11 -6
  18. kash/commands/workspace/workspace_commands.py +19 -16
  19. kash/config/colors.py +2 -0
  20. kash/config/env_settings.py +72 -0
  21. kash/config/init.py +2 -2
  22. kash/config/logger.py +61 -59
  23. kash/config/logger_basic.py +12 -5
  24. kash/config/server_config.py +6 -6
  25. kash/config/settings.py +117 -67
  26. kash/config/setup.py +35 -9
  27. kash/config/suppress_warnings.py +30 -12
  28. kash/config/text_styles.py +3 -13
  29. kash/docs/load_api_docs.py +2 -1
  30. kash/docs/markdown/topics/a2_installation.md +7 -3
  31. kash/docs/markdown/topics/a3_getting_started.md +3 -2
  32. kash/docs/markdown/warning.md +3 -8
  33. kash/docs/markdown/welcome.md +4 -0
  34. kash/docs_base/load_recipe_snippets.py +1 -1
  35. kash/docs_base/recipes/{general_system_commands.ksh → general_system_commands.sh} +1 -1
  36. kash/{concepts → embeddings}/cosine.py +2 -1
  37. kash/embeddings/text_similarity.py +57 -0
  38. kash/exec/__init__.py +20 -3
  39. kash/exec/action_decorators.py +18 -4
  40. kash/exec/action_exec.py +41 -23
  41. kash/exec/action_registry.py +13 -48
  42. kash/exec/command_registry.py +2 -1
  43. kash/exec/fetch_url_metadata.py +4 -6
  44. kash/exec/importing.py +56 -0
  45. kash/exec/llm_transforms.py +6 -6
  46. kash/exec/precondition_registry.py +2 -1
  47. kash/exec/preconditions.py +16 -1
  48. kash/exec/shell_callable_action.py +33 -19
  49. kash/file_storage/file_store.py +23 -14
  50. kash/file_storage/item_file_format.py +13 -3
  51. kash/file_storage/metadata_dirs.py +11 -2
  52. kash/help/assistant.py +2 -2
  53. kash/help/assistant_instructions.py +2 -1
  54. kash/help/help_embeddings.py +2 -2
  55. kash/help/help_printing.py +14 -10
  56. kash/help/tldr_help.py +5 -3
  57. kash/llm_utils/clean_headings.py +1 -1
  58. kash/llm_utils/llm_api_keys.py +4 -4
  59. kash/llm_utils/llm_completion.py +2 -2
  60. kash/llm_utils/llm_features.py +68 -0
  61. kash/llm_utils/llm_messages.py +1 -2
  62. kash/llm_utils/llm_names.py +1 -1
  63. kash/llm_utils/llms.py +17 -12
  64. kash/local_server/__init__.py +5 -2
  65. kash/local_server/local_server.py +56 -46
  66. kash/local_server/local_server_commands.py +15 -15
  67. kash/local_server/local_server_routes.py +2 -2
  68. kash/local_server/local_url_formatters.py +1 -1
  69. kash/mcp/__init__.py +5 -2
  70. kash/mcp/mcp_cli.py +54 -17
  71. kash/mcp/mcp_server_commands.py +5 -6
  72. kash/mcp/mcp_server_routes.py +14 -11
  73. kash/mcp/mcp_server_sse.py +61 -34
  74. kash/mcp/mcp_server_stdio.py +0 -8
  75. kash/media_base/audio_processing.py +81 -7
  76. kash/media_base/media_cache.py +18 -18
  77. kash/media_base/media_services.py +1 -1
  78. kash/media_base/media_tools.py +6 -6
  79. kash/media_base/services/local_file_media.py +2 -2
  80. kash/media_base/{speech_transcription.py → transcription_deepgram.py} +25 -109
  81. kash/media_base/transcription_format.py +73 -0
  82. kash/media_base/transcription_whisper.py +38 -0
  83. kash/model/__init__.py +73 -5
  84. kash/model/actions_model.py +38 -4
  85. kash/model/concept_model.py +30 -0
  86. kash/model/items_model.py +56 -13
  87. kash/model/params_model.py +24 -0
  88. kash/shell/completions/completion_scoring.py +37 -5
  89. kash/shell/output/kerm_codes.py +1 -2
  90. kash/shell/output/shell_formatting.py +14 -4
  91. kash/shell/shell_main.py +2 -2
  92. kash/shell/utils/exception_printing.py +6 -0
  93. kash/shell/utils/native_utils.py +26 -20
  94. kash/text_handling/custom_sliding_transforms.py +12 -4
  95. kash/text_handling/doc_normalization.py +6 -2
  96. kash/text_handling/markdown_render.py +117 -0
  97. kash/text_handling/markdown_utils.py +204 -0
  98. kash/utils/common/import_utils.py +12 -3
  99. kash/utils/common/type_utils.py +0 -29
  100. kash/utils/common/url.py +80 -28
  101. kash/utils/errors.py +6 -0
  102. kash/utils/file_utils/{dir_size.py → dir_info.py} +25 -4
  103. kash/utils/file_utils/file_ext.py +2 -3
  104. kash/utils/file_utils/file_formats.py +28 -2
  105. kash/utils/file_utils/file_formats_model.py +50 -19
  106. kash/utils/file_utils/filename_parsing.py +10 -4
  107. kash/web_content/dir_store.py +1 -2
  108. kash/web_content/file_cache_utils.py +37 -10
  109. kash/web_content/file_processing.py +68 -0
  110. kash/web_content/local_file_cache.py +12 -9
  111. kash/web_content/web_extract.py +8 -3
  112. kash/web_content/web_fetch.py +12 -4
  113. kash/web_gen/tabbed_webpage.py +5 -2
  114. kash/web_gen/templates/base_styles.css.jinja +120 -14
  115. kash/web_gen/templates/base_webpage.html.jinja +60 -13
  116. kash/web_gen/templates/content_styles.css.jinja +4 -2
  117. kash/web_gen/templates/item_view.html.jinja +2 -2
  118. kash/web_gen/templates/tabbed_webpage.html.jinja +1 -2
  119. kash/workspaces/__init__.py +15 -2
  120. kash/workspaces/selections.py +18 -3
  121. kash/workspaces/source_items.py +4 -2
  122. kash/workspaces/workspace_output.py +11 -4
  123. kash/workspaces/workspaces.py +5 -11
  124. kash/xonsh_custom/command_nl_utils.py +40 -19
  125. kash/xonsh_custom/custom_shell.py +44 -12
  126. kash/xonsh_custom/customize_prompt.py +39 -21
  127. kash/xonsh_custom/load_into_xonsh.py +26 -27
  128. kash/xonsh_custom/shell_load_commands.py +2 -2
  129. kash/xonsh_custom/xonsh_completers.py +2 -249
  130. kash/xonsh_custom/xonsh_keybindings.py +282 -0
  131. kash/xonsh_custom/xonsh_modern_tools.py +3 -3
  132. kash/xontrib/kash_extension.py +5 -6
  133. {kash_shell-0.3.8.dist-info → kash_shell-0.3.10.dist-info}/METADATA +26 -12
  134. {kash_shell-0.3.8.dist-info → kash_shell-0.3.10.dist-info}/RECORD +140 -140
  135. {kash_shell-0.3.8.dist-info → kash_shell-0.3.10.dist-info}/entry_points.txt +1 -1
  136. kash/concepts/concept_formats.py +0 -23
  137. kash/concepts/text_similarity.py +0 -112
  138. kash/shell/clideps/api_keys.py +0 -99
  139. kash/shell/clideps/dotenv_setup.py +0 -114
  140. kash/shell/clideps/dotenv_utils.py +0 -89
  141. kash/shell/clideps/pkg_deps.py +0 -232
  142. kash/shell/clideps/platforms.py +0 -11
  143. kash/shell/clideps/terminal_features.py +0 -56
  144. kash/shell/utils/osc_utils.py +0 -95
  145. kash/shell/utils/terminal_images.py +0 -133
  146. kash/text_handling/markdown_util.py +0 -167
  147. kash/utils/common/atomic_var.py +0 -158
  148. kash/utils/common/string_replace.py +0 -93
  149. kash/utils/common/string_template.py +0 -101
  150. /kash/docs_base/recipes/{python_dev_commands.ksh → python_dev_commands.sh} +0 -0
  151. /kash/docs_base/recipes/{tldr_standard_commands.ksh → tldr_standard_commands.sh} +0 -0
  152. /kash/{concepts → embeddings}/embeddings.py +0 -0
  153. {kash_shell-0.3.8.dist-info → kash_shell-0.3.10.dist-info}/WHEEL +0 -0
  154. {kash_shell-0.3.8.dist-info → kash_shell-0.3.10.dist-info}/licenses/LICENSE +0 -0
kash/actions/__init__.py CHANGED
@@ -1,16 +1,16 @@
1
1
  from dataclasses import dataclass
2
2
  from pathlib import Path
3
3
 
4
+ from strif import AtomicVar
5
+
4
6
  from kash.config.logger import get_logger
5
7
  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.exec import import_and_register
8
9
  from kash.utils.common.import_utils import import_namespace_modules
9
10
 
10
11
  log = get_logger(__name__)
11
12
 
12
- # This hook can be used for auto-registering actions from any module.
13
- import_action_subdirs(["core", "meta"], __package__, Path(__file__).parent)
13
+ import_and_register(__package__, Path(__file__).parent, ["core", "meta"])
14
14
 
15
15
 
16
16
  @dataclass(frozen=True)
@@ -2,6 +2,7 @@ from kash.config.logger import get_logger
2
2
  from kash.exec import kash_action
3
3
  from kash.exec.preconditions import has_html_body, is_url_item
4
4
  from kash.model import Format, Item, ItemType
5
+ from kash.model.params_model import common_params
5
6
  from kash.web_content.file_cache_utils import get_url_html
6
7
  from kash.web_content.web_extract_readabilipy import extract_text_readabilipy
7
8
 
@@ -10,16 +11,18 @@ log = get_logger(__name__)
10
11
 
11
12
  @kash_action(
12
13
  precondition=is_url_item | has_html_body,
14
+ params=common_params("refetch"),
13
15
  mcp_tool=True,
14
16
  )
15
- def markdownify(item: Item) -> Item:
17
+ def markdownify(item: Item, refetch: bool = False) -> Item:
16
18
  """
17
19
  Converts a URL or raw HTML item to Markdown, fetching with the content
18
20
  cache if needed. Also uses readability to clean up the HTML.
19
21
  """
20
22
  from markdownify import markdownify as markdownify_convert
21
23
 
22
- url, html_content = get_url_html(item)
24
+ expiration_sec = 0 if refetch else None
25
+ url, html_content = get_url_html(item, expiration_sec=expiration_sec)
23
26
  page_data = extract_text_readabilipy(url, html_content)
24
27
  markdown_content = markdownify_convert(page_data.clean_html)
25
28
 
@@ -2,6 +2,7 @@ from kash.config.logger import get_logger
2
2
  from kash.exec import kash_action
3
3
  from kash.exec.preconditions import has_html_body, is_url_item
4
4
  from kash.model import Format, Item, ItemType
5
+ from kash.model.params_model import common_params
5
6
  from kash.web_content.file_cache_utils import get_url_html
6
7
  from kash.web_content.web_extract_readabilipy import extract_text_readabilipy
7
8
 
@@ -10,14 +11,16 @@ log = get_logger(__name__)
10
11
 
11
12
  @kash_action(
12
13
  precondition=is_url_item | has_html_body,
14
+ params=common_params("refetch"),
13
15
  mcp_tool=True,
14
16
  )
15
- def readability(item: Item) -> Item:
17
+ def readability(item: Item, refetch: bool = False) -> Item:
16
18
  """
17
19
  Extracts clean HTML from a raw HTML item.
18
20
  See `markdownify` to also convert to Markdown.
19
21
  """
20
- url, html_content = get_url_html(item)
22
+ expiration_sec = 0 if refetch else None
23
+ url, html_content = get_url_html(item, expiration_sec=expiration_sec)
21
24
  page_data = extract_text_readabilipy(url, html_content)
22
25
 
23
26
  output_item = item.derived_copy(
@@ -0,0 +1,18 @@
1
+ from kash.actions.core.webpage_config import webpage_config
2
+ from kash.actions.core.webpage_generate import webpage_generate
3
+ from kash.exec import kash_action
4
+ from kash.exec.preconditions import has_text_body, is_html
5
+ from kash.model import ActionInput, ActionResult
6
+
7
+
8
+ @kash_action(
9
+ precondition=is_html | has_text_body,
10
+ )
11
+ def render_as_html(input: ActionInput) -> ActionResult:
12
+ """
13
+ Convert text, Markdown, or HTML to pretty, formatted HTML using the kash default
14
+ page template.
15
+ """
16
+ config_result = webpage_config(input)
17
+ result = webpage_generate(ActionInput(items=config_result.items))
18
+ return result
@@ -1,14 +1,22 @@
1
1
  from kash.config.logger import get_logger
2
2
  from kash.exec import kash_action
3
- from kash.model import ActionInput, ActionResult
3
+ from kash.model import ActionInput, ActionResult, Param
4
4
  from kash.utils.errors import InvalidInput
5
5
  from kash.web_gen import tabbed_webpage
6
6
 
7
7
  log = get_logger(__name__)
8
8
 
9
9
 
10
- @kash_action()
11
- def webpage_config(input: ActionInput) -> ActionResult:
10
+ @kash_action(
11
+ params=(
12
+ Param(
13
+ name="clean_headings",
14
+ type=bool,
15
+ description="Use an LLM to clean up headings.",
16
+ ),
17
+ )
18
+ )
19
+ def webpage_config(input: ActionInput, clean_headings: bool = False) -> ActionResult:
12
20
  """
13
21
  Set up a web page config with optional tabs for each page of content. Uses first item as the page title.
14
22
  """
@@ -16,6 +24,6 @@ def webpage_config(input: ActionInput) -> ActionResult:
16
24
  if not item.body:
17
25
  raise InvalidInput(f"Item must have a body: {item}")
18
26
 
19
- config_item = tabbed_webpage.webpage_config(input.items)
27
+ config_item = tabbed_webpage.webpage_config(input.items, clean_headings)
20
28
 
21
29
  return ActionResult([config_item])
kash/commands/__init__.py CHANGED
@@ -1,21 +1,9 @@
1
- # Import all command modules to ensure commands are registered.
1
+ from pathlib import Path
2
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.logs_commands # noqa: F401
10
- import kash.commands.base.model_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
3
+ from kash.exec.importing import import_and_register
4
+
5
+ import_and_register(
6
+ __package__,
7
+ Path(__file__).parent,
8
+ ["base", "extras", "help", "workspace"],
9
+ )
@@ -7,6 +7,7 @@ from strif import copyfile_atomic
7
7
  from kash.config.logger import get_logger
8
8
  from kash.config.text_styles import STYLE_EMPH
9
9
  from kash.exec import assemble_path_args, kash_command, resolve_path_arg
10
+ from kash.model.paths_model import StorePath
10
11
  from kash.shell.output.shell_output import (
11
12
  PadStyle,
12
13
  PrintHooks,
@@ -21,6 +22,7 @@ from kash.utils.common.format_utils import fmt_loc
21
22
  from kash.utils.errors import InvalidInput
22
23
  from kash.utils.file_utils.file_formats_model import detect_file_format
23
24
  from kash.workspaces.workspace_output import print_file_info
25
+ from kash.workspaces.workspaces import current_ws
24
26
 
25
27
  log = get_logger(__name__)
26
28
 
@@ -149,7 +151,20 @@ def trash(*paths: str) -> None:
149
151
  """
150
152
 
151
153
  resolved_paths = assemble_path_args(*paths)
154
+
155
+ ws = current_ws()
156
+ affected_store_paths = [
157
+ p for p in resolved_paths if isinstance(p, StorePath) and (ws.base_dir / p).exists()
158
+ ]
159
+
152
160
  native_trash(*resolved_paths)
161
+
162
+ if affected_store_paths:
163
+ log.info(
164
+ "Refreshing current selection due to deleted store paths: %s", affected_store_paths
165
+ )
166
+ ws.selections.refresh_current(ws.base_dir)
167
+
153
168
  print_status(f"Deleted (check trash or recycling bin to recover):\n{fmt_lines(resolved_paths)}")
154
169
 
155
170
 
@@ -179,7 +179,7 @@ def reload_system() -> None:
179
179
  the local the local server. Not perfect! But sometimes useful for development.
180
180
  """
181
181
  import kash
182
- from kash.local_server.local_server import restart_local_server
182
+ from kash.local_server.local_server import restart_ui_server
183
183
  from kash.utils.common.import_utils import recursive_reload
184
184
 
185
185
  module = kash
@@ -197,7 +197,7 @@ def reload_system() -> None:
197
197
  log.info("Reloaded modules: %s", ", ".join(package_names))
198
198
  log.message("Reloaded %s modules from %s.", len(package_names), module.__name__)
199
199
 
200
- restart_local_server()
200
+ restart_ui_server()
201
201
 
202
202
  # TODO Re-register commands and actions.
203
203
 
@@ -213,3 +213,16 @@ def reload_commands_and_actions() -> None:
213
213
 
214
214
  reload_shell_commands_and_actions()
215
215
  log_command_action_info()
216
+
217
+
218
+ @kash_command
219
+ def debug_exception() -> None:
220
+ """
221
+ Useful to debug exception handling/printing in xonsh.
222
+ """
223
+
224
+ def raise_exception():
225
+ cprint("Raising an unexpected exception to test the exception handler.")
226
+ raise Exception("This is a test exception.")
227
+
228
+ raise_exception()
@@ -1,7 +1,15 @@
1
+ from clideps.env_vars.dotenv_setup import interactive_dotenv_setup
2
+ from clideps.env_vars.dotenv_utils import load_dotenv_paths
3
+ from clideps.env_vars.env_check import format_dotenv_check, format_env_var_check, print_env_check
4
+ from clideps.env_vars.env_names import get_all_common_env_names
5
+ from clideps.pkgs.pkg_check import pkg_check
6
+ from clideps.terminal.terminal_features import terminal_check
7
+ from flowmark import Wrap
8
+ from rich.text import Text
9
+
1
10
  from kash.commands.base.model_commands import list_apis, list_models
2
- from kash.commands.workspace.workspace_commands import list_params
3
11
  from kash.config.logger import get_logger
4
- from kash.config.settings import RECOMMENDED_API_KEYS
12
+ from kash.config.settings import RECOMMENDED_API_KEYS, global_settings
5
13
  from kash.docs.all_docs import all_docs
6
14
  from kash.exec import kash_command
7
15
  from kash.help.tldr_help import tldr_refresh_cache
@@ -12,14 +20,6 @@ from kash.model.params_model import (
12
20
  DEFAULT_STANDARD_LLM,
13
21
  DEFAULT_STRUCTURED_LLM,
14
22
  )
15
- from kash.shell.clideps.api_keys import (
16
- ApiEnvKey,
17
- load_dotenv_paths,
18
- print_api_key_setup,
19
- )
20
- from kash.shell.clideps.dotenv_setup import interactive_dotenv_setup
21
- from kash.shell.clideps.pkg_deps import pkg_check
22
- from kash.shell.clideps.terminal_features import terminal_check
23
23
  from kash.shell.input.input_prompts import input_choice
24
24
  from kash.shell.output.shell_formatting import format_name_and_value
25
25
  from kash.shell.output.shell_output import (
@@ -48,8 +48,13 @@ def self_check(brief: bool = False) -> None:
48
48
  Self-check kash setup, including termal settings, tools, and API keys.
49
49
  """
50
50
  if brief:
51
- terminal_check().print_term_info()
52
- print_api_key_setup(recommended_keys=RECOMMENDED_API_KEYS, once=False)
51
+ cprint(terminal_check().formatted())
52
+ cprint(Text.assemble("Dotenv files: ", format_dotenv_check()))
53
+ cprint(
54
+ Text.assemble(
55
+ "Env vars: ", format_env_var_check(env_vars=RECOMMENDED_API_KEYS, one_line=True)
56
+ )
57
+ )
53
58
  check_system_tools(brief=brief)
54
59
  tldr_refresh_cache()
55
60
  try:
@@ -61,7 +66,7 @@ def self_check(brief: bool = False) -> None:
61
66
  else:
62
67
  version()
63
68
  cprint()
64
- terminal_check().print_term_info()
69
+ cprint(terminal_check().formatted())
65
70
  cprint()
66
71
  list_apis()
67
72
  cprint()
@@ -86,9 +91,10 @@ def self_configure(all: bool = False, update: bool = False) -> None:
86
91
  """
87
92
  Interactively configure API keys and preferred models.
88
93
  """
94
+ from kash.commands.workspace.workspace_commands import params as list_params
89
95
 
90
96
  if all:
91
- api_keys = [key.value for key in ApiEnvKey]
97
+ api_keys = list(set(get_all_common_env_names() + RECOMMENDED_API_KEYS))
92
98
  else:
93
99
  api_keys = RECOMMENDED_API_KEYS
94
100
  # Show APIs before starting.
@@ -175,10 +181,11 @@ def reload_env() -> None:
175
181
  Reload the environment variables from the .env file.
176
182
  """
177
183
 
178
- env_paths = load_dotenv_paths()
184
+ env_paths = load_dotenv_paths(True, True, global_settings().system_config_dir)
179
185
  if env_paths:
180
186
  cprint("Reloaded environment variables")
181
- print_api_key_setup(RECOMMENDED_API_KEYS)
187
+
188
+ print_env_check(RECOMMENDED_API_KEYS)
182
189
  else:
183
190
  raise InvalidState("No .env file found")
184
191
 
@@ -197,7 +204,9 @@ def kits() -> None:
197
204
  else:
198
205
  cprint("Currently imported kits:")
199
206
  for kit in get_loaded_kits().values():
200
- cprint(format_name_and_value(f"{kit.name} kit", str(kit.path or "")))
207
+ cprint(
208
+ format_name_and_value(f"{kit.name} kit", str(kit.path or ""), text_wrap=Wrap.NONE)
209
+ )
201
210
 
202
211
 
203
212
  @kash_command
@@ -210,5 +219,5 @@ def settings() -> None:
210
219
  settings = global_settings()
211
220
  print_h2("Global Settings")
212
221
  for field, value in settings.__dict__.items():
213
- cprint(format_name_and_value(field, str(value)))
222
+ cprint(format_name_and_value(field, str(value), text_wrap=Wrap.NONE))
214
223
  PrintHooks.spacer()
@@ -8,7 +8,6 @@ from kash.config.settings import (
8
8
  LogLevel,
9
9
  atomic_global_settings,
10
10
  global_settings,
11
- server_log_file_path,
12
11
  )
13
12
  from kash.exec import kash_command
14
13
  from kash.shell.output.shell_formatting import format_name_and_value
@@ -87,7 +86,5 @@ def log_settings() -> None:
87
86
  cprint(format_name_and_value("log_file_level", settings.log_file_level.name))
88
87
  cprint(format_name_and_value("log_console_level", settings.log_console_level.name))
89
88
  cprint(
90
- format_name_and_value(
91
- "server_log_file", str(server_log_file_path(global_settings().local_server_port))
92
- )
89
+ format_name_and_value("server_log_file_path", str(global_settings().local_server_log_path))
93
90
  )
@@ -1,10 +1,10 @@
1
+ from clideps.env_vars.dotenv_utils import env_var_is_set
1
2
  from flowmark import Wrap
2
3
  from rich.text import Text
3
4
 
5
+ from kash.config.settings import get_all_common_api_env_vars
4
6
  from kash.exec.command_registry import kash_command
5
7
  from kash.llm_utils import LLM, api_for_model
6
- from kash.shell.clideps.api_keys import ApiEnvKey
7
- from kash.shell.clideps.dotenv_utils import env_var_is_set
8
8
  from kash.shell.output.shell_formatting import (
9
9
  format_failure,
10
10
  format_name_and_value,
@@ -48,11 +48,11 @@ def list_apis() -> None:
48
48
  List and check configuration for all APIs.
49
49
  """
50
50
  print_h2("API keys")
51
- for api in ApiEnvKey:
52
- emoji = format_success_emoji(env_var_is_set(api.value))
51
+ for env_var in get_all_common_api_env_vars():
52
+ emoji = format_success_emoji(env_var_is_set(env_var))
53
53
  message = (
54
- f"API key {api.value} found"
55
- if env_var_is_set(api.value)
56
- else f"API key {api.value} not found"
54
+ f"API key {env_var} found"
55
+ if env_var_is_set(env_var)
56
+ else f"API key {env_var} not found"
57
57
  )
58
- cprint(Text.assemble(emoji, format_name_and_value(api.name, message)))
58
+ cprint(Text.assemble(emoji, format_name_and_value(env_var, message)))
@@ -1,9 +1,10 @@
1
1
  from pathlib import Path
2
2
 
3
+ from clideps.pkgs.pkg_check import pkg_check
4
+
3
5
  from kash.config.logger import get_logger
4
6
  from kash.exec import assemble_path_args, kash_command
5
7
  from kash.exec_model.shell_model import ShellResult
6
- from kash.shell.clideps.pkg_deps import Pkg, pkg_check
7
8
  from kash.shell.output.shell_output import cprint
8
9
  from kash.utils.common.parse_shell_args import shell_quote
9
10
  from kash.utils.errors import InvalidState
@@ -33,7 +34,7 @@ def search(
33
34
  :param ignore_case: Ignore case when searching.
34
35
  :param verbose: Also print the ripgrep command line.
35
36
  """
36
- pkg_check().require(Pkg.ripgrep)
37
+ pkg_check().require("ripgrep")
37
38
  from ripgrepy import RipGrepNotFound, Ripgrepy
38
39
 
39
40
  resolved_paths = assemble_path_args(*paths)
@@ -18,6 +18,7 @@ def show(
18
18
  native: bool = False,
19
19
  thumbnail: bool = False,
20
20
  browser: bool = False,
21
+ plain: bool = False,
21
22
  ) -> None:
22
23
  """
23
24
  Show the contents of a file if one is given, or the first file if multiple files
@@ -31,10 +32,11 @@ def show(
31
32
  :param native: Force display with a native app (depending on your system configuration).
32
33
  :param thumbnail: If there is a thumbnail image, show it too.
33
34
  :param browser: Force display with your default web browser.
35
+ :param plain: Use plain view in the console (this is `bat`'s `plain` style).
34
36
  """
35
37
  view_mode = (
36
38
  ViewMode.console
37
- if console
39
+ if console or plain
38
40
  else ViewMode.browser
39
41
  if browser
40
42
  else ViewMode.native
@@ -58,9 +60,9 @@ def show(
58
60
  log.info("Had trouble showing thumbnail image (will skip): %s", e)
59
61
  cprint(f"[Image: {item.thumbnail_url}]", style=STYLE_HINT)
60
62
 
61
- view_file_native(ws.base_dir / input_path, view_mode=view_mode)
63
+ view_file_native(ws.base_dir / input_path, view_mode=view_mode, plain=plain)
62
64
  else:
63
- view_file_native(input_path, view_mode=view_mode)
65
+ view_file_native(input_path, view_mode=view_mode, plain=plain)
64
66
  except (InvalidInput, InvalidState):
65
67
  if path:
66
68
  # If path is absolute or we couldbn't get a selection, just show the file.
@@ -0,0 +1,186 @@
1
+ import subprocess
2
+ import tomllib
3
+ from pathlib import Path
4
+
5
+ import pandas as pd
6
+ from packaging.tags import Tag, sys_tags
7
+ from packaging.utils import parse_wheel_filename
8
+ from prettyfmt import fmt_size_dual
9
+ from strif import atomic_output_file
10
+
11
+ from kash.config.logger import get_logger
12
+ from kash.config.text_styles import COLOR_STATUS
13
+ from kash.exec import kash_command
14
+ from kash.shell.output.shell_output import cprint
15
+
16
+ log = get_logger(__name__)
17
+
18
+
19
+ def choose_wheel(wheels: list[dict], allowed: list[Tag]) -> dict | None:
20
+ """
21
+ Pick the first wheel whose tag set intersects `allowed`
22
+ (highest priority wins).
23
+ """
24
+ priority = {tag: idx for idx, tag in enumerate(allowed)}
25
+ best: dict | None = None
26
+ best_rank = float("inf")
27
+
28
+ for meta in wheels:
29
+ url = meta.get("url", "")
30
+ filename = Path(url).name if url else ""
31
+ if not filename:
32
+ raise ValueError(f"No filename found for {url}")
33
+ tags = parse_wheel_filename(filename)[-1]
34
+
35
+ for tag in tags:
36
+ rank = priority.get(tag)
37
+ if rank is not None and rank < best_rank:
38
+ best = meta
39
+ best_rank = rank
40
+ break
41
+
42
+ return best
43
+
44
+
45
+ def get_platform() -> str:
46
+ """
47
+ Get the most-specific platform tag your interpreter supports.
48
+ """
49
+ return next(sys_tags()).platform
50
+
51
+
52
+ def parse_uv_lock(lock_path: Path) -> pd.DataFrame:
53
+ """
54
+ Return one row per package from a uv.lock file, selecting the best
55
+ matching wheel for the current interpreter or falling back to the sdist.
56
+
57
+ Columns: name, version, registry, file_type, url, hash, size, filename.
58
+ """
59
+ with open(lock_path, "rb") as f:
60
+ data = tomllib.load(f)
61
+
62
+ rows: list[dict] = []
63
+ for pkg in data.get("package", []):
64
+ name = pkg.get("name")
65
+ version = pkg.get("version")
66
+ registry = pkg.get("source", {}).get("registry")
67
+
68
+ wheels = pkg.get("wheels", [])
69
+ selected = choose_wheel(wheels, list(sys_tags()))
70
+ if selected:
71
+ meta = selected
72
+ file_type = "wheel"
73
+ else:
74
+ meta = pkg.get("sdist", {})
75
+ file_type = "sdist"
76
+
77
+ url = meta.get("url")
78
+ rows.append(
79
+ {
80
+ "name": name,
81
+ "version": version,
82
+ "registry": registry,
83
+ "file_type": file_type,
84
+ "url": url,
85
+ "hash": meta.get("hash"),
86
+ "size": meta.get("size"),
87
+ "filename": Path(url).name if url else None,
88
+ }
89
+ )
90
+
91
+ return pd.DataFrame(rows)
92
+
93
+
94
+ def uv_runtime_packages(
95
+ project_dir: str | Path = ".", no_dev: bool = False, uv_executable: str = "uv"
96
+ ) -> list[str]:
97
+ """
98
+ Return the *runtime* (non-dev) package names that would be installed for the
99
+ given project, as resolved by uv.
100
+ """
101
+ cmd = [
102
+ uv_executable,
103
+ "export",
104
+ "--format",
105
+ "requirements-txt",
106
+ "--no-header",
107
+ "--no-annotate",
108
+ "--no-hashes",
109
+ ]
110
+ if no_dev:
111
+ cmd.append("--no-dev")
112
+
113
+ result = subprocess.run(
114
+ cmd,
115
+ cwd=Path(project_dir),
116
+ check=True,
117
+ text=True,
118
+ capture_output=True,
119
+ )
120
+
121
+ packages: list[str] = []
122
+ for line in result.stdout.splitlines():
123
+ line = line.strip()
124
+ if "==" not in line: # skip “-e .” and blank lines
125
+ continue
126
+ pkg_name, _ = line.split("==", maxsplit=1)
127
+ packages.append(pkg_name.strip())
128
+
129
+ return packages
130
+
131
+
132
+ @kash_command
133
+ def uv_dep_info(
134
+ uv_lock: str = "uv.lock",
135
+ pyproject: str = "pyproject.toml",
136
+ save: str | None = None,
137
+ sort_by: str = "size",
138
+ ) -> None:
139
+ """
140
+ Parse a uv.lock file and print information about the packages.
141
+ By default, filters to show only 'main' dependencies from pyproject.toml.
142
+ Helpful for looking at sizes of dependencies.
143
+ """
144
+ uv_lock_path = Path(uv_lock)
145
+ pyproject_path = Path(pyproject)
146
+
147
+ main_deps: list[str] | None = None
148
+ all_deps: list[str] = []
149
+ if pyproject_path.exists():
150
+ cprint("Reading main dependencies from with uv", style=COLOR_STATUS)
151
+ main_deps = uv_runtime_packages(no_dev=True)
152
+ all_deps = uv_runtime_packages(no_dev=False)
153
+ else:
154
+ log.warning("pyproject.toml not found: %s", pyproject_path)
155
+
156
+ cprint(f"Parsing lock file: {uv_lock_path}", style=COLOR_STATUS)
157
+
158
+ df = parse_uv_lock(uv_lock_path)
159
+ df = df.sort_values(by=sort_by)
160
+
161
+ if main_deps:
162
+ cprint(
163
+ f"Filtering lock file entries to include only {len(main_deps)} of {len(all_deps)} dependencies.",
164
+ style=COLOR_STATUS,
165
+ )
166
+ df = df[df["name"].isin(main_deps)]
167
+ else:
168
+ cprint("Showing all packages from lock file.", style=COLOR_STATUS)
169
+ cprint()
170
+
171
+ # Show only selected columns and full output
172
+ cols = ["name", "version", "file_type", "size", "filename"]
173
+ print(df.loc[:, cols].to_string(max_rows=None))
174
+ cprint() # Add a newline for separation
175
+
176
+ # Calculate and print summary stats
177
+ num_deps = len(df)
178
+ total_size = pd.Series(pd.to_numeric(df["size"], errors="coerce")).fillna(0).sum()
179
+
180
+ cprint(f"Packages listed: {num_deps}", style=COLOR_STATUS)
181
+ cprint(f"Total size: {fmt_size_dual(int(total_size))}", style=COLOR_STATUS)
182
+
183
+ if save:
184
+ with atomic_output_file(save) as temp_name:
185
+ df.to_csv(temp_name, index=False) # Added index=False for cleaner CSV
186
+ cprint(f"Saved to {save}", style=COLOR_STATUS)
@@ -1,42 +1,13 @@
1
- from rich.box import SQUARE
2
- from rich.console import Group
3
- from rich.panel import Panel
4
-
5
- from kash.commands.help.logo import branded_box
1
+ from kash.commands.help.welcome import welcome
6
2
  from kash.config.logger import get_logger
7
- from kash.config.text_styles import (
8
- COLOR_HINT,
9
- )
10
3
  from kash.docs.all_docs import DocSelection, all_docs
11
4
  from kash.exec import kash_command
12
5
  from kash.help.help_pages import print_see_also
13
- from kash.shell.output.shell_output import PrintHooks, console_pager, cprint, print_markdown
14
- from kash.shell.version import get_full_version_name
15
- from kash.utils.rich_custom.rich_markdown_fork import Markdown
6
+ from kash.shell.output.shell_output import console_pager, print_markdown
16
7
 
17
8
  log = get_logger(__name__)
18
9
 
19
10
 
20
- @kash_command
21
- def welcome() -> None:
22
- """
23
- Print a welcome message.
24
- """
25
-
26
- help_topics = all_docs.help_topics
27
- version = get_full_version_name()
28
- # Create header with logo and right-justified version
29
-
30
- PrintHooks.before_welcome()
31
- cprint(
32
- branded_box(
33
- Group(Markdown(help_topics.welcome)),
34
- version,
35
- )
36
- )
37
- cprint(Panel(Markdown(help_topics.warning), box=SQUARE, border_style=COLOR_HINT))
38
-
39
-
40
11
  @kash_command
41
12
  def manual(no_pager: bool = False, doc_selection: DocSelection = DocSelection.full) -> None:
42
13
  """