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.
- kash/commands/base/debug_commands.py +2 -2
- kash/commands/base/general_commands.py +8 -4
- kash/commands/base/logs_commands.py +2 -7
- kash/commands/workspace/workspace_commands.py +1 -1
- kash/concepts/cosine.py +2 -1
- kash/concepts/text_similarity.py +3 -58
- kash/config/env_settings.py +59 -0
- kash/config/logger.py +2 -3
- kash/config/logger_basic.py +9 -2
- kash/config/server_config.py +6 -6
- kash/config/settings.py +25 -34
- kash/config/setup.py +13 -3
- kash/config/suppress_warnings.py +30 -12
- kash/docs/markdown/topics/a2_installation.md +7 -3
- kash/docs/markdown/warning.md +3 -8
- kash/docs/markdown/welcome.md +4 -0
- kash/docs_base/load_recipe_snippets.py +1 -1
- kash/docs_base/recipes/{general_system_commands.ksh → general_system_commands.sh} +1 -1
- kash/exec/llm_transforms.py +2 -1
- kash/file_storage/file_store.py +0 -4
- kash/file_storage/item_file_format.py +8 -1
- kash/help/assistant.py +1 -1
- kash/help/help_printing.py +15 -7
- kash/help/tldr_help.py +5 -3
- kash/llm_utils/llm_completion.py +2 -2
- kash/llm_utils/llms.py +9 -9
- kash/local_server/local_server.py +50 -43
- kash/local_server/local_server_commands.py +15 -15
- kash/local_server/local_server_routes.py +2 -2
- kash/mcp/mcp_cli.py +52 -15
- kash/mcp/mcp_server_commands.py +4 -5
- kash/mcp/mcp_server_routes.py +9 -6
- kash/mcp/mcp_server_sse.py +59 -34
- kash/mcp/mcp_server_stdio.py +0 -8
- kash/media_base/audio_processing.py +81 -7
- kash/media_base/media_cache.py +10 -10
- kash/media_base/speech_transcription.py +2 -1
- kash/model/items_model.py +12 -6
- kash/shell/clideps/api_keys.py +3 -2
- kash/shell/clideps/dotenv_setup.py +3 -2
- kash/shell/clideps/dotenv_utils.py +15 -6
- kash/shell/clideps/pkg_deps.py +27 -2
- kash/utils/common/atomic_var.py +16 -3
- kash/utils/common/url.py +53 -25
- kash/utils/file_utils/{dir_size.py → dir_info.py} +25 -4
- kash/utils/file_utils/file_ext.py +2 -3
- kash/utils/file_utils/file_formats.py +26 -0
- kash/utils/file_utils/file_formats_model.py +47 -19
- kash/utils/file_utils/filename_parsing.py +10 -4
- kash/workspaces/source_items.py +4 -1
- kash/workspaces/workspace_output.py +11 -4
- kash/xonsh_custom/custom_shell.py +1 -1
- kash/xonsh_custom/load_into_xonsh.py +5 -3
- {kash_shell-0.3.8.dist-info → kash_shell-0.3.9.dist-info}/METADATA +19 -7
- {kash_shell-0.3.8.dist-info → kash_shell-0.3.9.dist-info}/RECORD +60 -59
- {kash_shell-0.3.8.dist-info → kash_shell-0.3.9.dist-info}/entry_points.txt +1 -1
- /kash/docs_base/recipes/{python_dev_commands.ksh → python_dev_commands.sh} +0 -0
- /kash/docs_base/recipes/{tldr_standard_commands.ksh → tldr_standard_commands.sh} +0 -0
- {kash_shell-0.3.8.dist-info → kash_shell-0.3.9.dist-info}/WHEEL +0 -0
- {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
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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.
|
|
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:
|
kash/concepts/text_similarity.py
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
from typing import cast
|
|
2
2
|
|
|
3
3
|
import litellm
|
|
4
|
-
|
|
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
|
|
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}{
|
|
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:
|
kash/config/logger_basic.py
CHANGED
|
@@ -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 =
|
|
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
|
kash/config/server_config.py
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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():
|
kash/config/suppress_warnings.py
CHANGED
|
@@ -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
|
-
#
|
|
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
|
|
24
|
-
|
|
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
|
|
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
|
|
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.
|
kash/docs/markdown/warning.md
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
*
|
|
2
|
-
|
|
3
|
-
|
|
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!*
|
kash/docs/markdown/welcome.md
CHANGED
|
@@ -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`
|
kash/exec/llm_transforms.py
CHANGED
|
@@ -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(
|
kash/file_storage/file_store.py
CHANGED
|
@@ -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 [
|
|
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.
|
|
321
|
+
format=Format.shellscript,
|
|
322
322
|
body=script.script_str,
|
|
323
323
|
)
|
|
324
324
|
ws = current_ws()
|