nxtid-sdk 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.
- nxtid_sdk/__init__.py +5 -0
- nxtid_sdk/client.py +355 -0
- nxtid_sdk/entra.py +91 -0
- nxtid_sdk/exceptions.py +24 -0
- nxtid_sdk-0.1.0.dist-info/METADATA +6 -0
- nxtid_sdk-0.1.0.dist-info/RECORD +8 -0
- nxtid_sdk-0.1.0.dist-info/WHEEL +5 -0
- nxtid_sdk-0.1.0.dist-info/top_level.txt +1 -0
nxtid_sdk/__init__.py
ADDED
nxtid_sdk/client.py
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _raise_for_status(resp: httpx.Response) -> None:
|
|
10
|
+
"""Like raise_for_status() but includes the response body in the exception message."""
|
|
11
|
+
if resp.is_error:
|
|
12
|
+
try:
|
|
13
|
+
detail = resp.json()
|
|
14
|
+
except Exception:
|
|
15
|
+
detail = resp.text
|
|
16
|
+
raise httpx.HTTPStatusError(
|
|
17
|
+
f"Client error '{resp.status_code} {resp.reason_phrase}' for url '{resp.url}' — {detail}",
|
|
18
|
+
request=resp.request,
|
|
19
|
+
response=resp,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class WalletResult:
|
|
25
|
+
wallet_address: str
|
|
26
|
+
email: str
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class VerifyStartResult:
|
|
31
|
+
flow: str # 'onboarding' | 'login' | 'choose_document'
|
|
32
|
+
widget_url: Optional[str] = None
|
|
33
|
+
operation_id: Optional[str] = None
|
|
34
|
+
transaction_id: Optional[str] = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class VerifyCompleteResult:
|
|
39
|
+
is_verified: bool
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class PermissionsResult:
|
|
44
|
+
allowed: bool
|
|
45
|
+
allowed_tools: list[str] = field(default_factory=list)
|
|
46
|
+
denied_tools: list[str] = field(default_factory=list)
|
|
47
|
+
reason: Optional[str] = None
|
|
48
|
+
|
|
49
|
+
def has_permission(self, tool_name: str) -> bool:
|
|
50
|
+
if not self.allowed:
|
|
51
|
+
return False
|
|
52
|
+
if not self.allowed_tools or "ALL" in self.allowed_tools:
|
|
53
|
+
return True
|
|
54
|
+
return tool_name in self.allowed_tools
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
_ENV_URLS: dict[str, dict[str, str]] = {
|
|
58
|
+
"production": {
|
|
59
|
+
"ait": "https://ait-service.nxtlinq.ai",
|
|
60
|
+
"dashboard": "https://dashboard.nxtlinq.ai",
|
|
61
|
+
},
|
|
62
|
+
"staging": {
|
|
63
|
+
"ait": "https://staging-ait-service.nxtlinq.ai",
|
|
64
|
+
"dashboard": "https://dashboard-staging.nxtlinq.ai",
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class NxtidSDK:
|
|
70
|
+
"""
|
|
71
|
+
nxtID Python SDK.
|
|
72
|
+
|
|
73
|
+
Provides wallet connection, AuthID identity verification, and AIT
|
|
74
|
+
permission checks.
|
|
75
|
+
|
|
76
|
+
Usage::
|
|
77
|
+
|
|
78
|
+
sdk = NxtidSDK(
|
|
79
|
+
api_key="...",
|
|
80
|
+
api_secret="...",
|
|
81
|
+
env="production", # "production" (default) or "staging"
|
|
82
|
+
service_id="your-service-id", # optional, for log_chat
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# Register tools on startup
|
|
86
|
+
await sdk.register_tools([{"name": "transfer_funds", "description": "..."}])
|
|
87
|
+
|
|
88
|
+
# Connect wallet
|
|
89
|
+
wallet = await sdk.connect_wallet(email="user@example.com")
|
|
90
|
+
|
|
91
|
+
# Start IDV (returns widget URL for the user to complete)
|
|
92
|
+
verify = await sdk.start_verification(wallet.wallet_address, id_type="40")
|
|
93
|
+
|
|
94
|
+
# After user completes the widget:
|
|
95
|
+
result = await sdk.complete_verification(
|
|
96
|
+
wallet.wallet_address,
|
|
97
|
+
operation_id=verify.operation_id,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Check AIT permissions
|
|
101
|
+
perms = await sdk.get_permissions(wallet.wallet_address)
|
|
102
|
+
if perms.has_permission("transfer_funds"):
|
|
103
|
+
...
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
def __init__(
|
|
107
|
+
self,
|
|
108
|
+
api_key: str,
|
|
109
|
+
api_secret: str,
|
|
110
|
+
env: str = "production",
|
|
111
|
+
service_id: Optional[str] = None,
|
|
112
|
+
) -> None:
|
|
113
|
+
if env not in _ENV_URLS:
|
|
114
|
+
raise ValueError(f"env must be 'production' or 'staging', got '{env}'")
|
|
115
|
+
urls = _ENV_URLS[env]
|
|
116
|
+
self._base_url = urls["ait"]
|
|
117
|
+
self._dashboard_url = urls["dashboard"]
|
|
118
|
+
self._service_id = service_id
|
|
119
|
+
self._headers = {
|
|
120
|
+
"x-api-key": api_key,
|
|
121
|
+
"x-api-secret": api_secret,
|
|
122
|
+
"Content-Type": "application/json",
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
def _url(self, path: str) -> str:
|
|
126
|
+
return f"{self._base_url}{path}"
|
|
127
|
+
|
|
128
|
+
def _dashboard_api_url(self, path: str) -> str:
|
|
129
|
+
if not self._dashboard_url:
|
|
130
|
+
raise ValueError("dashboard_url is required for this operation")
|
|
131
|
+
return f"{self._dashboard_url}{path}"
|
|
132
|
+
|
|
133
|
+
# ── Wallet ────────────────────────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
async def connect_wallet(self, email: str) -> WalletResult:
|
|
136
|
+
"""Get (or create) a MetaKeep-managed wallet for the given email."""
|
|
137
|
+
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
138
|
+
resp = await client.post(
|
|
139
|
+
self._url("/api/wallet/connect"),
|
|
140
|
+
json={"email": email},
|
|
141
|
+
headers=self._headers,
|
|
142
|
+
)
|
|
143
|
+
_raise_for_status(resp)
|
|
144
|
+
data = resp.json()
|
|
145
|
+
return WalletResult(wallet_address=data["walletAddress"], email=data["email"])
|
|
146
|
+
|
|
147
|
+
# ── Verification ──────────────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
async def start_verification(
|
|
150
|
+
self,
|
|
151
|
+
wallet_address: str,
|
|
152
|
+
id_type: Optional[str] = None,
|
|
153
|
+
) -> VerifyStartResult:
|
|
154
|
+
"""
|
|
155
|
+
Start AuthID identity verification.
|
|
156
|
+
|
|
157
|
+
Returns a widget URL for the user to open in a browser or iframe.
|
|
158
|
+
- Returning users: flow='login' (face scan)
|
|
159
|
+
- New users: flow='onboarding' (document capture, id_type required)
|
|
160
|
+
- New users w/o id_type: flow='choose_document' (prompt user to pick)
|
|
161
|
+
"""
|
|
162
|
+
body: dict = {"walletAddress": wallet_address}
|
|
163
|
+
if id_type:
|
|
164
|
+
body["idType"] = id_type
|
|
165
|
+
|
|
166
|
+
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
167
|
+
resp = await client.post(
|
|
168
|
+
self._url("/api/verify/start"),
|
|
169
|
+
json=body,
|
|
170
|
+
headers=self._headers,
|
|
171
|
+
)
|
|
172
|
+
_raise_for_status(resp)
|
|
173
|
+
data = resp.json()
|
|
174
|
+
return VerifyStartResult(
|
|
175
|
+
flow=data["flow"],
|
|
176
|
+
widget_url=data.get("widgetUrl"),
|
|
177
|
+
operation_id=data.get("operationId"),
|
|
178
|
+
transaction_id=data.get("transactionId"),
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
async def complete_verification(
|
|
182
|
+
self,
|
|
183
|
+
wallet_address: str,
|
|
184
|
+
operation_id: Optional[str] = None,
|
|
185
|
+
transaction_id: Optional[str] = None,
|
|
186
|
+
) -> VerifyCompleteResult:
|
|
187
|
+
"""
|
|
188
|
+
Call after the user completes the AuthID widget.
|
|
189
|
+
|
|
190
|
+
Pass operation_id (onboarding flow) or transaction_id (login flow)
|
|
191
|
+
from the VerifyStartResult.
|
|
192
|
+
"""
|
|
193
|
+
body: dict = {"walletAddress": wallet_address}
|
|
194
|
+
if operation_id:
|
|
195
|
+
body["operationId"] = operation_id
|
|
196
|
+
if transaction_id:
|
|
197
|
+
body["transactionId"] = transaction_id
|
|
198
|
+
|
|
199
|
+
# Generous timeout: complete_verification polls AuthID for up to 60s
|
|
200
|
+
async with httpx.AsyncClient(timeout=90.0) as client:
|
|
201
|
+
resp = await client.post(
|
|
202
|
+
self._url("/api/verify/complete"),
|
|
203
|
+
json=body,
|
|
204
|
+
headers=self._headers,
|
|
205
|
+
)
|
|
206
|
+
_raise_for_status(resp)
|
|
207
|
+
data = resp.json()
|
|
208
|
+
return VerifyCompleteResult(is_verified=data["isVerified"])
|
|
209
|
+
|
|
210
|
+
# ── Permissions ───────────────────────────────────────────────────────────
|
|
211
|
+
|
|
212
|
+
async def set_permissions(
|
|
213
|
+
self,
|
|
214
|
+
wallet_address: str,
|
|
215
|
+
permissions: list[str],
|
|
216
|
+
granted_by: Optional[str] = None,
|
|
217
|
+
) -> dict:
|
|
218
|
+
"""
|
|
219
|
+
Create an AIT on-chain with the given permissions for wallet_address.
|
|
220
|
+
|
|
221
|
+
requested_by: email or identifier of whoever triggered this request —
|
|
222
|
+
stored in the AIT record for audit purposes. Optional;
|
|
223
|
+
existing callers that omit it are unaffected.
|
|
224
|
+
"""
|
|
225
|
+
body: dict = {"address": wallet_address, "permissions": permissions}
|
|
226
|
+
if granted_by:
|
|
227
|
+
body["grantedBy"] = granted_by
|
|
228
|
+
|
|
229
|
+
async with httpx.AsyncClient(timeout=60.0) as client:
|
|
230
|
+
resp = await client.post(
|
|
231
|
+
self._url("/api/ait/set-permissions"),
|
|
232
|
+
json=body,
|
|
233
|
+
headers=self._headers,
|
|
234
|
+
)
|
|
235
|
+
_raise_for_status(resp)
|
|
236
|
+
return resp.json()
|
|
237
|
+
|
|
238
|
+
async def get_admin_granted_tools(self, wallet_address: str) -> list[str]:
|
|
239
|
+
"""
|
|
240
|
+
Return the tools admin has pre-approved for this user to self-service request.
|
|
241
|
+
|
|
242
|
+
Empty list means admin hasn't granted this user any tools yet — the caller
|
|
243
|
+
should show "waiting for administrator" rather than checkboxes.
|
|
244
|
+
"""
|
|
245
|
+
async with httpx.AsyncClient(timeout=15.0) as client:
|
|
246
|
+
resp = await client.get(
|
|
247
|
+
self._url("/api/agent/permissions"),
|
|
248
|
+
params={"walletAddress": wallet_address},
|
|
249
|
+
headers=self._headers,
|
|
250
|
+
)
|
|
251
|
+
_raise_for_status(resp)
|
|
252
|
+
return resp.json().get("allowedTools", [])
|
|
253
|
+
|
|
254
|
+
async def get_permissions(self, wallet_address: str) -> PermissionsResult:
|
|
255
|
+
"""Query admin-granted AIT tool permissions for the given wallet address."""
|
|
256
|
+
async with httpx.AsyncClient(timeout=15.0) as client:
|
|
257
|
+
resp = await client.get(
|
|
258
|
+
self._url("/api/agent/user-permission"),
|
|
259
|
+
params={"walletAddress": wallet_address},
|
|
260
|
+
headers=self._headers,
|
|
261
|
+
)
|
|
262
|
+
_raise_for_status(resp)
|
|
263
|
+
data = resp.json()
|
|
264
|
+
return PermissionsResult(
|
|
265
|
+
allowed=data.get("allowed", False),
|
|
266
|
+
allowed_tools=data.get("allowedTools", []),
|
|
267
|
+
denied_tools=data.get("deniedTools", []),
|
|
268
|
+
reason=data.get("reason"),
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# ── Agent management ─────────────────────────────────────────────────────
|
|
272
|
+
|
|
273
|
+
async def register_tools(self, tools: list) -> int:
|
|
274
|
+
"""
|
|
275
|
+
Register this agent's tool list with nxtlinq so admins can grant permissions.
|
|
276
|
+
|
|
277
|
+
Call once at agent startup. Each tool should be a dict with 'name' and
|
|
278
|
+
optionally 'description'.
|
|
279
|
+
|
|
280
|
+
Returns the number of tools registered.
|
|
281
|
+
|
|
282
|
+
Usage::
|
|
283
|
+
|
|
284
|
+
tool_count = await sdk.register_tools([
|
|
285
|
+
{"name": "transfer_funds", "description": "Move money between accounts"},
|
|
286
|
+
{"name": "get_account_balance", "description": "Read-only finance access"},
|
|
287
|
+
])
|
|
288
|
+
"""
|
|
289
|
+
async with httpx.AsyncClient(timeout=15.0) as client:
|
|
290
|
+
resp = await client.post(
|
|
291
|
+
self._url("/api/agent/tools"),
|
|
292
|
+
json={"tools": tools},
|
|
293
|
+
headers=self._headers,
|
|
294
|
+
)
|
|
295
|
+
_raise_for_status(resp)
|
|
296
|
+
return resp.json().get("toolCount", len(tools))
|
|
297
|
+
|
|
298
|
+
async def log_chat(
|
|
299
|
+
self,
|
|
300
|
+
session_id: str,
|
|
301
|
+
user_message: str,
|
|
302
|
+
assistant_message: str,
|
|
303
|
+
external_user_id: str,
|
|
304
|
+
user_name: str,
|
|
305
|
+
model: str = "",
|
|
306
|
+
has_tool_call: bool = False,
|
|
307
|
+
tool_name: str = "",
|
|
308
|
+
turn_index: Optional[int] = None,
|
|
309
|
+
) -> None:
|
|
310
|
+
"""
|
|
311
|
+
Send one conversation turn to the nxtlinq Dashboard for monitoring.
|
|
312
|
+
|
|
313
|
+
Requires dashboard_url and service_id to be set at SDK init time.
|
|
314
|
+
Fails silently — chat logging is non-fatal.
|
|
315
|
+
|
|
316
|
+
Usage::
|
|
317
|
+
|
|
318
|
+
await sdk.log_chat(
|
|
319
|
+
session_id="abc123",
|
|
320
|
+
user_message="What's my balance?",
|
|
321
|
+
assistant_message="Your balance is $500.",
|
|
322
|
+
external_user_id="user@example.com",
|
|
323
|
+
user_name="Alice",
|
|
324
|
+
has_tool_call=True,
|
|
325
|
+
tool_name="get_account_balance",
|
|
326
|
+
)
|
|
327
|
+
"""
|
|
328
|
+
if not self._dashboard_url or not self._service_id:
|
|
329
|
+
return
|
|
330
|
+
payload: dict = {
|
|
331
|
+
"aiType": model,
|
|
332
|
+
"hasToolCall": has_tool_call,
|
|
333
|
+
"toolName": tool_name,
|
|
334
|
+
"userInfo": {
|
|
335
|
+
"externalUserId": external_user_id,
|
|
336
|
+
"userName": user_name,
|
|
337
|
+
},
|
|
338
|
+
"chatHistory": {
|
|
339
|
+
"externalChatHistoryId": session_id,
|
|
340
|
+
"userMessage": user_message,
|
|
341
|
+
"assistantMessage": assistant_message,
|
|
342
|
+
},
|
|
343
|
+
}
|
|
344
|
+
if turn_index is not None:
|
|
345
|
+
payload["chatHistory"]["externalTurnIndex"] = turn_index
|
|
346
|
+
try:
|
|
347
|
+
async with httpx.AsyncClient(timeout=10.0) as client:
|
|
348
|
+
await client.post(
|
|
349
|
+
self._dashboard_api_url(f"/api/service/{self._service_id}/external-service-call-log"),
|
|
350
|
+
json=payload,
|
|
351
|
+
headers=self._headers,
|
|
352
|
+
)
|
|
353
|
+
except Exception:
|
|
354
|
+
pass
|
|
355
|
+
|
nxtid_sdk/entra.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
|
|
5
|
+
from fastapi import APIRouter
|
|
6
|
+
from fastapi.responses import JSONResponse
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class EntraAuth:
|
|
11
|
+
"""
|
|
12
|
+
Optional Microsoft Entra SSO module for the nxtID SDK.
|
|
13
|
+
|
|
14
|
+
When enabled, mounts two FastAPI routes onto the host app:
|
|
15
|
+
GET /auth/entra/config — returns client_id/tenant_id for MSAL
|
|
16
|
+
POST /auth/entra — receives MSAL token claims, writes session state
|
|
17
|
+
|
|
18
|
+
Usage::
|
|
19
|
+
|
|
20
|
+
from nxtid_sdk import EntraAuth
|
|
21
|
+
|
|
22
|
+
entra = EntraAuth(
|
|
23
|
+
client_id="...",
|
|
24
|
+
tenant_id="...",
|
|
25
|
+
get_session=get_or_create_session, # callable: session_id -> Session
|
|
26
|
+
)
|
|
27
|
+
entra.mount(app) # app is a FastAPI instance
|
|
28
|
+
|
|
29
|
+
# Check if enabled
|
|
30
|
+
if entra.enabled:
|
|
31
|
+
...
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
client_id: Optional[str],
|
|
37
|
+
tenant_id: Optional[str],
|
|
38
|
+
get_session,
|
|
39
|
+
) -> None:
|
|
40
|
+
self._client_id = client_id or ""
|
|
41
|
+
self._tenant_id = tenant_id or ""
|
|
42
|
+
self._get_session = get_session
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def enabled(self) -> bool:
|
|
46
|
+
return bool(self._client_id and self._tenant_id)
|
|
47
|
+
|
|
48
|
+
def mount(self, app) -> None:
|
|
49
|
+
"""Mount /auth/entra/config and /auth/entra routes onto a FastAPI app."""
|
|
50
|
+
router = self._build_router()
|
|
51
|
+
app.include_router(router)
|
|
52
|
+
|
|
53
|
+
def _build_router(self) -> APIRouter:
|
|
54
|
+
router = APIRouter()
|
|
55
|
+
client_id = self._client_id
|
|
56
|
+
tenant_id = self._tenant_id
|
|
57
|
+
enabled = self.enabled
|
|
58
|
+
get_session = self._get_session
|
|
59
|
+
|
|
60
|
+
class _EntraAuthRequest(BaseModel):
|
|
61
|
+
session_id: str
|
|
62
|
+
name: str
|
|
63
|
+
email: str
|
|
64
|
+
tenant_id: Optional[str] = None
|
|
65
|
+
roles: List[str] = []
|
|
66
|
+
|
|
67
|
+
@router.get("/auth/entra/config")
|
|
68
|
+
async def entra_config() -> dict:
|
|
69
|
+
return {
|
|
70
|
+
"enabled": enabled,
|
|
71
|
+
"client_id": client_id,
|
|
72
|
+
"tenant_id": tenant_id,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@router.post("/auth/entra")
|
|
76
|
+
async def auth_entra(req: _EntraAuthRequest) -> dict:
|
|
77
|
+
session = get_session(req.session_id)
|
|
78
|
+
session.entra_authenticated = True
|
|
79
|
+
session.entra_name = req.name
|
|
80
|
+
session.entra_email = req.email
|
|
81
|
+
session.entra_tenant_id = req.tenant_id
|
|
82
|
+
session.entra_roles = req.roles
|
|
83
|
+
return {
|
|
84
|
+
"session_id": session.session_id,
|
|
85
|
+
"entra_authenticated": True,
|
|
86
|
+
"name": req.name,
|
|
87
|
+
"email": req.email,
|
|
88
|
+
"roles": req.roles,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return router
|
nxtid_sdk/exceptions.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ToolCallBlockedError(Exception):
|
|
5
|
+
"""
|
|
6
|
+
Raised to block a tool execution before it reaches the LLM tool handler.
|
|
7
|
+
|
|
8
|
+
reason: 'verification_required' | 'permission_denied'
|
|
9
|
+
message: user-facing explanation
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, reason: str, message: str) -> None:
|
|
13
|
+
self.reason = reason
|
|
14
|
+
self.message = message
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def permission_denied(cls, tool_name: str, allowed_tools: list[str]) -> "ToolCallBlockedError":
|
|
18
|
+
tools_str = ", ".join(allowed_tools) if allowed_tools else "none"
|
|
19
|
+
return cls(
|
|
20
|
+
"permission_denied",
|
|
21
|
+
f"You don't have permission to use '{tool_name}'. "
|
|
22
|
+
f"Your current permissions: [{tools_str}]. "
|
|
23
|
+
"Contact your administrator.",
|
|
24
|
+
)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
nxtid_sdk/__init__.py,sha256=AfqMHUgVM3Co__RV75hYLp6LrNL9Cd_5mmTB2yKcXKw,164
|
|
2
|
+
nxtid_sdk/client.py,sha256=tSt8-2ThR0Tg1s3LjQij7d9eI-18VbhMfmt1LR7Pg4Y,12477
|
|
3
|
+
nxtid_sdk/entra.py,sha256=-JrQMuiySH-oxKZG5-_fxcmVks1ibZx6_Lw4WxkkyzM,2707
|
|
4
|
+
nxtid_sdk/exceptions.py,sha256=rX8xPS6q0-JyBMMIlzVqlJfKSFI1seNK1TyQs0o2BJ0,804
|
|
5
|
+
nxtid_sdk-0.1.0.dist-info/METADATA,sha256=uCV3DhntSLVS4Kk2HhEUG6prMN6hT5xQxcipFkmD0Ak,170
|
|
6
|
+
nxtid_sdk-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
7
|
+
nxtid_sdk-0.1.0.dist-info/top_level.txt,sha256=6iYVrtyl5MlEgcQpmR1EXAkEamhhEA6KTQjHvD94eA8,10
|
|
8
|
+
nxtid_sdk-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
nxtid_sdk
|