kash-shell 0.3.9__py3-none-any.whl → 0.3.10__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 (135) hide show
  1. kash/actions/__init__.py +4 -4
  2. kash/actions/core/markdownify.py +5 -2
  3. kash/actions/core/readability.py +5 -2
  4. kash/actions/core/render_as_html.py +18 -0
  5. kash/actions/core/webpage_config.py +12 -4
  6. kash/commands/__init__.py +8 -20
  7. kash/commands/base/basic_file_commands.py +15 -0
  8. kash/commands/base/debug_commands.py +13 -0
  9. kash/commands/base/general_commands.py +21 -16
  10. kash/commands/base/logs_commands.py +4 -2
  11. kash/commands/base/model_commands.py +8 -8
  12. kash/commands/base/search_command.py +3 -2
  13. kash/commands/base/show_command.py +5 -3
  14. kash/commands/extras/parse_uv_lock.py +186 -0
  15. kash/commands/help/doc_commands.py +2 -31
  16. kash/commands/help/welcome.py +33 -0
  17. kash/commands/workspace/selection_commands.py +11 -6
  18. kash/commands/workspace/workspace_commands.py +18 -15
  19. kash/config/colors.py +2 -0
  20. kash/config/env_settings.py +14 -1
  21. kash/config/init.py +2 -2
  22. kash/config/logger.py +59 -56
  23. kash/config/logger_basic.py +3 -3
  24. kash/config/settings.py +116 -57
  25. kash/config/setup.py +28 -12
  26. kash/config/text_styles.py +3 -13
  27. kash/docs/load_api_docs.py +2 -1
  28. kash/docs/markdown/topics/a3_getting_started.md +3 -2
  29. kash/{concepts → embeddings}/text_similarity.py +2 -2
  30. kash/exec/__init__.py +20 -3
  31. kash/exec/action_decorators.py +18 -4
  32. kash/exec/action_exec.py +41 -23
  33. kash/exec/action_registry.py +13 -48
  34. kash/exec/command_registry.py +2 -1
  35. kash/exec/fetch_url_metadata.py +4 -6
  36. kash/exec/importing.py +56 -0
  37. kash/exec/llm_transforms.py +6 -7
  38. kash/exec/precondition_registry.py +2 -1
  39. kash/exec/preconditions.py +16 -1
  40. kash/exec/shell_callable_action.py +33 -19
  41. kash/file_storage/file_store.py +23 -10
  42. kash/file_storage/item_file_format.py +5 -2
  43. kash/file_storage/metadata_dirs.py +11 -2
  44. kash/help/assistant.py +1 -1
  45. kash/help/assistant_instructions.py +2 -1
  46. kash/help/help_embeddings.py +2 -2
  47. kash/help/help_printing.py +7 -11
  48. kash/llm_utils/clean_headings.py +1 -1
  49. kash/llm_utils/llm_api_keys.py +4 -4
  50. kash/llm_utils/llm_features.py +68 -0
  51. kash/llm_utils/llm_messages.py +1 -2
  52. kash/llm_utils/llm_names.py +1 -1
  53. kash/llm_utils/llms.py +8 -3
  54. kash/local_server/__init__.py +5 -2
  55. kash/local_server/local_server.py +8 -5
  56. kash/local_server/local_server_commands.py +2 -2
  57. kash/local_server/local_url_formatters.py +1 -1
  58. kash/mcp/__init__.py +5 -2
  59. kash/mcp/mcp_cli.py +5 -5
  60. kash/mcp/mcp_server_commands.py +5 -5
  61. kash/mcp/mcp_server_routes.py +5 -5
  62. kash/mcp/mcp_server_sse.py +4 -2
  63. kash/media_base/media_cache.py +8 -8
  64. kash/media_base/media_services.py +1 -1
  65. kash/media_base/media_tools.py +6 -6
  66. kash/media_base/services/local_file_media.py +2 -2
  67. kash/media_base/{speech_transcription.py → transcription_deepgram.py} +25 -110
  68. kash/media_base/transcription_format.py +73 -0
  69. kash/media_base/transcription_whisper.py +38 -0
  70. kash/model/__init__.py +73 -5
  71. kash/model/actions_model.py +38 -4
  72. kash/model/concept_model.py +30 -0
  73. kash/model/items_model.py +44 -7
  74. kash/model/params_model.py +24 -0
  75. kash/shell/completions/completion_scoring.py +37 -5
  76. kash/shell/output/kerm_codes.py +1 -2
  77. kash/shell/output/shell_formatting.py +14 -4
  78. kash/shell/shell_main.py +2 -2
  79. kash/shell/utils/exception_printing.py +6 -0
  80. kash/shell/utils/native_utils.py +26 -20
  81. kash/text_handling/custom_sliding_transforms.py +12 -4
  82. kash/text_handling/doc_normalization.py +6 -2
  83. kash/text_handling/markdown_render.py +117 -0
  84. kash/text_handling/markdown_utils.py +204 -0
  85. kash/utils/common/import_utils.py +12 -3
  86. kash/utils/common/type_utils.py +0 -29
  87. kash/utils/common/url.py +27 -3
  88. kash/utils/errors.py +6 -0
  89. kash/utils/file_utils/file_formats.py +2 -2
  90. kash/utils/file_utils/file_formats_model.py +3 -0
  91. kash/web_content/dir_store.py +1 -2
  92. kash/web_content/file_cache_utils.py +37 -10
  93. kash/web_content/file_processing.py +68 -0
  94. kash/web_content/local_file_cache.py +12 -9
  95. kash/web_content/web_extract.py +8 -3
  96. kash/web_content/web_fetch.py +12 -4
  97. kash/web_gen/tabbed_webpage.py +5 -2
  98. kash/web_gen/templates/base_styles.css.jinja +120 -14
  99. kash/web_gen/templates/base_webpage.html.jinja +60 -13
  100. kash/web_gen/templates/content_styles.css.jinja +4 -2
  101. kash/web_gen/templates/item_view.html.jinja +2 -2
  102. kash/web_gen/templates/tabbed_webpage.html.jinja +1 -2
  103. kash/workspaces/__init__.py +15 -2
  104. kash/workspaces/selections.py +18 -3
  105. kash/workspaces/source_items.py +0 -1
  106. kash/workspaces/workspaces.py +5 -11
  107. kash/xonsh_custom/command_nl_utils.py +40 -19
  108. kash/xonsh_custom/custom_shell.py +43 -11
  109. kash/xonsh_custom/customize_prompt.py +39 -21
  110. kash/xonsh_custom/load_into_xonsh.py +22 -25
  111. kash/xonsh_custom/shell_load_commands.py +2 -2
  112. kash/xonsh_custom/xonsh_completers.py +2 -249
  113. kash/xonsh_custom/xonsh_keybindings.py +282 -0
  114. kash/xonsh_custom/xonsh_modern_tools.py +3 -3
  115. kash/xontrib/kash_extension.py +5 -6
  116. {kash_shell-0.3.9.dist-info → kash_shell-0.3.10.dist-info}/METADATA +8 -6
  117. {kash_shell-0.3.9.dist-info → kash_shell-0.3.10.dist-info}/RECORD +122 -123
  118. kash/concepts/concept_formats.py +0 -23
  119. kash/shell/clideps/api_keys.py +0 -100
  120. kash/shell/clideps/dotenv_setup.py +0 -115
  121. kash/shell/clideps/dotenv_utils.py +0 -98
  122. kash/shell/clideps/pkg_deps.py +0 -257
  123. kash/shell/clideps/platforms.py +0 -11
  124. kash/shell/clideps/terminal_features.py +0 -56
  125. kash/shell/utils/osc_utils.py +0 -95
  126. kash/shell/utils/terminal_images.py +0 -133
  127. kash/text_handling/markdown_util.py +0 -167
  128. kash/utils/common/atomic_var.py +0 -171
  129. kash/utils/common/string_replace.py +0 -93
  130. kash/utils/common/string_template.py +0 -101
  131. /kash/{concepts → embeddings}/cosine.py +0 -0
  132. /kash/{concepts → embeddings}/embeddings.py +0 -0
  133. {kash_shell-0.3.9.dist-info → kash_shell-0.3.10.dist-info}/WHEEL +0 -0
  134. {kash_shell-0.3.9.dist-info → kash_shell-0.3.10.dist-info}/entry_points.txt +0 -0
  135. {kash_shell-0.3.9.dist-info → kash_shell-0.3.10.dist-info}/licenses/LICENSE +0 -0
kash/config/setup.py CHANGED
@@ -3,34 +3,50 @@ from functools import cache
3
3
  from pathlib import Path
4
4
  from typing import Any
5
5
 
6
+ from clideps.env_vars.dotenv_utils import load_dotenv_paths
7
+
8
+ from kash.config.logger import reset_rich_logging
6
9
  from kash.config.logger_basic import basic_logging_setup
7
- from kash.config.settings import LogLevel, get_system_config_dir
10
+ from kash.config.settings import LogLevel, configure_ws_and_settings, global_settings
8
11
 
9
12
 
10
13
  @cache
11
- def setup(rich_logging: bool, file_log_path: Path | None = None, level: LogLevel = LogLevel.info):
14
+ def kash_setup(
15
+ *,
16
+ rich_logging: bool,
17
+ kash_ws_root: Path | None = None,
18
+ log_path: Path | None = None,
19
+ level: LogLevel = LogLevel.info,
20
+ ):
12
21
  """
13
22
  One-time top-level setup of essential logging, keys, directories, and configs.
14
23
  Idempotent.
15
24
 
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.
25
+ Can call this if embedding kash in another app.
26
+ Can be used to set the global default workspace and logs directory
27
+ and/or the default log file.
28
+ If `rich_logging` is True, then rich logging with warnings only for console use.
29
+ If `rich_logging` is False, then use basic logging to a file and stderr.
18
30
  """
19
- from kash.config.logger import reload_rich_logging_setup
20
- from kash.shell.clideps.dotenv_utils import load_dotenv_paths
21
31
  from kash.utils.common.stack_traces import add_stacktrace_handler
22
32
 
33
+ add_stacktrace_handler()
34
+
35
+ # Settings may depend on environment variables, so load them first.
36
+ load_dotenv_paths(True, True, global_settings().system_config_dir)
37
+
38
+ # Then configure the workspace and settings before finalizing logging.
39
+ if kash_ws_root:
40
+ configure_ws_and_settings(kash_ws_root)
41
+
42
+ # Now set up logging, as it might depend on workspace root.
23
43
  if rich_logging:
24
- reload_rich_logging_setup()
44
+ reset_rich_logging(log_path=log_path)
25
45
  else:
26
- basic_logging_setup(file_log_path=file_log_path, level=level)
46
+ basic_logging_setup(log_path=log_path, level=level)
27
47
 
28
48
  _lib_setup()
29
49
 
30
- add_stacktrace_handler()
31
-
32
- load_dotenv_paths(True, True, get_system_config_dir())
33
-
34
50
 
35
51
  def _lib_setup():
36
52
  from frontmatter_format.yaml_util import add_default_yaml_customizer
@@ -46,7 +46,9 @@ SPINNER = "dots12"
46
46
 
47
47
  BAT_THEME = "Coldark-Dark"
48
48
 
49
- BAT_STYLE = "header-filename,header-filesize,grid,changes"
49
+ BAT_STYLE = "header-filename,header-filesize,grid,numbers,changes"
50
+
51
+ BAT_STYLE_PLAIN = "plain"
50
52
 
51
53
 
52
54
  ## Colors
@@ -296,18 +298,6 @@ EMOJI_MSG_INDENT = "⋮"
296
298
 
297
299
  EMOJI_BREADCRUMB_SEP = "›"
298
300
 
299
- EMOJI_TRUE = "✔︎"
300
-
301
- EMOJI_FALSE = "✘"
302
-
303
-
304
- def success_emoji(value: bool, success_only: bool = False) -> str:
305
- return EMOJI_TRUE if value else " " if success_only else EMOJI_FALSE
306
-
307
-
308
- def format_success_emoji(value: bool, success_only: bool = False) -> Text:
309
- return Text(success_emoji(value, success_only), style=COLOR_SUCCESS if value else COLOR_FAILURE)
310
-
311
301
 
312
302
  ## Special headings
313
303
 
@@ -1,7 +1,8 @@
1
+ from strif import StringTemplate
2
+
1
3
  from kash.config.logger import get_logger
2
4
  from kash.docs.load_help_topics import load_help_src
3
5
  from kash.docs.load_source_code import load_source_code
4
- from kash.utils.common.string_template import StringTemplate
5
6
 
6
7
  log = get_logger(__name__)
7
8
 
@@ -243,8 +243,9 @@ A few of the most important commands for managing files and work are these:
243
243
  browser to view it.
244
244
 
245
245
  - `workspace` shows or selects or creates a new workspace.
246
- Initially you work in the `global` workspace but for more real work you'll want to
247
- create a workspace, which is a directory to hold the files you are working with.
246
+ Initially you work in the default global workspace (typically at `~/Kash/workspace`)
247
+ but for more real work you'll want to create a workspace, which is a directory to hold
248
+ the files you are working with.
248
249
 
249
250
  - `select` shows or sets selections, which are the set of files the next command will
250
251
  run on, within the current workspace.
@@ -5,9 +5,9 @@ from funlog import log_calls
5
5
  from litellm import embedding
6
6
  from litellm.types.utils import EmbeddingResponse
7
7
 
8
- from kash.concepts.cosine import ArrayLike, cosine
9
- from kash.concepts.embeddings import Embeddings
10
8
  from kash.config.logger import get_logger
9
+ from kash.embeddings.cosine import ArrayLike, cosine
10
+ from kash.embeddings.embeddings import Embeddings
11
11
  from kash.llm_utils.llms import DEFAULT_EMBEDDING_MODEL, EmbeddingModel
12
12
  from kash.utils.errors import ApiResultError
13
13
 
kash/exec/__init__.py CHANGED
@@ -1,9 +1,7 @@
1
- # flake8: noqa: F401
2
-
3
1
  from kash.exec.action_decorators import kash_action, kash_action_class
4
2
  from kash.exec.action_exec import SkipItem, prepare_action_input, run_action_with_shell_context
5
- from kash.exec.action_registry import import_action_subdirs
6
3
  from kash.exec.command_registry import kash_command
4
+ from kash.exec.importing import import_and_register
7
5
  from kash.exec.llm_transforms import llm_transform_item, llm_transform_str
8
6
  from kash.exec.precondition_registry import kash_precondition
9
7
  from kash.exec.resolve_args import (
@@ -14,3 +12,22 @@ from kash.exec.resolve_args import (
14
12
  resolve_locator_arg,
15
13
  resolve_path_arg,
16
14
  )
15
+
16
+ __all__ = [
17
+ "kash_action",
18
+ "kash_action_class",
19
+ "SkipItem",
20
+ "prepare_action_input",
21
+ "run_action_with_shell_context",
22
+ "kash_command",
23
+ "import_and_register",
24
+ "llm_transform_item",
25
+ "llm_transform_str",
26
+ "kash_precondition",
27
+ "assemble_path_args",
28
+ "assemble_store_path_args",
29
+ "import_locator_args",
30
+ "resolvable_paths",
31
+ "resolve_locator_arg",
32
+ "resolve_path_arg",
33
+ ]
@@ -210,7 +210,12 @@ def kash_action(
210
210
  title_template: TitleTemplate = TitleTemplate("{title}"),
211
211
  llm_options: LLMOptions = LLMOptions(),
212
212
  override_state: State | None = None,
213
+ # Including these for completeness but usually don't want to set them globally
214
+ # in the decorator:
213
215
  rerun: bool = False,
216
+ refetch: bool = False,
217
+ tmp_output: bool = False,
218
+ no_format: bool = False,
214
219
  ) -> Callable[[AF], AF]:
215
220
  """
216
221
  A function decorator to create and register an action. The annotated function must
@@ -333,8 +338,11 @@ def kash_action(
333
338
  else:
334
339
  kw_args[fp.name] = self.get_param(fp.name)
335
340
 
336
- log.info("Action function param declarations:\n%s", fmt_lines(self.params))
337
- log.info("Action function param values:\n%s", self.param_value_summary_str())
341
+ if self.params:
342
+ log.info("Action function param declarations:\n%s", fmt_lines(self.params))
343
+ log.info("Action function param values:\n%s", self.param_value_summary_str())
344
+ else:
345
+ log.info("Action function has no declared params")
338
346
 
339
347
  log.message(
340
348
  "Action function call:\n%s",
@@ -379,11 +387,17 @@ def kash_action(
379
387
  context = provided_context
380
388
  else:
381
389
  context = ExecContext(
382
- action, current_ws().base_dir, rerun=rerun, override_state=override_state
390
+ action,
391
+ current_ws().base_dir,
392
+ rerun=rerun,
393
+ refetch=refetch,
394
+ override_state=override_state,
395
+ tmp_output=tmp_output,
396
+ no_format=no_format,
383
397
  )
384
398
 
385
399
  # Run the action.
386
- result, _, _ = run_action_with_caching(context, action_input, rerun=rerun)
400
+ result, _, _ = run_action_with_caching(context, action_input)
387
401
 
388
402
  return result
389
403
 
kash/exec/action_exec.py CHANGED
@@ -32,7 +32,7 @@ from kash.workspaces.workspace_importing import import_and_load
32
32
  log = get_logger(__name__)
33
33
 
34
34
 
35
- def prepare_action_input(*input_args: CommandArg) -> ActionInput:
35
+ def prepare_action_input(*input_args: CommandArg, refetch: bool = False) -> ActionInput:
36
36
  """
37
37
  Prepare input args, which may be URLs or paths, into items that correspond to
38
38
  URL or file resources, either finding them in the workspace or importing them.
@@ -50,13 +50,16 @@ def prepare_action_input(*input_args: CommandArg) -> ActionInput:
50
50
  if input_items:
51
51
  log.message("Assembling metadata for input items:\n%s", fmt_lines(input_items))
52
52
  input_items = [
53
- fetch_url_item_metadata(item) if is_url_item(item) else item for item in input_items
53
+ fetch_url_item_metadata(item, refetch=refetch) if is_url_item(item) else item
54
+ for item in input_items
54
55
  ]
55
56
 
56
57
  return ActionInput(input_items)
57
58
 
58
59
 
59
- def validate_action_input(ws: FileStore, action: Action, action_input: ActionInput) -> Operation:
60
+ def validate_action_input(
61
+ context: ExecContext, ws: FileStore, action: Action, action_input: ActionInput
62
+ ) -> Operation:
60
63
  """
61
64
  Validate an action input, ensuring the right number of args, all explicit params are filled,
62
65
  and the precondition holds and return an `Operation` that describes what will happen.
@@ -74,7 +77,9 @@ def validate_action_input(ws: FileStore, action: Action, action_input: ActionInp
74
77
  # If the inputs are paths, record the input paths, including hashes.
75
78
  store_paths = [StorePath(not_none(item.store_path)) for item in input_items if item.store_path]
76
79
  inputs = [Input(store_path, ws.hash(store_path)) for store_path in store_paths]
77
- operation = Operation(action.name, inputs, action.param_value_summary())
80
+ # Add any non-default runtime options into the options summary.
81
+ options = {**action.param_value_summary(), **context.runtime_options}
82
+ operation = Operation(action.name, inputs, options)
78
83
 
79
84
  return operation
80
85
 
@@ -93,10 +98,7 @@ def log_action(action: Action, action_input: ActionInput, operation: Operation):
93
98
 
94
99
 
95
100
  def check_for_existing_result(
96
- context: ExecContext,
97
- action_input: ActionInput,
98
- operation: Operation,
99
- rerun: bool = False,
101
+ context: ExecContext, action_input: ActionInput, operation: Operation
100
102
  ) -> ActionResult | None:
101
103
  """
102
104
  Check if we already have the results for this operation (same action and inputs)
@@ -105,7 +107,7 @@ def check_for_existing_result(
105
107
  """
106
108
  action = context.action
107
109
  ws = context.workspace
108
- rerun = context.rerun or rerun
110
+ rerun = context.rerun
109
111
 
110
112
  existing_result = None
111
113
 
@@ -261,7 +263,12 @@ def _run_for_each_item(context: ExecContext, input: ActionInput) -> ActionResult
261
263
 
262
264
 
263
265
  def save_action_result(
264
- ws: FileStore, result: ActionResult, action_input: ActionInput
266
+ ws: FileStore,
267
+ result: ActionResult,
268
+ action_input: ActionInput,
269
+ *,
270
+ as_tmp: bool = False,
271
+ no_format: bool = False,
265
272
  ) -> tuple[list[StorePath], list[StorePath]]:
266
273
  """
267
274
  Save the result of an action to the workspace. Handles skipping duplicates and
@@ -277,7 +284,7 @@ def save_action_result(
277
284
  skipped_paths.append(store_path)
278
285
  continue
279
286
 
280
- ws.save(item)
287
+ ws.save(item, as_tmp=as_tmp, no_format=no_format)
281
288
 
282
289
  if skipped_paths:
283
290
  log.message(
@@ -308,9 +315,7 @@ def save_action_result(
308
315
 
309
316
 
310
317
  def run_action_with_caching(
311
- context: ExecContext,
312
- action_input: ActionInput,
313
- rerun: bool = False,
318
+ context: ExecContext, action_input: ActionInput
314
319
  ) -> tuple[ActionResult, list[StorePath], list[StorePath]]:
315
320
  """
316
321
  Run an action, including validation, only rerunning if `rerun` requested or
@@ -328,15 +333,15 @@ def run_action_with_caching(
328
333
  item.context = context
329
334
 
330
335
  # Assemble the operation and validate the action input.
331
- operation = validate_action_input(ws, action, action_input)
336
+ operation = validate_action_input(context, ws, action, action_input)
332
337
 
333
338
  # Log what we're about to run.
334
339
  log_action(action, action_input, operation)
335
340
 
336
341
  # Check if a previous run already produced the result.
337
- existing_result = check_for_existing_result(context, action_input, operation, rerun=rerun)
342
+ existing_result = check_for_existing_result(context, action_input, operation)
338
343
 
339
- if existing_result and not rerun:
344
+ if existing_result and not context.rerun:
340
345
  # Use the cached result.
341
346
  result = existing_result
342
347
  result_store_paths = [StorePath(not_none(item.store_path)) for item in result.items]
@@ -353,7 +358,9 @@ def run_action_with_caching(
353
358
  else:
354
359
  # Run it!
355
360
  result = run_action_operation(context, action_input, operation)
356
- result_store_paths, archived_store_paths = save_action_result(ws, result, action_input)
361
+ result_store_paths, archived_store_paths = save_action_result(
362
+ ws, result, action_input, as_tmp=context.tmp_output, no_format=context.no_format
363
+ )
357
364
 
358
365
  PrintHooks.before_done_message()
359
366
  log.message(
@@ -371,9 +378,12 @@ def run_action_with_shell_context(
371
378
  action_spec: str | type[Action],
372
379
  explicit_param_values: RawParamValues,
373
380
  *provided_args: str,
374
- rerun=False,
381
+ rerun: bool = False,
382
+ refetch: bool = False,
375
383
  override_state: State | None = None,
376
- internal_call=False,
384
+ tmp_output: bool = False,
385
+ no_format: bool = False,
386
+ internal_call: bool = False,
377
387
  ) -> ActionResult:
378
388
  """
379
389
  Main function to run an action from the shell. Wraps `run_action_if_needed` to
@@ -404,8 +414,16 @@ def run_action_with_shell_context(
404
414
  action = action_cls.create(explicit_parsed, ws_parsed)
405
415
  action_name = action.name
406
416
 
407
- # Execution context.
408
- context = ExecContext(action, ws.base_dir, rerun, override_state)
417
+ # Execution context. This is fixed for the duration of the action.
418
+ context = ExecContext(
419
+ action=action,
420
+ workspace_dir=ws.base_dir,
421
+ rerun=rerun,
422
+ refetch=refetch,
423
+ override_state=override_state,
424
+ tmp_output=tmp_output,
425
+ no_format=no_format,
426
+ )
409
427
 
410
428
  # Collect args from the provided args or otherwise the current selection.
411
429
  args, from_selection = assemble_action_args(*provided_args, use_selection=action.uses_selection)
@@ -422,7 +440,7 @@ def run_action_with_shell_context(
422
440
  )
423
441
 
424
442
  # Get items for each input arg.
425
- input = prepare_action_input(*args)
443
+ input = prepare_action_input(*args, refetch=refetch)
426
444
 
427
445
  # Finally, run the action.
428
446
  result, result_store_paths, archived_store_paths = run_action_with_caching(context, input)
@@ -1,18 +1,14 @@
1
- from pathlib import Path
2
-
3
1
  from cachetools import Cache, cached
4
- from prettyfmt import fmt_lines, fmt_path
2
+ from strif import AtomicVar
5
3
 
6
4
  from kash.config.logger import get_logger
7
5
  from kash.model.actions_model import Action
8
- from kash.utils.common.atomic_var import AtomicVar
9
- from kash.utils.common.import_utils import Tallies, import_subdirs
10
6
  from kash.utils.errors import InvalidInput
11
7
 
12
8
  log = get_logger(__name__)
13
9
 
14
10
  # Global registry of action classes.
15
- _action_classes: AtomicVar[dict[str, type[Action]]] = AtomicVar({})
11
+ action_classes: AtomicVar[dict[str, type[Action]]] = AtomicVar({})
16
12
 
17
13
 
18
14
  # Want it fast to get the full list of actions (important for tab completions
@@ -30,64 +26,29 @@ def register_action_class(cls: type[Action]):
30
26
  """
31
27
  Register an action class.
32
28
  """
33
- with _action_classes.updates() as action_classes:
34
- if cls.name in action_classes:
29
+ with action_classes.updates() as ac:
30
+ if cls.name in ac:
35
31
  log.warning(
36
32
  "Duplicate action name (defined twice by accident?): %s (%s)",
37
33
  cls.name,
38
34
  cls,
39
35
  )
40
- action_classes[cls.name] = cls
36
+ ac[cls.name] = cls
41
37
 
42
38
  clear_action_cache()
43
39
 
44
40
 
45
- def import_action_subdirs(
46
- subdirs: list[str],
47
- package_name: str | None,
48
- parent_dir: Path,
49
- tallies: Tallies | None = None,
50
- ):
51
- """
52
- Hook to call from `__init__.py` in a directory containing actions,
53
- so that they are auto-registered on import.
54
-
55
- Usage:
56
- ```
57
- import_action_subdirs(["subdir_name"], __package__, Path(__file__).parent)
58
- ```
59
- """
60
- if tallies is None:
61
- tallies = {}
62
- with _action_classes.updates() as action_classes:
63
- prev_count = len(action_classes)
64
-
65
- if not package_name:
66
- raise ValueError(f"Package name missing importing actions: {fmt_path(parent_dir)}")
67
-
68
- import_subdirs(package_name, parent_dir, subdirs, tallies)
69
- reload_all_action_classes()
70
-
71
- log.info(
72
- "Loaded actions: %s new actions in %s directories below %s:\n%s",
73
- len(action_classes) - prev_count,
74
- len(tallies),
75
- fmt_path(parent_dir),
76
- fmt_lines(f"{k}: {v} files" for k, v in tallies.items()),
77
- )
78
-
79
-
80
41
  @cached(_action_classes_cache)
81
42
  def get_all_action_classes() -> dict[str, type[Action]]:
82
43
  # Be sure actions are imported.
83
44
  import kash.actions # noqa: F401
84
45
 
85
46
  # Returns a copy for safety.
86
- action_classes = _action_classes.copy()
87
- if len(action_classes) == 0:
47
+ ac = action_classes.copy()
48
+ if len(ac) == 0:
88
49
  log.error("No actions found! Was there an import error?")
89
50
 
90
- return dict(action_classes)
51
+ return dict(ac)
91
52
 
92
53
 
93
54
  def look_up_action_class(action_name: str) -> type[Action]:
@@ -97,7 +58,11 @@ def look_up_action_class(action_name: str) -> type[Action]:
97
58
  return actions[action_name]
98
59
 
99
60
 
100
- def reload_all_action_classes() -> dict[str, type[Action]]:
61
+ def refresh_action_classes() -> dict[str, type[Action]]:
62
+ """
63
+ Reload all action classes, refreshing the cache. Call after registering
64
+ new action classes.
65
+ """
101
66
  clear_action_cache()
102
67
  return get_all_action_classes()
103
68
 
@@ -1,9 +1,10 @@
1
1
  from collections.abc import Callable
2
2
  from typing import overload
3
3
 
4
+ from strif import AtomicVar
5
+
4
6
  from kash.config.logger import get_logger
5
7
  from kash.exec_model.shell_model import ShellResult
6
- from kash.utils.common.atomic_var import AtomicVar
7
8
  from kash.utils.errors import InvalidInput
8
9
 
9
10
  log = get_logger(__name__)
@@ -13,9 +13,7 @@ from kash.workspaces import current_ws
13
13
  log = get_logger(__name__)
14
14
 
15
15
 
16
- def fetch_url_metadata(
17
- locator: Url | StorePath, use_cache: bool = True, refetch: bool = False
18
- ) -> Item:
16
+ def fetch_url_metadata(locator: Url | StorePath, refetch: bool = False) -> Item:
19
17
  ws = current_ws()
20
18
  if is_url(locator):
21
19
  # Import or find URL as a resource in the current workspace.
@@ -28,10 +26,10 @@ def fetch_url_metadata(
28
26
  else:
29
27
  raise InvalidInput(f"Not a URL or URL resource: {fmt_loc(locator)}")
30
28
 
31
- return fetch_url_item_metadata(item, use_cache=use_cache, refetch=refetch)
29
+ return fetch_url_item_metadata(item, refetch=refetch)
32
30
 
33
31
 
34
- def fetch_url_item_metadata(item: Item, use_cache: bool = True, refetch: bool = False) -> Item:
32
+ def fetch_url_item_metadata(item: Item, refetch: bool = False) -> Item:
35
33
  """
36
34
  Fetch metadata for a URL using a media service if we recognize the URL,
37
35
  and otherwise fetching and extracting it from the web page HTML.
@@ -56,7 +54,7 @@ def fetch_url_item_metadata(item: Item, use_cache: bool = True, refetch: bool =
56
54
  fetched_item = Item.from_media_metadata(media_metadata)
57
55
  fetched_item = item.merged_copy(fetched_item)
58
56
  else:
59
- page_data = fetch_extract(url, use_cache=use_cache)
57
+ page_data = fetch_extract(url, refetch=refetch)
60
58
  fetched_item = item.new_copy_with(
61
59
  title=page_data.title or item.title,
62
60
  description=page_data.description or item.description,
kash/exec/importing.py ADDED
@@ -0,0 +1,56 @@
1
+ from pathlib import Path
2
+
3
+ from prettyfmt import fmt_lines, fmt_path
4
+
5
+ from kash.config.logger import get_logger
6
+ from kash.exec.action_registry import action_classes, refresh_action_classes
7
+ from kash.exec.command_registry import get_all_commands
8
+ from kash.utils.common.import_utils import Tallies, import_subdirs
9
+
10
+ log = get_logger(__name__)
11
+
12
+
13
+ def import_and_register(
14
+ package_name: str | None,
15
+ parent_dir: Path,
16
+ subdir_names: list[str] | None = None,
17
+ tallies: Tallies | None = None,
18
+ ):
19
+ """
20
+ This hook can be used for auto-registering commands and actions from any
21
+ subdirectory of a given package.
22
+
23
+ Useful to call from `__init__.py` files to import a directory of code,
24
+ auto-registering annotated commands and actions and also handles refreshing the
25
+ action cache if new actions are registered.
26
+
27
+ Usage:
28
+ ```
29
+ import_and_register(["subdir1", "subdir2"], __package__, Path(__file__).parent)
30
+ ```
31
+ """
32
+ if not package_name:
33
+ raise ValueError(f"Package name missing importing actions: {fmt_path(parent_dir)}")
34
+ if tallies is None:
35
+ tallies = {}
36
+
37
+ with action_classes.updates() as ac:
38
+ prev_command_count = len(get_all_commands())
39
+ prev_action_count = len(ac)
40
+
41
+ import_subdirs(package_name, parent_dir, subdir_names, tallies)
42
+
43
+ new_command_count = len(get_all_commands()) - prev_command_count
44
+ new_action_count = len(ac) - prev_action_count
45
+
46
+ if new_action_count > 0:
47
+ refresh_action_classes()
48
+
49
+ log.info(
50
+ "Loaded %s new commands and %s new actions in %s directories below %s:\n%s",
51
+ new_command_count,
52
+ new_action_count,
53
+ len(tallies),
54
+ fmt_path(parent_dir),
55
+ fmt_lines(f"{k}: {v} files" for k, v in tallies.items()),
56
+ )
@@ -1,18 +1,18 @@
1
1
  from dataclasses import replace
2
2
 
3
3
  from chopdiff.docs import DiffFilter, TextDoc
4
- from chopdiff.transforms import WindowSettings, accept_all, filtered_transform
4
+ from chopdiff.transforms import WindowSettings, filtered_transform
5
+ from clideps.env_vars.dotenv_utils import load_dotenv_paths
5
6
  from flowmark import fill_markdown
6
7
 
7
8
  from kash.config.logger import get_logger
8
- from kash.config.settings import get_system_config_dir
9
+ from kash.config.settings import global_settings
9
10
  from kash.llm_utils import LLMName
10
11
  from kash.llm_utils.fuzzy_parsing import strip_markdown_fence
11
12
  from kash.llm_utils.llm_completion import llm_template_completion
12
13
  from kash.llm_utils.llm_messages import Message, MessageTemplate
13
14
  from kash.model.actions_model import LLMOptions
14
15
  from kash.model.items_model import Item, ItemType
15
- from kash.shell.clideps.dotenv_utils import load_dotenv_paths
16
16
  from kash.utils.errors import InvalidInput
17
17
  from kash.utils.file_utils.file_formats_model import Format
18
18
 
@@ -25,7 +25,7 @@ def windowed_llm_transform(
25
25
  template: MessageTemplate,
26
26
  input: str,
27
27
  windowing: WindowSettings | None,
28
- diff_filter: DiffFilter,
28
+ diff_filter: DiffFilter | None = None,
29
29
  check_no_results: bool = True,
30
30
  ) -> TextDoc:
31
31
  def doc_transform(input_doc: TextDoc) -> TextDoc:
@@ -48,7 +48,7 @@ def windowed_llm_transform(
48
48
 
49
49
 
50
50
  def llm_transform_str(options: LLMOptions, input_str: str, check_no_results: bool = True) -> str:
51
- load_dotenv_paths(True, True, get_system_config_dir())
51
+ load_dotenv_paths(True, True, global_settings().system_config_dir)
52
52
 
53
53
  if options.windowing and options.windowing.size:
54
54
  log.message(
@@ -57,7 +57,6 @@ def llm_transform_str(options: LLMOptions, input_str: str, check_no_results: boo
57
57
  options.op_name,
58
58
  options.windowing,
59
59
  )
60
- diff_filter = options.diff_filter or accept_all
61
60
 
62
61
  result_str = windowed_llm_transform(
63
62
  options.model,
@@ -65,7 +64,7 @@ def llm_transform_str(options: LLMOptions, input_str: str, check_no_results: boo
65
64
  options.body_template,
66
65
  input_str,
67
66
  options.windowing,
68
- diff_filter,
67
+ diff_filter=options.diff_filter,
69
68
  ).reassemble()
70
69
  else:
71
70
  log.message(
@@ -1,9 +1,10 @@
1
1
  from collections.abc import Callable
2
2
 
3
+ from strif import AtomicVar
4
+
3
5
  from kash.config.logger import get_logger
4
6
  from kash.model.items_model import Item
5
7
  from kash.model.preconditions_model import Precondition
6
- from kash.utils.common.atomic_var import AtomicVar
7
8
 
8
9
  log = get_logger(__name__)
9
10