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
kash/exec/action_decorators.py
CHANGED
|
@@ -174,7 +174,7 @@ def _merge_param_declarations(
|
|
|
174
174
|
merged_params[fp.name] = Param(
|
|
175
175
|
name=fp.name,
|
|
176
176
|
description=None,
|
|
177
|
-
type=fp.
|
|
177
|
+
type=fp.effective_type or str,
|
|
178
178
|
default_value=fp.default if fp.has_default else None,
|
|
179
179
|
is_explicit=not fp.has_default,
|
|
180
180
|
)
|
|
@@ -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
|
|
@@ -243,13 +248,13 @@ def kash_action(
|
|
|
243
248
|
|
|
244
249
|
# Inspect and sanity check the formal params.
|
|
245
250
|
func_params = inspect_function_params(orig_func)
|
|
246
|
-
if len(func_params) == 0 or func_params[0].
|
|
251
|
+
if len(func_params) == 0 or func_params[0].effective_type not in (ActionInput, Item):
|
|
247
252
|
raise InvalidDefinition(
|
|
248
253
|
f"Decorator `@kash_action` requires exactly one positional parameter, "
|
|
249
254
|
f"`input` of type `ActionInput` or `Item` on function `{orig_func.__name__}` but "
|
|
250
255
|
f"got params: {func_params}"
|
|
251
256
|
)
|
|
252
|
-
if any(fp.
|
|
257
|
+
if any(fp.is_pure_positional for fp in func_params[1:]):
|
|
253
258
|
raise InvalidDefinition(
|
|
254
259
|
"Decorator `@kash_action` requires all parameters after the first positional "
|
|
255
260
|
f"parameter to be keyword parameters on function `{orig_func.__name__}` but "
|
|
@@ -260,7 +265,7 @@ def kash_action(
|
|
|
260
265
|
context_param = next((fp for fp in func_params if fp.name == "context"), None)
|
|
261
266
|
if context_param:
|
|
262
267
|
func_params.remove(context_param)
|
|
263
|
-
if context_param and context_param.
|
|
268
|
+
if context_param and context_param.is_pure_positional:
|
|
264
269
|
raise InvalidDefinition(
|
|
265
270
|
"Decorator `@kash_action` requires the `context` parameter to be a keyword "
|
|
266
271
|
"parameter, not positional, on function `{func.__name__}`"
|
|
@@ -268,7 +273,7 @@ def kash_action(
|
|
|
268
273
|
|
|
269
274
|
# If the original function is a simple action function (processes a single item),
|
|
270
275
|
# wrap it to convert to an ActionFunction.
|
|
271
|
-
is_simple_func = func_params[0].
|
|
276
|
+
is_simple_func = func_params[0].effective_type == Item
|
|
272
277
|
action_func: ActionFunction
|
|
273
278
|
if is_simple_func:
|
|
274
279
|
simple_func = cast(SimpleActionFunction, orig_func)
|
|
@@ -328,13 +333,16 @@ def kash_action(
|
|
|
328
333
|
if context_param:
|
|
329
334
|
kw_args["context"] = context
|
|
330
335
|
for fp in func_params[1:]:
|
|
331
|
-
if fp.
|
|
336
|
+
if fp.is_pure_positional:
|
|
332
337
|
pos_args.append(self.get_param(fp.name))
|
|
333
338
|
else:
|
|
334
339
|
kw_args[fp.name] = self.get_param(fp.name)
|
|
335
340
|
|
|
336
|
-
|
|
337
|
-
|
|
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,
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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)
|
kash/exec/action_registry.py
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
|
|
3
1
|
from cachetools import Cache, cached
|
|
4
|
-
from
|
|
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
|
-
|
|
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
|
|
34
|
-
if cls.name in
|
|
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
|
-
|
|
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
|
-
|
|
87
|
-
if len(
|
|
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(
|
|
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
|
|
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
|
|
kash/exec/command_registry.py
CHANGED
|
@@ -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__)
|
kash/exec/fetch_url_metadata.py
CHANGED
|
@@ -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,
|
|
29
|
+
return fetch_url_item_metadata(item, refetch=refetch)
|
|
32
30
|
|
|
33
31
|
|
|
34
|
-
def fetch_url_item_metadata(item: 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,
|
|
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
|
+
)
|
kash/exec/llm_transforms.py
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
from dataclasses import replace
|
|
2
2
|
|
|
3
3
|
from chopdiff.docs import DiffFilter, TextDoc
|
|
4
|
-
from chopdiff.transforms import WindowSettings,
|
|
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
|
|
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
|
-
from kash.model.items_model import Item
|
|
15
|
-
from kash.
|
|
15
|
+
from kash.model.items_model import Item
|
|
16
|
+
from kash.text_handling.doc_normalization import normalize_formatting_ansi
|
|
16
17
|
from kash.utils.errors import InvalidInput
|
|
17
18
|
from kash.utils.file_utils.file_formats_model import Format
|
|
18
19
|
|
|
@@ -25,7 +26,7 @@ def windowed_llm_transform(
|
|
|
25
26
|
template: MessageTemplate,
|
|
26
27
|
input: str,
|
|
27
28
|
windowing: WindowSettings | None,
|
|
28
|
-
diff_filter: DiffFilter,
|
|
29
|
+
diff_filter: DiffFilter | None = None,
|
|
29
30
|
check_no_results: bool = True,
|
|
30
31
|
) -> TextDoc:
|
|
31
32
|
def doc_transform(input_doc: TextDoc) -> TextDoc:
|
|
@@ -48,7 +49,7 @@ def windowed_llm_transform(
|
|
|
48
49
|
|
|
49
50
|
|
|
50
51
|
def llm_transform_str(options: LLMOptions, input_str: str, check_no_results: bool = True) -> str:
|
|
51
|
-
load_dotenv_paths(True, True,
|
|
52
|
+
load_dotenv_paths(True, True, global_settings().system_config_dir)
|
|
52
53
|
|
|
53
54
|
if options.windowing and options.windowing.size:
|
|
54
55
|
log.message(
|
|
@@ -57,7 +58,6 @@ def llm_transform_str(options: LLMOptions, input_str: str, check_no_results: boo
|
|
|
57
58
|
options.op_name,
|
|
58
59
|
options.windowing,
|
|
59
60
|
)
|
|
60
|
-
diff_filter = options.diff_filter or accept_all
|
|
61
61
|
|
|
62
62
|
result_str = windowed_llm_transform(
|
|
63
63
|
options.model,
|
|
@@ -65,7 +65,7 @@ def llm_transform_str(options: LLMOptions, input_str: str, check_no_results: boo
|
|
|
65
65
|
options.body_template,
|
|
66
66
|
input_str,
|
|
67
67
|
options.windowing,
|
|
68
|
-
diff_filter,
|
|
68
|
+
diff_filter=options.diff_filter,
|
|
69
69
|
).reassemble()
|
|
70
70
|
else:
|
|
71
71
|
log.message(
|
|
@@ -91,6 +91,7 @@ def llm_transform_item(
|
|
|
91
91
|
normalize: bool = True,
|
|
92
92
|
strip_fence: bool = True,
|
|
93
93
|
check_no_results: bool = True,
|
|
94
|
+
format: Format | None = None,
|
|
94
95
|
) -> Item:
|
|
95
96
|
"""
|
|
96
97
|
Main function for running an LLM action on an item.
|
|
@@ -111,12 +112,13 @@ def llm_transform_item(
|
|
|
111
112
|
log.message("LLM transform from action `%s` on item: %s", action.name, item)
|
|
112
113
|
log.message("LLM options: %s", action.llm_options)
|
|
113
114
|
|
|
114
|
-
|
|
115
|
+
format = format or item.format or Format.markdown
|
|
116
|
+
result_item = item.derived_copy(body=None, format=format)
|
|
115
117
|
result_str = llm_transform_str(llm_options, item.body, check_no_results=check_no_results)
|
|
116
118
|
if strip_fence:
|
|
117
119
|
result_str = strip_markdown_fence(result_str)
|
|
118
120
|
if normalize:
|
|
119
|
-
result_str =
|
|
121
|
+
result_str = normalize_formatting_ansi(result_str, format=format)
|
|
120
122
|
|
|
121
123
|
result_item.body = result_str
|
|
122
124
|
return result_item
|
|
@@ -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
|
|
kash/exec/preconditions.py
CHANGED
|
@@ -7,7 +7,8 @@ from chopdiff.html import has_timestamp
|
|
|
7
7
|
|
|
8
8
|
from kash.exec.precondition_registry import kash_precondition
|
|
9
9
|
from kash.model.items_model import Item, ItemType
|
|
10
|
-
from kash.text_handling.
|
|
10
|
+
from kash.text_handling.markdown_utils import extract_bullet_points
|
|
11
|
+
from kash.utils.file_utils.file_formats import is_full_html_page
|
|
11
12
|
from kash.utils.file_utils.file_formats_model import Format
|
|
12
13
|
|
|
13
14
|
|
|
@@ -16,6 +17,21 @@ def is_resource(item: Item) -> bool:
|
|
|
16
17
|
return item.type == ItemType.resource
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
@kash_precondition
|
|
21
|
+
def is_doc_resource(item: Item) -> bool:
|
|
22
|
+
return bool(is_resource(item) and item.format and item.format.is_doc)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@kash_precondition
|
|
26
|
+
def is_html_resource(item: Item) -> bool:
|
|
27
|
+
return bool(is_resource(item) and item.format and item.format == Format.html)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@kash_precondition
|
|
31
|
+
def is_docx_resource(item: Item) -> bool:
|
|
32
|
+
return bool(is_resource(item) and item.format and item.format == Format.docx)
|
|
33
|
+
|
|
34
|
+
|
|
19
35
|
@kash_precondition
|
|
20
36
|
def is_concept(item: Item) -> bool:
|
|
21
37
|
return item.type == ItemType.concept
|
|
@@ -78,6 +94,11 @@ def has_html_body(item: Item) -> bool:
|
|
|
78
94
|
return has_body(item) and item.format in (Format.html, Format.md_html)
|
|
79
95
|
|
|
80
96
|
|
|
97
|
+
@kash_precondition
|
|
98
|
+
def has_full_html_page_body(item: Item) -> bool:
|
|
99
|
+
return bool(has_html_body(item) and item.body and is_full_html_page(item.body))
|
|
100
|
+
|
|
101
|
+
|
|
81
102
|
@kash_precondition
|
|
82
103
|
def is_plaintext(item: Item) -> bool:
|
|
83
104
|
return has_body(item) and item.format == Format.plaintext
|
kash/exec/resolve_args.py
CHANGED
|
@@ -105,6 +105,10 @@ def assemble_action_args(
|
|
|
105
105
|
|
|
106
106
|
|
|
107
107
|
def resolvable_paths(paths: Sequence[StorePath | Path]) -> list[StorePath]:
|
|
108
|
+
"""
|
|
109
|
+
Return which of the given StorePaths are resolvable (exist) in the
|
|
110
|
+
current workspace.
|
|
111
|
+
"""
|
|
108
112
|
ws = current_ws()
|
|
109
113
|
resolvable = list(filter(None, (ws.resolve_path(p) for p in paths)))
|
|
110
114
|
return resolvable
|