kash-shell 0.3.9__py3-none-any.whl → 0.3.11__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/format_markdown_template.py +2 -5
- kash/actions/core/markdownify.py +7 -6
- kash/actions/core/readability.py +7 -6
- kash/actions/core/render_as_html.py +37 -0
- kash/actions/core/show_webpage.py +6 -11
- kash/actions/core/strip_html.py +2 -6
- kash/actions/core/tabbed_webpage_config.py +31 -0
- kash/actions/core/{webpage_generate.py → tabbed_webpage_generate.py} +5 -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/files_command.py +28 -10
- 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 +19 -17
- kash/config/colors.py +3 -1
- 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 +24 -10
- 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 +12 -10
- kash/exec/precondition_registry.py +2 -1
- kash/exec/preconditions.py +22 -1
- kash/exec/resolve_args.py +4 -0
- kash/exec/shell_callable_action.py +33 -19
- kash/file_storage/file_store.py +42 -27
- 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/function_param_info.py +1 -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_server_routes.py +1 -7
- 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 +115 -32
- 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/shell/utils/shell_function_wrapper.py +15 -15
- kash/text_handling/custom_sliding_transforms.py +12 -4
- kash/text_handling/doc_normalization.py +6 -2
- kash/text_handling/markdown_render.py +118 -0
- kash/text_handling/markdown_utils.py +226 -0
- kash/utils/common/function_inspect.py +360 -110
- 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_ext.py +4 -0
- kash/utils/file_utils/file_formats.py +2 -2
- kash/utils/file_utils/file_formats_model.py +20 -1
- 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/__init__.py +0 -4
- kash/web_gen/simple_webpage.py +52 -0
- kash/web_gen/tabbed_webpage.py +24 -14
- kash/web_gen/template_render.py +37 -2
- kash/web_gen/templates/base_styles.css.jinja +169 -43
- kash/web_gen/templates/base_webpage.html.jinja +110 -45
- kash/web_gen/templates/content_styles.css.jinja +4 -2
- kash/web_gen/templates/item_view.html.jinja +49 -39
- kash/web_gen/templates/simple_webpage.html.jinja +24 -0
- kash/web_gen/templates/tabbed_webpage.html.jinja +42 -33
- 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.11.dist-info}/METADATA +10 -8
- {kash_shell-0.3.9.dist-info → kash_shell-0.3.11.dist-info}/RECORD +137 -136
- kash/actions/core/webpage_config.py +0 -21
- 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.11.dist-info}/WHEEL +0 -0
- {kash_shell-0.3.9.dist-info → kash_shell-0.3.11.dist-info}/entry_points.txt +0 -0
- {kash_shell-0.3.9.dist-info → kash_shell-0.3.11.dist-info}/licenses/LICENSE +0 -0
|
@@ -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:
|
|
@@ -443,19 +450,11 @@ class FileStore(Workspace):
|
|
|
443
450
|
if not path.exists():
|
|
444
451
|
raise FileNotFound(f"File not found: {fmt_loc(path)}")
|
|
445
452
|
|
|
446
|
-
#
|
|
447
|
-
|
|
453
|
+
# First treat it as an external file to analyze file type and format.
|
|
454
|
+
item = Item.from_external_path(path)
|
|
448
455
|
|
|
449
|
-
#
|
|
450
|
-
|
|
451
|
-
if not item_type and filename_item_type:
|
|
452
|
-
item_type = filename_item_type
|
|
453
|
-
if not item_type and format:
|
|
454
|
-
item_type = ItemType.for_format(format)
|
|
455
|
-
if not item_type:
|
|
456
|
-
item_type = ItemType.resource
|
|
457
|
-
|
|
458
|
-
if format and format.supports_frontmatter:
|
|
456
|
+
# If it's a text/frontmatter-friendly, read it fully.
|
|
457
|
+
if item.format and item.format.supports_frontmatter:
|
|
459
458
|
log.message("Importing text file: %s", fmt_loc(path))
|
|
460
459
|
# This will read the file with or without frontmatter.
|
|
461
460
|
# We are importing so we want to drop the external path so we save the body.
|
|
@@ -479,15 +478,25 @@ class FileStore(Workspace):
|
|
|
479
478
|
log.message("Importing non-text file: %s", fmt_loc(path))
|
|
480
479
|
# Binary or other files we just copy over as-is, preserving the name.
|
|
481
480
|
# We know the extension is recognized.
|
|
482
|
-
|
|
483
|
-
store_path, _found, _prev = self.store_path_for(item)
|
|
481
|
+
store_path, _found, old_store_path = self.store_path_for(item)
|
|
484
482
|
if self.exists(store_path):
|
|
485
483
|
raise FileExists(f"Resource already in store: {fmt_loc(store_path)}")
|
|
486
484
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
log.message("Importing resource: %s -> %s", fmt_loc(path), fmt_loc(store_path))
|
|
485
|
+
log.message("Importing resource: %s", fmt_loc(path))
|
|
490
486
|
copyfile_atomic(path, self.base_dir / store_path, make_parents=True)
|
|
487
|
+
|
|
488
|
+
# Optimization: Don't import an identical file twice.
|
|
489
|
+
if old_store_path:
|
|
490
|
+
old_hash = self.hash(old_store_path)
|
|
491
|
+
new_hash = self.hash(store_path)
|
|
492
|
+
if old_hash == new_hash:
|
|
493
|
+
log.message(
|
|
494
|
+
"Imported resource is identical to the previous import: %s",
|
|
495
|
+
fmt_loc(old_store_path),
|
|
496
|
+
)
|
|
497
|
+
os.unlink(self.base_dir / store_path)
|
|
498
|
+
store_path = old_store_path
|
|
499
|
+
log.message("Imported resource: %s", fmt_loc(store_path))
|
|
491
500
|
return store_path
|
|
492
501
|
|
|
493
502
|
def import_items(
|
|
@@ -496,7 +505,9 @@ class FileStore(Workspace):
|
|
|
496
505
|
as_type: ItemType | None = None,
|
|
497
506
|
reimport: bool = False,
|
|
498
507
|
) -> list[StorePath]:
|
|
499
|
-
return [
|
|
508
|
+
return [
|
|
509
|
+
self.import_item(locator, as_type=as_type, reimport=reimport) for locator in locators
|
|
510
|
+
]
|
|
500
511
|
|
|
501
512
|
def _filter_selection_paths(self):
|
|
502
513
|
"""
|
|
@@ -537,7 +548,7 @@ class FileStore(Workspace):
|
|
|
537
548
|
# TODO: Update metadata of all relations that point to this path too.
|
|
538
549
|
|
|
539
550
|
def archive(
|
|
540
|
-
self, store_path: StorePath, missing_ok: bool = False, quiet: bool = False
|
|
551
|
+
self, store_path: StorePath, *, missing_ok: bool = False, quiet: bool = False
|
|
541
552
|
) -> StorePath:
|
|
542
553
|
"""
|
|
543
554
|
Archive the item by moving it into the archive directory.
|
|
@@ -553,6 +564,9 @@ class FileStore(Workspace):
|
|
|
553
564
|
if missing_ok and not orig_path.exists():
|
|
554
565
|
log.message("Item to archive not found so moving on: %s", fmt_loc(orig_path))
|
|
555
566
|
return store_path
|
|
567
|
+
if not orig_path.exists():
|
|
568
|
+
log.warning("Item to archive not found: %s", fmt_loc(orig_path))
|
|
569
|
+
return store_path
|
|
556
570
|
move_file(orig_path, archive_path)
|
|
557
571
|
self._remove_references([store_path])
|
|
558
572
|
|
|
@@ -572,7 +586,7 @@ class FileStore(Workspace):
|
|
|
572
586
|
move_file(full_input_path, original_path)
|
|
573
587
|
return StorePath(store_path)
|
|
574
588
|
|
|
575
|
-
def log_workspace_info(self, once: bool = False):
|
|
589
|
+
def log_workspace_info(self, *, once: bool = False):
|
|
576
590
|
"""
|
|
577
591
|
Log helpful information about the workspace.
|
|
578
592
|
"""
|
|
@@ -603,7 +617,7 @@ class FileStore(Workspace):
|
|
|
603
617
|
|
|
604
618
|
if self.is_global_ws:
|
|
605
619
|
PrintHooks.spacer()
|
|
606
|
-
log.warning("Note you are currently using the default
|
|
620
|
+
log.warning("Note you are currently using the default global workspace.")
|
|
607
621
|
cprint(
|
|
608
622
|
"Create or switch to another workspace with the `workspace` command.",
|
|
609
623
|
style=STYLE_HINT,
|
|
@@ -615,6 +629,7 @@ class FileStore(Workspace):
|
|
|
615
629
|
def walk_items(
|
|
616
630
|
self,
|
|
617
631
|
store_path: StorePath | None = None,
|
|
632
|
+
*,
|
|
618
633
|
use_ignore: bool = True,
|
|
619
634
|
) -> Generator[StorePath, None, None]:
|
|
620
635
|
"""
|
|
@@ -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/function_param_info.py
CHANGED
|
@@ -12,7 +12,7 @@ def _look_up_param_docs(func: Callable[..., Any], kw_params: list[FuncParam]) ->
|
|
|
12
12
|
name = func_param.name
|
|
13
13
|
param = ALL_COMMON_PARAMS.get(name)
|
|
14
14
|
if not param:
|
|
15
|
-
param = Param(name, description=None, type=func_param.
|
|
15
|
+
param = Param(name, description=None, type=func_param.effective_type or str)
|
|
16
16
|
|
|
17
17
|
# Also check the docstring for a description of this parameter.
|
|
18
18
|
docstring = parse_docstring(func.__doc__ or "")
|
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}"
|
|
@@ -18,7 +18,6 @@ from kash.shell.file_icons.nerd_icons import icon_for_file
|
|
|
18
18
|
from kash.shell.output.shell_output import Wrap
|
|
19
19
|
from kash.utils.common.type_utils import not_none
|
|
20
20
|
from kash.utils.errors import FileNotFound, InvalidFilename
|
|
21
|
-
from kash.web_gen import base_templates_dir
|
|
22
21
|
from kash.web_gen.template_render import render_web_template
|
|
23
22
|
from kash.workspaces.workspace_output import print_file_info
|
|
24
23
|
|
|
@@ -140,14 +139,11 @@ def explain(text: str):
|
|
|
140
139
|
|
|
141
140
|
return HTMLResponse(
|
|
142
141
|
render_web_template(
|
|
143
|
-
base_templates_dir,
|
|
144
142
|
"base_webpage.html.jinja",
|
|
145
143
|
{
|
|
146
144
|
"title": f"Help: {text}",
|
|
147
145
|
"content": render_web_template(
|
|
148
|
-
|
|
149
|
-
"explain_view.html.jinja",
|
|
150
|
-
{"help_html": help_html, "page_url": page_url},
|
|
146
|
+
"explain_view.html.jinja", {"help_html": help_html, "page_url": page_url}
|
|
151
147
|
),
|
|
152
148
|
},
|
|
153
149
|
)
|
|
@@ -270,12 +266,10 @@ def _serve_item(
|
|
|
270
266
|
|
|
271
267
|
return HTMLResponse(
|
|
272
268
|
render_web_template(
|
|
273
|
-
base_templates_dir,
|
|
274
269
|
"base_webpage.html.jinja",
|
|
275
270
|
{
|
|
276
271
|
"title": display_title,
|
|
277
272
|
"content": render_web_template(
|
|
278
|
-
base_templates_dir,
|
|
279
273
|
"item_view.html.jinja",
|
|
280
274
|
{
|
|
281
275
|
"item": item,
|
|
@@ -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
|