kash-shell 0.3.10__py3-none-any.whl → 0.3.12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. kash/actions/core/format_markdown_template.py +2 -5
  2. kash/actions/core/markdownify.py +2 -4
  3. kash/actions/core/readability.py +2 -4
  4. kash/actions/core/render_as_html.py +30 -11
  5. kash/actions/core/show_webpage.py +6 -11
  6. kash/actions/core/strip_html.py +4 -8
  7. kash/actions/core/{webpage_config.py → tabbed_webpage_config.py} +5 -3
  8. kash/actions/core/{webpage_generate.py → tabbed_webpage_generate.py} +5 -4
  9. kash/commands/base/basic_file_commands.py +21 -3
  10. kash/commands/base/files_command.py +29 -10
  11. kash/commands/extras/parse_uv_lock.py +12 -3
  12. kash/commands/workspace/selection_commands.py +1 -1
  13. kash/commands/workspace/workspace_commands.py +2 -3
  14. kash/config/colors.py +2 -2
  15. kash/config/env_settings.py +2 -42
  16. kash/config/logger.py +30 -25
  17. kash/config/logger_basic.py +6 -6
  18. kash/config/settings.py +23 -7
  19. kash/config/setup.py +33 -5
  20. kash/config/text_styles.py +25 -22
  21. kash/embeddings/cosine.py +12 -4
  22. kash/embeddings/embeddings.py +16 -6
  23. kash/embeddings/text_similarity.py +10 -4
  24. kash/exec/__init__.py +3 -0
  25. kash/exec/action_decorators.py +10 -25
  26. kash/exec/action_exec.py +43 -23
  27. kash/exec/llm_transforms.py +6 -3
  28. kash/exec/preconditions.py +10 -12
  29. kash/exec/resolve_args.py +4 -0
  30. kash/exec/runtime_settings.py +134 -0
  31. kash/exec/shell_callable_action.py +5 -3
  32. kash/file_storage/file_store.py +37 -38
  33. kash/file_storage/item_file_format.py +6 -3
  34. kash/file_storage/store_filenames.py +6 -3
  35. kash/help/function_param_info.py +1 -1
  36. kash/llm_utils/init_litellm.py +16 -0
  37. kash/llm_utils/llm_api_keys.py +6 -2
  38. kash/llm_utils/llm_completion.py +11 -4
  39. kash/local_server/local_server_routes.py +1 -7
  40. kash/mcp/mcp_cli.py +3 -2
  41. kash/mcp/mcp_server_routes.py +11 -12
  42. kash/media_base/transcription_deepgram.py +15 -2
  43. kash/model/__init__.py +1 -1
  44. kash/model/actions_model.py +6 -54
  45. kash/model/exec_model.py +79 -0
  46. kash/model/items_model.py +102 -35
  47. kash/model/operations_model.py +38 -15
  48. kash/model/paths_model.py +2 -0
  49. kash/shell/output/shell_output.py +10 -8
  50. kash/shell/shell_main.py +2 -2
  51. kash/shell/utils/exception_printing.py +2 -2
  52. kash/shell/utils/shell_function_wrapper.py +15 -15
  53. kash/text_handling/doc_normalization.py +16 -8
  54. kash/text_handling/markdown_render.py +1 -0
  55. kash/text_handling/markdown_utils.py +105 -2
  56. kash/utils/common/format_utils.py +2 -8
  57. kash/utils/common/function_inspect.py +360 -110
  58. kash/utils/common/inflection.py +22 -0
  59. kash/utils/common/task_stack.py +4 -15
  60. kash/utils/errors.py +14 -9
  61. kash/utils/file_utils/file_ext.py +4 -0
  62. kash/utils/file_utils/file_formats_model.py +32 -1
  63. kash/utils/file_utils/file_sort_filter.py +10 -3
  64. kash/web_gen/__init__.py +0 -4
  65. kash/web_gen/simple_webpage.py +52 -0
  66. kash/web_gen/tabbed_webpage.py +23 -16
  67. kash/web_gen/template_render.py +37 -2
  68. kash/web_gen/templates/base_styles.css.jinja +84 -59
  69. kash/web_gen/templates/base_webpage.html.jinja +85 -67
  70. kash/web_gen/templates/item_view.html.jinja +47 -37
  71. kash/web_gen/templates/simple_webpage.html.jinja +24 -0
  72. kash/web_gen/templates/tabbed_webpage.html.jinja +42 -32
  73. kash/workspaces/__init__.py +12 -3
  74. kash/workspaces/workspace_dirs.py +58 -0
  75. kash/workspaces/workspace_importing.py +1 -1
  76. kash/workspaces/workspaces.py +26 -90
  77. {kash_shell-0.3.10.dist-info → kash_shell-0.3.12.dist-info}/METADATA +7 -7
  78. {kash_shell-0.3.10.dist-info → kash_shell-0.3.12.dist-info}/RECORD +81 -76
  79. kash/shell/utils/argparse_utils.py +0 -20
  80. kash/utils/lang_utils/inflection.py +0 -18
  81. {kash_shell-0.3.10.dist-info → kash_shell-0.3.12.dist-info}/WHEEL +0 -0
  82. {kash_shell-0.3.10.dist-info → kash_shell-0.3.12.dist-info}/entry_points.txt +0 -0
  83. {kash_shell-0.3.10.dist-info → kash_shell-0.3.12.dist-info}/licenses/LICENSE +0 -0
@@ -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.type or str)
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 "")
@@ -0,0 +1,16 @@
1
+ from functools import cache
2
+
3
+
4
+ @cache
5
+ def init_litellm():
6
+ """
7
+ Configure litellm to suppress overly prominent exception messages.
8
+ Do this lazily since litellm is slow to import.
9
+ """
10
+ try:
11
+ import litellm
12
+ from litellm import _logging # noqa: F401
13
+
14
+ litellm.suppress_debug_info = True # Suppress overly prominent exception messages.
15
+ except ImportError:
16
+ pass
@@ -1,10 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
- import litellm
4
3
  from clideps.env_vars.dotenv_utils import env_var_is_set
5
4
  from clideps.env_vars.env_names import EnvName
6
- from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider
7
5
 
6
+ from kash.llm_utils.init_litellm import init_litellm
8
7
  from kash.llm_utils.llm_names import LLMName
9
8
  from kash.llm_utils.llms import LLM
10
9
 
@@ -13,6 +12,11 @@ def api_for_model(model: LLMName) -> EnvName | None:
13
12
  """
14
13
  Get the API key name for a model or None if not found.
15
14
  """
15
+ import litellm
16
+ from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider
17
+
18
+ init_litellm()
19
+
16
20
  try:
17
21
  _model, custom_llm_provider, _dynamic_api_key, _api_base = get_llm_provider(model)
18
22
  except litellm.exceptions.BadRequestError:
@@ -1,11 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  import time
2
- from typing import cast
4
+ from typing import TYPE_CHECKING, cast
3
5
 
4
- import litellm
5
6
  from flowmark import Wrap, fill_text
6
7
  from funlog import format_duration, log_calls
7
- from litellm.types.utils import Choices, ModelResponse
8
- from litellm.types.utils import Message as LiteLLMMessage
9
8
  from prettyfmt import slugify_snake
10
9
  from pydantic import BaseModel
11
10
  from pydantic.dataclasses import dataclass
@@ -13,12 +12,16 @@ from pydantic.dataclasses import dataclass
13
12
  from kash.config.logger import get_logger
14
13
  from kash.config.text_styles import EMOJI_TIMING
15
14
  from kash.llm_utils.fuzzy_parsing import is_no_results
15
+ from kash.llm_utils.init_litellm import init_litellm
16
16
  from kash.llm_utils.llm_messages import Message, MessageTemplate
17
17
  from kash.llm_utils.llm_names import LLMName
18
18
  from kash.utils.common.url import Url, is_url
19
19
  from kash.utils.errors import ApiResultError
20
20
  from kash.utils.file_formats.chat_format import ChatHistory, ChatMessage, ChatRole
21
21
 
22
+ if TYPE_CHECKING:
23
+ from litellm.types.utils import Message as LiteLLMMessage
24
+
22
25
  log = get_logger(__name__)
23
26
 
24
27
 
@@ -68,6 +71,10 @@ def llm_completion(
68
71
  """
69
72
  Perform an LLM completion with LiteLLM.
70
73
  """
74
+ import litellm
75
+ from litellm.types.utils import Choices, ModelResponse
76
+
77
+ init_litellm()
71
78
 
72
79
  chat_history = ChatHistory.from_dicts(messages)
73
80
  log.info(
@@ -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
- base_templates_dir,
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,
kash/mcp/mcp_cli.py CHANGED
@@ -9,9 +9,10 @@ import logging
9
9
  import os
10
10
  from pathlib import Path
11
11
 
12
+ from clideps.utils.readable_argparse import ReadableColorFormatter
13
+
12
14
  from kash.config.settings import DEFAULT_MCP_SERVER_PORT, LogLevel, global_settings
13
15
  from kash.config.setup import kash_setup
14
- from kash.shell.utils.argparse_utils import WrappedColorFormatter
15
16
  from kash.shell.version import get_version
16
17
 
17
18
  __version__ = get_version()
@@ -27,7 +28,7 @@ log = logging.getLogger()
27
28
  def build_parser():
28
29
  from kash.workspaces.workspaces import global_ws_dir
29
30
 
30
- parser = argparse.ArgumentParser(description=__doc__, formatter_class=WrappedColorFormatter)
31
+ parser = argparse.ArgumentParser(description=__doc__, formatter_class=ReadableColorFormatter)
31
32
  parser.add_argument(
32
33
  "--version",
33
34
  action="version",
@@ -13,12 +13,13 @@ from strif import AtomicVar
13
13
  from kash.config.capture_output import CapturedOutput, captured_output
14
14
  from kash.config.logger import get_logger
15
15
  from kash.config.settings import global_settings
16
+ from kash.exec import kash_runtime
16
17
  from kash.exec.action_exec import prepare_action_input, run_action_with_caching
17
18
  from kash.exec.action_registry import get_all_actions_defaults, look_up_action_class
18
- from kash.model.actions_model import Action, ActionResult, ExecContext
19
+ from kash.model.actions_model import Action, ActionResult
20
+ from kash.model.exec_model import ExecContext
19
21
  from kash.model.params_model import TypedParamValues
20
22
  from kash.model.paths_model import StorePath
21
- from kash.workspaces.workspaces import current_ws, get_ws
22
23
 
23
24
  log = get_logger(__name__)
24
25
 
@@ -214,9 +215,14 @@ def run_mcp_tool(action_name: str, arguments: dict) -> list[TextContent]:
214
215
  # current workspace, which could be changed by the user by changing working
215
216
  # directories. Maybe confusing?
216
217
  explicit_mcp_ws = global_settings().mcp_ws_dir
217
- ws = get_ws(explicit_mcp_ws) if explicit_mcp_ws else current_ws()
218
218
 
219
- with ws:
219
+ with kash_runtime(
220
+ workspace_dir=explicit_mcp_ws,
221
+ rerun=True, # Enabling rerun always for now, seems good for tools.
222
+ refetch=False, # Using the file caches.
223
+ # Keeping all transient files for now, but maybe make transient?
224
+ override_state=None,
225
+ ) as exec_settings:
220
226
  action_cls = look_up_action_class(action_name)
221
227
 
222
228
  # Extract items array and remaining params from arguments.
@@ -228,14 +234,7 @@ def run_mcp_tool(action_name: str, arguments: dict) -> list[TextContent]:
228
234
  action = action_cls.create(param_values)
229
235
 
230
236
  # Create execution context and assemble action input.
231
- context = ExecContext(
232
- action=action,
233
- workspace_dir=ws.base_dir,
234
- rerun=True, # Enabling rerun always for now, seems good for tools.
235
- refetch=False, # Using the file caches.
236
- # Keeping all transient files for now, but maybe make transient?
237
- override_state=None,
238
- )
237
+ context = ExecContext(action=action, settings=exec_settings)
239
238
  action_input = prepare_action_input(*input_items)
240
239
 
241
240
  result, result_store_paths, _archived_store_paths = run_action_with_caching(
@@ -1,8 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  from os.path import getsize
2
4
  from pathlib import Path
5
+ from typing import TYPE_CHECKING
3
6
 
4
7
  from clideps.env_vars.dotenv_utils import load_dotenv_paths
5
- from deepgram import ListenRESTClient, PrerecordedResponse
6
8
  from httpx import Timeout
7
9
 
8
10
  from kash.config.logger import CustomLogger, get_logger
@@ -10,6 +12,9 @@ from kash.config.settings import global_settings
10
12
  from kash.media_base.transcription_format import SpeakerSegment, format_speaker_segments
11
13
  from kash.utils.errors import ApiError, ContentError
12
14
 
15
+ if TYPE_CHECKING:
16
+ from deepgram import PrerecordedResponse
17
+
13
18
  log: CustomLogger = get_logger(__name__)
14
19
 
15
20
 
@@ -19,7 +24,15 @@ def deepgram_transcribe_raw(
19
24
  """
20
25
  Transcribe an audio file using Deepgram and return the raw response.
21
26
  """
22
- from deepgram import ClientOptionsFromEnv, DeepgramClient, FileSource, PrerecordedOptions
27
+ # Slow import, do lazily.
28
+ from deepgram import (
29
+ ClientOptionsFromEnv,
30
+ DeepgramClient,
31
+ FileSource,
32
+ ListenRESTClient,
33
+ PrerecordedOptions,
34
+ PrerecordedResponse,
35
+ )
23
36
 
24
37
  size = getsize(audio_file_path)
25
38
  log.info(
kash/model/__init__.py CHANGED
@@ -20,7 +20,6 @@ from kash.model.actions_model import (
20
20
  Action,
21
21
  ActionInput,
22
22
  ActionResult,
23
- ExecContext,
24
23
  LLMOptions,
25
24
  PathOp,
26
25
  PathOpType,
@@ -33,6 +32,7 @@ from kash.model.compound_actions_model import (
33
32
  look_up_actions,
34
33
  )
35
34
  from kash.model.concept_model import Concept, canonicalize_concept, normalize_concepts
35
+ from kash.model.exec_model import ExecContext
36
36
  from kash.model.graph_model import GraphData, Link, Node
37
37
  from kash.model.items_model import (
38
38
  SLUG_MAX_LEN,
@@ -4,7 +4,6 @@ from abc import ABC, abstractmethod
4
4
  from dataclasses import Field as DataclassField
5
5
  from dataclasses import field, replace
6
6
  from enum import Enum
7
- from pathlib import Path
8
7
  from textwrap import dedent
9
8
  from typing import Any, TypeVar, cast
10
9
 
@@ -20,10 +19,10 @@ from typing_extensions import override
20
19
  from kash.config.logger import get_logger
21
20
  from kash.exec_model.args_model import NO_ARGS, ONE_ARG, ArgCount, ArgType, Signature
22
21
  from kash.exec_model.shell_model import ShellResult
23
- from kash.file_storage.file_store import FileStore
24
22
  from kash.llm_utils import LLM, LLMName
25
23
  from kash.llm_utils.llm_messages import Message, MessageTemplate
26
- from kash.model.items_model import UNTITLED, Item, ItemType, State
24
+ from kash.model.exec_model import ExecContext
25
+ from kash.model.items_model import UNTITLED, Item, ItemType
27
26
  from kash.model.operations_model import Operation, Source
28
27
  from kash.model.params_model import (
29
28
  ALL_COMMON_PARAMS,
@@ -38,7 +37,6 @@ from kash.model.preconditions_model import Precondition
38
37
  from kash.utils.common.parse_key_vals import format_key_value
39
38
  from kash.utils.common.type_utils import not_none
40
39
  from kash.utils.errors import InvalidDefinition, InvalidInput
41
- from kash.workspaces.workspaces import get_ws
42
40
 
43
41
  log = get_logger(__name__)
44
42
 
@@ -64,53 +62,6 @@ class ActionInput:
64
62
  return ActionInput(items=[])
65
63
 
66
64
 
67
- @dataclass(frozen=True)
68
- class ExecContext:
69
- """
70
- An action and its context for execution. This is a good place for settings
71
- that apply to any action and are bothersome to pass as parameters.
72
- """
73
-
74
- action: Action
75
- """The action being executed."""
76
-
77
- workspace_dir: Path
78
- """The workspace directory in which the action is being executed."""
79
-
80
- rerun: bool = False
81
- """If True, always run actions, even cacheable ones that have results."""
82
-
83
- refetch: bool = False
84
- """If True, will refetch items even if they are already in the content caches."""
85
-
86
- override_state: State | None = None
87
- """If specified, override the state of result items. Useful to mark items as transient."""
88
-
89
- tmp_output: bool = False
90
- """If True, will save output items to a temporary file."""
91
-
92
- no_format: bool = False
93
- """If True, will not normalize the output item's body text formatting (for Markdown)."""
94
-
95
- @property
96
- def workspace(self) -> FileStore:
97
- return get_ws(self.workspace_dir)
98
-
99
- @property
100
- def runtime_options(self) -> dict[str, str]:
101
- """Return non-default runtime options."""
102
- opts: dict[str, str] = {}
103
- # Only these two settings directly affect the output:
104
- if self.no_format:
105
- opts["no_format"] = "true"
106
- if self.override_state:
107
- opts["override_state"] = self.override_state.name
108
- return opts
109
-
110
- def __repr__(self):
111
- return abbrev_obj(self, field_max_len=80)
112
-
113
-
114
65
  class PathOpType(Enum):
115
66
  archive = "archive"
116
67
  select = "select"
@@ -365,8 +316,8 @@ class Action(ABC):
365
316
  """
366
317
  Declaration sanity checks.
367
318
  """
368
- if not self.name or not self.description:
369
- raise InvalidDefinition("Action must have a name and description")
319
+ if not self.name:
320
+ raise InvalidDefinition("Action must have a name")
370
321
 
371
322
  for param in self.params:
372
323
  if not self.has_param(param.name):
@@ -535,7 +486,7 @@ class Action(ABC):
535
486
  log.info("Ignoring parameter for action `%s`: `%s`", self.name, param_name)
536
487
 
537
488
  if overrides:
538
- log.message(
489
+ log.info(
539
490
  "Overriding parameters for action `%s`:\n%s",
540
491
  self.name,
541
492
  fmt_lines(overrides),
@@ -677,3 +628,4 @@ class PerItemAction(Action, ABC):
677
628
 
678
629
  # Handle circular dependency in Python dataclasses.
679
630
  rebuild_dataclass(Item) # pyright: ignore
631
+ rebuild_dataclass(ExecContext) # pyright: ignore
@@ -0,0 +1,79 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import TYPE_CHECKING
5
+
6
+ from prettyfmt import abbrev_obj
7
+ from pydantic.dataclasses import dataclass
8
+
9
+ from kash.config.logger import get_logger
10
+ from kash.model.items_model import State
11
+
12
+ if TYPE_CHECKING:
13
+ from kash.file_storage.file_store import FileStore
14
+ from kash.model.actions_model import Action
15
+
16
+
17
+ log = get_logger(__name__)
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class RuntimeSettings:
22
+ """
23
+ Workspace and other runtime settings that may be set across runs of
24
+ one or more actions.
25
+ """
26
+
27
+ workspace_dir: Path
28
+ """The workspace directory in which the action is being executed."""
29
+
30
+ rerun: bool = False
31
+ """If True, always run actions, even cacheable ones that have results."""
32
+
33
+ refetch: bool = False
34
+ """If True, will refetch items even if they are already in the content caches."""
35
+
36
+ override_state: State | None = None
37
+ """If specified, override the state of result items. Useful to mark items as transient."""
38
+
39
+ tmp_output: bool = False
40
+ """If True, will save output items to a temporary file."""
41
+
42
+ no_format: bool = False
43
+ """If True, will not normalize the output item's body text formatting (for Markdown)."""
44
+
45
+ @property
46
+ def workspace(self) -> FileStore:
47
+ from kash.workspaces.workspaces import get_ws
48
+
49
+ return get_ws(self.workspace_dir)
50
+
51
+ @property
52
+ def non_default_options(self) -> dict[str, str]:
53
+ """
54
+ Summarize non-default runtime options as a dict.
55
+ """
56
+ opts: dict[str, str] = {}
57
+ # Only these two settings directly affect the output:
58
+ if self.no_format:
59
+ opts["no_format"] = "true"
60
+ if self.override_state:
61
+ opts["override_state"] = self.override_state.name
62
+ return opts
63
+
64
+ def __repr__(self):
65
+ return abbrev_obj(self, field_max_len=80)
66
+
67
+
68
+ @dataclass(frozen=True)
69
+ class ExecContext:
70
+ """
71
+ An action and its context for execution. This is a good place for settings
72
+ that apply to any action and are bothersome to pass as parameters.
73
+ """
74
+
75
+ action: Action
76
+ """The action being executed."""
77
+
78
+ settings: RuntimeSettings
79
+ """The workspace and other run-time settings for the action."""