toolslm 0.1.0__py3-none-any.whl → 0.1.2__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.
toolslm/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.1.0"
1
+ __version__ = "0.1.2"
toolslm/_modidx.py CHANGED
@@ -14,15 +14,19 @@ d = { 'settings': { 'branch': 'main',
14
14
  'toolslm.download.read_html': ('download.html#read_html', 'toolslm/download.py'),
15
15
  'toolslm.download.read_md': ('download.html#read_md', 'toolslm/download.py'),
16
16
  'toolslm.download.split_url': ('download.html#split_url', 'toolslm/download.py')},
17
- 'toolslm.funccall': { 'toolslm.funccall._copy_loc': ('funccall.html#_copy_loc', 'toolslm/funccall.py'),
17
+ 'toolslm.funccall': { 'toolslm.funccall.PathArg': ('funccall.html#patharg', 'toolslm/funccall.py'),
18
+ 'toolslm.funccall._copy_loc': ('funccall.html#_copy_loc', 'toolslm/funccall.py'),
18
19
  'toolslm.funccall._get_nested_schema': ('funccall.html#_get_nested_schema', 'toolslm/funccall.py'),
19
20
  'toolslm.funccall._handle_container': ('funccall.html#_handle_container', 'toolslm/funccall.py'),
20
21
  'toolslm.funccall._handle_type': ('funccall.html#_handle_type', 'toolslm/funccall.py'),
22
+ 'toolslm.funccall._is_container': ('funccall.html#_is_container', 'toolslm/funccall.py'),
23
+ 'toolslm.funccall._is_parameterized': ('funccall.html#_is_parameterized', 'toolslm/funccall.py'),
21
24
  'toolslm.funccall._param': ('funccall.html#_param', 'toolslm/funccall.py'),
22
25
  'toolslm.funccall._process_property': ('funccall.html#_process_property', 'toolslm/funccall.py'),
23
26
  'toolslm.funccall._run': ('funccall.html#_run', 'toolslm/funccall.py'),
24
27
  'toolslm.funccall._types': ('funccall.html#_types', 'toolslm/funccall.py'),
25
28
  'toolslm.funccall.call_func': ('funccall.html#call_func', 'toolslm/funccall.py'),
29
+ 'toolslm.funccall.call_func_async': ('funccall.html#call_func_async', 'toolslm/funccall.py'),
26
30
  'toolslm.funccall.get_schema': ('funccall.html#get_schema', 'toolslm/funccall.py'),
27
31
  'toolslm.funccall.mk_ns': ('funccall.html#mk_ns', 'toolslm/funccall.py'),
28
32
  'toolslm.funccall.python': ('funccall.html#python', 'toolslm/funccall.py')},
toolslm/download.py CHANGED
@@ -27,10 +27,10 @@ def read_md(url, rm_comments=True, rm_details=True, **kwargs):
27
27
  return clean_md(get(url, **kwargs).text, rm_comments=rm_comments, rm_details=rm_details)
28
28
 
29
29
  # %% ../03_download.ipynb 7
30
- def html2md(s:str):
30
+ def html2md(s:str, ignore_links=True):
31
31
  "Convert `s` from HTML to markdown"
32
32
  o = HTML2Text(bodywidth=5000)
33
- o.ignore_links = True
33
+ o.ignore_links = ignore_links
34
34
  o.mark_code = True
35
35
  o.ignore_images = True
36
36
  return o.handle(s)
@@ -42,6 +42,7 @@ def read_html(url, # URL to read
42
42
  rm_details=True, # Removes `<details>` tags
43
43
  multi=False, # Get all matches to `sel` or first one
44
44
  wrap_tag=None, #If multi, each selection wrapped with <wrap_tag>content</wrap_tag>
45
+ ignore_links=True,
45
46
  ): # Cleaned markdown
46
47
  "Get `url`, optionally selecting CSS selector `sel`, and convert to clean markdown"
47
48
  page = get(url).text
@@ -51,12 +52,12 @@ def read_html(url, # URL to read
51
52
  page = [str(el) for el in soup.select(sel)]
52
53
  if not wrap_tag: page = "\n".join(page)
53
54
  else: page = str(soup.select_one(sel))
54
- mds = map(lambda x: clean_md(html2md(x), rm_comments, rm_details=rm_details), tuplify(page))
55
+ mds = map(lambda x: clean_md(html2md(x, ignore_links=ignore_links), rm_comments, rm_details=rm_details), tuplify(page))
55
56
  if wrap_tag: return '\n'.join([f"\n<{wrap_tag}>\n{o}</{wrap_tag}>\n" for o in mds])
56
57
  else: return'\n'.join(mds)
57
58
 
58
59
 
59
- # %% ../03_download.ipynb 12
60
+ # %% ../03_download.ipynb 13
60
61
  def get_llmstxt(url, optional=False, n_workers=None):
61
62
  "Get llms.txt file from and expand it with `llms_txt.create_ctx()`"
62
63
  if not url.endswith('llms.txt'): return None
@@ -64,7 +65,7 @@ def get_llmstxt(url, optional=False, n_workers=None):
64
65
  if resp.status_code!=200: return None
65
66
  return create_ctx(resp.text, optional=optional, n_workers=n_workers)
66
67
 
67
- # %% ../03_download.ipynb 14
68
+ # %% ../03_download.ipynb 15
68
69
  def split_url(url):
69
70
  "Split `url` into base, path, and file name, normalising name to '/' if empty"
70
71
  parsed = urlparse(url.strip('/'))
@@ -74,13 +75,13 @@ def split_url(url):
74
75
  if not path and not fname: path='/'
75
76
  return base,path,fname
76
77
 
77
- # %% ../03_download.ipynb 16
78
+ # %% ../03_download.ipynb 17
78
79
  def _tryget(url):
79
80
  "Return response from `url` if `status_code!=404`, otherwise `None`"
80
81
  res = get(url)
81
82
  return None if res.status_code==404 else url
82
83
 
83
- # %% ../03_download.ipynb 17
84
+ # %% ../03_download.ipynb 18
84
85
  def find_docs(url):
85
86
  "If available, return LLM-friendly llms.txt context or markdown file location from `url`"
86
87
  base,path,fname = split_url(url)
@@ -100,7 +101,7 @@ def find_docs(url):
100
101
  if parsed_url.path == '/' or not parsed_url.path: return None
101
102
  return find_docs(urljoin(url, '..'))
102
103
 
103
- # %% ../03_download.ipynb 22
104
+ # %% ../03_download.ipynb 23
104
105
  def read_docs(url, optional=False, n_workers=None, rm_comments=True, rm_details=True):
105
106
  "If available, return LLM-friendly llms.txt context or markdown file response for `url`"
106
107
  url = find_docs(url)
toolslm/funccall.py CHANGED
@@ -1,13 +1,15 @@
1
1
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../01_funccall.ipynb.
2
2
 
3
3
  # %% auto 0
4
- __all__ = ['empty', 'get_schema', 'python', 'mk_ns', 'call_func']
4
+ __all__ = ['empty', 'custom_types', 'get_schema', 'PathArg', 'python', 'mk_ns', 'call_func', 'call_func_async']
5
5
 
6
6
  # %% ../01_funccall.ipynb 2
7
7
  import inspect
8
8
  from collections import abc
9
9
  from fastcore.utils import *
10
10
  from fastcore.docments import docments
11
+ from typing import get_origin, get_args, Dict, List, Optional, Tuple, Union
12
+ from types import UnionType
11
13
 
12
14
  # %% ../01_funccall.ipynb 3
13
15
  empty = inspect.Parameter.empty
@@ -18,11 +20,18 @@ def _types(t:type)->tuple[str,Optional[str]]:
18
20
  if t is empty: raise TypeError('Missing type')
19
21
  tmap = {int:"integer", float:"number", str:"string", bool:"boolean", list:"array", dict:"object"}
20
22
  tmap.update({k.__name__: v for k, v in tmap.items()})
21
- if getattr(t, '__origin__', None) in (list,tuple): return "array", tmap.get(t.__args__[0].__name__, "object")
23
+ if getattr(t, '__origin__', None) in (list,tuple):
24
+ args = getattr(t, '__args__', None)
25
+ item_type = "object" if not args else tmap.get(t.__args__[0].__name__, "object")
26
+ return "array", item_type
27
+ # if t is a string like 'int', directly use the string as the key
22
28
  elif isinstance(t, str): return tmap.get(t, "object"), None
29
+ # if t is the type itself and a container
30
+ elif get_origin(t): return tmap.get(get_origin(t).__name__, "object"), None
31
+ # if t is the type itself like int, use the __name__ representation as the key
23
32
  else: return tmap.get(t.__name__, "object"), None
24
33
 
25
- # %% ../01_funccall.ipynb 16
34
+ # %% ../01_funccall.ipynb 18
26
35
  def _param(name, info):
27
36
  "json schema parameter given `name` and `info` from docments full dict."
28
37
  paramt,itemt = _types(info.anno)
@@ -31,17 +40,33 @@ def _param(name, info):
31
40
  if info.default is not empty: pschema["default"] = info.default
32
41
  return pschema
33
42
 
34
- # %% ../01_funccall.ipynb 19
43
+ # %% ../01_funccall.ipynb 21
44
+ custom_types = {Path}
45
+
35
46
  def _handle_type(t, defs):
36
47
  "Handle a single type, creating nested schemas if necessary"
37
- if isinstance(t, type) and not issubclass(t, (int, float, str, bool)):
48
+ if t is NoneType: return {'type': 'null'}
49
+ if t in custom_types: return {'type':'string', 'format':t.__name__}
50
+ if isinstance(t, type) and not issubclass(t, (int, float, str, bool)) or inspect.isfunction(t):
38
51
  defs[t.__name__] = _get_nested_schema(t)
39
52
  return {'$ref': f'#/$defs/{t.__name__}'}
40
53
  return {'type': _types(t)[0]}
41
54
 
42
- # %% ../01_funccall.ipynb 20
55
+ # %% ../01_funccall.ipynb 23
56
+ def _is_container(t):
57
+ "Check if type is a container (list, dict, tuple, set, Union)"
58
+ origin = get_origin(t)
59
+ return origin in (list, dict, tuple, set, Union) if origin else False
60
+
61
+ def _is_parameterized(t):
62
+ "Check if type has arguments (e.g. list[int] vs list, dict[str, int] vs dict)"
63
+ return _is_container(t) and (get_args(t) != ())
64
+
65
+ # %% ../01_funccall.ipynb 29
43
66
  def _handle_container(origin, args, defs):
44
- "Handle container types like dict, list, tuple, set"
67
+ "Handle container types like dict, list, tuple, set, and Union"
68
+ if origin is Union or origin is UnionType:
69
+ return {"anyOf": [_handle_type(arg, defs) for arg in args]}
45
70
  if origin is dict:
46
71
  value_type = args[1].__args__[0] if hasattr(args[1], '__args__') else args[1]
47
72
  return {
@@ -58,34 +83,35 @@ def _handle_container(origin, args, defs):
58
83
  return schema
59
84
  return None
60
85
 
61
- # %% ../01_funccall.ipynb 21
86
+ # %% ../01_funccall.ipynb 30
62
87
  def _process_property(name, obj, props, req, defs):
63
88
  "Process a single property of the schema"
64
89
  p = _param(name, obj)
65
90
  props[name] = p
66
91
  if obj.default is empty: req[name] = True
67
-
68
- if hasattr(obj.anno, '__origin__'):
69
- p.update(_handle_container(obj.anno.__origin__, obj.anno.__args__, defs))
92
+
93
+ if _is_container(obj.anno) and _is_parameterized(obj.anno):
94
+ p.update(_handle_container(get_origin(obj.anno), get_args(obj.anno), defs))
70
95
  else:
96
+ # Non-container type or container without arguments
71
97
  p.update(_handle_type(obj.anno, defs))
72
98
 
73
- # %% ../01_funccall.ipynb 22
99
+ # %% ../01_funccall.ipynb 31
74
100
  def _get_nested_schema(obj):
75
101
  "Generate nested JSON schema for a class or function"
76
102
  d = docments(obj, full=True)
77
103
  props, req, defs = {}, {}, {}
78
-
104
+
79
105
  for n, o in d.items():
80
106
  if n != 'return' and n != 'self':
81
107
  _process_property(n, o, props, req, defs)
82
-
108
+
83
109
  schema = dict(type='object', properties=props, title=obj.__name__ if isinstance(obj, type) else None)
84
110
  if req: schema['required'] = list(req)
85
111
  if defs: schema['$defs'] = defs
86
112
  return schema
87
113
 
88
- # %% ../01_funccall.ipynb 26
114
+ # %% ../01_funccall.ipynb 35
89
115
  def get_schema(f:callable, pname='input_schema')->dict:
90
116
  "Generate JSON schema for a class, function, or method"
91
117
  schema = _get_nested_schema(f)
@@ -96,11 +122,16 @@ def get_schema(f:callable, pname='input_schema')->dict:
96
122
  if ret.anno is not empty: desc += f'\n\nReturns:\n- type: {_types(ret.anno)[0]}'
97
123
  return {"name": f.__name__, "description": desc, pname: schema}
98
124
 
99
- # %% ../01_funccall.ipynb 39
125
+ # %% ../01_funccall.ipynb 46
126
+ def PathArg(
127
+ path: str # A filesystem path
128
+ ): return Path(path)
129
+
130
+ # %% ../01_funccall.ipynb 66
100
131
  import ast, time, signal, traceback
101
132
  from fastcore.utils import *
102
133
 
103
- # %% ../01_funccall.ipynb 40
134
+ # %% ../01_funccall.ipynb 67
104
135
  def _copy_loc(new, orig):
105
136
  "Copy location information from original node to new node and all children."
106
137
  new = ast.copy_location(new, orig)
@@ -109,7 +140,7 @@ def _copy_loc(new, orig):
109
140
  elif isinstance(o, list): setattr(new, field, [_copy_loc(value, orig) for value in o])
110
141
  return new
111
142
 
112
- # %% ../01_funccall.ipynb 42
143
+ # %% ../01_funccall.ipynb 69
113
144
  def _run(code:str ):
114
145
  "Run `code`, returning final expression (similar to IPython)"
115
146
  tree = ast.parse(code)
@@ -132,7 +163,7 @@ def _run(code:str ):
132
163
  if _result is not None: return _result
133
164
  return stdout_buffer.getvalue().strip()
134
165
 
135
- # %% ../01_funccall.ipynb 47
166
+ # %% ../01_funccall.ipynb 74
136
167
  def python(code, # Code to execute
137
168
  timeout=5 # Maximum run time in seconds before a `TimeoutError` is raised
138
169
  ): # Result of last node, if it's an expression, or `None` otherwise
@@ -145,7 +176,7 @@ def python(code, # Code to execute
145
176
  except Exception as e: return traceback.format_exc()
146
177
  finally: signal.alarm(0)
147
178
 
148
- # %% ../01_funccall.ipynb 54
179
+ # %% ../01_funccall.ipynb 81
149
180
  def mk_ns(*funcs_or_objs):
150
181
  merged = {}
151
182
  for o in funcs_or_objs:
@@ -154,9 +185,16 @@ def mk_ns(*funcs_or_objs):
154
185
  if callable(o) and hasattr(o, '__name__'): merged |= {o.__name__: o}
155
186
  return merged
156
187
 
157
- # %% ../01_funccall.ipynb 63
188
+ # %% ../01_funccall.ipynb 90
158
189
  def call_func(fc_name, fc_inputs, ns):
159
190
  "Call the function `fc_name` with the given `fc_inputs` using namespace `ns`."
160
191
  if not isinstance(ns, abc.Mapping): ns = mk_ns(*ns)
161
192
  func = ns[fc_name]
162
193
  return func(**fc_inputs)
194
+
195
+ # %% ../01_funccall.ipynb 97
196
+ async def call_func_async(fc_name, fc_inputs, ns):
197
+ "Awaits the function `fc_name` with the given `fc_inputs` using namespace `ns`."
198
+ if not isinstance(ns, abc.Mapping): ns = mk_ns(*ns)
199
+ func = ns[fc_name]
200
+ return await func(**fc_inputs)
toolslm/md_hier.py CHANGED
@@ -7,9 +7,15 @@ def markdown_to_dict(markdown_content):
7
7
 
8
8
  lines = markdown_content.splitlines()
9
9
  headings = []
10
+ in_code_block = False
10
11
 
11
12
  # Parse headings with their levels and line numbers
12
13
  for idx, line in enumerate(lines):
14
+ # Toggle code block state when encountering fence
15
+ if line.strip().startswith('```'): in_code_block = not in_code_block
16
+
17
+ # Only detect headings when not in a code block
18
+ if in_code_block: continue
13
19
  match = re.match(r'^(#{1,6})\s*(.*)', line)
14
20
  if match:
15
21
  level = len(match.group(1))
@@ -29,13 +35,15 @@ def markdown_to_dict(markdown_content):
29
35
 
30
36
  # Build the dictionary with hierarchical keys
31
37
  result,stack = {},[]
38
+ first_level = headings[0]['level']
32
39
  for h in headings:
33
- stack = stack[:h['level'] - 1] + [clean_heading(h['text'])]
40
+ stack = stack[:h['level'] - first_level] + [clean_heading(h['text'])]
34
41
  key = '.'.join(stack)
35
42
  result[key] = h['content']
36
43
  return dict2obj(result)
37
44
 
38
45
  def create_heading_dict(text):
46
+ text = re.sub(r'```[\s\S]*?```', '', text)
39
47
  headings = re.findall(r'^#+.*', text, flags=re.MULTILINE)
40
48
  result = {}
41
49
  stack = [result]
@@ -126,11 +134,42 @@ Admin users management.
126
134
  assert result['Parent.Child'] == '## Child\nChild content.\n### Grandchild\nGrandchild content.'
127
135
  assert result['Parent.Child.Grandchild'] == '### Grandchild\nGrandchild content.'
128
136
 
137
+ def test_multiple_level2_siblings():
138
+ md_content = "##Sib 1\n##Sib 2\n##Sib 3\n##Sib 4\n##Sib 5'"
139
+ result = markdown_to_dict(md_content)
140
+ assert 'Sib 1' in result
141
+ assert 'Sib 2' in result
142
+ assert 'Sib 3' in result
143
+ assert 'Sib 4' in result
144
+ assert 'Sib 5' in result
145
+
146
+ def test_code_chunks_escaped():
147
+ md_content = "# Parent\nParent content.\n## Child\nChild content.\n```python\n# Code comment\nprint('Hello, world!')\n```"
148
+ result = markdown_to_dict(md_content)
149
+ assert 'Code comment' not in result
150
+ assert "# Code comment" in result['Parent.Child']
151
+
129
152
  test_empty_content()
130
153
  test_special_characters()
131
154
  test_duplicate_headings()
132
155
  test_no_content()
133
156
  test_different_levels()
134
157
  test_parent_includes_subheadings()
158
+ test_multiple_level2_siblings()
159
+ test_code_chunks_escaped()
135
160
  print('tests passed')
136
161
 
162
+ def test_nested_headings():
163
+ md_content = "# Parent\nParent content.\n## Child\nChild content.\n### Grandchild\nGrandchild content."
164
+ result = create_heading_dict(md_content)
165
+ assert 'Child' in result['Parent']
166
+ assert 'Grandchild' in result['Parent']['Child']
167
+
168
+ def test_code_chunks_escaped():
169
+ md_content = "# Parent\nParent content.\n## Child\nChild content.\n```python\n# Code comment\nprint('Hello, world!')\n```"
170
+ result = create_heading_dict(md_content)
171
+ assert 'Code comment' not in result
172
+
173
+ test_nested_headings()
174
+ test_code_chunks_escaped()
175
+ print('tests passed')
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: toolslm
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Tools to make language models a bit easier to use
5
5
  Home-page: https://github.com/AnswerDotAI/toolslm
6
6
  Author: Jeremy Howard
@@ -20,8 +20,21 @@ Requires-Dist: fastcore>=1.5.47
20
20
  Requires-Dist: beautifulsoup4
21
21
  Requires-Dist: html2text
22
22
  Requires-Dist: httpx
23
- Requires-Dist: llms-txt
23
+ Requires-Dist: llms_txt
24
24
  Provides-Extra: dev
25
+ Dynamic: author
26
+ Dynamic: author-email
27
+ Dynamic: classifier
28
+ Dynamic: description
29
+ Dynamic: description-content-type
30
+ Dynamic: home-page
31
+ Dynamic: keywords
32
+ Dynamic: license
33
+ Dynamic: license-file
34
+ Dynamic: provides-extra
35
+ Dynamic: requires-dist
36
+ Dynamic: requires-python
37
+ Dynamic: summary
25
38
 
26
39
  # toolslm
27
40
 
@@ -0,0 +1,13 @@
1
+ toolslm/__init__.py,sha256=YvuYzWnKtqBb-IqG8HAu-nhIYAsgj9Vmc_b9o7vO-js,22
2
+ toolslm/_modidx.py,sha256=-D-B5o30VGs11gBKf96lpADVXnZhdiVEshJpLzmUnDs,4378
3
+ toolslm/download.py,sha256=d84O8PCX7uAbLg0HTbNNfCdANPcnhXFu4qzOtcfJfHU,4465
4
+ toolslm/funccall.py,sha256=yAZmu2gIRtND-5pOuLCp7P4dor0FV_xntcacnZ81j0M,8210
5
+ toolslm/md_hier.py,sha256=4uC12443tPBduYJgIZZIcEat2VG0x7JYC8-SwDdS2JY,6360
6
+ toolslm/shell.py,sha256=GVqfL74NHw66zzZ7jvGVLjE55ZNJGBPvEb8kLz4aoYc,1576
7
+ toolslm/xml.py,sha256=QNwUavoMkFK84D7dMwnBjqlYJwN-pJ7u3BxOeDuNAmk,4088
8
+ toolslm-0.1.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
9
+ toolslm-0.1.2.dist-info/METADATA,sha256=gGurWuj5SWNPQvCPKbd8oc5Iz9x4NPxv8bx_C8w3Kps,2483
10
+ toolslm-0.1.2.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
11
+ toolslm-0.1.2.dist-info/entry_points.txt,sha256=xFz0Eymlo5X7BGpaO6DI9gMxvN5A7faebzrlr8ctp5I,95
12
+ toolslm-0.1.2.dist-info/top_level.txt,sha256=4hRTrFWayz_Kz5221XjvlpCwVFrW3WPi1P0fllkTq9s,8
13
+ toolslm-0.1.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.3.0)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,13 +0,0 @@
1
- toolslm/__init__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
2
- toolslm/_modidx.py,sha256=EIl2FBWhcZUS46r1AU0wURYg2O6Z3aXTPUr3p8Smrqk,3882
3
- toolslm/download.py,sha256=tXhq77GCqwVFDzTtzjcSAjxUWRiyPsjlXzkMjleH3dQ,4378
4
- toolslm/funccall.py,sha256=hSvBvfMv-YcBSUUs4-NrYu1f8jg4gfu2s82cPyIHVkU,6534
5
- toolslm/md_hier.py,sha256=hkCjuOfIFWuMEiM2_XCoD9QIBjy9huLOSvpX_bMdn0Y,4645
6
- toolslm/shell.py,sha256=GVqfL74NHw66zzZ7jvGVLjE55ZNJGBPvEb8kLz4aoYc,1576
7
- toolslm/xml.py,sha256=QNwUavoMkFK84D7dMwnBjqlYJwN-pJ7u3BxOeDuNAmk,4088
8
- toolslm-0.1.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
9
- toolslm-0.1.0.dist-info/METADATA,sha256=tQBydygSCJdH_wQaIbzC8Z8rZanQTJOdmpe1nEETkdE,2205
10
- toolslm-0.1.0.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
11
- toolslm-0.1.0.dist-info/entry_points.txt,sha256=xFz0Eymlo5X7BGpaO6DI9gMxvN5A7faebzrlr8ctp5I,95
12
- toolslm-0.1.0.dist-info/top_level.txt,sha256=4hRTrFWayz_Kz5221XjvlpCwVFrW3WPi1P0fllkTq9s,8
13
- toolslm-0.1.0.dist-info/RECORD,,