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.
- 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 +15 -2
- kash/commands/base/general_commands.py +27 -18
- kash/commands/base/logs_commands.py +1 -4
- 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 +19 -16
- kash/config/colors.py +2 -0
- kash/config/env_settings.py +72 -0
- kash/config/init.py +2 -2
- kash/config/logger.py +61 -59
- kash/config/logger_basic.py +12 -5
- kash/config/server_config.py +6 -6
- kash/config/settings.py +117 -67
- kash/config/setup.py +35 -9
- kash/config/suppress_warnings.py +30 -12
- kash/config/text_styles.py +3 -13
- kash/docs/load_api_docs.py +2 -1
- kash/docs/markdown/topics/a2_installation.md +7 -3
- kash/docs/markdown/topics/a3_getting_started.md +3 -2
- kash/docs/markdown/warning.md +3 -8
- kash/docs/markdown/welcome.md +4 -0
- kash/docs_base/load_recipe_snippets.py +1 -1
- kash/docs_base/recipes/{general_system_commands.ksh → general_system_commands.sh} +1 -1
- kash/{concepts → embeddings}/cosine.py +2 -1
- kash/embeddings/text_similarity.py +57 -0
- 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 -6
- 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 -14
- kash/file_storage/item_file_format.py +13 -3
- kash/file_storage/metadata_dirs.py +11 -2
- kash/help/assistant.py +2 -2
- kash/help/assistant_instructions.py +2 -1
- kash/help/help_embeddings.py +2 -2
- kash/help/help_printing.py +14 -10
- kash/help/tldr_help.py +5 -3
- kash/llm_utils/clean_headings.py +1 -1
- kash/llm_utils/llm_api_keys.py +4 -4
- kash/llm_utils/llm_completion.py +2 -2
- 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 +17 -12
- kash/local_server/__init__.py +5 -2
- kash/local_server/local_server.py +56 -46
- kash/local_server/local_server_commands.py +15 -15
- kash/local_server/local_server_routes.py +2 -2
- kash/local_server/local_url_formatters.py +1 -1
- kash/mcp/__init__.py +5 -2
- kash/mcp/mcp_cli.py +54 -17
- kash/mcp/mcp_server_commands.py +5 -6
- kash/mcp/mcp_server_routes.py +14 -11
- kash/mcp/mcp_server_sse.py +61 -34
- kash/mcp/mcp_server_stdio.py +0 -8
- kash/media_base/audio_processing.py +81 -7
- kash/media_base/media_cache.py +18 -18
- 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 -109
- 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 +56 -13
- 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 +80 -28
- kash/utils/errors.py +6 -0
- kash/utils/file_utils/{dir_size.py → dir_info.py} +25 -4
- kash/utils/file_utils/file_ext.py +2 -3
- kash/utils/file_utils/file_formats.py +28 -2
- kash/utils/file_utils/file_formats_model.py +50 -19
- kash/utils/file_utils/filename_parsing.py +10 -4
- 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 +4 -2
- kash/workspaces/workspace_output.py +11 -4
- kash/workspaces/workspaces.py +5 -11
- kash/xonsh_custom/command_nl_utils.py +40 -19
- kash/xonsh_custom/custom_shell.py +44 -12
- kash/xonsh_custom/customize_prompt.py +39 -21
- kash/xonsh_custom/load_into_xonsh.py +26 -27
- 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.8.dist-info → kash_shell-0.3.10.dist-info}/METADATA +26 -12
- {kash_shell-0.3.8.dist-info → kash_shell-0.3.10.dist-info}/RECORD +140 -140
- {kash_shell-0.3.8.dist-info → kash_shell-0.3.10.dist-info}/entry_points.txt +1 -1
- kash/concepts/concept_formats.py +0 -23
- kash/concepts/text_similarity.py +0 -112
- kash/shell/clideps/api_keys.py +0 -99
- kash/shell/clideps/dotenv_setup.py +0 -114
- kash/shell/clideps/dotenv_utils.py +0 -89
- kash/shell/clideps/pkg_deps.py +0 -232
- 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 -158
- kash/utils/common/string_replace.py +0 -93
- kash/utils/common/string_template.py +0 -101
- /kash/docs_base/recipes/{python_dev_commands.ksh → python_dev_commands.sh} +0 -0
- /kash/docs_base/recipes/{tldr_standard_commands.ksh → tldr_standard_commands.sh} +0 -0
- /kash/{concepts → embeddings}/embeddings.py +0 -0
- {kash_shell-0.3.8.dist-info → kash_shell-0.3.10.dist-info}/WHEEL +0 -0
- {kash_shell-0.3.8.dist-info → kash_shell-0.3.10.dist-info}/licenses/LICENSE +0 -0
|
@@ -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
|
|
@@ -445,7 +477,7 @@ def start_shell(single_command: str | None = None, ready_event: threading.Event
|
|
|
445
477
|
|
|
446
478
|
# If we want to replicate all the xonsh settings including .xonshrc, we could call
|
|
447
479
|
# start_services(). It may be problematic to support all xonsh enhancements, however,
|
|
448
|
-
# so let's only load ~/.kashrc files.
|
|
480
|
+
# so let's only load ~/.config/kash/kashrc files.
|
|
449
481
|
load_rcfiles(execer, ctx)
|
|
450
482
|
|
|
451
483
|
# Imports are so slow we will need to improve this. Let's time it.
|
|
@@ -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,51 +1,49 @@
|
|
|
1
|
-
from kash.config.setup import
|
|
2
|
-
from kash.config.text_styles import LOGO_NAME, STYLE_HINT
|
|
3
|
-
from kash.xonsh_custom.shell_load_commands import (
|
|
4
|
-
is_interactive,
|
|
5
|
-
log_command_action_info,
|
|
6
|
-
reload_shell_commands_and_actions,
|
|
7
|
-
set_env,
|
|
8
|
-
)
|
|
1
|
+
from kash.config.setup import kash_setup
|
|
9
2
|
|
|
10
|
-
|
|
3
|
+
kash_setup(rich_logging=True) # Set up logging first.
|
|
11
4
|
|
|
12
5
|
import time
|
|
13
6
|
|
|
7
|
+
from clideps.pkgs.pkg_check import pkg_check
|
|
14
8
|
from xonsh.built_ins import XSH
|
|
15
9
|
from xonsh.prompt.base import PromptFields
|
|
16
10
|
|
|
17
11
|
from kash.commands.base.general_commands import self_check
|
|
18
|
-
from kash.commands.help import
|
|
12
|
+
from kash.commands.help.welcome import welcome
|
|
19
13
|
from kash.config.logger import get_logger
|
|
20
|
-
from kash.config.settings import check_kerm_code_support
|
|
21
|
-
from kash.
|
|
14
|
+
from kash.config.settings import RECOMMENDED_PKGS, check_kerm_code_support
|
|
15
|
+
from kash.config.text_styles import LOGO_NAME, STYLE_HINT
|
|
16
|
+
from kash.local_server.local_server import start_ui_server
|
|
22
17
|
from kash.local_server.local_url_formatters import enable_local_urls
|
|
23
|
-
from kash.
|
|
18
|
+
from kash.mcp.mcp_server_commands import start_mcp_server
|
|
24
19
|
from kash.shell.output.shell_output import PrintHooks, cprint
|
|
25
20
|
from kash.shell.version import get_version_tag
|
|
26
21
|
from kash.workspaces import current_ws
|
|
27
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
|
+
)
|
|
28
29
|
from kash.xonsh_custom.xonsh_completers import load_completers
|
|
30
|
+
from kash.xonsh_custom.xonsh_keybindings import add_key_bindings
|
|
29
31
|
from kash.xonsh_custom.xonsh_modern_tools import modernize_shell
|
|
30
32
|
|
|
31
33
|
log = get_logger(__name__)
|
|
32
34
|
|
|
33
35
|
|
|
34
|
-
def _kash_workspace_str() -> str:
|
|
35
|
-
return get_prompt_info().workspace_name
|
|
36
|
-
|
|
37
|
-
|
|
38
36
|
def _shell_interactive_setup():
|
|
39
|
-
from kash.xonsh_custom.xonsh_completers import add_key_bindings
|
|
40
|
-
|
|
41
37
|
# Set up a prompt field for the workspace string.
|
|
42
38
|
fields = PromptFields(XSH)
|
|
43
|
-
|
|
39
|
+
prompt_info = get_prompt_info()
|
|
40
|
+
fields["workspace_str"] = prompt_info.workspace_name
|
|
41
|
+
fields["cwd_short_str"] = prompt_info.cwd_short_str
|
|
44
42
|
set_env("PROMPT_FIELDS", fields)
|
|
45
43
|
|
|
46
44
|
# Set up the prompt and title template.
|
|
47
45
|
set_env("PROMPT", kash_xonsh_prompt)
|
|
48
|
-
set_env("TITLE", LOGO_NAME + " - {workspace_str}")
|
|
46
|
+
set_env("TITLE", LOGO_NAME + " - {workspace_str} - {cwd_short_str}")
|
|
49
47
|
|
|
50
48
|
add_key_bindings()
|
|
51
49
|
|
|
@@ -58,12 +56,12 @@ def load_into_xonsh():
|
|
|
58
56
|
"""
|
|
59
57
|
|
|
60
58
|
if is_interactive():
|
|
59
|
+
# Do welcome first since init could take a few seconds.
|
|
60
|
+
welcome()
|
|
61
|
+
|
|
61
62
|
# Do first so in case there is an error, the shell prompt etc works as expected.
|
|
62
63
|
_shell_interactive_setup()
|
|
63
64
|
|
|
64
|
-
# Then do welcome first since init could take a few seconds.
|
|
65
|
-
doc_commands.welcome()
|
|
66
|
-
|
|
67
65
|
def load():
|
|
68
66
|
load_start_time = time.time()
|
|
69
67
|
|
|
@@ -91,20 +89,21 @@ def load_into_xonsh():
|
|
|
91
89
|
# Currently only Kerm supports our advanced UI with Kerm codes.
|
|
92
90
|
supports_kerm_codes = check_kerm_code_support()
|
|
93
91
|
if supports_kerm_codes:
|
|
94
|
-
|
|
92
|
+
start_ui_server()
|
|
95
93
|
enable_local_urls(True)
|
|
96
94
|
else:
|
|
97
95
|
cprint(
|
|
98
|
-
"If your terminal supports it, you may use `
|
|
96
|
+
"If your terminal supports it, you may use `start_ui_server` to enable local links.",
|
|
99
97
|
style=STYLE_HINT,
|
|
100
98
|
)
|
|
99
|
+
start_mcp_server()
|
|
101
100
|
|
|
102
101
|
cprint()
|
|
103
102
|
log_command_action_info()
|
|
104
103
|
|
|
105
104
|
current_ws() # Validates and logs info for user.
|
|
106
105
|
|
|
107
|
-
pkg_check().warn_if_missing()
|
|
106
|
+
pkg_check().warn_if_missing(*RECOMMENDED_PKGS)
|
|
108
107
|
|
|
109
108
|
else:
|
|
110
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
|
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
import re
|
|
2
1
|
from dataclasses import dataclass
|
|
3
2
|
from typing import Any, cast
|
|
4
3
|
|
|
5
4
|
from funlog import log_calls
|
|
6
|
-
from
|
|
7
|
-
from prompt_toolkit.application import get_app
|
|
8
|
-
from prompt_toolkit.filters import Condition
|
|
9
|
-
from prompt_toolkit.key_binding import KeyBindings, merge_key_bindings
|
|
10
|
-
from prompt_toolkit.key_binding.key_processor import KeyPressEvent
|
|
11
|
-
from prompt_toolkit.shortcuts import print_formatted_text
|
|
5
|
+
from strif import AtomicVar
|
|
12
6
|
from xonsh.built_ins import XSH
|
|
13
7
|
from xonsh.completers.completer import add_one_completer
|
|
14
8
|
from xonsh.completers.tools import (
|
|
@@ -19,7 +13,6 @@ from xonsh.completers.tools import (
|
|
|
19
13
|
)
|
|
20
14
|
from xonsh.parsers.completion_context import CommandContext
|
|
21
15
|
|
|
22
|
-
from kash.actions.core.assistant_chat import assistant_chat
|
|
23
16
|
from kash.config.logger import get_logger
|
|
24
17
|
from kash.exec.action_registry import get_all_actions_defaults
|
|
25
18
|
from kash.exec.command_registry import get_all_commands
|
|
@@ -34,8 +27,6 @@ from kash.shell.completions.shell_completions import (
|
|
|
34
27
|
get_std_command_completions,
|
|
35
28
|
trace_completions,
|
|
36
29
|
)
|
|
37
|
-
from kash.shell.ui.shell_syntax import assist_request_str
|
|
38
|
-
from kash.utils.common.atomic_var import AtomicVar
|
|
39
30
|
from kash.utils.errors import ApiResultError, InvalidState
|
|
40
31
|
from kash.xonsh_custom.command_nl_utils import as_nl_words, looks_like_nl
|
|
41
32
|
from kash.xonsh_custom.shell_which import is_valid_command
|
|
@@ -351,7 +342,7 @@ def _params_for_command(command_name: str) -> list[Param[Any]] | None:
|
|
|
351
342
|
if command:
|
|
352
343
|
return annotate_param_info(command)
|
|
353
344
|
elif action:
|
|
354
|
-
return
|
|
345
|
+
return action.shell_params
|
|
355
346
|
else:
|
|
356
347
|
return None
|
|
357
348
|
|
|
@@ -482,244 +473,6 @@ def options_enum_completer(context: CompletionContext) -> CompleterResult:
|
|
|
482
473
|
# - `source_code`
|
|
483
474
|
|
|
484
475
|
|
|
485
|
-
@Condition
|
|
486
|
-
def is_unquoted_assist_request():
|
|
487
|
-
app = get_app()
|
|
488
|
-
buf = app.current_buffer
|
|
489
|
-
text = buf.text.strip()
|
|
490
|
-
is_default_buffer = buf.name == "DEFAULT_BUFFER"
|
|
491
|
-
has_prefix = text.startswith("?") and not (text.startswith('? "') or text.startswith("? '"))
|
|
492
|
-
return is_default_buffer and has_prefix
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
_command_regex = re.compile(r"^[a-zA-Z0-9_-]+$")
|
|
496
|
-
|
|
497
|
-
_python_keyword_regex = re.compile(
|
|
498
|
-
r"assert|async|await|break|class|continue|def|del|elif|else|except|finally|"
|
|
499
|
-
r"for|from|global|if|import|lambda|nonlocal|pass|raise|return|try|while|with|yield"
|
|
500
|
-
)
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
def _extract_command_name(text: str) -> str | None:
|
|
504
|
-
text = text.split()[0]
|
|
505
|
-
if _python_keyword_regex.match(text):
|
|
506
|
-
return None
|
|
507
|
-
if _command_regex.match(text):
|
|
508
|
-
return text
|
|
509
|
-
return None
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
@Condition
|
|
513
|
-
def whitespace_only() -> bool:
|
|
514
|
-
app = get_app()
|
|
515
|
-
buf = app.current_buffer
|
|
516
|
-
return not buf.text.strip()
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
@Condition
|
|
520
|
-
def is_typo_command() -> bool:
|
|
521
|
-
"""
|
|
522
|
-
Is the command itself invalid? Should be conservative, so we can suppress
|
|
523
|
-
executing it if it is definitely a typo.
|
|
524
|
-
"""
|
|
525
|
-
|
|
526
|
-
app = get_app()
|
|
527
|
-
buf = app.current_buffer
|
|
528
|
-
text = buf.text.strip()
|
|
529
|
-
|
|
530
|
-
is_default_buffer = buf.name == "DEFAULT_BUFFER"
|
|
531
|
-
if not is_default_buffer:
|
|
532
|
-
return False
|
|
533
|
-
|
|
534
|
-
# Assistant NL requests always allowed.
|
|
535
|
-
has_assistant_prefix = text.startswith("?") or text.rstrip().endswith("?")
|
|
536
|
-
if has_assistant_prefix:
|
|
537
|
-
return False
|
|
538
|
-
|
|
539
|
-
# Anything more complex is probably Python.
|
|
540
|
-
# TODO: Do a better syntax parse of this as Python, or use xonsh's algorithm.
|
|
541
|
-
for s in ["\n", "(", ")"]:
|
|
542
|
-
if s in text:
|
|
543
|
-
return False
|
|
544
|
-
|
|
545
|
-
# Empty command line allowed.
|
|
546
|
-
if not text:
|
|
547
|
-
return False
|
|
548
|
-
|
|
549
|
-
# Now look at the command.
|
|
550
|
-
command_name = _extract_command_name(text)
|
|
551
|
-
|
|
552
|
-
# Python or missing command is fine.
|
|
553
|
-
if not command_name:
|
|
554
|
-
return False
|
|
555
|
-
|
|
556
|
-
# Recognized command.
|
|
557
|
-
if is_valid_command(command_name):
|
|
558
|
-
return False
|
|
559
|
-
|
|
560
|
-
# Okay it's almost certainly a command typo.
|
|
561
|
-
return True
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
@Condition
|
|
565
|
-
def is_completion_menu_active() -> bool:
|
|
566
|
-
app = get_app()
|
|
567
|
-
return app.current_buffer.complete_state is not None
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
@Condition
|
|
571
|
-
def could_show_more_tab_completions() -> bool:
|
|
572
|
-
return _MULTI_TAB_STATE.value.could_show_more()
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
# Set up prompt_toolkit key bindings.
|
|
576
|
-
def add_key_bindings() -> None:
|
|
577
|
-
custom_bindings = KeyBindings()
|
|
578
|
-
|
|
579
|
-
# Need to be careful only to bind with a filter if the state is suitable.
|
|
580
|
-
# Only add more completions if we've seen some results but user hasn't pressed
|
|
581
|
-
# tab a second time yet. Otherwise the behavior should fall back to usual ptk
|
|
582
|
-
# tab behavior (selecting each completion one by one).
|
|
583
|
-
@custom_bindings.add("tab", filter=could_show_more_tab_completions)
|
|
584
|
-
def _(event: KeyPressEvent):
|
|
585
|
-
"""
|
|
586
|
-
Add a second tab to show more completions.
|
|
587
|
-
"""
|
|
588
|
-
with _MULTI_TAB_STATE.updates() as state:
|
|
589
|
-
state.more_results_requested = True
|
|
590
|
-
|
|
591
|
-
trace_completions("More completion results requested", state)
|
|
592
|
-
|
|
593
|
-
# Restart completions.
|
|
594
|
-
buf = event.app.current_buffer
|
|
595
|
-
buf.complete_state = None
|
|
596
|
-
buf.start_completion()
|
|
597
|
-
|
|
598
|
-
@custom_bindings.add("s-tab")
|
|
599
|
-
def _(event: KeyPressEvent):
|
|
600
|
-
with _MULTI_TAB_STATE.updates() as state:
|
|
601
|
-
state.more_results_requested = True
|
|
602
|
-
|
|
603
|
-
trace_completions("More completion results requested", state)
|
|
604
|
-
|
|
605
|
-
# Restart completions.
|
|
606
|
-
buf = event.app.current_buffer
|
|
607
|
-
buf.complete_state = None
|
|
608
|
-
buf.start_completion()
|
|
609
|
-
|
|
610
|
-
@custom_bindings.add(" ", filter=whitespace_only)
|
|
611
|
-
def _(event: KeyPressEvent):
|
|
612
|
-
"""
|
|
613
|
-
Map space at the start of the line to `? ` to invoke an assistant question.
|
|
614
|
-
"""
|
|
615
|
-
buf = event.app.current_buffer
|
|
616
|
-
if buf.text == " " or buf.text == "":
|
|
617
|
-
buf.delete_before_cursor(len(buf.text))
|
|
618
|
-
buf.insert_text("? ")
|
|
619
|
-
else:
|
|
620
|
-
buf.insert_text(" ")
|
|
621
|
-
|
|
622
|
-
@custom_bindings.add(" ", filter=is_typo_command)
|
|
623
|
-
def _(event: KeyPressEvent):
|
|
624
|
-
"""
|
|
625
|
-
If the user types two words and the first word is likely an invalid
|
|
626
|
-
command, jump back to prefix the whole line with `? ` to make it clear we're
|
|
627
|
-
in natural language mode.
|
|
628
|
-
"""
|
|
629
|
-
|
|
630
|
-
buf = event.app.current_buffer
|
|
631
|
-
text = buf.text.strip()
|
|
632
|
-
|
|
633
|
-
if (
|
|
634
|
-
buf.cursor_position == len(buf.text)
|
|
635
|
-
and len(text.split()) >= 2
|
|
636
|
-
and not text.startswith("?")
|
|
637
|
-
):
|
|
638
|
-
buf.transform_current_line(lambda line: "? " + line)
|
|
639
|
-
buf.cursor_position += 2
|
|
640
|
-
|
|
641
|
-
buf.insert_text(" ")
|
|
642
|
-
|
|
643
|
-
@custom_bindings.add("enter", filter=whitespace_only)
|
|
644
|
-
def _(_event: KeyPressEvent):
|
|
645
|
-
"""
|
|
646
|
-
Suppress enter if the command line is empty, but add a newline above the prompt.
|
|
647
|
-
"""
|
|
648
|
-
print_formatted_text("")
|
|
649
|
-
|
|
650
|
-
@custom_bindings.add("enter", filter=is_unquoted_assist_request)
|
|
651
|
-
def _(event: KeyPressEvent):
|
|
652
|
-
"""
|
|
653
|
-
Automatically add quotes around assistant questions, so there are not
|
|
654
|
-
syntax errors if the command line contains unclosed quotes etc.
|
|
655
|
-
"""
|
|
656
|
-
|
|
657
|
-
buf = event.app.current_buffer
|
|
658
|
-
text = buf.text.strip()
|
|
659
|
-
|
|
660
|
-
question_text = text[1:].strip()
|
|
661
|
-
if not question_text:
|
|
662
|
-
# If the user enters an empty assistant request, treat it as a shortcut to go to the assistant chat.
|
|
663
|
-
buf.delete_before_cursor(len(buf.text))
|
|
664
|
-
buf.insert_text(assistant_chat.__name__)
|
|
665
|
-
else:
|
|
666
|
-
# Convert it to an assistant question starting with a `?`.
|
|
667
|
-
buf.delete_before_cursor(len(buf.text))
|
|
668
|
-
buf.insert_text(assist_request_str(question_text))
|
|
669
|
-
|
|
670
|
-
buf.validate_and_handle()
|
|
671
|
-
|
|
672
|
-
@custom_bindings.add("enter", filter=is_typo_command)
|
|
673
|
-
def _(event: KeyPressEvent):
|
|
674
|
-
"""
|
|
675
|
-
Suppress enter and if possible give completions if the command is just not a valid command.
|
|
676
|
-
"""
|
|
677
|
-
|
|
678
|
-
buf = event.app.current_buffer
|
|
679
|
-
buf.start_completion()
|
|
680
|
-
|
|
681
|
-
# TODO: Also suppress enter if a command or action doesn't meet the required args,
|
|
682
|
-
# selection, or preconditions.
|
|
683
|
-
# Perhaps also have a way to get confirmation if its a rarely used or unexpected command
|
|
684
|
-
# (based on history/suggestions).
|
|
685
|
-
# TODO: Add suggested replacements, e.g. df -> duf, top -> btm, etc.
|
|
686
|
-
|
|
687
|
-
@custom_bindings.add("@")
|
|
688
|
-
def _(event: KeyPressEvent):
|
|
689
|
-
"""
|
|
690
|
-
Auto-trigger item completions after `@` sign.
|
|
691
|
-
"""
|
|
692
|
-
buf = event.app.current_buffer
|
|
693
|
-
buf.insert_text("@")
|
|
694
|
-
buf.start_completion()
|
|
695
|
-
|
|
696
|
-
@custom_bindings.add("escape", eager=True, filter=is_completion_menu_active)
|
|
697
|
-
def _(event: KeyPressEvent):
|
|
698
|
-
"""
|
|
699
|
-
Close the completion menu when escape is pressed.
|
|
700
|
-
"""
|
|
701
|
-
event.app.current_buffer.cancel_completion()
|
|
702
|
-
|
|
703
|
-
@custom_bindings.add("c-c", eager=True)
|
|
704
|
-
def _(event):
|
|
705
|
-
"""
|
|
706
|
-
Control-C to reset the current buffer. Similar to usual behavior but doesn't
|
|
707
|
-
leave ugly prompt chars.
|
|
708
|
-
"""
|
|
709
|
-
print_formatted_text("")
|
|
710
|
-
buf = event.app.current_buffer
|
|
711
|
-
# Abort reverse search/filtering, clear any selection, and reset the buffer.
|
|
712
|
-
search.stop_search()
|
|
713
|
-
buf.exit_selection()
|
|
714
|
-
buf.reset()
|
|
715
|
-
|
|
716
|
-
existing_bindings = __xonsh__.shell.shell.prompter.app.key_bindings # noqa: F821 # pyright: ignore[reportUndefinedVariable]
|
|
717
|
-
merged_bindings = merge_key_bindings([existing_bindings, custom_bindings])
|
|
718
|
-
__xonsh__.shell.shell.prompter.app.key_bindings = merged_bindings # noqa: F821 # pyright: ignore[reportUndefinedVariable]
|
|
719
|
-
|
|
720
|
-
log.info("Added custom %s key bindings.", len(merged_bindings.bindings))
|
|
721
|
-
|
|
722
|
-
|
|
723
476
|
def load_completers():
|
|
724
477
|
add_one_completer("command_completer", command_completer, "start")
|
|
725
478
|
add_one_completer("recommended_shell_completer", recommended_shell_completer, "start")
|