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.
Files changed (151) hide show
  1. kash/actions/__init__.py +4 -4
  2. kash/actions/core/format_markdown_template.py +2 -5
  3. kash/actions/core/markdownify.py +7 -6
  4. kash/actions/core/readability.py +7 -6
  5. kash/actions/core/render_as_html.py +37 -0
  6. kash/actions/core/show_webpage.py +6 -11
  7. kash/actions/core/strip_html.py +2 -6
  8. kash/actions/core/tabbed_webpage_config.py +31 -0
  9. kash/actions/core/{webpage_generate.py → tabbed_webpage_generate.py} +5 -4
  10. kash/commands/__init__.py +8 -20
  11. kash/commands/base/basic_file_commands.py +15 -0
  12. kash/commands/base/debug_commands.py +13 -0
  13. kash/commands/base/files_command.py +28 -10
  14. kash/commands/base/general_commands.py +21 -16
  15. kash/commands/base/logs_commands.py +4 -2
  16. kash/commands/base/model_commands.py +8 -8
  17. kash/commands/base/search_command.py +3 -2
  18. kash/commands/base/show_command.py +5 -3
  19. kash/commands/extras/parse_uv_lock.py +186 -0
  20. kash/commands/help/doc_commands.py +2 -31
  21. kash/commands/help/welcome.py +33 -0
  22. kash/commands/workspace/selection_commands.py +11 -6
  23. kash/commands/workspace/workspace_commands.py +19 -17
  24. kash/config/colors.py +3 -1
  25. kash/config/env_settings.py +14 -1
  26. kash/config/init.py +2 -2
  27. kash/config/logger.py +59 -56
  28. kash/config/logger_basic.py +3 -3
  29. kash/config/settings.py +116 -57
  30. kash/config/setup.py +28 -12
  31. kash/config/text_styles.py +3 -13
  32. kash/docs/load_api_docs.py +2 -1
  33. kash/docs/markdown/topics/a3_getting_started.md +3 -2
  34. kash/{concepts → embeddings}/text_similarity.py +2 -2
  35. kash/exec/__init__.py +20 -3
  36. kash/exec/action_decorators.py +24 -10
  37. kash/exec/action_exec.py +41 -23
  38. kash/exec/action_registry.py +13 -48
  39. kash/exec/command_registry.py +2 -1
  40. kash/exec/fetch_url_metadata.py +4 -6
  41. kash/exec/importing.py +56 -0
  42. kash/exec/llm_transforms.py +12 -10
  43. kash/exec/precondition_registry.py +2 -1
  44. kash/exec/preconditions.py +22 -1
  45. kash/exec/resolve_args.py +4 -0
  46. kash/exec/shell_callable_action.py +33 -19
  47. kash/file_storage/file_store.py +42 -27
  48. kash/file_storage/item_file_format.py +5 -2
  49. kash/file_storage/metadata_dirs.py +11 -2
  50. kash/help/assistant.py +1 -1
  51. kash/help/assistant_instructions.py +2 -1
  52. kash/help/function_param_info.py +1 -1
  53. kash/help/help_embeddings.py +2 -2
  54. kash/help/help_printing.py +7 -11
  55. kash/llm_utils/clean_headings.py +1 -1
  56. kash/llm_utils/llm_api_keys.py +4 -4
  57. kash/llm_utils/llm_features.py +68 -0
  58. kash/llm_utils/llm_messages.py +1 -2
  59. kash/llm_utils/llm_names.py +1 -1
  60. kash/llm_utils/llms.py +8 -3
  61. kash/local_server/__init__.py +5 -2
  62. kash/local_server/local_server.py +8 -5
  63. kash/local_server/local_server_commands.py +2 -2
  64. kash/local_server/local_server_routes.py +1 -7
  65. kash/local_server/local_url_formatters.py +1 -1
  66. kash/mcp/__init__.py +5 -2
  67. kash/mcp/mcp_cli.py +5 -5
  68. kash/mcp/mcp_server_commands.py +5 -5
  69. kash/mcp/mcp_server_routes.py +5 -5
  70. kash/mcp/mcp_server_sse.py +4 -2
  71. kash/media_base/media_cache.py +8 -8
  72. kash/media_base/media_services.py +1 -1
  73. kash/media_base/media_tools.py +6 -6
  74. kash/media_base/services/local_file_media.py +2 -2
  75. kash/media_base/{speech_transcription.py → transcription_deepgram.py} +25 -110
  76. kash/media_base/transcription_format.py +73 -0
  77. kash/media_base/transcription_whisper.py +38 -0
  78. kash/model/__init__.py +73 -5
  79. kash/model/actions_model.py +38 -4
  80. kash/model/concept_model.py +30 -0
  81. kash/model/items_model.py +115 -32
  82. kash/model/params_model.py +24 -0
  83. kash/shell/completions/completion_scoring.py +37 -5
  84. kash/shell/output/kerm_codes.py +1 -2
  85. kash/shell/output/shell_formatting.py +14 -4
  86. kash/shell/shell_main.py +2 -2
  87. kash/shell/utils/exception_printing.py +6 -0
  88. kash/shell/utils/native_utils.py +26 -20
  89. kash/shell/utils/shell_function_wrapper.py +15 -15
  90. kash/text_handling/custom_sliding_transforms.py +12 -4
  91. kash/text_handling/doc_normalization.py +6 -2
  92. kash/text_handling/markdown_render.py +118 -0
  93. kash/text_handling/markdown_utils.py +226 -0
  94. kash/utils/common/function_inspect.py +360 -110
  95. kash/utils/common/import_utils.py +12 -3
  96. kash/utils/common/type_utils.py +0 -29
  97. kash/utils/common/url.py +27 -3
  98. kash/utils/errors.py +6 -0
  99. kash/utils/file_utils/file_ext.py +4 -0
  100. kash/utils/file_utils/file_formats.py +2 -2
  101. kash/utils/file_utils/file_formats_model.py +20 -1
  102. kash/web_content/dir_store.py +1 -2
  103. kash/web_content/file_cache_utils.py +37 -10
  104. kash/web_content/file_processing.py +68 -0
  105. kash/web_content/local_file_cache.py +12 -9
  106. kash/web_content/web_extract.py +8 -3
  107. kash/web_content/web_fetch.py +12 -4
  108. kash/web_gen/__init__.py +0 -4
  109. kash/web_gen/simple_webpage.py +52 -0
  110. kash/web_gen/tabbed_webpage.py +24 -14
  111. kash/web_gen/template_render.py +37 -2
  112. kash/web_gen/templates/base_styles.css.jinja +169 -43
  113. kash/web_gen/templates/base_webpage.html.jinja +110 -45
  114. kash/web_gen/templates/content_styles.css.jinja +4 -2
  115. kash/web_gen/templates/item_view.html.jinja +49 -39
  116. kash/web_gen/templates/simple_webpage.html.jinja +24 -0
  117. kash/web_gen/templates/tabbed_webpage.html.jinja +42 -33
  118. kash/workspaces/__init__.py +15 -2
  119. kash/workspaces/selections.py +18 -3
  120. kash/workspaces/source_items.py +0 -1
  121. kash/workspaces/workspaces.py +5 -11
  122. kash/xonsh_custom/command_nl_utils.py +40 -19
  123. kash/xonsh_custom/custom_shell.py +43 -11
  124. kash/xonsh_custom/customize_prompt.py +39 -21
  125. kash/xonsh_custom/load_into_xonsh.py +22 -25
  126. kash/xonsh_custom/shell_load_commands.py +2 -2
  127. kash/xonsh_custom/xonsh_completers.py +2 -249
  128. kash/xonsh_custom/xonsh_keybindings.py +282 -0
  129. kash/xonsh_custom/xonsh_modern_tools.py +3 -3
  130. kash/xontrib/kash_extension.py +5 -6
  131. {kash_shell-0.3.9.dist-info → kash_shell-0.3.11.dist-info}/METADATA +10 -8
  132. {kash_shell-0.3.9.dist-info → kash_shell-0.3.11.dist-info}/RECORD +137 -136
  133. kash/actions/core/webpage_config.py +0 -21
  134. kash/concepts/concept_formats.py +0 -23
  135. kash/shell/clideps/api_keys.py +0 -100
  136. kash/shell/clideps/dotenv_setup.py +0 -115
  137. kash/shell/clideps/dotenv_utils.py +0 -98
  138. kash/shell/clideps/pkg_deps.py +0 -257
  139. kash/shell/clideps/platforms.py +0 -11
  140. kash/shell/clideps/terminal_features.py +0 -56
  141. kash/shell/utils/osc_utils.py +0 -95
  142. kash/shell/utils/terminal_images.py +0 -133
  143. kash/text_handling/markdown_util.py +0 -167
  144. kash/utils/common/atomic_var.py +0 -171
  145. kash/utils/common/string_replace.py +0 -93
  146. kash/utils/common/string_template.py +0 -101
  147. /kash/{concepts → embeddings}/cosine.py +0 -0
  148. /kash/{concepts → embeddings}/embeddings.py +0 -0
  149. {kash_shell-0.3.9.dist-info → kash_shell-0.3.11.dist-info}/WHEEL +0 -0
  150. {kash_shell-0.3.9.dist-info → kash_shell-0.3.11.dist-info}/entry_points.txt +0 -0
  151. {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 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
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 PrintHooks, console_pager, cprint, print_markdown
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
- clear: bool = False,
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 clear: Clear the full selection history.
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
- # TODO: It would be nice to be able to read stdin from a pipe but this isn't working rn.
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, clear, clear_future]
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 clear:
100
- ws.selections.clear()
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 format_name_and_description, format_name_and_value
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. Inputs can be URLs or paths to URL resources.
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, change directory to the current workspace.
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 list_params(full: bool = False) -> None:
411
+ def params(full: bool = False) -> None:
406
412
  """
407
- Show or set currently set of workspace parameters, which are settings that may be used
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, use_cache=not no_cache, refetch=refetch)
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(44, 28%, 90%, 0.3)"),
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)"),
@@ -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, default: None) -> None: ...
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 reload_all_action_classes
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 = reload_all_action_classes()
16
+ actions = refresh_action_classes()
17
17
 
18
18
  return commands, actions