kash-shell 0.3.11__py3-none-any.whl → 0.3.13__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/core/markdownify.py +5 -4
- kash/actions/core/readability.py +4 -4
- kash/actions/core/render_as_html.py +8 -6
- kash/actions/core/show_webpage.py +2 -2
- kash/actions/core/strip_html.py +2 -2
- kash/commands/base/basic_file_commands.py +24 -3
- kash/commands/base/diff_commands.py +38 -3
- kash/commands/base/files_command.py +5 -4
- kash/commands/base/reformat_command.py +1 -1
- kash/commands/base/show_command.py +1 -1
- kash/commands/extras/parse_uv_lock.py +12 -3
- kash/commands/workspace/selection_commands.py +1 -1
- kash/commands/workspace/workspace_commands.py +62 -16
- kash/config/env_settings.py +2 -42
- kash/config/logger.py +30 -25
- kash/config/logger_basic.py +6 -6
- kash/config/settings.py +23 -7
- kash/config/setup.py +33 -5
- kash/config/text_styles.py +25 -22
- kash/docs/load_source_code.py +1 -1
- kash/embeddings/cosine.py +12 -4
- kash/embeddings/embeddings.py +16 -6
- kash/embeddings/text_similarity.py +10 -4
- kash/exec/__init__.py +3 -0
- kash/exec/action_decorators.py +4 -19
- kash/exec/action_exec.py +46 -27
- kash/exec/fetch_url_metadata.py +8 -5
- kash/exec/importing.py +4 -4
- kash/exec/llm_transforms.py +2 -2
- kash/exec/preconditions.py +11 -19
- kash/exec/runtime_settings.py +134 -0
- kash/exec/shell_callable_action.py +5 -3
- kash/file_storage/file_store.py +91 -53
- kash/file_storage/item_file_format.py +6 -3
- kash/file_storage/store_filenames.py +7 -3
- kash/help/help_embeddings.py +2 -2
- kash/llm_utils/clean_headings.py +1 -1
- kash/{text_handling → llm_utils}/custom_sliding_transforms.py +0 -3
- kash/llm_utils/init_litellm.py +16 -0
- kash/llm_utils/llm_api_keys.py +6 -2
- kash/llm_utils/llm_completion.py +12 -5
- kash/local_server/__init__.py +1 -1
- kash/local_server/local_server_commands.py +2 -1
- kash/mcp/__init__.py +1 -1
- kash/mcp/mcp_cli.py +3 -2
- kash/mcp/mcp_server_commands.py +8 -2
- kash/mcp/mcp_server_routes.py +11 -12
- kash/media_base/media_cache.py +10 -3
- kash/media_base/transcription_deepgram.py +15 -2
- kash/model/__init__.py +1 -1
- kash/model/actions_model.py +9 -54
- kash/model/exec_model.py +79 -0
- kash/model/items_model.py +131 -81
- kash/model/operations_model.py +38 -15
- kash/model/paths_model.py +2 -0
- kash/shell/output/shell_output.py +10 -8
- kash/shell/shell_main.py +2 -2
- kash/shell/ui/shell_results.py +2 -1
- kash/shell/utils/exception_printing.py +2 -2
- kash/utils/common/format_utils.py +0 -14
- kash/utils/common/import_utils.py +46 -18
- kash/utils/common/task_stack.py +4 -15
- kash/utils/errors.py +14 -9
- kash/utils/file_utils/file_formats_model.py +61 -26
- kash/utils/file_utils/file_sort_filter.py +10 -3
- kash/utils/file_utils/filename_parsing.py +41 -16
- kash/{text_handling → utils/text_handling}/doc_normalization.py +23 -13
- kash/utils/text_handling/escape_html_tags.py +156 -0
- kash/{text_handling → utils/text_handling}/markdown_utils.py +82 -4
- kash/utils/text_handling/markdownify_utils.py +87 -0
- kash/{text_handling → utils/text_handling}/unified_diffs.py +1 -44
- kash/web_content/file_cache_utils.py +42 -34
- kash/web_content/local_file_cache.py +29 -12
- kash/web_content/web_extract.py +1 -1
- kash/web_content/web_extract_readabilipy.py +4 -2
- kash/web_content/web_fetch.py +42 -7
- kash/web_content/web_page_model.py +2 -1
- kash/web_gen/simple_webpage.py +1 -1
- kash/web_gen/templates/base_styles.css.jinja +139 -16
- kash/web_gen/templates/simple_webpage.html.jinja +1 -1
- kash/workspaces/__init__.py +12 -3
- kash/workspaces/selections.py +2 -2
- kash/workspaces/workspace_dirs.py +58 -0
- kash/workspaces/workspace_importing.py +2 -2
- kash/workspaces/workspace_output.py +2 -2
- kash/workspaces/workspaces.py +26 -90
- kash/xonsh_custom/load_into_xonsh.py +4 -2
- {kash_shell-0.3.11.dist-info → kash_shell-0.3.13.dist-info}/METADATA +4 -4
- {kash_shell-0.3.11.dist-info → kash_shell-0.3.13.dist-info}/RECORD +93 -89
- kash/shell/utils/argparse_utils.py +0 -20
- kash/utils/lang_utils/inflection.py +0 -18
- /kash/{text_handling → utils/text_handling}/markdown_render.py +0 -0
- {kash_shell-0.3.11.dist-info → kash_shell-0.3.13.dist-info}/WHEEL +0 -0
- {kash_shell-0.3.11.dist-info → kash_shell-0.3.13.dist-info}/entry_points.txt +0 -0
- {kash_shell-0.3.11.dist-info → kash_shell-0.3.13.dist-info}/licenses/LICENSE +0 -0
kash/exec/action_exec.py
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import time
|
|
2
2
|
from dataclasses import replace
|
|
3
3
|
|
|
4
|
-
from prettyfmt import fmt_lines
|
|
4
|
+
from prettyfmt import fmt_lines, plural
|
|
5
5
|
|
|
6
6
|
from kash.config.logger import get_logger
|
|
7
|
-
from kash.config.text_styles import
|
|
8
|
-
|
|
7
|
+
from kash.config.text_styles import (
|
|
8
|
+
EMOJI_SKIP,
|
|
9
|
+
EMOJI_START,
|
|
10
|
+
EMOJI_SUCCESS,
|
|
11
|
+
EMOJI_TIMING,
|
|
12
|
+
)
|
|
13
|
+
from kash.exec.preconditions import is_url_resource
|
|
9
14
|
from kash.exec.resolve_args import assemble_action_args
|
|
10
15
|
from kash.exec_model.args_model import CommandArg
|
|
11
16
|
from kash.file_storage.file_store import FileStore
|
|
@@ -17,15 +22,15 @@ from kash.model.actions_model import (
|
|
|
17
22
|
ExecContext,
|
|
18
23
|
PathOpType,
|
|
19
24
|
)
|
|
25
|
+
from kash.model.exec_model import RuntimeSettings
|
|
20
26
|
from kash.model.items_model import Item, State
|
|
21
27
|
from kash.model.operations_model import Input, Operation, Source
|
|
22
28
|
from kash.model.params_model import ALL_COMMON_PARAMS, GLOBAL_PARAMS, RawParamValues
|
|
23
29
|
from kash.model.paths_model import StorePath
|
|
24
|
-
from kash.shell.output.shell_output import PrintHooks
|
|
30
|
+
from kash.shell.output.shell_output import PrintHooks
|
|
25
31
|
from kash.utils.common.task_stack import task_stack
|
|
26
32
|
from kash.utils.common.type_utils import not_none
|
|
27
|
-
from kash.utils.errors import
|
|
28
|
-
from kash.utils.lang_utils.inflection import plural
|
|
33
|
+
from kash.utils.errors import ContentError, InvalidOutput, get_nonfatal_exceptions
|
|
29
34
|
from kash.workspaces import Selection, current_ws
|
|
30
35
|
from kash.workspaces.workspace_importing import import_and_load
|
|
31
36
|
|
|
@@ -50,7 +55,7 @@ def prepare_action_input(*input_args: CommandArg, refetch: bool = False) -> Acti
|
|
|
50
55
|
if input_items:
|
|
51
56
|
log.message("Assembling metadata for input items:\n%s", fmt_lines(input_items))
|
|
52
57
|
input_items = [
|
|
53
|
-
fetch_url_item_metadata(item, refetch=refetch) if
|
|
58
|
+
fetch_url_item_metadata(item, refetch=refetch) if is_url_resource(item) else item
|
|
54
59
|
for item in input_items
|
|
55
60
|
]
|
|
56
61
|
|
|
@@ -63,6 +68,7 @@ def validate_action_input(
|
|
|
63
68
|
"""
|
|
64
69
|
Validate an action input, ensuring the right number of args, all explicit params are filled,
|
|
65
70
|
and the precondition holds and return an `Operation` that describes what will happen.
|
|
71
|
+
For flexibility, we don't require the items to be saved (have a store path).
|
|
66
72
|
"""
|
|
67
73
|
input_items = action_input.items
|
|
68
74
|
# Validations:
|
|
@@ -75,10 +81,16 @@ def validate_action_input(
|
|
|
75
81
|
|
|
76
82
|
# Now make a note of the the operation we will perform.
|
|
77
83
|
# If the inputs are paths, record the input paths, including hashes.
|
|
78
|
-
|
|
79
|
-
|
|
84
|
+
def input_for(item: Item) -> Input:
|
|
85
|
+
if item.store_path:
|
|
86
|
+
return Input(StorePath(item.store_path), ws.hash(StorePath(item.store_path)))
|
|
87
|
+
else:
|
|
88
|
+
return Input(path=None, source_info="unsaved")
|
|
89
|
+
|
|
90
|
+
inputs = [input_for(item) for item in input_items]
|
|
91
|
+
|
|
80
92
|
# Add any non-default runtime options into the options summary.
|
|
81
|
-
options = {**action.param_value_summary(), **context.
|
|
93
|
+
options = {**action.param_value_summary(), **context.settings.non_default_options}
|
|
82
94
|
operation = Operation(action.name, inputs, options)
|
|
83
95
|
|
|
84
96
|
return operation
|
|
@@ -89,7 +101,7 @@ def log_action(action: Action, action_input: ActionInput, operation: Operation):
|
|
|
89
101
|
Log the action and the operation we are about to run.
|
|
90
102
|
"""
|
|
91
103
|
PrintHooks.before_log_action_run()
|
|
92
|
-
|
|
104
|
+
log.message("%s Action: `%s`", EMOJI_START, action.name)
|
|
93
105
|
log.message("Running: `%s`", operation.command_line(with_options=True))
|
|
94
106
|
if len(action.param_value_summary()) > 0:
|
|
95
107
|
log.message("Parameters:\n%s", action.param_value_summary_str())
|
|
@@ -106,8 +118,9 @@ def check_for_existing_result(
|
|
|
106
118
|
already exist.
|
|
107
119
|
"""
|
|
108
120
|
action = context.action
|
|
109
|
-
|
|
110
|
-
|
|
121
|
+
settings = context.settings
|
|
122
|
+
ws = settings.workspace
|
|
123
|
+
rerun = settings.rerun
|
|
111
124
|
|
|
112
125
|
existing_result = None
|
|
113
126
|
|
|
@@ -154,6 +167,7 @@ def run_action_operation(
|
|
|
154
167
|
|
|
155
168
|
# Run the action.
|
|
156
169
|
action = context.action
|
|
170
|
+
settings = context.settings
|
|
157
171
|
if action.run_per_item:
|
|
158
172
|
result = _run_for_each_item(context, action_input)
|
|
159
173
|
else:
|
|
@@ -172,9 +186,9 @@ def run_action_operation(
|
|
|
172
186
|
item.update_history(Source(operation=this_op, output_num=i, cacheable=action.cacheable))
|
|
173
187
|
|
|
174
188
|
# Override the state if appropriate (this handles marking items as transient).
|
|
175
|
-
if
|
|
189
|
+
if settings.override_state:
|
|
176
190
|
for item in result.items:
|
|
177
|
-
item.state =
|
|
191
|
+
item.state = settings.override_state
|
|
178
192
|
|
|
179
193
|
log.info("Action `%s` result: %s", action.name, result)
|
|
180
194
|
|
|
@@ -233,7 +247,7 @@ def _run_for_each_item(context: ExecContext, input: ActionInput) -> ActionResult
|
|
|
233
247
|
log.info("Caught SkipItem exception, skipping run on this item")
|
|
234
248
|
result_items.append(item)
|
|
235
249
|
continue
|
|
236
|
-
except
|
|
250
|
+
except get_nonfatal_exceptions() as e:
|
|
237
251
|
errors.append(e)
|
|
238
252
|
had_error = True
|
|
239
253
|
|
|
@@ -284,7 +298,7 @@ def save_action_result(
|
|
|
284
298
|
skipped_paths.append(store_path)
|
|
285
299
|
continue
|
|
286
300
|
|
|
287
|
-
ws.save(item, as_tmp=as_tmp, no_format=no_format)
|
|
301
|
+
ws.save(item, overwrite=result.overwrite, as_tmp=as_tmp, no_format=no_format)
|
|
288
302
|
|
|
289
303
|
if skipped_paths:
|
|
290
304
|
log.message(
|
|
@@ -293,14 +307,18 @@ def save_action_result(
|
|
|
293
307
|
fmt_lines(skipped_paths),
|
|
294
308
|
)
|
|
295
309
|
|
|
296
|
-
|
|
310
|
+
unsaved_items = [item for item in input_items if not item.store_path]
|
|
311
|
+
input_store_paths = [StorePath(item.store_path) for item in input_items if item.store_path]
|
|
297
312
|
result_store_paths = [StorePath(item.store_path) for item in result.items if item.store_path]
|
|
298
313
|
old_inputs = sorted(set(input_store_paths) - set(result_store_paths))
|
|
314
|
+
if unsaved_items:
|
|
315
|
+
log.info("unsaved_items:\n%s", fmt_lines(unsaved_items))
|
|
299
316
|
log.info("result_store_paths:\n%s", fmt_lines(result_store_paths))
|
|
300
|
-
|
|
317
|
+
if old_inputs:
|
|
318
|
+
log.info("old_inputs:\n%s", fmt_lines(old_inputs))
|
|
301
319
|
|
|
302
320
|
# If there is a hint that the action replaces the input, archive any inputs that are not in the result.
|
|
303
|
-
archived_store_paths = []
|
|
321
|
+
archived_store_paths: list[StorePath] = []
|
|
304
322
|
if result.replaces_input and input_items:
|
|
305
323
|
for input_store_path in old_inputs:
|
|
306
324
|
# Note some outputs may be missing if replace_input was used.
|
|
@@ -325,7 +343,8 @@ def run_action_with_caching(
|
|
|
325
343
|
Note: Mutates the input but only to add `context` to each item.
|
|
326
344
|
"""
|
|
327
345
|
action = context.action
|
|
328
|
-
|
|
346
|
+
settings = context.settings
|
|
347
|
+
ws = settings.workspace
|
|
329
348
|
|
|
330
349
|
# For convenience, we include the context to each item too (this helps so per-item
|
|
331
350
|
# functions don't have to take context args everywhere).
|
|
@@ -341,7 +360,7 @@ def run_action_with_caching(
|
|
|
341
360
|
# Check if a previous run already produced the result.
|
|
342
361
|
existing_result = check_for_existing_result(context, action_input, operation)
|
|
343
362
|
|
|
344
|
-
if existing_result and not
|
|
363
|
+
if existing_result and not settings.rerun:
|
|
345
364
|
# Use the cached result.
|
|
346
365
|
result = existing_result
|
|
347
366
|
result_store_paths = [StorePath(not_none(item.store_path)) for item in result.items]
|
|
@@ -349,7 +368,7 @@ def run_action_with_caching(
|
|
|
349
368
|
|
|
350
369
|
PrintHooks.before_done_message()
|
|
351
370
|
log.message(
|
|
352
|
-
"%s
|
|
371
|
+
"%s Skipped: `%s` completed with %s %s",
|
|
353
372
|
EMOJI_SKIP,
|
|
354
373
|
action.name,
|
|
355
374
|
len(result.items),
|
|
@@ -359,12 +378,12 @@ def run_action_with_caching(
|
|
|
359
378
|
# Run it!
|
|
360
379
|
result = run_action_operation(context, action_input, operation)
|
|
361
380
|
result_store_paths, archived_store_paths = save_action_result(
|
|
362
|
-
ws, result, action_input, as_tmp=
|
|
381
|
+
ws, result, action_input, as_tmp=settings.tmp_output, no_format=settings.no_format
|
|
363
382
|
)
|
|
364
383
|
|
|
365
384
|
PrintHooks.before_done_message()
|
|
366
385
|
log.message(
|
|
367
|
-
"%s
|
|
386
|
+
"%s Done: `%s` completed with %s %s",
|
|
368
387
|
EMOJI_SUCCESS,
|
|
369
388
|
action.name,
|
|
370
389
|
len(result.items),
|
|
@@ -415,8 +434,7 @@ def run_action_with_shell_context(
|
|
|
415
434
|
action_name = action.name
|
|
416
435
|
|
|
417
436
|
# Execution context. This is fixed for the duration of the action.
|
|
418
|
-
|
|
419
|
-
action=action,
|
|
437
|
+
settings = RuntimeSettings(
|
|
420
438
|
workspace_dir=ws.base_dir,
|
|
421
439
|
rerun=rerun,
|
|
422
440
|
refetch=refetch,
|
|
@@ -424,6 +442,7 @@ def run_action_with_shell_context(
|
|
|
424
442
|
tmp_output=tmp_output,
|
|
425
443
|
no_format=no_format,
|
|
426
444
|
)
|
|
445
|
+
context = ExecContext(action, settings)
|
|
427
446
|
|
|
428
447
|
# Collect args from the provided args or otherwise the current selection.
|
|
429
448
|
args, from_selection = assemble_action_args(*provided_args, use_selection=action.uses_selection)
|
kash/exec/fetch_url_metadata.py
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
from kash.config.logger import get_logger
|
|
2
|
-
from kash.exec.preconditions import
|
|
2
|
+
from kash.exec.preconditions import is_url_resource
|
|
3
3
|
from kash.media_base.media_services import get_media_metadata
|
|
4
4
|
from kash.model.items_model import Item, ItemType
|
|
5
5
|
from kash.model.paths_model import StorePath
|
|
6
6
|
from kash.utils.common.format_utils import fmt_loc
|
|
7
7
|
from kash.utils.common.url import Url, is_url
|
|
8
8
|
from kash.utils.errors import InvalidInput
|
|
9
|
-
from kash.web_content.canon_url import canonicalize_url
|
|
10
|
-
from kash.web_content.web_extract import fetch_extract
|
|
11
|
-
from kash.workspaces import current_ws
|
|
12
9
|
|
|
13
10
|
log = get_logger(__name__)
|
|
14
11
|
|
|
15
12
|
|
|
16
13
|
def fetch_url_metadata(locator: Url | StorePath, refetch: bool = False) -> Item:
|
|
14
|
+
from kash.workspaces import current_ws
|
|
15
|
+
|
|
17
16
|
ws = current_ws()
|
|
18
17
|
if is_url(locator):
|
|
19
18
|
# Import or find URL as a resource in the current workspace.
|
|
@@ -21,7 +20,7 @@ def fetch_url_metadata(locator: Url | StorePath, refetch: bool = False) -> Item:
|
|
|
21
20
|
item = ws.load(store_path)
|
|
22
21
|
elif isinstance(locator, StorePath):
|
|
23
22
|
item = ws.load(locator)
|
|
24
|
-
if not
|
|
23
|
+
if not is_url_resource(item):
|
|
25
24
|
raise InvalidInput(f"Not a URL resource: {fmt_loc(locator)}")
|
|
26
25
|
else:
|
|
27
26
|
raise InvalidInput(f"Not a URL or URL resource: {fmt_loc(locator)}")
|
|
@@ -34,6 +33,10 @@ def fetch_url_item_metadata(item: Item, refetch: bool = False) -> Item:
|
|
|
34
33
|
Fetch metadata for a URL using a media service if we recognize the URL,
|
|
35
34
|
and otherwise fetching and extracting it from the web page HTML.
|
|
36
35
|
"""
|
|
36
|
+
from kash.web_content.canon_url import canonicalize_url
|
|
37
|
+
from kash.web_content.web_extract import fetch_extract
|
|
38
|
+
from kash.workspaces import current_ws
|
|
39
|
+
|
|
37
40
|
ws = current_ws()
|
|
38
41
|
if not refetch and item.title and item.description:
|
|
39
42
|
log.message(
|
kash/exec/importing.py
CHANGED
|
@@ -5,7 +5,7 @@ from prettyfmt import fmt_lines, fmt_path
|
|
|
5
5
|
from kash.config.logger import get_logger
|
|
6
6
|
from kash.exec.action_registry import action_classes, refresh_action_classes
|
|
7
7
|
from kash.exec.command_registry import get_all_commands
|
|
8
|
-
from kash.utils.common.import_utils import Tallies,
|
|
8
|
+
from kash.utils.common.import_utils import Tallies, import_recursive
|
|
9
9
|
|
|
10
10
|
log = get_logger(__name__)
|
|
11
11
|
|
|
@@ -13,12 +13,12 @@ log = get_logger(__name__)
|
|
|
13
13
|
def import_and_register(
|
|
14
14
|
package_name: str | None,
|
|
15
15
|
parent_dir: Path,
|
|
16
|
-
|
|
16
|
+
resource_names: list[str] | None = None,
|
|
17
17
|
tallies: Tallies | None = None,
|
|
18
18
|
):
|
|
19
19
|
"""
|
|
20
20
|
This hook can be used for auto-registering commands and actions from any
|
|
21
|
-
subdirectory of a given package.
|
|
21
|
+
module or subdirectory of a given package.
|
|
22
22
|
|
|
23
23
|
Useful to call from `__init__.py` files to import a directory of code,
|
|
24
24
|
auto-registering annotated commands and actions and also handles refreshing the
|
|
@@ -38,7 +38,7 @@ def import_and_register(
|
|
|
38
38
|
prev_command_count = len(get_all_commands())
|
|
39
39
|
prev_action_count = len(ac)
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
import_recursive(package_name, parent_dir, resource_names, tallies)
|
|
42
42
|
|
|
43
43
|
new_command_count = len(get_all_commands()) - prev_command_count
|
|
44
44
|
new_action_count = len(ac) - prev_action_count
|
kash/exec/llm_transforms.py
CHANGED
|
@@ -13,9 +13,9 @@ from kash.llm_utils.llm_completion import llm_template_completion
|
|
|
13
13
|
from kash.llm_utils.llm_messages import Message, MessageTemplate
|
|
14
14
|
from kash.model.actions_model import LLMOptions
|
|
15
15
|
from kash.model.items_model import Item
|
|
16
|
-
from kash.text_handling.doc_normalization import normalize_formatting_ansi
|
|
17
16
|
from kash.utils.errors import InvalidInput
|
|
18
17
|
from kash.utils.file_utils.file_formats_model import Format
|
|
18
|
+
from kash.utils.text_handling.doc_normalization import normalize_formatting
|
|
19
19
|
|
|
20
20
|
log = get_logger(__name__)
|
|
21
21
|
|
|
@@ -118,7 +118,7 @@ def llm_transform_item(
|
|
|
118
118
|
if strip_fence:
|
|
119
119
|
result_str = strip_markdown_fence(result_str)
|
|
120
120
|
if normalize:
|
|
121
|
-
result_str =
|
|
121
|
+
result_str = normalize_formatting(result_str, format=format)
|
|
122
122
|
|
|
123
123
|
result_item.body = result_str
|
|
124
124
|
return result_item
|
kash/exec/preconditions.py
CHANGED
|
@@ -7,9 +7,9 @@ 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.markdown_utils import extract_bullet_points
|
|
11
10
|
from kash.utils.file_utils.file_formats import is_full_html_page
|
|
12
11
|
from kash.utils.file_utils.file_formats_model import Format
|
|
12
|
+
from kash.utils.text_handling.markdown_utils import extract_bullet_points
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
@kash_precondition
|
|
@@ -32,6 +32,11 @@ def is_docx_resource(item: Item) -> bool:
|
|
|
32
32
|
return bool(is_resource(item) and item.format and item.format == Format.docx)
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
@kash_precondition
|
|
36
|
+
def is_pdf_resource(item: Item) -> bool:
|
|
37
|
+
return bool(is_resource(item) and item.format and item.format == Format.pdf)
|
|
38
|
+
|
|
39
|
+
|
|
35
40
|
@kash_precondition
|
|
36
41
|
def is_concept(item: Item) -> bool:
|
|
37
42
|
return item.type == ItemType.concept
|
|
@@ -53,7 +58,7 @@ def is_instructions(item: Item) -> bool:
|
|
|
53
58
|
|
|
54
59
|
|
|
55
60
|
@kash_precondition
|
|
56
|
-
def
|
|
61
|
+
def is_url_resource(item: Item) -> bool:
|
|
57
62
|
return bool(item.type == ItemType.resource and item.url)
|
|
58
63
|
|
|
59
64
|
|
|
@@ -85,13 +90,13 @@ def contains_curly_vars(item: Item) -> bool:
|
|
|
85
90
|
|
|
86
91
|
|
|
87
92
|
@kash_precondition
|
|
88
|
-
def
|
|
89
|
-
return has_body(item) and item.format
|
|
93
|
+
def has_simple_text_body(item: Item) -> bool:
|
|
94
|
+
return bool(has_body(item) and item.format and item.format.is_simple_text)
|
|
90
95
|
|
|
91
96
|
|
|
92
97
|
@kash_precondition
|
|
93
98
|
def has_html_body(item: Item) -> bool:
|
|
94
|
-
return has_body(item) and item.format
|
|
99
|
+
return bool(has_body(item) and item.format and item.format.is_html)
|
|
95
100
|
|
|
96
101
|
|
|
97
102
|
@kash_precondition
|
|
@@ -106,7 +111,7 @@ def is_plaintext(item: Item) -> bool:
|
|
|
106
111
|
|
|
107
112
|
@kash_precondition
|
|
108
113
|
def is_markdown(item: Item) -> bool:
|
|
109
|
-
return has_body(item) and item.format
|
|
114
|
+
return bool(has_body(item) and item.format and item.format.is_markdown)
|
|
110
115
|
|
|
111
116
|
|
|
112
117
|
@kash_precondition
|
|
@@ -114,19 +119,6 @@ def is_markdown_template(item: Item) -> bool:
|
|
|
114
119
|
return is_markdown(item) and contains_curly_vars(item)
|
|
115
120
|
|
|
116
121
|
|
|
117
|
-
@kash_precondition
|
|
118
|
-
def is_html(item: Item) -> bool:
|
|
119
|
-
return has_body(item) and item.format == Format.html
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
@kash_precondition
|
|
123
|
-
def is_text_doc(item: Item) -> bool:
|
|
124
|
-
"""
|
|
125
|
-
A document that can be processed by LLMs and other plaintext tools.
|
|
126
|
-
"""
|
|
127
|
-
return (is_plaintext(item) or is_markdown(item)) and has_body(item)
|
|
128
|
-
|
|
129
|
-
|
|
130
122
|
@kash_precondition
|
|
131
123
|
def is_markdown_list(item: Item) -> bool:
|
|
132
124
|
try:
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import contextvars
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from kash.config.logger import get_logger
|
|
6
|
+
from kash.model.exec_model import RuntimeSettings
|
|
7
|
+
from kash.model.items_model import State
|
|
8
|
+
from kash.workspaces.workspace_dirs import enclosing_ws_dir, global_ws_dir
|
|
9
|
+
|
|
10
|
+
log = get_logger(__name__)
|
|
11
|
+
|
|
12
|
+
_current_settings: contextvars.ContextVar[RuntimeSettings | None] = contextvars.ContextVar(
|
|
13
|
+
"current_runtime_settings", default=None
|
|
14
|
+
)
|
|
15
|
+
"""
|
|
16
|
+
Context variable that tracks the current runtime settings. Only used if it is
|
|
17
|
+
explicitly set with a `with runtime_settings(...):` block.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def current_runtime_settings() -> RuntimeSettings:
|
|
22
|
+
"""
|
|
23
|
+
Get the current runtime settings. Uses the ambient context var settings if
|
|
24
|
+
set and otherwise infers the workspace from the current working directory
|
|
25
|
+
with default runtime settings.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
ambient_settings = _current_settings.get()
|
|
29
|
+
if ambient_settings:
|
|
30
|
+
return ambient_settings
|
|
31
|
+
|
|
32
|
+
default_ws_dir = enclosing_ws_dir() or global_ws_dir()
|
|
33
|
+
return RuntimeSettings(default_ws_dir)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen=True)
|
|
37
|
+
class WsContext:
|
|
38
|
+
global_ws_dir: Path
|
|
39
|
+
enclosing_ws_dir: Path | None
|
|
40
|
+
override_dir: Path | None
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def current_ws_dir(self) -> Path:
|
|
44
|
+
if self.override_dir:
|
|
45
|
+
return self.override_dir
|
|
46
|
+
elif self.enclosing_ws_dir:
|
|
47
|
+
return self.enclosing_ws_dir
|
|
48
|
+
else:
|
|
49
|
+
return self.global_ws_dir
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def current_ws_context() -> WsContext:
|
|
53
|
+
"""
|
|
54
|
+
Context path info about the current workspace, including the global workspace
|
|
55
|
+
directory, any workspace directory that encloses the current working directory,
|
|
56
|
+
and override set via runtime settings.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
override_dir = None
|
|
60
|
+
ambient_settings = _current_settings.get()
|
|
61
|
+
if ambient_settings:
|
|
62
|
+
override_dir = ambient_settings.workspace_dir
|
|
63
|
+
|
|
64
|
+
return WsContext(global_ws_dir(), enclosing_ws_dir(), override_dir)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass
|
|
68
|
+
class RuntimeSettingsManager:
|
|
69
|
+
"""
|
|
70
|
+
Manage the context for executing actions, including `RuntimeSettings` and
|
|
71
|
+
`Workspace`.
|
|
72
|
+
|
|
73
|
+
This is a minimal base class for use as a context manager. Most functionality
|
|
74
|
+
is still in `FileStore`.
|
|
75
|
+
|
|
76
|
+
Workspaces may be detected based on the current working directory or explicitly
|
|
77
|
+
set using a `with` block:
|
|
78
|
+
```
|
|
79
|
+
ws = get_ws("my_workspace")
|
|
80
|
+
with ws:
|
|
81
|
+
# code that calls current_ws() will use this workspace
|
|
82
|
+
```
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
settings: RuntimeSettings
|
|
86
|
+
|
|
87
|
+
def __enter__(self):
|
|
88
|
+
self._token = _current_settings.set(self.settings)
|
|
89
|
+
log.info("New runtime context: %s", self.settings)
|
|
90
|
+
return self.settings
|
|
91
|
+
|
|
92
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
93
|
+
_current_settings.reset(self._token)
|
|
94
|
+
log.info("Exiting runtime context: %s", self.settings)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def kash_runtime(
|
|
98
|
+
workspace_dir: Path | None,
|
|
99
|
+
*,
|
|
100
|
+
rerun: bool = False,
|
|
101
|
+
refetch: bool = False,
|
|
102
|
+
override_state: State | None = None,
|
|
103
|
+
tmp_output: bool = False,
|
|
104
|
+
no_format: bool = False,
|
|
105
|
+
) -> RuntimeSettingsManager:
|
|
106
|
+
"""
|
|
107
|
+
Set a specific kash execution context for a with block.
|
|
108
|
+
This allows defining a workspace and other execution settings as the ambient
|
|
109
|
+
context within the block.
|
|
110
|
+
|
|
111
|
+
If `workspace_dir` is not provided, the current workspace will be inferred
|
|
112
|
+
from the working directory or fall back to the global workspace.
|
|
113
|
+
|
|
114
|
+
Example usage:
|
|
115
|
+
```
|
|
116
|
+
with kash_runtime(ws_path, rerun=args.rerun) as runtime:
|
|
117
|
+
runtime.workspace.log_workspace_info()
|
|
118
|
+
# Perform actions.
|
|
119
|
+
```
|
|
120
|
+
"""
|
|
121
|
+
from kash.workspaces.workspaces import current_ws
|
|
122
|
+
|
|
123
|
+
if workspace_dir is None:
|
|
124
|
+
workspace_dir = current_ws().base_dir
|
|
125
|
+
|
|
126
|
+
settings = RuntimeSettings(
|
|
127
|
+
workspace_dir=workspace_dir,
|
|
128
|
+
rerun=rerun,
|
|
129
|
+
refetch=refetch,
|
|
130
|
+
override_state=override_state,
|
|
131
|
+
tmp_output=tmp_output,
|
|
132
|
+
no_format=no_format,
|
|
133
|
+
)
|
|
134
|
+
return RuntimeSettingsManager(settings=settings)
|
|
@@ -13,7 +13,7 @@ from kash.model.params_model import RawParamValues
|
|
|
13
13
|
from kash.shell.output.shell_output import PrintHooks
|
|
14
14
|
from kash.shell.utils.exception_printing import summarize_traceback
|
|
15
15
|
from kash.utils.common.parse_shell_args import parse_shell_args
|
|
16
|
-
from kash.utils.errors import
|
|
16
|
+
from kash.utils.errors import get_nonfatal_exceptions
|
|
17
17
|
|
|
18
18
|
log = get_logger(__name__)
|
|
19
19
|
|
|
@@ -71,14 +71,16 @@ class ShellCallableAction:
|
|
|
71
71
|
action_cls, explicit_values, *shell_args.args, rerun=rerun, refetch=refetch
|
|
72
72
|
)
|
|
73
73
|
# We don't return the result to keep the xonsh shell output clean.
|
|
74
|
-
except
|
|
74
|
+
except get_nonfatal_exceptions() as e:
|
|
75
75
|
PrintHooks.nonfatal_exception()
|
|
76
76
|
log.error(f"[{COLOR_ERROR}]Action error:[/{COLOR_ERROR}] %s", summarize_traceback(e))
|
|
77
77
|
log.info("Action error details: %s", e, exc_info=True)
|
|
78
78
|
return ShellResult(exception=e)
|
|
79
79
|
except Exception as e:
|
|
80
80
|
# Log here while we are in the true call stack (not inside the xonsh call stack).
|
|
81
|
-
log.error(
|
|
81
|
+
log.error(
|
|
82
|
+
"Action error: %s", e, exc_info=KashEnv.KASH_SHOW_TRACEBACK.read_bool(default=True)
|
|
83
|
+
)
|
|
82
84
|
raise
|
|
83
85
|
finally:
|
|
84
86
|
log_tallies(level="warning", if_slower_than=10.0)
|