onetool-mcp 1.0.0b1__py3-none-any.whl → 1.0.0rc2__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.
Files changed (81) hide show
  1. onetool/cli.py +63 -4
  2. onetool_mcp-1.0.0rc2.dist-info/METADATA +266 -0
  3. onetool_mcp-1.0.0rc2.dist-info/RECORD +129 -0
  4. {onetool_mcp-1.0.0b1.dist-info → onetool_mcp-1.0.0rc2.dist-info}/licenses/LICENSE.txt +1 -1
  5. {onetool_mcp-1.0.0b1.dist-info → onetool_mcp-1.0.0rc2.dist-info}/licenses/NOTICE.txt +54 -64
  6. ot/__main__.py +6 -6
  7. ot/config/__init__.py +48 -46
  8. ot/config/global_templates/__init__.py +2 -2
  9. ot/config/{defaults → global_templates}/diagram-templates/api-flow.mmd +33 -33
  10. ot/config/{defaults → global_templates}/diagram-templates/c4-context.puml +30 -30
  11. ot/config/{defaults → global_templates}/diagram-templates/class-diagram.mmd +87 -87
  12. ot/config/{defaults → global_templates}/diagram-templates/feature-mindmap.mmd +70 -70
  13. ot/config/{defaults → global_templates}/diagram-templates/microservices.d2 +81 -81
  14. ot/config/{defaults → global_templates}/diagram-templates/project-gantt.mmd +37 -37
  15. ot/config/{defaults → global_templates}/diagram-templates/state-machine.mmd +42 -42
  16. ot/config/global_templates/diagram.yaml +167 -0
  17. ot/config/global_templates/onetool.yaml +3 -1
  18. ot/config/{defaults → global_templates}/prompts.yaml +102 -97
  19. ot/config/global_templates/security.yaml +31 -0
  20. ot/config/global_templates/servers.yaml +93 -12
  21. ot/config/global_templates/snippets.yaml +5 -26
  22. ot/config/{defaults → global_templates}/tool_templates/__init__.py +7 -7
  23. ot/config/loader.py +221 -105
  24. ot/config/mcp.py +5 -1
  25. ot/config/secrets.py +192 -190
  26. ot/decorators.py +116 -116
  27. ot/executor/__init__.py +35 -35
  28. ot/executor/base.py +16 -16
  29. ot/executor/fence_processor.py +83 -83
  30. ot/executor/linter.py +142 -142
  31. ot/executor/pep723.py +288 -288
  32. ot/executor/runner.py +20 -6
  33. ot/executor/simple.py +163 -163
  34. ot/executor/validator.py +603 -164
  35. ot/http_client.py +145 -145
  36. ot/logging/__init__.py +37 -37
  37. ot/logging/entry.py +213 -213
  38. ot/logging/format.py +191 -188
  39. ot/logging/span.py +349 -349
  40. ot/meta.py +236 -14
  41. ot/paths.py +32 -49
  42. ot/prompts.py +218 -218
  43. ot/proxy/manager.py +14 -2
  44. ot/registry/__init__.py +189 -189
  45. ot/registry/parser.py +269 -269
  46. ot/server.py +330 -315
  47. ot/shortcuts/__init__.py +15 -15
  48. ot/shortcuts/aliases.py +87 -87
  49. ot/shortcuts/snippets.py +258 -258
  50. ot/stats/__init__.py +35 -35
  51. ot/stats/html.py +2 -2
  52. ot/stats/reader.py +354 -354
  53. ot/stats/timing.py +57 -57
  54. ot/support.py +63 -63
  55. ot/tools.py +1 -1
  56. ot/utils/batch.py +161 -161
  57. ot/utils/cache.py +120 -120
  58. ot/utils/exceptions.py +23 -23
  59. ot/utils/factory.py +178 -179
  60. ot/utils/format.py +65 -65
  61. ot/utils/http.py +202 -202
  62. ot/utils/platform.py +45 -45
  63. ot/utils/truncate.py +69 -69
  64. ot_tools/__init__.py +4 -4
  65. ot_tools/_convert/__init__.py +12 -12
  66. ot_tools/_convert/pdf.py +254 -254
  67. ot_tools/diagram.yaml +167 -167
  68. ot_tools/scaffold.py +2 -2
  69. ot_tools/transform.py +124 -19
  70. ot_tools/web_fetch.py +94 -43
  71. onetool_mcp-1.0.0b1.dist-info/METADATA +0 -163
  72. onetool_mcp-1.0.0b1.dist-info/RECORD +0 -132
  73. ot/config/defaults/bench.yaml +0 -4
  74. ot/config/defaults/onetool.yaml +0 -25
  75. ot/config/defaults/servers.yaml +0 -7
  76. ot/config/defaults/snippets.yaml +0 -4
  77. ot_tools/firecrawl.py +0 -732
  78. {onetool_mcp-1.0.0b1.dist-info → onetool_mcp-1.0.0rc2.dist-info}/WHEEL +0 -0
  79. {onetool_mcp-1.0.0b1.dist-info → onetool_mcp-1.0.0rc2.dist-info}/entry_points.txt +0 -0
  80. /ot/config/{defaults → global_templates}/tool_templates/extension.py +0 -0
  81. /ot/config/{defaults → global_templates}/tool_templates/isolated.py +0 -0
ot/utils/cache.py CHANGED
@@ -1,120 +1,120 @@
1
- """In-memory caching with TTL for tools.
2
-
3
- Provides both a decorator for function memoization and manual cache operations.
4
- Cache persists for the lifetime of the process.
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- import functools
10
- import time
11
- from collections.abc import Callable
12
- from typing import Any, TypeVar
13
-
14
- __all__ = ["CacheNamespace", "cache"]
15
-
16
- F = TypeVar("F", bound=Callable[..., Any])
17
-
18
-
19
- class CacheEntry:
20
- """A cached value with expiration time."""
21
-
22
- def __init__(self, value: Any, ttl: float) -> None:
23
- self.value = value
24
- self.expires_at = time.time() + ttl
25
-
26
-
27
- class CacheNamespace:
28
- """Cache namespace with get/set/clear operations and decorator."""
29
-
30
- def __init__(self) -> None:
31
- self._store: dict[str, CacheEntry] = {}
32
- self._memoize_stores: dict[str, dict[str, CacheEntry]] = {}
33
-
34
- def get(self, key: str) -> Any | None:
35
- """Get a cached value by key.
36
-
37
- Args:
38
- key: Cache key
39
-
40
- Returns:
41
- Cached value, or None if not found or expired
42
- """
43
- entry = self._store.get(key)
44
- if entry is None:
45
- return None
46
- if time.time() > entry.expires_at:
47
- del self._store[key]
48
- return None
49
- return entry.value
50
-
51
- def set(self, key: str, value: Any, ttl: float = 300.0) -> None:
52
- """Set a cached value with TTL.
53
-
54
- Args:
55
- key: Cache key
56
- value: Value to cache
57
- ttl: Time-to-live in seconds (default: 5 minutes)
58
- """
59
- self._store[key] = CacheEntry(value, ttl)
60
-
61
- def clear(self, key: str | None = None) -> None:
62
- """Clear cached values.
63
-
64
- Args:
65
- key: Specific key to clear, or None to clear all
66
- """
67
- if key is None:
68
- self._store.clear()
69
- self._memoize_stores.clear()
70
- else:
71
- self._store.pop(key, None)
72
-
73
- def __call__(self, ttl: float = 300.0) -> Callable[[F], F]:
74
- """Decorator for memoizing function results with TTL.
75
-
76
- Args:
77
- ttl: Time-to-live in seconds (default: 5 minutes)
78
-
79
- Returns:
80
- Decorator function
81
-
82
- Example:
83
- @cache(ttl=60)
84
- def expensive_operation(query: str) -> str:
85
- ...
86
- """
87
-
88
- def decorator(func: F) -> F:
89
- # Create a separate store for this function
90
- func_id = f"{func.__module__}.{func.__qualname__}"
91
- self._memoize_stores[func_id] = {}
92
-
93
- @functools.wraps(func)
94
- def wrapper(*args: Any, **kwargs: Any) -> Any:
95
- # Build cache key from args and kwargs
96
- key_parts = [repr(arg) for arg in args]
97
- key_parts.extend(f"{k}={v!r}" for k, v in sorted(kwargs.items()))
98
- cache_key = ":".join(key_parts)
99
-
100
- # Get or create store (may have been cleared by cache.clear())
101
- store = self._memoize_stores.get(func_id)
102
- if store is None:
103
- store = {}
104
- self._memoize_stores[func_id] = store
105
- entry = store.get(cache_key)
106
-
107
- if entry is not None and time.time() <= entry.expires_at:
108
- return entry.value
109
-
110
- result = func(*args, **kwargs)
111
- store[cache_key] = CacheEntry(result, ttl)
112
- return result
113
-
114
- return wrapper # type: ignore[return-value]
115
-
116
- return decorator
117
-
118
-
119
- # Singleton instance
120
- cache = CacheNamespace()
1
+ """In-memory caching with TTL for tools.
2
+
3
+ Provides both a decorator for function memoization and manual cache operations.
4
+ Cache persists for the lifetime of the process.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import functools
10
+ import time
11
+ from collections.abc import Callable
12
+ from typing import Any, TypeVar
13
+
14
+ __all__ = ["CacheNamespace", "cache"]
15
+
16
+ F = TypeVar("F", bound=Callable[..., Any])
17
+
18
+
19
+ class CacheEntry:
20
+ """A cached value with expiration time."""
21
+
22
+ def __init__(self, value: Any, ttl: float) -> None:
23
+ self.value = value
24
+ self.expires_at = time.time() + ttl
25
+
26
+
27
+ class CacheNamespace:
28
+ """Cache namespace with get/set/clear operations and decorator."""
29
+
30
+ def __init__(self) -> None:
31
+ self._store: dict[str, CacheEntry] = {}
32
+ self._memoize_stores: dict[str, dict[str, CacheEntry]] = {}
33
+
34
+ def get(self, key: str) -> Any | None:
35
+ """Get a cached value by key.
36
+
37
+ Args:
38
+ key: Cache key
39
+
40
+ Returns:
41
+ Cached value, or None if not found or expired
42
+ """
43
+ entry = self._store.get(key)
44
+ if entry is None:
45
+ return None
46
+ if time.time() > entry.expires_at:
47
+ del self._store[key]
48
+ return None
49
+ return entry.value
50
+
51
+ def set(self, key: str, value: Any, ttl: float = 300.0) -> None:
52
+ """Set a cached value with TTL.
53
+
54
+ Args:
55
+ key: Cache key
56
+ value: Value to cache
57
+ ttl: Time-to-live in seconds (default: 5 minutes)
58
+ """
59
+ self._store[key] = CacheEntry(value, ttl)
60
+
61
+ def clear(self, key: str | None = None) -> None:
62
+ """Clear cached values.
63
+
64
+ Args:
65
+ key: Specific key to clear, or None to clear all
66
+ """
67
+ if key is None:
68
+ self._store.clear()
69
+ self._memoize_stores.clear()
70
+ else:
71
+ self._store.pop(key, None)
72
+
73
+ def __call__(self, ttl: float = 300.0) -> Callable[[F], F]:
74
+ """Decorator for memoizing function results with TTL.
75
+
76
+ Args:
77
+ ttl: Time-to-live in seconds (default: 5 minutes)
78
+
79
+ Returns:
80
+ Decorator function
81
+
82
+ Example:
83
+ @cache(ttl=60)
84
+ def expensive_operation(query: str) -> str:
85
+ ...
86
+ """
87
+
88
+ def decorator(func: F) -> F:
89
+ # Create a separate store for this function
90
+ func_id = f"{func.__module__}.{func.__qualname__}"
91
+ self._memoize_stores[func_id] = {}
92
+
93
+ @functools.wraps(func)
94
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
95
+ # Build cache key from args and kwargs
96
+ key_parts = [repr(arg) for arg in args]
97
+ key_parts.extend(f"{k}={v!r}" for k, v in sorted(kwargs.items()))
98
+ cache_key = ":".join(key_parts)
99
+
100
+ # Get or create store (may have been cleared by cache.clear())
101
+ store = self._memoize_stores.get(func_id)
102
+ if store is None:
103
+ store = {}
104
+ self._memoize_stores[func_id] = store
105
+ entry = store.get(cache_key)
106
+
107
+ if entry is not None and time.time() <= entry.expires_at:
108
+ return entry.value
109
+
110
+ result = func(*args, **kwargs)
111
+ store[cache_key] = CacheEntry(result, ttl)
112
+ return result
113
+
114
+ return wrapper # type: ignore[return-value]
115
+
116
+ return decorator
117
+
118
+
119
+ # Singleton instance
120
+ cache = CacheNamespace()
ot/utils/exceptions.py CHANGED
@@ -1,23 +1,23 @@
1
- """Exception handling utilities."""
2
-
3
- from __future__ import annotations
4
-
5
-
6
- def flatten_exception_group(
7
- eg: BaseExceptionGroup[BaseException],
8
- ) -> list[BaseException]:
9
- """Recursively flatten nested ExceptionGroups to get leaf exceptions.
10
-
11
- Args:
12
- eg: The exception group to flatten.
13
-
14
- Returns:
15
- List of leaf exceptions (non-group exceptions).
16
- """
17
- result: list[BaseException] = []
18
- for exc in eg.exceptions:
19
- if isinstance(exc, BaseExceptionGroup):
20
- result.extend(flatten_exception_group(exc))
21
- else:
22
- result.append(exc)
23
- return result
1
+ """Exception handling utilities."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ def flatten_exception_group(
7
+ eg: BaseExceptionGroup[BaseException],
8
+ ) -> list[BaseException]:
9
+ """Recursively flatten nested ExceptionGroups to get leaf exceptions.
10
+
11
+ Args:
12
+ eg: The exception group to flatten.
13
+
14
+ Returns:
15
+ List of leaf exceptions (non-group exceptions).
16
+ """
17
+ result: list[BaseException] = []
18
+ for exc in eg.exceptions:
19
+ if isinstance(exc, BaseExceptionGroup):
20
+ result.extend(flatten_exception_group(exc))
21
+ else:
22
+ result.append(exc)
23
+ return result
ot/utils/factory.py CHANGED
@@ -1,179 +1,178 @@
1
- """Factory utilities for OneTool.
2
-
3
- Provides thread-safe lazy initialization patterns for API clients.
4
-
5
- Example:
6
- from ot.utils import lazy_client
7
-
8
- # Define a client factory
9
- def create_my_client():
10
- from mylib import Client
11
- api_key = get_secret("MY_API_KEY")
12
- return Client(api_key=api_key)
13
-
14
- # Create a lazy-initialized getter
15
- get_client = lazy_client(create_my_client)
16
-
17
- # Use it anywhere - initialized once, thread-safe
18
- client = get_client()
19
- """
20
-
21
- from __future__ import annotations
22
-
23
- import threading
24
- from typing import TYPE_CHECKING, TypeVar
25
-
26
- if TYPE_CHECKING:
27
- from collections.abc import Callable
28
-
29
- __all__ = ["LazyClient", "lazy_client"]
30
-
31
- T = TypeVar("T")
32
-
33
-
34
- def lazy_client(
35
- factory: Callable[[], T | None],
36
- *,
37
- allow_none: bool = False,
38
- ) -> Callable[[], T | None]:
39
- """Create a thread-safe lazy-initialized client getter.
40
-
41
- Wraps a factory function with double-checked locking to ensure
42
- the client is created exactly once, even under concurrent access.
43
-
44
- The factory function should:
45
- - Return the client instance on success
46
- - Return None if required credentials are missing
47
- - Raise exceptions for other errors
48
-
49
- Args:
50
- factory: Callable that creates and returns the client instance
51
- allow_none: If True, cache None results. If False (default), retry
52
- factory on each call when it returns None.
53
-
54
- Returns:
55
- A callable that returns the lazily-initialized client
56
-
57
- Example:
58
- from ot.utils import lazy_client
59
- from ot.config import get_secret, get_tool_config
60
-
61
- def create_firecrawl():
62
- from firecrawl import FirecrawlApp
63
- api_key = get_secret("FIRECRAWL_API_KEY")
64
- if not api_key:
65
- return None
66
- api_url = get_tool_config("firecrawl", Config).api_url
67
- return FirecrawlApp(api_key=api_key, api_url=api_url or None)
68
-
69
- get_firecrawl = lazy_client(create_firecrawl)
70
-
71
- # Later, in tool functions:
72
- def scrape(url: str) -> str:
73
- client = get_firecrawl()
74
- if client is None:
75
- return "Error: FIRECRAWL_API_KEY not configured"
76
- return client.scrape_url(url)
77
- """
78
- client: T | None = None
79
- initialized = False
80
- lock = threading.Lock()
81
-
82
- def get_client() -> T | None:
83
- nonlocal client, initialized
84
-
85
- # Fast path: already initialized
86
- if initialized:
87
- return client
88
-
89
- # Slow path: acquire lock and initialize
90
- with lock:
91
- # Double-check after acquiring lock
92
- if initialized:
93
- return client
94
-
95
- result = factory()
96
-
97
- # Cache result if successful or allow_none is True
98
- if result is not None or allow_none:
99
- client = result
100
- initialized = True
101
-
102
- return result
103
-
104
- return get_client
105
-
106
-
107
- class LazyClient:
108
- """Class-based lazy client for more complex initialization patterns.
109
-
110
- Useful when you need to pass the factory as a method or need
111
- additional client management features.
112
-
113
- Example:
114
- class MyPack:
115
- def __init__(self):
116
- self._client = LazyClient(self._create_client)
117
-
118
- def _create_client(self):
119
- return SomeClient(api_key=get_secret("API_KEY"))
120
-
121
- def search(self, query: str) -> str:
122
- client = self._client.get()
123
- if client is None:
124
- return "Error: API_KEY not configured"
125
- return client.search(query)
126
- """
127
-
128
- def __init__(
129
- self,
130
- factory: Callable[[], T | None],
131
- *,
132
- allow_none: bool = False,
133
- ) -> None:
134
- """Initialize the lazy client wrapper.
135
-
136
- Args:
137
- factory: Callable that creates the client
138
- allow_none: If True, cache None results
139
- """
140
- self._factory = factory
141
- self._allow_none = allow_none
142
- self._client: T | None = None
143
- self._initialized = False
144
- self._lock = threading.Lock()
145
-
146
- def get(self) -> T | None:
147
- """Get the client, initializing if necessary.
148
-
149
- Returns:
150
- The client instance, or None if not available
151
- """
152
- if self._initialized:
153
- return self._client # type: ignore[return-value]
154
-
155
- with self._lock:
156
- if self._initialized:
157
- return self._client
158
-
159
- result = self._factory()
160
-
161
- if result is not None or self._allow_none:
162
- self._client = result
163
- self._initialized = True
164
-
165
- return result # type: ignore[return-value]
166
-
167
- def reset(self) -> None:
168
- """Reset the client, forcing re-initialization on next access.
169
-
170
- Useful for testing or when configuration changes.
171
- """
172
- with self._lock:
173
- self._client = None
174
- self._initialized = False
175
-
176
- @property
177
- def is_initialized(self) -> bool:
178
- """Check if the client has been initialized."""
179
- return self._initialized
1
+ """Factory utilities for OneTool.
2
+
3
+ Provides thread-safe lazy initialization patterns for API clients.
4
+
5
+ Example:
6
+ from ot.utils import lazy_client
7
+
8
+ # Define a client factory
9
+ def create_my_client():
10
+ from mylib import Client
11
+ api_key = get_secret("MY_API_KEY")
12
+ return Client(api_key=api_key)
13
+
14
+ # Create a lazy-initialized getter
15
+ get_client = lazy_client(create_my_client)
16
+
17
+ # Use it anywhere - initialized once, thread-safe
18
+ client = get_client()
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import threading
24
+ from typing import TYPE_CHECKING, TypeVar
25
+
26
+ if TYPE_CHECKING:
27
+ from collections.abc import Callable
28
+
29
+ __all__ = ["LazyClient", "lazy_client"]
30
+
31
+ T = TypeVar("T")
32
+
33
+
34
+ def lazy_client(
35
+ factory: Callable[[], T | None],
36
+ *,
37
+ allow_none: bool = False,
38
+ ) -> Callable[[], T | None]:
39
+ """Create a thread-safe lazy-initialized client getter.
40
+
41
+ Wraps a factory function with double-checked locking to ensure
42
+ the client is created exactly once, even under concurrent access.
43
+
44
+ The factory function should:
45
+ - Return the client instance on success
46
+ - Return None if required credentials are missing
47
+ - Raise exceptions for other errors
48
+
49
+ Args:
50
+ factory: Callable that creates and returns the client instance
51
+ allow_none: If True, cache None results. If False (default), retry
52
+ factory on each call when it returns None.
53
+
54
+ Returns:
55
+ A callable that returns the lazily-initialized client
56
+
57
+ Example:
58
+ from ot.utils import lazy_client
59
+ from ot.config import get_secret
60
+
61
+ def create_brave():
62
+ from brave import Brave
63
+ api_key = get_secret("BRAVE_API_KEY")
64
+ if not api_key:
65
+ return None
66
+ return Brave(api_key=api_key)
67
+
68
+ get_brave = lazy_client(create_brave)
69
+
70
+ # Later, in tool functions:
71
+ def search(query: str) -> str:
72
+ client = get_brave()
73
+ if client is None:
74
+ return "Error: BRAVE_API_KEY not configured"
75
+ return client.search(query)
76
+ """
77
+ client: T | None = None
78
+ initialized = False
79
+ lock = threading.Lock()
80
+
81
+ def get_client() -> T | None:
82
+ nonlocal client, initialized
83
+
84
+ # Fast path: already initialized
85
+ if initialized:
86
+ return client
87
+
88
+ # Slow path: acquire lock and initialize
89
+ with lock:
90
+ # Double-check after acquiring lock
91
+ if initialized:
92
+ return client
93
+
94
+ result = factory()
95
+
96
+ # Cache result if successful or allow_none is True
97
+ if result is not None or allow_none:
98
+ client = result
99
+ initialized = True
100
+
101
+ return result
102
+
103
+ return get_client
104
+
105
+
106
+ class LazyClient:
107
+ """Class-based lazy client for more complex initialization patterns.
108
+
109
+ Useful when you need to pass the factory as a method or need
110
+ additional client management features.
111
+
112
+ Example:
113
+ class MyPack:
114
+ def __init__(self):
115
+ self._client = LazyClient(self._create_client)
116
+
117
+ def _create_client(self):
118
+ return SomeClient(api_key=get_secret("API_KEY"))
119
+
120
+ def search(self, query: str) -> str:
121
+ client = self._client.get()
122
+ if client is None:
123
+ return "Error: API_KEY not configured"
124
+ return client.search(query)
125
+ """
126
+
127
+ def __init__(
128
+ self,
129
+ factory: Callable[[], T | None],
130
+ *,
131
+ allow_none: bool = False,
132
+ ) -> None:
133
+ """Initialize the lazy client wrapper.
134
+
135
+ Args:
136
+ factory: Callable that creates the client
137
+ allow_none: If True, cache None results
138
+ """
139
+ self._factory = factory
140
+ self._allow_none = allow_none
141
+ self._client: T | None = None
142
+ self._initialized = False
143
+ self._lock = threading.Lock()
144
+
145
+ def get(self) -> T | None:
146
+ """Get the client, initializing if necessary.
147
+
148
+ Returns:
149
+ The client instance, or None if not available
150
+ """
151
+ if self._initialized:
152
+ return self._client # type: ignore[return-value]
153
+
154
+ with self._lock:
155
+ if self._initialized:
156
+ return self._client
157
+
158
+ result = self._factory()
159
+
160
+ if result is not None or self._allow_none:
161
+ self._client = result
162
+ self._initialized = True
163
+
164
+ return result # type: ignore[return-value]
165
+
166
+ def reset(self) -> None:
167
+ """Reset the client, forcing re-initialization on next access.
168
+
169
+ Useful for testing or when configuration changes.
170
+ """
171
+ with self._lock:
172
+ self._client = None
173
+ self._initialized = False
174
+
175
+ @property
176
+ def is_initialized(self) -> bool:
177
+ """Check if the client has been initialized."""
178
+ return self._initialized