kash-shell 0.3.20__py3-none-any.whl → 0.3.22__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 +11 -0
- kash/actions/core/tabbed_webpage_generate.py +2 -2
- kash/commands/help/assistant_commands.py +2 -4
- kash/commands/help/logo.py +12 -17
- kash/commands/help/welcome.py +5 -4
- kash/config/colors.py +8 -6
- kash/config/text_styles.py +2 -0
- kash/docs/markdown/topics/b1_kash_overview.md +34 -45
- kash/docs/markdown/warning.md +3 -3
- kash/docs/markdown/welcome.md +2 -1
- kash/exec/action_decorators.py +20 -5
- kash/exec/fetch_url_items.py +6 -4
- kash/exec/llm_transforms.py +1 -1
- kash/exec/preconditions.py +7 -2
- kash/exec/shell_callable_action.py +1 -1
- kash/llm_utils/llm_completion.py +1 -1
- kash/model/actions_model.py +6 -0
- kash/model/items_model.py +14 -11
- kash/shell/output/shell_output.py +20 -1
- kash/utils/api_utils/api_retries.py +305 -0
- kash/utils/api_utils/cache_requests_limited.py +84 -0
- kash/utils/api_utils/gather_limited.py +987 -0
- kash/utils/api_utils/progress_protocol.py +299 -0
- kash/utils/common/function_inspect.py +66 -1
- kash/utils/common/testing.py +10 -7
- kash/utils/rich_custom/multitask_status.py +631 -0
- kash/utils/text_handling/escape_html_tags.py +16 -11
- kash/utils/text_handling/markdown_render.py +1 -0
- kash/utils/text_handling/markdown_utils.py +158 -1
- kash/web_gen/tabbed_webpage.py +2 -2
- kash/web_gen/templates/base_styles.css.jinja +26 -20
- kash/web_gen/templates/components/toc_styles.css.jinja +1 -1
- kash/web_gen/templates/components/tooltip_scripts.js.jinja +171 -19
- kash/web_gen/templates/components/tooltip_styles.css.jinja +23 -8
- kash/xonsh_custom/load_into_xonsh.py +0 -3
- {kash_shell-0.3.20.dist-info → kash_shell-0.3.22.dist-info}/METADATA +3 -1
- {kash_shell-0.3.20.dist-info → kash_shell-0.3.22.dist-info}/RECORD +40 -35
- {kash_shell-0.3.20.dist-info → kash_shell-0.3.22.dist-info}/WHEEL +0 -0
- {kash_shell-0.3.20.dist-info → kash_shell-0.3.22.dist-info}/entry_points.txt +0 -0
- {kash_shell-0.3.20.dist-info → kash_shell-0.3.22.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
from prettyfmt import abbrev_on_words
|
|
2
|
+
|
|
1
3
|
from kash.config.logger import get_logger
|
|
2
4
|
from kash.exec import kash_action
|
|
3
5
|
from kash.exec.preconditions import has_html_body, is_url_resource
|
|
4
6
|
from kash.exec.runtime_settings import current_runtime_settings
|
|
5
7
|
from kash.model import Format, Item
|
|
6
8
|
from kash.model.items_model import ItemType
|
|
9
|
+
from kash.utils.text_handling.markdown_utils import first_heading
|
|
7
10
|
from kash.utils.text_handling.markdownify_utils import markdownify_custom
|
|
8
11
|
from kash.web_content.file_cache_utils import get_url_html
|
|
9
12
|
from kash.web_content.web_extract_readabilipy import extract_text_readabilipy
|
|
@@ -25,6 +28,14 @@ def markdownify_html(item: Item) -> Item:
|
|
|
25
28
|
assert page_data.clean_html
|
|
26
29
|
markdown_content = markdownify_custom(page_data.clean_html)
|
|
27
30
|
|
|
31
|
+
# Sometimes readability doesn't include the title, in which case we add it.
|
|
32
|
+
first_h1 = first_heading(markdown_content, allowed_tags=("h1",))
|
|
33
|
+
title = page_data.title and abbrev_on_words(page_data.title.strip(), 80)
|
|
34
|
+
if not first_h1 and title:
|
|
35
|
+
log.message(f"No h1 found, inserting h1: {title}")
|
|
36
|
+
# Insert a h1 at the top of the document
|
|
37
|
+
markdown_content = f"# {title}\n\n{markdown_content}"
|
|
38
|
+
|
|
28
39
|
output_item = item.derived_copy(
|
|
29
40
|
type=ItemType.doc, format=Format.markdown, body=markdown_content
|
|
30
41
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from kash.config.logger import get_logger
|
|
2
2
|
from kash.exec import kash_action
|
|
3
|
-
from kash.exec.preconditions import
|
|
3
|
+
from kash.exec.preconditions import is_data
|
|
4
4
|
from kash.model import ONE_ARG, ActionInput, ActionResult, FileExt, Format, Item, ItemType, Param
|
|
5
5
|
from kash.web_gen import tabbed_webpage
|
|
6
6
|
|
|
@@ -9,7 +9,7 @@ log = get_logger(__name__)
|
|
|
9
9
|
|
|
10
10
|
@kash_action(
|
|
11
11
|
expected_args=ONE_ARG,
|
|
12
|
-
precondition=
|
|
12
|
+
precondition=is_data,
|
|
13
13
|
params=(Param("add_title", "Add a title to the page body.", type=bool),),
|
|
14
14
|
)
|
|
15
15
|
def tabbed_webpage_generate(input: ActionInput, add_title: bool = False) -> ActionResult:
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
from rich import get_console
|
|
2
|
-
|
|
3
1
|
from kash.commands.base.basic_file_commands import trash
|
|
4
2
|
from kash.commands.workspace.selection_commands import select
|
|
5
|
-
from kash.config.logger import get_logger
|
|
3
|
+
from kash.config.logger import get_console, get_logger
|
|
6
4
|
from kash.config.text_styles import PROMPT_ASSIST, SPINNER
|
|
7
5
|
from kash.docs.all_docs import DocSelection
|
|
8
6
|
from kash.exec import kash_command
|
|
@@ -45,7 +43,7 @@ def assist(
|
|
|
45
43
|
help()
|
|
46
44
|
return
|
|
47
45
|
|
|
48
|
-
with get_console().status("Thinking…", spinner=SPINNER):
|
|
46
|
+
with get_console().status("Thinking…", spinner=SPINNER): # noqa: F821
|
|
49
47
|
shell_context_assistance(input, model=model, assistance_type=type)
|
|
50
48
|
|
|
51
49
|
|
kash/commands/help/logo.py
CHANGED
|
@@ -2,7 +2,8 @@ import re
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
|
|
4
4
|
from rich.box import SQUARE
|
|
5
|
-
from rich.console import Group
|
|
5
|
+
from rich.console import Group, RenderableType
|
|
6
|
+
from rich.padding import Padding
|
|
6
7
|
from rich.panel import Panel
|
|
7
8
|
from rich.text import Text
|
|
8
9
|
|
|
@@ -62,7 +63,13 @@ def color_logo() -> Group:
|
|
|
62
63
|
)
|
|
63
64
|
|
|
64
65
|
|
|
65
|
-
def
|
|
66
|
+
def simple_box(content: RenderableType) -> Panel:
|
|
67
|
+
return Panel(
|
|
68
|
+
content, border_style=COLOR_HINT, padding=(0, 1), width=CONSOLE_WRAP_WIDTH, box=SQUARE
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def logo_box(content: Group | None = None) -> Padding:
|
|
66
73
|
panel_width = CONSOLE_WRAP_WIDTH
|
|
67
74
|
|
|
68
75
|
logo_lines = LOGO_LARGE.split("\n")
|
|
@@ -70,29 +77,17 @@ def branded_box(content: Group | None, version: str | None = None) -> Panel:
|
|
|
70
77
|
tagline_offset = (panel_width - 4 - len(TAGLINE_STYLED)) // 2
|
|
71
78
|
|
|
72
79
|
colored_lines = [logo_colorize_line(line, " ", rest_offset) for line in logo_lines]
|
|
73
|
-
header = None
|
|
74
|
-
if version:
|
|
75
|
-
footer = Text(version, style=COLOR_HINT, justify="right")
|
|
76
|
-
else:
|
|
77
|
-
footer = None
|
|
78
80
|
|
|
79
81
|
body = ["", content] if content else []
|
|
80
82
|
|
|
81
|
-
return
|
|
83
|
+
return Padding(
|
|
82
84
|
Group(
|
|
83
85
|
Text.assemble(" " * tagline_offset, LOGO_SPACER),
|
|
84
86
|
*colored_lines,
|
|
85
87
|
Text.assemble(" " * tagline_offset, TAGLINE_STYLED),
|
|
86
88
|
*body,
|
|
87
89
|
),
|
|
88
|
-
|
|
89
|
-
title_align="center",
|
|
90
|
-
subtitle=footer,
|
|
91
|
-
subtitle_align="right",
|
|
92
|
-
border_style=COLOR_HINT,
|
|
93
|
-
padding=(0, 1),
|
|
94
|
-
width=panel_width,
|
|
95
|
-
box=SQUARE,
|
|
90
|
+
pad=(1, 1),
|
|
96
91
|
)
|
|
97
92
|
|
|
98
93
|
|
|
@@ -101,7 +96,7 @@ def kash_logo(box: bool = False, svg_out: str | None = None, html_out: str | Non
|
|
|
101
96
|
"""
|
|
102
97
|
Show the kash logo.
|
|
103
98
|
"""
|
|
104
|
-
logo =
|
|
99
|
+
logo = logo_box(None) if box else color_logo()
|
|
105
100
|
|
|
106
101
|
cprint(logo)
|
|
107
102
|
|
kash/commands/help/welcome.py
CHANGED
|
@@ -2,7 +2,7 @@ from rich.box import SQUARE
|
|
|
2
2
|
from rich.console import Group
|
|
3
3
|
from rich.panel import Panel
|
|
4
4
|
|
|
5
|
-
from kash.commands.help.logo import
|
|
5
|
+
from kash.commands.help.logo import logo_box, simple_box
|
|
6
6
|
from kash.config.text_styles import (
|
|
7
7
|
COLOR_HINT,
|
|
8
8
|
)
|
|
@@ -20,14 +20,15 @@ def welcome() -> None:
|
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
22
|
help_topics = all_docs.help_topics
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
# Create header with logo and right-justified version
|
|
25
25
|
|
|
26
26
|
PrintHooks.before_welcome()
|
|
27
|
+
cprint(logo_box())
|
|
27
28
|
cprint(
|
|
28
|
-
|
|
29
|
+
simple_box(
|
|
29
30
|
Group(Markdown(help_topics.welcome)),
|
|
30
|
-
version,
|
|
31
31
|
)
|
|
32
32
|
)
|
|
33
33
|
cprint(Panel(Markdown(help_topics.warning), box=SQUARE, border_style=COLOR_HINT))
|
|
34
|
+
cprint("%s", get_full_version_name())
|
kash/config/colors.py
CHANGED
|
@@ -139,14 +139,15 @@ web_light_translucent = SimpleNamespace(
|
|
|
139
139
|
bg_header=hsl_to_hex("hsla(188, 42%, 70%, 0.2)"),
|
|
140
140
|
bg_alt=hsl_to_hex("hsla(39, 24%, 90%, 0.3)"),
|
|
141
141
|
bg_alt_solid=hsl_to_hex("hsla(39, 24%, 97%, 1)"),
|
|
142
|
-
|
|
142
|
+
bg_meta_solid=hsl_to_hex("hsla(39, 24%, 94%, 1)"),
|
|
143
|
+
bg_selected=hsl_to_hex("hsla(188, 21%, 94%, 0.9)"),
|
|
143
144
|
text=hsl_to_hex("hsl(188, 39%, 11%)"),
|
|
144
145
|
code=hsl_to_hex("hsl(44, 38%, 23%)"),
|
|
145
146
|
border=hsl_to_hex("hsl(188, 8%, 50%)"),
|
|
146
147
|
border_hint=hsl_to_hex("hsla(188, 8%, 72%, 0.3)"),
|
|
147
148
|
border_accent=hsl_to_hex("hsla(305, 18%, 65%, 0.85)"),
|
|
148
149
|
hover=hsl_to_hex("hsl(188, 12%, 84%)"),
|
|
149
|
-
hover_bg=hsl_to_hex("hsla(188,
|
|
150
|
+
hover_bg=hsl_to_hex("hsla(188, 18%, 97%, 1)"),
|
|
150
151
|
hint=hsl_to_hex("hsl(188, 11%, 65%)"),
|
|
151
152
|
hint_strong=hsl_to_hex("hsl(188, 11%, 46%)"),
|
|
152
153
|
hint_gentle=hsl_to_hex("hsla(188, 11%, 65%, 0.2)"),
|
|
@@ -165,14 +166,15 @@ web_light_translucent = SimpleNamespace(
|
|
|
165
166
|
web_dark_translucent = SimpleNamespace(
|
|
166
167
|
primary=hsl_to_hex("hsl(188, 40%, 62%)"),
|
|
167
168
|
primary_light=hsl_to_hex("hsl(188, 50%, 72%)"),
|
|
168
|
-
secondary=hsl_to_hex("hsl(188, 12%,
|
|
169
|
-
tertiary=hsl_to_hex("hsl(188, 7%,
|
|
169
|
+
secondary=hsl_to_hex("hsl(188, 12%, 70%)"),
|
|
170
|
+
tertiary=hsl_to_hex("hsl(188, 7%, 45%)"),
|
|
170
171
|
bg=hsl_to_hex("hsla(220, 14%, 7%, 0.95)"),
|
|
171
172
|
bg_solid=hsl_to_hex("hsl(220, 14%, 7%)"),
|
|
172
173
|
bg_header=hsl_to_hex("hsla(188, 42%, 20%, 0.3)"),
|
|
173
174
|
bg_alt=hsl_to_hex("hsla(220, 14%, 12%, 0.5)"),
|
|
174
|
-
bg_alt_solid=hsl_to_hex("hsl(220,
|
|
175
|
-
|
|
175
|
+
bg_alt_solid=hsl_to_hex("hsl(220, 15%, 16%)"),
|
|
176
|
+
bg_meta_solid=hsl_to_hex("hsl(220, 14%, 25%)"),
|
|
177
|
+
bg_selected=hsl_to_hex("hsla(188, 13%, 33%, 0.95)"),
|
|
176
178
|
text=hsl_to_hex("hsl(188, 10%, 90%)"),
|
|
177
179
|
code=hsl_to_hex("hsl(44, 38%, 72%)"),
|
|
178
180
|
border=hsl_to_hex("hsl(188, 8%, 25%)"),
|
kash/config/text_styles.py
CHANGED
|
@@ -165,48 +165,37 @@ works on readable text such as Markdown.
|
|
|
165
165
|
This catches errors and allows you to find actions that might apply to a given selected
|
|
166
166
|
set of items using `suggest_actions`.
|
|
167
167
|
|
|
168
|
-
### Programmatic
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
Kash
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
doc into an edited document, such as pulling timestamps from a full transcript back
|
|
203
|
-
into an edited transcript or summary.
|
|
204
|
-
|
|
205
|
-
- Paragraph and sentence operations: A lot of operations within actions should be done
|
|
206
|
-
in chunks at the paragraph or sentence level.
|
|
207
|
-
Kash offers simple tools to subdivide documents into paragraphs and sentences and
|
|
208
|
-
these can be used together with sliding windows to process large documents.
|
|
209
|
-
|
|
210
|
-
In addition, there are built-in kash commands that are part of the kash tool itself.
|
|
211
|
-
These allow you to list items in the workspace, see or change the current selection,
|
|
212
|
-
archive items, view logs, etc.
|
|
168
|
+
### Programmatic Usage
|
|
169
|
+
|
|
170
|
+
Kash can be used entirely programmatically, so that actions are called just like
|
|
171
|
+
functions from Python, but the additional functionality of the items model, saving files
|
|
172
|
+
to a workspace, and so on, are all automatic.
|
|
173
|
+
|
|
174
|
+
This means you can use Kash to build your own CLI apps much more quickly.
|
|
175
|
+
|
|
176
|
+
For an example of this, see [textpress](https://github.com/jlevy/textpress), which wraps
|
|
177
|
+
quite a few kash actions to allow clean publishing of docx or PDF files on
|
|
178
|
+
[textpress.md](https://textpress.md/).
|
|
179
|
+
|
|
180
|
+
### Utilities and Supporting Libraries
|
|
181
|
+
|
|
182
|
+
Kash includes a number of utility libraries to help with common tasks, either in the
|
|
183
|
+
base `kash-shell` package or or smaller dependencies:
|
|
184
|
+
|
|
185
|
+
- See [frontmatter-format](https://github.com/jlevy/frontmatter-format) for the spec and
|
|
186
|
+
implementation we use of frontmatter YAML format.
|
|
187
|
+
|
|
188
|
+
- See
|
|
189
|
+
[utils/file_utils](https://github.com/jlevy/kash/tree/main/src/kash/utils/file_utils)
|
|
190
|
+
for file format detection, conversion, filename handling, etc.
|
|
191
|
+
|
|
192
|
+
- See [chopdiff](https://github.com/jlevy/chopdiff) for a simple text doc data model
|
|
193
|
+
that includes sentences and paragraphs and fairly advanced diffing, filtered diffing,
|
|
194
|
+
and windowed transformations of text via LLM calls.
|
|
195
|
+
|
|
196
|
+
- See [clideps](https://github.com/jlevy/clideps) for utilities for helping with dot-env
|
|
197
|
+
files, API key setup, and dependency checks.
|
|
198
|
+
|
|
199
|
+
- See [utils/common](https://github.com/jlevy/kash/tree/main/src/kash/utils/common) the
|
|
200
|
+
rest of [utils/](https://github.com/jlevy/kash/tree/main/src/kash/utils) for a variety
|
|
201
|
+
of other general utilities.
|
kash/docs/markdown/warning.md
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
**Important:**
|
|
2
|
-
|
|
3
|
-
Review commands carefully
|
|
1
|
+
**Important:** This is a shell.
|
|
2
|
+
Commands can be destructive.
|
|
3
|
+
Review commands carefully!
|
kash/docs/markdown/welcome.md
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
**Welcome to
|
|
1
|
+
**Welcome to Kash!**
|
|
2
2
|
|
|
3
|
+
Use `help` for the manual and full list of available commands.
|
|
3
4
|
Press **tab** for contextual autocomplete of commands, questions, actions, and files.
|
|
4
5
|
You may simply ask a question and the kash assistant will help you.
|
|
5
6
|
Press **space** (or type **?**), then write your question or request.
|
kash/exec/action_decorators.py
CHANGED
|
@@ -105,7 +105,10 @@ def kash_action_class(cls: type[A]) -> type[A]:
|
|
|
105
105
|
|
|
106
106
|
|
|
107
107
|
def _register_dynamic_action(
|
|
108
|
-
action_cls: type[A],
|
|
108
|
+
action_cls: type[A],
|
|
109
|
+
action_name: str,
|
|
110
|
+
action_description: str,
|
|
111
|
+
source_path: Path | None,
|
|
109
112
|
) -> type[A]:
|
|
110
113
|
# Set class fields for name and description for convenience.
|
|
111
114
|
action_cls.name = action_name
|
|
@@ -206,6 +209,7 @@ def kash_action(
|
|
|
206
209
|
run_per_item: bool | None = None,
|
|
207
210
|
uses_selection: bool = True,
|
|
208
211
|
interactive_input: bool = False,
|
|
212
|
+
live_output: bool = False,
|
|
209
213
|
mcp_tool: bool = False,
|
|
210
214
|
title_template: TitleTemplate = TitleTemplate("{title}"),
|
|
211
215
|
llm_options: LLMOptions = LLMOptions(),
|
|
@@ -235,13 +239,17 @@ def kash_action(
|
|
|
235
239
|
def decorator(orig_func: AF) -> AF:
|
|
236
240
|
if hasattr(orig_func, "__action_class__"):
|
|
237
241
|
log.warning(
|
|
238
|
-
"Function `%s` is already decorated with `@kash_action`",
|
|
242
|
+
"Function `%s` is already decorated with `@kash_action`",
|
|
243
|
+
orig_func.__name__,
|
|
239
244
|
)
|
|
240
245
|
return orig_func
|
|
241
246
|
|
|
242
247
|
# Inspect and sanity check the formal params.
|
|
243
248
|
func_params = inspect_function_params(orig_func)
|
|
244
|
-
if len(func_params) == 0 or func_params[0].effective_type not in (
|
|
249
|
+
if len(func_params) == 0 or func_params[0].effective_type not in (
|
|
250
|
+
ActionInput,
|
|
251
|
+
Item,
|
|
252
|
+
):
|
|
245
253
|
raise InvalidDefinition(
|
|
246
254
|
f"Decorator `@kash_action` requires exactly one positional parameter, "
|
|
247
255
|
f"`input` of type `ActionInput` or `Item` on function `{orig_func.__name__}` but "
|
|
@@ -311,6 +319,7 @@ def kash_action(
|
|
|
311
319
|
self.uses_selection = uses_selection
|
|
312
320
|
self.output_type = output_type
|
|
313
321
|
self.interactive_input = interactive_input
|
|
322
|
+
self.live_output = live_output
|
|
314
323
|
self.mcp_tool = mcp_tool
|
|
315
324
|
self.title_template = title_template
|
|
316
325
|
self.llm_options = llm_options
|
|
@@ -332,8 +341,14 @@ def kash_action(
|
|
|
332
341
|
kw_args[fp.name] = self.get_param(fp.name)
|
|
333
342
|
|
|
334
343
|
if self.params:
|
|
335
|
-
log.info(
|
|
336
|
-
|
|
344
|
+
log.info(
|
|
345
|
+
"Action function param declarations:\n%s",
|
|
346
|
+
fmt_lines(self.params),
|
|
347
|
+
)
|
|
348
|
+
log.info(
|
|
349
|
+
"Action function param values:\n%s",
|
|
350
|
+
self.param_value_summary_str(),
|
|
351
|
+
)
|
|
337
352
|
else:
|
|
338
353
|
log.info("Action function has no declared params")
|
|
339
354
|
|
kash/exec/fetch_url_items.py
CHANGED
|
@@ -48,9 +48,10 @@ def fetch_url_item_content(item: Item, *, save_content: bool = True, refetch: bo
|
|
|
48
48
|
from kash.workspaces import current_ws
|
|
49
49
|
|
|
50
50
|
ws = current_ws()
|
|
51
|
-
if not refetch and item.title and item.description:
|
|
51
|
+
if not refetch and item.title and item.description and item.body:
|
|
52
52
|
log.message(
|
|
53
|
-
"Already have title and
|
|
53
|
+
"Already have title, description, and body, will not fetch: %s",
|
|
54
|
+
item.fmt_loc(),
|
|
54
55
|
)
|
|
55
56
|
return item
|
|
56
57
|
|
|
@@ -100,10 +101,11 @@ def fetch_url_item_content(item: Item, *, save_content: bool = True, refetch: bo
|
|
|
100
101
|
# Now save the updated URL item and also the content item if we have one.
|
|
101
102
|
ws.save(url_item)
|
|
102
103
|
assert url_item.store_path
|
|
103
|
-
log.debug("Saved URL item: %s", url_item.fmt_loc())
|
|
104
104
|
if content_item:
|
|
105
105
|
ws.save(content_item)
|
|
106
106
|
assert content_item.store_path
|
|
107
|
-
log.
|
|
107
|
+
log.info("Saved content item: %s", content_item.fmt_loc())
|
|
108
|
+
else:
|
|
109
|
+
log.info("Saved URL item: %s", url_item.fmt_loc())
|
|
108
110
|
|
|
109
111
|
return content_item or url_item
|
kash/exec/llm_transforms.py
CHANGED
|
@@ -68,7 +68,7 @@ def llm_transform_str(options: LLMOptions, input_str: str, check_no_results: boo
|
|
|
68
68
|
diff_filter=options.diff_filter,
|
|
69
69
|
).reassemble()
|
|
70
70
|
else:
|
|
71
|
-
log.
|
|
71
|
+
log.info(
|
|
72
72
|
"Running simple LLM transform action %s with model %s",
|
|
73
73
|
options.op_name,
|
|
74
74
|
options.model.litellm_name,
|
kash/exec/preconditions.py
CHANGED
|
@@ -48,8 +48,13 @@ def is_concept(item: Item) -> bool:
|
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
@kash_precondition
|
|
51
|
-
def
|
|
52
|
-
return item.type == ItemType.
|
|
51
|
+
def is_data(item: Item) -> bool:
|
|
52
|
+
return item.type == ItemType.data
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@kash_precondition
|
|
56
|
+
def is_table(item: Item) -> bool:
|
|
57
|
+
return item.type == ItemType.table
|
|
53
58
|
|
|
54
59
|
|
|
55
60
|
@kash_precondition
|
|
@@ -56,7 +56,7 @@ class ShellCallableAction:
|
|
|
56
56
|
|
|
57
57
|
log.info("Action shell args: %s", shell_args)
|
|
58
58
|
explicit_values = RawParamValues(shell_args.options)
|
|
59
|
-
if not action.interactive_input:
|
|
59
|
+
if not action.interactive_input and not action.live_output:
|
|
60
60
|
with get_console().status(f"Running action {action.name}…", spinner=SPINNER):
|
|
61
61
|
result = run_action_with_shell_context(
|
|
62
62
|
action_cls,
|
kash/llm_utils/llm_completion.py
CHANGED
|
@@ -173,7 +173,7 @@ def llm_template_completion(
|
|
|
173
173
|
)
|
|
174
174
|
|
|
175
175
|
if check_no_results and is_no_results(result.content):
|
|
176
|
-
log.
|
|
176
|
+
log.info("No results for LLM transform, will ignore: %r", result.content)
|
|
177
177
|
result.content = ""
|
|
178
178
|
|
|
179
179
|
return result
|
kash/model/actions_model.py
CHANGED
|
@@ -270,6 +270,12 @@ class Action(ABC):
|
|
|
270
270
|
Does this action ask for input interactively?
|
|
271
271
|
"""
|
|
272
272
|
|
|
273
|
+
live_output: bool = False
|
|
274
|
+
"""
|
|
275
|
+
Does this action have live output (e.g., progress bars, spinners)?
|
|
276
|
+
If True, the shell should not show its own status spinner.
|
|
277
|
+
"""
|
|
278
|
+
|
|
273
279
|
mcp_tool: bool = False
|
|
274
280
|
"""
|
|
275
281
|
If True, this action is published as an MCP tool.
|
kash/model/items_model.py
CHANGED
|
@@ -55,7 +55,8 @@ class ItemType(Enum):
|
|
|
55
55
|
concept = "concept"
|
|
56
56
|
resource = "resource"
|
|
57
57
|
asset = "asset"
|
|
58
|
-
|
|
58
|
+
data = "data"
|
|
59
|
+
table = "table"
|
|
59
60
|
export = "export"
|
|
60
61
|
chat = "chat"
|
|
61
62
|
extension = "extension"
|
|
@@ -86,7 +87,9 @@ class ItemType(Enum):
|
|
|
86
87
|
Format.diff: ItemType.doc,
|
|
87
88
|
Format.python: ItemType.extension,
|
|
88
89
|
Format.json: ItemType.doc,
|
|
89
|
-
Format.csv: ItemType.
|
|
90
|
+
Format.csv: ItemType.table,
|
|
91
|
+
Format.xlsx: ItemType.table,
|
|
92
|
+
Format.npz: ItemType.table,
|
|
90
93
|
Format.log: ItemType.log,
|
|
91
94
|
Format.pdf: ItemType.resource,
|
|
92
95
|
Format.jpeg: ItemType.asset,
|
|
@@ -646,7 +649,7 @@ class Item:
|
|
|
646
649
|
body_text = abbrev_str(self.body_text(), max_len)
|
|
647
650
|
|
|
648
651
|
# Just for aesthetics, especially for titles of chat files.
|
|
649
|
-
if self.type in [ItemType.chat, ItemType.
|
|
652
|
+
if self.type in [ItemType.chat, ItemType.data] or self.format == Format.yaml:
|
|
650
653
|
try:
|
|
651
654
|
yaml_obj = list(new_yaml().load_all(self.body_text()))
|
|
652
655
|
if len(yaml_obj) > 0:
|
|
@@ -663,16 +666,16 @@ class Item:
|
|
|
663
666
|
"""
|
|
664
667
|
return bool(self.body and self.body.strip())
|
|
665
668
|
|
|
666
|
-
def
|
|
669
|
+
def read_as_data(self) -> Any:
|
|
667
670
|
"""
|
|
668
|
-
If it is a
|
|
671
|
+
If it is a data Item, return the parsed YAML.
|
|
669
672
|
"""
|
|
670
|
-
if not self.type == ItemType.
|
|
671
|
-
raise FileFormatError(f"Item is not a
|
|
673
|
+
if not self.type == ItemType.data:
|
|
674
|
+
raise FileFormatError(f"Item is not a data item: {self}")
|
|
672
675
|
if not self.body:
|
|
673
|
-
raise FileFormatError(f"
|
|
676
|
+
raise FileFormatError(f"Data item has no body: {self}")
|
|
674
677
|
if self.format != Format.yaml:
|
|
675
|
-
raise FileFormatError(f"
|
|
678
|
+
raise FileFormatError(f"Data item is not YAML: {self.format}: {self}")
|
|
676
679
|
return from_yaml_string(self.body)
|
|
677
680
|
|
|
678
681
|
def get_filename(self) -> str | None:
|
|
@@ -709,8 +712,8 @@ class Item:
|
|
|
709
712
|
elif self.type == ItemType.script:
|
|
710
713
|
# Same for kash/xonsh scripts.
|
|
711
714
|
return f"{self.type.value}.{FileExt.xsh.value}"
|
|
712
|
-
elif self.type
|
|
713
|
-
# For exports, skip the item type to keep it maximally compatible for external tools.
|
|
715
|
+
elif self.type in [ItemType.export, ItemType.data, ItemType.table]:
|
|
716
|
+
# For exports, data, and tables, skip the item type to keep it maximally compatible for external tools.
|
|
714
717
|
return f"{self.get_file_ext().value}"
|
|
715
718
|
else:
|
|
716
719
|
return f"{self.type.value}.{self.get_file_ext().value}"
|
|
@@ -4,7 +4,7 @@ Output to the shell UI. These are for user interaction, not logging.
|
|
|
4
4
|
|
|
5
5
|
import contextvars
|
|
6
6
|
from collections.abc import Callable
|
|
7
|
-
from contextlib import contextmanager
|
|
7
|
+
from contextlib import contextmanager, nullcontext
|
|
8
8
|
from enum import Enum, auto
|
|
9
9
|
|
|
10
10
|
import rich
|
|
@@ -28,6 +28,7 @@ from kash.config.text_styles import (
|
|
|
28
28
|
STYLE_HINT,
|
|
29
29
|
)
|
|
30
30
|
from kash.shell.output.kmarkdown import KMarkdown
|
|
31
|
+
from kash.utils.rich_custom.multitask_status import MultiTaskStatus, StatusSettings
|
|
31
32
|
from kash.utils.rich_custom.rich_indent import Indent
|
|
32
33
|
from kash.utils.rich_custom.rich_markdown_fork import Markdown
|
|
33
34
|
|
|
@@ -80,6 +81,24 @@ def console_pager(use_pager: bool = True):
|
|
|
80
81
|
PrintHooks.after_pager()
|
|
81
82
|
|
|
82
83
|
|
|
84
|
+
def multitask_status(
|
|
85
|
+
settings: StatusSettings | None = None, *, auto_summary: bool = True, enabled: bool = True
|
|
86
|
+
) -> MultiTaskStatus | nullcontext:
|
|
87
|
+
"""
|
|
88
|
+
Create a `MultiTaskStatus` context manager for displaying multiple task progress
|
|
89
|
+
using the global shell console. If disabled, returns a null context, so it's convenient
|
|
90
|
+
to disable status display.
|
|
91
|
+
"""
|
|
92
|
+
if not enabled:
|
|
93
|
+
return nullcontext()
|
|
94
|
+
|
|
95
|
+
return MultiTaskStatus(
|
|
96
|
+
console=get_console(),
|
|
97
|
+
settings=settings,
|
|
98
|
+
auto_summary=auto_summary,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
83
102
|
null_style = rich.style.Style.null()
|
|
84
103
|
|
|
85
104
|
|