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.
- kash/actions/__init__.py +4 -4
- kash/actions/core/markdownify.py +5 -2
- kash/actions/core/readability.py +5 -2
- kash/actions/core/render_as_html.py +18 -0
- kash/actions/core/webpage_config.py +12 -4
- kash/commands/__init__.py +8 -20
- kash/commands/base/basic_file_commands.py +15 -0
- kash/commands/base/debug_commands.py +13 -0
- kash/commands/base/general_commands.py +21 -16
- kash/commands/base/logs_commands.py +4 -2
- kash/commands/base/model_commands.py +8 -8
- kash/commands/base/search_command.py +3 -2
- kash/commands/base/show_command.py +5 -3
- kash/commands/extras/parse_uv_lock.py +186 -0
- kash/commands/help/doc_commands.py +2 -31
- kash/commands/help/welcome.py +33 -0
- kash/commands/workspace/selection_commands.py +11 -6
- kash/commands/workspace/workspace_commands.py +18 -15
- kash/config/colors.py +2 -0
- kash/config/env_settings.py +14 -1
- kash/config/init.py +2 -2
- kash/config/logger.py +59 -56
- kash/config/logger_basic.py +3 -3
- kash/config/settings.py +116 -57
- kash/config/setup.py +28 -12
- kash/config/text_styles.py +3 -13
- kash/docs/load_api_docs.py +2 -1
- kash/docs/markdown/topics/a3_getting_started.md +3 -2
- kash/{concepts → embeddings}/text_similarity.py +2 -2
- kash/exec/__init__.py +20 -3
- kash/exec/action_decorators.py +18 -4
- kash/exec/action_exec.py +41 -23
- kash/exec/action_registry.py +13 -48
- kash/exec/command_registry.py +2 -1
- kash/exec/fetch_url_metadata.py +4 -6
- kash/exec/importing.py +56 -0
- kash/exec/llm_transforms.py +6 -7
- kash/exec/precondition_registry.py +2 -1
- kash/exec/preconditions.py +16 -1
- kash/exec/shell_callable_action.py +33 -19
- kash/file_storage/file_store.py +23 -10
- kash/file_storage/item_file_format.py +5 -2
- kash/file_storage/metadata_dirs.py +11 -2
- kash/help/assistant.py +1 -1
- kash/help/assistant_instructions.py +2 -1
- kash/help/help_embeddings.py +2 -2
- kash/help/help_printing.py +7 -11
- kash/llm_utils/clean_headings.py +1 -1
- kash/llm_utils/llm_api_keys.py +4 -4
- kash/llm_utils/llm_features.py +68 -0
- kash/llm_utils/llm_messages.py +1 -2
- kash/llm_utils/llm_names.py +1 -1
- kash/llm_utils/llms.py +8 -3
- kash/local_server/__init__.py +5 -2
- kash/local_server/local_server.py +8 -5
- kash/local_server/local_server_commands.py +2 -2
- kash/local_server/local_url_formatters.py +1 -1
- kash/mcp/__init__.py +5 -2
- kash/mcp/mcp_cli.py +5 -5
- kash/mcp/mcp_server_commands.py +5 -5
- kash/mcp/mcp_server_routes.py +5 -5
- kash/mcp/mcp_server_sse.py +4 -2
- kash/media_base/media_cache.py +8 -8
- kash/media_base/media_services.py +1 -1
- kash/media_base/media_tools.py +6 -6
- kash/media_base/services/local_file_media.py +2 -2
- kash/media_base/{speech_transcription.py → transcription_deepgram.py} +25 -110
- kash/media_base/transcription_format.py +73 -0
- kash/media_base/transcription_whisper.py +38 -0
- kash/model/__init__.py +73 -5
- kash/model/actions_model.py +38 -4
- kash/model/concept_model.py +30 -0
- kash/model/items_model.py +44 -7
- kash/model/params_model.py +24 -0
- kash/shell/completions/completion_scoring.py +37 -5
- kash/shell/output/kerm_codes.py +1 -2
- kash/shell/output/shell_formatting.py +14 -4
- kash/shell/shell_main.py +2 -2
- kash/shell/utils/exception_printing.py +6 -0
- kash/shell/utils/native_utils.py +26 -20
- kash/text_handling/custom_sliding_transforms.py +12 -4
- kash/text_handling/doc_normalization.py +6 -2
- kash/text_handling/markdown_render.py +117 -0
- kash/text_handling/markdown_utils.py +204 -0
- kash/utils/common/import_utils.py +12 -3
- kash/utils/common/type_utils.py +0 -29
- kash/utils/common/url.py +27 -3
- kash/utils/errors.py +6 -0
- kash/utils/file_utils/file_formats.py +2 -2
- kash/utils/file_utils/file_formats_model.py +3 -0
- kash/web_content/dir_store.py +1 -2
- kash/web_content/file_cache_utils.py +37 -10
- kash/web_content/file_processing.py +68 -0
- kash/web_content/local_file_cache.py +12 -9
- kash/web_content/web_extract.py +8 -3
- kash/web_content/web_fetch.py +12 -4
- kash/web_gen/tabbed_webpage.py +5 -2
- kash/web_gen/templates/base_styles.css.jinja +120 -14
- kash/web_gen/templates/base_webpage.html.jinja +60 -13
- kash/web_gen/templates/content_styles.css.jinja +4 -2
- kash/web_gen/templates/item_view.html.jinja +2 -2
- kash/web_gen/templates/tabbed_webpage.html.jinja +1 -2
- kash/workspaces/__init__.py +15 -2
- kash/workspaces/selections.py +18 -3
- kash/workspaces/source_items.py +0 -1
- kash/workspaces/workspaces.py +5 -11
- kash/xonsh_custom/command_nl_utils.py +40 -19
- kash/xonsh_custom/custom_shell.py +43 -11
- kash/xonsh_custom/customize_prompt.py +39 -21
- kash/xonsh_custom/load_into_xonsh.py +22 -25
- kash/xonsh_custom/shell_load_commands.py +2 -2
- kash/xonsh_custom/xonsh_completers.py +2 -249
- kash/xonsh_custom/xonsh_keybindings.py +282 -0
- kash/xonsh_custom/xonsh_modern_tools.py +3 -3
- kash/xontrib/kash_extension.py +5 -6
- {kash_shell-0.3.9.dist-info → kash_shell-0.3.10.dist-info}/METADATA +8 -6
- {kash_shell-0.3.9.dist-info → kash_shell-0.3.10.dist-info}/RECORD +122 -123
- kash/concepts/concept_formats.py +0 -23
- kash/shell/clideps/api_keys.py +0 -100
- kash/shell/clideps/dotenv_setup.py +0 -115
- kash/shell/clideps/dotenv_utils.py +0 -98
- kash/shell/clideps/pkg_deps.py +0 -257
- kash/shell/clideps/platforms.py +0 -11
- kash/shell/clideps/terminal_features.py +0 -56
- kash/shell/utils/osc_utils.py +0 -95
- kash/shell/utils/terminal_images.py +0 -133
- kash/text_handling/markdown_util.py +0 -167
- kash/utils/common/atomic_var.py +0 -171
- kash/utils/common/string_replace.py +0 -93
- kash/utils/common/string_template.py +0 -101
- /kash/{concepts → embeddings}/cosine.py +0 -0
- /kash/{concepts → embeddings}/embeddings.py +0 -0
- {kash_shell-0.3.9.dist-info → kash_shell-0.3.10.dist-info}/WHEEL +0 -0
- {kash_shell-0.3.9.dist-info → kash_shell-0.3.10.dist-info}/entry_points.txt +0 -0
- {kash_shell-0.3.9.dist-info → kash_shell-0.3.10.dist-info}/licenses/LICENSE +0 -0
kash/workspaces/workspaces.py
CHANGED
|
@@ -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,
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
9
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
34
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
191
|
-
|
|
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
|
-
|
|
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":
|
|
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
|
-
|
|
46
|
-
|
|
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
|
-
|
|
53
|
-
|
|
56
|
+
cwd_short_str = cwd_str
|
|
57
|
+
elif cwd_in_home:
|
|
54
58
|
rel_to_home = cwd.relative_to(Path.home())
|
|
55
|
-
if
|
|
56
|
-
cwd_str = "
|
|
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
|
-
|
|
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,
|
|
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
|
-
#
|
|
171
|
-
|
|
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,
|
|
174
|
-
+ text_with_tooltip(
|
|
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
|
|
180
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
2
|
+
from kash.config.setup import kash_setup
|
|
3
3
|
from kash.config.text_styles import COLOR_VALUE, STYLE_HINT
|
|
4
4
|
|
|
5
|
-
|
|
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
|