kash-shell 0.3.8__py3-none-any.whl → 0.3.9__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 (60) hide show
  1. kash/commands/base/debug_commands.py +2 -2
  2. kash/commands/base/general_commands.py +8 -4
  3. kash/commands/base/logs_commands.py +2 -7
  4. kash/commands/workspace/workspace_commands.py +1 -1
  5. kash/concepts/cosine.py +2 -1
  6. kash/concepts/text_similarity.py +3 -58
  7. kash/config/env_settings.py +59 -0
  8. kash/config/logger.py +2 -3
  9. kash/config/logger_basic.py +9 -2
  10. kash/config/server_config.py +6 -6
  11. kash/config/settings.py +25 -34
  12. kash/config/setup.py +13 -3
  13. kash/config/suppress_warnings.py +30 -12
  14. kash/docs/markdown/topics/a2_installation.md +7 -3
  15. kash/docs/markdown/warning.md +3 -8
  16. kash/docs/markdown/welcome.md +4 -0
  17. kash/docs_base/load_recipe_snippets.py +1 -1
  18. kash/docs_base/recipes/{general_system_commands.ksh → general_system_commands.sh} +1 -1
  19. kash/exec/llm_transforms.py +2 -1
  20. kash/file_storage/file_store.py +0 -4
  21. kash/file_storage/item_file_format.py +8 -1
  22. kash/help/assistant.py +1 -1
  23. kash/help/help_printing.py +15 -7
  24. kash/help/tldr_help.py +5 -3
  25. kash/llm_utils/llm_completion.py +2 -2
  26. kash/llm_utils/llms.py +9 -9
  27. kash/local_server/local_server.py +50 -43
  28. kash/local_server/local_server_commands.py +15 -15
  29. kash/local_server/local_server_routes.py +2 -2
  30. kash/mcp/mcp_cli.py +52 -15
  31. kash/mcp/mcp_server_commands.py +4 -5
  32. kash/mcp/mcp_server_routes.py +9 -6
  33. kash/mcp/mcp_server_sse.py +59 -34
  34. kash/mcp/mcp_server_stdio.py +0 -8
  35. kash/media_base/audio_processing.py +81 -7
  36. kash/media_base/media_cache.py +10 -10
  37. kash/media_base/speech_transcription.py +2 -1
  38. kash/model/items_model.py +12 -6
  39. kash/shell/clideps/api_keys.py +3 -2
  40. kash/shell/clideps/dotenv_setup.py +3 -2
  41. kash/shell/clideps/dotenv_utils.py +15 -6
  42. kash/shell/clideps/pkg_deps.py +27 -2
  43. kash/utils/common/atomic_var.py +16 -3
  44. kash/utils/common/url.py +53 -25
  45. kash/utils/file_utils/{dir_size.py → dir_info.py} +25 -4
  46. kash/utils/file_utils/file_ext.py +2 -3
  47. kash/utils/file_utils/file_formats.py +26 -0
  48. kash/utils/file_utils/file_formats_model.py +47 -19
  49. kash/utils/file_utils/filename_parsing.py +10 -4
  50. kash/workspaces/source_items.py +4 -1
  51. kash/workspaces/workspace_output.py +11 -4
  52. kash/xonsh_custom/custom_shell.py +1 -1
  53. kash/xonsh_custom/load_into_xonsh.py +5 -3
  54. {kash_shell-0.3.8.dist-info → kash_shell-0.3.9.dist-info}/METADATA +19 -7
  55. {kash_shell-0.3.8.dist-info → kash_shell-0.3.9.dist-info}/RECORD +60 -59
  56. {kash_shell-0.3.8.dist-info → kash_shell-0.3.9.dist-info}/entry_points.txt +1 -1
  57. /kash/docs_base/recipes/{python_dev_commands.ksh → python_dev_commands.sh} +0 -0
  58. /kash/docs_base/recipes/{tldr_standard_commands.ksh → tldr_standard_commands.sh} +0 -0
  59. {kash_shell-0.3.8.dist-info → kash_shell-0.3.9.dist-info}/WHEEL +0 -0
  60. {kash_shell-0.3.8.dist-info → kash_shell-0.3.9.dist-info}/licenses/LICENSE +0 -0
@@ -179,7 +179,7 @@ def reload_system() -> None:
179
179
  the local the local server. Not perfect! But sometimes useful for development.
180
180
  """
181
181
  import kash
182
- from kash.local_server.local_server import restart_local_server
182
+ from kash.local_server.local_server import restart_ui_server
183
183
  from kash.utils.common.import_utils import recursive_reload
184
184
 
185
185
  module = kash
@@ -197,7 +197,7 @@ def reload_system() -> None:
197
197
  log.info("Reloaded modules: %s", ", ".join(package_names))
198
198
  log.message("Reloaded %s modules from %s.", len(package_names), module.__name__)
199
199
 
200
- restart_local_server()
200
+ restart_ui_server()
201
201
 
202
202
  # TODO Re-register commands and actions.
203
203
 
@@ -1,7 +1,9 @@
1
+ from flowmark import Wrap
2
+
1
3
  from kash.commands.base.model_commands import list_apis, list_models
2
4
  from kash.commands.workspace.workspace_commands import list_params
3
5
  from kash.config.logger import get_logger
4
- from kash.config.settings import RECOMMENDED_API_KEYS
6
+ from kash.config.settings import RECOMMENDED_API_KEYS, get_system_config_dir
5
7
  from kash.docs.all_docs import all_docs
6
8
  from kash.exec import kash_command
7
9
  from kash.help.tldr_help import tldr_refresh_cache
@@ -175,7 +177,7 @@ def reload_env() -> None:
175
177
  Reload the environment variables from the .env file.
176
178
  """
177
179
 
178
- env_paths = load_dotenv_paths()
180
+ env_paths = load_dotenv_paths(True, True, get_system_config_dir())
179
181
  if env_paths:
180
182
  cprint("Reloaded environment variables")
181
183
  print_api_key_setup(RECOMMENDED_API_KEYS)
@@ -197,7 +199,9 @@ def kits() -> None:
197
199
  else:
198
200
  cprint("Currently imported kits:")
199
201
  for kit in get_loaded_kits().values():
200
- cprint(format_name_and_value(f"{kit.name} kit", str(kit.path or "")))
202
+ cprint(
203
+ format_name_and_value(f"{kit.name} kit", str(kit.path or ""), text_wrap=Wrap.NONE)
204
+ )
201
205
 
202
206
 
203
207
  @kash_command
@@ -210,5 +214,5 @@ def settings() -> None:
210
214
  settings = global_settings()
211
215
  print_h2("Global Settings")
212
216
  for field, value in settings.__dict__.items():
213
- cprint(format_name_and_value(field, str(value)))
217
+ cprint(format_name_and_value(field, str(value), text_wrap=Wrap.NONE))
214
218
  PrintHooks.spacer()
@@ -7,8 +7,7 @@ from kash.config.logger import get_log_settings, get_logger, reload_rich_logging
7
7
  from kash.config.settings import (
8
8
  LogLevel,
9
9
  atomic_global_settings,
10
- global_settings,
11
- server_log_file_path,
10
+ local_server_log_path,
12
11
  )
13
12
  from kash.exec import kash_command
14
13
  from kash.shell.output.shell_formatting import format_name_and_value
@@ -86,8 +85,4 @@ def log_settings() -> None:
86
85
  cprint(format_name_and_value("log_objects_dir", str(settings.log_objects_dir)))
87
86
  cprint(format_name_and_value("log_file_level", settings.log_file_level.name))
88
87
  cprint(format_name_and_value("log_console_level", settings.log_console_level.name))
89
- cprint(
90
- format_name_and_value(
91
- "server_log_file", str(server_log_file_path(global_settings().local_server_port))
92
- )
93
- )
88
+ cprint(format_name_and_value("server_log_file_path", str(local_server_log_path())))
@@ -54,7 +54,7 @@ from kash.utils.common.type_utils import not_none
54
54
  from kash.utils.common.url import Url, is_url
55
55
  from kash.utils.errors import InvalidInput
56
56
  from kash.utils.file_formats.chat_format import tail_chat_history
57
- from kash.utils.file_utils.dir_size import is_nonempty_dir
57
+ from kash.utils.file_utils.dir_info import is_nonempty_dir
58
58
  from kash.utils.lang_utils.inflection import plural
59
59
  from kash.web_content.file_cache_utils import cache_file
60
60
  from kash.workspaces import (
kash/concepts/cosine.py CHANGED
@@ -1,9 +1,10 @@
1
1
  from collections.abc import Sequence
2
+ from typing import Any, TypeAlias
2
3
 
3
4
  import numpy as np
4
5
 
5
6
  # Type aliases for clarity
6
- ArrayLike = Sequence[float] | np.ndarray
7
+ ArrayLike: TypeAlias = Sequence[float] | np.ndarray[Any, Any]
7
8
 
8
9
 
9
10
  def cosine(u: ArrayLike, v: ArrayLike) -> float:
@@ -1,12 +1,11 @@
1
1
  from typing import cast
2
2
 
3
3
  import litellm
4
- import pandas as pd
5
- from funlog import log_calls, tally_calls
4
+ from funlog import log_calls
6
5
  from litellm import embedding
7
6
  from litellm.types.utils import EmbeddingResponse
8
7
 
9
- from kash.concepts.cosine import cosine
8
+ from kash.concepts.cosine import ArrayLike, cosine
10
9
  from kash.concepts.embeddings import Embeddings
11
10
  from kash.config.logger import get_logger
12
11
  from kash.llm_utils.llms import DEFAULT_EMBEDDING_MODEL, EmbeddingModel
@@ -15,11 +14,7 @@ from kash.utils.errors import ApiResultError
15
14
  log = get_logger(__name__)
16
15
 
17
16
 
18
- def sort_by_length(values: list[str]) -> list[str]:
19
- return sorted(values, key=lambda x: (len(x), x))
20
-
21
-
22
- def cosine_relatedness(x, y):
17
+ def cosine_relatedness(x: ArrayLike, y: ArrayLike) -> float:
23
18
  return 1 - cosine(x, y)
24
19
 
25
20
 
@@ -60,53 +55,3 @@ def rank_by_relatedness(
60
55
  scored_strings.sort(key=lambda x: x[2], reverse=True)
61
56
 
62
57
  return scored_strings[:top_n]
63
-
64
-
65
- @tally_calls(level="warning", min_total_runtime=5, if_slower_than=10)
66
- def relate_texts_by_embedding(
67
- embeddings: Embeddings, relatedness_fn=cosine_relatedness
68
- ) -> pd.DataFrame:
69
- log.message("Computing relatedness matrix of %d text embeddings…", len(embeddings.data))
70
-
71
- keys = [key for key, _, _ in embeddings.as_iterable()]
72
- relatedness_matrix = pd.DataFrame(index=keys, columns=keys) # pyright: ignore
73
-
74
- for i, (key1, _, emb1) in enumerate(embeddings.as_iterable()):
75
- for j, (key2, _, emb2) in enumerate(embeddings.as_iterable()):
76
- if i <= j:
77
- score = relatedness_fn(emb1, emb2)
78
- relatedness_matrix.at[key1, key2] = score
79
- relatedness_matrix.at[key2, key1] = score
80
-
81
- # Fill diagonal (self-relatedness).
82
- for key in keys:
83
- relatedness_matrix.at[key, key] = 1.0
84
-
85
- return relatedness_matrix
86
-
87
-
88
- def find_related_pairs(
89
- relatedness_matrix: pd.DataFrame, threshold: float = 0.9
90
- ) -> list[tuple[str, str, float]]:
91
- log.message(
92
- "Finding near duplicates among %s items (threshold %s)",
93
- relatedness_matrix.shape[0],
94
- threshold,
95
- )
96
-
97
- pairs: list[tuple[str, str, float]] = []
98
- keys = relatedness_matrix.index.tolist()
99
-
100
- for i, key1 in enumerate(keys):
101
- for j, key2 in enumerate(keys):
102
- if i < j:
103
- relatedness = relatedness_matrix.at[key1, key2]
104
- if relatedness >= threshold:
105
- # Put shortest one first.
106
- [short_key, long_key] = sort_by_length([key1, key2])
107
- pairs.append((short_key, long_key, relatedness))
108
-
109
- # Sort with highest relatedness first.
110
- pairs.sort(key=lambda x: x[2], reverse=True)
111
-
112
- return pairs
@@ -0,0 +1,59 @@
1
+ import os
2
+ from enum import Enum
3
+ from pathlib import Path
4
+ from typing import overload
5
+
6
+
7
+ class KashEnv(str, Enum):
8
+ """
9
+ Environment variable settings for kash. None are required, but these may be
10
+ used to override default values.
11
+ """
12
+
13
+ KASH_LOG_LEVEL = "KASH_LOG_LEVEL"
14
+ """The log level for console-based logging."""
15
+
16
+ KASH_WS_ROOT = "KASH_WS_ROOT"
17
+ """The root directory for kash workspaces."""
18
+
19
+ KASH_GLOBAL_WS = "KASH_GLOBAL_WS"
20
+ """The global workspace directory."""
21
+
22
+ KASH_SYSTEM_LOGS_DIR = "KASH_SYSTEM_LOGS_DIR"
23
+ """The directory for system logs."""
24
+
25
+ KASH_SYSTEM_CACHE_DIR = "KASH_SYSTEM_CACHE_DIR"
26
+ """The directory for system cache (caches separate from workspace caches)."""
27
+
28
+ KASH_MCP_WS = "KASH_MCP_WS"
29
+ """The directory for the workspace for MCP servers."""
30
+
31
+ @overload
32
+ def read_str(self) -> str | None: ...
33
+
34
+ @overload
35
+ def read_str(self, default: str) -> str: ...
36
+
37
+ def read_str(self, default: str | None = None) -> str | None:
38
+ """
39
+ Get the value of the environment variable from the environment (with
40
+ optional default).
41
+ """
42
+ return os.environ.get(self.value, default)
43
+
44
+ @overload
45
+ def read_path(self, default: None) -> None: ...
46
+
47
+ @overload
48
+ def read_path(self, default: Path) -> Path: ...
49
+
50
+ def read_path(self, default: Path | None = None) -> Path | None:
51
+ """
52
+ Get the value of the environment variable as a resolved path (with
53
+ optional default).
54
+ """
55
+ value = os.environ.get(self.value)
56
+ if value:
57
+ return Path(value).expanduser().resolve()
58
+ else:
59
+ return default.expanduser().resolve() if default else None
kash/config/logger.py CHANGED
@@ -12,11 +12,11 @@ from pathlib import Path
12
12
  from typing import IO, Any, cast
13
13
 
14
14
  import rich
15
+ from prettyfmt import slugify_snake
15
16
  from rich._null_file import NULL_FILE
16
17
  from rich.console import Console
17
18
  from rich.logging import RichHandler
18
19
  from rich.theme import Theme
19
- from slugify import slugify
20
20
  from strif import atomic_output_file, new_timestamped_uid
21
21
  from typing_extensions import override
22
22
 
@@ -317,8 +317,7 @@ class CustomLogger(logging.Logger):
317
317
  global _log_settings
318
318
  prefix = prefix_slug + "." if prefix_slug else ""
319
319
  filename = (
320
- f"{prefix}{slugify(description, separator='_')}."
321
- f"{new_timestamped_uid()}.{file_ext.lstrip('.')}"
320
+ f"{prefix}{slugify_snake(description)}.{new_timestamped_uid()}.{file_ext.lstrip('.')}"
322
321
  )
323
322
  path = _log_settings.log_objects_dir / filename
324
323
  with atomic_output_file(path, make_parents=True) as tmp_filename:
@@ -1,14 +1,21 @@
1
1
  import logging
2
2
  import sys
3
- from logging import FileHandler, Formatter
3
+ from logging import FileHandler, Formatter, LogRecord
4
4
  from pathlib import Path
5
5
 
6
6
  from kash.config.settings import LogLevel
7
+ from kash.config.suppress_warnings import demote_warnings
7
8
 
8
9
  # Basic logging setup for non-interactive logging, like on a server.
9
10
  # For richer logging, see logger.py.
10
11
 
11
12
 
13
+ class SuppressedWarningsStreamHandler(logging.StreamHandler):
14
+ def emit(self, record: LogRecord):
15
+ demote_warnings(record, level=logging.DEBUG)
16
+ super().emit(record)
17
+
18
+
12
19
  def basic_file_handler(path: Path, level: LogLevel) -> logging.FileHandler:
13
20
  handler = logging.FileHandler(path)
14
21
  handler.setLevel(level.value)
@@ -17,7 +24,7 @@ def basic_file_handler(path: Path, level: LogLevel) -> logging.FileHandler:
17
24
 
18
25
 
19
26
  def basic_stderr_handler(level: LogLevel) -> logging.StreamHandler:
20
- handler = logging.StreamHandler(stream=sys.stderr)
27
+ handler = SuppressedWarningsStreamHandler(stream=sys.stderr)
21
28
  handler.setLevel(level.value)
22
29
  handler.setFormatter(Formatter("%(asctime)s %(levelname).1s %(name)s - %(message)s"))
23
30
  return handler
@@ -7,7 +7,7 @@ if TYPE_CHECKING:
7
7
 
8
8
 
9
9
  def create_server_config(
10
- app: Callable[..., Any], host: str, port: int, server_name: str, log_path: Path
10
+ app: Callable[..., Any], host: str, port: int, _server_name: str, log_path: Path
11
11
  ) -> "uvicorn.Config":
12
12
  """
13
13
  Create a common server configuration for both local and MCP servers.
@@ -43,11 +43,11 @@ def create_server_config(
43
43
  "uvicorn": {"handlers": ["default"], "level": "INFO", "propagate": False},
44
44
  "uvicorn.error": {"handlers": ["default"], "level": "INFO", "propagate": False},
45
45
  "uvicorn.access": {"handlers": ["default"], "level": "INFO", "propagate": False},
46
- f"kash.{server_name}": {
47
- "handlers": ["default"],
48
- "level": "INFO",
49
- "propagate": False,
50
- },
46
+ # f"kash.{server_name}": {
47
+ # "handlers": ["default"],
48
+ # "level": "INFO",
49
+ # "propagate": False,
50
+ # },
51
51
  },
52
52
  },
53
53
  )
kash/config/settings.py CHANGED
@@ -3,10 +3,10 @@ from enum import Enum
3
3
  from functools import cache
4
4
  from logging import DEBUG, ERROR, INFO, WARNING
5
5
  from pathlib import Path
6
- from typing import overload
7
6
 
8
7
  from pydantic.dataclasses import dataclass
9
8
 
9
+ from kash.config.env_settings import KashEnv
10
10
  from kash.utils.common.atomic_var import AtomicVar
11
11
 
12
12
  APP_NAME = "kash"
@@ -23,61 +23,53 @@ RECOMMENDED_API_KEYS = [
23
23
  ]
24
24
 
25
25
 
26
- @overload
27
- def path_from_env(env_name: str, default: None) -> None: ...
28
-
29
-
30
- @overload
31
- def path_from_env(env_name: str, default: Path) -> Path: ...
32
-
33
-
34
- def path_from_env(env_name: str, default: Path | None) -> Path | None:
35
- value = os.environ.get(env_name)
36
- if value:
37
- return Path(value).expanduser().resolve()
38
- else:
39
- return default.expanduser().resolve() if default else None
40
-
41
-
42
26
  def get_ws_root_dir() -> Path:
43
27
  """Default root directory for kash workspaces."""
44
- return path_from_env("KASH_WS_ROOT", Path("~/Kash").expanduser().resolve())
28
+ return KashEnv.KASH_WS_ROOT.read_path(Path("~/Kash"))
45
29
 
46
30
 
47
31
  def get_global_ws_dir() -> Path:
48
32
  """Default global workspace directory."""
49
- kash_ws_dir = path_from_env("KASH_GLOBAL_WS", None)
33
+ kash_ws_dir = KashEnv.KASH_GLOBAL_WS.read_path(None)
50
34
  if kash_ws_dir:
51
35
  return kash_ws_dir
52
36
  else:
53
37
  return get_ws_root_dir() / GLOBAL_WS_NAME
54
38
 
55
39
 
40
+ def get_system_config_dir() -> Path:
41
+ return Path("~/.config/kash").expanduser().resolve()
42
+
43
+
44
+ def get_rcfile_path() -> Path:
45
+ return get_system_config_dir() / "kashrc"
46
+
47
+
56
48
  def get_system_logs_dir() -> Path:
57
49
  """Default global and system logs directory (for server logs, etc)."""
58
- return path_from_env("KASH_SYSTEM_LOGS_DIR", get_ws_root_dir() / "logs")
50
+ return KashEnv.KASH_SYSTEM_LOGS_DIR.read_path(get_ws_root_dir() / "logs")
59
51
 
60
52
 
61
53
  def get_system_cache_dir() -> Path:
62
54
  """Default global and system cache directory (for global media, content, etc)."""
63
- return path_from_env("KASH_SYSTEM_CACHE_DIR", get_ws_root_dir() / "cache")
55
+ return KashEnv.KASH_SYSTEM_CACHE_DIR.read_path(get_ws_root_dir() / "cache")
56
+
57
+
58
+ def get_system_env_path() -> Path:
59
+ return get_system_config_dir() / "env.local"
64
60
 
65
61
 
66
62
  def get_mcp_ws_dir() -> Path | None:
67
63
  """
68
64
  Get the directory for the MCP workspace, if set.
69
65
  """
70
- mcp_dir = os.environ.get("KASH_MCP_WS")
66
+ mcp_dir = KashEnv.KASH_MCP_WS.read_str()
71
67
  if mcp_dir:
72
68
  return Path(mcp_dir).expanduser().resolve()
73
69
  else:
74
70
  return None
75
71
 
76
72
 
77
- def get_rcfile_path() -> Path:
78
- return Path("~/.kashrc").expanduser().resolve()
79
-
80
-
81
73
  MEDIA_CACHE_NAME = "media"
82
74
  CONTENT_CACHE_NAME = "content"
83
75
 
@@ -91,7 +83,12 @@ LOCAL_SERVER_PORT_START = 4470
91
83
  LOCAL_SERVER_PORTS_MAX = 30
92
84
 
93
85
 
94
- SERVER_LOG_FILE = str(get_system_logs_dir() / "{name}_{port}.log")
86
+ LOCAL_SERVER_LOG_NAME = "local_server"
87
+
88
+
89
+ @cache
90
+ def local_server_log_path() -> Path:
91
+ return resolve_and_create_dirs(get_system_logs_dir() / f"{LOCAL_SERVER_LOG_NAME}.log")
95
92
 
96
93
 
97
94
  class LogLevel(Enum):
@@ -117,7 +114,7 @@ class LogLevel(Enum):
117
114
  return self.name
118
115
 
119
116
 
120
- DEFAULT_LOG_LEVEL = LogLevel.parse(os.environ.get("KASH_LOG_LEVEL", "warning"))
117
+ DEFAULT_LOG_LEVEL = LogLevel.parse(KashEnv.KASH_LOG_LEVEL.read_str("warning"))
121
118
 
122
119
 
123
120
  def resolve_and_create_dirs(path: Path | str, is_dir: bool = False) -> Path:
@@ -201,12 +198,6 @@ class Settings:
201
198
  """If true, use Nerd Icons in file listings. Requires a compatible font."""
202
199
 
203
200
 
204
- @cache
205
- def server_log_file_path(name: str, port: int | str) -> Path:
206
- # Use a different log file for each port (server instance).
207
- return resolve_and_create_dirs(SERVER_LOG_FILE.format(name=name, port=port))
208
-
209
-
210
201
  # Initial default settings.
211
202
  _settings = AtomicVar(
212
203
  Settings(
kash/config/setup.py CHANGED
@@ -1,12 +1,20 @@
1
1
  from enum import Enum
2
2
  from functools import cache
3
+ from pathlib import Path
3
4
  from typing import Any
4
5
 
6
+ from kash.config.logger_basic import basic_logging_setup
7
+ from kash.config.settings import LogLevel, get_system_config_dir
8
+
5
9
 
6
10
  @cache
7
- def setup(rich_logging: bool):
11
+ def setup(rich_logging: bool, file_log_path: Path | None = None, level: LogLevel = LogLevel.info):
8
12
  """
9
- One-time setup of essential keys, directories, and configs. Idempotent.
13
+ One-time top-level setup of essential logging, keys, directories, and configs.
14
+ Idempotent.
15
+
16
+ If rich_logging is True, then rich logging with warnings only for console use.
17
+ If rich_logging is False, then use basic logging to a file and stderr.
10
18
  """
11
19
  from kash.config.logger import reload_rich_logging_setup
12
20
  from kash.shell.clideps.dotenv_utils import load_dotenv_paths
@@ -14,12 +22,14 @@ def setup(rich_logging: bool):
14
22
 
15
23
  if rich_logging:
16
24
  reload_rich_logging_setup()
25
+ else:
26
+ basic_logging_setup(file_log_path=file_log_path, level=level)
17
27
 
18
28
  _lib_setup()
19
29
 
20
30
  add_stacktrace_handler()
21
31
 
22
- load_dotenv_paths()
32
+ load_dotenv_paths(True, True, get_system_config_dir())
23
33
 
24
34
 
25
35
  def _lib_setup():
@@ -1,27 +1,45 @@
1
1
  import logging
2
2
  import warnings
3
3
  from logging import LogRecord
4
+ from typing import Any
5
+ from warnings import formatwarning
6
+
7
+ FILTER_PATTERNS = [
8
+ "deprecated",
9
+ "Deprecation",
10
+ "PydanticDeprecatedSince20",
11
+ "pydantic",
12
+ # "pydub",
13
+ ]
14
+ """Warning messages to always suppress in console output."""
15
+
16
+
17
+ def should_suppress(message: Any):
18
+ return any(pattern in str(message) for pattern in FILTER_PATTERNS)
4
19
 
5
20
 
6
21
  def filter_warnings():
22
+ for pattern in FILTER_PATTERNS:
23
+ warnings.filterwarnings("ignore", message=f".*{pattern}.*")
7
24
  warnings.filterwarnings("ignore", category=DeprecationWarning)
8
- warnings.filterwarnings("ignore", message=".*deprecated.*")
9
- warnings.filterwarnings("ignore", message=".*Deprecation.*")
10
- warnings.filterwarnings("ignore", message=".*PydanticDeprecatedSince20.*")
11
- warnings.filterwarnings("ignore", module="pydub")
12
- warnings.filterwarnings("ignore", module="pydantic")
13
25
  warnings.filterwarnings("ignore", category=RuntimeWarning, module="xonsh.tools")
14
26
 
27
+ log = logging.getLogger("warnings")
28
+
29
+ def custom_showwarning(message, category, filename, lineno, _file=None, line=None):
30
+ if not should_suppress(message) and not should_suppress(category):
31
+ log.warning(formatwarning(message, category, filename, lineno, line))
32
+
33
+ # Override system default, which writes to stderr.
34
+ warnings.showwarning = custom_showwarning
35
+
15
36
 
16
37
  filter_warnings()
17
38
 
18
39
 
19
- # Doing it even more brute force since the approach above often doesn't work.
20
- def demote_warnings(record: LogRecord):
40
+ # An even more brute force approach if the approach above doesn't work.
41
+ def demote_warnings(record: LogRecord, level: int = logging.INFO):
21
42
  if record.levelno == logging.WARNING:
22
43
  # Check for any warning patterns that we're filtering in filter_warnings
23
- if any(
24
- pattern in record.msg
25
- for pattern in ["deprecated", "Deprecation", "PydanticDeprecatedSince20"]
26
- ) or any(module in str(record.pathname) for module in ["pydub", "pydantic", "xonsh.tools"]):
27
- record.levelno = logging.INFO
44
+ if should_suppress(record.msg) or should_suppress(record.module):
45
+ record.levelno = level
@@ -65,15 +65,19 @@ These are for `kash-media` but you can use a `kash-shell` for a more basic setup
65
65
  For macOS, you can again use brew:
66
66
 
67
67
  ```shell
68
- # Install pyenv, pipx, and other tools:
69
68
  brew update
70
- brew install ripgrep bat eza hexyl imagemagick libmagic ffmpeg
69
+ brew install libmagic ffmpeg ripgrep bat eza hexyl imagemagick zoxide
71
70
  ```
72
71
 
73
72
  For Ubuntu:
74
73
 
75
74
  ```shell
76
- apt install ripgrep bat eza hexyl imagemagick libmagic ffmpeg
75
+ sudo apt-get update
76
+ sudo apt-get install -y libgl1 ffmpeg libmagic-dev
77
+ # For the additional command-line tools, pixi is better on Ubuntu:
78
+ curl -fsSL https://pixi.sh/install.sh | sh
79
+ . ~/.bashrc
80
+ pixi global install ripgrep bat eza hexyl imagemagick zoxide
77
81
  ```
78
82
 
79
83
  For Windows or other platforms, see the uv instructions.
@@ -1,8 +1,3 @@
1
- *Thank you for trying kash.
2
- It is new and has rough edges.
3
- I'd love to hear from you with issues, bugs, and ideas.
4
- Discuss at github.com/jlevy/kash and contact info at: github.com/jlevy*
5
-
6
- **Warning:** *This is a shell so actions and commands can be destructive or dangerous,
7
- including deleting files.
8
- Exercise caution and review commands carefully!*
1
+ **Important:** *Kash will not execute commands without confirmation.
2
+ But this is a shell so commands can be destructive or dangerous.
3
+ Review commands carefully!*
@@ -5,5 +5,9 @@ You may simply ask a question and the kash assistant will help you.
5
5
  Press **space** (or type **?**), then write your question or request.
6
6
  Use `logs` for detailed logs.
7
7
 
8
+ *I'd love to hear from you with issues, bugs, and ideas.
9
+ Discuss at github.com/jlevy/kash or contact me github.com/jlevy or x.com/ojoshe (DMs
10
+ open).*
11
+
8
12
  Try: `What is kash?`, `How can I transcribe a YouTube video?`, `getting_started`, `faq`,
9
13
  `self_check`, `self_configure`
@@ -8,7 +8,7 @@ from kash.help.help_types import RecipeScript, RecipeSnippet
8
8
 
9
9
  log = get_logger(__name__)
10
10
 
11
- RECIPE_EXT = ".ksh"
11
+ RECIPE_EXT = ".sh"
12
12
 
13
13
  RECIPES_DIR = Path(__file__).parent / "recipes"
14
14
 
@@ -1,5 +1,5 @@
1
1
 
2
- # Additional snippets not already in tldr_standard_commands.ksh
2
+ # Additional snippets not already in tldr_standard_commands.sh
3
3
 
4
4
  # Show all current processes running `python`
5
5
  procs python
@@ -5,6 +5,7 @@ from chopdiff.transforms import WindowSettings, accept_all, filtered_transform
5
5
  from flowmark import fill_markdown
6
6
 
7
7
  from kash.config.logger import get_logger
8
+ from kash.config.settings import get_system_config_dir
8
9
  from kash.llm_utils import LLMName
9
10
  from kash.llm_utils.fuzzy_parsing import strip_markdown_fence
10
11
  from kash.llm_utils.llm_completion import llm_template_completion
@@ -47,7 +48,7 @@ def windowed_llm_transform(
47
48
 
48
49
 
49
50
  def llm_transform_str(options: LLMOptions, input_str: str, check_no_results: bool = True) -> str:
50
- load_dotenv_paths()
51
+ load_dotenv_paths(True, True, get_system_config_dir())
51
52
 
52
53
  if options.windowing and options.windowing.size:
53
54
  log.message(
@@ -97,10 +97,6 @@ class FileStore(Workspace):
97
97
 
98
98
  add_to_ignore(self.base_dir / ".gitignore", [".kash/"])
99
99
 
100
- self.vector_index = None
101
- # FIXME: Add vector index support dynamically if available.
102
- # self.vector_index = WsVectorIndex(self.base_dir / self.dirs.index_dir)
103
-
104
100
  # Initialize selection with history support.
105
101
  self.selections = SelectionHistory.init(self.base_dir / self.dirs.selection_yml)
106
102
 
@@ -48,7 +48,14 @@ def write_item(item: Item, path: Path):
48
48
  format = Format(item.format)
49
49
  if format == Format.html:
50
50
  fm_style = FmStyle.html
51
- elif format in [Format.python, Format.kash_script, Format.diff, Format.csv, Format.log]:
51
+ elif format in [
52
+ Format.python,
53
+ Format.shellscript,
54
+ Format.xonsh,
55
+ Format.diff,
56
+ Format.csv,
57
+ Format.log,
58
+ ]:
52
59
  fm_style = FmStyle.hash
53
60
  elif format == Format.json:
54
61
  fm_style = FmStyle.slash
kash/help/assistant.py CHANGED
@@ -318,7 +318,7 @@ def shell_context_assistance(
318
318
  type=ItemType.script,
319
319
  title=f"Assistant Answer: {capitalize_cms(input)}",
320
320
  description=response_text,
321
- format=Format.kash_script,
321
+ format=Format.shellscript,
322
322
  body=script.script_str,
323
323
  )
324
324
  ws = current_ws()