kash-shell 0.3.9__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 (135) 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 +13 -0
  9. kash/commands/base/general_commands.py +21 -16
  10. kash/commands/base/logs_commands.py +4 -2
  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 +18 -15
  19. kash/config/colors.py +2 -0
  20. kash/config/env_settings.py +14 -1
  21. kash/config/init.py +2 -2
  22. kash/config/logger.py +59 -56
  23. kash/config/logger_basic.py +3 -3
  24. kash/config/settings.py +116 -57
  25. kash/config/setup.py +28 -12
  26. kash/config/text_styles.py +3 -13
  27. kash/docs/load_api_docs.py +2 -1
  28. kash/docs/markdown/topics/a3_getting_started.md +3 -2
  29. kash/{concepts → embeddings}/text_similarity.py +2 -2
  30. kash/exec/__init__.py +20 -3
  31. kash/exec/action_decorators.py +18 -4
  32. kash/exec/action_exec.py +41 -23
  33. kash/exec/action_registry.py +13 -48
  34. kash/exec/command_registry.py +2 -1
  35. kash/exec/fetch_url_metadata.py +4 -6
  36. kash/exec/importing.py +56 -0
  37. kash/exec/llm_transforms.py +6 -7
  38. kash/exec/precondition_registry.py +2 -1
  39. kash/exec/preconditions.py +16 -1
  40. kash/exec/shell_callable_action.py +33 -19
  41. kash/file_storage/file_store.py +23 -10
  42. kash/file_storage/item_file_format.py +5 -2
  43. kash/file_storage/metadata_dirs.py +11 -2
  44. kash/help/assistant.py +1 -1
  45. kash/help/assistant_instructions.py +2 -1
  46. kash/help/help_embeddings.py +2 -2
  47. kash/help/help_printing.py +7 -11
  48. kash/llm_utils/clean_headings.py +1 -1
  49. kash/llm_utils/llm_api_keys.py +4 -4
  50. kash/llm_utils/llm_features.py +68 -0
  51. kash/llm_utils/llm_messages.py +1 -2
  52. kash/llm_utils/llm_names.py +1 -1
  53. kash/llm_utils/llms.py +8 -3
  54. kash/local_server/__init__.py +5 -2
  55. kash/local_server/local_server.py +8 -5
  56. kash/local_server/local_server_commands.py +2 -2
  57. kash/local_server/local_url_formatters.py +1 -1
  58. kash/mcp/__init__.py +5 -2
  59. kash/mcp/mcp_cli.py +5 -5
  60. kash/mcp/mcp_server_commands.py +5 -5
  61. kash/mcp/mcp_server_routes.py +5 -5
  62. kash/mcp/mcp_server_sse.py +4 -2
  63. kash/media_base/media_cache.py +8 -8
  64. kash/media_base/media_services.py +1 -1
  65. kash/media_base/media_tools.py +6 -6
  66. kash/media_base/services/local_file_media.py +2 -2
  67. kash/media_base/{speech_transcription.py → transcription_deepgram.py} +25 -110
  68. kash/media_base/transcription_format.py +73 -0
  69. kash/media_base/transcription_whisper.py +38 -0
  70. kash/model/__init__.py +73 -5
  71. kash/model/actions_model.py +38 -4
  72. kash/model/concept_model.py +30 -0
  73. kash/model/items_model.py +44 -7
  74. kash/model/params_model.py +24 -0
  75. kash/shell/completions/completion_scoring.py +37 -5
  76. kash/shell/output/kerm_codes.py +1 -2
  77. kash/shell/output/shell_formatting.py +14 -4
  78. kash/shell/shell_main.py +2 -2
  79. kash/shell/utils/exception_printing.py +6 -0
  80. kash/shell/utils/native_utils.py +26 -20
  81. kash/text_handling/custom_sliding_transforms.py +12 -4
  82. kash/text_handling/doc_normalization.py +6 -2
  83. kash/text_handling/markdown_render.py +117 -0
  84. kash/text_handling/markdown_utils.py +204 -0
  85. kash/utils/common/import_utils.py +12 -3
  86. kash/utils/common/type_utils.py +0 -29
  87. kash/utils/common/url.py +27 -3
  88. kash/utils/errors.py +6 -0
  89. kash/utils/file_utils/file_formats.py +2 -2
  90. kash/utils/file_utils/file_formats_model.py +3 -0
  91. kash/web_content/dir_store.py +1 -2
  92. kash/web_content/file_cache_utils.py +37 -10
  93. kash/web_content/file_processing.py +68 -0
  94. kash/web_content/local_file_cache.py +12 -9
  95. kash/web_content/web_extract.py +8 -3
  96. kash/web_content/web_fetch.py +12 -4
  97. kash/web_gen/tabbed_webpage.py +5 -2
  98. kash/web_gen/templates/base_styles.css.jinja +120 -14
  99. kash/web_gen/templates/base_webpage.html.jinja +60 -13
  100. kash/web_gen/templates/content_styles.css.jinja +4 -2
  101. kash/web_gen/templates/item_view.html.jinja +2 -2
  102. kash/web_gen/templates/tabbed_webpage.html.jinja +1 -2
  103. kash/workspaces/__init__.py +15 -2
  104. kash/workspaces/selections.py +18 -3
  105. kash/workspaces/source_items.py +0 -1
  106. kash/workspaces/workspaces.py +5 -11
  107. kash/xonsh_custom/command_nl_utils.py +40 -19
  108. kash/xonsh_custom/custom_shell.py +43 -11
  109. kash/xonsh_custom/customize_prompt.py +39 -21
  110. kash/xonsh_custom/load_into_xonsh.py +22 -25
  111. kash/xonsh_custom/shell_load_commands.py +2 -2
  112. kash/xonsh_custom/xonsh_completers.py +2 -249
  113. kash/xonsh_custom/xonsh_keybindings.py +282 -0
  114. kash/xonsh_custom/xonsh_modern_tools.py +3 -3
  115. kash/xontrib/kash_extension.py +5 -6
  116. {kash_shell-0.3.9.dist-info → kash_shell-0.3.10.dist-info}/METADATA +8 -6
  117. {kash_shell-0.3.9.dist-info → kash_shell-0.3.10.dist-info}/RECORD +122 -123
  118. kash/concepts/concept_formats.py +0 -23
  119. kash/shell/clideps/api_keys.py +0 -100
  120. kash/shell/clideps/dotenv_setup.py +0 -115
  121. kash/shell/clideps/dotenv_utils.py +0 -98
  122. kash/shell/clideps/pkg_deps.py +0 -257
  123. kash/shell/clideps/platforms.py +0 -11
  124. kash/shell/clideps/terminal_features.py +0 -56
  125. kash/shell/utils/osc_utils.py +0 -95
  126. kash/shell/utils/terminal_images.py +0 -133
  127. kash/text_handling/markdown_util.py +0 -167
  128. kash/utils/common/atomic_var.py +0 -171
  129. kash/utils/common/string_replace.py +0 -93
  130. kash/utils/common/string_template.py +0 -101
  131. /kash/{concepts → embeddings}/cosine.py +0 -0
  132. /kash/{concepts → embeddings}/embeddings.py +0 -0
  133. {kash_shell-0.3.9.dist-info → kash_shell-0.3.10.dist-info}/WHEEL +0 -0
  134. {kash_shell-0.3.9.dist-info → kash_shell-0.3.10.dist-info}/entry_points.txt +0 -0
  135. {kash_shell-0.3.9.dist-info → kash_shell-0.3.10.dist-info}/licenses/LICENSE +0 -0
@@ -7,18 +7,14 @@ from typing import TYPE_CHECKING, TypeVar
7
7
 
8
8
  from prettyfmt import fmt_path
9
9
 
10
- from kash.config.logger import get_logger, reset_log_root
10
+ from kash.config.logger import get_logger, reset_rich_logging
11
11
  from kash.config.settings import (
12
12
  GLOBAL_WS_NAME,
13
- RECOMMENDED_API_KEYS,
14
- get_global_ws_dir,
15
- get_ws_root_dir,
16
13
  global_settings,
17
14
  resolve_and_create_dirs,
18
15
  )
19
16
  from kash.file_storage.metadata_dirs import MetadataDirs
20
17
  from kash.model.params_model import GLOBAL_PARAMS, RawParamValues
21
- from kash.shell.clideps.api_keys import print_api_key_setup
22
18
  from kash.utils.errors import FileNotFound, InvalidInput, InvalidState
23
19
  from kash.utils.file_utils.ignore_files import IgnoreFilter, is_ignored_default
24
20
  from kash.workspaces.workspace_registry import WorkspaceInfo, get_ws_registry
@@ -128,7 +124,7 @@ def resolve_ws(name: str | Path) -> WorkspaceInfo:
128
124
  resolved = name
129
125
  parent_dir = resolved.parent
130
126
  else:
131
- parent_dir = get_ws_root_dir()
127
+ parent_dir = global_settings().ws_root_dir
132
128
  resolved = parent_dir / name
133
129
  elif name_str.startswith(".") or name_str.startswith("/"):
134
130
  # Explicit paths respected otherwise use workspace root.
@@ -136,7 +132,7 @@ def resolve_ws(name: str | Path) -> WorkspaceInfo:
136
132
  parent_dir = resolved.parent
137
133
  name = resolved.name
138
134
  else:
139
- parent_dir = get_ws_root_dir()
135
+ parent_dir = global_settings().ws_root_dir
140
136
  resolved = parent_dir / Path(name_str)
141
137
 
142
138
  ws_name = check_strict_workspace_name(resolved.name)
@@ -161,7 +157,7 @@ def get_ws(name_or_path: str | Path, auto_init: bool = True) -> "FileStore":
161
157
 
162
158
  @cache
163
159
  def global_ws_dir() -> Path:
164
- kb_path = resolve_and_create_dirs(get_global_ws_dir(), is_dir=True)
160
+ kb_path = resolve_and_create_dirs(global_settings().global_ws_dir, is_dir=True)
165
161
  log.debug("Global workspace path: %s", kb_path)
166
162
  return kb_path
167
163
 
@@ -190,7 +186,7 @@ def switch_to_ws(base_dir: Path) -> "FileStore":
190
186
  ws_dirs = MetadataDirs(base_dir=info.base_dir, is_global_ws=info.is_global_ws)
191
187
 
192
188
  # Use the global log root for the global_ws, and the workspace log root otherwise.
193
- reset_log_root(None, info.name if not info.is_global_ws else None)
189
+ reset_rich_logging(None, info.name if not info.is_global_ws else None)
194
190
 
195
191
  if info.is_global_ws:
196
192
  # If not in a workspace, use the global cache locations.
@@ -237,8 +233,6 @@ def current_ws(silent: bool = False) -> "FileStore":
237
233
  ws = switch_to_ws(base_dir)
238
234
 
239
235
  if not silent:
240
- # Delayed, once-only logging of any setup warnings.
241
- print_api_key_setup(RECOMMENDED_API_KEYS, once=True)
242
236
  ws.log_workspace_info(once=True)
243
237
 
244
238
  return ws
@@ -3,20 +3,16 @@ import re
3
3
  from prettyfmt import fmt_words
4
4
 
5
5
  INNER_PUNCT_CHARS = r"-'’–—"
6
- OUTER_PUNCT_CHARS = r".,'\"“”‘’:!?()"
6
+ OUTER_PUNCT_CHARS = r".,'\"" "''':;!?()"
7
7
 
8
- WORD_PAT = (
9
- rf"[{OUTER_PUNCT_CHARS}]{{0,2}}[\w]+(?:[{INNER_PUNCT_CHARS}\w]+)*[{OUTER_PUNCT_CHARS}]{{0,2}}"
10
- )
11
- """
12
- Pattern to match a word in natural language text (i.e. words and natural
13
- language-only punctuation).
14
- """
8
+ ESCAPED_INNER_PUNCT_CHARS = re.escape(INNER_PUNCT_CHARS)
9
+ ESCAPED_OUTER_PUNCT_CHARS = re.escape(OUTER_PUNCT_CHARS)
15
10
 
16
- NL_PAT = rf"^{WORD_PAT}(?:\s+{WORD_PAT})*$"
17
- """
18
- Pattern to match natural language text in a command line.
19
- """
11
+ PUNCT_SEQ_RE = re.compile(rf"[{ESCAPED_INNER_PUNCT_CHARS}{ESCAPED_OUTER_PUNCT_CHARS}]+")
12
+
13
+ ONLY_WORDS_RE = re.compile(rf"^[\w\s{ESCAPED_INNER_PUNCT_CHARS}]*$")
14
+
15
+ PLAIN_WORD_RE = re.compile(r"^\w.*\w$")
20
16
 
21
17
 
22
18
  def as_nl_words(text: str) -> str:
@@ -30,18 +26,25 @@ def as_nl_words(text: str) -> str:
30
26
 
31
27
  def looks_like_nl(text: str) -> bool:
32
28
  """
33
- Check if a text looks like plain natural language text, i.e. word chars,
34
- possibly with ? or hyphens/apostrophes when inside words but not other
35
- code or punctuation.
29
+ Check if a text looks like plain natural language text. Just very simple
30
+ based on words and only basic punctuation.
36
31
  """
37
- return bool(re.match(NL_PAT, text.strip()))
32
+ is_only_word_chars = bool(ONLY_WORDS_RE.fullmatch(text))
33
+ without_punct = PUNCT_SEQ_RE.sub("", text)
34
+ is_only_words_punct = bool(ONLY_WORDS_RE.fullmatch(without_punct))
35
+ words = without_punct.strip().split()
36
+ one_longer_word = any(len(word) > 3 for word in words)
37
+
38
+ return one_longer_word and (
39
+ (is_only_words_punct and len(words) >= 3) or (is_only_word_chars and len(words) >= 2)
40
+ )
38
41
 
39
42
 
40
43
  ## Tests
41
44
 
42
45
 
43
46
  def test_as_nl_words():
44
- assert as_nl_words("x=3+9; foo('bar')") == "x=3+9; foo('bar"
47
+ assert as_nl_words("x=3+9; foo('bar')") == "x=3+9 foo('bar"
45
48
  assert as_nl_words("cd ..") == "cd .."
46
49
  assert as_nl_words("transcribe some-file_23.mp3") == "transcribe some-file_23.mp3"
47
50
  assert as_nl_words("hello world ") == "hello world"
@@ -57,14 +60,32 @@ def test_looks_like_nl():
57
60
  assert looks_like_nl("hello world")
58
61
  assert looks_like_nl(" hello world ")
59
62
  assert looks_like_nl("what's up")
60
- assert looks_like_nl("hello-world")
61
63
  assert looks_like_nl("is this a question?")
62
64
  assert looks_like_nl("'quoted text'")
63
65
  assert looks_like_nl("git push origin main")
66
+ assert looks_like_nl("this is natural language")
67
+ assert looks_like_nl(" what's up, doc? ")
68
+ assert looks_like_nl("multiple spaces here")
69
+ assert looks_like_nl("go to the store (buy milk)")
70
+ assert looks_like_nl("'quoted text' has three words")
71
+ assert looks_like_nl("git push origin main")
72
+ assert looks_like_nl("what's up")
64
73
 
74
+ assert not looks_like_nl("hello-world")
75
+ assert not looks_like_nl("cd ..")
76
+ assert not looks_like_nl("file_name.txt")
77
+ assert not looks_like_nl("ls -la")
78
+ assert not looks_like_nl("https://example.com")
79
+ assert not looks_like_nl("cmd | grep pattern")
80
+ assert not looks_like_nl("use a+b")
81
+ assert not looks_like_nl("x=3")
82
+ assert not looks_like_nl("a > b")
83
+ assert not looks_like_nl("file_name.txt")
84
+ assert not looks_like_nl("foo;")
85
+ assert not looks_like_nl("hello-world")
65
86
  assert not looks_like_nl("ls -la")
66
87
  assert not looks_like_nl("cd ..")
67
88
  assert not looks_like_nl("echo $HOME")
68
89
  assert not looks_like_nl("https://example.com")
69
- assert not looks_like_nl("file.txt")
90
+ assert not looks_like_nl(text="file.txt")
70
91
  assert not looks_like_nl("cmd | grep pattern")
@@ -1,15 +1,17 @@
1
1
  import os
2
- import sys
2
+ import signal
3
3
  import threading
4
4
  import time
5
5
  from collections.abc import Callable
6
6
  from os.path import expanduser
7
- from typing import cast
7
+ from subprocess import CalledProcessError
8
+ from types import TracebackType
9
+ from typing import TypeAlias, cast
8
10
 
9
11
  import xonsh.tools as xt
10
12
  from prompt_toolkit.formatted_text import FormattedText
11
13
  from pygments.token import Token
12
- from strif import abbrev_str
14
+ from strif import abbrev_str, quote_if_needed
13
15
  from typing_extensions import override
14
16
  from xonsh.built_ins import XSH
15
17
  from xonsh.environ import xonshrc_context
@@ -27,7 +29,7 @@ from kash.config import colors
27
29
  from kash.config.lazy_imports import import_start_time # usort:skip
28
30
  from kash.config.logger import get_console, get_log_settings, get_logger
29
31
  from kash.config.settings import APP_NAME, find_rcfiles
30
- from kash.config.text_styles import SPINNER, STYLE_ASSISTANCE
32
+ from kash.config.text_styles import SPINNER, STYLE_ASSISTANCE, STYLE_HINT
31
33
  from kash.help.assistant import AssistanceType
32
34
  from kash.shell.output.shell_output import cprint
33
35
  from kash.shell.ui.shell_syntax import is_assist_request_str
@@ -108,12 +110,33 @@ class CustomPTKPromptFormatter(PTKPromptFormatter):
108
110
  return super().__call__(template=cast(str, template), **kwargs)
109
111
 
110
112
 
113
+ def exit_code_str(e: CalledProcessError) -> str:
114
+ """
115
+ Prettier version of `CalledProcessError.__str__()`.
116
+ """
117
+ if isinstance(e.cmd, list):
118
+ cmd = "`" + " ".join(quote_if_needed(c) for c in e.cmd) + "`"
119
+ else:
120
+ cmd = str(e.cmd)
121
+ if e.returncode and e.returncode < 0:
122
+ try:
123
+ signal_name = signal.Signals(-e.returncode).name
124
+ return f"Command died with {signal_name} ({e.returncode}): {cmd}"
125
+ except ValueError:
126
+ return f"Command died with unknown signal {e.returncode}: {cmd}"
127
+ else:
128
+ return f"Command returned non-zero exit status {e.returncode}: {cmd}"
129
+
130
+
111
131
  # Base shell can be ReadlineShell or PromptToolkitShell.
112
132
  # Completer can be RankingCompleter or the standard Completer.
113
133
  # from xonsh.completer import Completer
114
134
  # from xonsh.shells.readline_shell import ReadlineShell
115
135
  from xonsh.shells.ptk_shell import PromptToolkitShell
116
136
 
137
+ ExcInfo: TypeAlias = tuple[type[BaseException], BaseException, TracebackType]
138
+ OptExcInfo: TypeAlias = ExcInfo | tuple[None, None, None]
139
+
117
140
 
118
141
  class CustomAssistantShell(PromptToolkitShell):
119
142
  """
@@ -122,7 +145,6 @@ class CustomAssistantShell(PromptToolkitShell):
122
145
  We're trying to reuse code where possible but need to change some of xonsh's
123
146
  behavior. Note event hooks in xonsh do let you customize handling but don't
124
147
  let you disable xonsh's processing, so it seems like this is necessary.
125
- so it seems like this is necessary.
126
148
  """
127
149
 
128
150
  def __init__(self, **kwargs):
@@ -186,16 +208,24 @@ class CustomAssistantShell(PromptToolkitShell):
186
208
  exc_info = (None, None, None)
187
209
  try:
188
210
  log.info("Running shell code: %r", src)
189
- exc_info = run_compiled_code(code, self.ctx, None, "single")
190
- if exc_info != (None, None, None):
191
- raise exc_info[1] # pyright: ignore
211
+ exc_info: OptExcInfo = run_compiled_code(code, self.ctx, None, "single") # pyright: ignore
212
+ log.debug("Completed shell code: %r", src)
213
+ _type, exc, _traceback = exc_info
214
+ if exc:
215
+ log.info("Shell exception info: %s", exc)
216
+ raise exc
192
217
  ts1 = time.time()
193
218
  if hist is not None and hist.last_cmd_rtn is None:
194
219
  hist.last_cmd_rtn = 0 # returncode for success
195
- log.info("Shell code completed successfully: %s", src)
220
+ except CalledProcessError as e:
221
+ # No point in logging stack trace here as it is only the shell stack,
222
+ # not the original code.
223
+ log.warning("%s", exit_code_str(e))
224
+ cprint("See `logs` for more details.", style=STYLE_HINT)
225
+ # print(e.args[0], file=sys.stderr)
196
226
  except xt.XonshError as e:
197
227
  log.info("Shell exception details: %s", e, exc_info=True)
198
- print(e.args[0], file=sys.stderr)
228
+ # print(e.args[0], file=sys.stderr)
199
229
  if hist is not None and hist.last_cmd_rtn is None: # pyright: ignore
200
230
  hist.last_cmd_rtn = 1 # return code for failure
201
231
  except (SystemExit, KeyboardInterrupt) as err:
@@ -323,7 +353,9 @@ def customize_xonsh_settings(is_interactive: bool):
323
353
  # Disable suggest for command not found errors (we handle this ourselves).
324
354
  "SUGGEST_COMMANDS": False,
325
355
  # TODO: Consider enabling and adapting auto-suggestions.
326
- "AUTO_SUGGEST": False,
356
+ "AUTO_SUGGEST": True,
357
+ # Show auto-suggestions in the completion menu.
358
+ "AUTO_SUGGEST_IN_COMPLETIONS": False,
327
359
  # Completions can be "none", "single", "multi", or "readline".
328
360
  # "single" lets us have rich completions with descriptions alongside.
329
361
  # https://xon.sh/envvars.html#completions-display
@@ -1,3 +1,4 @@
1
+ import os
1
2
  from dataclasses import dataclass
2
3
  from enum import Enum
3
4
  from pathlib import Path
@@ -29,8 +30,10 @@ class PromptInfo:
29
30
  workspace_details: str
30
31
  is_global_ws: bool
31
32
  cwd_str: str
33
+ cwd_short_str: str
32
34
  cwd_details: str
33
35
  cwd_in_workspace: bool
36
+ cwd_in_home: bool
34
37
 
35
38
 
36
39
  def get_prompt_info() -> PromptInfo:
@@ -42,28 +45,39 @@ def get_prompt_info() -> PromptInfo:
42
45
  workspace_details = f"Workspace at {ws.base_dir}"
43
46
 
44
47
  cwd = Path(".").resolve()
45
- if cwd.is_relative_to(ws.base_dir):
46
- cwd_in_workspace = True
48
+ cwd_in_home = cwd.is_relative_to(Path.home())
49
+ cwd_in_workspace = cwd.is_relative_to(ws.base_dir)
50
+ if cwd_in_workspace:
47
51
  rel_cwd = cwd.relative_to(ws.base_dir)
48
52
  if rel_cwd != Path("."):
49
53
  cwd_str = str(rel_cwd)
50
54
  else:
51
55
  cwd_str = ""
52
- elif cwd.is_relative_to(Path.home()):
53
- cwd_in_workspace = False
56
+ cwd_short_str = cwd_str
57
+ elif cwd_in_home:
54
58
  rel_to_home = cwd.relative_to(Path.home())
55
- if rel_to_home != Path("."):
56
- cwd_str = "~/" + rel_to_home.as_posix()
59
+ if cwd == Path.home():
60
+ cwd_str = cwd_short_str = "~"
61
+ elif cwd.parent == Path.home():
62
+ cwd_str = cwd_short_str = os.path.join("~", cwd.name)
57
63
  else:
58
- cwd_str = "~"
64
+ cwd_str = "~/" + str(rel_to_home)
65
+ # Show only last two levels of dirs when inside home.
66
+ cwd_short_str = os.path.join(cwd.parent.name, cwd.name)
59
67
  else:
60
- cwd_in_workspace = False
61
- cwd_str = cwd.as_posix()
68
+ cwd_str = cwd_short_str = str(cwd)
62
69
 
63
70
  cwd_details = f"Current directory at {cwd}"
64
71
 
65
72
  return PromptInfo(
66
- ws_name, workspace_details, is_global_ws, cwd_str, cwd_details, cwd_in_workspace
73
+ ws_name,
74
+ workspace_details,
75
+ is_global_ws,
76
+ cwd_str,
77
+ cwd_short_str,
78
+ cwd_details,
79
+ cwd_in_workspace,
80
+ cwd_in_home,
67
81
  )
68
82
 
69
83
 
@@ -167,17 +181,21 @@ def kash_xonsh_prompt() -> FormattedText:
167
181
  ).as_ptk_tokens(style=workspace_color)
168
182
 
169
183
  cwd_style = settings.ptk_style_bright if info.cwd_in_workspace else settings.ptk_style_normal
170
- # Use two-line prompt if cwd is outside workspace, one-line prompt otherwise.
171
- if info.cwd_str and not info.cwd_in_workspace:
184
+ # Separator could be "\n" if you like a 2-line prompt.
185
+ # separator = "\n" + settings.prompt_prefix
186
+ separator = ""
187
+ # Using shorter cwd str.
188
+ cwd_str = info.cwd_short_str
189
+ # If outside the workspace, show both workspace and cwd.
190
+ if info.cwd_short_str and not info.cwd_in_workspace:
172
191
  cwd_tokens = (
173
- [(settings.ptk_style_dim, "\n" + settings.prompt_prefix)]
174
- + text_with_tooltip(info.cwd_str, hover_text=info.cwd_details).as_ptk_tokens(
175
- style=cwd_style
176
- )
192
+ [(settings.ptk_style_dim, separator)]
193
+ + text_with_tooltip(cwd_str, hover_text=info.cwd_details).as_ptk_tokens(style=cwd_style)
177
194
  + [(settings.ptk_style_dim, " ")]
178
195
  )
179
- elif info.cwd_str:
180
- cwd_tokens = text_with_tooltip(info.cwd_str, hover_text=info.cwd_details).as_ptk_tokens(
196
+ elif cwd_str:
197
+ # If inside the workspace, show just cwd.
198
+ cwd_tokens = text_with_tooltip(cwd_str, hover_text=info.cwd_details).as_ptk_tokens(
181
199
  style=cwd_style
182
200
  )
183
201
  else:
@@ -187,9 +205,9 @@ def kash_xonsh_prompt() -> FormattedText:
187
205
  ptk_tokens = (
188
206
  settings.hrule
189
207
  + [(settings.ptk_style_dim, settings.prompt_prefix)]
190
- + [
191
- (settings.ptk_style_dim, "workspace: "),
192
- ]
208
+ # + [
209
+ # (settings.ptk_style_dim, "ws: "),
210
+ # ]
193
211
  + workspace_tokens
194
212
  + [
195
213
  (settings.ptk_style_dim, " "),
@@ -1,52 +1,49 @@
1
- from kash.config.setup import setup
2
- from kash.config.text_styles import LOGO_NAME, STYLE_HINT
3
- from kash.mcp.mcp_server_commands import start_mcp_server
4
- from kash.xonsh_custom.shell_load_commands import (
5
- is_interactive,
6
- log_command_action_info,
7
- reload_shell_commands_and_actions,
8
- set_env,
9
- )
1
+ from kash.config.setup import kash_setup
10
2
 
11
- setup(rich_logging=True) # Set up logging first.
3
+ kash_setup(rich_logging=True) # Set up logging first.
12
4
 
13
5
  import time
14
6
 
7
+ from clideps.pkgs.pkg_check import pkg_check
15
8
  from xonsh.built_ins import XSH
16
9
  from xonsh.prompt.base import PromptFields
17
10
 
18
11
  from kash.commands.base.general_commands import self_check
19
- from kash.commands.help import doc_commands
12
+ from kash.commands.help.welcome import welcome
20
13
  from kash.config.logger import get_logger
21
- from kash.config.settings import check_kerm_code_support
14
+ from kash.config.settings import RECOMMENDED_PKGS, check_kerm_code_support
15
+ from kash.config.text_styles import LOGO_NAME, STYLE_HINT
22
16
  from kash.local_server.local_server import start_ui_server
23
17
  from kash.local_server.local_url_formatters import enable_local_urls
24
- from kash.shell.clideps.pkg_deps import pkg_check
18
+ from kash.mcp.mcp_server_commands import start_mcp_server
25
19
  from kash.shell.output.shell_output import PrintHooks, cprint
26
20
  from kash.shell.version import get_version_tag
27
21
  from kash.workspaces import current_ws
28
22
  from kash.xonsh_custom.customize_prompt import get_prompt_info, kash_xonsh_prompt
23
+ from kash.xonsh_custom.shell_load_commands import (
24
+ is_interactive,
25
+ log_command_action_info,
26
+ reload_shell_commands_and_actions,
27
+ set_env,
28
+ )
29
29
  from kash.xonsh_custom.xonsh_completers import load_completers
30
+ from kash.xonsh_custom.xonsh_keybindings import add_key_bindings
30
31
  from kash.xonsh_custom.xonsh_modern_tools import modernize_shell
31
32
 
32
33
  log = get_logger(__name__)
33
34
 
34
35
 
35
- def _kash_workspace_str() -> str:
36
- return get_prompt_info().workspace_name
37
-
38
-
39
36
  def _shell_interactive_setup():
40
- from kash.xonsh_custom.xonsh_completers import add_key_bindings
41
-
42
37
  # Set up a prompt field for the workspace string.
43
38
  fields = PromptFields(XSH)
44
- fields["workspace_str"] = _kash_workspace_str
39
+ prompt_info = get_prompt_info()
40
+ fields["workspace_str"] = prompt_info.workspace_name
41
+ fields["cwd_short_str"] = prompt_info.cwd_short_str
45
42
  set_env("PROMPT_FIELDS", fields)
46
43
 
47
44
  # Set up the prompt and title template.
48
45
  set_env("PROMPT", kash_xonsh_prompt)
49
- set_env("TITLE", LOGO_NAME + " - {workspace_str}")
46
+ set_env("TITLE", LOGO_NAME + " - {workspace_str} - {cwd_short_str}")
50
47
 
51
48
  add_key_bindings()
52
49
 
@@ -59,12 +56,12 @@ def load_into_xonsh():
59
56
  """
60
57
 
61
58
  if is_interactive():
59
+ # Do welcome first since init could take a few seconds.
60
+ welcome()
61
+
62
62
  # Do first so in case there is an error, the shell prompt etc works as expected.
63
63
  _shell_interactive_setup()
64
64
 
65
- # Then do welcome first since init could take a few seconds.
66
- doc_commands.welcome()
67
-
68
65
  def load():
69
66
  load_start_time = time.time()
70
67
 
@@ -106,7 +103,7 @@ def load_into_xonsh():
106
103
 
107
104
  current_ws() # Validates and logs info for user.
108
105
 
109
- pkg_check().warn_if_missing()
106
+ pkg_check().warn_if_missing(*RECOMMENDED_PKGS)
110
107
 
111
108
  else:
112
109
  reload_shell_commands_and_actions()
@@ -1,8 +1,8 @@
1
1
  from kash.actions import get_loaded_kits
2
- from kash.config.setup import setup
2
+ from kash.config.setup import kash_setup
3
3
  from kash.config.text_styles import COLOR_VALUE, STYLE_HINT
4
4
 
5
- setup(rich_logging=True) # Set up logging first.
5
+ kash_setup(rich_logging=True) # Set up logging first.
6
6
 
7
7
  from collections.abc import Callable
8
8
  from typing import TYPE_CHECKING, TypeVar