kash-shell 0.3.23__py3-none-any.whl → 0.3.24__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.
@@ -2,11 +2,13 @@ from __future__ import annotations
2
2
 
3
3
  import logging
4
4
  from dataclasses import dataclass
5
- from functools import cached_property
5
+ from enum import Enum
6
+ from functools import cache, cached_property
6
7
  from pathlib import Path
7
8
  from typing import TYPE_CHECKING, Any
8
9
  from urllib.parse import urlparse
9
10
 
11
+ from cachetools import TTLCache
10
12
  from strif import atomic_output_file, copyfile_atomic
11
13
 
12
14
  from kash.config.env_settings import KashEnv
@@ -14,59 +16,245 @@ from kash.utils.common.url import Url
14
16
  from kash.utils.file_utils.file_formats import MimeType
15
17
 
16
18
  if TYPE_CHECKING:
17
- from httpx import Client, Response
19
+ from curl_cffi.requests import Response as CurlCffiResponse
20
+ from curl_cffi.requests import Session as CurlCffiSession
21
+ from httpx import Client as HttpxClient
22
+ from httpx import Response as HttpxResponse
18
23
 
19
24
  log = logging.getLogger(__name__)
20
25
 
21
26
 
22
27
  DEFAULT_TIMEOUT = 30
28
+ CURL_CFFI_IMPERSONATE_VERSION = "chrome120"
23
29
 
24
-
25
- DEFAULT_USER_AGENT = (
26
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0"
30
+ # Header helpers
31
+ _DEFAULT_UA = (
32
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_3) "
33
+ "AppleWebKit/537.36 (KHTML, like Gecko) "
34
+ "Chrome/126.0.0.0 Safari/537.36"
27
35
  )
36
+ _SIMPLE_HEADERS = {"User-Agent": KashEnv.KASH_USER_AGENT.read_str(default=_DEFAULT_UA)}
37
+
38
+
39
+ class ClientMode(Enum):
40
+ """
41
+ Defines the web client and settings.
42
+ """
43
+
44
+ SIMPLE = "SIMPLE"
45
+ """httpx with minimal headers"""
46
+
47
+ BROWSER_HEADERS = "BROWSER_HEADERS"
48
+ """httpx with extensive, manually-set headers"""
49
+
50
+ CURL_CFFI = "CURL_CFFI"
51
+ """curl_cffi for full browser impersonation (incl. TLS)"""
52
+
53
+ AUTO = "AUTO"
54
+ """Automatically pick CURL_CFFI if available, otherwise BROWSER_HEADERS"""
55
+
56
+
57
+ @cache
58
+ def _have_brotli() -> bool:
59
+ """
60
+ Check if brotli compression is available.
61
+ Warns once if brotli is not installed.
62
+ """
63
+ try:
64
+ import brotli # noqa: F401
65
+
66
+ return True
67
+ except ImportError:
68
+ log.warning("web_fetch: brotli package not found; install for better download performance")
69
+ return False
70
+
71
+
72
+ @cache
73
+ def _have_curl_cffi() -> bool:
74
+ """
75
+ Check if curl_cffi is available.
76
+ Warns once if curl_cffi is not installed.
77
+ """
78
+ try:
79
+ import curl_cffi.requests # noqa: F401
80
+
81
+ return True
82
+ except ImportError:
83
+ log.warning("web_fetch: curl_cffi package not found; install for browser impersonation")
84
+ return False
28
85
 
29
86
 
30
- def default_headers() -> dict[str, str]:
31
- return {"User-Agent": KashEnv.KASH_USER_AGENT.read_str(default=DEFAULT_USER_AGENT)}
87
+ @cache
88
+ def _get_auto_mode() -> ClientMode:
89
+ """
90
+ Automatically select the best available client mode.
91
+ Logs the decision once due to caching.
92
+ """
93
+ if _have_curl_cffi():
94
+ log.info("web_fetch: AUTO mode selected CURL_CFFI (full browser impersonation)")
95
+ return ClientMode.CURL_CFFI
96
+ else:
97
+ log.info("web_fetch: AUTO mode selected BROWSER_HEADERS (httpx with browser headers)")
98
+ return ClientMode.BROWSER_HEADERS
99
+
100
+
101
+ @cache
102
+ def _browser_like_headers() -> dict[str, str]:
103
+ """
104
+ Full header set that looks like a 2025-era Chrome GET.
105
+ """
106
+ ua = KashEnv.KASH_USER_AGENT.read_str(default=_DEFAULT_UA)
107
+
108
+ # Build Accept-Encoding based on available compression support
109
+ encodings = ["gzip", "deflate"]
110
+ if _have_brotli():
111
+ encodings.append("br")
112
+ accept_encoding = ", ".join(encodings)
113
+
114
+ return {
115
+ "User-Agent": ua,
116
+ "Accept": (
117
+ "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"
118
+ ),
119
+ "Accept-Language": "en-US,en;q=0.9",
120
+ "Accept-Encoding": accept_encoding,
121
+ "Referer": "https://www.google.com/",
122
+ "DNT": "1",
123
+ "Upgrade-Insecure-Requests": "1",
124
+ }
125
+
126
+
127
+ # Cookie priming cache - tracks which hosts have been primed
128
+ _primed_hosts = TTLCache(maxsize=10000, ttl=3600)
129
+
130
+
131
+ def _prime_host(host: str, client: HttpxClient | CurlCffiSession, timeout: int, **kwargs) -> bool:
132
+ """
133
+ Prime cookies for a host using the provided client and extra arguments.
134
+ """
135
+ if host in _primed_hosts:
136
+ log.debug("Cookie priming for %s skipped (cached)", host)
137
+ return True
138
+
139
+ try:
140
+ root = f"https://{host}/"
141
+ # Pass client-specific kwargs like `impersonate`
142
+ client.get(root, timeout=timeout, **kwargs)
143
+ log.debug("Cookie priming completed for %s", host)
144
+ except Exception as exc:
145
+ log.debug("Cookie priming for %s failed (%s); continuing", host, exc)
146
+
147
+ # Mark as primed (both success and failure to avoid immediate retries)
148
+ _primed_hosts[host] = True
149
+ return True
150
+
151
+
152
+ def _get_req_headers(
153
+ mode: ClientMode, user_headers: dict[str, str] | None = None
154
+ ) -> dict[str, str]:
155
+ """
156
+ Build headers based on the selected ClientMode.
157
+ For CURL_CFFI, curl_cffi handles headers automatically.
158
+ """
159
+ if mode is ClientMode.AUTO:
160
+ mode = _get_auto_mode()
161
+
162
+ base_headers = {}
163
+ if mode is ClientMode.SIMPLE:
164
+ base_headers = _SIMPLE_HEADERS
165
+ elif mode is ClientMode.BROWSER_HEADERS:
166
+ base_headers = _browser_like_headers()
167
+ elif mode is ClientMode.CURL_CFFI:
168
+ # curl_cffi handles the important headers (UA, Accept-*, etc.)
169
+ # We only need to add user-provided ones.
170
+ return user_headers or {}
171
+
172
+ if user_headers:
173
+ return {**base_headers, **user_headers}
174
+
175
+ return base_headers
32
176
 
33
177
 
34
178
  def fetch_url(
35
179
  url: Url,
180
+ *,
36
181
  timeout: int = DEFAULT_TIMEOUT,
37
182
  auth: Any | None = None,
38
183
  headers: dict[str, str] | None = None,
39
- ) -> Response:
184
+ mode: ClientMode = ClientMode.AUTO,
185
+ ) -> HttpxResponse | CurlCffiResponse:
40
186
  """
41
- Fetch a URL using httpx with logging and reasonable defaults.
42
- Raise httpx.HTTPError for non-2xx responses.
187
+ Fetch a URL, dispatching to httpx or curl_cffi based on the mode.
43
188
  """
44
- import httpx
189
+ if mode is ClientMode.AUTO:
190
+ mode = _get_auto_mode()
191
+
192
+ req_headers = _get_req_headers(mode, headers)
193
+ parsed_url = urlparse(str(url))
194
+
195
+ # Handle curl_cffi mode
196
+ if mode is ClientMode.CURL_CFFI:
197
+ if not _have_curl_cffi():
198
+ raise ValueError("Could not find curl_cffi, which is needed for CURL_CFFI mode")
45
199
 
46
- with httpx.Client(
47
- follow_redirects=True,
48
- timeout=timeout,
49
- auth=auth,
50
- headers=headers or default_headers(),
51
- ) as client:
52
- log.debug("fetch_url: using headers: %s", client.headers)
53
- response = client.get(url)
54
- log.info("Fetched: %s (%s bytes): %s", response.status_code, len(response.content), url)
55
- response.raise_for_status()
56
- return response
200
+ from curl_cffi.requests import Session
201
+
202
+ with Session() as client:
203
+ # Set headers on the session - they will be sent with all requests
204
+ client.headers.update(req_headers)
205
+ _prime_host(
206
+ parsed_url.netloc, client, timeout, impersonate=CURL_CFFI_IMPERSONATE_VERSION
207
+ )
208
+ log.debug("fetch_url (curl_cffi): using session headers: %s", client.headers)
209
+ response = client.get(
210
+ url,
211
+ impersonate=CURL_CFFI_IMPERSONATE_VERSION,
212
+ timeout=timeout,
213
+ auth=auth,
214
+ allow_redirects=True,
215
+ )
216
+ log.info(
217
+ "Fetched (curl_cffi): %s (%s bytes): %s",
218
+ response.status_code,
219
+ len(response.content),
220
+ url,
221
+ )
222
+ response.raise_for_status()
223
+ return response
224
+
225
+ # Handle httpx modes
226
+ else:
227
+ import httpx
228
+
229
+ with httpx.Client(
230
+ follow_redirects=True,
231
+ timeout=timeout,
232
+ auth=auth,
233
+ headers=req_headers,
234
+ ) as client:
235
+ log.debug("fetch_url (httpx): using headers: %s", client.headers)
236
+
237
+ # Cookie priming only makes sense for the browser-like mode
238
+ if mode is ClientMode.BROWSER_HEADERS:
239
+ _prime_host(parsed_url.netloc, client, timeout)
240
+
241
+ response = client.get(url)
242
+ log.info(
243
+ "Fetched (httpx): %s (%s bytes): %s",
244
+ response.status_code,
245
+ len(response.content),
246
+ url,
247
+ )
248
+ response.raise_for_status()
249
+ return response
57
250
 
58
251
 
59
252
  @dataclass(frozen=True)
60
253
  class HttpHeaders:
61
- """
62
- HTTP response headers.
63
- """
64
-
65
254
  headers: dict[str, str]
66
255
 
67
256
  @cached_property
68
257
  def mime_type(self) -> MimeType | None:
69
- """Get content type header, if available."""
70
258
  for key, value in self.headers.items():
71
259
  if key.lower() == "content-type":
72
260
  return MimeType(value)
@@ -76,11 +264,12 @@ class HttpHeaders:
76
264
  def download_url(
77
265
  url: Url,
78
266
  target_filename: str | Path,
79
- session: Client | None = None,
267
+ *,
80
268
  show_progress: bool = False,
81
269
  timeout: int = DEFAULT_TIMEOUT,
82
270
  auth: Any | None = None,
83
271
  headers: dict[str, str] | None = None,
272
+ mode: ClientMode = ClientMode.AUTO,
84
273
  ) -> HttpHeaders | None:
85
274
  """
86
275
  Download given file, optionally with progress bar, streaming to a target file.
@@ -88,8 +277,8 @@ def download_url(
88
277
  Raise httpx.HTTPError for non-2xx responses.
89
278
  Returns response headers for HTTP/HTTPS requests, None for other URL types.
90
279
  """
91
- import httpx
92
- from tqdm import tqdm
280
+ if mode is ClientMode.AUTO:
281
+ mode = _get_auto_mode()
93
282
 
94
283
  target_filename = str(target_filename)
95
284
  parsed_url = urlparse(url)
@@ -106,39 +295,79 @@ def download_url(
106
295
  s3_path = parsed_url.path.lstrip("/")
107
296
  s3.Bucket(parsed_url.netloc).download_file(s3_path, target_filename)
108
297
  return None
109
- else:
110
- client = session or httpx.Client(follow_redirects=True, timeout=timeout)
111
- response: httpx.Response | None = None
112
- response_headers: dict[str, str] | None = None
113
- try:
114
- headers = headers or default_headers()
115
- log.debug("download_url: using headers: %s", headers)
116
- with client.stream(
117
- "GET",
298
+
299
+ req_headers = _get_req_headers(mode, headers)
300
+ response_headers = None
301
+
302
+ def stream_to_file(response_iterator, total_size):
303
+ with atomic_output_file(target_filename, make_parents=True) as temp_filename:
304
+ with open(temp_filename, "wb") as f:
305
+ if not show_progress:
306
+ for chunk in response_iterator:
307
+ if chunk: # Skip empty chunks
308
+ f.write(chunk)
309
+ else:
310
+ from tqdm import tqdm
311
+
312
+ with tqdm(
313
+ total=total_size,
314
+ unit="B",
315
+ unit_scale=True,
316
+ desc=f"Downloading {Path(target_filename).name}",
317
+ ) as progress:
318
+ for chunk in response_iterator:
319
+ if chunk: # Skip empty chunks
320
+ f.write(chunk)
321
+ progress.update(len(chunk))
322
+
323
+ # Handle curl_cffi mode
324
+ if mode is ClientMode.CURL_CFFI:
325
+ if not _have_curl_cffi():
326
+ raise ValueError("Could not find curl_cffi, which is needed for CURL_CFFI mode")
327
+
328
+ from curl_cffi.requests import Session
329
+
330
+ with Session() as client:
331
+ # Set headers on the session; they will be sent with all requests
332
+ client.headers.update(req_headers)
333
+ _prime_host(
334
+ parsed_url.netloc, client, timeout, impersonate=CURL_CFFI_IMPERSONATE_VERSION
335
+ )
336
+ log.debug("download_url (curl_cffi): using session headers: %s", client.headers)
337
+
338
+ response = client.get(
118
339
  url,
119
- follow_redirects=True,
340
+ impersonate=CURL_CFFI_IMPERSONATE_VERSION,
120
341
  timeout=timeout,
121
342
  auth=auth,
122
- headers=headers,
123
- ) as response:
343
+ allow_redirects=True,
344
+ stream=True,
345
+ )
346
+ response.raise_for_status()
347
+ response_headers = dict(response.headers)
348
+ total = int(response.headers.get("content-length", "0"))
349
+
350
+ # Use iter_content for streaming; this is the standard method for curl_cffi
351
+ chunk_iterator = response.iter_content(chunk_size=8192)
352
+ stream_to_file(chunk_iterator, total)
353
+
354
+ # Handle httpx modes
355
+ else:
356
+ import httpx
357
+
358
+ with httpx.Client(follow_redirects=True, timeout=timeout, headers=req_headers) as client:
359
+ if mode is ClientMode.BROWSER_HEADERS:
360
+ _prime_host(parsed_url.netloc, client, timeout)
361
+
362
+ log.debug("download_url (httpx): using headers: %s", client.headers)
363
+ with client.stream("GET", url, auth=auth, follow_redirects=True) as response:
124
364
  response.raise_for_status()
125
365
  response_headers = dict(response.headers)
126
- total_size = int(response.headers.get("content-length", "0"))
366
+ total = int(response.headers.get("content-length", "0"))
367
+ stream_to_file(response.iter_bytes(), total)
127
368
 
128
- with atomic_output_file(target_filename, make_parents=True) as temp_filename:
129
- with open(temp_filename, "wb") as f:
130
- if not show_progress:
131
- for chunk in response.iter_bytes():
132
- f.write(chunk)
133
- else:
134
- with tqdm(total=total_size, unit="B", unit_scale=True) as progress:
135
- for chunk in response.iter_bytes():
136
- f.write(chunk)
137
- progress.update(len(chunk))
138
- finally:
139
- if not session: # Only close if we created the client
140
- client.close()
141
- if response:
142
- response.raise_for_status() # In case of errors during streaming
143
-
144
- return HttpHeaders(response_headers) if response_headers else None
369
+ # Filter out None values from headers for HttpHeaders type compatibility
370
+ if response_headers:
371
+ clean_headers = {k: v for k, v in response_headers.items() if v is not None}
372
+ return HttpHeaders(clean_headers)
373
+ return None
@@ -6,6 +6,7 @@ from pydantic.dataclasses import dataclass
6
6
 
7
7
  from kash.utils.common.url import Url
8
8
  from kash.utils.file_utils.file_formats_model import FileFormatInfo
9
+ from kash.web_content.local_file_cache import CacheResult
9
10
 
10
11
 
11
12
  @dataclass
@@ -18,6 +19,9 @@ class WebPageData:
18
19
  The `clean_html` field should be a clean HTML version of the page, if available.
19
20
  The `saved_content` is optional but can be used to reference the original content,
20
21
  especially for large or non-text content.
22
+
23
+ Optionally exposes the cache result for the content, so the client can have
24
+ information about headers and whether it was cached.
21
25
  """
22
26
 
23
27
  locator: Url | Path
@@ -29,6 +33,7 @@ class WebPageData:
29
33
  saved_content: Path | None = None
30
34
  format_info: FileFormatInfo | None = None
31
35
  thumbnail_url: Url | None = None
36
+ cache_result: CacheResult | None = None
32
37
 
33
38
  def __repr__(self):
34
39
  return abbrev_obj(self)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kash-shell
3
- Version: 0.3.23
3
+ Version: 0.3.24
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>
@@ -24,12 +24,14 @@ Requires-Dist: chopdiff>=0.2.3
24
24
  Requires-Dist: clideps>=0.1.4
25
25
  Requires-Dist: colour>=0.1.5
26
26
  Requires-Dist: cssselect>=1.2.0
27
+ Requires-Dist: curl-cffi>=0.11.4
27
28
  Requires-Dist: deepgram-sdk>=3.10.1
28
29
  Requires-Dist: dunamai>=1.23.0
29
30
  Requires-Dist: fastapi>=0.115.11
30
31
  Requires-Dist: flowmark>=0.4.6
31
32
  Requires-Dist: frontmatter-format>=0.2.1
32
33
  Requires-Dist: funlog>=0.2.0
34
+ Requires-Dist: httpx[brotli]>=0.28.1
33
35
  Requires-Dist: humanfriendly>=10.0
34
36
  Requires-Dist: inquirerpy>=0.3.4
35
37
  Requires-Dist: jinja2>=3.1.6
@@ -329,7 +331,7 @@ These are for `kash-media` but you can use a `kash-shell` for a more basic setup
329
331
 
330
332
  You can use kash from your MCP client (such as Anthropic Desktop or Cursor).
331
333
 
332
- You do this by running the the `kash_mcp` binary to make kash actions available as MCP
334
+ You do this by running the the `kash-mcp` binary to make kash actions available as MCP
333
335
  tools.
334
336
 
335
337
  For Claude Desktop, my config looks like this:
@@ -338,7 +340,7 @@ For Claude Desktop, my config looks like this:
338
340
  {
339
341
  "mcpServers": {
340
342
  "kash": {
341
- "command": "/Users/levy/.local/bin/kash_mcp",
343
+ "command": "/Users/levy/.local/bin/kash-mcp",
342
344
  "args": ["--proxy"]
343
345
  }
344
346
  }
@@ -3,6 +3,8 @@ kash/__main__.py,sha256=83JiHvCNK7BAwEkaTD0kVekj7PEMucvCyquSl4p-1QA,78
3
3
  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
+ kash/actions/core/combine_docs.py,sha256=5bTU7n_ICavvTXfC7fs5BDMeZYn7Xh5FkU7DVQqDHAQ,1536
7
+ kash/actions/core/concat_docs.py,sha256=Umx3VzFiHJGY-76AEs4ju_1HnB9SbQsBux03Mkeig24,1345
6
8
  kash/actions/core/format_markdown_template.py,sha256=ZJbtyTSypPo2ewLiGRSyIpVf711vQMhI_-Ng-FgCs80,2991
7
9
  kash/actions/core/markdownify_html.py,sha256=luMXwwaY6E8oidgdrwV6KSgzdG0iX2JKS5aTNYDdZpA,1810
8
10
  kash/actions/core/minify_html.py,sha256=99r3SjpI2NQP7e5MnMixAiT5lxPx7t2nyJvJi6Yps6w,1365
@@ -35,18 +37,18 @@ kash/commands/help/help_commands.py,sha256=eJTpIhXck123PAUq2k-D3Q6UL6IQ8atOVYurL
35
37
  kash/commands/help/logo.py,sha256=4K3pPIiNlPIgdNeOgdIGbJQCKmqCEsDOhuWPH51msWo,3377
36
38
  kash/commands/help/welcome.py,sha256=uM2UMtHG4oXTxU8IFlFbTOTmR5_XTCUtT828V_tKMsk,920
37
39
  kash/commands/workspace/selection_commands.py,sha256=nZzA-H7Pk8kqSJVRlX7j1m6cZX-e0X8isOryDU41vqU,8156
38
- kash/commands/workspace/workspace_commands.py,sha256=_2TcthGOu-nU9E_-jjf4kba9ldLRA6qe6Do6zV06EKc,21960
40
+ kash/commands/workspace/workspace_commands.py,sha256=xJ3Agg_tzGvH55705ovoZDYiTEO4FejWclwcsMWKitY,21965
39
41
  kash/config/__init__.py,sha256=ytly9Typ1mWV4CXfV9G3CIPtPQ02u2rpZ304L3GlFro,148
40
42
  kash/config/capture_output.py,sha256=ud3uUVNuDicHj3mI_nBUBO-VmOrxtBdA3z-I3D1lSCU,2398
41
43
  kash/config/colors.py,sha256=osHrR1rLrFV5j2kTdN4z-DDM-HmbnBfMaOj4NiBdFzg,13527
42
44
  kash/config/env_settings.py,sha256=uhCdfs9-TzJ15SzbuIQP1yIORaLUqYXCxh9qq_Z8cJc,996
43
45
  kash/config/init.py,sha256=aE4sZ6DggBmmoZEx9C5mQKrEbcDiswX--HF7pfCFKzc,526
44
46
  kash/config/lazy_imports.py,sha256=MCZXLnKvNyfHi0k7MU5rNwcdJtUF28naCixuogsAOAA,805
45
- kash/config/logger.py,sha256=VHsHb9Llp9Z3QmPqh4x42IaZs8pyQqfWT3pWvxY7x8o,11923
47
+ kash/config/logger.py,sha256=M5_w7LTxHO60VqLVM0yqu8pK6FHU-Twt4wfoJwuxWbY,12102
46
48
  kash/config/logger_basic.py,sha256=Gsxitz7xeMGCUr5qjWK6y72qeMsIz4mcDZP5xyzK0WU,1598
47
49
  kash/config/logo.txt,sha256=P4RO1cJ9HRF1BavTp3Kae9iByDNhzhEC-qLAa6ww1RA,217
48
50
  kash/config/server_config.py,sha256=eQ1yxDk031QI0Efp0I1VetqQd9wG7MrLVBCHFm4gp2g,1790
49
- kash/config/settings.py,sha256=P3g0RSR6vrvfovDDYOIx6Hxafrg1mYTAxrTiVgH2Tm4,8889
51
+ kash/config/settings.py,sha256=Tja5MNZJDjo5SRaCngfQQFTTjmVGUlmkCZ7UEAhvXqw,9087
50
52
  kash/config/setup.py,sha256=zFxfTPG1cbsozuwUkIyAYebxuHhpYCiaexHnYnYJG1c,3524
51
53
  kash/config/suppress_warnings.py,sha256=yty5ZodMLIpmjphRtcVmRamXfiWbyfga9annve6qxb0,1475
52
54
  kash/config/text_styles.py,sha256=QCidaUPRbelZjBnI6Q_Dtw-g_4VHdr99g4KHvDyQAo4,13810
@@ -62,7 +64,7 @@ kash/docs/markdown/readme_template.md,sha256=iGx9IjSni1t_9BuYD5d2GgkxkNIkqvE3k78
62
64
  kash/docs/markdown/warning.md,sha256=ipTFWQc1F0cPGrIG0epX5RqQH6PmshyZOoCQDz0zDos,88
63
65
  kash/docs/markdown/welcome.md,sha256=uNI6CyZOuPUe6FZgKd9MpO_pumHxlGN7Lfhb52XJRa0,611
64
66
  kash/docs/markdown/topics/a1_what_is_kash.md,sha256=rgVrv6tRXEwdqQ54DAfHP3BSAuq8Ux4wCNeluTwpkhU,6758
65
- kash/docs/markdown/topics/a2_installation.md,sha256=DSzaniHjOYPC3soGLPTGOGDVvbiPTROtb3S8zYUCPEs,5736
67
+ kash/docs/markdown/topics/a2_installation.md,sha256=MHZN0xBsNRYllg7H-Y4mribzXCMN_zweyE2CVELbhf0,5736
66
68
  kash/docs/markdown/topics/a3_getting_started.md,sha256=xOMevEXMIpVJvTGuuwI9Cc9sun3tQM3OqCgynSgMpeM,9376
67
69
  kash/docs/markdown/topics/a4_elements.md,sha256=XNJRw-iqnytiIHOAshp1YnUpHM5KBgFAhuOdp_fekxQ,4615
68
70
  kash/docs/markdown/topics/a5_tips_for_use_with_other_tools.md,sha256=VGUdq8dm78E8PFbNR9BmV8Gj-r8zP-vOQz8TibQmGw0,3259
@@ -79,16 +81,16 @@ kash/docs_base/recipes/general_system_commands.sh,sha256=rFNPuLj3Md09L5yzWx6ILja
79
81
  kash/docs_base/recipes/python_dev_commands.sh,sha256=9vJsQiDZKJ7ShokFnzc2Jsto6n87MNRkbgcc2Ee9Kro,179
80
82
  kash/docs_base/recipes/tldr_standard_commands.sh,sha256=7nPES55aT45HF3eDhQRrEUiWRpPdvvp40Sg88uADa80,60491
81
83
  kash/embeddings/cosine.py,sha256=QTWPWUHivXjxCM6APSqij_-4mywM2BVVm0xb0hu7FHA,1587
82
- kash/embeddings/embeddings.py,sha256=v6RmrEHsx5PuE3fPrY15RK4fgW0K_VlNWDTjCVr11zY,4451
84
+ kash/embeddings/embeddings.py,sha256=l1mDCTi296_hP_jvY8lJ8-WTBq43FWS4jy3kNvKJy44,4448
83
85
  kash/embeddings/text_similarity.py,sha256=BOo9Vcs5oi2Zs5La56uTkPMHo65XSd4qz_yr6GTfUA4,1924
84
86
  kash/exec/__init__.py,sha256=Najls8No143yoj_KAaOQgo8ufC2LWCB_DwwEQ-8nDM0,1277
85
87
  kash/exec/action_decorators.py,sha256=kCEqQFN1MIhRbFeIEGux956LzsiwonhyIIrJ8Pq9zPk,16765
86
- kash/exec/action_exec.py,sha256=O_4UB_Vt7QRxltviMeBwNIfw9ten06n4fQ39MregacE,19017
88
+ kash/exec/action_exec.py,sha256=tLL3ESIuxqS_gSOD4eGRYLuTzesRMTyKb_Uy3i_28VU,19022
87
89
  kash/exec/action_registry.py,sha256=numU9pH_W5RgIrYmfi0iYMYy_kLJl6vup8PMrhxAfdc,2627
88
90
  kash/exec/combiners.py,sha256=AJ6wgPUHsmwanObsUw64B83XzU26yuh5t4l7igLn82I,4291
89
91
  kash/exec/command_exec.py,sha256=zc-gWm7kyB5J5Kp8xhULQ9Jj9AL927KkDPXXk-Yr1Bw,1292
90
92
  kash/exec/command_registry.py,sha256=1s2ogU8b8nqK_AEtslbr1eYrXCGDkeT30UrB7L0BRoM,2027
91
- kash/exec/fetch_url_items.py,sha256=mKwPW_RJKVmYMUID6ESevolIn6Q76do4PfxiAAalqVY,4595
93
+ kash/exec/fetch_url_items.py,sha256=onKoAOg747r5T9fAIO8sTjJcllxKh3XLzh59FU5EFE8,5466
92
94
  kash/exec/history.py,sha256=l2XwHGBR1UgTGSFPSBE9mltmxvjR_5qFFO6d-Z008nc,1208
93
95
  kash/exec/importing.py,sha256=xunmBapeUMNc6Zox7y6e_DZkidyWeouiFZpphajwSzc,1843
94
96
  kash/exec/llm_transforms.py,sha256=n7S-Dew8z_BoAwp-14lY4LueFeUtG117eK_8msbn02c,4375
@@ -128,7 +130,7 @@ kash/llm_utils/custom_sliding_transforms.py,sha256=z07WCdBoiywBIGk2EXK3xY4t_6g8I
128
130
  kash/llm_utils/fuzzy_parsing.py,sha256=bbG2Y7i5w6kxAVPAixyluv3MDS2hW_pkhnJpVOLHZQc,3278
129
131
  kash/llm_utils/init_litellm.py,sha256=5Fn9uW4P7lfEO8Rk1EJJUzDEGNjw-PDvxFgHlKDf-Ok,409
130
132
  kash/llm_utils/llm_api_keys.py,sha256=nTB9wSFfHTOXvqshSQCQGCPxUwOW1U7oslngya8nHkw,1168
131
- kash/llm_utils/llm_completion.py,sha256=HPgxFys8RZsenfKq2S2dkUrpu-A88xXb-xK578zNd2g,5439
133
+ kash/llm_utils/llm_completion.py,sha256=SzeWGRrsjuN1TXdPwscYG6whLQkHclITtwTvQK19GE0,5436
132
134
  kash/llm_utils/llm_features.py,sha256=tgcz8qQByZ80noybXS2144K9eCw1iOFxPYkCK2kJJOw,1861
133
135
  kash/llm_utils/llm_messages.py,sha256=70QwIIvdwo-h4Jfn_6MbEHb3LTUjUmzg_v_dU_Ey__g,1174
134
136
  kash/llm_utils/llm_names.py,sha256=VZbdKnoeBx_luB5YQ-Rz37gMt3_FcueJdp40ZaQbpUA,3620
@@ -141,7 +143,7 @@ kash/local_server/local_url_formatters.py,sha256=SqHjGMEufvm43n34SCa_8Asdwm7utx9
141
143
  kash/local_server/port_tools.py,sha256=oFfOvO6keqS5GowTpVg2FTu5KqkPHBq-dWAEomUIgGo,2008
142
144
  kash/local_server/rich_html_template.py,sha256=O9CnkMYkWuMvKJkqD0P8jaZqfUe6hMP4LXFvcLpwN8Q,196
143
145
  kash/mcp/__init__.py,sha256=HA_aFskEchpAwA0wOKi5nasytx2JZfH8z9oCVXUI7MQ,160
144
- kash/mcp/mcp_cli.py,sha256=po0BrdAYwrhmzX4X6ro1wT7XOYSZgK3XxgI7BCZRYtA,3841
146
+ kash/mcp/mcp_cli.py,sha256=UC0Jwd7DLxx2zEFj0hK1UWPkXXXGntV1te33CensyHM,3849
145
147
  kash/mcp/mcp_main.py,sha256=6PhjKwp631mezDTUAd-pL8lUZx9Gl7yCrCQFW61pqJU,3167
146
148
  kash/mcp/mcp_server_commands.py,sha256=C-gKLbUVEpF76SmW3NgjlEWoM3YaA3ARrCos5UH1Zh8,4345
147
149
  kash/mcp/mcp_server_routes.py,sha256=radxy5cc0yBmcbqAbB1LpZ0qcNtfDbtS5RTGwvgliDM,10617
@@ -199,10 +201,11 @@ kash/shell/utils/native_utils.py,sha256=pAiuqqrjfNTesdArSya6CVavKVsuAXOcX3_XAIQr
199
201
  kash/shell/utils/shell_function_wrapper.py,sha256=fgUuVhocYMKLkGJJQJOER5nFMAvM0ZVpfGu7iJPJI9s,7385
200
202
  kash/utils/__init__.py,sha256=4Jl_AtgRADdGORimWhYZwbSfQSpQ6SiexNIZzmbcngI,111
201
203
  kash/utils/errors.py,sha256=2lPL0fxI8pPOiDvjl0j-rvwY8uhmWetsrYYIc2-x1WY,3906
202
- kash/utils/api_utils/api_retries.py,sha256=Cn32BJlTnvVMo9pbfMcPZG1jGSNJl99aZbnyFHYmscg,20695
204
+ kash/utils/api_utils/api_retries.py,sha256=TtgxLxoMnXIzYMKbMUzsnVcPf-aKFm3cJ95zOcSeae8,21844
203
205
  kash/utils/api_utils/cache_requests_limited.py,sha256=TA5buZ9Dgbj4I1zHhwerTXre018i0TCACGsezsjX9Uc,3140
204
- kash/utils/api_utils/gather_limited.py,sha256=-qUoriVVI5iTWxziTLSoUee-4NevUNQqVvkSYGaxbkY,27823
205
- kash/utils/api_utils/progress_protocol.py,sha256=Z_umf2DVmyTlfjiW57juN3wwlruU0camvVAHJKPHjtk,9041
206
+ kash/utils/api_utils/gather_limited.py,sha256=CV-9YjZakbKZ5AN3xIZSOyHwGvtvMljpl5Dq_SuDfNs,32945
207
+ kash/utils/api_utils/http_utils.py,sha256=Ou6QNiba5w7n71cgNmV168OFTLmMDNxWW5MM-XkFEME,1461
208
+ kash/utils/api_utils/progress_protocol.py,sha256=6cT5URY6cScHYd6UZoTT_rHI0mbsE52joBf88regEN8,8816
206
209
  kash/utils/common/__init__.py,sha256=ggeWw1xmbl1mgCQD3c4CNN2h5WXFCsN2wXlCWurEUEI,161
207
210
  kash/utils/common/format_utils.py,sha256=83FhAwbMnOQIFudpnOGMuCqCiyoAlWGS6cc8q6xgZus,2072
208
211
  kash/utils/common/function_inspect.py,sha256=KE5QgIPx16iupCZrMlcvU6MuA5fUFboPEkqmltpFw3E,19824
@@ -235,14 +238,14 @@ kash/utils/lang_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
235
238
  kash/utils/lang_utils/capitalization.py,sha256=5XbqBvjkzlxsm1Ue5AQP3P1J1IG0PubMVmGnoKVTF-c,3903
236
239
  kash/utils/rich_custom/__init__.py,sha256=_g2F3Bqc1UnLTdAdCwkvzXmW7OvmqXrA8DpfT1dKy6w,75
237
240
  kash/utils/rich_custom/ansi_cell_len.py,sha256=oQlNrqWB0f6pmigkbRRyeK6oWlGHMPbV_YLO_qmDH5E,2356
238
- kash/utils/rich_custom/multitask_status.py,sha256=9K8jA_WCskb2-S3WZOgGQo19qkWYpX4WqB2RF1p1apE,21540
241
+ kash/utils/rich_custom/multitask_status.py,sha256=ViNx35akEaV-fihef57_b3l8FnLytNomV6t2I_peJv8,23676
239
242
  kash/utils/rich_custom/rich_char_transform.py,sha256=3M89tViKM0y31VHsDoHi5eHFWlv5ME7F4p35IdDxnrw,2616
240
243
  kash/utils/rich_custom/rich_indent.py,sha256=nz72yNpUuYjOsaPNVmxM81oEQm-GKEfQkNsuWmv16G0,2286
241
244
  kash/utils/rich_custom/rich_markdown_fork.py,sha256=M_JRaSAyHrSg-wuLv9C9P7SkehSim3lwkqQPuMIFkVw,26551
242
245
  kash/utils/text_handling/doc_normalization.py,sha256=C211eSof8PUDVCqQtShuC4AMJpTZeBK8GHlGATp3c9E,2976
243
246
  kash/utils/text_handling/escape_html_tags.py,sha256=8pC3JgoKRtdnbnOu8DiWrlvNR6GAqjwhGbQgl3jiFG4,6441
244
247
  kash/utils/text_handling/markdown_render.py,sha256=LHPdJc__2ejBx7iwkp_P9wIePNmiVSgwu4-uhamVjms,3791
245
- kash/utils/text_handling/markdown_utils.py,sha256=4RGIK9weadZ1UYTk7eVmCIFX9yktMyLSFsMLNTCnmoY,31681
248
+ kash/utils/text_handling/markdown_utils.py,sha256=tAPMZulEcJOmWQHnZVKRqstThmsLB0mm7Fey5DHMFvc,31985
246
249
  kash/utils/text_handling/markdownify_utils.py,sha256=xNMlBX36BJ5VK5kxY2Ofo-Q84R2kBSM91u1grkQ-5As,2925
247
250
  kash/utils/text_handling/unified_diffs.py,sha256=JfHSakISkT_GuBPBI4fTooHrp2aenWzDKiVvDewVfMk,2655
248
251
  kash/web_content/canon_url.py,sha256=Zv2q7xQdIHBFkxxwyJn3_ME-qqMFRi_fKxE_IgV2Z50,742
@@ -250,11 +253,11 @@ kash/web_content/dir_store.py,sha256=BJc-s-RL5CC-GwhFTC_lhLXSMWluPPnLVmVBx-66DiM
250
253
  kash/web_content/file_cache_utils.py,sha256=JRXUCAmrc83iAgdiICU2EYGWcoORflWNl6GAVq-O80I,5529
251
254
  kash/web_content/file_processing.py,sha256=cQC-MnJMM5qG9-y0S4yobkmRi6A75qhHjV6xTwbtYDY,1904
252
255
  kash/web_content/local_file_cache.py,sha256=PEDKU5VIwhCnSC-HXG4EkO2OzrOUDuuDBMuo3lP2EN0,9466
253
- kash/web_content/web_extract.py,sha256=w6tjayTCgHFcyfSUk1UWBJjdYneoHQuTcV4foyUd_HQ,2844
256
+ kash/web_content/web_extract.py,sha256=TrGOohn99LF1F0zbycgXqYiIR-V3leefFFEVk3tnW6Y,2979
254
257
  kash/web_content/web_extract_justext.py,sha256=74HLJBKDGKatwxyRDX6za70bZG9LrVmtj9jLX7UJzg4,2540
255
258
  kash/web_content/web_extract_readabilipy.py,sha256=IT7ET5IoU2-Nf37-Neh6CkKMvLL3WTNVJjq7ZMOx6OM,808
256
- kash/web_content/web_fetch.py,sha256=J8DLFP1vzp7aScanFq0Bd7xCP6AVL4JgMMBqyRPtZjQ,4720
257
- kash/web_content/web_page_model.py,sha256=aPpgC1fH2z2LTzGJhEDvZgq_mYwgsQIZaDS3UE7v98w,1147
259
+ kash/web_content/web_fetch.py,sha256=hC4kijKVez4_QtfwdE0OPrEkfEGT2hVcB1vVZko8o78,12129
260
+ kash/web_content/web_page_model.py,sha256=TWgWreGOoOvl7lUkjMSd0rxoDf2VuaKOq7WijwydW0Q,1387
258
261
  kash/web_gen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
259
262
  kash/web_gen/simple_webpage.py,sha256=ks_0ljxCeS2-gAAEaUc1JEnzY3JY0nzqGFiyyqyRuZs,1537
260
263
  kash/web_gen/tabbed_webpage.py,sha256=e0GGG1bQ4CQegpJgOIT2KpyM6cmwN_BQ9eJSsi4BjgY,4945
@@ -291,8 +294,8 @@ kash/xonsh_custom/xonsh_modern_tools.py,sha256=mj_b34LZXfE8MJe9EpDmp5JZ0tDM1biYN
291
294
  kash/xonsh_custom/xonsh_ranking_completer.py,sha256=ZRGiAfoEgqgnlq2-ReUVEaX5oOgW1DQ9WxIv2OJLuTo,5620
292
295
  kash/xontrib/fnm.py,sha256=V2tsOdmIDgbFbZSfMLpsvDIwwJJqiYnOkOySD1cXNXw,3700
293
296
  kash/xontrib/kash_extension.py,sha256=FLIMlgR3C_6A1fwKE-Ul0nmmpJSszVPbAriinUyQ8Zg,1896
294
- kash_shell-0.3.23.dist-info/METADATA,sha256=1y1zNJVuhHB8lUyLOKZoErj_A3nhZlOuEuERxtIM5Wo,32656
295
- kash_shell-0.3.23.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
296
- kash_shell-0.3.23.dist-info/entry_points.txt,sha256=SQraWDAo8SqYpthLXThei0mf_hGGyhYBUO-Er_0HcwI,85
297
- kash_shell-0.3.23.dist-info/licenses/LICENSE,sha256=rCh2PsfYeiU6FK_0wb58kHGm_Fj5c43fdcHEexiVzIo,34562
298
- kash_shell-0.3.23.dist-info/RECORD,,
297
+ kash_shell-0.3.24.dist-info/METADATA,sha256=zgoOSgrSaBs5rmbe9RaoUHai2XJTLc3YFLTdM73MwWU,32726
298
+ kash_shell-0.3.24.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
299
+ kash_shell-0.3.24.dist-info/entry_points.txt,sha256=SQraWDAo8SqYpthLXThei0mf_hGGyhYBUO-Er_0HcwI,85
300
+ kash_shell-0.3.24.dist-info/licenses/LICENSE,sha256=rCh2PsfYeiU6FK_0wb58kHGm_Fj5c43fdcHEexiVzIo,34562
301
+ kash_shell-0.3.24.dist-info/RECORD,,