kash-shell 0.3.9__py3-none-any.whl → 0.3.11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kash/actions/__init__.py +4 -4
- kash/actions/core/format_markdown_template.py +2 -5
- kash/actions/core/markdownify.py +7 -6
- kash/actions/core/readability.py +7 -6
- kash/actions/core/render_as_html.py +37 -0
- kash/actions/core/show_webpage.py +6 -11
- kash/actions/core/strip_html.py +2 -6
- kash/actions/core/tabbed_webpage_config.py +31 -0
- kash/actions/core/{webpage_generate.py → tabbed_webpage_generate.py} +5 -4
- kash/commands/__init__.py +8 -20
- kash/commands/base/basic_file_commands.py +15 -0
- kash/commands/base/debug_commands.py +13 -0
- kash/commands/base/files_command.py +28 -10
- kash/commands/base/general_commands.py +21 -16
- kash/commands/base/logs_commands.py +4 -2
- kash/commands/base/model_commands.py +8 -8
- kash/commands/base/search_command.py +3 -2
- kash/commands/base/show_command.py +5 -3
- kash/commands/extras/parse_uv_lock.py +186 -0
- kash/commands/help/doc_commands.py +2 -31
- kash/commands/help/welcome.py +33 -0
- kash/commands/workspace/selection_commands.py +11 -6
- kash/commands/workspace/workspace_commands.py +19 -17
- kash/config/colors.py +3 -1
- kash/config/env_settings.py +14 -1
- kash/config/init.py +2 -2
- kash/config/logger.py +59 -56
- kash/config/logger_basic.py +3 -3
- kash/config/settings.py +116 -57
- kash/config/setup.py +28 -12
- kash/config/text_styles.py +3 -13
- kash/docs/load_api_docs.py +2 -1
- kash/docs/markdown/topics/a3_getting_started.md +3 -2
- kash/{concepts → embeddings}/text_similarity.py +2 -2
- kash/exec/__init__.py +20 -3
- kash/exec/action_decorators.py +24 -10
- kash/exec/action_exec.py +41 -23
- kash/exec/action_registry.py +13 -48
- kash/exec/command_registry.py +2 -1
- kash/exec/fetch_url_metadata.py +4 -6
- kash/exec/importing.py +56 -0
- kash/exec/llm_transforms.py +12 -10
- kash/exec/precondition_registry.py +2 -1
- kash/exec/preconditions.py +22 -1
- kash/exec/resolve_args.py +4 -0
- kash/exec/shell_callable_action.py +33 -19
- kash/file_storage/file_store.py +42 -27
- kash/file_storage/item_file_format.py +5 -2
- kash/file_storage/metadata_dirs.py +11 -2
- kash/help/assistant.py +1 -1
- kash/help/assistant_instructions.py +2 -1
- kash/help/function_param_info.py +1 -1
- kash/help/help_embeddings.py +2 -2
- kash/help/help_printing.py +7 -11
- kash/llm_utils/clean_headings.py +1 -1
- kash/llm_utils/llm_api_keys.py +4 -4
- kash/llm_utils/llm_features.py +68 -0
- kash/llm_utils/llm_messages.py +1 -2
- kash/llm_utils/llm_names.py +1 -1
- kash/llm_utils/llms.py +8 -3
- kash/local_server/__init__.py +5 -2
- kash/local_server/local_server.py +8 -5
- kash/local_server/local_server_commands.py +2 -2
- kash/local_server/local_server_routes.py +1 -7
- kash/local_server/local_url_formatters.py +1 -1
- kash/mcp/__init__.py +5 -2
- kash/mcp/mcp_cli.py +5 -5
- kash/mcp/mcp_server_commands.py +5 -5
- kash/mcp/mcp_server_routes.py +5 -5
- kash/mcp/mcp_server_sse.py +4 -2
- kash/media_base/media_cache.py +8 -8
- kash/media_base/media_services.py +1 -1
- kash/media_base/media_tools.py +6 -6
- kash/media_base/services/local_file_media.py +2 -2
- kash/media_base/{speech_transcription.py → transcription_deepgram.py} +25 -110
- kash/media_base/transcription_format.py +73 -0
- kash/media_base/transcription_whisper.py +38 -0
- kash/model/__init__.py +73 -5
- kash/model/actions_model.py +38 -4
- kash/model/concept_model.py +30 -0
- kash/model/items_model.py +115 -32
- kash/model/params_model.py +24 -0
- kash/shell/completions/completion_scoring.py +37 -5
- kash/shell/output/kerm_codes.py +1 -2
- kash/shell/output/shell_formatting.py +14 -4
- kash/shell/shell_main.py +2 -2
- kash/shell/utils/exception_printing.py +6 -0
- kash/shell/utils/native_utils.py +26 -20
- kash/shell/utils/shell_function_wrapper.py +15 -15
- kash/text_handling/custom_sliding_transforms.py +12 -4
- kash/text_handling/doc_normalization.py +6 -2
- kash/text_handling/markdown_render.py +118 -0
- kash/text_handling/markdown_utils.py +226 -0
- kash/utils/common/function_inspect.py +360 -110
- kash/utils/common/import_utils.py +12 -3
- kash/utils/common/type_utils.py +0 -29
- kash/utils/common/url.py +27 -3
- kash/utils/errors.py +6 -0
- kash/utils/file_utils/file_ext.py +4 -0
- kash/utils/file_utils/file_formats.py +2 -2
- kash/utils/file_utils/file_formats_model.py +20 -1
- kash/web_content/dir_store.py +1 -2
- kash/web_content/file_cache_utils.py +37 -10
- kash/web_content/file_processing.py +68 -0
- kash/web_content/local_file_cache.py +12 -9
- kash/web_content/web_extract.py +8 -3
- kash/web_content/web_fetch.py +12 -4
- kash/web_gen/__init__.py +0 -4
- kash/web_gen/simple_webpage.py +52 -0
- kash/web_gen/tabbed_webpage.py +24 -14
- kash/web_gen/template_render.py +37 -2
- kash/web_gen/templates/base_styles.css.jinja +169 -43
- kash/web_gen/templates/base_webpage.html.jinja +110 -45
- kash/web_gen/templates/content_styles.css.jinja +4 -2
- kash/web_gen/templates/item_view.html.jinja +49 -39
- kash/web_gen/templates/simple_webpage.html.jinja +24 -0
- kash/web_gen/templates/tabbed_webpage.html.jinja +42 -33
- kash/workspaces/__init__.py +15 -2
- kash/workspaces/selections.py +18 -3
- kash/workspaces/source_items.py +0 -1
- kash/workspaces/workspaces.py +5 -11
- kash/xonsh_custom/command_nl_utils.py +40 -19
- kash/xonsh_custom/custom_shell.py +43 -11
- kash/xonsh_custom/customize_prompt.py +39 -21
- kash/xonsh_custom/load_into_xonsh.py +22 -25
- kash/xonsh_custom/shell_load_commands.py +2 -2
- kash/xonsh_custom/xonsh_completers.py +2 -249
- kash/xonsh_custom/xonsh_keybindings.py +282 -0
- kash/xonsh_custom/xonsh_modern_tools.py +3 -3
- kash/xontrib/kash_extension.py +5 -6
- {kash_shell-0.3.9.dist-info → kash_shell-0.3.11.dist-info}/METADATA +10 -8
- {kash_shell-0.3.9.dist-info → kash_shell-0.3.11.dist-info}/RECORD +137 -136
- kash/actions/core/webpage_config.py +0 -21
- kash/concepts/concept_formats.py +0 -23
- kash/shell/clideps/api_keys.py +0 -100
- kash/shell/clideps/dotenv_setup.py +0 -115
- kash/shell/clideps/dotenv_utils.py +0 -98
- kash/shell/clideps/pkg_deps.py +0 -257
- kash/shell/clideps/platforms.py +0 -11
- kash/shell/clideps/terminal_features.py +0 -56
- kash/shell/utils/osc_utils.py +0 -95
- kash/shell/utils/terminal_images.py +0 -133
- kash/text_handling/markdown_util.py +0 -167
- kash/utils/common/atomic_var.py +0 -171
- kash/utils/common/string_replace.py +0 -93
- kash/utils/common/string_template.py +0 -101
- /kash/{concepts → embeddings}/cosine.py +0 -0
- /kash/{concepts → embeddings}/embeddings.py +0 -0
- {kash_shell-0.3.9.dist-info → kash_shell-0.3.11.dist-info}/WHEEL +0 -0
- {kash_shell-0.3.9.dist-info → kash_shell-0.3.11.dist-info}/entry_points.txt +0 -0
- {kash_shell-0.3.9.dist-info → kash_shell-0.3.11.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import tomllib
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from packaging.tags import Tag, sys_tags
|
|
7
|
+
from packaging.utils import parse_wheel_filename
|
|
8
|
+
from prettyfmt import fmt_size_dual
|
|
9
|
+
from strif import atomic_output_file
|
|
10
|
+
|
|
11
|
+
from kash.config.logger import get_logger
|
|
12
|
+
from kash.config.text_styles import COLOR_STATUS
|
|
13
|
+
from kash.exec import kash_command
|
|
14
|
+
from kash.shell.output.shell_output import cprint
|
|
15
|
+
|
|
16
|
+
log = get_logger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def choose_wheel(wheels: list[dict], allowed: list[Tag]) -> dict | None:
|
|
20
|
+
"""
|
|
21
|
+
Pick the first wheel whose tag set intersects `allowed`
|
|
22
|
+
(highest priority wins).
|
|
23
|
+
"""
|
|
24
|
+
priority = {tag: idx for idx, tag in enumerate(allowed)}
|
|
25
|
+
best: dict | None = None
|
|
26
|
+
best_rank = float("inf")
|
|
27
|
+
|
|
28
|
+
for meta in wheels:
|
|
29
|
+
url = meta.get("url", "")
|
|
30
|
+
filename = Path(url).name if url else ""
|
|
31
|
+
if not filename:
|
|
32
|
+
raise ValueError(f"No filename found for {url}")
|
|
33
|
+
tags = parse_wheel_filename(filename)[-1]
|
|
34
|
+
|
|
35
|
+
for tag in tags:
|
|
36
|
+
rank = priority.get(tag)
|
|
37
|
+
if rank is not None and rank < best_rank:
|
|
38
|
+
best = meta
|
|
39
|
+
best_rank = rank
|
|
40
|
+
break
|
|
41
|
+
|
|
42
|
+
return best
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_platform() -> str:
|
|
46
|
+
"""
|
|
47
|
+
Get the most-specific platform tag your interpreter supports.
|
|
48
|
+
"""
|
|
49
|
+
return next(sys_tags()).platform
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def parse_uv_lock(lock_path: Path) -> pd.DataFrame:
|
|
53
|
+
"""
|
|
54
|
+
Return one row per package from a uv.lock file, selecting the best
|
|
55
|
+
matching wheel for the current interpreter or falling back to the sdist.
|
|
56
|
+
|
|
57
|
+
Columns: name, version, registry, file_type, url, hash, size, filename.
|
|
58
|
+
"""
|
|
59
|
+
with open(lock_path, "rb") as f:
|
|
60
|
+
data = tomllib.load(f)
|
|
61
|
+
|
|
62
|
+
rows: list[dict] = []
|
|
63
|
+
for pkg in data.get("package", []):
|
|
64
|
+
name = pkg.get("name")
|
|
65
|
+
version = pkg.get("version")
|
|
66
|
+
registry = pkg.get("source", {}).get("registry")
|
|
67
|
+
|
|
68
|
+
wheels = pkg.get("wheels", [])
|
|
69
|
+
selected = choose_wheel(wheels, list(sys_tags()))
|
|
70
|
+
if selected:
|
|
71
|
+
meta = selected
|
|
72
|
+
file_type = "wheel"
|
|
73
|
+
else:
|
|
74
|
+
meta = pkg.get("sdist", {})
|
|
75
|
+
file_type = "sdist"
|
|
76
|
+
|
|
77
|
+
url = meta.get("url")
|
|
78
|
+
rows.append(
|
|
79
|
+
{
|
|
80
|
+
"name": name,
|
|
81
|
+
"version": version,
|
|
82
|
+
"registry": registry,
|
|
83
|
+
"file_type": file_type,
|
|
84
|
+
"url": url,
|
|
85
|
+
"hash": meta.get("hash"),
|
|
86
|
+
"size": meta.get("size"),
|
|
87
|
+
"filename": Path(url).name if url else None,
|
|
88
|
+
}
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
return pd.DataFrame(rows)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def uv_runtime_packages(
|
|
95
|
+
project_dir: str | Path = ".", no_dev: bool = False, uv_executable: str = "uv"
|
|
96
|
+
) -> list[str]:
|
|
97
|
+
"""
|
|
98
|
+
Return the *runtime* (non-dev) package names that would be installed for the
|
|
99
|
+
given project, as resolved by uv.
|
|
100
|
+
"""
|
|
101
|
+
cmd = [
|
|
102
|
+
uv_executable,
|
|
103
|
+
"export",
|
|
104
|
+
"--format",
|
|
105
|
+
"requirements-txt",
|
|
106
|
+
"--no-header",
|
|
107
|
+
"--no-annotate",
|
|
108
|
+
"--no-hashes",
|
|
109
|
+
]
|
|
110
|
+
if no_dev:
|
|
111
|
+
cmd.append("--no-dev")
|
|
112
|
+
|
|
113
|
+
result = subprocess.run(
|
|
114
|
+
cmd,
|
|
115
|
+
cwd=Path(project_dir),
|
|
116
|
+
check=True,
|
|
117
|
+
text=True,
|
|
118
|
+
capture_output=True,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
packages: list[str] = []
|
|
122
|
+
for line in result.stdout.splitlines():
|
|
123
|
+
line = line.strip()
|
|
124
|
+
if "==" not in line: # skip “-e .” and blank lines
|
|
125
|
+
continue
|
|
126
|
+
pkg_name, _ = line.split("==", maxsplit=1)
|
|
127
|
+
packages.append(pkg_name.strip())
|
|
128
|
+
|
|
129
|
+
return packages
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@kash_command
|
|
133
|
+
def uv_dep_info(
|
|
134
|
+
uv_lock: str = "uv.lock",
|
|
135
|
+
pyproject: str = "pyproject.toml",
|
|
136
|
+
save: str | None = None,
|
|
137
|
+
sort_by: str = "size",
|
|
138
|
+
) -> None:
|
|
139
|
+
"""
|
|
140
|
+
Parse a uv.lock file and print information about the packages.
|
|
141
|
+
By default, filters to show only 'main' dependencies from pyproject.toml.
|
|
142
|
+
Helpful for looking at sizes of dependencies.
|
|
143
|
+
"""
|
|
144
|
+
uv_lock_path = Path(uv_lock)
|
|
145
|
+
pyproject_path = Path(pyproject)
|
|
146
|
+
|
|
147
|
+
main_deps: list[str] | None = None
|
|
148
|
+
all_deps: list[str] = []
|
|
149
|
+
if pyproject_path.exists():
|
|
150
|
+
cprint("Reading main dependencies from with uv", style=COLOR_STATUS)
|
|
151
|
+
main_deps = uv_runtime_packages(no_dev=True)
|
|
152
|
+
all_deps = uv_runtime_packages(no_dev=False)
|
|
153
|
+
else:
|
|
154
|
+
log.warning("pyproject.toml not found: %s", pyproject_path)
|
|
155
|
+
|
|
156
|
+
cprint(f"Parsing lock file: {uv_lock_path}", style=COLOR_STATUS)
|
|
157
|
+
|
|
158
|
+
df = parse_uv_lock(uv_lock_path)
|
|
159
|
+
df = df.sort_values(by=sort_by)
|
|
160
|
+
|
|
161
|
+
if main_deps:
|
|
162
|
+
cprint(
|
|
163
|
+
f"Filtering lock file entries to include only {len(main_deps)} of {len(all_deps)} dependencies.",
|
|
164
|
+
style=COLOR_STATUS,
|
|
165
|
+
)
|
|
166
|
+
df = df[df["name"].isin(main_deps)]
|
|
167
|
+
else:
|
|
168
|
+
cprint("Showing all packages from lock file.", style=COLOR_STATUS)
|
|
169
|
+
cprint()
|
|
170
|
+
|
|
171
|
+
# Show only selected columns and full output
|
|
172
|
+
cols = ["name", "version", "file_type", "size", "filename"]
|
|
173
|
+
print(df.loc[:, cols].to_string(max_rows=None))
|
|
174
|
+
cprint() # Add a newline for separation
|
|
175
|
+
|
|
176
|
+
# Calculate and print summary stats
|
|
177
|
+
num_deps = len(df)
|
|
178
|
+
total_size = pd.Series(pd.to_numeric(df["size"], errors="coerce")).fillna(0).sum()
|
|
179
|
+
|
|
180
|
+
cprint(f"Packages listed: {num_deps}", style=COLOR_STATUS)
|
|
181
|
+
cprint(f"Total size: {fmt_size_dual(int(total_size))}", style=COLOR_STATUS)
|
|
182
|
+
|
|
183
|
+
if save:
|
|
184
|
+
with atomic_output_file(save) as temp_name:
|
|
185
|
+
df.to_csv(temp_name, index=False) # Added index=False for cleaner CSV
|
|
186
|
+
cprint(f"Saved to {save}", style=COLOR_STATUS)
|
|
@@ -1,42 +1,13 @@
|
|
|
1
|
-
from
|
|
2
|
-
from rich.console import Group
|
|
3
|
-
from rich.panel import Panel
|
|
4
|
-
|
|
5
|
-
from kash.commands.help.logo import branded_box
|
|
1
|
+
from kash.commands.help.welcome import welcome
|
|
6
2
|
from kash.config.logger import get_logger
|
|
7
|
-
from kash.config.text_styles import (
|
|
8
|
-
COLOR_HINT,
|
|
9
|
-
)
|
|
10
3
|
from kash.docs.all_docs import DocSelection, all_docs
|
|
11
4
|
from kash.exec import kash_command
|
|
12
5
|
from kash.help.help_pages import print_see_also
|
|
13
|
-
from kash.shell.output.shell_output import
|
|
14
|
-
from kash.shell.version import get_full_version_name
|
|
15
|
-
from kash.utils.rich_custom.rich_markdown_fork import Markdown
|
|
6
|
+
from kash.shell.output.shell_output import console_pager, print_markdown
|
|
16
7
|
|
|
17
8
|
log = get_logger(__name__)
|
|
18
9
|
|
|
19
10
|
|
|
20
|
-
@kash_command
|
|
21
|
-
def welcome() -> None:
|
|
22
|
-
"""
|
|
23
|
-
Print a welcome message.
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
help_topics = all_docs.help_topics
|
|
27
|
-
version = get_full_version_name()
|
|
28
|
-
# Create header with logo and right-justified version
|
|
29
|
-
|
|
30
|
-
PrintHooks.before_welcome()
|
|
31
|
-
cprint(
|
|
32
|
-
branded_box(
|
|
33
|
-
Group(Markdown(help_topics.welcome)),
|
|
34
|
-
version,
|
|
35
|
-
)
|
|
36
|
-
)
|
|
37
|
-
cprint(Panel(Markdown(help_topics.warning), box=SQUARE, border_style=COLOR_HINT))
|
|
38
|
-
|
|
39
|
-
|
|
40
11
|
@kash_command
|
|
41
12
|
def manual(no_pager: bool = False, doc_selection: DocSelection = DocSelection.full) -> None:
|
|
42
13
|
"""
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from rich.box import SQUARE
|
|
2
|
+
from rich.console import Group
|
|
3
|
+
from rich.panel import Panel
|
|
4
|
+
|
|
5
|
+
from kash.commands.help.logo import branded_box
|
|
6
|
+
from kash.config.text_styles import (
|
|
7
|
+
COLOR_HINT,
|
|
8
|
+
)
|
|
9
|
+
from kash.docs.all_docs import all_docs
|
|
10
|
+
from kash.exec import kash_command
|
|
11
|
+
from kash.shell.output.shell_output import PrintHooks, cprint
|
|
12
|
+
from kash.shell.version import get_full_version_name
|
|
13
|
+
from kash.utils.rich_custom.rich_markdown_fork import Markdown
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@kash_command
|
|
17
|
+
def welcome() -> None:
|
|
18
|
+
"""
|
|
19
|
+
Print a welcome message.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
help_topics = all_docs.help_topics
|
|
23
|
+
version = get_full_version_name()
|
|
24
|
+
# Create header with logo and right-justified version
|
|
25
|
+
|
|
26
|
+
PrintHooks.before_welcome()
|
|
27
|
+
cprint(
|
|
28
|
+
branded_box(
|
|
29
|
+
Group(Markdown(help_topics.welcome)),
|
|
30
|
+
version,
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
cprint(Panel(Markdown(help_topics.warning), box=SQUARE, border_style=COLOR_HINT))
|
|
@@ -27,8 +27,9 @@ def select(
|
|
|
27
27
|
previous: bool = False,
|
|
28
28
|
next: bool = False,
|
|
29
29
|
pop: bool = False,
|
|
30
|
-
|
|
30
|
+
clear_all: bool = False,
|
|
31
31
|
clear_future: bool = False,
|
|
32
|
+
refresh: bool = False,
|
|
32
33
|
) -> ShellResult:
|
|
33
34
|
"""
|
|
34
35
|
Set or show the current selection.
|
|
@@ -47,12 +48,13 @@ def select(
|
|
|
47
48
|
:param previous: Move back in the selection history to the previous selection.
|
|
48
49
|
:param next: Move forward in the selection history to the next selection.
|
|
49
50
|
:param pop: Pop the current selection from the history.
|
|
50
|
-
:param
|
|
51
|
+
:param clear_all: Clear the full selection history.
|
|
51
52
|
:param clear_future: Clear all selections from history after the current one.
|
|
53
|
+
:param refresh: Refresh the current selection to drop any paths that no longer exist.
|
|
52
54
|
"""
|
|
53
55
|
ws = current_ws()
|
|
54
56
|
|
|
55
|
-
#
|
|
57
|
+
# FIXME: It would be nice to be able to read stdin from a pipe but this isn't working rn.
|
|
56
58
|
# You could then run `... | select --stdin` to select the piped input.
|
|
57
59
|
# Globally we have THREAD_SUBPROCS=False to avoid hard-to-interrupt subprocesses.
|
|
58
60
|
# But xonsh seems to hang with stdin unless we modify the spec to be threadable?
|
|
@@ -61,7 +63,7 @@ def select(
|
|
|
61
63
|
# if stdin:
|
|
62
64
|
# paths = tuple(sys.stdin.read().splitlines())
|
|
63
65
|
|
|
64
|
-
exclusive_flags = [history, last, back, forward, previous, next, pop,
|
|
66
|
+
exclusive_flags = [history, last, back, forward, previous, next, pop, clear_all, clear_future]
|
|
65
67
|
if sum(bool(f) for f in exclusive_flags) > 1:
|
|
66
68
|
raise InvalidInput("Cannot combine multiple flags")
|
|
67
69
|
if paths and any(exclusive_flags):
|
|
@@ -96,12 +98,15 @@ def select(
|
|
|
96
98
|
elif pop:
|
|
97
99
|
ws.selections.pop()
|
|
98
100
|
return ShellResult(show_selection=True)
|
|
99
|
-
elif
|
|
100
|
-
ws.selections.
|
|
101
|
+
elif clear_all:
|
|
102
|
+
ws.selections.clear_all()
|
|
101
103
|
return ShellResult(show_selection=True)
|
|
102
104
|
elif clear_future:
|
|
103
105
|
ws.selections.clear_future()
|
|
104
106
|
return ShellResult(show_selection=True)
|
|
107
|
+
elif refresh:
|
|
108
|
+
ws.selections.refresh_current(ws.base_dir)
|
|
109
|
+
return ShellResult(show_selection=True)
|
|
105
110
|
else:
|
|
106
111
|
return ShellResult(show_selection=True)
|
|
107
112
|
|
|
@@ -15,7 +15,6 @@ from kash.config.text_styles import (
|
|
|
15
15
|
EMOJI_WARN,
|
|
16
16
|
STYLE_EMPH,
|
|
17
17
|
STYLE_HINT,
|
|
18
|
-
format_success_emoji,
|
|
19
18
|
)
|
|
20
19
|
from kash.exec import (
|
|
21
20
|
assemble_path_args,
|
|
@@ -36,7 +35,11 @@ from kash.model.items_model import Item, ItemType
|
|
|
36
35
|
from kash.model.params_model import GLOBAL_PARAMS
|
|
37
36
|
from kash.model.paths_model import StorePath, fmt_store_path
|
|
38
37
|
from kash.shell.input.param_inputs import input_param_name, input_param_value
|
|
39
|
-
from kash.shell.output.shell_formatting import
|
|
38
|
+
from kash.shell.output.shell_formatting import (
|
|
39
|
+
format_name_and_description,
|
|
40
|
+
format_name_and_value,
|
|
41
|
+
format_success_emoji,
|
|
42
|
+
)
|
|
40
43
|
from kash.shell.output.shell_output import (
|
|
41
44
|
PrintHooks,
|
|
42
45
|
Wrap,
|
|
@@ -170,14 +173,15 @@ def cache_media(*urls: str) -> None:
|
|
|
170
173
|
|
|
171
174
|
|
|
172
175
|
@kash_command
|
|
173
|
-
def cache_content(*urls_or_paths: str) -> None:
|
|
176
|
+
def cache_content(*urls_or_paths: str, refetch: bool = False) -> None:
|
|
174
177
|
"""
|
|
175
178
|
Cache the given file in the content cache. Downloads any URL or copies a local file.
|
|
176
179
|
"""
|
|
180
|
+
expiration_sec = 0 if refetch else None
|
|
177
181
|
PrintHooks.spacer()
|
|
178
182
|
for url_or_path in urls_or_paths:
|
|
179
183
|
locator = resolve_locator_arg(url_or_path)
|
|
180
|
-
cache_path, was_cached = cache_file(locator)
|
|
184
|
+
cache_path, was_cached = cache_file(locator, expiration_sec=expiration_sec)
|
|
181
185
|
cache_str = " (already cached)" if was_cached else ""
|
|
182
186
|
cprint(f"{fmt_loc(url_or_path)}{cache_str}:", style=STYLE_EMPH, text_wrap=Wrap.NONE)
|
|
183
187
|
cprint(f"{cache_path}", text_wrap=Wrap.INDENT_ONLY)
|
|
@@ -185,10 +189,13 @@ def cache_content(*urls_or_paths: str) -> None:
|
|
|
185
189
|
|
|
186
190
|
|
|
187
191
|
@kash_command
|
|
188
|
-
def download(*urls_or_paths: str) -> None:
|
|
192
|
+
def download(*urls_or_paths: str, refetch: bool = False) -> None:
|
|
189
193
|
"""
|
|
190
|
-
Download a URL or resource.
|
|
194
|
+
Download a URL or resource. Uses cached content if available, unless `refetch` is true.
|
|
195
|
+
Inputs can be URLs or paths to URL resources.
|
|
191
196
|
"""
|
|
197
|
+
expiration_sec = 0 if refetch else None
|
|
198
|
+
|
|
192
199
|
# TODO: Add option to include frontmatter metadata for text files.
|
|
193
200
|
ws = current_ws()
|
|
194
201
|
for url_or_path in urls_or_paths:
|
|
@@ -211,7 +218,7 @@ def download(*urls_or_paths: str) -> None:
|
|
|
211
218
|
media_tools.cache_media(url)
|
|
212
219
|
else:
|
|
213
220
|
log.message("Will cache file and save to workspace: %s", fmt_loc(url))
|
|
214
|
-
cache_path, _was_cached = cache_file(url)
|
|
221
|
+
cache_path, _was_cached = cache_file(url, expiration_sec=expiration_sec)
|
|
215
222
|
item = Item.from_external_path(cache_path, item_type=ItemType.resource)
|
|
216
223
|
store_path = ws.save(item)
|
|
217
224
|
|
|
@@ -278,7 +285,7 @@ def init_workspace(path: str | None = None) -> None:
|
|
|
278
285
|
@kash_command
|
|
279
286
|
def workspace(workspace_name: str | None = None) -> None:
|
|
280
287
|
"""
|
|
281
|
-
If no args are given,
|
|
288
|
+
If no args are given, show current workspace info.
|
|
282
289
|
If a workspace name is given, change to that workspace, creating it if it doesn't exist.
|
|
283
290
|
"""
|
|
284
291
|
if workspace_name:
|
|
@@ -295,7 +302,6 @@ def workspace(workspace_name: str | None = None) -> None:
|
|
|
295
302
|
ws.log_workspace_info()
|
|
296
303
|
else:
|
|
297
304
|
ws = current_ws(silent=True)
|
|
298
|
-
os.chdir(ws.base_dir)
|
|
299
305
|
ws.log_workspace_info()
|
|
300
306
|
|
|
301
307
|
|
|
@@ -402,12 +408,10 @@ def set_params(*key_vals: str) -> None:
|
|
|
402
408
|
|
|
403
409
|
|
|
404
410
|
@kash_command
|
|
405
|
-
def
|
|
411
|
+
def params(full: bool = False) -> None:
|
|
406
412
|
"""
|
|
407
|
-
|
|
413
|
+
List currently set workspace parameters, which are settings that may be used
|
|
408
414
|
by commands and actions or to override default parameters.
|
|
409
|
-
|
|
410
|
-
Run with no args to interactively set parameters.
|
|
411
415
|
"""
|
|
412
416
|
ws: Workspace = current_ws()
|
|
413
417
|
settable_params = GLOBAL_PARAMS
|
|
@@ -461,9 +465,7 @@ def import_item(
|
|
|
461
465
|
|
|
462
466
|
|
|
463
467
|
@kash_command
|
|
464
|
-
def fetch_metadata(
|
|
465
|
-
*files_or_urls: str, no_cache: bool = False, refetch: bool = False
|
|
466
|
-
) -> ShellResult:
|
|
468
|
+
def fetch_metadata(*files_or_urls: str, refetch: bool = False) -> ShellResult:
|
|
467
469
|
"""
|
|
468
470
|
Fetch metadata for the given URLs or resources. Imports new URLs and saves back
|
|
469
471
|
the fetched metadata for existing resources.
|
|
@@ -483,7 +485,7 @@ def fetch_metadata(
|
|
|
483
485
|
try:
|
|
484
486
|
if isinstance(locator, Path):
|
|
485
487
|
raise InvalidInput()
|
|
486
|
-
fetched_item = fetch_url_metadata(locator,
|
|
488
|
+
fetched_item = fetch_url_metadata(locator, refetch=refetch)
|
|
487
489
|
store_paths.append(fetched_item.store_path)
|
|
488
490
|
except InvalidInput:
|
|
489
491
|
log.warning("Not a URL or URL resource, will not fetch metadata: %s", fmt_loc(locator))
|
kash/config/colors.py
CHANGED
|
@@ -134,8 +134,10 @@ web_light_translucent = SimpleNamespace(
|
|
|
134
134
|
primary_light=hsl_to_hex("hsl(188, 40%, 62%)"),
|
|
135
135
|
secondary=hsl_to_hex("hsl(188, 12%, 28%)"),
|
|
136
136
|
bg=hsl_to_hex("hsla(44, 6%, 100%, 0.75)"),
|
|
137
|
+
bg_solid=hsl_to_hex("hsla(44, 6%, 100%, 1)"),
|
|
137
138
|
bg_header=hsl_to_hex("hsla(188, 42%, 70%, 0.2)"),
|
|
138
|
-
bg_alt=hsl_to_hex("hsla(
|
|
139
|
+
bg_alt=hsl_to_hex("hsla(39, 24%, 90%, 0.3)"),
|
|
140
|
+
bg_alt_solid=hsl_to_hex("hsla(39, 24%, 97%, 1)"),
|
|
139
141
|
text=hsl_to_hex("hsl(188, 39%, 11%)"),
|
|
140
142
|
border=hsl_to_hex("hsl(188, 8%, 50%)"),
|
|
141
143
|
border_hint=hsl_to_hex("hsla(188, 8%, 72%, 0.7)"),
|
kash/config/env_settings.py
CHANGED
|
@@ -28,6 +28,12 @@ class KashEnv(str, Enum):
|
|
|
28
28
|
KASH_MCP_WS = "KASH_MCP_WS"
|
|
29
29
|
"""The directory for the workspace for MCP servers."""
|
|
30
30
|
|
|
31
|
+
KASH_SHOW_TRACEBACK = "KASH_SHOW_TRACEBACK"
|
|
32
|
+
"""Whether to show tracebacks on actions and commands in the shell."""
|
|
33
|
+
|
|
34
|
+
KASH_USER_AGENT = "KASH_USER_AGENT"
|
|
35
|
+
"""The user agent to use for HTTP requests."""
|
|
36
|
+
|
|
31
37
|
@overload
|
|
32
38
|
def read_str(self) -> str | None: ...
|
|
33
39
|
|
|
@@ -42,7 +48,7 @@ class KashEnv(str, Enum):
|
|
|
42
48
|
return os.environ.get(self.value, default)
|
|
43
49
|
|
|
44
50
|
@overload
|
|
45
|
-
def read_path(self
|
|
51
|
+
def read_path(self) -> Path | None: ...
|
|
46
52
|
|
|
47
53
|
@overload
|
|
48
54
|
def read_path(self, default: Path) -> Path: ...
|
|
@@ -57,3 +63,10 @@ class KashEnv(str, Enum):
|
|
|
57
63
|
return Path(value).expanduser().resolve()
|
|
58
64
|
else:
|
|
59
65
|
return default.expanduser().resolve() if default else None
|
|
66
|
+
|
|
67
|
+
def read_bool(self, default: bool = False) -> bool:
|
|
68
|
+
"""
|
|
69
|
+
Get the value of the environment variable as a boolean.
|
|
70
|
+
"""
|
|
71
|
+
value = str(os.environ.get(self.value, default) or "").lower()
|
|
72
|
+
return bool(value and value != "0" and value != "false" and value != "no")
|
kash/config/init.py
CHANGED
|
@@ -9,10 +9,10 @@ def kash_reload_all() -> tuple[dict[str, Callable], dict[str, type["Action"]]]:
|
|
|
9
9
|
"""
|
|
10
10
|
Import all kash modules that define actions and commands.
|
|
11
11
|
"""
|
|
12
|
-
from kash.exec.action_registry import
|
|
12
|
+
from kash.exec.action_registry import refresh_action_classes
|
|
13
13
|
from kash.exec.command_registry import get_all_commands
|
|
14
14
|
|
|
15
15
|
commands = get_all_commands()
|
|
16
|
-
actions =
|
|
16
|
+
actions = refresh_action_classes()
|
|
17
17
|
|
|
18
18
|
return commands, actions
|