vaultkit 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.
@@ -0,0 +1,199 @@
1
+ # vaultkit/tools/executor.py
2
+ from __future__ import annotations
3
+
4
+ from typing import TYPE_CHECKING, Any, Dict, Optional
5
+
6
+ from vaultkit.errors.exceptions import (
7
+ ApprovalRequiredError,
8
+ DeniedError,
9
+ GrantExpiredError,
10
+ GrantRevokedError,
11
+ PolicyBundleRevokedError,
12
+ QueuedError,
13
+ ValidationError,
14
+ )
15
+
16
+ if TYPE_CHECKING:
17
+ from vaultkit.client import VaultKitClient
18
+
19
+
20
+ class ToolExecutor:
21
+ """
22
+ Provider-agnostic dispatcher for tool calls -> VaultKitClient methods.
23
+
24
+ Any agent runtime can use this as long as it can provide:
25
+ - tool_name: str
26
+ - tool_args: dict
27
+
28
+ Example (pseudo):
29
+ name = tool_call["name"]
30
+ args = tool_call["args"]
31
+ result = executor.execute(name, args)
32
+ """
33
+
34
+ def __init__(
35
+ self,
36
+ client: "VaultKitClient",
37
+ *,
38
+ default_purpose: Optional[str] = None,
39
+ default_requester_region: Optional[str] = None,
40
+ ) -> None:
41
+ self._client = client
42
+ self._default_purpose = default_purpose
43
+ self._default_requester_region = default_requester_region
44
+
45
+ def execute(self, tool_name: str, tool_args: Dict[str, Any]) -> Dict[str, Any]:
46
+ """Dispatch a tool call. Always returns a dict — never raises."""
47
+ try:
48
+ if tool_name == "vaultkit_discover":
49
+ return self._execute_discover(tool_args)
50
+ if tool_name == "vaultkit_query":
51
+ return self._execute_query(tool_args)
52
+ if tool_name == "vaultkit_check_approval":
53
+ return self._execute_check_approval(tool_args)
54
+
55
+ return self._error(
56
+ f"Unknown tool '{tool_name}'. "
57
+ "Available: vaultkit_discover, vaultkit_query, vaultkit_check_approval.",
58
+ code="unknown_tool",
59
+ )
60
+ except Exception as e:
61
+ return self._translate_error(e)
62
+
63
+ # dispatch
64
+
65
+ def _execute_discover(self, args: Dict[str, Any]) -> Dict[str, Any]:
66
+ environment = args.get("environment", "production")
67
+ requester_region = args.get("requester_region")
68
+ dataset_region = args.get("dataset_region")
69
+
70
+ datasets = self._client.datasets(
71
+ environment=environment,
72
+ requester_region=requester_region,
73
+ dataset_region=dataset_region,
74
+ )
75
+
76
+ visibility_map = {
77
+ "allow": "accessible",
78
+ "require_approval": "requires approval",
79
+ "deny": "not accessible",
80
+ }
81
+
82
+ return {
83
+ "status": "ok",
84
+ "datasets": [
85
+ {
86
+ "name": d.dataset,
87
+ "datasource": d.datasource,
88
+ "visibility": visibility_map.get(getattr(d, "visibility", None), getattr(d, "visibility", None)),
89
+ "field_count": len(getattr(d, "fields", []) or []),
90
+ }
91
+ for d in datasets
92
+ ],
93
+ "count": len(datasets),
94
+ }
95
+
96
+ def _execute_query(self, args: Dict[str, Any]) -> Dict[str, Any]:
97
+ dataset = args.get("dataset")
98
+ if not dataset:
99
+ return self._error("'dataset' is required for vaultkit_query.", code="validation_error")
100
+
101
+ purpose = args.get("purpose") or self._default_purpose
102
+
103
+ result = self._client.execute(
104
+ dataset=dataset,
105
+ fields=args.get("fields"),
106
+ filters=args.get("filters"),
107
+ limit=args.get("limit"),
108
+ purpose=purpose,
109
+ requester_region=args.get("requester_region") or self._default_requester_region,
110
+ )
111
+
112
+ response: Dict[str, Any] = {
113
+ "status": "ok",
114
+ "dataset": dataset,
115
+ "row_count": result.row_count,
116
+ "data": result.data,
117
+ }
118
+
119
+ if result.masked_fields:
120
+ response["note"] = f"Fields {result.masked_fields} were masked per data policy."
121
+
122
+ return response
123
+
124
+ def _execute_check_approval(self, args: Dict[str, Any]) -> Dict[str, Any]:
125
+ request_id = args.get("request_id")
126
+ if not request_id:
127
+ return self._error("'request_id' is required for vaultkit_check_approval.", code="validation_error")
128
+
129
+ poll_result = self._client.poll_request(request_id=request_id)
130
+
131
+ if poll_result.is_granted and poll_result.grant_ref:
132
+ fetch = self._client.fetch(grant_ref=poll_result.grant_ref)
133
+ return {
134
+ "status": "approved",
135
+ "row_count": fetch.row_count,
136
+ "data": fetch.data,
137
+ "masked_fields": fetch.masked_fields,
138
+ }
139
+
140
+ if poll_result.is_denied:
141
+ return {"status": "denied", "reason": poll_result.reason or "Approval was denied."}
142
+
143
+ return {
144
+ "status": "pending",
145
+ "message": "Approval is still pending. Try again shortly.",
146
+ "request_id": request_id,
147
+ }
148
+
149
+ # error translation
150
+
151
+ def _translate_error(self, exc: Exception) -> Dict[str, Any]:
152
+ msg = str(exc)
153
+ error_code = getattr(exc, "error_code", "error")
154
+
155
+ if isinstance(exc, DeniedError):
156
+ policy_id = getattr(exc, "policy_id", None)
157
+ extra = f" (policy_id={policy_id})" if policy_id else ""
158
+ return self._error(
159
+ f"Access denied by VaultKit policy{extra}. Reason: {msg}. "
160
+ "Do not retry — this denial is deterministic.",
161
+ code="denied",
162
+ )
163
+
164
+ if isinstance(exc, ApprovalRequiredError):
165
+ return {
166
+ "status": "pending_approval",
167
+ "message": (
168
+ "This request requires human approval. "
169
+ f"Use vaultkit_check_approval with request_id='{exc.request_id}' to check status."
170
+ ),
171
+ "request_id": exc.request_id,
172
+ }
173
+
174
+ if isinstance(exc, (GrantExpiredError, GrantRevokedError)):
175
+ return self._error(
176
+ f"{msg} Re-submit your query to get a fresh grant.",
177
+ code=error_code,
178
+ )
179
+
180
+ if isinstance(exc, PolicyBundleRevokedError):
181
+ return self._error(
182
+ "The VaultKit policy bundle has been revoked. "
183
+ "No data access is possible. Escalate to your VaultKit administrator.",
184
+ code="bundle_revoked",
185
+ )
186
+
187
+ if isinstance(exc, ValidationError):
188
+ return self._error(f"Invalid query: {msg}", code="validation_error")
189
+
190
+ if isinstance(exc, QueuedError):
191
+ return {"status": "queued", "message": msg, "request_id": exc.request_id}
192
+
193
+ return self._error(f"Unexpected error: {type(exc).__name__}: {msg}", code=error_code)
194
+
195
+ # helpers
196
+
197
+ @staticmethod
198
+ def _error(message: str, *, code: str = "error") -> Dict[str, Any]:
199
+ return {"status": code, "error": message}
@@ -0,0 +1,39 @@
1
+ """
2
+ Backward-compatible module.
3
+
4
+ If you previously imported:
5
+ from vaultkit.tools.schemas import query_tool_schema
6
+
7
+ You can keep doing so, but these now generate *OpenAI* tools by default.
8
+
9
+ Prefer the new canonical definitions via:
10
+ from vaultkit.tools.definitions import query_tool_def
11
+ ToolBuilder(...).build(provider=ToolProvider.ANTHROPIC)
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from typing import Any, Dict, List, Optional
17
+
18
+ from .adapters.openai import to_openai_tool
19
+ from .definitions import (
20
+ check_approval_tool_def,
21
+ discover_tool_def,
22
+ query_tool_def,
23
+ )
24
+
25
+
26
+ def discover_tool_schema(*, dataset_names: Optional[List[str]] = None) -> Dict[str, Any]:
27
+ return to_openai_tool(discover_tool_def(dataset_names=dataset_names))
28
+
29
+
30
+ def query_tool_schema(
31
+ *,
32
+ dataset_names: Optional[List[str]] = None,
33
+ schema_hints: Optional[Dict[str, List[str]]] = None,
34
+ ) -> Dict[str, Any]:
35
+ return to_openai_tool(query_tool_def(dataset_names=dataset_names, schema_hints=schema_hints))
36
+
37
+
38
+ def check_approval_tool_schema() -> Dict[str, Any]:
39
+ return to_openai_tool(check_approval_tool_def())
@@ -0,0 +1 @@
1
+ __all__ = []
@@ -0,0 +1,81 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ import random
5
+ import logging
6
+ from dataclasses import dataclass
7
+ from typing import Callable, TypeVar, Optional
8
+
9
+ from vaultkit.errors.exceptions import RateLimitError, TransportError, ServerError
10
+
11
+ T = TypeVar("T")
12
+
13
+ logger = logging.getLogger("vaultkit.retry")
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class RetryConfig:
18
+ max_attempts: int = 3
19
+ base_sleep_s: float = 0.5
20
+ max_sleep_s: float = 4.0
21
+
22
+ # Add jitter (±20%)
23
+ jitter_ratio: float = 0.2
24
+
25
+
26
+ def retryable(exc: Exception) -> bool:
27
+ return isinstance(exc, (RateLimitError, TransportError, ServerError))
28
+
29
+
30
+ def with_retries(
31
+ fn: Callable[[], T],
32
+ *,
33
+ config: RetryConfig,
34
+ ) -> T:
35
+ attempt = 0
36
+ sleep_s = config.base_sleep_s
37
+
38
+ while True:
39
+ attempt += 1
40
+
41
+ try:
42
+ return fn()
43
+
44
+ except Exception as e:
45
+ is_retryable = retryable(e)
46
+
47
+ if attempt >= config.max_attempts or not is_retryable:
48
+ logger.debug(
49
+ "retry.exit",
50
+ extra={
51
+ "attempt": attempt,
52
+ "retryable": is_retryable,
53
+ "error_type": type(e).__name__,
54
+ },
55
+ )
56
+ raise
57
+
58
+ # Apply jitter
59
+ jitter = random.uniform(
60
+ -sleep_s * config.jitter_ratio,
61
+ sleep_s * config.jitter_ratio,
62
+ )
63
+
64
+ sleep_time = min(
65
+ max(0.0, sleep_s + jitter),
66
+ config.max_sleep_s,
67
+ )
68
+
69
+ logger.debug(
70
+ "retry.sleep",
71
+ extra={
72
+ "attempt": attempt,
73
+ "sleep_s": sleep_time,
74
+ "error_type": type(e).__name__,
75
+ },
76
+ )
77
+
78
+ time.sleep(sleep_time)
79
+
80
+ # Exponential backoff
81
+ sleep_s = min(sleep_s * 2, config.max_sleep_s)
@@ -0,0 +1,87 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict, Optional
4
+
5
+ from vaultkit.errors.base import ErrorContext
6
+ from vaultkit.errors.exceptions import ValidationError
7
+
8
+
9
+ def require_str(name: str, value: Optional[str]) -> str:
10
+ if value is None:
11
+ raise ValidationError(
12
+ f"{name} is required",
13
+ context=ErrorContext(raw={name: value}),
14
+ )
15
+
16
+ result = str(value).strip()
17
+ if not result:
18
+ raise ValidationError(
19
+ f"{name} cannot be empty",
20
+ context=ErrorContext(raw={name: value}),
21
+ )
22
+
23
+ return result
24
+
25
+
26
+ def require_dict(name: str, value: Any) -> Dict[str, Any]:
27
+ if not isinstance(value, dict):
28
+ raise ValidationError(
29
+ f"{name} must be an object/dict",
30
+ context=ErrorContext(raw={name: value}),
31
+ )
32
+ return value
33
+
34
+
35
+ def require_list(name: str, value: Any) -> list:
36
+ if not isinstance(value, list):
37
+ raise ValidationError(
38
+ f"{name} must be a list",
39
+ context=ErrorContext(raw={name: value}),
40
+ )
41
+ return value
42
+
43
+
44
+ def validate_limit(value: Optional[int]) -> Optional[int]:
45
+ if value is None:
46
+ return None
47
+
48
+ if not isinstance(value, int):
49
+ raise ValidationError(
50
+ "limit must be an integer",
51
+ context=ErrorContext(raw=value),
52
+ )
53
+
54
+ if value <= 0:
55
+ raise ValidationError(
56
+ "limit must be greater than 0",
57
+ context=ErrorContext(raw=value),
58
+ )
59
+
60
+ if value > 10000:
61
+ raise ValidationError(
62
+ "limit cannot exceed 10000",
63
+ context=ErrorContext(raw=value),
64
+ )
65
+
66
+ return value
67
+
68
+
69
+ def validate_filters(value: Optional[Any]) -> Optional[list]:
70
+ if value is None:
71
+ return None
72
+
73
+ if not isinstance(value, list):
74
+ raise ValidationError(
75
+ "filters must be a list",
76
+ context=ErrorContext(raw=value),
77
+ )
78
+
79
+ # light structural validation
80
+ for f in value:
81
+ if not isinstance(f, dict):
82
+ raise ValidationError(
83
+ "each filter must be an object",
84
+ context=ErrorContext(raw=f),
85
+ )
86
+
87
+ return value
@@ -0,0 +1,207 @@
1
+ Metadata-Version: 2.4
2
+ Name: vaultkit
3
+ Version: 0.1.0
4
+ Summary: VaultKit Python SDK for policy-driven, runtime governed data access
5
+ Author-email: VaultKit <founders@vaultkit.io>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/vaultkit-inc/vaultkit-sdk-python
8
+ Project-URL: Documentation, https://docs.vaultkit.io
9
+ Project-URL: Source, https://github.com/vaultkit-inc/vaultkit-sdk-python
10
+ Project-URL: Issues, https://github.com/vaultkit-inc/vaultkit-sdk-python/issues
11
+ Keywords: ai,data,sdk,governance,security,agents,runtime
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Topic :: Software Development :: Libraries
21
+ Requires-Python: >=3.8
22
+ Description-Content-Type: text/markdown
23
+ Requires-Dist: httpx>=0.24.0
24
+
25
+ # VaultKit Python SDK
26
+
27
+ > Secure, policy-driven data access for AI agents and applications.
28
+
29
+ VaultKit is a control plane for governed data access. This SDK allows Python applications and AI agents to safely query data with built-in policy enforcement, approval workflows, and auditability.
30
+
31
+ ---
32
+
33
+ ## Features
34
+
35
+ - Policy-enforced data access (masking, approval, deny)
36
+ - First-class support for AI agents (OpenAI, Anthropic)
37
+ - Built-in approval workflows
38
+ - Automatic polling and retries
39
+ - Schema-aware dataset discovery
40
+ - Simple, high-level API via `execute()`
41
+
42
+ ---
43
+
44
+ ## Installation
45
+
46
+ ```bash
47
+ pip install vaultkit
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Quick Start
53
+
54
+ ```python
55
+ from vaultkit import VaultKitClient
56
+
57
+ client = VaultKitClient(
58
+ base_url="http://localhost:3000",
59
+ token="YOUR_TOKEN",
60
+ org="YOUR_ORG",
61
+ )
62
+
63
+ result = client.execute(
64
+ dataset="users",
65
+ fields=["id", "email"],
66
+ limit=10,
67
+ purpose="Analyze user activity",
68
+ )
69
+
70
+ print(result.rows)
71
+ ```
72
+
73
+ ---
74
+
75
+ ## AI Agent Usage
76
+
77
+ VaultKit provides built-in tools for LLM agents.
78
+
79
+ ```python
80
+ from vaultkit.tools import ToolBuilder, ToolExecutor, ToolProvider
81
+
82
+ builder = ToolBuilder(client)
83
+
84
+ tools = builder.build(
85
+ provider=ToolProvider.OPENAI,
86
+ include_check_approval=True,
87
+ )
88
+
89
+ executor = ToolExecutor(client)
90
+
91
+ result = executor.execute(
92
+ "vaultkit_query",
93
+ {
94
+ "dataset": "users",
95
+ "limit": 5,
96
+ "purpose": "Analyze user trends",
97
+ },
98
+ )
99
+ ```
100
+
101
+ See full example: [`examples/agent_openai_demo.py`](examples/agent_openai_demo.py)
102
+
103
+ ---
104
+
105
+ ## Approval Flow
106
+
107
+ Some queries require human approval before data is returned.
108
+
109
+ ```python
110
+ from vaultkit.errors.exceptions import ApprovalRequiredError
111
+
112
+ try:
113
+ client.execute(dataset="sensitive_data", purpose="Analysis")
114
+ except ApprovalRequiredError as e:
115
+ print(f"Approval required. Request ID: {e.request_id}")
116
+ ```
117
+
118
+ Once approved, resume with:
119
+
120
+ ```python
121
+ result = client.poll_request(request_id="req_123")
122
+ ```
123
+
124
+ ---
125
+
126
+ ## API Overview
127
+
128
+ ### High-Level
129
+
130
+ | Method | Description |
131
+ |---|---|
132
+ | `client.execute(...)` | Full lifecycle: query → poll → fetch. Recommended for most use cases. |
133
+
134
+ ### Low-Level
135
+
136
+ | Method | Description |
137
+ |---|---|
138
+ | `client.query(...)` | Submit an intent request, get a `QueryResult` |
139
+ | `client.poll(result)` | Block until a queued result reaches a terminal state |
140
+ | `client.fetch(grant_ref=...)` | Redeem a grant for data |
141
+ | `client.poll_request(request_id=...)` | Poll by request ID (used in approval flows) |
142
+
143
+ ### Discovery
144
+
145
+ | Method | Description |
146
+ |---|---|
147
+ | `client.datasets()` | List authorized datasets from the registry |
148
+ | `client.schema("users")` | Get field-level schema for a dataset |
149
+
150
+ ---
151
+
152
+ ## How It Works
153
+
154
+ ```
155
+ Client → VaultKit → Policy Engine → Data Source
156
+
157
+ Enforced Policies
158
+ ```
159
+
160
+ 1. Queries are evaluated against policy bundles at runtime
161
+ 2. Sensitive fields may be masked based on requester context
162
+ 3. Some datasets require human approval before access is granted
163
+ 4. All access is logged and auditable
164
+
165
+ ---
166
+
167
+ ## Why VaultKit?
168
+
169
+ Traditional access control is static — permissions are set upfront and rarely change. VaultKit enables:
170
+
171
+ - **Runtime, policy-driven access** — decisions made at query time based on context
172
+ - **AI-safe data access** — purpose and clearance are first-class query parameters
173
+ - **Auditability and compliance** — every request is tracked with correlation IDs
174
+
175
+ ---
176
+
177
+ ## Environment Variables
178
+
179
+ ```bash
180
+ export VAULTKIT_URL=http://localhost:3000
181
+ export VAULTKIT_TOKEN=your_token
182
+ export VAULTKIT_ORG=your_org
183
+ ```
184
+
185
+ Or use a `.env` file (see [`.env.example`](.env.example)).
186
+
187
+ ---
188
+
189
+ ## Local Development
190
+
191
+ Start VaultKit locally with Docker:
192
+
193
+ ```bash
194
+ docker compose up
195
+ ```
196
+
197
+ Run the test suite:
198
+
199
+ ```bash
200
+ pytest
201
+ ```
202
+
203
+ ---
204
+
205
+ ## License
206
+
207
+ MIT
@@ -0,0 +1,28 @@
1
+ vaultkit/__init__.py,sha256=xOscURPo7m01LpD36Px0bNKEz04QuMSM9oWSyTq9HTQ,1775
2
+ vaultkit/client.py,sha256=ojdvJD4B5C81SBEDLHVCIqUVmCveyGKi3u0jcbjPDug,14499
3
+ vaultkit/core/__init__.py,sha256=d4IG0OxUXj2HffepzQcYixHlZeuuuDMAFa09H_6LtmU,12
4
+ vaultkit/core/http.py,sha256=0eu91QTQr8I4YtRQSoemNVgKC6yY8L8q3E9ezEcsKsk,5451
5
+ vaultkit/core/polling.py,sha256=K_Y-5MvemWaeTppNPsBdevK5f_wkBB9KUPA_PZUBDe4,4163
6
+ vaultkit/errors/__init__.py,sha256=nh2Uu7MjIVcp3rD8uQTYidZXD-ysdV62dlj78AA6WhE,691
7
+ vaultkit/errors/base.py,sha256=U8ctwfA7d6IBXN5NBzIIn-sPTkd1kYZiLNQNNyaeBYU,835
8
+ vaultkit/errors/exceptions.py,sha256=sNZSqqONoT1DoSFLDsiZaO0jC5PBZIcHt_dt3gGAUKQ,4954
9
+ vaultkit/models/__init__.py,sha256=uUf4xeeM2Xz5hS-OZA9oAhy7LWLQd60Dv22Pw0Ff6uI,248
10
+ vaultkit/models/dataset_info.py,sha256=tJDklLxx7YYqUJ6xM2ICDMLqtozAyaaRtEN5ppHaVDE,699
11
+ vaultkit/models/dataset_schema.py,sha256=F7Dtz2doNka5lwUyMZNC-y_vFu1OHIMxXmxNgIJxwuo,1957
12
+ vaultkit/models/fetch_result.py,sha256=eTIsO3KXw-ga9XD3XFGygXLKrE0vLaRRKPPfd65box8,1547
13
+ vaultkit/models/query_result.py,sha256=u-CJlebz1xbYjDDaKYkZ7Q0-0-frLPx0KkvUww7aBW0,2233
14
+ vaultkit/tools/__init__.py,sha256=TnuStyD_9vVwiJ_aXO0TFx112w0ZHO_QfSajRTOizN0,162
15
+ vaultkit/tools/builder.py,sha256=Wui6reEqDSyKlGZg7-cPu3XLO7P9WEmHRSAtWzuoLIM,4137
16
+ vaultkit/tools/definitions.py,sha256=_p-LxBIGOSDnUruywMQfDc7HhXRYgDvMAOmQX2PIwGU,6163
17
+ vaultkit/tools/executor.py,sha256=1qNXEONY1-qVICAZ5mZQxUoGfMUEHjlHLWDvFbjbuZ0,6859
18
+ vaultkit/tools/schemas.py,sha256=EXP8PjOucffvuB1bGRYiSWBJ6nrZxz-AH2HUWsIxkRQ,1107
19
+ vaultkit/tools/adapters/__init__.py,sha256=r3-XRYvPkobjr0zA3bhKLSI8hH7j7oEkTaRpOTzdL3c,186
20
+ vaultkit/tools/adapters/anthropic.py,sha256=sV0rsgq-wEMBM5Yi6iwvvHpr_wBHG08GXugErgqqtbU,490
21
+ vaultkit/tools/adapters/openai.py,sha256=zmkI6y3m9HF7ppOqqLvqLoKIrYhTsp29pHeSYgxAFc4,658
22
+ vaultkit/utils/__init__.py,sha256=d4IG0OxUXj2HffepzQcYixHlZeuuuDMAFa09H_6LtmU,12
23
+ vaultkit/utils/retry.py,sha256=ONhY98EV3MviWN9nP-WRO5rNws_L1D6yK01kj5AneIY,1943
24
+ vaultkit/utils/validation.py,sha256=DBA_xS7frqn4IQTSFL0edxmG4oHwNmalAF9gHiXP7Vs,2183
25
+ vaultkit-0.1.0.dist-info/METADATA,sha256=b2BVQy5RSul26ouSwFEqpajGwYo4gx-kcwV9BYkRhLs,4821
26
+ vaultkit-0.1.0.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
27
+ vaultkit-0.1.0.dist-info/top_level.txt,sha256=c2ZPwFHOBzAB3cplCOpzxS7rp5RrYId8W3o2gnxOIUg,9
28
+ vaultkit-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ vaultkit