actpass 1.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.
- actpass/__init__.py +30 -0
- actpass/client.py +348 -0
- actpass-1.1.0.dist-info/METADATA +61 -0
- actpass-1.1.0.dist-info/RECORD +5 -0
- actpass-1.1.0.dist-info/WHEEL +4 -0
actpass/__init__.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""ActPass Python SDK (spec section 14).
|
|
2
|
+
|
|
3
|
+
Zero-dependency client mirroring the TypeScript ``@actpass/sdk`` surface.
|
|
4
|
+
|
|
5
|
+
from actpass import create_actpass
|
|
6
|
+
|
|
7
|
+
client = create_actpass(api_key="...", tenant_id="1", agent_id="support_agent")
|
|
8
|
+
result = client.guard(
|
|
9
|
+
goal="resolve_refund_request",
|
|
10
|
+
tool="stripe.refund.create",
|
|
11
|
+
args={"chargeId": "ch_123", "amount": 14900, "currency": "USD"},
|
|
12
|
+
execute_fn=lambda ctx: do_refund(),
|
|
13
|
+
)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from .client import (
|
|
17
|
+
ActPassClient,
|
|
18
|
+
ActPassError,
|
|
19
|
+
create_actpass,
|
|
20
|
+
DEFAULT_BASE_URL,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"ActPassClient",
|
|
25
|
+
"ActPassError",
|
|
26
|
+
"create_actpass",
|
|
27
|
+
"DEFAULT_BASE_URL",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
__version__ = "0.1.0"
|
actpass/client.py
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"""ActPass Python SDK client (spec section 14).
|
|
2
|
+
|
|
3
|
+
A zero-dependency client (Python standard library only) that mirrors the
|
|
4
|
+
TypeScript ``packages/sdk`` surface: an :class:`ActPassClient` with
|
|
5
|
+
``preflight()``, ``guard()``, ``issue_passport()``, ``verify_passport()``,
|
|
6
|
+
``revoke_passport()`` and ``record_evidence()``.
|
|
7
|
+
|
|
8
|
+
Design notes that mirror the TS client (``packages/sdk/src/client.ts``):
|
|
9
|
+
|
|
10
|
+
* HTTP transport is ``urllib.request`` + ``json`` from the stdlib so the SDK
|
|
11
|
+
carries **zero third-party dependencies**.
|
|
12
|
+
* Requests authenticate with a ``Bearer`` API key plus the
|
|
13
|
+
``x-actpass-tenant`` header. The tenant is resolved server-side from the
|
|
14
|
+
authenticated principal (spec section 11.2) — it is sent only as a routing
|
|
15
|
+
hint, never trusted by the gateway.
|
|
16
|
+
* The client **fails closed**: any non-2xx response raises
|
|
17
|
+
:class:`ActPassError`, except ``401``/``403`` which are returned as the
|
|
18
|
+
parsed JSON body so callers can inspect the auth failure (matching the TS
|
|
19
|
+
``post()`` behaviour). ``guard()`` therefore only executes the caller's
|
|
20
|
+
function when the decision is ``allow`` or ``warn``.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import hashlib
|
|
26
|
+
import hmac
|
|
27
|
+
import json
|
|
28
|
+
import os
|
|
29
|
+
import urllib.error
|
|
30
|
+
import urllib.request
|
|
31
|
+
from typing import Any, Callable, Dict, List, Optional, TypeVar
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
"ActPassClient",
|
|
35
|
+
"ActPassError",
|
|
36
|
+
"create_actpass",
|
|
37
|
+
"DEFAULT_BASE_URL",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
DEFAULT_BASE_URL = "https://api.actpass.org"
|
|
41
|
+
|
|
42
|
+
T = TypeVar("T")
|
|
43
|
+
|
|
44
|
+
# Decisions for which guard() proceeds to execute the caller's function.
|
|
45
|
+
_EXECUTABLE_DECISIONS = frozenset({"allow", "warn"})
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ActPassError(Exception):
|
|
49
|
+
"""Raised on non-2xx responses (other than 401/403) or transport errors.
|
|
50
|
+
|
|
51
|
+
Failing closed is the whole point: callers should treat an
|
|
52
|
+
:class:`ActPassError` as "deny / do not execute".
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(self, message: str, status: Optional[int] = None, body: Optional[str] = None):
|
|
56
|
+
super().__init__(message)
|
|
57
|
+
self.status = status
|
|
58
|
+
self.body = body
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ActPassClient:
|
|
62
|
+
"""HTTP client for the ActPass public v1 endpoints.
|
|
63
|
+
|
|
64
|
+
Mirrors ``ActPassClient`` from ``packages/sdk/src/client.ts``.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
api_key: Bearer API key (``<key>`` half of an ``ACTPASS_API_KEYS`` pair).
|
|
68
|
+
tenant_id: Tenant routing hint sent as ``x-actpass-tenant``. The server
|
|
69
|
+
resolves the authoritative tenant from the principal.
|
|
70
|
+
agent_id: Identifier injected into preflight/passport request bodies.
|
|
71
|
+
base_url: API base URL. Falls back to the ``ACTPASS_API_BASE_URL`` env
|
|
72
|
+
var, then to :data:`DEFAULT_BASE_URL`.
|
|
73
|
+
timeout: Per-request socket timeout in seconds.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def __init__(
|
|
77
|
+
self,
|
|
78
|
+
api_key: str,
|
|
79
|
+
tenant_id: Optional[str] = None,
|
|
80
|
+
agent_id: Optional[str] = None,
|
|
81
|
+
base_url: Optional[str] = None,
|
|
82
|
+
timeout: float = 30.0,
|
|
83
|
+
) -> None:
|
|
84
|
+
self.api_key = api_key
|
|
85
|
+
# Identity is resolved server-side from the key (§11.2); these are hints.
|
|
86
|
+
self.tenant_id = tenant_id
|
|
87
|
+
self.agent_id = agent_id or "sdk"
|
|
88
|
+
self.base_url = (
|
|
89
|
+
base_url
|
|
90
|
+
or os.environ.get("ACTPASS_API_BASE_URL")
|
|
91
|
+
or DEFAULT_BASE_URL
|
|
92
|
+
).rstrip("/")
|
|
93
|
+
self.timeout = timeout
|
|
94
|
+
|
|
95
|
+
# ------------------------------------------------------------------ #
|
|
96
|
+
# Low-level transport
|
|
97
|
+
# ------------------------------------------------------------------ #
|
|
98
|
+
def _headers(self, with_content_type: bool) -> Dict[str, str]:
|
|
99
|
+
headers = {"authorization": f"Bearer {self.api_key}"}
|
|
100
|
+
if self.tenant_id:
|
|
101
|
+
headers["x-actpass-tenant"] = self.tenant_id
|
|
102
|
+
if with_content_type:
|
|
103
|
+
headers["content-type"] = "application/json"
|
|
104
|
+
return headers
|
|
105
|
+
|
|
106
|
+
def _request(self, method: str, path: str, body: Optional[Any]) -> Dict[str, Any]:
|
|
107
|
+
url = f"{self.base_url}{path}"
|
|
108
|
+
data = None
|
|
109
|
+
if body is not None:
|
|
110
|
+
data = json.dumps(body).encode("utf-8")
|
|
111
|
+
req = urllib.request.Request(
|
|
112
|
+
url,
|
|
113
|
+
data=data,
|
|
114
|
+
method=method,
|
|
115
|
+
headers=self._headers(with_content_type=body is not None),
|
|
116
|
+
)
|
|
117
|
+
try:
|
|
118
|
+
with urllib.request.urlopen(req, timeout=self.timeout) as resp:
|
|
119
|
+
return self._read_json(resp)
|
|
120
|
+
except urllib.error.HTTPError as exc:
|
|
121
|
+
# 401/403 carry an inspectable JSON body in the TS client; surface
|
|
122
|
+
# it the same way instead of raising. Everything else fails closed.
|
|
123
|
+
status = exc.code
|
|
124
|
+
raw = exc.read().decode("utf-8", "replace") if exc.fp else ""
|
|
125
|
+
if method == "POST" and status in (401, 403):
|
|
126
|
+
try:
|
|
127
|
+
return json.loads(raw) if raw else {}
|
|
128
|
+
except (ValueError, TypeError):
|
|
129
|
+
return {}
|
|
130
|
+
raise ActPassError(
|
|
131
|
+
f"ActPass {path} {status}: {raw}", status=status, body=raw
|
|
132
|
+
) from exc
|
|
133
|
+
except urllib.error.URLError as exc:
|
|
134
|
+
# Network failure -> fail closed.
|
|
135
|
+
raise ActPassError(f"ActPass {path} unreachable: {exc.reason}") from exc
|
|
136
|
+
|
|
137
|
+
@staticmethod
|
|
138
|
+
def _read_json(resp: Any) -> Dict[str, Any]:
|
|
139
|
+
raw = resp.read().decode("utf-8", "replace")
|
|
140
|
+
if not raw:
|
|
141
|
+
return {}
|
|
142
|
+
try:
|
|
143
|
+
return json.loads(raw)
|
|
144
|
+
except (ValueError, TypeError) as exc:
|
|
145
|
+
raise ActPassError(f"ActPass returned non-JSON body: {raw}") from exc
|
|
146
|
+
|
|
147
|
+
def _post(self, path: str, body: Any) -> Dict[str, Any]:
|
|
148
|
+
return self._request("POST", path, body)
|
|
149
|
+
|
|
150
|
+
def _get(self, path: str) -> Dict[str, Any]:
|
|
151
|
+
return self._request("GET", path, None)
|
|
152
|
+
|
|
153
|
+
# ------------------------------------------------------------------ #
|
|
154
|
+
# Public API surface (mirrors packages/sdk/src/client.ts)
|
|
155
|
+
# ------------------------------------------------------------------ #
|
|
156
|
+
def preflight(
|
|
157
|
+
self,
|
|
158
|
+
goal: str,
|
|
159
|
+
tool: str,
|
|
160
|
+
args: Dict[str, Any],
|
|
161
|
+
resource: str = "",
|
|
162
|
+
user_id: Optional[str] = None,
|
|
163
|
+
passport: Optional[str] = None,
|
|
164
|
+
mode: str = "enforce",
|
|
165
|
+
idempotency_key: Optional[str] = None,
|
|
166
|
+
audience: Optional[str] = None,
|
|
167
|
+
) -> Dict[str, Any]:
|
|
168
|
+
"""Evaluate the deterministic policy for a tool execution.
|
|
169
|
+
|
|
170
|
+
Returns the raw preflight decision dict (``decision``, ``reason_code``,
|
|
171
|
+
``risk_tier``, ``explain``, ...).
|
|
172
|
+
"""
|
|
173
|
+
return self._post(
|
|
174
|
+
"/api/v1/actions/preflight",
|
|
175
|
+
{
|
|
176
|
+
"agent_id": self.agent_id,
|
|
177
|
+
"goal": goal,
|
|
178
|
+
"tool": tool,
|
|
179
|
+
"resource": resource,
|
|
180
|
+
"args": args,
|
|
181
|
+
"user_id": user_id,
|
|
182
|
+
"passport": passport,
|
|
183
|
+
"mode": mode,
|
|
184
|
+
"idempotency_key": idempotency_key,
|
|
185
|
+
"audience": audience,
|
|
186
|
+
},
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def guard(
|
|
190
|
+
self,
|
|
191
|
+
goal: str,
|
|
192
|
+
tool: str,
|
|
193
|
+
args: Dict[str, Any],
|
|
194
|
+
execute_fn: Callable[[Dict[str, Any]], T],
|
|
195
|
+
resource: str = "",
|
|
196
|
+
user_id: Optional[str] = None,
|
|
197
|
+
passport: Optional[str] = None,
|
|
198
|
+
mode: str = "enforce",
|
|
199
|
+
idempotency_key: Optional[str] = None,
|
|
200
|
+
audience: Optional[str] = None,
|
|
201
|
+
) -> Dict[str, Any]:
|
|
202
|
+
"""Preflight + (optionally) execute in one call.
|
|
203
|
+
|
|
204
|
+
``execute_fn`` is invoked with a ``{"credential": {...}}`` context only
|
|
205
|
+
when the decision is ``allow`` or ``warn``. Returns a dict shaped like
|
|
206
|
+
the TS ``GuardOutput``: ``{"decision": ..., "result"?: ..., "error"?: ...}``.
|
|
207
|
+
"""
|
|
208
|
+
decision = self.preflight(
|
|
209
|
+
goal=goal,
|
|
210
|
+
tool=tool,
|
|
211
|
+
args=args,
|
|
212
|
+
resource=resource,
|
|
213
|
+
user_id=user_id,
|
|
214
|
+
passport=passport,
|
|
215
|
+
mode=mode,
|
|
216
|
+
idempotency_key=idempotency_key,
|
|
217
|
+
audience=audience,
|
|
218
|
+
)
|
|
219
|
+
if decision.get("decision") not in _EXECUTABLE_DECISIONS:
|
|
220
|
+
return {"decision": decision}
|
|
221
|
+
ctx = {"credential": {"token": "sdk-local", "expires_at": None}}
|
|
222
|
+
try:
|
|
223
|
+
result = execute_fn(ctx)
|
|
224
|
+
return {"decision": decision, "result": result}
|
|
225
|
+
except Exception as err: # noqa: BLE001 - surface as GuardOutput.error
|
|
226
|
+
return {"decision": decision, "error": str(err)}
|
|
227
|
+
|
|
228
|
+
def issue_passport(
|
|
229
|
+
self,
|
|
230
|
+
audience: str,
|
|
231
|
+
goal: str,
|
|
232
|
+
allowed_tools: List[str],
|
|
233
|
+
allowed_resources: Optional[List[str]] = None,
|
|
234
|
+
resource_constraints: Optional[Dict[str, Any]] = None,
|
|
235
|
+
user_id: Optional[str] = None,
|
|
236
|
+
ttl_seconds: Optional[int] = None,
|
|
237
|
+
risk_tier: str = "medium",
|
|
238
|
+
) -> Dict[str, Any]:
|
|
239
|
+
"""Mint a scoped action passport (returns ``{token, jwks_uri}``)."""
|
|
240
|
+
return self._post(
|
|
241
|
+
"/api/v1/passports/issue",
|
|
242
|
+
{
|
|
243
|
+
"audience": audience,
|
|
244
|
+
"agent_id": self.agent_id,
|
|
245
|
+
"goal": goal,
|
|
246
|
+
"allowed_tools": allowed_tools,
|
|
247
|
+
"allowed_resources": allowed_resources or [],
|
|
248
|
+
"resource_constraints": resource_constraints or {},
|
|
249
|
+
"user_id": user_id,
|
|
250
|
+
"risk_tier": risk_tier,
|
|
251
|
+
"ttl_seconds": ttl_seconds,
|
|
252
|
+
},
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
def verify_passport(
|
|
256
|
+
self,
|
|
257
|
+
token: str,
|
|
258
|
+
audience: str,
|
|
259
|
+
agent_id: Optional[str] = None,
|
|
260
|
+
user_id: Optional[str] = None,
|
|
261
|
+
current_tool_manifest_hash: Optional[str] = None,
|
|
262
|
+
policy_hash: Optional[str] = None,
|
|
263
|
+
) -> Dict[str, Any]:
|
|
264
|
+
"""Verify a passport (returns ``{valid, errors}``)."""
|
|
265
|
+
return self._post(
|
|
266
|
+
"/api/v1/passports/verify",
|
|
267
|
+
{
|
|
268
|
+
"token": token,
|
|
269
|
+
"audience": audience,
|
|
270
|
+
"agent_id": agent_id,
|
|
271
|
+
"user_id": user_id,
|
|
272
|
+
"current_tool_manifest_hash": current_tool_manifest_hash,
|
|
273
|
+
"policy_hash": policy_hash,
|
|
274
|
+
},
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
def revoke_passport(self, jti: str, reason: Optional[str] = None) -> Dict[str, Any]:
|
|
278
|
+
"""Revoke a passport by its ``jti`` (returns ``{revoked}``)."""
|
|
279
|
+
return self._post("/api/v1/passports/revoke", {"jti": jti, "reason": reason})
|
|
280
|
+
|
|
281
|
+
def record_evidence(
|
|
282
|
+
self,
|
|
283
|
+
event_type: str,
|
|
284
|
+
chain_id: str,
|
|
285
|
+
tool_id: Optional[str] = None,
|
|
286
|
+
decision: Optional[str] = None,
|
|
287
|
+
reason_code: Optional[str] = None,
|
|
288
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
289
|
+
request_hash: Optional[str] = None,
|
|
290
|
+
response_hash: Optional[str] = None,
|
|
291
|
+
) -> Dict[str, Any]:
|
|
292
|
+
"""Append an event to a tamper-evident evidence chain.
|
|
293
|
+
|
|
294
|
+
Returns ``{event_id, current_event_hash}``.
|
|
295
|
+
"""
|
|
296
|
+
return self._post(
|
|
297
|
+
"/api/v1/evidence/events",
|
|
298
|
+
{
|
|
299
|
+
"event_type": event_type,
|
|
300
|
+
"chain_id": chain_id,
|
|
301
|
+
"tool_id": tool_id,
|
|
302
|
+
"decision": decision,
|
|
303
|
+
"reason_code": reason_code,
|
|
304
|
+
"metadata": metadata,
|
|
305
|
+
"request_hash": request_hash,
|
|
306
|
+
"response_hash": response_hash,
|
|
307
|
+
},
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
# ------------------------------------------------------------------ #
|
|
311
|
+
# Local helpers (stdlib hashing — no network, mirror TS canonical hashing)
|
|
312
|
+
# ------------------------------------------------------------------ #
|
|
313
|
+
@staticmethod
|
|
314
|
+
def hash_payload(payload: Any) -> str:
|
|
315
|
+
"""SHA-256 of a canonical (sorted-key) JSON encoding of ``payload``.
|
|
316
|
+
|
|
317
|
+
Useful for ``request_hash`` / ``response_hash`` on evidence events.
|
|
318
|
+
"""
|
|
319
|
+
canonical = json.dumps(
|
|
320
|
+
payload, sort_keys=True, separators=(",", ":"), ensure_ascii=False
|
|
321
|
+
).encode("utf-8")
|
|
322
|
+
return hashlib.sha256(canonical).hexdigest()
|
|
323
|
+
|
|
324
|
+
@staticmethod
|
|
325
|
+
def sign_payload(secret: str, payload: Any) -> str:
|
|
326
|
+
"""HMAC-SHA256 hex signature over a canonical JSON encoding."""
|
|
327
|
+
canonical = json.dumps(
|
|
328
|
+
payload, sort_keys=True, separators=(",", ":"), ensure_ascii=False
|
|
329
|
+
).encode("utf-8")
|
|
330
|
+
return hmac.new(secret.encode("utf-8"), canonical, hashlib.sha256).hexdigest()
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def create_actpass(
|
|
334
|
+
api_key: str,
|
|
335
|
+
tenant_id: Optional[str] = None,
|
|
336
|
+
agent_id: Optional[str] = None,
|
|
337
|
+
base_url: Optional[str] = None,
|
|
338
|
+
) -> ActPassClient:
|
|
339
|
+
"""Convenience factory mirroring the TS ``createActPass`` (spec section 14.2).
|
|
340
|
+
|
|
341
|
+
Only ``api_key`` is required: the server resolves tenant + agent from it.
|
|
342
|
+
"""
|
|
343
|
+
return ActPassClient(
|
|
344
|
+
api_key=api_key,
|
|
345
|
+
tenant_id=tenant_id,
|
|
346
|
+
agent_id=agent_id,
|
|
347
|
+
base_url=base_url,
|
|
348
|
+
)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: actpass
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: ActPass Python SDK — signed action authorization for AI agents (zero dependencies)
|
|
5
|
+
Project-URL: Homepage, https://app.actpass.org
|
|
6
|
+
Author: ActPass
|
|
7
|
+
License: MIT
|
|
8
|
+
Keywords: agents,ai,authorization,mcp,security
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Topic :: Security
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# ActPass Python SDK
|
|
16
|
+
|
|
17
|
+
Zero-dependency (stdlib-only) Python client for the ActPass runtime, mirroring
|
|
18
|
+
the TypeScript `@actpass/sdk` surface (spec §14).
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install actpass
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage (§14.2)
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from actpass import create_actpass
|
|
30
|
+
|
|
31
|
+
# The API key IS your identity: the server resolves tenant + agent from it.
|
|
32
|
+
client = create_actpass(api_key="sk_...")
|
|
33
|
+
|
|
34
|
+
# Preflight + guarded execution in one call. execute_fn runs ONLY on allow/warn.
|
|
35
|
+
result = client.guard(
|
|
36
|
+
goal="resolve_refund_request",
|
|
37
|
+
tool="stripe.refund.create",
|
|
38
|
+
resource="stripe:charge:ch_123",
|
|
39
|
+
args={"chargeId": "ch_123", "amount": 14900, "currency": "USD"},
|
|
40
|
+
execute_fn=lambda ctx: stripe.Refund.create(charge="ch_123", amount=14900),
|
|
41
|
+
)
|
|
42
|
+
print(result["decision"]["decision"]) # allow | deny | require_approval | ...
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Other methods: `preflight()`, `issue_passport()`, `verify_passport()`,
|
|
46
|
+
`revoke_passport()`, `record_evidence()`, plus `hash_payload()` /
|
|
47
|
+
`sign_payload()` helpers for evidence hashing.
|
|
48
|
+
|
|
49
|
+
## Behavior
|
|
50
|
+
- **Fails closed:** any non-2xx (except 401/403, which return the parsed body)
|
|
51
|
+
raises `ActPassError`; a network failure raises too. `guard()` only executes
|
|
52
|
+
your function on `allow`/`warn`.
|
|
53
|
+
- **Tenant is a hint:** sent as `x-actpass-tenant`; the gateway resolves the
|
|
54
|
+
authoritative tenant from the authenticated principal (§11.2).
|
|
55
|
+
- **Zero dependencies:** `urllib`/`json`/`hmac`/`hashlib` from the stdlib only.
|
|
56
|
+
|
|
57
|
+
## Test
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
cd packages/sdk-python && python -m unittest discover -s tests
|
|
61
|
+
```
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
actpass/__init__.py,sha256=hohXmKfVGOP0TBUnHWPkruaNMzEDujICT93ZDbnynA8,700
|
|
2
|
+
actpass/client.py,sha256=Zd7xMKGHAKE1BF7h9v7OuToFsviZ3E83YNvlohkBQOM,12752
|
|
3
|
+
actpass-1.1.0.dist-info/METADATA,sha256=Y8XIFzLhEf6TLBAaajtAd25dSKopw3msajgJv9jfLao,1986
|
|
4
|
+
actpass-1.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
5
|
+
actpass-1.1.0.dist-info/RECORD,,
|