kash-shell 0.3.13__py3-none-any.whl → 0.3.15__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/model/items_model.py CHANGED
@@ -181,7 +181,7 @@ class ItemId:
181
181
  item_id = ItemId(item.type, IdType.url, canonicalize_url(item.url))
182
182
  elif item.type == ItemType.concept and item.title:
183
183
  item_id = ItemId(item.type, IdType.concept, canonicalize_concept(item.title))
184
- elif item.source and item.source.cacheable:
184
+ elif item.source and item.source.cacheable and item.source.operation.has_known_inputs:
185
185
  # We know the source of this and if the action was cacheable, we can create
186
186
  # an identity based on the source.
187
187
  item_id = ItemId(item.type, IdType.source, item.source.as_str())
@@ -363,21 +363,24 @@ class Item:
363
363
  *,
364
364
  title: str | None = None,
365
365
  original_filename: str | None = None,
366
+ url: Url | None = None,
366
367
  mime_type: MimeType | None = None,
367
368
  ) -> Item:
368
369
  """
369
370
  Create a resource Item for a file with a format inferred from the file extension
370
371
  or the content. Only sets basic metadata. Does not read the content. Will set
371
372
  `format` and `file_ext` if possible but will leave them as None if unrecognized.
372
- If `mime_type` is provided, it can help determine the file extension.
373
+ If `mime_type` is provided, it can help determine the file extension if the
374
+ extension isn't recognized from the filename or URL.
373
375
  """
374
376
  from kash.file_storage.store_filenames import parse_item_filename
375
- from kash.utils.file_utils.file_formats_model import choose_file_ext, detect_file_format
377
+ from kash.utils.file_utils.file_formats_model import file_format_info
376
378
 
377
379
  # Will raise error for unrecognized file ext.
378
380
  _name, filename_item_type, format, file_ext = parse_item_filename(path)
381
+ format_info = file_format_info(path, suggested_mime_type=mime_type)
379
382
  if not format:
380
- format = detect_file_format(path)
383
+ format = format_info.format
381
384
  if not item_type and filename_item_type:
382
385
  item_type = filename_item_type
383
386
  if not item_type:
@@ -385,9 +388,10 @@ class Item:
385
388
  item_type = (
386
389
  ItemType.doc if format and format.supports_frontmatter else ItemType.resource
387
390
  )
388
- # Do our best to determine a good file extension if it's not already on the filename.
389
- if not file_ext and mime_type:
390
- file_ext = choose_file_ext(path, mime_type)
391
+
392
+ # Try to determine a good file extension if it's not already on the filename.
393
+ if not file_ext:
394
+ file_ext = format_info.suggested_file_ext
391
395
 
392
396
  item = cls(
393
397
  type=item_type,
@@ -396,6 +400,7 @@ class Item:
396
400
  format=format,
397
401
  external_path=str(path),
398
402
  original_filename=original_filename,
403
+ url=url,
399
404
  )
400
405
 
401
406
  # Update modified time from the file system.
@@ -623,7 +628,7 @@ class Item:
623
628
  if not display_title and self.store_path:
624
629
  display_title = Path(self.store_path).name
625
630
  if not display_title:
626
- display_title = self.abbrev_title()
631
+ display_title = self.abbrev_title(pull_body_heading=True)
627
632
  return display_title
628
633
 
629
634
  def abbrev_description(self, max_len: int = 1000) -> str:
@@ -66,6 +66,13 @@ class Input:
66
66
  else:
67
67
  return "[input info missing]"
68
68
 
69
+ @property
70
+ def is_known(self) -> bool:
71
+ """
72
+ Whether the input is known, i.e. we had saved inputs with hashes.
73
+ """
74
+ return bool(self.path and self.hash)
75
+
69
76
  # Inputs are equal if the hashes match (even if the paths have changed).
70
77
 
71
78
  def __hash__(self):
@@ -117,6 +124,13 @@ class Operation:
117
124
 
118
125
  return d
119
126
 
127
+ @property
128
+ def has_known_inputs(self) -> bool:
129
+ """
130
+ Whether the operation has known inputs, i.e. all inputs have hashes.
131
+ """
132
+ return all(arg.is_known for arg in self.arguments)
133
+
120
134
  def summary(self) -> OperationSummary:
121
135
  return OperationSummary(self.action_name)
122
136
 
@@ -23,7 +23,7 @@ from kash.shell.output.shell_output import cprint
23
23
  from kash.utils.common.format_utils import fmt_loc
24
24
  from kash.utils.common.url import as_file_url, is_file_url, is_url
25
25
  from kash.utils.errors import FileNotFound, SetupError
26
- from kash.utils.file_utils.file_formats import is_full_html_page, read_partial_text
26
+ from kash.utils.file_utils.file_formats import is_fullpage_html, read_partial_text
27
27
  from kash.utils.file_utils.file_formats_model import file_format_info
28
28
 
29
29
  log = get_logger(__name__)
@@ -88,7 +88,7 @@ def _detect_view_mode(file_or_url: str) -> ViewMode:
88
88
  path = Path(file_or_url)
89
89
  if path.is_file(): # File or symlink.
90
90
  content = read_partial_text(path)
91
- if content and is_full_html_page(content):
91
+ if content and is_fullpage_html(content):
92
92
  return ViewMode.browser
93
93
 
94
94
  info = file_format_info(path)
kash/utils/common/url.py CHANGED
@@ -47,7 +47,9 @@ def check_if_url(
47
47
  if only_schemes:
48
48
  return result if result.scheme in only_schemes else None
49
49
  else:
50
- return result if result.scheme != "" else None
50
+ # Consider it a URL if the scheme is present and longer than a single character.
51
+ # This helps avoid misinterpreting Windows drive letters (e.g., "C:\foo") as schemes.
52
+ return result if result.scheme and len(result.scheme) > 1 else None
51
53
  except ValueError:
52
54
  return None
53
55
 
@@ -145,6 +147,41 @@ def normalize_url(
145
147
  return Url(normalized_url)
146
148
 
147
149
 
150
+ def is_valid_path(text: UnresolvedLocator) -> bool:
151
+ """
152
+ Sanity check if the input is plausibly a file path, i.e. not a URL or malformed in
153
+ an obvious way. Does not check for existence or OS-specific naming restrictions.
154
+ For a more thorough check there are other more complex options like:
155
+ https://github.com/thombashi/pathvalidate
156
+ """
157
+ if isinstance(text, Path):
158
+ return True
159
+ elif isinstance(text, str):
160
+ path_str = text
161
+ else:
162
+ return False
163
+
164
+ # Check for empty or whitespace-only strings or null characters
165
+ # (never acceptable paths).
166
+ if not path_str or path_str.isspace():
167
+ return False
168
+ if "\0" in path_str:
169
+ return False
170
+
171
+ # Explicitly disallow URLs.
172
+ if is_url(path_str):
173
+ return False
174
+
175
+ # As a final lightweight check, ensure it can be instantiated as a Path object
176
+ # This doesn't validate existence or character restrictions.
177
+ try:
178
+ _ = Path(path_str)
179
+ except (TypeError, ValueError):
180
+ return False
181
+
182
+ return True
183
+
184
+
148
185
  ## Tests
149
186
 
150
187
 
@@ -155,13 +192,19 @@ def test_is_url():
155
192
  assert is_url("ftp://example.com") == True
156
193
  assert is_url("file:///path/to/file") == True
157
194
  assert is_url("file://hostname/path/to/file") == True
158
- assert is_url("invalid-url") == False
159
- assert is_url("www.example.com") == False
160
195
  assert is_url("http://example.com", only_schemes=HTTP_ONLY) == True
161
196
  assert is_url("https://example.com", only_schemes=HTTP_ONLY) == True
197
+
198
+ assert is_url("invalid-url") == False
199
+ assert is_url("www.example.com") == False
162
200
  assert is_url("ftp://example.com", only_schemes=HTTP_ONLY) == False
163
201
  assert is_url("file:///path/to/file", only_schemes=HTTP_ONLY) == False
164
202
 
203
+ assert is_url("www.example.com") is False
204
+ assert is_url("c:\\path\\to\\file") is False
205
+ assert is_url("/foo/bar") is False
206
+ assert is_url("//foo") is False
207
+
165
208
 
166
209
  def test_as_file_url():
167
210
  assert as_file_url("file:///path/to/file") == "file:///path/to/file"
@@ -205,3 +248,37 @@ def test_normalize_url():
205
248
  str(e)
206
249
  == "Scheme 'ftp' not in allowed schemes: ['http', 'https', 'file']: ftp://example.com"
207
250
  )
251
+
252
+
253
+ def test_is_path():
254
+ assert is_valid_path("foo/bar") is True
255
+ assert is_valid_path("/foo/bar") is True
256
+ assert is_valid_path("./foo/bar") is True
257
+ assert is_valid_path("../foo/bar") is True
258
+ assert is_valid_path("foo.txt") is True
259
+ assert is_valid_path(Path("foo/bar")) is True
260
+ assert is_valid_path(Path()) is True
261
+ assert is_valid_path(".") is True
262
+ assert is_valid_path("..") is True
263
+ assert is_valid_path("C:\\Users\\name") is True # Windows-style
264
+ assert is_valid_path("file_with:colon.txt") is True # Valid on POSIX
265
+ assert is_valid_path(Url("relative/path")) is True # Url type with relative content
266
+
267
+ assert is_valid_path("http://example.com") is False
268
+ assert is_valid_path("https://example.com/path") is False
269
+ assert is_valid_path("file:///path/to/file") is False
270
+ assert is_valid_path(Url("http://example.com")) is False
271
+ assert is_valid_path("") is False
272
+ assert is_valid_path(" ") is False
273
+ assert is_valid_path("foo\0bar.txt") is False
274
+ assert is_valid_path(None) is False # pyright: ignore
275
+ assert is_valid_path(123) is False # pyright: ignore
276
+
277
+ # Edge cases
278
+ assert is_valid_path("www.example.com") is True # No scheme
279
+ assert str(Path("")) == "."
280
+ assert str(Path(" ")) == " "
281
+ assert is_valid_path(Path(" ")) is True # A bad idea but allowed
282
+ assert is_valid_path(Path("")) is True
283
+ assert is_valid_path(" ") is False
284
+ assert is_valid_path("") is False
@@ -11,9 +11,10 @@ from kash.config.logger import get_logger
11
11
  log = get_logger(__name__)
12
12
 
13
13
 
14
- def is_full_html_page(content: str) -> bool:
14
+ def is_fullpage_html(content: str) -> bool:
15
15
  """
16
- A full HTML document that is probably best rendered in a browser.
16
+ A full HTML document that is a full page (headers, footers, etc.) and
17
+ so probably best rendered in a browser.
17
18
  """
18
19
  return bool(re.search(r"<!DOCTYPE html>|<html>|<body>|<head>", content[:2048], re.IGNORECASE))
19
20
 
@@ -4,7 +4,7 @@ from dataclasses import dataclass
4
4
  from enum import Enum
5
5
  from pathlib import Path
6
6
 
7
- from kash.utils.common.url import Url, is_file_url, is_url, parse_file_url
7
+ from kash.utils.common.url import is_valid_path
8
8
  from kash.utils.file_utils.file_ext import FileExt
9
9
  from kash.utils.file_utils.file_formats import (
10
10
  MIME_EMPTY,
@@ -143,11 +143,23 @@ class Format(Enum):
143
143
 
144
144
  @property
145
145
  def is_markdown(self) -> bool:
146
- return self in [self.markdown, self.md_html]
146
+ """Is this pure Markdown? Does not include Markdown mixed with HTML."""
147
+ return self in [self.markdown]
148
+
149
+ @property
150
+ def is_markdown_with_html(self) -> bool:
151
+ """Is this Markdown mixed with HTML?"""
152
+ return self in [self.md_html]
147
153
 
148
154
  @property
149
155
  def is_html(self) -> bool:
150
- return self in [self.html, self.md_html]
156
+ """Is this format HTML? Does not include Markdown mixed with HTML."""
157
+ return self in [self.html]
158
+
159
+ @property
160
+ def is_html_compatible(self) -> bool:
161
+ """Is this format directly compatible with HTML (any combination of text, markdown, or HTML)?"""
162
+ return self in [self.plaintext, self.markdown, self.md_html, self.html]
151
163
 
152
164
  @property
153
165
  def is_data(self) -> bool:
@@ -406,15 +418,6 @@ class FileFormatInfo:
406
418
  return self.as_str()
407
419
 
408
420
 
409
- def _guess_format(file_ext: FileExt | None, mime_type: MimeType | None) -> Format | None:
410
- format = None
411
- if file_ext:
412
- format = Format.guess_by_file_ext(file_ext)
413
- if not format and mime_type:
414
- format = Format.from_mime_type(mime_type)
415
- return format
416
-
417
-
418
421
  def guess_format_by_name(path: str | Path) -> Format | None:
419
422
  """
420
423
  Fast guess of file format by the file name only.
@@ -423,22 +426,39 @@ def guess_format_by_name(path: str | Path) -> Format | None:
423
426
  return Format.guess_by_file_ext(file_ext) if file_ext else None
424
427
 
425
428
 
426
- def file_format_info(path: str | Path, always_check_content: bool = False) -> FileFormatInfo:
429
+ def file_format_info(
430
+ path: str | Path,
431
+ suggested_mime_type: MimeType | None = None,
432
+ ) -> FileFormatInfo:
427
433
  """
428
434
  Get info on the file format path and content (file extension and file content).
429
435
  Looks at the file extension first and then the file content if needed.
430
- If `always_check_content` is True, look at the file content even if we
431
- recognize the file extension.
436
+ If `suggested_mime_type` is provided, it will be used as the detected mime type
437
+ instead of detecting it from the file content.
432
438
  """
439
+ if not is_valid_path(path):
440
+ raise ValueError(f"Expected a file path but got: {path!r}")
441
+
433
442
  path = Path(path)
434
443
  file_ext = parse_file_ext(path)
435
- if always_check_content or not file_ext:
444
+ if not suggested_mime_type and not file_ext:
436
445
  # Look at the file content.
437
446
  detected_mime_type = detect_mime_type(path)
447
+ elif suggested_mime_type:
448
+ detected_mime_type = suggested_mime_type
438
449
  else:
439
450
  detected_mime_type = None
440
- format = _guess_format(file_ext, detected_mime_type)
451
+
452
+ # Pick format first by file extension, then by detected mime type.
453
+ format = None
454
+ if file_ext:
455
+ format = Format.guess_by_file_ext(file_ext)
456
+ if not format and detected_mime_type:
457
+ format = Format.from_mime_type(detected_mime_type)
458
+
459
+ # Attempt to canonicalize the mime type to match the format.
441
460
  final_mime_type = format.mime_type if format else detected_mime_type
461
+
442
462
  return FileFormatInfo(file_ext, format, final_mime_type)
443
463
 
444
464
 
@@ -456,35 +476,3 @@ def detect_media_type(filename: str | Path) -> MediaType:
456
476
  fmt = detect_file_format(filename)
457
477
  media_type = fmt.media_type if fmt else MediaType.binary
458
478
  return media_type
459
-
460
-
461
- def choose_file_ext(
462
- url_or_path: Url | Path | str, mime_type: MimeType | None = None
463
- ) -> FileExt | None:
464
- """
465
- Pick a file extension to reflect the type of the content. First tries from any
466
- provided content type (e.g. if this item was just downloaded). Then
467
- recognizes known file extensions on the filename or URL, then tries looking
468
- at the content with libmagic and heuristics, then gives up.
469
- """
470
- if mime_type:
471
- fmt = Format.from_mime_type(mime_type)
472
- if fmt:
473
- return fmt.file_ext
474
-
475
- # First check if it's a known standard extension.
476
- filename_ext = parse_file_ext(url_or_path)
477
- if filename_ext:
478
- return filename_ext
479
-
480
- local_path = None
481
- if isinstance(url_or_path, str) and is_file_url(url_or_path):
482
- local_path = parse_file_url(url_or_path)
483
- elif not is_url(url_or_path):
484
- local_path = Path(url_or_path)
485
-
486
- # If it's local based the extension on the file content.
487
- if local_path:
488
- return file_format_info(local_path).suggested_file_ext
489
-
490
- return None
@@ -9,6 +9,13 @@ from kash.utils.file_utils.file_formats_model import Format, detect_file_format
9
9
  from kash.utils.rich_custom.ansi_cell_len import ansi_cell_len
10
10
 
11
11
 
12
+ def can_normalize(format: Format) -> bool:
13
+ """
14
+ True for Markdown (the only format we currently normalize).
15
+ """
16
+ return format == Format.markdown or format == Format.md_html
17
+
18
+
12
19
  def normalize_formatting(
13
20
  text: str,
14
21
  format: Format | None,
@@ -10,10 +10,17 @@ from funlog import log_if_modifies
10
10
  from prettyfmt import fmt_path
11
11
  from strif import atomic_output_file, copyfile_atomic
12
12
 
13
- from kash.utils.common.url import Url, is_file_url, is_url, normalize_url, parse_file_url
13
+ from kash.utils.common.url import (
14
+ Url,
15
+ is_file_url,
16
+ is_url,
17
+ is_valid_path,
18
+ normalize_url,
19
+ parse_file_url,
20
+ )
14
21
  from kash.utils.errors import FileNotFound
15
- from kash.utils.file_utils.file_formats import MimeType
16
- from kash.utils.file_utils.file_formats_model import choose_file_ext
22
+ from kash.utils.file_utils.file_formats_model import file_format_info
23
+ from kash.utils.file_utils.filename_parsing import parse_file_ext
17
24
  from kash.web_content.dir_store import DirStore
18
25
  from kash.web_content.web_fetch import HttpHeaders, download_url
19
26
 
@@ -91,9 +98,25 @@ class CacheResult:
91
98
  was_cached: bool
92
99
 
93
100
 
94
- def _suffix_for(cacheable: Cacheable, mime_type: MimeType | None = None) -> str | None:
101
+ def _suffix_for(cacheable: Cacheable) -> str | None:
95
102
  key = cacheable.key if isinstance(cacheable, Loadable) else cacheable
96
- file_ext = choose_file_ext(key, mime_type)
103
+
104
+ # Check for recognized file extensions on URLs and Paths.
105
+ filename_ext = parse_file_ext(str(key))
106
+ if filename_ext:
107
+ return filename_ext.dot_ext
108
+
109
+ # Handle local paths
110
+ if is_file_url(str(key)):
111
+ path = parse_file_url(str(key))
112
+ elif is_valid_path(str(key)):
113
+ path = Path(str(key))
114
+ else:
115
+ # A non-local path with no recognized extension.
116
+ return None
117
+
118
+ # If it's a local file, check the file content too.
119
+ file_ext = file_format_info(path).suggested_file_ext
97
120
  return file_ext.dot_ext if file_ext else None
98
121
 
99
122
 
@@ -121,13 +121,17 @@ h1 {
121
121
 
122
122
  h2 {
123
123
  font-size: 1.4rem;
124
- margin-top: 1.5rem;
124
+ margin-top: 2rem;
125
125
  margin-bottom: 1rem;
126
126
  }
127
127
 
128
+ h1 + h2 {
129
+ margin-top: 2rem;
130
+ }
131
+
128
132
  h3 {
129
133
  font-size: 1.03rem;
130
- margin-top: 1.5rem;
134
+ margin-top: 1.6rem;
131
135
  margin-bottom: 0.5rem;
132
136
  }
133
137
 
@@ -340,7 +344,7 @@ tbody tr:nth-child(even) {
340
344
  left: 50%;
341
345
  transform: translateX(-50%);
342
346
  box-sizing: border-box;
343
- margin-bottom: 1rem;
347
+ margin: 2rem 0;
344
348
  background-color: var(--color-bg-solid);
345
349
  }
346
350
  {% endblock table_styles %}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kash-shell
3
- Version: 0.3.13
3
+ Version: 0.3.15
4
4
  Summary: The knowledge agent shell (core)
5
5
  Project-URL: Repository, https://github.com/jlevy/kash-shell
6
6
  Author-email: Joshua Levy <joshua@cal.berkeley.edu>
@@ -4,10 +4,10 @@ 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.py,sha256=sCfPXQCylQjrVbZxeRR11kZ8CtFw-tMsUSrRT6gimVU,1190
8
- kash/actions/core/readability.py,sha256=C71mHruXDoTn0Y6Q5v5FnUfUn1Q4WVuWQ5PbOGpZEuk,985
9
- kash/actions/core/render_as_html.py,sha256=JdrVvMo3DC1xDQ1SXrTDCLzm3S-Z9V6SSrjG-_z5Stw,1826
10
- kash/actions/core/show_webpage.py,sha256=052HG80kSBP98qITp0gfKJ0G4PxGGS0o0N-L64JxWP0,889
7
+ kash/actions/core/markdownify.py,sha256=KjdUeY4c9EhZ5geQrn22IoBv0P_p62q4zyyOYE0NRHM,1270
8
+ kash/actions/core/readability.py,sha256=ljdB2rOpzfKU2FpEJ2UELIzcdOAWvdUjFsxoHRTE3xo,989
9
+ kash/actions/core/render_as_html.py,sha256=bSyZdX9nZnP33QBdGSzWhInRREWXWayMG2oyiKn4rxw,1824
10
+ kash/actions/core/show_webpage.py,sha256=Ggba9jkx9U-FZOcuL0lkS-SwtPNUyxVsGdeQrqwWs1s,887
11
11
  kash/actions/core/strip_html.py,sha256=FDLN_4CKB11q5cU4NixTf7PGrAq92AjQNbKAdvQDwCY,849
12
12
  kash/actions/core/summarize_as_bullets.py,sha256=Zwr8lNzL77pwpnW_289LQjNBijNDpTPANfFdOJA-PZ4,2070
13
13
  kash/actions/core/tabbed_webpage_config.py,sha256=rIbzEhBTmnkbSiRZC-Rj46T1J6c0jOztiKE9Usa4nsc,980
@@ -33,8 +33,8 @@ kash/commands/help/doc_commands.py,sha256=7lKR7mzue2N7pSkNqblpFJy892fS5N6jWVOHqe
33
33
  kash/commands/help/help_commands.py,sha256=eJTpIhXck123PAUq2k-D3Q6UL6IQ8atOVYurLi2GD0A,4229
34
34
  kash/commands/help/logo.py,sha256=W8SUach9FjoTqpHZwTGS582ry4ZluxbBp86ZCiAtDkY,3505
35
35
  kash/commands/help/welcome.py,sha256=F4QBgj3e1dM9Pf0H4TSzCrkVfXQVKUIl0b6Qmofbdo4,905
36
- kash/commands/workspace/selection_commands.py,sha256=yr0fFPlFIJUPHyFni1byXz8UDvYstIw4oRpOMa8iOBo,7428
37
- kash/commands/workspace/workspace_commands.py,sha256=0dyZ2EkThWArW5MfSjGVottgXKtibKXAVfI41NGvsUM,24177
36
+ kash/commands/workspace/selection_commands.py,sha256=nZzA-H7Pk8kqSJVRlX7j1m6cZX-e0X8isOryDU41vqU,8156
37
+ kash/commands/workspace/workspace_commands.py,sha256=ZJ3aPsnQ0FOkaA6stpV4YPEOQRCOKTazbMCIQkk9Cmk,25119
38
38
  kash/config/__init__.py,sha256=ytly9Typ1mWV4CXfV9G3CIPtPQ02u2rpZ304L3GlFro,148
39
39
  kash/config/capture_output.py,sha256=ud3uUVNuDicHj3mI_nBUBO-VmOrxtBdA3z-I3D1lSCU,2398
40
40
  kash/config/colors.py,sha256=6lqrB2RQYF2OLw-njfOqVHO9Bwiq7bW6K1ROCOAd1EM,9949
@@ -82,7 +82,7 @@ kash/embeddings/embeddings.py,sha256=v6RmrEHsx5PuE3fPrY15RK4fgW0K_VlNWDTjCVr11zY
82
82
  kash/embeddings/text_similarity.py,sha256=BOo9Vcs5oi2Zs5La56uTkPMHo65XSd4qz_yr6GTfUA4,1924
83
83
  kash/exec/__init__.py,sha256=rdSsKzTaXfSZmD5JvmUSSwmpfvl-moNv9PUgtE_WUpQ,1148
84
84
  kash/exec/action_decorators.py,sha256=VOSCnFiev2_DuFoSk0i_moejwM4wJ1j6QfsQd93uetI,16480
85
- kash/exec/action_exec.py,sha256=RXuTvsnkVqnE_PdbFqCWZ94morLUd06folT0lcmwCwk,18563
85
+ kash/exec/action_exec.py,sha256=UHs-gKbu63d313MwfKsbVoRoTq7LbY8Vs0u_R4QZMh8,19025
86
86
  kash/exec/action_registry.py,sha256=numU9pH_W5RgIrYmfi0iYMYy_kLJl6vup8PMrhxAfdc,2627
87
87
  kash/exec/combiners.py,sha256=AJ6wgPUHsmwanObsUw64B83XzU26yuh5t4l7igLn82I,4291
88
88
  kash/exec/command_exec.py,sha256=zc-gWm7kyB5J5Kp8xhULQ9Jj9AL927KkDPXXk-Yr1Bw,1292
@@ -92,9 +92,9 @@ kash/exec/history.py,sha256=l2XwHGBR1UgTGSFPSBE9mltmxvjR_5qFFO6d-Z008nc,1208
92
92
  kash/exec/importing.py,sha256=xunmBapeUMNc6Zox7y6e_DZkidyWeouiFZpphajwSzc,1843
93
93
  kash/exec/llm_transforms.py,sha256=p_aLp70VoIgheW4v8uoweeuEVWj06AzQekvn_jM3B-g,4378
94
94
  kash/exec/precondition_checks.py,sha256=HymxL7qm4Yz8V76Um5pKdIRnQ2N-p9rpQQi1fI38bNA,2139
95
- kash/exec/precondition_registry.py,sha256=cmp0mUfLS42AbAByDhwGx8GWz9PuZNR7z5rPZW9WQE4,1244
96
- kash/exec/preconditions.py,sha256=yJSQ1MWnejxQHPH4ULb6mEPPsMUK_ViLkUaFMW09z_w,4375
97
- kash/exec/resolve_args.py,sha256=yGU6Jjzn5yyAN9pNZx8Qfc9oBrosFEdazIs5g9pjWTs,4410
95
+ kash/exec/precondition_registry.py,sha256=-vYWN2wduq-hau2aDShQ5tglTF-cHC1vaZqU-peir9A,1384
96
+ kash/exec/preconditions.py,sha256=6bJ76AVFo0TLoykrGBNB0rrpIDWD0umfb3F5sMcmoKo,5131
97
+ kash/exec/resolve_args.py,sha256=gu65epzVrwWHdHA-KwAwYssncJIB84oHOJeoXXrQ2mM,4428
98
98
  kash/exec/runtime_settings.py,sha256=aK6nGbZhKSIDVmV6AqV68hQkiaIGWnCiNzHtwwZ5V0w,3960
99
99
  kash/exec/shell_callable_action.py,sha256=x-Hs4EqpsZfKEcwhWkhc27HCIfoI91b-DrbG40BLxRY,4350
100
100
  kash/exec_model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -103,7 +103,7 @@ kash/exec_model/commands_model.py,sha256=iM8QhzA0tAas5OwF5liUfHtm45XIH1LcvCviuh3
103
103
  kash/exec_model/script_model.py,sha256=1VG3LhkTmlKzHOYouZ92ZpOSKSCcsz3-tHNcFMQF788,5031
104
104
  kash/exec_model/shell_model.py,sha256=LUhQivbpXlerM-DUzNY7BtctNBbn08Wto8CSSxQDxRU,568
105
105
  kash/file_storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
106
- kash/file_storage/file_store.py,sha256=_Up5TkgnIekVfPZ4mCyhjMUxLtGL_GsAhcpo1m3-Dj0,28428
106
+ kash/file_storage/file_store.py,sha256=c9Vt40JwleHOVqk-2b7yayW80RRiH-AApD-HZ3gdebo,30021
107
107
  kash/file_storage/item_file_format.py,sha256=YAz7VqyfIoiSLQOoFdWsp-FI_2tTLXAPi8V8QXbo5ag,5475
108
108
  kash/file_storage/metadata_dirs.py,sha256=9AqO3S3SSY1dtvP2iLX--E4ui0VIzXttG8R040otfyg,3820
109
109
  kash/file_storage/persisted_yaml.py,sha256=4-4RkFqdlBUkTOwkdA4vRKUywEE9TaDo13OGaDUyU9M,1309
@@ -164,11 +164,11 @@ 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=OZ88M15qp0m2OnUqu1pvrJrGrP-hANdXUVbCXdKKrqQ,34700
167
+ kash/model/items_model.py,sha256=RLbRTo36AZR5QLHotcYo4s6na8u40rcLXA0F6POUHyw,34913
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=64Zic4cRjQpgf_-tOuZlZZe59mz_qu0s6OQSU0YlDUI,3357
171
- kash/model/operations_model.py,sha256=dPgccwh6HwWhag_MkhEfEwByuZamcJEFrvq4w4NtrII,6112
171
+ kash/model/operations_model.py,sha256=WmU-xeWGsqMLVN369dQEyVGU8T7G_KyLLsj6YFc5sVw,6517
172
172
  kash/model/params_model.py,sha256=qGhsGvtDQoSqWkrKk9QZZfEh-jO1q2V-s-p6X-F37_M,14939
173
173
  kash/model/paths_model.py,sha256=KDFm7wan7hjObHbnV2rR8-jsyLTVqbKcwFdKeLFRtdM,15889
174
174
  kash/model/preconditions_model.py,sha256=-IfsVR0NkQhq_3hUTXzK2bFYAd--3YjSwUiDKHVQQqk,2887
@@ -195,7 +195,7 @@ 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
197
197
  kash/shell/utils/exception_printing.py,sha256=UizjOkBPhW6YbkiFP965BE5FrCwn04MXGDbxyTuyvOk,1908
198
- kash/shell/utils/native_utils.py,sha256=FtIjjB1sOgNifTx1u7nKSAwg9A8wc9-fkACw2_xWnNg,9153
198
+ kash/shell/utils/native_utils.py,sha256=pAiuqqrjfNTesdArSya6CVavKVsuAXOcX3_XAIQrWtE,9151
199
199
  kash/shell/utils/shell_function_wrapper.py,sha256=fgUuVhocYMKLkGJJQJOER5nFMAvM0ZVpfGu7iJPJI9s,7385
200
200
  kash/utils/__init__.py,sha256=4Jl_AtgRADdGORimWhYZwbSfQSpQ6SiexNIZzmbcngI,111
201
201
  kash/utils/errors.py,sha256=2lPL0fxI8pPOiDvjl0j-rvwY8uhmWetsrYYIc2-x1WY,3906
@@ -211,13 +211,13 @@ kash/utils/common/stack_traces.py,sha256=a2NwlK_0xxnjMCDC4LrQu7ueFylF-OImFG3bAAH
211
211
  kash/utils/common/task_stack.py,sha256=XkeBz3BwYY1HxxTqd3f7CulV0s61PePAKw1Irrtvf5o,4536
212
212
  kash/utils/common/type_utils.py,sha256=SJirXhPilQom_-OKkFToDLm_82ZwpjcNjRy8U1HaQ0Q,3829
213
213
  kash/utils/common/uniquifier.py,sha256=75OY4KIVF8u1eoO0FCPbEGTyVpPOtM-0ctoG_s_jahM,3082
214
- kash/utils/common/url.py,sha256=hEDC0ImO3DLvaPRflcmiUZ1wK_Ilsm6_9fLaG23sUfo,6515
214
+ kash/utils/common/url.py,sha256=R_P-CkOUiFxVdo9COcaL7YFvFIoAULj5-XxvmlFLvzo,9416
215
215
  kash/utils/file_formats/chat_format.py,sha256=Onby7Zany1UQSUo_JzLs6MIfmoXViZeOAacRTMVe92M,11818
216
216
  kash/utils/file_utils/__init__.py,sha256=loL_iW0oOZs0mJ5GelBPptBcqzYKSWdsGcHrpRyxitQ,43
217
217
  kash/utils/file_utils/dir_info.py,sha256=HamMr58k_DanTLifj7A2JDxTGWXEZZx2pQuE6Hjcm8g,1856
218
218
  kash/utils/file_utils/file_ext.py,sha256=-H63vlrVI3pfE2Cn_9qF7-QLDaUIu_njc4TieNgAHSY,1860
219
- kash/utils/file_utils/file_formats.py,sha256=tmrmqM5YTxfvlpvqmeOVz0yVRuPxxIAkVZf1-D0fp5Y,4902
220
- kash/utils/file_utils/file_formats_model.py,sha256=pwCkt84xOmt2cvnPk4uB5lB6q9pKy_ARr-o4OE2t8A0,15801
219
+ kash/utils/file_utils/file_formats.py,sha256=vnihRFLl85G1uzpqDc_uiGH9SIvbFTYVszz3srdSSz0,4949
220
+ kash/utils/file_utils/file_formats_model.py,sha256=M1KTGJdwC91SiQkBbEkext-hjMcjYpSnoNGUIuWiJKo,15448
221
221
  kash/utils/file_utils/file_sort_filter.py,sha256=_k1chT3dJl5lSmKA2PW90KaoG4k4zftGdtwWoNEljP4,7136
222
222
  kash/utils/file_utils/file_walk.py,sha256=cpwVDPuaVm95_ZwFJiAdIuZAGhASI3gJ3ZUsCGP75b8,5527
223
223
  kash/utils/file_utils/filename_parsing.py,sha256=drHrH2B9W_5yAbXURNGJxNqj9GmTe8FayH6Gjw9e4-U,4194
@@ -231,7 +231,7 @@ kash/utils/rich_custom/ansi_cell_len.py,sha256=oQlNrqWB0f6pmigkbRRyeK6oWlGHMPbV_
231
231
  kash/utils/rich_custom/rich_char_transform.py,sha256=3M89tViKM0y31VHsDoHi5eHFWlv5ME7F4p35IdDxnrw,2616
232
232
  kash/utils/rich_custom/rich_indent.py,sha256=nz72yNpUuYjOsaPNVmxM81oEQm-GKEfQkNsuWmv16G0,2286
233
233
  kash/utils/rich_custom/rich_markdown_fork.py,sha256=M_JRaSAyHrSg-wuLv9C9P7SkehSim3lwkqQPuMIFkVw,26551
234
- kash/utils/text_handling/doc_normalization.py,sha256=Lz2wiSXCFGT15S9siNksiiND08uQIj3sgFCYYWkamT8,2786
234
+ kash/utils/text_handling/doc_normalization.py,sha256=C211eSof8PUDVCqQtShuC4AMJpTZeBK8GHlGATp3c9E,2976
235
235
  kash/utils/text_handling/escape_html_tags.py,sha256=7ZNQw3wfGzATVBBKmJMWmBTuznEPGzS6utjrH9HmmlQ,6361
236
236
  kash/utils/text_handling/markdown_render.py,sha256=Ea-QiBND0kp4Dc9rYr8Z3dB_CRpAxneGHBOlTDWsDo0,3751
237
237
  kash/utils/text_handling/markdown_utils.py,sha256=ndEd5ai80ZjSeMR104KLmAj0LOCPZln0ntd9tP5mf-E,9783
@@ -241,7 +241,7 @@ kash/web_content/canon_url.py,sha256=Zv2q7xQdIHBFkxxwyJn3_ME-qqMFRi_fKxE_IgV2Z50
241
241
  kash/web_content/dir_store.py,sha256=BJc-s-RL5CC-GwhFTC_lhLXSMWluPPnLVmVBx-66DiM,3425
242
242
  kash/web_content/file_cache_utils.py,sha256=JRXUCAmrc83iAgdiICU2EYGWcoORflWNl6GAVq-O80I,5529
243
243
  kash/web_content/file_processing.py,sha256=cQC-MnJMM5qG9-y0S4yobkmRi6A75qhHjV6xTwbtYDY,1904
244
- kash/web_content/local_file_cache.py,sha256=zajvWajDx-TYMGn3p8-K4KZOo2C2PQOomQRLbT3808o,8968
244
+ kash/web_content/local_file_cache.py,sha256=PEDKU5VIwhCnSC-HXG4EkO2OzrOUDuuDBMuo3lP2EN0,9466
245
245
  kash/web_content/web_extract.py,sha256=LbuG4AFEeIiXyUrN9CAxX0ret41Fqu_iTJSjIWyk3Bg,2296
246
246
  kash/web_content/web_extract_justext.py,sha256=74HLJBKDGKatwxyRDX6za70bZG9LrVmtj9jLX7UJzg4,2540
247
247
  kash/web_content/web_extract_readabilipy.py,sha256=IT7ET5IoU2-Nf37-Neh6CkKMvLL3WTNVJjq7ZMOx6OM,808
@@ -251,7 +251,7 @@ kash/web_gen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
251
251
  kash/web_gen/simple_webpage.py,sha256=c_kLXAsjP9wB9-ppF7MMJMw5VHXzVwkcHymfo5YOfS0,1402
252
252
  kash/web_gen/tabbed_webpage.py,sha256=Q_Htw2QO0O9H3A9OFrWw9GBD73cbwB6hOKF-W6mO6YE,4807
253
253
  kash/web_gen/template_render.py,sha256=aypo6UanouftV4RpxgNm6JdquelI52fV0IlihdA3yjE,1908
254
- kash/web_gen/templates/base_styles.css.jinja,sha256=sPYXVMtK1f-Ndf6zbHv71gdq_lkkOwjf8JNqDc_37go,9176
254
+ kash/web_gen/templates/base_styles.css.jinja,sha256=xrA7567wXHp9oMRqXhiMcEjH_IMeo6Ny1_pThYWpe4o,9202
255
255
  kash/web_gen/templates/base_webpage.html.jinja,sha256=Nvdd8pLSG2OdbrDQRvYcexYIZoMdr1G_7MdUVHNDoA8,7945
256
256
  kash/web_gen/templates/content_styles.css.jinja,sha256=3qcIwIt3DipCDJa9z6oIM_BMxmwoT7E_loTK0F3L9Vo,3629
257
257
  kash/web_gen/templates/explain_view.html.jinja,sha256=DNw5Iw5SrhIUFRGB4qNvfcKXsBHVbEJVURGdhvyC75Q,949
@@ -263,7 +263,6 @@ kash/workspaces/param_state.py,sha256=vT_eGWqg2SRviIM5jqEAauznX2B5Xt2nHHu2oRxTcI
263
263
  kash/workspaces/selections.py,sha256=rEUuQlrQ3C_54bzBSKDTTptgX8oZPqN0Ao4uaXSWA-Q,12003
264
264
  kash/workspaces/source_items.py,sha256=Pwnw3OhjR2IJEMEeHf6hpKloj-ellM5vsY7LgkGevRY,2861
265
265
  kash/workspaces/workspace_dirs.py,sha256=kjuY4t7mSSXq00fZmln7p9TWq4kAZoPTCDM0DG7uEaI,1545
266
- kash/workspaces/workspace_importing.py,sha256=4IJo713Kuoynhd_lcZF9M_DZ0rrMK_IDfhTVgwKmVyQ,1934
267
266
  kash/workspaces/workspace_output.py,sha256=MMg_KumkHKFGc0DOUFaW5ImpgqIfdlsLtvXbLEt1hwI,5692
268
267
  kash/workspaces/workspace_registry.py,sha256=SQt2DZgBEu95Zj9fpy67XdJPgJyKFDCU2laSuiZswNo,2200
269
268
  kash/workspaces/workspaces.py,sha256=kQyS3F57Y9A9xVT_Ss7HzJhDGlI-UXHKvRDnEVkBnik,6764
@@ -280,8 +279,8 @@ kash/xonsh_custom/xonsh_modern_tools.py,sha256=mj_b34LZXfE8MJe9EpDmp5JZ0tDM1biYN
280
279
  kash/xonsh_custom/xonsh_ranking_completer.py,sha256=ZRGiAfoEgqgnlq2-ReUVEaX5oOgW1DQ9WxIv2OJLuTo,5620
281
280
  kash/xontrib/fnm.py,sha256=V2tsOdmIDgbFbZSfMLpsvDIwwJJqiYnOkOySD1cXNXw,3700
282
281
  kash/xontrib/kash_extension.py,sha256=JRRJC3cZSMOl4sSWEdKAQ_dVRMubWaOltKr8G0dWt6Y,1876
283
- kash_shell-0.3.13.dist-info/METADATA,sha256=X_tk6xWJni7Il0DKb_fAD8VCQR4D6y64XFyy5VZNap4,31258
284
- kash_shell-0.3.13.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
285
- kash_shell-0.3.13.dist-info/entry_points.txt,sha256=SQraWDAo8SqYpthLXThei0mf_hGGyhYBUO-Er_0HcwI,85
286
- kash_shell-0.3.13.dist-info/licenses/LICENSE,sha256=rCh2PsfYeiU6FK_0wb58kHGm_Fj5c43fdcHEexiVzIo,34562
287
- kash_shell-0.3.13.dist-info/RECORD,,
282
+ kash_shell-0.3.15.dist-info/METADATA,sha256=nxMDgU5r65l0fnmvMfJkeIXQeUMxXKdvjk6EFTpgyQY,31258
283
+ kash_shell-0.3.15.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
284
+ kash_shell-0.3.15.dist-info/entry_points.txt,sha256=SQraWDAo8SqYpthLXThei0mf_hGGyhYBUO-Er_0HcwI,85
285
+ kash_shell-0.3.15.dist-info/licenses/LICENSE,sha256=rCh2PsfYeiU6FK_0wb58kHGm_Fj5c43fdcHEexiVzIo,34562
286
+ kash_shell-0.3.15.dist-info/RECORD,,