kash-shell 0.3.28__py3-none-any.whl → 0.3.30__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_html.py +1 -4
- kash/actions/core/minify_html.py +4 -5
- kash/actions/core/render_as_html.py +9 -7
- kash/actions/core/save_sidematter_meta.py +47 -0
- kash/actions/core/zip_sidematter.py +47 -0
- kash/commands/base/basic_file_commands.py +7 -4
- kash/commands/base/diff_commands.py +6 -4
- kash/commands/base/files_command.py +31 -30
- kash/commands/base/general_commands.py +3 -2
- kash/commands/base/logs_commands.py +6 -4
- kash/commands/base/reformat_command.py +3 -2
- kash/commands/base/search_command.py +4 -3
- kash/commands/base/show_command.py +9 -7
- kash/commands/help/assistant_commands.py +6 -4
- kash/commands/help/help_commands.py +7 -4
- kash/commands/workspace/selection_commands.py +18 -16
- kash/commands/workspace/workspace_commands.py +39 -26
- kash/config/setup.py +2 -27
- kash/docs/markdown/topics/a1_what_is_kash.md +26 -18
- kash/exec/action_decorators.py +2 -2
- kash/exec/action_exec.py +56 -50
- kash/exec/fetch_url_items.py +36 -9
- kash/exec/preconditions.py +2 -2
- kash/exec/resolve_args.py +4 -1
- kash/exec/runtime_settings.py +1 -0
- kash/file_storage/file_store.py +59 -23
- kash/file_storage/item_file_format.py +91 -26
- kash/help/help_types.py +1 -1
- kash/llm_utils/llms.py +6 -1
- kash/local_server/local_server_commands.py +2 -1
- kash/mcp/mcp_server_commands.py +3 -2
- kash/mcp/mcp_server_routes.py +1 -1
- kash/model/actions_model.py +31 -30
- kash/model/compound_actions_model.py +4 -3
- kash/model/exec_model.py +30 -3
- kash/model/items_model.py +114 -57
- kash/model/params_model.py +4 -4
- kash/shell/output/shell_output.py +1 -2
- kash/utils/file_formats/chat_format.py +7 -4
- kash/utils/file_utils/file_ext.py +1 -0
- kash/utils/file_utils/file_formats.py +4 -2
- kash/utils/file_utils/file_formats_model.py +12 -0
- kash/utils/text_handling/doc_normalization.py +1 -1
- kash/utils/text_handling/markdown_footnotes.py +224 -0
- kash/utils/text_handling/markdown_utils.py +532 -41
- kash/utils/text_handling/markdownify_utils.py +2 -1
- kash/web_gen/templates/components/tooltip_scripts.js.jinja +186 -1
- kash/web_gen/templates/components/youtube_popover_scripts.js.jinja +223 -0
- kash/web_gen/templates/components/youtube_popover_styles.css.jinja +150 -0
- kash/web_gen/templates/content_styles.css.jinja +53 -1
- kash/web_gen/templates/youtube_webpage.html.jinja +47 -0
- kash/web_gen/webpage_render.py +103 -0
- kash/workspaces/workspaces.py +0 -5
- kash/xonsh_custom/custom_shell.py +4 -3
- {kash_shell-0.3.28.dist-info → kash_shell-0.3.30.dist-info}/METADATA +33 -24
- {kash_shell-0.3.28.dist-info → kash_shell-0.3.30.dist-info}/RECORD +59 -54
- kash/llm_utils/llm_features.py +0 -72
- kash/web_gen/simple_webpage.py +0 -55
- {kash_shell-0.3.28.dist-info → kash_shell-0.3.30.dist-info}/WHEEL +0 -0
- {kash_shell-0.3.28.dist-info → kash_shell-0.3.30.dist-info}/entry_points.txt +0 -0
- {kash_shell-0.3.28.dist-info → kash_shell-0.3.30.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
|
-
from frontmatter_format import
|
|
3
|
+
from frontmatter_format import (
|
|
4
|
+
FmStyle,
|
|
5
|
+
fmf_has_frontmatter,
|
|
6
|
+
fmf_read,
|
|
7
|
+
fmf_read_frontmatter,
|
|
8
|
+
fmf_write,
|
|
9
|
+
)
|
|
4
10
|
from funlog import tally_calls
|
|
5
11
|
from prettyfmt import custom_key_sort, fmt_size_human
|
|
12
|
+
from sidematter_format import Sidematter
|
|
13
|
+
from strif import atomic_output_file, single_line
|
|
6
14
|
|
|
7
15
|
from kash.config.logger import get_logger
|
|
8
16
|
from kash.model.items_model import ITEM_FIELDS, Item
|
|
@@ -22,19 +30,28 @@ _item_cache = MtimeCache[Item](max_size=2000, name="Item")
|
|
|
22
30
|
|
|
23
31
|
|
|
24
32
|
@tally_calls()
|
|
25
|
-
def write_item(item: Item, path: Path, normalize: bool = True):
|
|
33
|
+
def write_item(item: Item, path: Path, *, normalize: bool = True, use_frontmatter: bool = True):
|
|
26
34
|
"""
|
|
27
|
-
Write a text item to a file with standard frontmatter format YAML.
|
|
35
|
+
Write a text item to a file with standard frontmatter format YAML or sidematter format.
|
|
28
36
|
By default normalizes formatting of the body text and updates the item's body.
|
|
37
|
+
|
|
38
|
+
If `use_frontmatter` is True, uses frontmatter on the file for metadata, and omits
|
|
39
|
+
the sidematter metadata file.
|
|
40
|
+
|
|
41
|
+
This function does not explicitly write sidematter assets; these can be written
|
|
42
|
+
separately.
|
|
29
43
|
"""
|
|
30
44
|
item.validate()
|
|
31
|
-
if item.format and not item.format.supports_frontmatter:
|
|
45
|
+
if use_frontmatter and item.format and not item.format.supports_frontmatter:
|
|
32
46
|
raise ValueError(f"Item format `{item.format.value}` does not support frontmatter: {item}")
|
|
33
47
|
|
|
34
48
|
# Clear cache before writing.
|
|
35
49
|
_item_cache.delete(path)
|
|
36
50
|
|
|
51
|
+
title = item.title
|
|
37
52
|
if normalize:
|
|
53
|
+
if item.title:
|
|
54
|
+
title = single_line(item.title)
|
|
38
55
|
body = normalize_formatting(item.body_text(), item.format)
|
|
39
56
|
else:
|
|
40
57
|
body = item.body_text()
|
|
@@ -65,29 +82,44 @@ def write_item(item: Item, path: Path, normalize: bool = True):
|
|
|
65
82
|
|
|
66
83
|
log.debug("Writing item to %s: body length %s, metadata %s", path, len(body), item.metadata())
|
|
67
84
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
85
|
+
# Use sidematter format
|
|
86
|
+
spath = Sidematter(path)
|
|
87
|
+
if use_frontmatter:
|
|
88
|
+
# Use frontmatter format
|
|
89
|
+
fmf_write(
|
|
90
|
+
path,
|
|
91
|
+
body,
|
|
92
|
+
item.metadata(),
|
|
93
|
+
style=fm_style,
|
|
94
|
+
key_sort=ITEM_FIELD_SORT,
|
|
95
|
+
make_parents=True,
|
|
96
|
+
)
|
|
97
|
+
else:
|
|
98
|
+
# Write the main file with just the body (no frontmatter)
|
|
99
|
+
with atomic_output_file(path, make_parents=True) as f:
|
|
100
|
+
f.write_text(body, encoding="utf-8")
|
|
101
|
+
|
|
102
|
+
# Sidematter metadata
|
|
103
|
+
spath.write_meta(item.metadata(), key_sort=ITEM_FIELD_SORT)
|
|
76
104
|
|
|
77
105
|
# Update cache.
|
|
78
106
|
_item_cache.update(path, item)
|
|
79
107
|
|
|
80
|
-
# Update the item
|
|
108
|
+
# Update the item.
|
|
109
|
+
item.title = title
|
|
81
110
|
item.body = body
|
|
82
111
|
|
|
83
112
|
|
|
84
|
-
def read_item(path: Path, base_dir: Path | None) -> Item:
|
|
113
|
+
def read_item(path: Path, base_dir: Path | None, preserve_filename: bool = True) -> Item:
|
|
85
114
|
"""
|
|
86
|
-
Read
|
|
115
|
+
Read a text item from a file. Uses `base_dir` to resolve paths, so the item's
|
|
87
116
|
`store_path` will be set and be relative to `base_dir`.
|
|
88
117
|
|
|
89
|
-
|
|
90
|
-
|
|
118
|
+
Automatically detects and reads sidematter format (metadata in .meta.yml/.meta.json
|
|
119
|
+
sidecar files), which takes precedence over frontmatter when present. Falls back to
|
|
120
|
+
frontmatter format YAML if no sidematter is found. If neither is present, the item
|
|
121
|
+
will be a resource with a format inferred from the file extension or the content.
|
|
122
|
+
|
|
91
123
|
The `store_path` will be the path relative to the `base_dir`, if the file
|
|
92
124
|
is within `base_dir`, or otherwise the `external_path` will be set to the path
|
|
93
125
|
it was read from.
|
|
@@ -98,20 +130,48 @@ def read_item(path: Path, base_dir: Path | None) -> Item:
|
|
|
98
130
|
log.debug("Cache hit for item: %s", path)
|
|
99
131
|
return cached_item
|
|
100
132
|
|
|
101
|
-
return _read_item_uncached(path, base_dir)
|
|
133
|
+
return _read_item_uncached(path, base_dir, preserve_filename=preserve_filename)
|
|
102
134
|
|
|
103
135
|
|
|
104
136
|
@tally_calls()
|
|
105
|
-
def _read_item_uncached(
|
|
137
|
+
def _read_item_uncached(
|
|
138
|
+
path: Path,
|
|
139
|
+
base_dir: Path | None,
|
|
140
|
+
*,
|
|
141
|
+
preserve_filename: bool = True,
|
|
142
|
+
prefer_frontmatter: bool = True,
|
|
143
|
+
) -> Item:
|
|
144
|
+
# First, try to resolve sidematter
|
|
145
|
+
sidematter = Sidematter(path).resolve(use_frontmatter=False)
|
|
146
|
+
|
|
147
|
+
# Use sidematter metadata unless we find and prefer frontmatter for metadata.
|
|
106
148
|
has_frontmatter = fmf_has_frontmatter(path)
|
|
107
|
-
|
|
108
|
-
if
|
|
149
|
+
frontmatter_meta = prefer_frontmatter and has_frontmatter and fmf_read_frontmatter(path)
|
|
150
|
+
if sidematter.meta and not frontmatter_meta:
|
|
151
|
+
metadata = sidematter.meta
|
|
152
|
+
body, _frontmatter_metadata = fmf_read(path)
|
|
153
|
+
log.debug(
|
|
154
|
+
"Read item from sidematter %s: body length %s, metadata %s",
|
|
155
|
+
sidematter.meta_path,
|
|
156
|
+
len(body),
|
|
157
|
+
metadata,
|
|
158
|
+
)
|
|
159
|
+
elif has_frontmatter:
|
|
109
160
|
body, metadata = fmf_read(path)
|
|
110
|
-
log.debug(
|
|
161
|
+
log.debug(
|
|
162
|
+
"Read item from %s: body length %s, metadata %s",
|
|
163
|
+
path,
|
|
164
|
+
len(body),
|
|
165
|
+
metadata,
|
|
166
|
+
)
|
|
167
|
+
else:
|
|
168
|
+
# Not readable, binary or otherwise.
|
|
169
|
+
metadata = None
|
|
170
|
+
body = None
|
|
111
171
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
172
|
+
path = path.resolve()
|
|
173
|
+
if base_dir:
|
|
174
|
+
base_dir = base_dir.resolve()
|
|
115
175
|
|
|
116
176
|
# Ensure store_path is used if it's within the base_dir, and
|
|
117
177
|
# external_path otherwise.
|
|
@@ -128,7 +188,8 @@ def _read_item_uncached(path: Path, base_dir: Path | None) -> Item:
|
|
|
128
188
|
metadata, body=body, store_path=store_path, external_path=external_path
|
|
129
189
|
)
|
|
130
190
|
else:
|
|
131
|
-
# This is a file without frontmatter
|
|
191
|
+
# This is a file without frontmatter or sidematter.
|
|
192
|
+
# Infer format from the file and content,
|
|
132
193
|
# and use store_path or external_path as appropriate.
|
|
133
194
|
item = Item.from_external_path(path)
|
|
134
195
|
if item.format:
|
|
@@ -154,6 +215,10 @@ def _read_item_uncached(path: Path, base_dir: Path | None) -> Item:
|
|
|
154
215
|
item.body = f.read()
|
|
155
216
|
item.external_path = None
|
|
156
217
|
|
|
218
|
+
# Preserve the original filename.
|
|
219
|
+
if preserve_filename:
|
|
220
|
+
item.original_filename = path.name
|
|
221
|
+
|
|
157
222
|
# Update modified time.
|
|
158
223
|
item.set_modified(path.stat().st_mtime)
|
|
159
224
|
|
kash/help/help_types.py
CHANGED
|
@@ -5,7 +5,7 @@ from dataclasses import dataclass
|
|
|
5
5
|
from enum import Enum
|
|
6
6
|
from typing import TYPE_CHECKING, ClassVar
|
|
7
7
|
|
|
8
|
-
from flowmark
|
|
8
|
+
from flowmark import split_sentences_regex
|
|
9
9
|
from rich.console import Group
|
|
10
10
|
from rich.text import Text
|
|
11
11
|
from strif import abbrev_str, single_line
|
kash/llm_utils/llms.py
CHANGED
|
@@ -13,6 +13,10 @@ class LLM(LLMName, Enum):
|
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
15
|
# https://platform.openai.com/docs/models
|
|
16
|
+
gpt_5 = LLMName("gpt-5")
|
|
17
|
+
gpt_5_mini = LLMName("gpt-5-mini")
|
|
18
|
+
gpt_5_nano = LLMName("gpt-5-nano")
|
|
19
|
+
gpt_5_chat = LLMName("gpt-5-chat")
|
|
16
20
|
o4_mini = LLMName("o4-mini")
|
|
17
21
|
o3 = LLMName("o3")
|
|
18
22
|
o3_pro = LLMName("o3-pro")
|
|
@@ -25,11 +29,12 @@ class LLM(LLMName, Enum):
|
|
|
25
29
|
gpt_4o = LLMName("gpt-4o")
|
|
26
30
|
gpt_4o_mini = LLMName("gpt-4o-mini")
|
|
27
31
|
gpt_4 = LLMName("gpt-4")
|
|
28
|
-
|
|
29
32
|
gpt_4_1_mini = LLMName("gpt-4.1-mini")
|
|
30
33
|
gpt_4_1_nano = LLMName("gpt-4.1-nano")
|
|
31
34
|
|
|
32
35
|
# https://docs.anthropic.com/en/docs/about-claude/models/all-models
|
|
36
|
+
|
|
37
|
+
claude_4_1_opus = LLMName("claude-opus-4-1")
|
|
33
38
|
claude_4_opus = LLMName("claude-opus-4-20250514")
|
|
34
39
|
claude_4_sonnet = LLMName("claude-sonnet-4-20250514")
|
|
35
40
|
claude_3_7_sonnet = LLMName("claude-3-7-sonnet-latest")
|
|
@@ -49,7 +49,8 @@ def local_server_logs(follow: bool = False) -> None:
|
|
|
49
49
|
"""
|
|
50
50
|
Show the logs from the kash local (UI and MCP) servers.
|
|
51
51
|
|
|
52
|
-
:
|
|
52
|
+
Args:
|
|
53
|
+
follow: Follow the file as it grows.
|
|
53
54
|
"""
|
|
54
55
|
log_path = global_settings().local_server_log_path
|
|
55
56
|
if not log_path.exists():
|
kash/mcp/mcp_server_commands.py
CHANGED
|
@@ -54,8 +54,9 @@ def mcp_logs(follow: bool = False, all: bool = False) -> None:
|
|
|
54
54
|
"""
|
|
55
55
|
Show the logs from the MCP server and CLI proxy process.
|
|
56
56
|
|
|
57
|
-
:
|
|
58
|
-
|
|
57
|
+
Args:
|
|
58
|
+
follow: Follow the file as it grows.
|
|
59
|
+
all: Show all logs, not just the server logs, including Claude Desktop logs if found.
|
|
59
60
|
"""
|
|
60
61
|
from kash.mcp.mcp_cli import MCP_CLI_LOG_PATH
|
|
61
62
|
|
kash/mcp/mcp_server_routes.py
CHANGED
|
@@ -238,7 +238,7 @@ def run_mcp_tool(action_name: str, arguments: dict) -> list[TextContent]:
|
|
|
238
238
|
action_input = prepare_action_input(*input_items)
|
|
239
239
|
|
|
240
240
|
result, result_store_paths, _archived_store_paths = run_action_with_caching(
|
|
241
|
-
context
|
|
241
|
+
context, action_input
|
|
242
242
|
)
|
|
243
243
|
|
|
244
244
|
# Return final result, formatted for the LLM to understand.
|
kash/model/actions_model.py
CHANGED
|
@@ -21,9 +21,8 @@ from kash.exec_model.args_model import NO_ARGS, ONE_ARG, ArgCount, ArgType, Sign
|
|
|
21
21
|
from kash.exec_model.shell_model import ShellResult
|
|
22
22
|
from kash.llm_utils import LLM, LLMName
|
|
23
23
|
from kash.llm_utils.llm_messages import Message, MessageTemplate
|
|
24
|
-
from kash.model.exec_model import ExecContext
|
|
24
|
+
from kash.model.exec_model import ActionContext, ExecContext
|
|
25
25
|
from kash.model.items_model import UNTITLED, Format, Item, ItemType
|
|
26
|
-
from kash.model.operations_model import Operation, Source
|
|
27
26
|
from kash.model.params_model import (
|
|
28
27
|
ALL_COMMON_PARAMS,
|
|
29
28
|
COMMON_SHELL_PARAMS,
|
|
@@ -61,6 +60,17 @@ class ActionInput:
|
|
|
61
60
|
"""An empty input, for when an action processes no items."""
|
|
62
61
|
return ActionInput(items=[])
|
|
63
62
|
|
|
63
|
+
# XXX For convenience, we have the ability to include the context on each item
|
|
64
|
+
# (this helps soper-item functions don't have to take context args everywhere).
|
|
65
|
+
# TODO: Probably better to move this to a context var.
|
|
66
|
+
def set_context(self, context: ActionContext) -> None:
|
|
67
|
+
for item in self.items:
|
|
68
|
+
item.context = context
|
|
69
|
+
|
|
70
|
+
def clear_context(self) -> None:
|
|
71
|
+
for item in self.items:
|
|
72
|
+
item.context = None
|
|
73
|
+
|
|
64
74
|
|
|
65
75
|
class PathOpType(Enum):
|
|
66
76
|
archive = "archive"
|
|
@@ -111,6 +121,10 @@ class ActionResult:
|
|
|
111
121
|
self.replaces_input or self.skip_duplicates or self.path_ops or self.shell_result
|
|
112
122
|
)
|
|
113
123
|
|
|
124
|
+
def set_context(self, context: ActionContext) -> None:
|
|
125
|
+
for item in self.items:
|
|
126
|
+
item.context = context
|
|
127
|
+
|
|
114
128
|
def clear_context(self):
|
|
115
129
|
for item in self.items:
|
|
116
130
|
item.context = None
|
|
@@ -505,33 +519,17 @@ class Action(ABC):
|
|
|
505
519
|
fmt_lines(overrides),
|
|
506
520
|
)
|
|
507
521
|
|
|
508
|
-
def
|
|
509
|
-
self,
|
|
510
|
-
operation: Operation,
|
|
511
|
-
input: ActionInput,
|
|
512
|
-
output_num: int,
|
|
513
|
-
type: ItemType,
|
|
514
|
-
**kwargs,
|
|
515
|
-
) -> Item:
|
|
522
|
+
def format_title(self, prev_title: str | None) -> str:
|
|
516
523
|
"""
|
|
517
|
-
|
|
518
|
-
type, and last Operation so we can do an identity check if the output already exists.
|
|
524
|
+
Format the title for an output item of this action.
|
|
519
525
|
"""
|
|
520
|
-
|
|
521
|
-
item = primary_input.derived_copy(type=type, body=None, **kwargs)
|
|
522
|
-
|
|
526
|
+
prev_title = prev_title or UNTITLED
|
|
523
527
|
if self.title_template:
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
item.update_history(
|
|
529
|
-
Source(operation=operation, output_num=output_num, cacheable=self.cacheable)
|
|
530
|
-
)
|
|
531
|
-
|
|
532
|
-
return item
|
|
528
|
+
return self.title_template.format(title=prev_title, action_name=self.name)
|
|
529
|
+
else:
|
|
530
|
+
return prev_title
|
|
533
531
|
|
|
534
|
-
def
|
|
532
|
+
def preassemble_result(self, context: ActionContext) -> ActionResult | None:
|
|
535
533
|
"""
|
|
536
534
|
Actions can have a separate preliminary step to pre-assemble outputs. This allows
|
|
537
535
|
us to determine the title and types for the output items and check if they were
|
|
@@ -549,9 +547,11 @@ class Action(ABC):
|
|
|
549
547
|
self.cacheable,
|
|
550
548
|
)
|
|
551
549
|
if can_preassemble:
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
550
|
+
# Using first input to determine the output title.
|
|
551
|
+
primary_input = context.action_input.items[0]
|
|
552
|
+
# In this case we only expect one output.
|
|
553
|
+
item = primary_input.derived_copy(context, 0)
|
|
554
|
+
return ActionResult([item])
|
|
555
555
|
else:
|
|
556
556
|
# Caching disabled.
|
|
557
557
|
return None
|
|
@@ -600,7 +600,7 @@ class Action(ABC):
|
|
|
600
600
|
return input_schema
|
|
601
601
|
|
|
602
602
|
@abstractmethod
|
|
603
|
-
def run(self, input: ActionInput, context:
|
|
603
|
+
def run(self, input: ActionInput, context: ActionContext) -> ActionResult:
|
|
604
604
|
pass
|
|
605
605
|
|
|
606
606
|
def __repr__(self):
|
|
@@ -626,7 +626,7 @@ class PerItemAction(Action, ABC):
|
|
|
626
626
|
super().__post_init__()
|
|
627
627
|
|
|
628
628
|
@override
|
|
629
|
-
def run(self, input: ActionInput, context:
|
|
629
|
+
def run(self, input: ActionInput, context: ActionContext) -> ActionResult:
|
|
630
630
|
log.info("Running action `%s` per-item.", self.name)
|
|
631
631
|
for item in input.items:
|
|
632
632
|
item.context = context
|
|
@@ -642,3 +642,4 @@ class PerItemAction(Action, ABC):
|
|
|
642
642
|
# Handle circular dependency in Python dataclasses.
|
|
643
643
|
rebuild_dataclass(Item) # pyright: ignore
|
|
644
644
|
rebuild_dataclass(ExecContext) # pyright: ignore
|
|
645
|
+
rebuild_dataclass(ActionContext) # pyright: ignore
|
|
@@ -5,7 +5,8 @@ from pydantic.dataclasses import dataclass
|
|
|
5
5
|
|
|
6
6
|
from kash.config.logger import get_logger
|
|
7
7
|
from kash.exec.combiners import Combiner
|
|
8
|
-
from kash.model.actions_model import Action, ActionInput, ActionResult
|
|
8
|
+
from kash.model.actions_model import Action, ActionInput, ActionResult
|
|
9
|
+
from kash.model.exec_model import ActionContext
|
|
9
10
|
from kash.model.items_model import Item, State
|
|
10
11
|
from kash.model.params_model import RawParamValues
|
|
11
12
|
from kash.model.paths_model import StorePath
|
|
@@ -46,7 +47,7 @@ class SequenceAction(Action):
|
|
|
46
47
|
)
|
|
47
48
|
self.description = seq_description
|
|
48
49
|
|
|
49
|
-
def run(self, input: ActionInput, context:
|
|
50
|
+
def run(self, input: ActionInput, context: ActionContext) -> ActionResult:
|
|
50
51
|
from kash.exec.action_exec import run_action_with_shell_context
|
|
51
52
|
from kash.workspaces import current_ws
|
|
52
53
|
|
|
@@ -140,7 +141,7 @@ class ComboAction(Action):
|
|
|
140
141
|
|
|
141
142
|
self.description = combo_description
|
|
142
143
|
|
|
143
|
-
def run(self, input: ActionInput, context:
|
|
144
|
+
def run(self, input: ActionInput, context: ActionContext) -> ActionResult:
|
|
144
145
|
from kash.exec.action_exec import run_action_with_shell_context
|
|
145
146
|
from kash.exec.combiners import combine_as_paragraphs
|
|
146
147
|
|
kash/model/exec_model.py
CHANGED
|
@@ -8,10 +8,11 @@ from pydantic.dataclasses import dataclass
|
|
|
8
8
|
|
|
9
9
|
from kash.config.logger import get_logger
|
|
10
10
|
from kash.model.items_model import State
|
|
11
|
+
from kash.model.operations_model import Operation
|
|
11
12
|
|
|
12
13
|
if TYPE_CHECKING:
|
|
13
14
|
from kash.file_storage.file_store import FileStore
|
|
14
|
-
from kash.model.actions_model import Action
|
|
15
|
+
from kash.model.actions_model import Action, ActionInput
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
log = get_logger(__name__)
|
|
@@ -68,8 +69,8 @@ class RuntimeSettings:
|
|
|
68
69
|
@dataclass(frozen=True)
|
|
69
70
|
class ExecContext:
|
|
70
71
|
"""
|
|
71
|
-
An action and its context for execution. This is a good place for
|
|
72
|
-
that apply to any action and are bothersome to pass as parameters.
|
|
72
|
+
An action and its general context for execution. This is a good place for general
|
|
73
|
+
settings that apply to any action and are bothersome to pass as parameters.
|
|
73
74
|
"""
|
|
74
75
|
|
|
75
76
|
action: Action
|
|
@@ -77,3 +78,29 @@ class ExecContext:
|
|
|
77
78
|
|
|
78
79
|
settings: RuntimeSettings
|
|
79
80
|
"""The workspace and other run-time settings for the action."""
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass(frozen=True)
|
|
84
|
+
class ActionContext:
|
|
85
|
+
"""
|
|
86
|
+
All context for the currently executing action, with all inputs and options.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
exec_context: ExecContext
|
|
90
|
+
"""The context of the current execution."""
|
|
91
|
+
|
|
92
|
+
action_input: ActionInput
|
|
93
|
+
"""The assembled input to the current action."""
|
|
94
|
+
|
|
95
|
+
operation: Operation
|
|
96
|
+
"""The operation in full detail, including inputs and options."""
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def action(self) -> Action:
|
|
100
|
+
"""The action being executed."""
|
|
101
|
+
return self.exec_context.action
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def settings(self) -> RuntimeSettings:
|
|
105
|
+
"""The workspace and other run-time settings for the action."""
|
|
106
|
+
return self.exec_context.settings
|