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/exec/preconditions.py
CHANGED
|
@@ -7,7 +7,7 @@ from chopdiff.html import has_timestamp
|
|
|
7
7
|
|
|
8
8
|
from kash.exec.precondition_registry import kash_precondition
|
|
9
9
|
from kash.model.items_model import Item, ItemType
|
|
10
|
-
from kash.text_handling.
|
|
10
|
+
from kash.text_handling.markdown_utils import extract_bullet_points
|
|
11
11
|
from kash.utils.file_utils.file_formats_model import Format
|
|
12
12
|
|
|
13
13
|
|
|
@@ -16,6 +16,21 @@ def is_resource(item: Item) -> bool:
|
|
|
16
16
|
return item.type == ItemType.resource
|
|
17
17
|
|
|
18
18
|
|
|
19
|
+
@kash_precondition
|
|
20
|
+
def is_doc_resource(item: Item) -> bool:
|
|
21
|
+
return bool(is_resource(item) and item.format and item.format.is_doc)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@kash_precondition
|
|
25
|
+
def is_html_resource(item: Item) -> bool:
|
|
26
|
+
return bool(is_resource(item) and item.format and item.format == Format.html)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@kash_precondition
|
|
30
|
+
def is_docx_resource(item: Item) -> bool:
|
|
31
|
+
return bool(is_resource(item) and item.format and item.format == Format.docx)
|
|
32
|
+
|
|
33
|
+
|
|
19
34
|
@kash_precondition
|
|
20
35
|
def is_concept(item: Item) -> bool:
|
|
21
36
|
return item.type == ItemType.concept
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from funlog import log_tallies
|
|
2
2
|
|
|
3
|
+
from kash.config.env_settings import KashEnv
|
|
3
4
|
from kash.config.logger import get_console, get_logger
|
|
4
5
|
from kash.config.text_styles import COLOR_ERROR, SPINNER
|
|
5
6
|
from kash.exec.action_exec import run_action_with_shell_context
|
|
@@ -31,34 +32,43 @@ class ShellCallableAction:
|
|
|
31
32
|
def __call__(self, args: list[str]) -> ShellResult | None:
|
|
32
33
|
from kash.commands.help import help_commands
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
PrintHooks.before_shell_action_run()
|
|
35
|
+
log.debug("ShellCallableAction: %s: %s", self.action_cls.name, args)
|
|
36
36
|
|
|
37
|
-
shell_args = parse_shell_args(args)
|
|
38
|
-
|
|
39
|
-
# We will instantiate the action later but we create an unconfigured
|
|
40
|
-
# instance for help/info.
|
|
41
|
-
action = action_cls.create(None)
|
|
42
|
-
if shell_args.show_help:
|
|
43
|
-
print_action_help(action, verbose=True)
|
|
44
|
-
return ShellResult()
|
|
45
|
-
elif shell_args.options.get("show_source", False):
|
|
46
|
-
return help_commands.source_code(action_cls.name)
|
|
47
|
-
|
|
48
|
-
# Handle --rerun option at action invocation time.
|
|
49
|
-
rerun = bool(shell_args.options.get("rerun", False))
|
|
50
|
-
|
|
51
|
-
log.info("Action shell args: %s", shell_args)
|
|
52
37
|
try:
|
|
38
|
+
action_cls = self.action_cls
|
|
39
|
+
PrintHooks.before_shell_action_run()
|
|
40
|
+
|
|
41
|
+
shell_args = parse_shell_args(args)
|
|
42
|
+
|
|
43
|
+
# We will instantiate the action later but we create an unconfigured
|
|
44
|
+
# instance for help/info.
|
|
45
|
+
action = action_cls.create(None)
|
|
46
|
+
if shell_args.show_help:
|
|
47
|
+
print_action_help(action, verbose=True)
|
|
48
|
+
return ShellResult()
|
|
49
|
+
elif shell_args.options.get("show_source", False):
|
|
50
|
+
return help_commands.source_code(action_cls.name)
|
|
51
|
+
|
|
52
|
+
# Handle --rerun and --refetch options at action invocation time.
|
|
53
|
+
rerun = bool(shell_args.options.get("rerun", False))
|
|
54
|
+
refetch = bool(shell_args.options.get("refetch", False))
|
|
55
|
+
no_format = bool(shell_args.options.get("no_format", False))
|
|
56
|
+
|
|
57
|
+
log.info("Action shell args: %s", shell_args)
|
|
53
58
|
explicit_values = RawParamValues(shell_args.options)
|
|
54
59
|
if not action.interactive_input:
|
|
55
60
|
with get_console().status(f"Running action {action.name}…", spinner=SPINNER):
|
|
56
61
|
result = run_action_with_shell_context(
|
|
57
|
-
action_cls,
|
|
62
|
+
action_cls,
|
|
63
|
+
explicit_values,
|
|
64
|
+
*shell_args.args,
|
|
65
|
+
rerun=rerun,
|
|
66
|
+
refetch=refetch,
|
|
67
|
+
no_format=no_format,
|
|
58
68
|
)
|
|
59
69
|
else:
|
|
60
70
|
result = run_action_with_shell_context(
|
|
61
|
-
action_cls, explicit_values, *shell_args.args, rerun=rerun
|
|
71
|
+
action_cls, explicit_values, *shell_args.args, rerun=rerun, refetch=refetch
|
|
62
72
|
)
|
|
63
73
|
# We don't return the result to keep the xonsh shell output clean.
|
|
64
74
|
except NONFATAL_EXCEPTIONS as e:
|
|
@@ -66,6 +76,10 @@ class ShellCallableAction:
|
|
|
66
76
|
log.error(f"[{COLOR_ERROR}]Action error:[/{COLOR_ERROR}] %s", summarize_traceback(e))
|
|
67
77
|
log.info("Action error details: %s", e, exc_info=True)
|
|
68
78
|
return ShellResult(exception=e)
|
|
79
|
+
except Exception as e:
|
|
80
|
+
# Log here while we are in the true call stack (not inside the xonsh call stack).
|
|
81
|
+
log.error("Action error: %s", e, exc_info=KashEnv.KASH_SHOW_TRACEBACK.read_bool(True))
|
|
82
|
+
raise
|
|
69
83
|
finally:
|
|
70
84
|
log_tallies(level="warning", if_slower_than=10.0)
|
|
71
85
|
# output_separator()
|
kash/file_storage/file_store.py
CHANGED
|
@@ -270,10 +270,10 @@ class FileStore(Workspace):
|
|
|
270
270
|
return self._tmp_path_for(item), False, None
|
|
271
271
|
elif item.store_path:
|
|
272
272
|
return StorePath(item.store_path), True, None
|
|
273
|
-
elif item_id in self.id_map:
|
|
273
|
+
elif item_id in self.id_map and self.exists(self.id_map[item_id]):
|
|
274
274
|
# If this item has an identity and we've saved under that id before, use the same store path.
|
|
275
275
|
store_path = self.id_map[item_id]
|
|
276
|
-
log.
|
|
276
|
+
log.warning(
|
|
277
277
|
"Found existing item with same id:\n%s",
|
|
278
278
|
fmt_lines([fmt_loc(store_path), item_id]),
|
|
279
279
|
)
|
|
@@ -309,10 +309,16 @@ class FileStore(Workspace):
|
|
|
309
309
|
)
|
|
310
310
|
|
|
311
311
|
@log_calls()
|
|
312
|
-
def save(
|
|
312
|
+
def save(
|
|
313
|
+
self, item: Item, *, overwrite: bool = True, as_tmp: bool = False, no_format: bool = False
|
|
314
|
+
) -> StorePath:
|
|
313
315
|
"""
|
|
314
|
-
Save the item. Uses the store_path if it's already set or generates a new one.
|
|
315
|
-
Updates item.store_path
|
|
316
|
+
Save the item. Uses the `store_path` if it's already set or generates a new one.
|
|
317
|
+
Updates `item.store_path`.
|
|
318
|
+
|
|
319
|
+
If `as_tmp` is true, will save the item to a temporary file.
|
|
320
|
+
If `overwrite` is false, will skip saving if the item already exists.
|
|
321
|
+
If `no_format` is true, will not normalize body text formatting (for Markdown).
|
|
316
322
|
"""
|
|
317
323
|
# If external file already exists within the workspace, the file is already saved (without metadata).
|
|
318
324
|
external_path = item.external_path and Path(item.external_path).resolve()
|
|
@@ -348,7 +354,7 @@ class FileStore(Workspace):
|
|
|
348
354
|
if item.external_path:
|
|
349
355
|
copyfile_atomic(item.external_path, full_path)
|
|
350
356
|
else:
|
|
351
|
-
write_item(item, full_path)
|
|
357
|
+
write_item(item, full_path, normalize=not no_format)
|
|
352
358
|
except OSError as e:
|
|
353
359
|
log.error("Error saving item: %s", e)
|
|
354
360
|
try:
|
|
@@ -398,6 +404,7 @@ class FileStore(Workspace):
|
|
|
398
404
|
def import_item(
|
|
399
405
|
self,
|
|
400
406
|
locator: Locator,
|
|
407
|
+
*,
|
|
401
408
|
as_type: ItemType | None = None,
|
|
402
409
|
reimport: bool = False,
|
|
403
410
|
) -> StorePath:
|
|
@@ -496,7 +503,9 @@ class FileStore(Workspace):
|
|
|
496
503
|
as_type: ItemType | None = None,
|
|
497
504
|
reimport: bool = False,
|
|
498
505
|
) -> list[StorePath]:
|
|
499
|
-
return [
|
|
506
|
+
return [
|
|
507
|
+
self.import_item(locator, as_type=as_type, reimport=reimport) for locator in locators
|
|
508
|
+
]
|
|
500
509
|
|
|
501
510
|
def _filter_selection_paths(self):
|
|
502
511
|
"""
|
|
@@ -537,7 +546,7 @@ class FileStore(Workspace):
|
|
|
537
546
|
# TODO: Update metadata of all relations that point to this path too.
|
|
538
547
|
|
|
539
548
|
def archive(
|
|
540
|
-
self, store_path: StorePath, missing_ok: bool = False, quiet: bool = False
|
|
549
|
+
self, store_path: StorePath, *, missing_ok: bool = False, quiet: bool = False
|
|
541
550
|
) -> StorePath:
|
|
542
551
|
"""
|
|
543
552
|
Archive the item by moving it into the archive directory.
|
|
@@ -553,6 +562,9 @@ class FileStore(Workspace):
|
|
|
553
562
|
if missing_ok and not orig_path.exists():
|
|
554
563
|
log.message("Item to archive not found so moving on: %s", fmt_loc(orig_path))
|
|
555
564
|
return store_path
|
|
565
|
+
if not orig_path.exists():
|
|
566
|
+
log.warning("Item to archive not found: %s", fmt_loc(orig_path))
|
|
567
|
+
return store_path
|
|
556
568
|
move_file(orig_path, archive_path)
|
|
557
569
|
self._remove_references([store_path])
|
|
558
570
|
|
|
@@ -572,7 +584,7 @@ class FileStore(Workspace):
|
|
|
572
584
|
move_file(full_input_path, original_path)
|
|
573
585
|
return StorePath(store_path)
|
|
574
586
|
|
|
575
|
-
def log_workspace_info(self, once: bool = False):
|
|
587
|
+
def log_workspace_info(self, *, once: bool = False):
|
|
576
588
|
"""
|
|
577
589
|
Log helpful information about the workspace.
|
|
578
590
|
"""
|
|
@@ -603,7 +615,7 @@ class FileStore(Workspace):
|
|
|
603
615
|
|
|
604
616
|
if self.is_global_ws:
|
|
605
617
|
PrintHooks.spacer()
|
|
606
|
-
log.warning("Note you are currently using the default
|
|
618
|
+
log.warning("Note you are currently using the default global workspace.")
|
|
607
619
|
cprint(
|
|
608
620
|
"Create or switch to another workspace with the `workspace` command.",
|
|
609
621
|
style=STYLE_HINT,
|
|
@@ -615,6 +627,7 @@ class FileStore(Workspace):
|
|
|
615
627
|
def walk_items(
|
|
616
628
|
self,
|
|
617
629
|
store_path: StorePath | None = None,
|
|
630
|
+
*,
|
|
618
631
|
use_ignore: bool = True,
|
|
619
632
|
) -> Generator[StorePath, None, None]:
|
|
620
633
|
"""
|
|
@@ -22,7 +22,7 @@ _item_cache = MtimeCache[Item](max_size=2000, name="Item")
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
@tally_calls()
|
|
25
|
-
def write_item(item: Item, path: Path):
|
|
25
|
+
def write_item(item: Item, path: Path, normalize: bool = True):
|
|
26
26
|
"""
|
|
27
27
|
Write a text item to a file with standard frontmatter format YAML.
|
|
28
28
|
Also normalizes formatting of the body text.
|
|
@@ -36,7 +36,10 @@ def write_item(item: Item, path: Path):
|
|
|
36
36
|
# Clear cache before writing.
|
|
37
37
|
_item_cache.delete(path)
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
if normalize:
|
|
40
|
+
body = normalize_formatting_ansi(item.body_text(), item.format)
|
|
41
|
+
else:
|
|
42
|
+
body = item.body_text()
|
|
40
43
|
|
|
41
44
|
# Special case for YAML files to avoid a possible duplicate `---` divider in the body.
|
|
42
45
|
if body and item.format == Format.yaml:
|
|
@@ -7,7 +7,12 @@ from pathlib import Path
|
|
|
7
7
|
from pydantic.dataclasses import dataclass
|
|
8
8
|
|
|
9
9
|
from kash.config.logger import get_logger
|
|
10
|
-
from kash.config.settings import
|
|
10
|
+
from kash.config.settings import (
|
|
11
|
+
CONTENT_CACHE_NAME,
|
|
12
|
+
DOT_DIR,
|
|
13
|
+
MEDIA_CACHE_NAME,
|
|
14
|
+
global_settings,
|
|
15
|
+
)
|
|
11
16
|
from kash.file_storage.persisted_yaml import PersistedYaml
|
|
12
17
|
from kash.model.paths_model import StorePath
|
|
13
18
|
from kash.utils.common.format_utils import fmt_loc
|
|
@@ -62,7 +67,11 @@ class MetadataDirs:
|
|
|
62
67
|
# in which case it is in the global cache path.
|
|
63
68
|
@property
|
|
64
69
|
def cache_dir(self) -> Path:
|
|
65
|
-
return
|
|
70
|
+
return (
|
|
71
|
+
global_settings().system_cache_dir
|
|
72
|
+
if self.is_global_ws
|
|
73
|
+
else StorePath(f"{DOT_DIR}/cache")
|
|
74
|
+
)
|
|
66
75
|
|
|
67
76
|
@property
|
|
68
77
|
def media_cache_dir(self) -> Path:
|
kash/help/assistant.py
CHANGED
|
@@ -110,7 +110,7 @@ def assist_current_state() -> Message:
|
|
|
110
110
|
ws_info = f"Based on the current directory, the current workspace is: {ws_base_dir.name} at {fmt_loc(ws_base_dir)}"
|
|
111
111
|
else:
|
|
112
112
|
if is_global_ws:
|
|
113
|
-
about_ws = "You are currently using the
|
|
113
|
+
about_ws = "You are currently using the default global workspace."
|
|
114
114
|
else:
|
|
115
115
|
about_ws = "The current directory is not a workspace."
|
|
116
116
|
ws_info = (
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
from functools import cache
|
|
2
2
|
from textwrap import dedent
|
|
3
3
|
|
|
4
|
+
from strif import StringTemplate
|
|
5
|
+
|
|
4
6
|
from kash.config.logger import get_logger
|
|
5
7
|
from kash.docs.all_docs import all_docs
|
|
6
8
|
from kash.docs.load_help_topics import load_help_src
|
|
7
|
-
from kash.utils.common.string_template import StringTemplate
|
|
8
9
|
|
|
9
10
|
log = get_logger(__name__)
|
|
10
11
|
|
kash/help/help_embeddings.py
CHANGED
|
@@ -3,9 +3,9 @@ from __future__ import annotations
|
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
|
-
from kash.concepts.embeddings import Embeddings
|
|
7
|
-
from kash.concepts.text_similarity import rank_by_relatedness
|
|
8
6
|
from kash.config.logger import get_logger
|
|
7
|
+
from kash.embeddings.embeddings import Embeddings
|
|
8
|
+
from kash.embeddings.text_similarity import rank_by_relatedness
|
|
9
9
|
from kash.help.help_types import HelpDoc, HelpDocType
|
|
10
10
|
from kash.web_content.local_file_cache import Loadable
|
|
11
11
|
|
kash/help/help_printing.py
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
1
3
|
from kash.config.logger import get_logger
|
|
2
4
|
from kash.config.text_styles import STYLE_HINT
|
|
5
|
+
from kash.docs.all_docs import DocSelection
|
|
3
6
|
from kash.exec.action_registry import look_up_action_class
|
|
4
7
|
from kash.exec.command_registry import CommandFunction, look_up_command
|
|
5
8
|
from kash.help.assistant import assist_preamble, assistance_unstructured
|
|
@@ -10,7 +13,7 @@ from kash.help.tldr_help import tldr_help
|
|
|
10
13
|
from kash.llm_utils import LLM
|
|
11
14
|
from kash.llm_utils.llm_messages import Message
|
|
12
15
|
from kash.model.actions_model import Action
|
|
13
|
-
from kash.model.params_model import COMMON_SHELL_PARAMS,
|
|
16
|
+
from kash.model.params_model import COMMON_SHELL_PARAMS, Param
|
|
14
17
|
from kash.model.preconditions_model import Precondition
|
|
15
18
|
from kash.shell.output.shell_formatting import format_name_and_description, format_name_and_value
|
|
16
19
|
from kash.shell.output.shell_output import (
|
|
@@ -32,7 +35,7 @@ GENERAL_HELP = (
|
|
|
32
35
|
def _print_command_help(
|
|
33
36
|
name: str,
|
|
34
37
|
description: str | None = None,
|
|
35
|
-
param_info: list[Param] | None = None,
|
|
38
|
+
param_info: list[Param[Any]] | None = None,
|
|
36
39
|
precondition: Precondition | None = None,
|
|
37
40
|
verbose: bool = True,
|
|
38
41
|
is_action: bool = False, # pyright: ignore[reportUnusedParameter]
|
|
@@ -97,14 +100,7 @@ def print_action_help(
|
|
|
97
100
|
include_options: bool = True,
|
|
98
101
|
include_precondition: bool = True,
|
|
99
102
|
):
|
|
100
|
-
if include_options
|
|
101
|
-
params = (
|
|
102
|
-
list(action.params)
|
|
103
|
-
+ list(RUNTIME_ACTION_PARAMS.values())
|
|
104
|
-
+ list(COMMON_SHELL_PARAMS.values())
|
|
105
|
-
)
|
|
106
|
-
else:
|
|
107
|
-
params = None
|
|
103
|
+
params = action.shell_params if include_options else None
|
|
108
104
|
|
|
109
105
|
_print_command_help(
|
|
110
106
|
action.name,
|
|
@@ -176,7 +172,7 @@ def print_explain_command(text: str, assistant_model: LLM | None = None):
|
|
|
176
172
|
# Give the LLM full context on kash APIs.
|
|
177
173
|
# But we do this here lazily to prevent circular dependencies.
|
|
178
174
|
system_message = Message(
|
|
179
|
-
assist_preamble(is_structured=False,
|
|
175
|
+
assist_preamble(is_structured=False, doc_selection=DocSelection.full)
|
|
180
176
|
)
|
|
181
177
|
chat_history.extend(
|
|
182
178
|
[
|
kash/llm_utils/clean_headings.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from kash.llm_utils import Message, MessageTemplate, llm_template_completion
|
|
2
2
|
from kash.llm_utils.llms import LLM
|
|
3
|
-
from kash.text_handling.
|
|
3
|
+
from kash.text_handling.markdown_utils import as_bullet_points
|
|
4
4
|
|
|
5
5
|
# TODO: Enforce that the edits below doesn't contain anything extraneous.
|
|
6
6
|
|
kash/llm_utils/llm_api_keys.py
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import litellm
|
|
4
|
+
from clideps.env_vars.dotenv_utils import env_var_is_set
|
|
5
|
+
from clideps.env_vars.env_names import EnvName
|
|
4
6
|
from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider
|
|
5
7
|
|
|
6
8
|
from kash.llm_utils.llm_names import LLMName
|
|
7
9
|
from kash.llm_utils.llms import LLM
|
|
8
|
-
from kash.shell.clideps.api_keys import ApiEnvKey
|
|
9
|
-
from kash.shell.clideps.dotenv_utils import env_var_is_set
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
def api_for_model(model: LLMName) ->
|
|
12
|
+
def api_for_model(model: LLMName) -> EnvName | None:
|
|
13
13
|
"""
|
|
14
14
|
Get the API key name for a model or None if not found.
|
|
15
15
|
"""
|
|
@@ -18,7 +18,7 @@ def api_for_model(model: LLMName) -> ApiEnvKey | None:
|
|
|
18
18
|
except litellm.exceptions.BadRequestError:
|
|
19
19
|
return None
|
|
20
20
|
|
|
21
|
-
return
|
|
21
|
+
return EnvName.api_env_name(custom_llm_provider)
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
def have_key_for_model(model: LLMName) -> bool:
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Literal, TypeAlias
|
|
5
|
+
|
|
6
|
+
from prettyfmt import custom_key_sort
|
|
7
|
+
|
|
8
|
+
from kash.llm_utils.llm_names import LLMName
|
|
9
|
+
from kash.llm_utils.llms import LLM
|
|
10
|
+
|
|
11
|
+
Speed: TypeAlias = Literal["fast", "medium", "slow"]
|
|
12
|
+
|
|
13
|
+
ContextSize: TypeAlias = Literal["small", "medium", "large"]
|
|
14
|
+
|
|
15
|
+
ModelSize: TypeAlias = Literal["small", "medium", "large"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class LLMFeatures:
|
|
20
|
+
speed: Speed | None = None
|
|
21
|
+
context_size: ContextSize | None = None
|
|
22
|
+
model_size: ModelSize | None = None
|
|
23
|
+
structured_output: bool | None = None
|
|
24
|
+
thinking: bool = False
|
|
25
|
+
|
|
26
|
+
def satisfies(self, features: LLMFeatures) -> bool:
|
|
27
|
+
return all(
|
|
28
|
+
getattr(self, attr) == getattr(features, attr)
|
|
29
|
+
for attr in features.__dataclass_fields__
|
|
30
|
+
if getattr(self, attr) is not None
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def pick_llm(desired_features: LLMFeatures) -> LLMName:
|
|
35
|
+
"""
|
|
36
|
+
Pick the preferred model that satisfies the desired features.
|
|
37
|
+
"""
|
|
38
|
+
satisfied_models: list[LLMName] = [
|
|
39
|
+
llm for llm, features in FEATURES.items() if features.satisfies(desired_features)
|
|
40
|
+
]
|
|
41
|
+
satisfied_models.sort(key=custom_key_sort(preferred_llms))
|
|
42
|
+
if not satisfied_models:
|
|
43
|
+
raise ValueError(f"No model found for features: {desired_features}")
|
|
44
|
+
return satisfied_models[0]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
FEATURES = {
|
|
48
|
+
LLM.o3_mini: LLMFeatures(
|
|
49
|
+
speed="fast",
|
|
50
|
+
context_size="small",
|
|
51
|
+
model_size="small",
|
|
52
|
+
structured_output=True,
|
|
53
|
+
thinking=True,
|
|
54
|
+
),
|
|
55
|
+
# FIXME
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
preferred_llms: list[LLMName] = [
|
|
59
|
+
LLM.o3_mini,
|
|
60
|
+
LLM.o1_mini,
|
|
61
|
+
LLM.o1,
|
|
62
|
+
LLM.gpt_4o_mini,
|
|
63
|
+
LLM.gpt_4o,
|
|
64
|
+
LLM.gpt_4,
|
|
65
|
+
LLM.claude_3_7_sonnet,
|
|
66
|
+
LLM.claude_3_5_sonnet,
|
|
67
|
+
LLM.claude_3_5_haiku,
|
|
68
|
+
]
|
kash/llm_utils/llm_messages.py
CHANGED
kash/llm_utils/llm_names.py
CHANGED
|
@@ -12,7 +12,7 @@ from pydantic_core.core_schema import (
|
|
|
12
12
|
)
|
|
13
13
|
from rich.text import Text
|
|
14
14
|
|
|
15
|
-
from kash.
|
|
15
|
+
from kash.shell.output.shell_formatting import format_success_emoji
|
|
16
16
|
from kash.utils.common.type_utils import not_none
|
|
17
17
|
|
|
18
18
|
|
kash/llm_utils/llms.py
CHANGED
|
@@ -8,18 +8,23 @@ from kash.llm_utils.llm_names import LLMName
|
|
|
8
8
|
class LLM(LLMName, Enum):
|
|
9
9
|
"""
|
|
10
10
|
Convenience names for common LLMs. This isn't exhaustive, but just common
|
|
11
|
-
ones for autocomplete, docs, etc. Values are all LiteLLM names.
|
|
11
|
+
ones for autocomplete, docs, etc. Values are all LiteLLM names. See:
|
|
12
|
+
https://github.com/BerriAI/litellm/blob/main/litellm/model_prices_and_context_window_backup.json
|
|
12
13
|
"""
|
|
13
14
|
|
|
14
15
|
# https://platform.openai.com/docs/models
|
|
15
|
-
o3_mini = LLMName("o3-mini")
|
|
16
16
|
o1_mini = LLMName("o1-mini")
|
|
17
17
|
o1 = LLMName("o1")
|
|
18
|
+
o3 = LLMName("o3")
|
|
19
|
+
o3_mini = LLMName("o3-mini")
|
|
20
|
+
o4_mini = LLMName("o4-mini")
|
|
18
21
|
o1_preview = LLMName("o1-preview")
|
|
19
22
|
gpt_4o_mini = LLMName("gpt-4o-mini")
|
|
20
23
|
gpt_4o = LLMName("gpt-4o")
|
|
21
24
|
gpt_4 = LLMName("gpt-4")
|
|
22
|
-
|
|
25
|
+
gpt_4_1 = LLMName("gpt-4.1")
|
|
26
|
+
gpt_4_1_mini = LLMName("gpt-4.1-mini")
|
|
27
|
+
gpt_4_1_nano = LLMName("gpt-4.1-nano")
|
|
23
28
|
|
|
24
29
|
# https://docs.anthropic.com/en/docs/about-claude/models/all-models
|
|
25
30
|
claude_3_7_sonnet = LLMName("claude-3-7-sonnet-latest")
|
kash/local_server/__init__.py
CHANGED
|
@@ -14,7 +14,10 @@ from prettyfmt import fmt_path
|
|
|
14
14
|
|
|
15
15
|
from kash.config.logger import get_logger
|
|
16
16
|
from kash.config.server_config import create_server_config
|
|
17
|
-
from kash.config.settings import
|
|
17
|
+
from kash.config.settings import (
|
|
18
|
+
atomic_global_settings,
|
|
19
|
+
global_settings,
|
|
20
|
+
)
|
|
18
21
|
from kash.local_server import local_server_routes
|
|
19
22
|
from kash.local_server.port_tools import find_available_local_port
|
|
20
23
|
from kash.utils.errors import InvalidInput, InvalidState
|
|
@@ -32,14 +35,14 @@ def _app_setup() -> FastAPI:
|
|
|
32
35
|
|
|
33
36
|
# Map common exceptions to HTTP codes.
|
|
34
37
|
# FileNotFound first, since it might also be an InvalidInput.
|
|
35
|
-
@app.exception_handler(FileNotFoundError)
|
|
38
|
+
@app.exception_handler(FileNotFoundError) # pyright: ignore[reportUntypedFunctionDecorator]
|
|
36
39
|
async def file_not_found_exception_handler(_request: Request, exc: FileNotFoundError):
|
|
37
40
|
return JSONResponse(
|
|
38
41
|
status_code=404,
|
|
39
42
|
content={"message": f"File not found: {exc}"},
|
|
40
43
|
)
|
|
41
44
|
|
|
42
|
-
@app.exception_handler(InvalidInput)
|
|
45
|
+
@app.exception_handler(InvalidInput) # pyright: ignore[reportUntypedFunctionDecorator]
|
|
43
46
|
async def invalid_input_exception_handler(_request: Request, exc: InvalidInput):
|
|
44
47
|
return JSONResponse(
|
|
45
48
|
status_code=400,
|
|
@@ -47,7 +50,7 @@ def _app_setup() -> FastAPI:
|
|
|
47
50
|
)
|
|
48
51
|
|
|
49
52
|
# Global exception handler.
|
|
50
|
-
@app.exception_handler(Exception)
|
|
53
|
+
@app.exception_handler(Exception) # pyright: ignore[reportUntypedFunctionDecorator]
|
|
51
54
|
async def global_exception_handler(_request: Request, _exc: Exception):
|
|
52
55
|
return JSONResponse(
|
|
53
56
|
status_code=500,
|
|
@@ -175,7 +178,7 @@ class LocalServer:
|
|
|
175
178
|
|
|
176
179
|
# Singleton instance for the UI server.
|
|
177
180
|
# Note this is quick to set up (lazy imports).
|
|
178
|
-
_ui_server = LocalServer(UI_SERVER_NAME, UI_SERVER_HOST,
|
|
181
|
+
_ui_server = LocalServer(UI_SERVER_NAME, UI_SERVER_HOST, global_settings().local_server_log_path)
|
|
179
182
|
|
|
180
183
|
|
|
181
184
|
def start_ui_server():
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from kash.config.logger import get_logger
|
|
2
|
-
from kash.config.settings import
|
|
2
|
+
from kash.config.settings import global_settings
|
|
3
3
|
from kash.exec import kash_command
|
|
4
4
|
from kash.local_server.local_url_formatters import enable_local_urls
|
|
5
5
|
from kash.shell.utils.native_utils import tail_file
|
|
@@ -50,7 +50,7 @@ def local_server_logs(follow: bool = False) -> None:
|
|
|
50
50
|
|
|
51
51
|
:param follow: Follow the file as it grows.
|
|
52
52
|
"""
|
|
53
|
-
log_path =
|
|
53
|
+
log_path = global_settings().local_server_log_path
|
|
54
54
|
if not log_path.exists():
|
|
55
55
|
raise InvalidState(
|
|
56
56
|
f"Local ui server log not found (forgot to run `start_ui_server`?): {log_path}"
|
|
@@ -4,13 +4,13 @@ from pathlib import Path
|
|
|
4
4
|
|
|
5
5
|
from rich.style import Style
|
|
6
6
|
from rich.text import Text
|
|
7
|
+
from strif import AtomicVar
|
|
7
8
|
from typing_extensions import override
|
|
8
9
|
|
|
9
10
|
from kash.config.logger import get_logger
|
|
10
11
|
from kash.config.text_styles import STYLE_HINT
|
|
11
12
|
from kash.model.paths_model import StorePath
|
|
12
13
|
from kash.shell.output.kerm_codes import KriLink, TextTooltip, UIAction, UIActionType
|
|
13
|
-
from kash.utils.common.atomic_var import AtomicVar
|
|
14
14
|
from kash.utils.common.format_utils import fmt_loc
|
|
15
15
|
from kash.utils.errors import InvalidState
|
|
16
16
|
from kash.workspaces import current_ws
|
kash/mcp/__init__.py
CHANGED
kash/mcp/mcp_cli.py
CHANGED
|
@@ -9,8 +9,8 @@ import logging
|
|
|
9
9
|
import os
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
|
-
from kash.config.settings import DEFAULT_MCP_SERVER_PORT, LogLevel,
|
|
13
|
-
from kash.config.setup import
|
|
12
|
+
from kash.config.settings import DEFAULT_MCP_SERVER_PORT, LogLevel, global_settings
|
|
13
|
+
from kash.config.setup import kash_setup
|
|
14
14
|
from kash.shell.utils.argparse_utils import WrappedColorFormatter
|
|
15
15
|
from kash.shell.version import get_version
|
|
16
16
|
|
|
@@ -18,7 +18,7 @@ __version__ = get_version()
|
|
|
18
18
|
|
|
19
19
|
DEFAULT_PROXY_URL = f"http://localhost:{DEFAULT_MCP_SERVER_PORT}/sse"
|
|
20
20
|
|
|
21
|
-
MCP_CLI_LOG_PATH =
|
|
21
|
+
MCP_CLI_LOG_PATH = global_settings().system_logs_dir / "mcp_cli.log"
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
log = logging.getLogger()
|
|
@@ -113,10 +113,10 @@ def main():
|
|
|
113
113
|
args = build_parser().parse_args()
|
|
114
114
|
|
|
115
115
|
if args.list_tools or args.tool_help:
|
|
116
|
-
|
|
116
|
+
kash_setup(rich_logging=True, level=LogLevel.warning)
|
|
117
117
|
show_tool_info(args.tool_help)
|
|
118
118
|
else:
|
|
119
|
-
|
|
119
|
+
kash_setup(rich_logging=False, level=LogLevel.info)
|
|
120
120
|
run_server(args)
|
|
121
121
|
|
|
122
122
|
|