toolbase 0.1.0__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 (55) hide show
  1. toolbase/__init__.py +22 -0
  2. toolbase/_setup_host.py +243 -0
  3. toolbase/_toolkit_host.py +585 -0
  4. toolbase/astro.py +9 -0
  5. toolbase/auth.py +631 -0
  6. toolbase/cli.py +5510 -0
  7. toolbase/config.py +41 -0
  8. toolbase/envs/__init__.py +147 -0
  9. toolbase/envs/cache.py +326 -0
  10. toolbase/envs/config.py +122 -0
  11. toolbase/envs/discovery.py +115 -0
  12. toolbase/envs/manifest.py +209 -0
  13. toolbase/envs/paths.py +163 -0
  14. toolbase/envs/schema.py +348 -0
  15. toolbase/hep.py +8 -0
  16. toolbase/ingest.py +913 -0
  17. toolbase/logging/__init__.py +5 -0
  18. toolbase/logging/logger.py +558 -0
  19. toolbase/neutrino.py +7 -0
  20. toolbase/quantum.py +7 -0
  21. toolbase/serve/__init__.py +7 -0
  22. toolbase/serve/config.py +436 -0
  23. toolbase/serve/orchestrator.py +1526 -0
  24. toolbase/serve/proxy_tool.py +134 -0
  25. toolbase/serve/tool_groups.py +189 -0
  26. toolbase/setup/__init__.py +91 -0
  27. toolbase/setup/_rpc.py +326 -0
  28. toolbase/setup/context.py +379 -0
  29. toolbase/setup/declarative.py +363 -0
  30. toolbase/setup/downloads.py +416 -0
  31. toolbase/setup/prompts.py +271 -0
  32. toolbase/setup/runner.py +1179 -0
  33. toolbase/setup/schema.py +465 -0
  34. toolbase/setup/storage.py +364 -0
  35. toolbase/setup/validate_cache.py +152 -0
  36. toolbase/skills.py +256 -0
  37. toolbase/templates/Dockerfile.template +25 -0
  38. toolbase/templates/README.md.template +50 -0
  39. toolbase/templates/__init__.py.template +10 -0
  40. toolbase/templates/mcp/__init__.py.template +4 -0
  41. toolbase/templates/mcp/server_stdio.py.template +46 -0
  42. toolbase/templates/requirements.txt.template +11 -0
  43. toolbase/templates/setup.py.template +94 -0
  44. toolbase/templates/skills/example_skill.md +44 -0
  45. toolbase/templates/tool_example.py +100 -0
  46. toolbase/templates/toolkit.yaml.template +68 -0
  47. toolbase/toolkit.py +387 -0
  48. toolbase/validation.py +801 -0
  49. toolbase/versioning.py +100 -0
  50. toolbase-0.1.0.dist-info/METADATA +247 -0
  51. toolbase-0.1.0.dist-info/RECORD +55 -0
  52. toolbase-0.1.0.dist-info/WHEEL +5 -0
  53. toolbase-0.1.0.dist-info/entry_points.txt +3 -0
  54. toolbase-0.1.0.dist-info/licenses/LICENSE +21 -0
  55. toolbase-0.1.0.dist-info/top_level.txt +1 -0
toolbase/__init__.py ADDED
@@ -0,0 +1,22 @@
1
+ """
2
+ Toolbase - The community registry and CLI for AI agent toolkits
3
+
4
+ Toolbase provides a centralized platform for discovering, sharing, and using
5
+ tools for AI agents. Think of it as an app store for agent toolkits across any
6
+ domain - from general-purpose utilities to specialized fields like astrophysics,
7
+ high-energy physics, and quantum computing.
8
+
9
+ Features:
10
+ - Easy tool creation and sharing
11
+ - Curated categories across domains
12
+ - MCP (Model Context Protocol) compatibility
13
+ - Integration with Orchestral AI and other agent frameworks
14
+ """
15
+
16
+ __version__ = "0.1.0"
17
+ __author__ = "Alex Roman"
18
+
19
+ # Placeholder imports for future toolkit categories
20
+ # from .astro import aster
21
+ # from .hep import heptapod
22
+ # from .quantum import quantum_toolkit
@@ -0,0 +1,243 @@
1
+ """
2
+ Per-toolkit subprocess host for Phase 3C-2 setup.
3
+
4
+ Mirrors ``_toolkit_host.py``'s shape but for the setup lifecycle
5
+ instead of the serve lifecycle:
6
+
7
+ - Runs *inside* a toolkit's Python interpreter (its own venv/conda env)
8
+ so ``setup.py`` can import the toolkit's declared dependencies.
9
+ - Talks to the parent process over line-mode JSON-RPC on stdin/stdout
10
+ (see ``toolbase/setup/_rpc.py``). This is **not** MCP and is not
11
+ the same protocol the serve-time host speaks.
12
+ - Imports only stdlib + a minimal slice of ``toolbase.setup`` (the
13
+ RPC primitives and ``SetupContext``). The parent ships those into
14
+ the toolkit env via ``PYTHONPATH``, the same way ``_toolkit_host.py``
15
+ is reachable from the toolkit env today.
16
+
17
+ The lifecycle:
18
+
19
+ 1. Parse ``--toolkit-dir``, ``--name`` from argv.
20
+ 2. Open the line-mode RPC channel over stdin/stdout.
21
+ 3. Send a ``hello`` message announcing protocol version + which
22
+ functions ``setup.py`` exports (``setup`` and/or ``validate``).
23
+ 4. Wait for ``go`` from parent. ``go`` carries the mode (``setup``
24
+ or ``validate``), prompt mode, current config snapshot, and the
25
+ four standard paths.
26
+ 5. Construct ``SetupContext`` from the ``go`` payload.
27
+ 6. Invoke ``setup.py::setup(ctx)`` or ``setup.py::validate(ctx)``.
28
+ 7. Send ``done`` with the function's return value (cast to bool) and
29
+ any traceback if it raised.
30
+ 8. Exit.
31
+
32
+ Errors during setup.py loading or invocation are captured as a
33
+ ``done`` message with ``result=false`` + a traceback. The runner on
34
+ the parent side renders the one-line summary and writes the full
35
+ traceback to ``~/.toolbase/logs/setup-<toolkit>-<date>.log`` per
36
+ the spec's error-handling rules.
37
+ """
38
+
39
+ from __future__ import annotations
40
+
41
+ import argparse
42
+ import importlib.util
43
+ import io
44
+ import json
45
+ import sys
46
+ import traceback
47
+ from pathlib import Path
48
+ from typing import Any, Optional
49
+
50
+
51
+ def _load_setup_module(toolkit_dir: Path):
52
+ """Import ``<toolkit_dir>/setup.py`` without polluting sys.path.
53
+
54
+ Same discipline as ``_toolkit_host.py::_import_tools_package``:
55
+ use an explicit ``spec_from_file_location`` so the toolkit's
56
+ sibling directories don't shadow installed packages. ``setup.py``
57
+ sits at toolkit root; tools live in ``tools/``. The two are
58
+ independent imports.
59
+
60
+ Returns the loaded module, or None if no setup.py exists.
61
+ """
62
+ setup_file = toolkit_dir / "setup.py"
63
+ if not setup_file.exists():
64
+ return None
65
+ # Use a unique module name so we don't collide with anything else
66
+ # the toolkit might import that happens to be called 'setup'.
67
+ spec = importlib.util.spec_from_file_location(
68
+ "_toolbase_toolkit_setup",
69
+ str(setup_file),
70
+ )
71
+ if spec is None or spec.loader is None:
72
+ raise ImportError(f"could not build module spec for {setup_file}")
73
+ module = importlib.util.module_from_spec(spec)
74
+ sys.modules["_toolbase_toolkit_setup"] = module
75
+ spec.loader.exec_module(module)
76
+ return module
77
+
78
+
79
+ def main(argv: Optional[list] = None) -> int:
80
+ parser = argparse.ArgumentParser(
81
+ prog="python -m toolbase._setup_host",
82
+ description="Per-toolkit subprocess host for toolbase setup.",
83
+ )
84
+ parser.add_argument(
85
+ "--toolkit-dir", required=True, type=Path,
86
+ help="Path to the installed toolkit directory.",
87
+ )
88
+ parser.add_argument(
89
+ "--name", required=True,
90
+ help="Toolkit name (used in error messages).",
91
+ )
92
+ args = parser.parse_args(argv)
93
+
94
+ # Import the RPC primitives and SetupContext from the parent's
95
+ # PYTHONPATH-injected toolbase slice.
96
+ try:
97
+ from toolbase.setup import _rpc
98
+ from toolbase.setup.context import SetupContext, _SetupContextRPC
99
+ except ImportError as e: # pragma: no cover — defensive
100
+ sys.stderr.write(
101
+ f"setup-host: failed to import toolbase.setup: {e}\n"
102
+ "(parent should have set PYTHONPATH; this is a bug in the "
103
+ "orchestrator's spawn wiring)\n"
104
+ )
105
+ return 2
106
+
107
+ # The line-mode helpers expect text streams. stdout/stdin should
108
+ # already be text-mode under Python 3, but force UTF-8 to match the
109
+ # parent's expectations exactly (the parent uses ensure_ascii=False
110
+ # when emitting messages with potential Unicode, so the channel
111
+ # must be UTF-8 capable end-to-end).
112
+ rx = sys.stdin
113
+ tx = sys.stdout
114
+ # Reconfigure for UTF-8 if possible. ``reconfigure`` exists on
115
+ # Python 3.7+; safe to call multiple times.
116
+ try:
117
+ rx.reconfigure(encoding="utf-8") # type: ignore[union-attr]
118
+ tx.reconfigure(encoding="utf-8") # type: ignore[union-attr]
119
+ except (AttributeError, io.UnsupportedOperation):
120
+ pass
121
+
122
+ # Step 1: try to load setup.py. We hold any load-time exception
123
+ # to be reported as the eventual ``done``, not as a pre-handshake
124
+ # error — the protocol is "always send hello first," and the
125
+ # parent's pump treats a pre-hello ``done`` as a protocol violation.
126
+ setup_module = None
127
+ load_traceback: Optional[str] = None
128
+ try:
129
+ setup_module = _load_setup_module(args.toolkit_dir)
130
+ except Exception:
131
+ load_traceback = traceback.format_exc()
132
+
133
+ has_setup = bool(setup_module and callable(getattr(setup_module, "setup", None)))
134
+ has_validate = bool(setup_module and callable(getattr(setup_module, "validate", None)))
135
+
136
+ # Step 2: send hello. If the setup.py couldn't even load, we
137
+ # advertise has_setup=has_validate=False AND include the load
138
+ # traceback in ``load_error``. The parent uses load_error to
139
+ # surface the actual root cause (syntax error, ImportError) to
140
+ # the user rather than the misleading "setup() not defined."
141
+ try:
142
+ _rpc.write_message(tx, _rpc.make_hello(
143
+ has_setup=has_setup,
144
+ has_validate=has_validate,
145
+ load_error=load_traceback,
146
+ ))
147
+ except Exception:
148
+ return 4
149
+
150
+ # Step 3: wait for go. The parent may decline to send 'go' if it
151
+ # decided based on the hello alone that there's nothing to do
152
+ # (e.g., validate-mode + has_validate=False); EOF here is fine.
153
+ msg = _rpc.read_message(rx)
154
+ if msg is None:
155
+ # Parent disconnected without sending go — clean exit.
156
+ return 0
157
+ if msg.method != "go":
158
+ try:
159
+ _rpc.write_message(tx, _rpc.make_done(
160
+ result=False,
161
+ traceback_str=f"expected 'go' message, got {msg.method!r}",
162
+ ))
163
+ except Exception:
164
+ pass
165
+ return 6
166
+ go_params = msg.params
167
+
168
+ mode = go_params.get("mode", "setup")
169
+ prompt_mode = go_params.get("prompt_mode", "ask")
170
+ config_snapshot = go_params.get("config") or {}
171
+ toolkit_path = Path(go_params.get("toolkit_path") or args.toolkit_dir)
172
+ data_dir = Path(go_params.get("data_dir") or "")
173
+ cache_dir = Path(go_params.get("cache_dir") or "")
174
+ config_path = Path(go_params.get("config_path") or "")
175
+
176
+ # Step 4: build the RPC client and ctx.
177
+ rpc_client = _SetupContextRPC(rx=rx, tx=tx)
178
+ ctx = SetupContext(
179
+ rpc=rpc_client,
180
+ mode=mode,
181
+ prompt_mode=prompt_mode,
182
+ config_snapshot=config_snapshot,
183
+ toolkit_path=toolkit_path,
184
+ data_dir=data_dir,
185
+ cache_dir=cache_dir,
186
+ config_path=config_path,
187
+ )
188
+
189
+ # Step 5: invoke the requested function.
190
+ if mode == "validate":
191
+ if not has_validate:
192
+ # No validate(ctx) defined → trivially passes. The
193
+ # canonical "no setup_script means no validate" path goes
194
+ # through the orchestrator's _resolve_state_config, not
195
+ # through here, but defending against the edge is cheap.
196
+ _rpc.write_message(tx, _rpc.make_done(result=True))
197
+ return 0
198
+ target_fn = setup_module.validate
199
+ target_label = "validate"
200
+ else:
201
+ if not has_setup:
202
+ _rpc.write_message(tx, _rpc.make_done(
203
+ result=False,
204
+ traceback_str=(
205
+ f"setup.py at {args.toolkit_dir / 'setup.py'} does not "
206
+ "define `setup(ctx)`. See "
207
+ "https://toolbase-ai.com/docs/configuration#setup-script "
208
+ "(or tb-package/docs/SETUP_SYSTEM_SPEC.md §'Tier 2 — "
209
+ "script' until that page lands)."
210
+ ),
211
+ ))
212
+ return 0
213
+ target_fn = setup_module.setup
214
+ target_label = "setup"
215
+
216
+ try:
217
+ result = target_fn(ctx)
218
+ except Exception:
219
+ tb = traceback.format_exc()
220
+ try:
221
+ _rpc.write_message(tx, _rpc.make_done(
222
+ result=False,
223
+ traceback_str=tb,
224
+ ))
225
+ except Exception:
226
+ pass
227
+ return 0 # not 7 — we communicated cleanly; the *result* is failure.
228
+
229
+ # Allow setup/validate to return None (treat as success) for
230
+ # author-friendly ergonomics. Authors who explicitly return False
231
+ # signal failure; everyone else just returns.
232
+ success = True if result is None else bool(result)
233
+
234
+ try:
235
+ _rpc.write_message(tx, _rpc.make_done(result=success))
236
+ except Exception:
237
+ return 8
238
+
239
+ return 0
240
+
241
+
242
+ if __name__ == "__main__":
243
+ sys.exit(main())