kash-shell 0.3.7__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 +4 -3
- kash/commands/base/general_commands.py +33 -41
- kash/commands/base/logs_commands.py +4 -8
- kash/commands/base/model_commands.py +16 -14
- kash/commands/base/search_command.py +2 -2
- kash/commands/workspace/workspace_commands.py +2 -3
- kash/concepts/cosine.py +2 -1
- kash/concepts/text_similarity.py +3 -58
- kash/config/env_settings.py +59 -0
- kash/config/logger.py +46 -38
- kash/config/logger_basic.py +10 -2
- kash/config/server_config.py +6 -6
- kash/config/settings.py +42 -47
- kash/config/setup.py +14 -4
- 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 +3 -2
- kash/file_storage/file_store.py +0 -4
- kash/file_storage/item_file_format.py +8 -1
- kash/file_storage/metadata_dirs.py +2 -2
- kash/help/assistant.py +1 -1
- kash/help/help_pages.py +1 -1
- kash/help/help_printing.py +16 -9
- kash/help/tldr_help.py +5 -3
- kash/llm_utils/__init__.py +1 -0
- kash/llm_utils/llm_api_keys.py +39 -0
- 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 +53 -16
- kash/mcp/mcp_server_commands.py +10 -6
- 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/services/local_file_media.py +2 -2
- kash/media_base/speech_transcription.py +3 -2
- kash/model/items_model.py +12 -6
- kash/shell/clideps/api_keys.py +100 -0
- kash/shell/{input/collect_dotenv.py → clideps/dotenv_setup.py} +41 -6
- kash/{config → shell/clideps}/dotenv_utils.py +16 -6
- kash/shell/{utils/sys_tool_deps.py → clideps/pkg_deps.py} +63 -99
- kash/shell/clideps/platforms.py +11 -0
- kash/shell/clideps/terminal_features.py +56 -0
- kash/shell/output/shell_formatting.py +106 -0
- kash/shell/output/shell_output.py +1 -94
- kash/shell/utils/native_utils.py +11 -10
- 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 +28 -2
- 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 +13 -5
- kash/workspaces/workspaces.py +9 -10
- kash/xonsh_custom/custom_shell.py +1 -1
- kash/xonsh_custom/load_into_xonsh.py +7 -5
- kash/xonsh_custom/xonsh_modern_tools.py +5 -5
- {kash_shell-0.3.7.dist-info → kash_shell-0.3.9.dist-info}/METADATA +19 -7
- {kash_shell-0.3.7.dist-info → kash_shell-0.3.9.dist-info}/RECORD +74 -69
- {kash_shell-0.3.7.dist-info → kash_shell-0.3.9.dist-info}/entry_points.txt +1 -1
- kash/config/api_keys.py +0 -109
- /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.7.dist-info → kash_shell-0.3.9.dist-info}/WHEEL +0 -0
- {kash_shell-0.3.7.dist-info → kash_shell-0.3.9.dist-info}/licenses/LICENSE +0 -0
|
@@ -12,7 +12,8 @@ from kash.help.function_param_info import annotate_param_info
|
|
|
12
12
|
from kash.help.recommended_commands import RECOMMENDED_TLDR_COMMANDS
|
|
13
13
|
from kash.model.params_model import Param
|
|
14
14
|
from kash.shell.output.kerm_codes import IframePopover, TextTooltip
|
|
15
|
-
from kash.shell.output.
|
|
15
|
+
from kash.shell.output.shell_formatting import format_name_and_value
|
|
16
|
+
from kash.shell.output.shell_output import PrintHooks, console_pager, cprint
|
|
16
17
|
from kash.utils.errors import InvalidInput
|
|
17
18
|
|
|
18
19
|
log = get_logger(__name__)
|
|
@@ -178,7 +179,7 @@ def reload_system() -> None:
|
|
|
178
179
|
the local the local server. Not perfect! But sometimes useful for development.
|
|
179
180
|
"""
|
|
180
181
|
import kash
|
|
181
|
-
from kash.local_server.local_server import
|
|
182
|
+
from kash.local_server.local_server import restart_ui_server
|
|
182
183
|
from kash.utils.common.import_utils import recursive_reload
|
|
183
184
|
|
|
184
185
|
module = kash
|
|
@@ -196,7 +197,7 @@ def reload_system() -> None:
|
|
|
196
197
|
log.info("Reloaded modules: %s", ", ".join(package_names))
|
|
197
198
|
log.message("Reloaded %s modules from %s.", len(package_names), module.__name__)
|
|
198
199
|
|
|
199
|
-
|
|
200
|
+
restart_ui_server()
|
|
200
201
|
|
|
201
202
|
# TODO Re-register commands and actions.
|
|
202
203
|
|
|
@@ -1,34 +1,34 @@
|
|
|
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
|
-
from kash.config.api_keys import (
|
|
4
|
-
RECOMMENDED_APIS,
|
|
5
|
-
Api,
|
|
6
|
-
get_all_configured_models,
|
|
7
|
-
load_dotenv_paths,
|
|
8
|
-
print_api_key_setup,
|
|
9
|
-
)
|
|
10
|
-
from kash.config.dotenv_utils import env_var_is_set
|
|
11
5
|
from kash.config.logger import get_logger
|
|
6
|
+
from kash.config.settings import RECOMMENDED_API_KEYS, get_system_config_dir
|
|
12
7
|
from kash.docs.all_docs import all_docs
|
|
13
8
|
from kash.exec import kash_command
|
|
14
9
|
from kash.help.tldr_help import tldr_refresh_cache
|
|
10
|
+
from kash.llm_utils.llm_api_keys import get_all_configured_models
|
|
15
11
|
from kash.model.params_model import (
|
|
16
12
|
DEFAULT_CAREFUL_LLM,
|
|
17
13
|
DEFAULT_FAST_LLM,
|
|
18
14
|
DEFAULT_STANDARD_LLM,
|
|
19
15
|
DEFAULT_STRUCTURED_LLM,
|
|
20
16
|
)
|
|
21
|
-
from kash.shell.
|
|
17
|
+
from kash.shell.clideps.api_keys import (
|
|
18
|
+
ApiEnvKey,
|
|
19
|
+
load_dotenv_paths,
|
|
20
|
+
print_api_key_setup,
|
|
21
|
+
)
|
|
22
|
+
from kash.shell.clideps.dotenv_setup import interactive_dotenv_setup
|
|
23
|
+
from kash.shell.clideps.pkg_deps import pkg_check
|
|
24
|
+
from kash.shell.clideps.terminal_features import terminal_check
|
|
22
25
|
from kash.shell.input.input_prompts import input_choice
|
|
26
|
+
from kash.shell.output.shell_formatting import format_name_and_value
|
|
23
27
|
from kash.shell.output.shell_output import (
|
|
24
28
|
PrintHooks,
|
|
25
29
|
cprint,
|
|
26
|
-
format_failure,
|
|
27
|
-
format_name_and_value,
|
|
28
|
-
format_success,
|
|
29
30
|
print_h2,
|
|
30
31
|
)
|
|
31
|
-
from kash.shell.utils.sys_tool_deps import sys_tool_check, terminal_feature_check
|
|
32
32
|
from kash.shell.version import get_full_version_name
|
|
33
33
|
from kash.utils.errors import InvalidState
|
|
34
34
|
from kash.workspaces.workspaces import current_ws
|
|
@@ -50,8 +50,8 @@ def self_check(brief: bool = False) -> None:
|
|
|
50
50
|
Self-check kash setup, including termal settings, tools, and API keys.
|
|
51
51
|
"""
|
|
52
52
|
if brief:
|
|
53
|
-
|
|
54
|
-
print_api_key_setup(once=False)
|
|
53
|
+
terminal_check().print_term_info()
|
|
54
|
+
print_api_key_setup(recommended_keys=RECOMMENDED_API_KEYS, once=False)
|
|
55
55
|
check_system_tools(brief=brief)
|
|
56
56
|
tldr_refresh_cache()
|
|
57
57
|
try:
|
|
@@ -63,7 +63,7 @@ def self_check(brief: bool = False) -> None:
|
|
|
63
63
|
else:
|
|
64
64
|
version()
|
|
65
65
|
cprint()
|
|
66
|
-
|
|
66
|
+
terminal_check().print_term_info()
|
|
67
67
|
cprint()
|
|
68
68
|
list_apis()
|
|
69
69
|
cprint()
|
|
@@ -86,28 +86,18 @@ def self_check(brief: bool = False) -> None:
|
|
|
86
86
|
@kash_command
|
|
87
87
|
def self_configure(all: bool = False, update: bool = False) -> None:
|
|
88
88
|
"""
|
|
89
|
-
Interactively configure
|
|
90
|
-
|
|
91
|
-
:param all: Configure all known API keys (instead of just recommended ones).
|
|
92
|
-
:param update: Update values even if they are already set.
|
|
89
|
+
Interactively configure API keys and preferred models.
|
|
93
90
|
"""
|
|
94
91
|
|
|
92
|
+
if all:
|
|
93
|
+
api_keys = [key.value for key in ApiEnvKey]
|
|
94
|
+
else:
|
|
95
|
+
api_keys = RECOMMENDED_API_KEYS
|
|
95
96
|
# Show APIs before starting.
|
|
96
97
|
list_apis()
|
|
97
98
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if not update:
|
|
101
|
-
keys = [key for key in keys if not env_var_is_set(key)]
|
|
102
|
-
|
|
103
|
-
cprint()
|
|
104
|
-
print_h2("Configuring .env file")
|
|
105
|
-
if keys:
|
|
106
|
-
cprint(format_failure(f"API keys needed: {', '.join(keys)}"))
|
|
107
|
-
fill_missing_dotenv(keys)
|
|
108
|
-
reload_env()
|
|
109
|
-
else:
|
|
110
|
-
cprint(format_success("All requested API keys are set!"))
|
|
99
|
+
interactive_dotenv_setup(api_keys, update=update)
|
|
100
|
+
reload_env()
|
|
111
101
|
|
|
112
102
|
cprint()
|
|
113
103
|
ws = current_ws()
|
|
@@ -170,15 +160,15 @@ def check_system_tools(warn_only: bool = False, brief: bool = False) -> None:
|
|
|
170
160
|
:param brief: Print summary as a single line.
|
|
171
161
|
"""
|
|
172
162
|
if warn_only:
|
|
173
|
-
|
|
163
|
+
pkg_check().warn_if_missing()
|
|
174
164
|
else:
|
|
175
165
|
if brief:
|
|
176
|
-
cprint(
|
|
166
|
+
cprint(pkg_check().status())
|
|
177
167
|
else:
|
|
178
168
|
print_h2("Installed System Tools")
|
|
179
|
-
cprint(
|
|
169
|
+
cprint(pkg_check().formatted())
|
|
180
170
|
cprint()
|
|
181
|
-
|
|
171
|
+
pkg_check().warn_if_missing()
|
|
182
172
|
|
|
183
173
|
|
|
184
174
|
@kash_command
|
|
@@ -187,10 +177,10 @@ def reload_env() -> None:
|
|
|
187
177
|
Reload the environment variables from the .env file.
|
|
188
178
|
"""
|
|
189
179
|
|
|
190
|
-
env_paths = load_dotenv_paths()
|
|
180
|
+
env_paths = load_dotenv_paths(True, True, get_system_config_dir())
|
|
191
181
|
if env_paths:
|
|
192
182
|
cprint("Reloaded environment variables")
|
|
193
|
-
print_api_key_setup()
|
|
183
|
+
print_api_key_setup(RECOMMENDED_API_KEYS)
|
|
194
184
|
else:
|
|
195
185
|
raise InvalidState("No .env file found")
|
|
196
186
|
|
|
@@ -209,7 +199,9 @@ def kits() -> None:
|
|
|
209
199
|
else:
|
|
210
200
|
cprint("Currently imported kits:")
|
|
211
201
|
for kit in get_loaded_kits().values():
|
|
212
|
-
cprint(
|
|
202
|
+
cprint(
|
|
203
|
+
format_name_and_value(f"{kit.name} kit", str(kit.path or ""), text_wrap=Wrap.NONE)
|
|
204
|
+
)
|
|
213
205
|
|
|
214
206
|
|
|
215
207
|
@kash_command
|
|
@@ -222,5 +214,5 @@ def settings() -> None:
|
|
|
222
214
|
settings = global_settings()
|
|
223
215
|
print_h2("Global Settings")
|
|
224
216
|
for field, value in settings.__dict__.items():
|
|
225
|
-
cprint(format_name_and_value(field, str(value)))
|
|
217
|
+
cprint(format_name_and_value(field, str(value), text_wrap=Wrap.NONE))
|
|
226
218
|
PrintHooks.spacer()
|
|
@@ -7,11 +7,11 @@ 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
|
-
from kash.shell.output.
|
|
13
|
+
from kash.shell.output.shell_formatting import format_name_and_value
|
|
14
|
+
from kash.shell.output.shell_output import cprint, print_status
|
|
15
15
|
from kash.shell.utils.native_utils import tail_file
|
|
16
16
|
from kash.utils.common.format_utils import fmt_loc
|
|
17
17
|
|
|
@@ -85,8 +85,4 @@ def log_settings() -> None:
|
|
|
85
85
|
cprint(format_name_and_value("log_objects_dir", str(settings.log_objects_dir)))
|
|
86
86
|
cprint(format_name_and_value("log_file_level", settings.log_file_level.name))
|
|
87
87
|
cprint(format_name_and_value("log_console_level", settings.log_console_level.name))
|
|
88
|
-
cprint(
|
|
89
|
-
format_name_and_value(
|
|
90
|
-
"server_log_file", str(server_log_file_path(global_settings().local_server_port))
|
|
91
|
-
)
|
|
92
|
-
)
|
|
88
|
+
cprint(format_name_and_value("server_log_file_path", str(local_server_log_path())))
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
from flowmark import Wrap
|
|
2
2
|
from rich.text import Text
|
|
3
3
|
|
|
4
|
-
from kash.config.api_keys import Api
|
|
5
|
-
from kash.config.dotenv_utils import env_var_is_set
|
|
6
|
-
from kash.config.text_styles import format_success_emoji
|
|
7
4
|
from kash.exec.command_registry import kash_command
|
|
8
|
-
from kash.llm_utils import LLM
|
|
9
|
-
from kash.shell.
|
|
10
|
-
|
|
5
|
+
from kash.llm_utils import LLM, api_for_model
|
|
6
|
+
from kash.shell.clideps.api_keys import ApiEnvKey
|
|
7
|
+
from kash.shell.clideps.dotenv_utils import env_var_is_set
|
|
8
|
+
from kash.shell.output.shell_formatting import (
|
|
11
9
|
format_failure,
|
|
12
10
|
format_name_and_value,
|
|
13
11
|
format_success,
|
|
12
|
+
format_success_emoji,
|
|
14
13
|
format_success_or_failure,
|
|
14
|
+
)
|
|
15
|
+
from kash.shell.output.shell_output import (
|
|
16
|
+
cprint,
|
|
15
17
|
print_h2,
|
|
16
18
|
)
|
|
17
19
|
|
|
@@ -23,11 +25,11 @@ def list_models() -> None:
|
|
|
23
25
|
"""
|
|
24
26
|
print_h2("Models")
|
|
25
27
|
for model in LLM.all_names():
|
|
26
|
-
api =
|
|
27
|
-
have_key = bool(api and env_var_is_set(api.
|
|
28
|
+
api = api_for_model(model)
|
|
29
|
+
have_key = bool(api and env_var_is_set(api.value))
|
|
28
30
|
if api:
|
|
29
31
|
provider_msg = format_success(f"provider {api.name}")
|
|
30
|
-
key_message = format_success_or_failure(have_key, f"{api.
|
|
32
|
+
key_message = format_success_or_failure(have_key, f"{api.value}")
|
|
31
33
|
else:
|
|
32
34
|
provider_msg = format_failure("provider not recognized")
|
|
33
35
|
key_message = format_failure("unknown API key")
|
|
@@ -46,11 +48,11 @@ def list_apis() -> None:
|
|
|
46
48
|
List and check configuration for all APIs.
|
|
47
49
|
"""
|
|
48
50
|
print_h2("API keys")
|
|
49
|
-
for api in
|
|
50
|
-
emoji = format_success_emoji(env_var_is_set(api.
|
|
51
|
+
for api in ApiEnvKey:
|
|
52
|
+
emoji = format_success_emoji(env_var_is_set(api.value))
|
|
51
53
|
message = (
|
|
52
|
-
f"API key {api.
|
|
53
|
-
if env_var_is_set(api.
|
|
54
|
-
else f"API key {api.
|
|
54
|
+
f"API key {api.value} found"
|
|
55
|
+
if env_var_is_set(api.value)
|
|
56
|
+
else f"API key {api.value} not found"
|
|
55
57
|
)
|
|
56
58
|
cprint(Text.assemble(emoji, format_name_and_value(api.name, message)))
|
|
@@ -3,8 +3,8 @@ from pathlib import Path
|
|
|
3
3
|
from kash.config.logger import get_logger
|
|
4
4
|
from kash.exec import assemble_path_args, kash_command
|
|
5
5
|
from kash.exec_model.shell_model import ShellResult
|
|
6
|
+
from kash.shell.clideps.pkg_deps import Pkg, pkg_check
|
|
6
7
|
from kash.shell.output.shell_output import cprint
|
|
7
|
-
from kash.shell.utils.sys_tool_deps import SysTool, sys_tool_check
|
|
8
8
|
from kash.utils.common.parse_shell_args import shell_quote
|
|
9
9
|
from kash.utils.errors import InvalidState
|
|
10
10
|
|
|
@@ -33,7 +33,7 @@ def search(
|
|
|
33
33
|
:param ignore_case: Ignore case when searching.
|
|
34
34
|
:param verbose: Also print the ripgrep command line.
|
|
35
35
|
"""
|
|
36
|
-
|
|
36
|
+
pkg_check().require(Pkg.ripgrep)
|
|
37
37
|
from ripgrepy import RipGrepNotFound, Ripgrepy
|
|
38
38
|
|
|
39
39
|
resolved_paths = assemble_path_args(*paths)
|
|
@@ -36,13 +36,12 @@ from kash.model.items_model import Item, ItemType
|
|
|
36
36
|
from kash.model.params_model import GLOBAL_PARAMS
|
|
37
37
|
from kash.model.paths_model import StorePath, fmt_store_path
|
|
38
38
|
from kash.shell.input.param_inputs import input_param_name, input_param_value
|
|
39
|
+
from kash.shell.output.shell_formatting import format_name_and_description, format_name_and_value
|
|
39
40
|
from kash.shell.output.shell_output import (
|
|
40
41
|
PrintHooks,
|
|
41
42
|
Wrap,
|
|
42
43
|
console_pager,
|
|
43
44
|
cprint,
|
|
44
|
-
format_name_and_description,
|
|
45
|
-
format_name_and_value,
|
|
46
45
|
print_h2,
|
|
47
46
|
print_h3,
|
|
48
47
|
print_status,
|
|
@@ -55,7 +54,7 @@ from kash.utils.common.type_utils import not_none
|
|
|
55
54
|
from kash.utils.common.url import Url, is_url
|
|
56
55
|
from kash.utils.errors import InvalidInput
|
|
57
56
|
from kash.utils.file_formats.chat_format import tail_chat_history
|
|
58
|
-
from kash.utils.file_utils.
|
|
57
|
+
from kash.utils.file_utils.dir_info import is_nonempty_dir
|
|
59
58
|
from kash.utils.lang_utils.inflection import plural
|
|
60
59
|
from kash.web_content.file_cache_utils import cache_file
|
|
61
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,18 +12,21 @@ from pathlib import Path
|
|
|
12
12
|
from typing import IO, Any, cast
|
|
13
13
|
|
|
14
14
|
import rich
|
|
15
|
-
from
|
|
15
|
+
from prettyfmt import slugify_snake
|
|
16
16
|
from rich._null_file import NULL_FILE
|
|
17
17
|
from rich.console import Console
|
|
18
18
|
from rich.logging import RichHandler
|
|
19
19
|
from rich.theme import Theme
|
|
20
|
-
from slugify import slugify
|
|
21
20
|
from strif import atomic_output_file, new_timestamped_uid
|
|
22
21
|
from typing_extensions import override
|
|
23
22
|
|
|
24
23
|
import kash.config.suppress_warnings # noqa: F401
|
|
25
|
-
from kash.config.logger_basic import basic_file_handler
|
|
26
|
-
from kash.config.settings import
|
|
24
|
+
from kash.config.logger_basic import basic_file_handler, basic_stderr_handler
|
|
25
|
+
from kash.config.settings import (
|
|
26
|
+
LogLevel,
|
|
27
|
+
get_system_logs_dir,
|
|
28
|
+
global_settings,
|
|
29
|
+
)
|
|
27
30
|
from kash.config.text_styles import (
|
|
28
31
|
EMOJI_ERROR,
|
|
29
32
|
EMOJI_SAVED,
|
|
@@ -48,9 +51,9 @@ class LogSettings:
|
|
|
48
51
|
log_file_path: Path
|
|
49
52
|
|
|
50
53
|
|
|
51
|
-
|
|
54
|
+
_log_dir = get_system_logs_dir()
|
|
52
55
|
"""
|
|
53
|
-
Parent of the "logs" directory. Initially the global kash
|
|
56
|
+
Parent of the "logs" directory. Initially the global kash workspace.
|
|
54
57
|
"""
|
|
55
58
|
|
|
56
59
|
|
|
@@ -72,14 +75,14 @@ def make_valid_log_name(name: str) -> str:
|
|
|
72
75
|
|
|
73
76
|
|
|
74
77
|
def _read_log_settings() -> LogSettings:
|
|
75
|
-
global
|
|
78
|
+
global _log_dir, _log_name
|
|
76
79
|
return LogSettings(
|
|
77
80
|
log_console_level=global_settings().console_log_level,
|
|
78
81
|
log_file_level=global_settings().file_log_level,
|
|
79
|
-
global_log_dir=
|
|
80
|
-
log_dir=
|
|
81
|
-
log_objects_dir=
|
|
82
|
-
log_file_path=
|
|
82
|
+
global_log_dir=get_system_logs_dir(),
|
|
83
|
+
log_dir=_log_dir,
|
|
84
|
+
log_objects_dir=_log_dir / "objects" / _log_name,
|
|
85
|
+
log_file_path=_log_dir / f"{_log_name}.log",
|
|
83
86
|
)
|
|
84
87
|
|
|
85
88
|
|
|
@@ -102,7 +105,7 @@ def reset_log_root(log_root: Path | None = None, log_name: str | None = None):
|
|
|
102
105
|
"""
|
|
103
106
|
global _log_lock, _log_base, _log_name
|
|
104
107
|
with _log_lock:
|
|
105
|
-
_log_base = log_root or
|
|
108
|
+
_log_base = log_root or get_system_logs_dir()
|
|
106
109
|
_log_name = make_valid_log_name(log_name or LOG_NAME_GLOBAL)
|
|
107
110
|
reload_rich_logging_setup()
|
|
108
111
|
|
|
@@ -125,9 +128,6 @@ def get_theme():
|
|
|
125
128
|
return Theme(RICH_STYLES)
|
|
126
129
|
|
|
127
130
|
|
|
128
|
-
reconfigure(theme=get_theme(), highlighter=get_highlighter())
|
|
129
|
-
|
|
130
|
-
|
|
131
131
|
def get_console() -> Console:
|
|
132
132
|
"""
|
|
133
133
|
Return the Rich global console, unless it is overridden by a
|
|
@@ -166,7 +166,7 @@ def record_console() -> Generator[Console, None, None]:
|
|
|
166
166
|
|
|
167
167
|
|
|
168
168
|
_file_handler: logging.FileHandler
|
|
169
|
-
_console_handler:
|
|
169
|
+
_console_handler: logging.Handler
|
|
170
170
|
|
|
171
171
|
|
|
172
172
|
def reload_rich_logging_setup():
|
|
@@ -210,20 +210,25 @@ def _do_logging_setup(log_settings: LogSettings):
|
|
|
210
210
|
super().emit(record)
|
|
211
211
|
|
|
212
212
|
global _console_handler
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
213
|
+
|
|
214
|
+
# Use the Rich stdout handler only on terminals, stderr for servers or non-interactive use.
|
|
215
|
+
if get_console().is_terminal:
|
|
216
|
+
_console_handler = PrefixedRichHandler(
|
|
217
|
+
# For now we use the fixed global console for logging.
|
|
218
|
+
# In the future we may want to add a way to have thread-local capture
|
|
219
|
+
# of all system logs.
|
|
220
|
+
console=rich.get_console(),
|
|
221
|
+
level=log_settings.log_console_level.value,
|
|
222
|
+
show_time=False,
|
|
223
|
+
show_path=False,
|
|
224
|
+
show_level=False,
|
|
225
|
+
highlighter=get_highlighter(),
|
|
226
|
+
markup=True,
|
|
227
|
+
)
|
|
228
|
+
_console_handler.setLevel(log_settings.log_console_level.value)
|
|
229
|
+
_console_handler.setFormatter(Formatter("%(message)s"))
|
|
230
|
+
else:
|
|
231
|
+
_console_handler = basic_stderr_handler(log_settings.log_console_level)
|
|
227
232
|
|
|
228
233
|
# Manually adjust logging for a few packages, removing previous verbose default handlers.
|
|
229
234
|
|
|
@@ -312,8 +317,7 @@ class CustomLogger(logging.Logger):
|
|
|
312
317
|
global _log_settings
|
|
313
318
|
prefix = prefix_slug + "." if prefix_slug else ""
|
|
314
319
|
filename = (
|
|
315
|
-
f"{prefix}{
|
|
316
|
-
f"{new_timestamped_uid()}.{file_ext.lstrip('.')}"
|
|
320
|
+
f"{prefix}{slugify_snake(description)}.{new_timestamped_uid()}.{file_ext.lstrip('.')}"
|
|
317
321
|
)
|
|
318
322
|
path = _log_settings.log_objects_dir / filename
|
|
319
323
|
with atomic_output_file(path, make_parents=True) as tmp_filename:
|
|
@@ -337,17 +341,12 @@ class CustomLogger(logging.Logger):
|
|
|
337
341
|
)
|
|
338
342
|
|
|
339
343
|
|
|
340
|
-
logging.setLoggerClass(CustomLogger)
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
reload_rich_logging_setup()
|
|
344
|
-
|
|
345
|
-
|
|
346
344
|
def get_logger(name: str) -> CustomLogger:
|
|
347
345
|
"""
|
|
348
346
|
Get a logger that's compatible with system logging but has our additional custom
|
|
349
347
|
methods.
|
|
350
348
|
"""
|
|
349
|
+
init_rich_logging()
|
|
351
350
|
logger = logging.getLogger(name)
|
|
352
351
|
# print("Logger is", logger)
|
|
353
352
|
return cast(CustomLogger, logger)
|
|
@@ -355,3 +354,12 @@ def get_logger(name: str) -> CustomLogger:
|
|
|
355
354
|
|
|
356
355
|
def get_log_file_stream():
|
|
357
356
|
return _file_handler.stream
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
@cache
|
|
360
|
+
def init_rich_logging():
|
|
361
|
+
rich.reconfigure(theme=get_theme(), highlighter=get_highlighter())
|
|
362
|
+
|
|
363
|
+
logging.setLoggerClass(CustomLogger)
|
|
364
|
+
|
|
365
|
+
reload_rich_logging_setup()
|
kash/config/logger_basic.py
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
|
|
2
|
+
import sys
|
|
3
|
+
from logging import FileHandler, Formatter, LogRecord
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
|
|
5
6
|
from kash.config.settings import LogLevel
|
|
7
|
+
from kash.config.suppress_warnings import demote_warnings
|
|
6
8
|
|
|
7
9
|
# Basic logging setup for non-interactive logging, like on a server.
|
|
8
10
|
# For richer logging, see logger.py.
|
|
9
11
|
|
|
10
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
|
+
|
|
11
19
|
def basic_file_handler(path: Path, level: LogLevel) -> logging.FileHandler:
|
|
12
20
|
handler = logging.FileHandler(path)
|
|
13
21
|
handler.setLevel(level.value)
|
|
@@ -16,7 +24,7 @@ def basic_file_handler(path: Path, level: LogLevel) -> logging.FileHandler:
|
|
|
16
24
|
|
|
17
25
|
|
|
18
26
|
def basic_stderr_handler(level: LogLevel) -> logging.StreamHandler:
|
|
19
|
-
handler =
|
|
27
|
+
handler = SuppressedWarningsStreamHandler(stream=sys.stderr)
|
|
20
28
|
handler.setLevel(level.value)
|
|
21
29
|
handler.setFormatter(Formatter("%(asctime)s %(levelname).1s %(name)s - %(message)s"))
|
|
22
30
|
return handler
|