useoutlet 0.1.0__tar.gz

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,13 @@
1
+ node_modules/
2
+ dist/
3
+ coverage/
4
+ .DS_Store
5
+ .env
6
+ .env.*
7
+ .dev.vars
8
+ *.tgz
9
+ __pycache__/
10
+ .venv/
11
+ *.egg-info/
12
+ .pytest_cache/
13
+ .ruff_cache/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Outlet
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,238 @@
1
+ Metadata-Version: 2.5
2
+ Name: useoutlet
3
+ Version: 0.1.0
4
+ Summary: Connect your users' AI accounts to your app. Capped, revocable App keys, never raw credentials. Outlet is never in the data path.
5
+ Project-URL: Homepage, https://useoutlet.dev
6
+ Project-URL: Documentation, https://useoutlet.dev/docs/
7
+ Project-URL: Source, https://github.com/PARZ1V3L/outlet/tree/main/sdk-python
8
+ Project-URL: Issues, https://github.com/PARZ1V3L/outlet/issues
9
+ Author-email: Parz <hello@useoutlet.dev>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: ai,anthropic,api-key-management,api-keys,bring-your-own-key,byok,connect-your-ai,fal,gemini,google,llm,mcp,oauth,openai,openrouter,outlet,python
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: Software Development :: Libraries
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.10
26
+ Description-Content-Type: text/markdown
27
+
28
+ <p align="center">
29
+ <a href="https://useoutlet.dev"><img src="https://useoutlet.dev/og.png" alt="outlet: use the AI power you already pay for" width="640"></a>
30
+ </p>
31
+
32
+ <p align="center">
33
+ <a href="https://pypi.org/project/useoutlet/"><img src="https://img.shields.io/pypi/v/useoutlet?labelColor=18181A&color=D6F436" alt="PyPI version"></a>
34
+ <img src="https://img.shields.io/badge/dependencies-0-D6F436?labelColor=18181A" alt="zero dependencies">
35
+ <img src="https://img.shields.io/badge/types-included-D6F436?labelColor=18181A" alt="type hints included">
36
+ <img src="https://img.shields.io/badge/license-MIT-D6F436?labelColor=18181A" alt="MIT license">
37
+ </p>
38
+
39
+ # useoutlet
40
+
41
+ **Let your users plug in the AI they already pay for.**
42
+
43
+ Outlet connects a user's existing AI account (Anthropic, OpenAI, Google, or
44
+ any OpenAI-compatible provider) to your app through **capped, revocable App
45
+ keys, one per app.** Never their raw credentials. Your app calls the provider
46
+ directly with the official SDK. Outlet is **never in the data path**.
47
+
48
+ > Status: **direct mode works today** (validated bring-your-own-key, no
49
+ > server). Vault mode (capped, revocable App keys) is open.
50
+ > Register your app at useoutlet.dev/register.
51
+ > The same session in both modes. Protocol docs at [useoutlet.dev](https://useoutlet.dev).
52
+ > Feedback welcome.
53
+
54
+ This is the Python SDK, the server side of Outlet. It starts a Vault
55
+ connection, takes the session back, and refreshes, checks and revokes it.
56
+ Standard library only. Python 3.10 and up. The Connect your AI button, the
57
+ CLI and the MCP docs server live in the npm package,
58
+ [@useoutlet/sdk](https://www.npmjs.com/package/@useoutlet/sdk).
59
+
60
+ ## Why
61
+
62
+ - Users already pay for AI. They shouldn't pay again inside every app.
63
+ - Developers shouldn't store customer API keys (breach target, compliance).
64
+ - Raw keys are all-or-nothing. Outlet keys are per-app, capped, revocable.
65
+
66
+ ## Install
67
+
68
+ ```sh
69
+ pip install useoutlet
70
+ ```
71
+
72
+ ## Quickstart
73
+
74
+ ```sh
75
+ pip install useoutlet
76
+ ```
77
+
78
+ Start a Vault connection from your server. Vault needs a registered app ID
79
+ and app secret. [Register your app](https://useoutlet.dev/register) to get
80
+ them. The app secret stays on your server.
81
+
82
+ ```python
83
+ import os
84
+
85
+ from useoutlet import Outlet
86
+
87
+ outlet = Outlet(app_id="app_yourapp", app_secret=os.environ["OUTLET_APP_SECRET"])
88
+
89
+ # 1. Start a Vault connection request. Send the user to its grant URL.
90
+ request = outlet.connect(providers=["openai"], requested_cap_usd=10)
91
+ print("Approve the Vault connection at", request.grant_url)
92
+
93
+ # 2. Wait for the approval. The session holds the Vault App key.
94
+ session = outlet.wait(request.id)
95
+ print("Connected:", session.grant_id)
96
+
97
+ # 3. Call the provider like you already do, with its official SDK.
98
+ # ai = OpenAI(api_key=session.keys["openai"])
99
+ ```
100
+
101
+ Save it as quickstart.py and run it.
102
+
103
+ ```sh
104
+ python quickstart.py
105
+ ```
106
+
107
+ `session.keys["openai"]` holds the Vault App key. Store `session.grant_id`,
108
+ not the key. Then call the provider like you already do, with its official
109
+ SDK.
110
+
111
+ ### When a connection ends
112
+
113
+ A connection can end while your app is running: the user's Vault connection
114
+ is paused at its cap, revoked or disconnected, or its refresh token is gone.
115
+ The SDK turns each into one typed error, `ConnectionEndedError`, with
116
+ `reason` (`capped`, `revoked` or `expired`) and `grant_id`. `status()`,
117
+ `refresh()` and `wait()` raise it.
118
+
119
+ ```python
120
+ from useoutlet import ConnectionEndedError
121
+
122
+ try:
123
+ info = outlet.status(session.grant_id)
124
+ except ConnectionEndedError as e:
125
+ print(e.reason) # "capped", "revoked" or "expired"
126
+ ```
127
+
128
+ For a capped connection, the user raises the cap on useoutlet.dev and
129
+ `refresh()` returns the key. For a revoked or expired one, ask the user to
130
+ connect again.
131
+
132
+ ## Usage today (direct mode)
133
+
134
+ Your user pastes their own API key; the SDK validates it locally (catches
135
+ provider mix-ups, **refuses admin keys**) and hands back a session. No
136
+ network, nothing sent to Outlet. A local format check.
137
+
138
+ ```python
139
+ from useoutlet import direct
140
+
141
+ session = direct({"openai": user_pasted_key})
142
+
143
+ from openai import OpenAI
144
+
145
+ ai = OpenAI(api_key=session.keys["openai"])
146
+ ```
147
+
148
+ Works with **any OpenAI-compatible provider**. Pass the key under that
149
+ provider and point the OpenAI SDK at its base URL:
150
+
151
+ ```python
152
+ session = direct({"groq": user_pasted_key})
153
+
154
+ ai = OpenAI(api_key=session.keys["groq"], base_url="https://api.groq.com/openai/v1")
155
+ ```
156
+
157
+ OpenAI, Anthropic, and Google get strict key-format checks (mix-ups caught,
158
+ admin keys refused); other providers are accepted with the same admin-key
159
+ safety check, since their key formats vary.
160
+
161
+ ## Vault mode (the same session)
162
+
163
+ Same session shape. The pasted key becomes an App key: provisioned inside the
164
+ user's own account for your app alone, capped, and revocable.
165
+
166
+ ```python
167
+ # later: re-fetch keys (session.grant_id is the thing you persist)
168
+ fresh = outlet.refresh(session.grant_id)
169
+
170
+ # check spend / status
171
+ info = outlet.status(session.grant_id)
172
+ print(f"{info.spend_usd} of {info.cap_usd} used")
173
+
174
+ # the app's side of revoke
175
+ outlet.revoke(session.grant_id)
176
+ ```
177
+
178
+ ### No app secret? Public clients use PKCE
179
+
180
+ An app without an app secret registers as a **public client** instead: no
181
+ secret, just a registered return address. Grant creation is secured by PKCE
182
+ (protocol docs at [useoutlet.dev](https://useoutlet.dev)), so the flow spans
183
+ the return address in two calls:
184
+
185
+ ```python
186
+ outlet = Outlet(app_id="app_yourapp") # no secret: a public client
187
+
188
+ # on your [ Connect your AI ] route: generates PKCE, send the user to the grant URL
189
+ request = outlet.connect(providers=["anthropic"], redirect_uri="https://yourapp.com/outlet/return")
190
+
191
+ # on the route at the return address: verifies state, exchanges the code
192
+ session = outlet.exchange(code, state)
193
+ ai = Anthropic(api_key=session.keys["anthropic"])
194
+
195
+ # session.refresh_token (not an app secret) authorizes later calls. The client holds it.
196
+ fresh = outlet.refresh(session.grant_id)
197
+ # fresh.refresh_token replaces the one you sent; the old one now gets 401. Store the new one.
198
+ ```
199
+
200
+ The provider key arrives only on the back-channel exchange, never in the
201
+ redirect URL. The `refresh_token` belongs to this one grant.
202
+
203
+ Outlet registers `http://localhost/outlet/return` for every app. It matches
204
+ any port.
205
+
206
+ The client holds each grant's refresh token in memory. From another process,
207
+ pass `refresh_token=` to `refresh()`, `status()` or `revoke()` from your own
208
+ store, and pass the `GrantRequest` you kept to `exchange()`. `AsyncOutlet`
209
+ has the same methods for asyncio apps.
210
+
211
+ Example: [examples/python/](https://github.com/PARZ1V3L/outlet/tree/main/examples/python),
212
+ a FastAPI app with the connect route and the return address.
213
+
214
+ ## Several providers
215
+
216
+ Create a separate connection for each provider your app uses. A text
217
+ connection can sit alongside connections for video and voice. If the same
218
+ provider serves several models, those models can use that provider’s
219
+ connection.
220
+
221
+ ## What your app never sees (vault mode)
222
+
223
+ - The user's root API key or account credentials
224
+ - Other apps' keys or spend
225
+ - Anything after revocation. A revoked grant stops working
226
+
227
+ The user's own key, in your app, by their choice. Validated, never an admin
228
+ credential. The same API you keep when you upgrade to vault mode.
229
+
230
+ ## What Outlet never does
231
+
232
+ No proxying. No token markup. No model routing. No prompt storage. Free for
233
+ end users, forever. Direct mode is free. The vault is the paid product for
234
+ developers. MIT licensed.
235
+
236
+ ---
237
+
238
+ Docs: [useoutlet.dev](https://useoutlet.dev) · [Source](https://github.com/PARZ1V3L/outlet) · Contact: hello@useoutlet.dev
@@ -0,0 +1,211 @@
1
+ <p align="center">
2
+ <a href="https://useoutlet.dev"><img src="https://useoutlet.dev/og.png" alt="outlet: use the AI power you already pay for" width="640"></a>
3
+ </p>
4
+
5
+ <p align="center">
6
+ <a href="https://pypi.org/project/useoutlet/"><img src="https://img.shields.io/pypi/v/useoutlet?labelColor=18181A&color=D6F436" alt="PyPI version"></a>
7
+ <img src="https://img.shields.io/badge/dependencies-0-D6F436?labelColor=18181A" alt="zero dependencies">
8
+ <img src="https://img.shields.io/badge/types-included-D6F436?labelColor=18181A" alt="type hints included">
9
+ <img src="https://img.shields.io/badge/license-MIT-D6F436?labelColor=18181A" alt="MIT license">
10
+ </p>
11
+
12
+ # useoutlet
13
+
14
+ **Let your users plug in the AI they already pay for.**
15
+
16
+ Outlet connects a user's existing AI account (Anthropic, OpenAI, Google, or
17
+ any OpenAI-compatible provider) to your app through **capped, revocable App
18
+ keys, one per app.** Never their raw credentials. Your app calls the provider
19
+ directly with the official SDK. Outlet is **never in the data path**.
20
+
21
+ > Status: **direct mode works today** (validated bring-your-own-key, no
22
+ > server). Vault mode (capped, revocable App keys) is open.
23
+ > Register your app at useoutlet.dev/register.
24
+ > The same session in both modes. Protocol docs at [useoutlet.dev](https://useoutlet.dev).
25
+ > Feedback welcome.
26
+
27
+ This is the Python SDK, the server side of Outlet. It starts a Vault
28
+ connection, takes the session back, and refreshes, checks and revokes it.
29
+ Standard library only. Python 3.10 and up. The Connect your AI button, the
30
+ CLI and the MCP docs server live in the npm package,
31
+ [@useoutlet/sdk](https://www.npmjs.com/package/@useoutlet/sdk).
32
+
33
+ ## Why
34
+
35
+ - Users already pay for AI. They shouldn't pay again inside every app.
36
+ - Developers shouldn't store customer API keys (breach target, compliance).
37
+ - Raw keys are all-or-nothing. Outlet keys are per-app, capped, revocable.
38
+
39
+ ## Install
40
+
41
+ ```sh
42
+ pip install useoutlet
43
+ ```
44
+
45
+ ## Quickstart
46
+
47
+ ```sh
48
+ pip install useoutlet
49
+ ```
50
+
51
+ Start a Vault connection from your server. Vault needs a registered app ID
52
+ and app secret. [Register your app](https://useoutlet.dev/register) to get
53
+ them. The app secret stays on your server.
54
+
55
+ ```python
56
+ import os
57
+
58
+ from useoutlet import Outlet
59
+
60
+ outlet = Outlet(app_id="app_yourapp", app_secret=os.environ["OUTLET_APP_SECRET"])
61
+
62
+ # 1. Start a Vault connection request. Send the user to its grant URL.
63
+ request = outlet.connect(providers=["openai"], requested_cap_usd=10)
64
+ print("Approve the Vault connection at", request.grant_url)
65
+
66
+ # 2. Wait for the approval. The session holds the Vault App key.
67
+ session = outlet.wait(request.id)
68
+ print("Connected:", session.grant_id)
69
+
70
+ # 3. Call the provider like you already do, with its official SDK.
71
+ # ai = OpenAI(api_key=session.keys["openai"])
72
+ ```
73
+
74
+ Save it as quickstart.py and run it.
75
+
76
+ ```sh
77
+ python quickstart.py
78
+ ```
79
+
80
+ `session.keys["openai"]` holds the Vault App key. Store `session.grant_id`,
81
+ not the key. Then call the provider like you already do, with its official
82
+ SDK.
83
+
84
+ ### When a connection ends
85
+
86
+ A connection can end while your app is running: the user's Vault connection
87
+ is paused at its cap, revoked or disconnected, or its refresh token is gone.
88
+ The SDK turns each into one typed error, `ConnectionEndedError`, with
89
+ `reason` (`capped`, `revoked` or `expired`) and `grant_id`. `status()`,
90
+ `refresh()` and `wait()` raise it.
91
+
92
+ ```python
93
+ from useoutlet import ConnectionEndedError
94
+
95
+ try:
96
+ info = outlet.status(session.grant_id)
97
+ except ConnectionEndedError as e:
98
+ print(e.reason) # "capped", "revoked" or "expired"
99
+ ```
100
+
101
+ For a capped connection, the user raises the cap on useoutlet.dev and
102
+ `refresh()` returns the key. For a revoked or expired one, ask the user to
103
+ connect again.
104
+
105
+ ## Usage today (direct mode)
106
+
107
+ Your user pastes their own API key; the SDK validates it locally (catches
108
+ provider mix-ups, **refuses admin keys**) and hands back a session. No
109
+ network, nothing sent to Outlet. A local format check.
110
+
111
+ ```python
112
+ from useoutlet import direct
113
+
114
+ session = direct({"openai": user_pasted_key})
115
+
116
+ from openai import OpenAI
117
+
118
+ ai = OpenAI(api_key=session.keys["openai"])
119
+ ```
120
+
121
+ Works with **any OpenAI-compatible provider**. Pass the key under that
122
+ provider and point the OpenAI SDK at its base URL:
123
+
124
+ ```python
125
+ session = direct({"groq": user_pasted_key})
126
+
127
+ ai = OpenAI(api_key=session.keys["groq"], base_url="https://api.groq.com/openai/v1")
128
+ ```
129
+
130
+ OpenAI, Anthropic, and Google get strict key-format checks (mix-ups caught,
131
+ admin keys refused); other providers are accepted with the same admin-key
132
+ safety check, since their key formats vary.
133
+
134
+ ## Vault mode (the same session)
135
+
136
+ Same session shape. The pasted key becomes an App key: provisioned inside the
137
+ user's own account for your app alone, capped, and revocable.
138
+
139
+ ```python
140
+ # later: re-fetch keys (session.grant_id is the thing you persist)
141
+ fresh = outlet.refresh(session.grant_id)
142
+
143
+ # check spend / status
144
+ info = outlet.status(session.grant_id)
145
+ print(f"{info.spend_usd} of {info.cap_usd} used")
146
+
147
+ # the app's side of revoke
148
+ outlet.revoke(session.grant_id)
149
+ ```
150
+
151
+ ### No app secret? Public clients use PKCE
152
+
153
+ An app without an app secret registers as a **public client** instead: no
154
+ secret, just a registered return address. Grant creation is secured by PKCE
155
+ (protocol docs at [useoutlet.dev](https://useoutlet.dev)), so the flow spans
156
+ the return address in two calls:
157
+
158
+ ```python
159
+ outlet = Outlet(app_id="app_yourapp") # no secret: a public client
160
+
161
+ # on your [ Connect your AI ] route: generates PKCE, send the user to the grant URL
162
+ request = outlet.connect(providers=["anthropic"], redirect_uri="https://yourapp.com/outlet/return")
163
+
164
+ # on the route at the return address: verifies state, exchanges the code
165
+ session = outlet.exchange(code, state)
166
+ ai = Anthropic(api_key=session.keys["anthropic"])
167
+
168
+ # session.refresh_token (not an app secret) authorizes later calls. The client holds it.
169
+ fresh = outlet.refresh(session.grant_id)
170
+ # fresh.refresh_token replaces the one you sent; the old one now gets 401. Store the new one.
171
+ ```
172
+
173
+ The provider key arrives only on the back-channel exchange, never in the
174
+ redirect URL. The `refresh_token` belongs to this one grant.
175
+
176
+ Outlet registers `http://localhost/outlet/return` for every app. It matches
177
+ any port.
178
+
179
+ The client holds each grant's refresh token in memory. From another process,
180
+ pass `refresh_token=` to `refresh()`, `status()` or `revoke()` from your own
181
+ store, and pass the `GrantRequest` you kept to `exchange()`. `AsyncOutlet`
182
+ has the same methods for asyncio apps.
183
+
184
+ Example: [examples/python/](https://github.com/PARZ1V3L/outlet/tree/main/examples/python),
185
+ a FastAPI app with the connect route and the return address.
186
+
187
+ ## Several providers
188
+
189
+ Create a separate connection for each provider your app uses. A text
190
+ connection can sit alongside connections for video and voice. If the same
191
+ provider serves several models, those models can use that provider’s
192
+ connection.
193
+
194
+ ## What your app never sees (vault mode)
195
+
196
+ - The user's root API key or account credentials
197
+ - Other apps' keys or spend
198
+ - Anything after revocation. A revoked grant stops working
199
+
200
+ The user's own key, in your app, by their choice. Validated, never an admin
201
+ credential. The same API you keep when you upgrade to vault mode.
202
+
203
+ ## What Outlet never does
204
+
205
+ No proxying. No token markup. No model routing. No prompt storage. Free for
206
+ end users, forever. Direct mode is free. The vault is the paid product for
207
+ developers. MIT licensed.
208
+
209
+ ---
210
+
211
+ Docs: [useoutlet.dev](https://useoutlet.dev) · [Source](https://github.com/PARZ1V3L/outlet) · Contact: hello@useoutlet.dev
@@ -0,0 +1,75 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "useoutlet"
7
+ dynamic = ["version"]
8
+ description = "Connect your users' AI accounts to your app. Capped, revocable App keys, never raw credentials. Outlet is never in the data path."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.10"
13
+ authors = [{ name = "Parz", email = "hello@useoutlet.dev" }]
14
+ keywords = [
15
+ "ai",
16
+ "byok",
17
+ "bring-your-own-key",
18
+ "api-keys",
19
+ "api-key-management",
20
+ "llm",
21
+ "anthropic",
22
+ "openai",
23
+ "google",
24
+ "gemini",
25
+ "oauth",
26
+ "outlet",
27
+ "mcp",
28
+ "openrouter",
29
+ "fal",
30
+ "connect-your-ai",
31
+ "python",
32
+ ]
33
+ classifiers = [
34
+ "Development Status :: 3 - Alpha",
35
+ "Intended Audience :: Developers",
36
+ "Operating System :: OS Independent",
37
+ "Programming Language :: Python :: 3",
38
+ "Programming Language :: Python :: 3 :: Only",
39
+ "Programming Language :: Python :: 3.10",
40
+ "Programming Language :: Python :: 3.11",
41
+ "Programming Language :: Python :: 3.12",
42
+ "Programming Language :: Python :: 3.13",
43
+ "Programming Language :: Python :: 3.14",
44
+ "Topic :: Software Development :: Libraries",
45
+ "Typing :: Typed",
46
+ ]
47
+ dependencies = []
48
+
49
+ [project.urls]
50
+ Homepage = "https://useoutlet.dev"
51
+ Documentation = "https://useoutlet.dev/docs/"
52
+ Source = "https://github.com/PARZ1V3L/outlet/tree/main/sdk-python"
53
+ Issues = "https://github.com/PARZ1V3L/outlet/issues"
54
+
55
+ [dependency-groups]
56
+ test = ["pytest>=9"]
57
+
58
+ [tool.hatch.version]
59
+ path = "src/useoutlet/_version.py"
60
+
61
+ [tool.hatch.build.targets.wheel]
62
+ packages = ["src/useoutlet"]
63
+
64
+ [tool.hatch.build.targets.sdist]
65
+ exclude = ["/scripts"]
66
+
67
+ [tool.pytest.ini_options]
68
+ testpaths = ["tests"]
69
+
70
+ [tool.ruff]
71
+ line-length = 100
72
+ target-version = "py310"
73
+
74
+ [tool.ruff.lint]
75
+ select = ["E", "F", "W", "I", "UP", "B", "SIM"]
@@ -0,0 +1,79 @@
1
+ """useoutlet: connect your users' AI accounts to your app.
2
+
3
+ Status: direct mode is live today. Vault mode (connect / refresh / status /
4
+ revoke) is open. Register your app at useoutlet.dev/register.
5
+
6
+ from useoutlet import Outlet
7
+
8
+ outlet = Outlet(app_id="app_yourapp", app_secret=os.environ["OUTLET_APP_SECRET"])
9
+ request = outlet.connect(providers=["openai"], requested_cap_usd=10)
10
+ # send the user to request.grant_url, then
11
+ session = outlet.wait(request.id)
12
+
13
+ # use the official provider SDK: Outlet is not in the data path
14
+ ai = OpenAI(api_key=session.keys["openai"])
15
+
16
+ A connection that ends (capped, revoked or expired) is a ConnectionEndedError
17
+ from status(), refresh() and wait(). The Connect your AI button, the CLI and
18
+ the MCP docs server live in the npm package, @useoutlet/sdk.
19
+ """
20
+
21
+ from ._version import __version__
22
+ from .client import AsyncOutlet, Outlet
23
+ from .direct import direct
24
+ from .grants import refresh, revoke, status
25
+ from .pkce import PkceChallenge, create_grant, exchange_code, pkce_challenge
26
+ from .providers import (
27
+ KeyShape,
28
+ ProviderCheck,
29
+ ProviderEntry,
30
+ ProviderKind,
31
+ ProviderMode,
32
+ ProviderModes,
33
+ get_provider,
34
+ provider_ids,
35
+ providers,
36
+ )
37
+ from .types import (
38
+ CapReason,
39
+ ConnectionEndedError,
40
+ EndReason,
41
+ GrantInfo,
42
+ GrantRequest,
43
+ GrantStatus,
44
+ Mode,
45
+ OutletError,
46
+ OutletSession,
47
+ )
48
+
49
+ __all__ = [
50
+ "AsyncOutlet",
51
+ "CapReason",
52
+ "ConnectionEndedError",
53
+ "EndReason",
54
+ "GrantInfo",
55
+ "GrantRequest",
56
+ "GrantStatus",
57
+ "KeyShape",
58
+ "Mode",
59
+ "Outlet",
60
+ "OutletError",
61
+ "OutletSession",
62
+ "PkceChallenge",
63
+ "ProviderCheck",
64
+ "ProviderEntry",
65
+ "ProviderKind",
66
+ "ProviderMode",
67
+ "ProviderModes",
68
+ "__version__",
69
+ "create_grant",
70
+ "direct",
71
+ "exchange_code",
72
+ "get_provider",
73
+ "pkce_challenge",
74
+ "provider_ids",
75
+ "providers",
76
+ "refresh",
77
+ "revoke",
78
+ "status",
79
+ ]
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"