kash-shell 0.3.25__py3-none-any.whl → 0.3.27__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 +51 -6
- kash/actions/core/minify_html.py +2 -2
- kash/commands/base/general_commands.py +4 -2
- kash/commands/help/assistant_commands.py +4 -3
- kash/commands/help/welcome.py +1 -1
- kash/config/colors.py +7 -3
- kash/config/logger.py +4 -0
- kash/config/text_styles.py +1 -0
- kash/config/unified_live.py +249 -0
- kash/docs/markdown/assistant_instructions_template.md +3 -3
- kash/docs/markdown/topics/a1_what_is_kash.md +22 -20
- kash/docs/markdown/topics/a2_installation.md +10 -10
- kash/docs/markdown/topics/a3_getting_started.md +8 -8
- kash/docs/markdown/topics/a4_elements.md +3 -3
- kash/docs/markdown/topics/a5_tips_for_use_with_other_tools.md +12 -12
- kash/docs/markdown/topics/b0_philosophy_of_kash.md +17 -17
- kash/docs/markdown/topics/b1_kash_overview.md +7 -7
- kash/docs/markdown/topics/b2_workspace_and_file_formats.md +1 -1
- kash/docs/markdown/topics/b3_modern_shell_tool_recommendations.md +1 -1
- kash/docs/markdown/topics/b4_faq.md +7 -7
- kash/docs/markdown/welcome.md +1 -1
- kash/embeddings/embeddings.py +110 -39
- kash/embeddings/text_similarity.py +2 -2
- kash/exec/shell_callable_action.py +4 -3
- kash/help/help_embeddings.py +5 -2
- kash/mcp/mcp_server_sse.py +0 -5
- kash/model/graph_model.py +2 -0
- kash/model/items_model.py +4 -4
- kash/shell/output/shell_output.py +2 -2
- kash/shell/shell_main.py +64 -6
- kash/shell/version.py +18 -2
- kash/utils/file_utils/csv_utils.py +105 -0
- kash/utils/rich_custom/multitask_status.py +19 -5
- kash/web_gen/templates/base_styles.css.jinja +384 -31
- kash/web_gen/templates/base_webpage.html.jinja +43 -0
- kash/web_gen/templates/components/toc_styles.css.jinja +25 -4
- kash/web_gen/templates/components/tooltip_styles.css.jinja +2 -0
- kash/web_gen/templates/content_styles.css.jinja +23 -9
- kash/web_gen/templates/item_view.html.jinja +12 -4
- kash/web_gen/templates/simple_webpage.html.jinja +2 -2
- kash/xonsh_custom/custom_shell.py +6 -6
- {kash_shell-0.3.25.dist-info → kash_shell-0.3.27.dist-info}/METADATA +59 -56
- {kash_shell-0.3.25.dist-info → kash_shell-0.3.27.dist-info}/RECORD +46 -44
- {kash_shell-0.3.25.dist-info → kash_shell-0.3.27.dist-info}/WHEEL +0 -0
- {kash_shell-0.3.25.dist-info → kash_shell-0.3.27.dist-info}/entry_points.txt +0 -0
- {kash_shell-0.3.25.dist-info → kash_shell-0.3.27.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from funlog import log_tallies
|
|
2
2
|
|
|
3
3
|
from kash.config.env_settings import KashEnv
|
|
4
|
-
from kash.config.logger import
|
|
5
|
-
from kash.config.text_styles import COLOR_ERROR
|
|
4
|
+
from kash.config.logger import get_logger
|
|
5
|
+
from kash.config.text_styles import COLOR_ERROR
|
|
6
|
+
from kash.config.unified_live import get_unified_live
|
|
6
7
|
from kash.exec.action_exec import run_action_with_shell_context
|
|
7
8
|
from kash.exec.history import record_command
|
|
8
9
|
from kash.exec_model.commands_model import Command
|
|
@@ -57,7 +58,7 @@ class ShellCallableAction:
|
|
|
57
58
|
log.info("Action shell args: %s", shell_args)
|
|
58
59
|
explicit_values = RawParamValues(shell_args.options)
|
|
59
60
|
if not action.interactive_input and not action.live_output:
|
|
60
|
-
with
|
|
61
|
+
with get_unified_live().status(f"Running action {action.name}…"):
|
|
61
62
|
result = run_action_with_shell_context(
|
|
62
63
|
action_cls,
|
|
63
64
|
explicit_values,
|
kash/help/help_embeddings.py
CHANGED
|
@@ -6,7 +6,7 @@ from pathlib import Path
|
|
|
6
6
|
from typing_extensions import override
|
|
7
7
|
|
|
8
8
|
from kash.config.logger import get_logger
|
|
9
|
-
from kash.embeddings.embeddings import Embeddings
|
|
9
|
+
from kash.embeddings.embeddings import Embeddings, EmbValue, KeyVal
|
|
10
10
|
from kash.embeddings.text_similarity import rank_by_relatedness
|
|
11
11
|
from kash.help.help_types import HelpDoc, HelpDocType
|
|
12
12
|
from kash.web_content.local_file_cache import Loadable
|
|
@@ -59,7 +59,10 @@ class HelpIndex:
|
|
|
59
59
|
from kash.web_content.file_cache_utils import cache_file
|
|
60
60
|
|
|
61
61
|
def calculate_and_save_help_embeddings(target_path: Path) -> None:
|
|
62
|
-
keyvals = [
|
|
62
|
+
keyvals = [
|
|
63
|
+
KeyVal(key=str(key), value=EmbValue(emb_text=doc.embedding_text()))
|
|
64
|
+
for key, doc in self._docs_by_key()
|
|
65
|
+
]
|
|
63
66
|
embeddings = Embeddings.embed(keyvals)
|
|
64
67
|
log.info("Embedded %d help documents, cached at: %s", len(embeddings.data), target_path)
|
|
65
68
|
embeddings.to_npz(target_path)
|
kash/mcp/mcp_server_sse.py
CHANGED
|
@@ -8,7 +8,6 @@ from typing import TYPE_CHECKING
|
|
|
8
8
|
|
|
9
9
|
from mcp.server.sse import SseServerTransport
|
|
10
10
|
from prettyfmt import fmt_path
|
|
11
|
-
from sse_starlette.sse import AppStatus
|
|
12
11
|
from starlette.applications import Starlette
|
|
13
12
|
from starlette.routing import Mount, Route
|
|
14
13
|
|
|
@@ -72,10 +71,6 @@ class MCPServerSSE:
|
|
|
72
71
|
def _setup_server(self):
|
|
73
72
|
import uvicorn
|
|
74
73
|
|
|
75
|
-
# Reset AppStatus.should_exit_event to None to ensure it's created
|
|
76
|
-
# in the correct event loop when needed.
|
|
77
|
-
AppStatus.should_exit_event = None
|
|
78
|
-
|
|
79
74
|
port = global_settings().mcp_server_port
|
|
80
75
|
|
|
81
76
|
# Check if the port is available.
|
kash/model/graph_model.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from collections.abc import Iterable
|
|
2
2
|
from dataclasses import asdict, field
|
|
3
|
+
from typing import Any
|
|
3
4
|
|
|
4
5
|
from pydantic.dataclasses import dataclass
|
|
5
6
|
from strif import abbrev_list
|
|
@@ -18,6 +19,7 @@ class Node:
|
|
|
18
19
|
body: str | None = None
|
|
19
20
|
url: str | None = None
|
|
20
21
|
thumbnail_url: str | None = None
|
|
22
|
+
data: dict[str, Any] | None = None
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
@dataclass(frozen=True)
|
kash/model/items_model.py
CHANGED
|
@@ -263,7 +263,7 @@ class Item:
|
|
|
263
263
|
thumbnail_url: Url | None = None
|
|
264
264
|
|
|
265
265
|
# Optional additional metadata.
|
|
266
|
-
extra: dict | None = None
|
|
266
|
+
extra: dict[str, Any] | None = None
|
|
267
267
|
|
|
268
268
|
# Optional execution context. Useful for letting functions that take only an Item
|
|
269
269
|
# arg get access to context.
|
|
@@ -935,8 +935,8 @@ class Item:
|
|
|
935
935
|
"type": 64,
|
|
936
936
|
"format": 64,
|
|
937
937
|
"title": 40,
|
|
938
|
-
"url":
|
|
939
|
-
"external_path":
|
|
938
|
+
"url": 128,
|
|
939
|
+
"external_path": 0,
|
|
940
940
|
},
|
|
941
941
|
)
|
|
942
942
|
+ f"[{len(self.body) if self.body else 0} body chars]"
|
|
@@ -961,7 +961,7 @@ class Item:
|
|
|
961
961
|
+ f"[{len(self.body) if self.body else 0} body chars]"
|
|
962
962
|
)
|
|
963
963
|
|
|
964
|
-
def __repr__(self):
|
|
964
|
+
def __repr__(self) -> str:
|
|
965
965
|
return self.as_str_brief()
|
|
966
966
|
|
|
967
967
|
|
|
@@ -86,8 +86,8 @@ def multitask_status(
|
|
|
86
86
|
) -> MultiTaskStatus | nullcontext:
|
|
87
87
|
"""
|
|
88
88
|
Create a `MultiTaskStatus` context manager for displaying multiple task progress
|
|
89
|
-
using the global shell console
|
|
90
|
-
to disable status display.
|
|
89
|
+
using the global shell console with live display conflict prevention. If disabled,
|
|
90
|
+
returns a null context, so it's convenient to disable status display.
|
|
91
91
|
"""
|
|
92
92
|
if not enabled:
|
|
93
93
|
return nullcontext()
|
kash/shell/shell_main.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Welcome to
|
|
2
|
+
Welcome to Kash! This command is the main way to run the kash shell.
|
|
3
3
|
|
|
4
4
|
Usually this is used to start the kash interactively but you can also pass a single
|
|
5
5
|
command to run non-interactively.
|
|
@@ -13,16 +13,16 @@ More information at: github.com/jlevy/kash
|
|
|
13
13
|
import argparse
|
|
14
14
|
import threading
|
|
15
15
|
|
|
16
|
-
import xonsh.main
|
|
17
|
-
from clideps.utils.readable_argparse import ReadableColorFormatter
|
|
18
16
|
from strif import quote_if_needed
|
|
19
17
|
|
|
18
|
+
from kash.config.logger import get_console, get_logger
|
|
20
19
|
from kash.config.setup import kash_setup
|
|
21
20
|
from kash.shell.version import get_full_version_name, get_version
|
|
22
|
-
from kash.xonsh_custom.custom_shell import install_to_xonshrc, start_shell
|
|
23
21
|
|
|
24
22
|
kash_setup(rich_logging=True) # Set up logging first.
|
|
25
23
|
|
|
24
|
+
log = get_logger(__name__)
|
|
25
|
+
|
|
26
26
|
|
|
27
27
|
__version__ = get_version()
|
|
28
28
|
|
|
@@ -35,6 +35,10 @@ def run_plain_xonsh():
|
|
|
35
35
|
xontrib only (in ~/.xonshrc), but the full customizations of prompts, tab
|
|
36
36
|
completion, etc are not available.
|
|
37
37
|
"""
|
|
38
|
+
import xonsh.main
|
|
39
|
+
|
|
40
|
+
from kash.xonsh_custom.custom_shell import install_to_xonshrc
|
|
41
|
+
|
|
38
42
|
install_to_xonshrc()
|
|
39
43
|
xonsh.main.main()
|
|
40
44
|
|
|
@@ -42,25 +46,77 @@ def run_plain_xonsh():
|
|
|
42
46
|
# Event to monitor loading.
|
|
43
47
|
shell_ready_event = threading.Event()
|
|
44
48
|
|
|
49
|
+
imports_done_event = threading.Event()
|
|
50
|
+
|
|
45
51
|
|
|
46
52
|
def run_shell(single_command: str | None = None):
|
|
47
53
|
"""
|
|
48
54
|
Run the kash shell interactively or non-interactively with a single command.
|
|
49
55
|
"""
|
|
56
|
+
from kash.xonsh_custom.custom_shell import start_shell
|
|
57
|
+
|
|
50
58
|
start_shell(single_command, shell_ready_event)
|
|
51
59
|
|
|
52
60
|
|
|
53
61
|
def build_parser() -> argparse.ArgumentParser:
|
|
62
|
+
from clideps.utils.readable_argparse import ReadableColorFormatter
|
|
63
|
+
|
|
54
64
|
parser = argparse.ArgumentParser(description=__doc__, formatter_class=ReadableColorFormatter)
|
|
55
65
|
|
|
56
|
-
|
|
66
|
+
# Don't call get_full_version_name() here, as it's slow.
|
|
67
|
+
parser.add_argument("--version", action="store_true", help="show version and exit")
|
|
57
68
|
|
|
58
69
|
return parser
|
|
59
70
|
|
|
60
71
|
|
|
72
|
+
def _import_packages():
|
|
73
|
+
try:
|
|
74
|
+
# Slowest packages:
|
|
75
|
+
import uvicorn.protocols # noqa: F401
|
|
76
|
+
import uvicorn.protocols.http.h11_impl # noqa: F401
|
|
77
|
+
import uvicorn.protocols.websockets.websockets_impl # noqa: F401
|
|
78
|
+
import xonsh.completers.init # noqa: F401
|
|
79
|
+
import xonsh.pyghooks # noqa: F401
|
|
80
|
+
|
|
81
|
+
import kash.actions # noqa: F401
|
|
82
|
+
import kash.local_server # noqa: F401
|
|
83
|
+
import kash.local_server.local_server # noqa: F401
|
|
84
|
+
import kash.mcp.mcp_server_sse # noqa: F401
|
|
85
|
+
except ImportError as e:
|
|
86
|
+
log.warning(f"Error pre-importing packages: {e}")
|
|
87
|
+
|
|
88
|
+
imports_done_event.set()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def import_with_status_if_slow(min_time: float = 1.0):
|
|
92
|
+
"""
|
|
93
|
+
Not required, but imports can be remarkably slow the first time, so this shows a status message.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
# Start imports in background thread
|
|
97
|
+
import_thread = threading.Thread(target=_import_packages, daemon=True)
|
|
98
|
+
import_thread.start()
|
|
99
|
+
|
|
100
|
+
# Wait for imports to complete, with a short timeout
|
|
101
|
+
if not imports_done_event.wait(timeout=min_time):
|
|
102
|
+
# If imports aren't done quickly, show status message
|
|
103
|
+
if get_console().is_terminal:
|
|
104
|
+
with get_console().status(
|
|
105
|
+
"Importing packages (this is a bit slow the first time) …", spinner="line"
|
|
106
|
+
):
|
|
107
|
+
import_thread.join()
|
|
108
|
+
else:
|
|
109
|
+
import_thread.join()
|
|
110
|
+
|
|
111
|
+
|
|
61
112
|
def main():
|
|
62
113
|
parser = build_parser()
|
|
63
|
-
|
|
114
|
+
|
|
115
|
+
args, unknown = parser.parse_known_args()
|
|
116
|
+
|
|
117
|
+
if args.version:
|
|
118
|
+
print(get_full_version_name(with_kits=True))
|
|
119
|
+
return
|
|
64
120
|
|
|
65
121
|
# Join remaining arguments to pass as a single command to kash.
|
|
66
122
|
# Use Python-style quoting only if needed for xonsh.
|
|
@@ -68,6 +124,8 @@ def main():
|
|
|
68
124
|
if unknown:
|
|
69
125
|
single_command = " ".join(quote_if_needed(arg) for arg in unknown)
|
|
70
126
|
|
|
127
|
+
import_with_status_if_slow()
|
|
128
|
+
|
|
71
129
|
run_shell(single_command)
|
|
72
130
|
|
|
73
131
|
|
kash/shell/version.py
CHANGED
|
@@ -28,8 +28,24 @@ def get_version_tag():
|
|
|
28
28
|
return f"v{get_version()}"
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
def get_full_version_name():
|
|
32
|
-
|
|
31
|
+
def get_full_version_name(with_kits: bool = False):
|
|
32
|
+
"""
|
|
33
|
+
Get the full version name, including the version number and any loaded kits.
|
|
34
|
+
|
|
35
|
+
If `with_kits` is True, will also import `kash.kits` packages and get those versions,
|
|
36
|
+
but be careful as this can be much slower due to imports!
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
version_items = [f"{PACKAGE_NAME} {get_version_tag()}"]
|
|
40
|
+
if with_kits:
|
|
41
|
+
from kash.actions import get_loaded_kits
|
|
42
|
+
|
|
43
|
+
kits = get_loaded_kits()
|
|
44
|
+
for kit in kits.values():
|
|
45
|
+
if kit.version:
|
|
46
|
+
version_items.append(f"{kit.distribution_name} v{kit.version}")
|
|
47
|
+
|
|
48
|
+
return ", ".join(version_items)
|
|
33
49
|
|
|
34
50
|
|
|
35
51
|
if __name__ == "__main__":
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import csv
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import NamedTuple
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CsvMetadata(NamedTuple):
|
|
9
|
+
"""
|
|
10
|
+
Result of CSV analysis, containing skip rows, metadata, and dialect.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
skip_rows: int
|
|
14
|
+
metadata: dict[str, str]
|
|
15
|
+
dialect: type[csv.Dialect]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def sniff_csv_metadata(
|
|
19
|
+
file_path: Path,
|
|
20
|
+
*,
|
|
21
|
+
max_scan_lines: int = 500,
|
|
22
|
+
threshold_ratio: float = 0.8,
|
|
23
|
+
min_columns: int = 3,
|
|
24
|
+
sample_size: int = 32768,
|
|
25
|
+
) -> CsvMetadata:
|
|
26
|
+
"""
|
|
27
|
+
Detect CSV metadata and where the table data starts by finding the first row that looks
|
|
28
|
+
like proper headers.
|
|
29
|
+
|
|
30
|
+
This function handles various CSV formats:
|
|
31
|
+
- Normal CSV files: returns skip_rows=0 (no rows to skip)
|
|
32
|
+
- Files with metadata: detects the first row with multiple columns that looks like headers
|
|
33
|
+
- Survey exports: handles key-value metadata followed by proper CSV structure
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
file_path: Path to the CSV file to analyze
|
|
37
|
+
max_scan_lines: Maximum number of lines to scan before giving up
|
|
38
|
+
threshold_ratio: Minimum ratio of max columns a row must have to be considered headers
|
|
39
|
+
min_columns: Minimum number of columns required to be considered headers
|
|
40
|
+
sample_size: Number of bytes to read for dialect detection
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
CsvMetadata with skip_rows, metadata dict, and detected dialect
|
|
44
|
+
"""
|
|
45
|
+
# Read sample for dialect detection
|
|
46
|
+
sample_text = file_path.read_text(encoding="utf-8", errors="replace")[:sample_size]
|
|
47
|
+
|
|
48
|
+
# Detect CSV dialect
|
|
49
|
+
try:
|
|
50
|
+
dialect = csv.Sniffer().sniff(sample_text)
|
|
51
|
+
except csv.Error:
|
|
52
|
+
# Fall back to default dialect if detection fails
|
|
53
|
+
dialect = csv.excel
|
|
54
|
+
|
|
55
|
+
# Analyze file structure
|
|
56
|
+
with open(file_path, encoding="utf-8", errors="replace") as file:
|
|
57
|
+
reader = csv.reader(file, dialect=dialect)
|
|
58
|
+
|
|
59
|
+
max_columns = 0
|
|
60
|
+
header_candidates = []
|
|
61
|
+
metadata = {}
|
|
62
|
+
|
|
63
|
+
for line_num, row in enumerate(reader):
|
|
64
|
+
# Stop scanning if we've looked at too many lines
|
|
65
|
+
if line_num >= max_scan_lines:
|
|
66
|
+
break
|
|
67
|
+
|
|
68
|
+
# Skip completely empty rows
|
|
69
|
+
non_empty_cells = [cell.strip() for cell in row if cell.strip()]
|
|
70
|
+
if not non_empty_cells:
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
column_count = len(non_empty_cells)
|
|
74
|
+
|
|
75
|
+
# Track the maximum number of columns seen
|
|
76
|
+
if column_count > max_columns:
|
|
77
|
+
max_columns = column_count
|
|
78
|
+
|
|
79
|
+
# Collect potential key-value metadata (exactly 2 columns)
|
|
80
|
+
# Only collect metadata before we find any header candidates with min_columns
|
|
81
|
+
if column_count == 2 and not any(hc[1] >= min_columns for hc in header_candidates):
|
|
82
|
+
key, value = non_empty_cells[0], non_empty_cells[1]
|
|
83
|
+
# Simple heuristic: if it looks like a key-value pair, store it
|
|
84
|
+
if not key.isdigit() and not value.replace(".", "").replace(",", "").isdigit():
|
|
85
|
+
metadata[key] = value
|
|
86
|
+
|
|
87
|
+
# Consider this a potential header if it has minimum required columns
|
|
88
|
+
if column_count >= min_columns:
|
|
89
|
+
header_candidates.append((line_num, column_count, row))
|
|
90
|
+
|
|
91
|
+
# If no multi-column rows found, assume it's a normal CSV starting at line 0
|
|
92
|
+
if not header_candidates:
|
|
93
|
+
return CsvMetadata(skip_rows=0, metadata=metadata, dialect=dialect)
|
|
94
|
+
|
|
95
|
+
# Look for the first row that has close to the maximum number of columns
|
|
96
|
+
# This helps distinguish metadata (usually fewer columns) from real headers (many columns)
|
|
97
|
+
threshold = max(min_columns, max_columns * threshold_ratio)
|
|
98
|
+
|
|
99
|
+
for line_num, column_count, _row in header_candidates:
|
|
100
|
+
if column_count >= threshold:
|
|
101
|
+
return CsvMetadata(skip_rows=line_num, metadata=metadata, dialect=dialect)
|
|
102
|
+
|
|
103
|
+
# If no clear header found but we have candidates, return the first multi-column row
|
|
104
|
+
first_candidate_line = header_candidates[0][0]
|
|
105
|
+
return CsvMetadata(skip_rows=first_candidate_line, metadata=metadata, dialect=dialect)
|
|
@@ -4,7 +4,7 @@ import asyncio
|
|
|
4
4
|
from contextlib import AbstractAsyncContextManager
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from types import TracebackType
|
|
7
|
-
from typing import TYPE_CHECKING, TypeVar
|
|
7
|
+
from typing import TYPE_CHECKING, Any, TypeVar
|
|
8
8
|
|
|
9
9
|
from strif import abbrev_str, single_line
|
|
10
10
|
from typing_extensions import override
|
|
@@ -17,6 +17,7 @@ from rich.progress import BarColumn, Progress, ProgressColumn, Task, TaskID
|
|
|
17
17
|
from rich.spinner import Spinner
|
|
18
18
|
from rich.text import Text
|
|
19
19
|
|
|
20
|
+
from kash.config.unified_live import get_unified_live
|
|
20
21
|
from kash.utils.api_utils.progress_protocol import (
|
|
21
22
|
EMOJI_FAILURE,
|
|
22
23
|
EMOJI_RETRY,
|
|
@@ -229,7 +230,7 @@ class TruncatedLabelColumn(ProgressColumn):
|
|
|
229
230
|
def __init__(self, console_width: int):
|
|
230
231
|
super().__init__()
|
|
231
232
|
# Reserve half the console width for labels/status messages
|
|
232
|
-
self.max_label_width = console_width // 2
|
|
233
|
+
self.max_label_width: int = console_width // 2
|
|
233
234
|
|
|
234
235
|
@override
|
|
235
236
|
def render(self, task: Task) -> Text:
|
|
@@ -298,6 +299,9 @@ class MultiTaskStatus(AbstractAsyncContextManager):
|
|
|
298
299
|
self._next_id: int = 1
|
|
299
300
|
self._rich_task_ids: dict[int, TaskID] = {} # Map our IDs to Rich Progress IDs
|
|
300
301
|
|
|
302
|
+
# Unified live integration
|
|
303
|
+
self._unified_live: Any | None = None # Reference to the global unified live
|
|
304
|
+
|
|
301
305
|
# Calculate spinner width for consistent spacing
|
|
302
306
|
self._spinner_width = _get_spinner_width(SPINNER_NAME)
|
|
303
307
|
|
|
@@ -367,7 +371,13 @@ class MultiTaskStatus(AbstractAsyncContextManager):
|
|
|
367
371
|
@override
|
|
368
372
|
async def __aenter__(self) -> MultiTaskStatus:
|
|
369
373
|
"""Start the live display."""
|
|
370
|
-
|
|
374
|
+
# Try to integrate with unified live display
|
|
375
|
+
|
|
376
|
+
# Always integrate with unified live display (auto-initialized)
|
|
377
|
+
unified_live = get_unified_live()
|
|
378
|
+
self._unified_live = unified_live
|
|
379
|
+
# Register our progress display with the unified live
|
|
380
|
+
unified_live.set_multitask_display(self._progress)
|
|
371
381
|
return self
|
|
372
382
|
|
|
373
383
|
@override
|
|
@@ -378,9 +388,13 @@ class MultiTaskStatus(AbstractAsyncContextManager):
|
|
|
378
388
|
exc_tb: TracebackType | None,
|
|
379
389
|
) -> None:
|
|
380
390
|
"""Stop the live display and show automatic summary if enabled."""
|
|
381
|
-
|
|
391
|
+
# Always clean up unified live integration
|
|
392
|
+
if self._unified_live is not None:
|
|
393
|
+
# Remove our display from the unified live
|
|
394
|
+
self._unified_live.set_multitask_display(None)
|
|
395
|
+
self._unified_live = None
|
|
382
396
|
|
|
383
|
-
# Show automatic summary if enabled
|
|
397
|
+
# Show automatic summary if enabled (always print to console now)
|
|
384
398
|
if self.auto_summary:
|
|
385
399
|
summary = self.get_summary()
|
|
386
400
|
self.console.print(summary)
|