kash-shell 0.3.21__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/docs/markdown/warning.md +3 -3
- kash/docs/markdown/welcome.md +2 -1
- kash/exec/fetch_url_items.py +6 -4
- kash/exec/preconditions.py +7 -2
- kash/model/items_model.py +14 -11
- kash/shell/output/shell_output.py +8 -4
- kash/utils/text_handling/markdown_utils.py +158 -1
- kash/web_gen/tabbed_webpage.py +2 -2
- kash/xonsh_custom/load_into_xonsh.py +0 -3
- {kash_shell-0.3.21.dist-info → kash_shell-0.3.22.dist-info}/METADATA +1 -1
- {kash_shell-0.3.21.dist-info → kash_shell-0.3.22.dist-info}/RECORD +19 -19
- {kash_shell-0.3.21.dist-info → kash_shell-0.3.22.dist-info}/WHEEL +0 -0
- {kash_shell-0.3.21.dist-info → kash_shell-0.3.22.dist-info}/entry_points.txt +0 -0
- {kash_shell-0.3.21.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/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/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/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
|
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
|
|
@@ -82,12 +82,16 @@ def console_pager(use_pager: bool = True):
|
|
|
82
82
|
|
|
83
83
|
|
|
84
84
|
def multitask_status(
|
|
85
|
-
settings: StatusSettings | None = None, *, auto_summary: bool = True
|
|
86
|
-
) -> MultiTaskStatus:
|
|
85
|
+
settings: StatusSettings | None = None, *, auto_summary: bool = True, enabled: bool = True
|
|
86
|
+
) -> MultiTaskStatus | nullcontext:
|
|
87
87
|
"""
|
|
88
88
|
Create a `MultiTaskStatus` context manager for displaying multiple task progress
|
|
89
|
-
using the global shell console.
|
|
89
|
+
using the global shell console. If disabled, returns a null context, so it's convenient
|
|
90
|
+
to disable status display.
|
|
90
91
|
"""
|
|
92
|
+
if not enabled:
|
|
93
|
+
return nullcontext()
|
|
94
|
+
|
|
91
95
|
return MultiTaskStatus(
|
|
92
96
|
console=get_console(),
|
|
93
97
|
settings=settings,
|
|
@@ -6,7 +6,7 @@ from typing import Any, TypeAlias
|
|
|
6
6
|
import marko
|
|
7
7
|
import regex
|
|
8
8
|
from marko.block import Heading, ListItem
|
|
9
|
-
from marko.inline import Link
|
|
9
|
+
from marko.inline import AutoLink, Link
|
|
10
10
|
|
|
11
11
|
from kash.utils.common.url import Url
|
|
12
12
|
|
|
@@ -64,6 +64,9 @@ def _tree_links(element, include_internal=False):
|
|
|
64
64
|
case Link():
|
|
65
65
|
if include_internal or not element.dest.startswith("#"):
|
|
66
66
|
links.append(element.dest)
|
|
67
|
+
case AutoLink():
|
|
68
|
+
if include_internal or not element.dest.startswith("#"):
|
|
69
|
+
links.append(element.dest)
|
|
67
70
|
case _:
|
|
68
71
|
if hasattr(element, "children"):
|
|
69
72
|
for child in element.children:
|
|
@@ -710,3 +713,157 @@ def test_markdown_utils_exceptions() -> None:
|
|
|
710
713
|
result_with_internal = extract_links(content, include_internal=True)
|
|
711
714
|
assert "https://example.com" in result_with_internal
|
|
712
715
|
assert "#section" in result_with_internal
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
def test_extract_links_comprehensive() -> None:
|
|
719
|
+
"""Test extract_links with various link formats including bare links and footnotes."""
|
|
720
|
+
|
|
721
|
+
# Test regular markdown links
|
|
722
|
+
regular_links = "Check out [this link](https://example.com) and [another](https://test.com)"
|
|
723
|
+
result = extract_links(regular_links)
|
|
724
|
+
assert "https://example.com" in result
|
|
725
|
+
assert "https://test.com" in result
|
|
726
|
+
assert len(result) == 2
|
|
727
|
+
|
|
728
|
+
# Test bare/autolinks in angle brackets
|
|
729
|
+
bare_links = "Visit <https://google.com> and also <https://github.com>"
|
|
730
|
+
result_bare = extract_links(bare_links)
|
|
731
|
+
assert "https://google.com" in result_bare
|
|
732
|
+
assert "https://github.com" in result_bare
|
|
733
|
+
assert len(result_bare) == 2
|
|
734
|
+
|
|
735
|
+
# Test autolinks without brackets (expected to not work with standard markdown)
|
|
736
|
+
auto_links = "Visit https://stackoverflow.com or http://reddit.com"
|
|
737
|
+
result_auto = extract_links(auto_links)
|
|
738
|
+
assert (
|
|
739
|
+
result_auto == []
|
|
740
|
+
) # Plain URLs without brackets aren't parsed as links in standard markdown
|
|
741
|
+
|
|
742
|
+
# Test GFM footnotes (the original issue)
|
|
743
|
+
footnote_content = """
|
|
744
|
+
[^109]: What Is The Future Of Ketamine Therapy For Mental Health Treatment?
|
|
745
|
+
- The Ko-Op, accessed June 28, 2025,
|
|
746
|
+
<https://psychedelictherapists.co/blog/the-future-of-ketamine-assisted-psychotherapy/>
|
|
747
|
+
"""
|
|
748
|
+
result_footnote = extract_links(footnote_content)
|
|
749
|
+
assert (
|
|
750
|
+
"https://psychedelictherapists.co/blog/the-future-of-ketamine-assisted-psychotherapy/"
|
|
751
|
+
in result_footnote
|
|
752
|
+
)
|
|
753
|
+
assert len(result_footnote) == 1
|
|
754
|
+
|
|
755
|
+
# Test mixed content with all types (excluding reference-style which has parsing conflicts with footnotes)
|
|
756
|
+
mixed_content = """
|
|
757
|
+
# Header
|
|
758
|
+
|
|
759
|
+
Regular link: [Example](https://example.com)
|
|
760
|
+
Bare link: <https://bare-link.com>
|
|
761
|
+
Auto link: https://auto-link.com
|
|
762
|
+
|
|
763
|
+
[^1]: Footnote with [regular link](https://footnote-regular.com)
|
|
764
|
+
[^2]: Footnote with bare link <https://footnote-bare.com>
|
|
765
|
+
"""
|
|
766
|
+
result_mixed = extract_links(mixed_content)
|
|
767
|
+
expected_links = [
|
|
768
|
+
"https://example.com", # Regular link
|
|
769
|
+
"https://bare-link.com", # Bare link
|
|
770
|
+
"https://footnote-regular.com", # Link in footnote
|
|
771
|
+
"https://footnote-bare.com", # Bare link in footnote
|
|
772
|
+
]
|
|
773
|
+
for link in expected_links:
|
|
774
|
+
assert link in result_mixed, f"Missing expected link: {link}"
|
|
775
|
+
# Should not include plain auto link (https://auto-link.com) as it's not in angle brackets
|
|
776
|
+
assert "https://auto-link.com" not in result_mixed
|
|
777
|
+
assert len(result_mixed) == len(expected_links)
|
|
778
|
+
|
|
779
|
+
|
|
780
|
+
def test_extract_bare_links() -> None:
|
|
781
|
+
"""Test extraction of bare links in angle brackets."""
|
|
782
|
+
content = "Visit <https://example.com> and <https://github.com/user/repo> for more info"
|
|
783
|
+
result = extract_links(content)
|
|
784
|
+
assert "https://example.com" in result
|
|
785
|
+
assert "https://github.com/user/repo" in result
|
|
786
|
+
assert len(result) == 2
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
def test_extract_footnote_links() -> None:
|
|
790
|
+
"""Test extraction of links within footnotes."""
|
|
791
|
+
content = dedent("""
|
|
792
|
+
Main text with reference[^1].
|
|
793
|
+
|
|
794
|
+
[^1]: This footnote has a [regular link](https://example.com) and <https://bare-link.com>
|
|
795
|
+
""")
|
|
796
|
+
result = extract_links(content)
|
|
797
|
+
assert "https://example.com" in result
|
|
798
|
+
assert "https://bare-link.com" in result
|
|
799
|
+
assert len(result) == 2
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
def test_extract_reference_style_links() -> None:
|
|
803
|
+
"""Test extraction of reference-style links."""
|
|
804
|
+
content = dedent("""
|
|
805
|
+
Check out [this article][ref1] and [this other one][ref2].
|
|
806
|
+
|
|
807
|
+
[ref1]: https://example.com/article1
|
|
808
|
+
[ref2]: https://example.com/article2
|
|
809
|
+
""")
|
|
810
|
+
result = extract_links(content)
|
|
811
|
+
assert "https://example.com/article1" in result
|
|
812
|
+
assert "https://example.com/article2" in result
|
|
813
|
+
assert len(result) == 2
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
def test_extract_links_with_internal_fragments() -> None:
|
|
817
|
+
"""Test that internal fragment links are excluded by default but included when requested."""
|
|
818
|
+
content = dedent("""
|
|
819
|
+
See [this section](#introduction) and [external link](https://example.com).
|
|
820
|
+
Also check [another section](#conclusion) here.
|
|
821
|
+
""")
|
|
822
|
+
|
|
823
|
+
# Default behavior: exclude internal links
|
|
824
|
+
result = extract_links(content)
|
|
825
|
+
assert "https://example.com" in result
|
|
826
|
+
assert "#introduction" not in result
|
|
827
|
+
assert "#conclusion" not in result
|
|
828
|
+
assert len(result) == 1
|
|
829
|
+
|
|
830
|
+
# Include internal links
|
|
831
|
+
result_with_internal = extract_links(content, include_internal=True)
|
|
832
|
+
assert "https://example.com" in result_with_internal
|
|
833
|
+
assert "#introduction" in result_with_internal
|
|
834
|
+
assert "#conclusion" in result_with_internal
|
|
835
|
+
assert len(result_with_internal) == 3
|
|
836
|
+
|
|
837
|
+
|
|
838
|
+
def test_extract_links_mixed_real_world() -> None:
|
|
839
|
+
"""Test with a realistic mixed document containing various link types."""
|
|
840
|
+
content = dedent("""
|
|
841
|
+
# Research Article
|
|
842
|
+
|
|
843
|
+
This study examines ketamine therapy[^109] and references multiple sources.
|
|
844
|
+
|
|
845
|
+
## Methods
|
|
846
|
+
|
|
847
|
+
We reviewed literature from [PubMed](https://pubmed.ncbi.nlm.nih.gov)
|
|
848
|
+
and other databases <https://scholar.google.com>.
|
|
849
|
+
|
|
850
|
+
For protocol details, see [our methodology][methodology].
|
|
851
|
+
|
|
852
|
+
[methodology]: https://research.example.com/protocol
|
|
853
|
+
|
|
854
|
+
[^109]: What Is The Future Of Ketamine Therapy For Mental Health Treatment?
|
|
855
|
+
- The Ko-Op, accessed June 28, 2025,
|
|
856
|
+
<https://psychedelictherapists.co/blog/the-future-of-ketamine-assisted-psychotherapy/>
|
|
857
|
+
""")
|
|
858
|
+
|
|
859
|
+
result = extract_links(content)
|
|
860
|
+
expected_links = [
|
|
861
|
+
"https://pubmed.ncbi.nlm.nih.gov",
|
|
862
|
+
"https://scholar.google.com",
|
|
863
|
+
"https://research.example.com/protocol",
|
|
864
|
+
"https://psychedelictherapists.co/blog/the-future-of-ketamine-assisted-psychotherapy/",
|
|
865
|
+
]
|
|
866
|
+
|
|
867
|
+
for link in expected_links:
|
|
868
|
+
assert link in result, f"Missing expected link: {link}"
|
|
869
|
+
assert len(result) == len(expected_links)
|
kash/web_gen/tabbed_webpage.py
CHANGED
|
@@ -82,7 +82,7 @@ def tabbed_webpage_config(
|
|
|
82
82
|
|
|
83
83
|
config_item = Item(
|
|
84
84
|
title=f"{title} (config)",
|
|
85
|
-
type=ItemType.
|
|
85
|
+
type=ItemType.data,
|
|
86
86
|
format=Format.yaml,
|
|
87
87
|
body=to_yaml_string(asdict(config)),
|
|
88
88
|
)
|
|
@@ -108,7 +108,7 @@ def tabbed_webpage_generate(
|
|
|
108
108
|
"""
|
|
109
109
|
Generate a web page using the supplied config.
|
|
110
110
|
"""
|
|
111
|
-
config = config_item.
|
|
111
|
+
config = config_item.read_as_data()
|
|
112
112
|
tabbed_webpage = as_dataclass(config, TabbedWebpage) # Checks the format.
|
|
113
113
|
|
|
114
114
|
_load_tab_content(tabbed_webpage)
|
|
@@ -15,7 +15,6 @@ from kash.config.settings import RECOMMENDED_PKGS, check_kerm_code_support
|
|
|
15
15
|
from kash.config.text_styles import LOGO_NAME, STYLE_HINT
|
|
16
16
|
from kash.mcp.mcp_server_commands import start_mcp_server
|
|
17
17
|
from kash.shell.output.shell_output import PrintHooks, cprint
|
|
18
|
-
from kash.shell.version import get_version_tag
|
|
19
18
|
from kash.workspaces import current_ws
|
|
20
19
|
from kash.xonsh_custom.customize_prompt import get_prompt_info, kash_xonsh_prompt
|
|
21
20
|
from kash.xonsh_custom.shell_load_commands import (
|
|
@@ -109,5 +108,3 @@ def load_into_xonsh():
|
|
|
109
108
|
|
|
110
109
|
else:
|
|
111
110
|
reload_shell_commands_and_actions()
|
|
112
|
-
|
|
113
|
-
log.info("kash %s loaded", get_version_tag())
|
|
@@ -4,7 +4,7 @@ kash/actions/__init__.py,sha256=a4pQw8O-Y3q5N4Qg2jUV0xEZLX6d164FQhZ6zizY9fE,1357
|
|
|
4
4
|
kash/actions/core/assistant_chat.py,sha256=28G20cSr7Z94cltouTPve5TXY3km0lACrRvpLE27fK8,1837
|
|
5
5
|
kash/actions/core/chat.py,sha256=yCannBFa0cSpR_in-XSSuMm1x2ZZQUCKmlqzhsUfpOo,2696
|
|
6
6
|
kash/actions/core/format_markdown_template.py,sha256=ZJbtyTSypPo2ewLiGRSyIpVf711vQMhI_-Ng-FgCs80,2991
|
|
7
|
-
kash/actions/core/markdownify_html.py,sha256=
|
|
7
|
+
kash/actions/core/markdownify_html.py,sha256=luMXwwaY6E8oidgdrwV6KSgzdG0iX2JKS5aTNYDdZpA,1810
|
|
8
8
|
kash/actions/core/minify_html.py,sha256=99r3SjpI2NQP7e5MnMixAiT5lxPx7t2nyJvJi6Yps6w,1365
|
|
9
9
|
kash/actions/core/readability.py,sha256=ljdB2rOpzfKU2FpEJ2UELIzcdOAWvdUjFsxoHRTE3xo,989
|
|
10
10
|
kash/actions/core/render_as_html.py,sha256=CIPGKCjUEVNsnXmpqHCUnjGwTfEfOyCXxlYFUN8mahY,1870
|
|
@@ -12,7 +12,7 @@ kash/actions/core/show_webpage.py,sha256=Ggba9jkx9U-FZOcuL0lkS-SwtPNUyxVsGdeQrqw
|
|
|
12
12
|
kash/actions/core/strip_html.py,sha256=FDLN_4CKB11q5cU4NixTf7PGrAq92AjQNbKAdvQDwCY,849
|
|
13
13
|
kash/actions/core/summarize_as_bullets.py,sha256=Zwr8lNzL77pwpnW_289LQjNBijNDpTPANfFdOJA-PZ4,2070
|
|
14
14
|
kash/actions/core/tabbed_webpage_config.py,sha256=rIbzEhBTmnkbSiRZC-Rj46T1J6c0jOztiKE9Usa4nsc,980
|
|
15
|
-
kash/actions/core/tabbed_webpage_generate.py,sha256=
|
|
15
|
+
kash/actions/core/tabbed_webpage_generate.py,sha256=935HkDSuP4eZ1e0xf-LhjPOdicU3wI5Kuh79r61QCl8,988
|
|
16
16
|
kash/actions/meta/write_instructions.py,sha256=zeKKX-Yi8jSyjvZ4Ii_4MNBRtM2MENuHyrD0Vxsaos8,1277
|
|
17
17
|
kash/actions/meta/write_new_action.py,sha256=w7SJZ2FjzRbKwqKX9PeozUrh8cNJAumX7F80wW7dQts,6356
|
|
18
18
|
kash/commands/__init__.py,sha256=MhdPSluWGE3XVQ7LSv2L8_aAxaey8CLjQBjGC9B4nRM,191
|
|
@@ -29,11 +29,11 @@ kash/commands/base/search_command.py,sha256=FXtP09HmmfHRrjenvmU4j0Lcw34piIzdI3UF
|
|
|
29
29
|
kash/commands/base/show_command.py,sha256=rFyIW_5oZArRSTpe71PaX87Hq30WE-t6RAJKeCwJ3lM,3299
|
|
30
30
|
kash/commands/extras/parse_uv_lock.py,sha256=HVqmGhJAd3ZCTTTaXe_nzzTMFKhwjLLP7hx4NTM224U,5696
|
|
31
31
|
kash/commands/extras/utils_commands.py,sha256=rz3N7VxoUHvCxksRt-sLubkvyW-VH9OTltLEMEnjRfU,769
|
|
32
|
-
kash/commands/help/assistant_commands.py,sha256=
|
|
32
|
+
kash/commands/help/assistant_commands.py,sha256=TR5HtKEoWWyK1VRfqm-SdSVHX0F3RUWrSMqAD78WXqs,3039
|
|
33
33
|
kash/commands/help/doc_commands.py,sha256=7lKR7mzue2N7pSkNqblpFJy892fS5N6jWVOHqeXziHw,2466
|
|
34
34
|
kash/commands/help/help_commands.py,sha256=eJTpIhXck123PAUq2k-D3Q6UL6IQ8atOVYurLi2GD0A,4229
|
|
35
|
-
kash/commands/help/logo.py,sha256=
|
|
36
|
-
kash/commands/help/welcome.py,sha256=
|
|
35
|
+
kash/commands/help/logo.py,sha256=4K3pPIiNlPIgdNeOgdIGbJQCKmqCEsDOhuWPH51msWo,3377
|
|
36
|
+
kash/commands/help/welcome.py,sha256=uM2UMtHG4oXTxU8IFlFbTOTmR5_XTCUtT828V_tKMsk,920
|
|
37
37
|
kash/commands/workspace/selection_commands.py,sha256=nZzA-H7Pk8kqSJVRlX7j1m6cZX-e0X8isOryDU41vqU,8156
|
|
38
38
|
kash/commands/workspace/workspace_commands.py,sha256=_2TcthGOu-nU9E_-jjf4kba9ldLRA6qe6Do6zV06EKc,21960
|
|
39
39
|
kash/config/__init__.py,sha256=ytly9Typ1mWV4CXfV9G3CIPtPQ02u2rpZ304L3GlFro,148
|
|
@@ -59,8 +59,8 @@ kash/docs/load_source_code.py,sha256=CtMBUXrRetSO81HYW-DgJZ0bL_9Nn4GNidGKg-U11LU
|
|
|
59
59
|
kash/docs/markdown/api_docs_template.md,sha256=nUjrV5FhDql8odjjPiwOK8roCUiauy31G2ARiS_OGiQ,1457
|
|
60
60
|
kash/docs/markdown/assistant_instructions_template.md,sha256=Fzeledd_nr3bKhvQ1qZfMqyNtA7-_e05J5Jx5ecerBA,4758
|
|
61
61
|
kash/docs/markdown/readme_template.md,sha256=iGx9IjSni1t_9BuYD5d2GgkxkNIkqvE3k78IufHF6Yg,409
|
|
62
|
-
kash/docs/markdown/warning.md,sha256=
|
|
63
|
-
kash/docs/markdown/welcome.md,sha256=
|
|
62
|
+
kash/docs/markdown/warning.md,sha256=ipTFWQc1F0cPGrIG0epX5RqQH6PmshyZOoCQDz0zDos,88
|
|
63
|
+
kash/docs/markdown/welcome.md,sha256=uNI6CyZOuPUe6FZgKd9MpO_pumHxlGN7Lfhb52XJRa0,611
|
|
64
64
|
kash/docs/markdown/topics/a1_what_is_kash.md,sha256=rgVrv6tRXEwdqQ54DAfHP3BSAuq8Ux4wCNeluTwpkhU,6758
|
|
65
65
|
kash/docs/markdown/topics/a2_installation.md,sha256=DSzaniHjOYPC3soGLPTGOGDVvbiPTROtb3S8zYUCPEs,5736
|
|
66
66
|
kash/docs/markdown/topics/a3_getting_started.md,sha256=xOMevEXMIpVJvTGuuwI9Cc9sun3tQM3OqCgynSgMpeM,9376
|
|
@@ -88,13 +88,13 @@ kash/exec/action_registry.py,sha256=numU9pH_W5RgIrYmfi0iYMYy_kLJl6vup8PMrhxAfdc,
|
|
|
88
88
|
kash/exec/combiners.py,sha256=AJ6wgPUHsmwanObsUw64B83XzU26yuh5t4l7igLn82I,4291
|
|
89
89
|
kash/exec/command_exec.py,sha256=zc-gWm7kyB5J5Kp8xhULQ9Jj9AL927KkDPXXk-Yr1Bw,1292
|
|
90
90
|
kash/exec/command_registry.py,sha256=1s2ogU8b8nqK_AEtslbr1eYrXCGDkeT30UrB7L0BRoM,2027
|
|
91
|
-
kash/exec/fetch_url_items.py,sha256=
|
|
91
|
+
kash/exec/fetch_url_items.py,sha256=egTnrYaPHWRIXwO-Mgaxh1GINPtoy4VQXJlzGgQBj6Y,4441
|
|
92
92
|
kash/exec/history.py,sha256=l2XwHGBR1UgTGSFPSBE9mltmxvjR_5qFFO6d-Z008nc,1208
|
|
93
93
|
kash/exec/importing.py,sha256=xunmBapeUMNc6Zox7y6e_DZkidyWeouiFZpphajwSzc,1843
|
|
94
94
|
kash/exec/llm_transforms.py,sha256=n7S-Dew8z_BoAwp-14lY4LueFeUtG117eK_8msbn02c,4375
|
|
95
95
|
kash/exec/precondition_checks.py,sha256=HymxL7qm4Yz8V76Um5pKdIRnQ2N-p9rpQQi1fI38bNA,2139
|
|
96
96
|
kash/exec/precondition_registry.py,sha256=9O-01d2OrsYLuhqodVuWjouLR2ABJbUotAmfJNBCRzs,1439
|
|
97
|
-
kash/exec/preconditions.py,sha256=
|
|
97
|
+
kash/exec/preconditions.py,sha256=fuK_tfI7FiJ5F36MXOgopFtD5O_gf8yLvQ-xjowNKSw,5221
|
|
98
98
|
kash/exec/resolve_args.py,sha256=gu65epzVrwWHdHA-KwAwYssncJIB84oHOJeoXXrQ2mM,4428
|
|
99
99
|
kash/exec/runtime_settings.py,sha256=aK6nGbZhKSIDVmV6AqV68hQkiaIGWnCiNzHtwwZ5V0w,3960
|
|
100
100
|
kash/exec/shell_callable_action.py,sha256=iSv3w33oJbIenOI6s9v5cHU4gX2U9w7MIWc8jHgk6HE,4377
|
|
@@ -164,7 +164,7 @@ kash/model/compound_actions_model.py,sha256=HiDK5wwCu3WwZYHATZoLEguiqwR9V6V296wi
|
|
|
164
164
|
kash/model/concept_model.py,sha256=we2qOcy9Mv1q7XPfkDLp_CyO_-8DwAUfUYlpgy_jrFs,1011
|
|
165
165
|
kash/model/exec_model.py,sha256=IlfvtQyoFRRWhWju7vdXp9J-w_NGcGtL5DhDLy9gRd8,2250
|
|
166
166
|
kash/model/graph_model.py,sha256=jnctrPiBZ0xwAR8D54JMAJPanA1yZdaxSFQoIpe8anA,2662
|
|
167
|
-
kash/model/items_model.py,sha256=
|
|
167
|
+
kash/model/items_model.py,sha256=d95duvrAvi7LjMQF6urUuwGiizo1K1QCnRfyAa7tcbA,35827
|
|
168
168
|
kash/model/language_list.py,sha256=I3RIbxTseVmPdhExQimimEv18Gmy2ImMbpXe0-_t1Qw,450
|
|
169
169
|
kash/model/llm_actions_model.py,sha256=a29uXVNfS2CiqvM7HPdC6H9A23rSQQihAideuBLMH8g,2110
|
|
170
170
|
kash/model/media_model.py,sha256=ZnlZ-FkswbAIGpUAuNqLce1WDZK-WbnwHn2ipg8x7-0,3511
|
|
@@ -190,7 +190,7 @@ kash/shell/output/kerm_code_utils.py,sha256=92A4AV-IFKKZMWLNZnd_zksNFMBgE_VNXySy
|
|
|
190
190
|
kash/shell/output/kerm_codes.py,sha256=KLVdTM_dL_NeYmGbllzsQoW4IHXJjEsgqqIp1s7P1yI,18877
|
|
191
191
|
kash/shell/output/kmarkdown.py,sha256=RRB5b0Ip0KZ71vnJKFfvxerYkeDFTCVTlHqHfmMy80Y,3675
|
|
192
192
|
kash/shell/output/shell_formatting.py,sha256=oxmAeJ2j0ANYSUsL15CUv--KcGlQ6Wa_rywXSDlsZM4,3331
|
|
193
|
-
kash/shell/output/shell_output.py,sha256=
|
|
193
|
+
kash/shell/output/shell_output.py,sha256=A4XcEo2P3CVVOuJHFHut3Wd4I2DBnim-NiYEsdokb9s,11831
|
|
194
194
|
kash/shell/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
195
195
|
kash/shell/ui/shell_results.py,sha256=mvFHxK_oz3bNfF5_Twt6VqDO44TA1b256Bjf5oco804,4130
|
|
196
196
|
kash/shell/ui/shell_syntax.py,sha256=1fuDqcCV16AAWwWS4w4iT-tlSnl-Ywdrf68Ge8XIfmQ,751
|
|
@@ -242,7 +242,7 @@ kash/utils/rich_custom/rich_markdown_fork.py,sha256=M_JRaSAyHrSg-wuLv9C9P7SkehSi
|
|
|
242
242
|
kash/utils/text_handling/doc_normalization.py,sha256=C211eSof8PUDVCqQtShuC4AMJpTZeBK8GHlGATp3c9E,2976
|
|
243
243
|
kash/utils/text_handling/escape_html_tags.py,sha256=8pC3JgoKRtdnbnOu8DiWrlvNR6GAqjwhGbQgl3jiFG4,6441
|
|
244
244
|
kash/utils/text_handling/markdown_render.py,sha256=LHPdJc__2ejBx7iwkp_P9wIePNmiVSgwu4-uhamVjms,3791
|
|
245
|
-
kash/utils/text_handling/markdown_utils.py,sha256=
|
|
245
|
+
kash/utils/text_handling/markdown_utils.py,sha256=4RGIK9weadZ1UYTk7eVmCIFX9yktMyLSFsMLNTCnmoY,31681
|
|
246
246
|
kash/utils/text_handling/markdownify_utils.py,sha256=xNMlBX36BJ5VK5kxY2Ofo-Q84R2kBSM91u1grkQ-5As,2925
|
|
247
247
|
kash/utils/text_handling/unified_diffs.py,sha256=JfHSakISkT_GuBPBI4fTooHrp2aenWzDKiVvDewVfMk,2655
|
|
248
248
|
kash/web_content/canon_url.py,sha256=Zv2q7xQdIHBFkxxwyJn3_ME-qqMFRi_fKxE_IgV2Z50,742
|
|
@@ -257,7 +257,7 @@ kash/web_content/web_fetch.py,sha256=J8DLFP1vzp7aScanFq0Bd7xCP6AVL4JgMMBqyRPtZjQ
|
|
|
257
257
|
kash/web_content/web_page_model.py,sha256=aPpgC1fH2z2LTzGJhEDvZgq_mYwgsQIZaDS3UE7v98w,1147
|
|
258
258
|
kash/web_gen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
259
259
|
kash/web_gen/simple_webpage.py,sha256=ks_0ljxCeS2-gAAEaUc1JEnzY3JY0nzqGFiyyqyRuZs,1537
|
|
260
|
-
kash/web_gen/tabbed_webpage.py,sha256=
|
|
260
|
+
kash/web_gen/tabbed_webpage.py,sha256=e0GGG1bQ4CQegpJgOIT2KpyM6cmwN_BQ9eJSsi4BjgY,4945
|
|
261
261
|
kash/web_gen/template_render.py,sha256=aypo6UanouftV4RpxgNm6JdquelI52fV0IlihdA3yjE,1908
|
|
262
262
|
kash/web_gen/templates/base_styles.css.jinja,sha256=kLFEntQ7SHkbG9BEJTLtsiJCP5ldflILMXHAZGKjaF0,14942
|
|
263
263
|
kash/web_gen/templates/base_webpage.html.jinja,sha256=gwxXMSC_eY-stu8uT5AVyQJ-Ppy2GyWwq9vEdTEbVi0,12887
|
|
@@ -281,7 +281,7 @@ kash/workspaces/workspaces.py,sha256=ypxlX2K64QOhNsta69arlvKbiVpUW_4CDeX_sEcIQbg
|
|
|
281
281
|
kash/xonsh_custom/command_nl_utils.py,sha256=6Xcx98HXL5HywziHi0XskwFx6kfvz7oCmMX-siJib1A,3392
|
|
282
282
|
kash/xonsh_custom/custom_shell.py,sha256=jydWwHR0-RslioaHHY1sHnM6H6fG3cwOw1XO__sG_b0,19026
|
|
283
283
|
kash/xonsh_custom/customize_prompt.py,sha256=u-jRY-ftXYflBkbJVetEg6GYelPUvqZEpjrPRUTfy8w,6896
|
|
284
|
-
kash/xonsh_custom/load_into_xonsh.py,sha256=
|
|
284
|
+
kash/xonsh_custom/load_into_xonsh.py,sha256=Z_PAaW_Uv-PDRKdw9E_7dC1eMe6FbwwIUioxyS1Sp_g,3596
|
|
285
285
|
kash/xonsh_custom/shell_load_commands.py,sha256=bndmVEecIhuzQBI3OUDpH4ac7DXGkAMUVqXXcbrmEqI,4719
|
|
286
286
|
kash/xonsh_custom/shell_which.py,sha256=zRaLhFVg9HEDFXo4dqWKwX07YpfPh25kopeG_fgctOE,1926
|
|
287
287
|
kash/xonsh_custom/xonsh_completers.py,sha256=jxsc279DwZUHm0-iCnCtvbzqJBtq8zu0FUls5J1BA8o,17959
|
|
@@ -291,8 +291,8 @@ kash/xonsh_custom/xonsh_modern_tools.py,sha256=mj_b34LZXfE8MJe9EpDmp5JZ0tDM1biYN
|
|
|
291
291
|
kash/xonsh_custom/xonsh_ranking_completer.py,sha256=ZRGiAfoEgqgnlq2-ReUVEaX5oOgW1DQ9WxIv2OJLuTo,5620
|
|
292
292
|
kash/xontrib/fnm.py,sha256=V2tsOdmIDgbFbZSfMLpsvDIwwJJqiYnOkOySD1cXNXw,3700
|
|
293
293
|
kash/xontrib/kash_extension.py,sha256=FLIMlgR3C_6A1fwKE-Ul0nmmpJSszVPbAriinUyQ8Zg,1896
|
|
294
|
-
kash_shell-0.3.
|
|
295
|
-
kash_shell-0.3.
|
|
296
|
-
kash_shell-0.3.
|
|
297
|
-
kash_shell-0.3.
|
|
298
|
-
kash_shell-0.3.
|
|
294
|
+
kash_shell-0.3.22.dist-info/METADATA,sha256=IySaDz82xNb4v9ukcdw_kzOGEJxNWUBG4_fltjOfB90,32656
|
|
295
|
+
kash_shell-0.3.22.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
296
|
+
kash_shell-0.3.22.dist-info/entry_points.txt,sha256=SQraWDAo8SqYpthLXThei0mf_hGGyhYBUO-Er_0HcwI,85
|
|
297
|
+
kash_shell-0.3.22.dist-info/licenses/LICENSE,sha256=rCh2PsfYeiU6FK_0wb58kHGm_Fj5c43fdcHEexiVzIo,34562
|
|
298
|
+
kash_shell-0.3.22.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|