aisubs 0.1.0 → 0.2.0
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.
- package/README.md +381 -425
- package/dist/auth.js +7 -2
- package/dist/cli.js +10 -7
- package/dist/dashboard/assets/index-DYe3pr2a.css +2 -0
- package/dist/dashboard/assets/index-V-AoOW2F.js +83 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/dashboard.d.ts +1 -0
- package/dist/dashboard.js +86 -27
- package/dist/http.js +140 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/store.d.ts +6 -0
- package/dist/store.js +51 -2
- package/examples/server.mjs +6 -4
- package/package.json +1 -1
- package/public/aisubs-dashboard.png +0 -0
- package/dist/dashboard/assets/index-BMoILzPw.js +0 -64
- package/dist/dashboard/assets/index-DHqDdNVe.css +0 -2
package/README.md
CHANGED
|
@@ -1,543 +1,526 @@
|
|
|
1
1
|
<table>
|
|
2
2
|
<tr>
|
|
3
3
|
<td><img src="./dashboard/public/aisubs-mark.svg" alt="AISubs icon" width="72"></td>
|
|
4
|
-
<td>
|
|
5
|
-
<h1>AISubs</h1>
|
|
6
|
-
<strong>Connect your AI subscriptions. Use them anywhere.</strong>
|
|
7
|
-
</td>
|
|
4
|
+
<td><h1>AISubs</h1><strong>Connect your AI subscriptions. Use them anywhere.</strong></td>
|
|
8
5
|
</tr>
|
|
9
6
|
</table>
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
API
|
|
8
|
+
AISubs connects provider accounts once and exposes each account through a local,
|
|
9
|
+
account-scoped API. Use it from the official OpenAI or Anthropic SDK, Vercel AI SDK,
|
|
10
|
+
TanStack AI, Python, cURL, or an app that accepts a custom API base URL.
|
|
13
11
|
|
|
14
12
|
<p align="center">
|
|
15
|
-
<img src="./public/aisubs-dashboard.png" alt="
|
|
13
|
+
<img src="./public/aisubs-dashboard.png" alt="AI Subs dashboard with provider connections and local API access" width="100%" />
|
|
16
14
|
</p>
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
<img src="./public/aisubs-grok-account.png" alt="AISubs Grok account details showing plan, usage, and integration examples" width="33%" />
|
|
21
|
-
<img src="./public/aisubs-copilot-account.png" alt="AISubs GitHub Copilot account details showing credits and integration examples" width="33%" />
|
|
22
|
-
</p>
|
|
23
|
-
|
|
24
|
-
> AISubs keeps credentials on your computer. It collects no telemetry, analytics, request logs, or activity history.
|
|
16
|
+
> Credentials, API keys, and requests remain on your computer. AISubs collects no
|
|
17
|
+
> telemetry or analytics. The dashboard keeps up to 200 redacted account request logs in memory for debugging.
|
|
25
18
|
|
|
26
19
|
## Quick start
|
|
27
20
|
|
|
21
|
+
AISubs requires [Node.js 24 or newer](https://nodejs.org/en/download/). Run it
|
|
22
|
+
without adding it to a project:
|
|
23
|
+
|
|
28
24
|
```bash
|
|
29
|
-
nubx aisubs dashboard # Nub
|
|
30
|
-
npx aisubs dashboard # npm
|
|
31
|
-
pnpm
|
|
32
|
-
bunx aisubs dashboard # Bun
|
|
25
|
+
nubx aisubs@latest dashboard # Nub
|
|
26
|
+
npx aisubs@latest dashboard # npm
|
|
27
|
+
pnpm dlx aisubs@latest dashboard # pnpm
|
|
28
|
+
bunx aisubs@latest dashboard # Bun
|
|
33
29
|
```
|
|
34
30
|
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
The terminal prints only the local dashboard URL. Open it, click **Add account**,
|
|
32
|
+
and complete the provider sign-in. The dashboard manages the persistent local API
|
|
33
|
+
key: reveal it, copy it, or deliberately regenerate it there.
|
|
37
34
|
|
|
38
|
-
|
|
35
|
+
The default URL is `http://127.0.0.1:4319`. Credentials and the API key are stored
|
|
36
|
+
under `~/.aisubs` and reused on later starts.
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
2. Ask AISubs which models and request format that account supports.
|
|
42
|
-
3. Send the provider-native request through that account.
|
|
38
|
+
## Use an account from any compatible app
|
|
43
39
|
|
|
44
|
-
|
|
40
|
+
Open an account in the dashboard and copy its base URL:
|
|
45
41
|
|
|
46
42
|
```text
|
|
47
|
-
|
|
48
|
-
ChatGPT / work
|
|
49
|
-
Claude / team
|
|
43
|
+
http://127.0.0.1:4319/aisubs/PROVIDER/ACCOUNT/v1
|
|
50
44
|
```
|
|
51
45
|
|
|
52
|
-
|
|
53
|
-
for each request.
|
|
46
|
+
Then configure the app with:
|
|
54
47
|
|
|
55
|
-
|
|
48
|
+
```text
|
|
49
|
+
API base URL: the account URL copied from AISubs
|
|
50
|
+
API key: the persistent key shown on the AISubs dashboard
|
|
51
|
+
Model: an exact model ID shown for that account
|
|
52
|
+
```
|
|
56
53
|
|
|
57
|
-
|
|
58
|
-
| -------------- | -------------- | ---------------------- | --------------------------- |
|
|
59
|
-
| ChatGPT | `chatgpt` | Browser or device code | Responses |
|
|
60
|
-
| Claude | `claude` | Browser | Anthropic Messages |
|
|
61
|
-
| GitHub Copilot | `copilot` | Device code | Read from the model catalog |
|
|
62
|
-
| Grok | `grok` | Device code | Read from the model catalog |
|
|
63
|
-
| OpenCode Go | `opencode-go` | API key | Read from the model catalog |
|
|
64
|
-
| OpenCode Zen | `opencode-zen` | API key | Read from the model catalog |
|
|
54
|
+
For apps configured with environment variables:
|
|
65
55
|
|
|
66
|
-
|
|
67
|
-
|
|
56
|
+
```bash
|
|
57
|
+
export OPENAI_BASE_URL="http://127.0.0.1:4319/aisubs/grok/personal/v1"
|
|
58
|
+
export OPENAI_API_KEY="aisubs_..."
|
|
59
|
+
```
|
|
68
60
|
|
|
69
|
-
|
|
61
|
+
AISubs removes its local key before forwarding a request and adds only the
|
|
62
|
+
selected account's provider credential.
|
|
70
63
|
|
|
71
|
-
|
|
72
|
-
key.
|
|
64
|
+
### Compatibility contract
|
|
73
65
|
|
|
74
|
-
|
|
66
|
+
AISubs preserves the provider's native request and response protocol. It also
|
|
67
|
+
provides a focused, non-streaming text Chat Completions adapter for ChatGPT
|
|
68
|
+
accounts so apps such as [Handy](https://github.com/cjpais/Handy) can use them.
|
|
75
69
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
70
|
+
| Provider/model protocol | Base URL | Supported request path |
|
|
71
|
+
| ---------------------------------- | --------------------------- | --------------------------------------- |
|
|
72
|
+
| OpenAI Responses | Account URL ending in `/v1` | `POST /responses` |
|
|
73
|
+
| OpenAI-compatible Chat Completions | Account URL ending in `/v1` | `POST /chat/completions` |
|
|
74
|
+
| Anthropic Messages | Account URL ending in `/v1` | `POST /messages` |
|
|
75
|
+
| Google generateContent | Account URL ending in `/v1` | `POST /models/MODEL_ID:generateContent` |
|
|
76
|
+
| Model discovery | Account URL ending in `/v1` | `GET /models` |
|
|
79
77
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
By default, credentials are stored at `~/.aisubs/credentials.json`.
|
|
78
|
+
Embeddings are intentionally not exposed. The ChatGPT compatibility adapter
|
|
79
|
+
supports text messages and JSON-schema response formats; tools, images, audio,
|
|
80
|
+
and streaming Chat Completions remain native-Responses-only features.
|
|
86
81
|
|
|
87
|
-
|
|
82
|
+
### Handy
|
|
88
83
|
|
|
89
|
-
|
|
84
|
+
Choose **Custom** in Handy and copy these values from the ChatGPT account page:
|
|
90
85
|
|
|
91
|
-
```
|
|
92
|
-
|
|
86
|
+
```text
|
|
87
|
+
Base URL: http://127.0.0.1:4319/aisubs/chatgpt/ACCOUNT/v1
|
|
88
|
+
API key: the persistent key shown on the AISubs dashboard
|
|
89
|
+
Model: an exact model ID shown for the account
|
|
90
|
+
```
|
|
93
91
|
|
|
94
|
-
|
|
92
|
+
Handy sends `POST /chat/completions` with streaming disabled. AISubs translates
|
|
93
|
+
that request to ChatGPT Responses and returns the `choices[0].message.content`
|
|
94
|
+
shape Handy reads.
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
const login = await account.signIn();
|
|
96
|
+
## SDK examples
|
|
98
97
|
|
|
99
|
-
|
|
100
|
-
console.log("Open:", login.prompt.authorizationUri);
|
|
101
|
-
}
|
|
102
|
-
if (login.prompt.mode === "device") {
|
|
103
|
-
console.log("Open:", login.prompt.verificationUri);
|
|
104
|
-
console.log("Code:", login.prompt.userCode);
|
|
105
|
-
}
|
|
98
|
+
Set the key once for the shell running your client:
|
|
106
99
|
|
|
107
|
-
|
|
108
|
-
|
|
100
|
+
```bash
|
|
101
|
+
export AISUBS_API_KEY="aisubs_..."
|
|
109
102
|
```
|
|
110
103
|
|
|
111
|
-
|
|
104
|
+
<details>
|
|
105
|
+
<summary><strong>Official OpenAI JavaScript SDK — Responses and Chat Completions</strong></summary>
|
|
112
106
|
|
|
113
|
-
|
|
114
|
-
|
|
107
|
+
Install:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
nub install openai
|
|
111
|
+
npm install openai
|
|
112
|
+
pnpm add openai
|
|
113
|
+
bun add openai
|
|
114
|
+
```
|
|
115
115
|
|
|
116
116
|
```js
|
|
117
|
-
|
|
118
|
-
if (!catalog) throw new Error("This provider does not expose models");
|
|
119
|
-
console.log(
|
|
120
|
-
"Available model IDs:",
|
|
121
|
-
catalog.models.map((item) => item.id),
|
|
122
|
-
);
|
|
117
|
+
import OpenAI from "openai";
|
|
123
118
|
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
119
|
+
const client = new OpenAI({
|
|
120
|
+
baseURL: "http://127.0.0.1:4319/aisubs/chatgpt/personal/v1",
|
|
121
|
+
apiKey: process.env.AISUBS_API_KEY,
|
|
122
|
+
});
|
|
127
123
|
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
store: false,
|
|
134
|
-
stream: true,
|
|
135
|
-
input: "Hello from AISubs",
|
|
136
|
-
}),
|
|
124
|
+
const stream = await client.responses.create({
|
|
125
|
+
model: "MODEL_ID_FROM_DASHBOARD",
|
|
126
|
+
input: "Hello from AISubs",
|
|
127
|
+
store: false,
|
|
128
|
+
stream: true,
|
|
137
129
|
});
|
|
138
130
|
|
|
139
|
-
|
|
140
|
-
if (response.body) {
|
|
141
|
-
for await (const chunk of response.body) process.stdout.write(Buffer.from(chunk));
|
|
142
|
-
}
|
|
131
|
+
for await (const event of stream) console.log(event);
|
|
143
132
|
```
|
|
144
133
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
instead of copying a fixed ID from documentation.
|
|
134
|
+
Chat Completions uses the same client with an account/model that reports that
|
|
135
|
+
endpoint:
|
|
148
136
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
137
|
+
```js
|
|
138
|
+
const response = await client.chat.completions.create({
|
|
139
|
+
model: "MODEL_ID_FROM_DASHBOARD",
|
|
140
|
+
messages: [{ role: "user", content: "Hello from AISubs" }],
|
|
141
|
+
});
|
|
153
142
|
|
|
154
|
-
|
|
155
|
-
|
|
143
|
+
console.log(response.choices[0]?.message.content);
|
|
144
|
+
```
|
|
156
145
|
|
|
157
|
-
|
|
158
|
-
<summary><strong>Use another provider</strong></summary>
|
|
146
|
+
</details>
|
|
159
147
|
|
|
160
|
-
|
|
148
|
+
<details>
|
|
149
|
+
<summary><strong>Official Anthropic JavaScript SDK</strong></summary>
|
|
161
150
|
|
|
162
|
-
|
|
163
|
-
import {
|
|
164
|
-
chatGptProvider,
|
|
165
|
-
claudeProvider,
|
|
166
|
-
copilotProvider,
|
|
167
|
-
createSubscriptionAuth,
|
|
168
|
-
grokProvider,
|
|
169
|
-
openCodeGoProvider,
|
|
170
|
-
openCodeZenProvider,
|
|
171
|
-
} from "aisubs";
|
|
151
|
+
Install:
|
|
172
152
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
grokProvider(),
|
|
179
|
-
openCodeGoProvider(),
|
|
180
|
-
openCodeZenProvider(),
|
|
181
|
-
],
|
|
182
|
-
});
|
|
153
|
+
```bash
|
|
154
|
+
nub install @anthropic-ai/sdk
|
|
155
|
+
npm install @anthropic-ai/sdk
|
|
156
|
+
pnpm add @anthropic-ai/sdk
|
|
157
|
+
bun add @anthropic-ai/sdk
|
|
183
158
|
```
|
|
184
159
|
|
|
185
|
-
To use a different file, pass a custom store:
|
|
186
|
-
|
|
187
160
|
```js
|
|
188
|
-
import
|
|
161
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
189
162
|
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
163
|
+
const client = new Anthropic({
|
|
164
|
+
baseURL: "http://127.0.0.1:4319/aisubs/claude/team",
|
|
165
|
+
apiKey: process.env.AISUBS_API_KEY,
|
|
193
166
|
});
|
|
194
|
-
```
|
|
195
167
|
|
|
196
|
-
|
|
168
|
+
const message = await client.messages.create({
|
|
169
|
+
model: "MODEL_ID_FROM_DASHBOARD",
|
|
170
|
+
max_tokens: 1024,
|
|
171
|
+
messages: [{ role: "user", content: "Hello from AISubs" }],
|
|
172
|
+
});
|
|
197
173
|
|
|
198
|
-
|
|
199
|
-
const claude = subscriptions.account("claude", "team");
|
|
200
|
-
const copilot = subscriptions.account("copilot", "github");
|
|
201
|
-
const grok = subscriptions.account("grok", "personal");
|
|
202
|
-
const go = subscriptions.account("opencode-go", "team");
|
|
203
|
-
const zen = subscriptions.account("opencode-zen", "lab");
|
|
174
|
+
console.log(message.content);
|
|
204
175
|
```
|
|
205
176
|
|
|
206
|
-
|
|
177
|
+
</details>
|
|
207
178
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
179
|
+
<details>
|
|
180
|
+
<summary><strong>Vercel AI SDK</strong></summary>
|
|
181
|
+
|
|
182
|
+
For a Responses model:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
nub install ai @ai-sdk/openai
|
|
186
|
+
npm install ai @ai-sdk/openai
|
|
187
|
+
pnpm add ai @ai-sdk/openai
|
|
188
|
+
bun add ai @ai-sdk/openai
|
|
211
189
|
```
|
|
212
190
|
|
|
213
|
-
|
|
214
|
-
|
|
191
|
+
```js
|
|
192
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
193
|
+
import { streamText } from "ai";
|
|
194
|
+
|
|
195
|
+
const aisubs = createOpenAI({
|
|
196
|
+
baseURL: "http://127.0.0.1:4319/aisubs/chatgpt/personal/v1",
|
|
197
|
+
apiKey: process.env.AISUBS_API_KEY,
|
|
198
|
+
});
|
|
215
199
|
|
|
216
|
-
|
|
200
|
+
const result = streamText({
|
|
201
|
+
model: aisubs.responses("MODEL_ID_FROM_DASHBOARD"),
|
|
202
|
+
prompt: "Hello from AISubs",
|
|
203
|
+
providerOptions: { openai: { store: false } },
|
|
204
|
+
});
|
|
217
205
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
| `responses` | `responses` |
|
|
221
|
-
| `chat/completions` | `chat/completions` |
|
|
222
|
-
| `messages` | `messages` |
|
|
223
|
-
| `models/MODEL_ID` | `models/MODEL_ID:generateContent` |
|
|
206
|
+
for await (const text of result.textStream) process.stdout.write(text);
|
|
207
|
+
```
|
|
224
208
|
|
|
225
|
-
|
|
226
|
-
|
|
209
|
+
For Chat Completions, install `@ai-sdk/openai-compatible`, create the provider
|
|
210
|
+
with the same account base URL, and select the model with `provider("MODEL_ID")`.
|
|
227
211
|
|
|
228
212
|
</details>
|
|
229
213
|
|
|
230
214
|
<details>
|
|
231
|
-
<summary><strong>
|
|
215
|
+
<summary><strong>TanStack AI — Chat Completions</strong></summary>
|
|
232
216
|
|
|
233
|
-
|
|
234
|
-
|
|
217
|
+
TanStack's generic compatibility adapter targets Chat Completions. Use it only
|
|
218
|
+
for a model that lists `chat/completions`.
|
|
235
219
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
220
|
+
```bash
|
|
221
|
+
nub install @tanstack/ai @tanstack/ai-openai
|
|
222
|
+
npm install @tanstack/ai @tanstack/ai-openai
|
|
223
|
+
pnpm add @tanstack/ai @tanstack/ai-openai
|
|
224
|
+
bun add @tanstack/ai @tanstack/ai-openai
|
|
240
225
|
```
|
|
241
226
|
|
|
242
|
-
|
|
227
|
+
```ts
|
|
228
|
+
import { chat } from "@tanstack/ai";
|
|
229
|
+
import { openaiCompatible } from "@tanstack/ai-openai/compatible";
|
|
230
|
+
|
|
231
|
+
const aisubs = openaiCompatible({
|
|
232
|
+
name: "aisubs",
|
|
233
|
+
baseURL: "http://127.0.0.1:4319/aisubs/grok/personal/v1",
|
|
234
|
+
apiKey: process.env.AISUBS_API_KEY!,
|
|
235
|
+
models: ["MODEL_ID_FROM_DASHBOARD"],
|
|
236
|
+
});
|
|
243
237
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
| `account.signOut()` | Remove this account's locally stored credentials |
|
|
249
|
-
| `account.getModels()` | Get the provider's current model catalog |
|
|
250
|
-
| `account.getUsage()` | Get current plan usage, if supported |
|
|
251
|
-
| `account.details()` | Get safe session, credential, usage, and model data |
|
|
252
|
-
| `account.fetch(url, init?)` | Make an authorized request to an allowed provider URL |
|
|
253
|
-
| `account.proxy(path, init?)` | Make a provider-native request without handling tokens |
|
|
238
|
+
const stream = chat({
|
|
239
|
+
adapter: aisubs("MODEL_ID_FROM_DASHBOARD"),
|
|
240
|
+
messages: [{ role: "user", content: "Hello from AISubs" }],
|
|
241
|
+
});
|
|
254
242
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
result secret and prefer `fetch()` or `proxy()` when possible.
|
|
243
|
+
for await (const event of stream) console.log(event);
|
|
244
|
+
```
|
|
258
245
|
|
|
259
246
|
</details>
|
|
260
247
|
|
|
261
248
|
<details>
|
|
262
|
-
<summary><strong>
|
|
263
|
-
|
|
264
|
-
```js
|
|
265
|
-
const personal = subscriptions.account("chatgpt", "personal");
|
|
266
|
-
const work = subscriptions.account("chatgpt", "work");
|
|
249
|
+
<summary><strong>Python OpenAI SDK</strong></summary>
|
|
267
250
|
|
|
268
|
-
|
|
269
|
-
|
|
251
|
+
```bash
|
|
252
|
+
python -m pip install openai
|
|
270
253
|
```
|
|
271
254
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
</details>
|
|
255
|
+
```python
|
|
256
|
+
import os
|
|
257
|
+
from openai import OpenAI
|
|
276
258
|
|
|
277
|
-
|
|
259
|
+
client = OpenAI(
|
|
260
|
+
base_url="http://127.0.0.1:4319/aisubs/grok/personal/v1",
|
|
261
|
+
api_key=os.environ["AISUBS_API_KEY"],
|
|
262
|
+
)
|
|
278
263
|
|
|
279
|
-
|
|
264
|
+
response = client.chat.completions.create(
|
|
265
|
+
model="MODEL_ID_FROM_DASHBOARD",
|
|
266
|
+
messages=[{"role": "user", "content": "Hello from AISubs"}],
|
|
267
|
+
)
|
|
268
|
+
print(response.choices[0].message.content)
|
|
269
|
+
```
|
|
280
270
|
|
|
281
|
-
|
|
282
|
-
- A terminal and a browser for provider sign-in.
|
|
283
|
-
- [Nub 0.6 or newer](https://nubjs.com/docs/install) is recommended. It is not
|
|
284
|
-
required; [pnpm](https://pnpm.io/installation),
|
|
285
|
-
[npm](https://docs.npmjs.com/cli/install/), and
|
|
286
|
-
[Bun](https://bun.sh/docs/installation) also work.
|
|
287
|
-
- An API key for OpenCode Go or OpenCode Zen; the other providers use browser
|
|
288
|
-
or device-code sign-in.
|
|
271
|
+
</details>
|
|
289
272
|
|
|
290
|
-
|
|
273
|
+
<details>
|
|
274
|
+
<summary><strong>cURL — Responses, Chat Completions, Anthropic, and Google</strong></summary>
|
|
291
275
|
|
|
292
276
|
```bash
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
277
|
+
curl "http://127.0.0.1:4319/aisubs/chatgpt/personal/v1/responses" \
|
|
278
|
+
-H "Authorization: Bearer $AISUBS_API_KEY" \
|
|
279
|
+
-H "Content-Type: application/json" \
|
|
280
|
+
-d '{"model":"MODEL_ID_FROM_DASHBOARD","store":false,"input":"Hello"}'
|
|
297
281
|
```
|
|
298
282
|
|
|
299
|
-
|
|
283
|
+
Chat Completions:
|
|
300
284
|
|
|
301
285
|
```bash
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
286
|
+
curl "http://127.0.0.1:4319/aisubs/grok/personal/v1/chat/completions" \
|
|
287
|
+
-H "Authorization: Bearer $AISUBS_API_KEY" \
|
|
288
|
+
-H "Content-Type: application/json" \
|
|
289
|
+
-d '{"model":"MODEL_ID_FROM_DASHBOARD","messages":[{"role":"user","content":"Hello from AISubs"}]}'
|
|
306
290
|
```
|
|
307
291
|
|
|
308
|
-
|
|
309
|
-
local account name. The dashboard shows safe account details, usage, models,
|
|
310
|
-
and copy-ready integration examples.
|
|
311
|
-
|
|
312
|
-
By default, AISubs uses Node.js 24 or newer, listens on
|
|
313
|
-
`127.0.0.1:4319`, and stores credentials at `~/.aisubs/credentials.json`.
|
|
314
|
-
|
|
315
|
-
To use another directory, choose an available port, or prevent the browser
|
|
316
|
-
from opening:
|
|
292
|
+
Anthropic Messages:
|
|
317
293
|
|
|
318
294
|
```bash
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
295
|
+
curl "http://127.0.0.1:4319/aisubs/claude/team/v1/messages" \
|
|
296
|
+
-H "x-api-key: $AISUBS_API_KEY" \
|
|
297
|
+
-H "anthropic-version: 2023-06-01" \
|
|
298
|
+
-H "Content-Type: application/json" \
|
|
299
|
+
-d '{"model":"MODEL_ID_FROM_DASHBOARD","max_tokens":1024,"messages":[{"role":"user","content":"Hello from AISubs"}]}'
|
|
323
300
|
```
|
|
324
301
|
|
|
325
|
-
|
|
326
|
-
or Bun.
|
|
327
|
-
|
|
328
|
-
The dashboard prints the secure link when `--no-open` is used. Credentials stay
|
|
329
|
-
on your computer; do not commit the data directory or expose the dashboard to
|
|
330
|
-
the network.
|
|
331
|
-
|
|
332
|
-
Use `AISUBS_DATA_DIR` or `--data-dir` to choose another data directory. Use
|
|
333
|
-
`--port 0` for an available port and `--no-open` to print the secure link
|
|
334
|
-
without opening a browser.
|
|
335
|
-
|
|
336
|
-
## Local HTTP bridge
|
|
337
|
-
|
|
338
|
-
Use the bridge for an existing SDK, Python, cURL, or another program that
|
|
339
|
-
cannot import AISubs:
|
|
302
|
+
Google `generateContent`:
|
|
340
303
|
|
|
341
304
|
```bash
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
export AISUBS_API_KEY="the-control-key-printed-by-aisubs"
|
|
305
|
+
curl "http://127.0.0.1:4319/aisubs/opencode-zen/lab/v1/models/MODEL_ID_FROM_DASHBOARD:generateContent" \
|
|
306
|
+
-H "Authorization: Bearer $AISUBS_API_KEY" \
|
|
307
|
+
-H "Content-Type: application/json" \
|
|
308
|
+
-d '{"contents":[{"role":"user","parts":[{"text":"Hello from AISubs"}]}]}'
|
|
347
309
|
```
|
|
348
310
|
|
|
349
|
-
|
|
350
|
-
path shown by `getModels()` or the dashboard:
|
|
351
|
-
|
|
352
|
-
```text
|
|
353
|
-
http://127.0.0.1:4319/aisubs/chatgpt/personal/responses
|
|
354
|
-
http://127.0.0.1:4319/aisubs/claude/team/messages
|
|
355
|
-
http://127.0.0.1:4319/aisubs/grok/personal/chat/completions
|
|
356
|
-
```
|
|
311
|
+
</details>
|
|
357
312
|
|
|
358
|
-
|
|
359
|
-
spaces or other URL characters. AISubs removes the control key before sending
|
|
360
|
-
the request to a provider.
|
|
313
|
+
## Direct AISubs SDK
|
|
361
314
|
|
|
362
|
-
|
|
363
|
-
|
|
315
|
+
Use the in-process SDK when AISubs is part of your trusted Node.js backend. It
|
|
316
|
+
needs no local server or AISubs API key.
|
|
364
317
|
|
|
365
|
-
|
|
318
|
+
Install the package in a project:
|
|
366
319
|
|
|
367
320
|
```bash
|
|
368
|
-
nub install
|
|
321
|
+
nub install aisubs
|
|
322
|
+
npm install aisubs
|
|
323
|
+
pnpm add aisubs
|
|
324
|
+
bun add aisubs
|
|
369
325
|
```
|
|
370
326
|
|
|
371
327
|
```js
|
|
372
|
-
import {
|
|
373
|
-
import { streamText } from "ai";
|
|
328
|
+
import { chatGptProvider, createSubscriptionAuth } from "aisubs";
|
|
374
329
|
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
apiKey: process.env.AISUBS_API_KEY,
|
|
378
|
-
});
|
|
330
|
+
const subscriptions = createSubscriptionAuth({ providers: [chatGptProvider()] });
|
|
331
|
+
const account = subscriptions.account("chatgpt", "personal");
|
|
379
332
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
prompt
|
|
383
|
-
|
|
333
|
+
if (!(await account.status()).authenticated) {
|
|
334
|
+
const login = await account.signIn();
|
|
335
|
+
console.log(login.prompt);
|
|
336
|
+
await login.wait();
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const catalog = await account.getModels();
|
|
340
|
+
const model = catalog?.models.find((item) => item.selectable !== false)?.id;
|
|
341
|
+
if (!model) throw new Error("No selectable model is available");
|
|
342
|
+
|
|
343
|
+
const response = await account.proxy("responses", {
|
|
344
|
+
method: "POST",
|
|
345
|
+
headers: { "content-type": "application/json" },
|
|
346
|
+
body: JSON.stringify({ model, input: "Hello", store: false, stream: true }),
|
|
384
347
|
});
|
|
385
348
|
|
|
386
|
-
|
|
349
|
+
if (!response.ok) throw new Error(await response.text());
|
|
350
|
+
for await (const chunk of response.body ?? []) process.stdout.write(Buffer.from(chunk));
|
|
387
351
|
```
|
|
388
352
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
`
|
|
353
|
+
Available provider factories are `chatGptProvider()`, `claudeProvider()`,
|
|
354
|
+
`copilotProvider()`, `grokProvider()`, `openCodeGoProvider()`, and
|
|
355
|
+
`openCodeZenProvider()`.
|
|
392
356
|
|
|
393
|
-
|
|
357
|
+
Useful account methods:
|
|
358
|
+
|
|
359
|
+
| Method | Purpose |
|
|
360
|
+
| -------------------- | ------------------------------------------------------ |
|
|
361
|
+
| `status()` | Check whether the account is connected |
|
|
362
|
+
| `signIn(options?)` | Start browser, device-code, or API-key sign-in |
|
|
363
|
+
| `signOut()` | Remove the account's locally stored credential |
|
|
364
|
+
| `getModels()` | Read the current provider model catalog |
|
|
365
|
+
| `getUsage()` | Read current provider usage when available |
|
|
366
|
+
| `details()` | Read safe identity, credential, usage, and model data |
|
|
367
|
+
| `fetch(url, init?)` | Send an authorized request to an allowed provider host |
|
|
368
|
+
| `proxy(path, init?)` | Send a provider-native request without handling tokens |
|
|
394
369
|
|
|
395
370
|
<details>
|
|
396
|
-
<summary><strong>
|
|
371
|
+
<summary><strong>Configure every provider and custom credential storage</strong></summary>
|
|
397
372
|
|
|
398
|
-
|
|
373
|
+
```js
|
|
374
|
+
import {
|
|
375
|
+
FileCredentialStore,
|
|
376
|
+
chatGptProvider,
|
|
377
|
+
claudeProvider,
|
|
378
|
+
copilotProvider,
|
|
379
|
+
createSubscriptionAuth,
|
|
380
|
+
grokProvider,
|
|
381
|
+
openCodeGoProvider,
|
|
382
|
+
openCodeZenProvider,
|
|
383
|
+
} from "aisubs";
|
|
399
384
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
385
|
+
const subscriptions = createSubscriptionAuth({
|
|
386
|
+
store: new FileCredentialStore("./data/aisubs-credentials.json"),
|
|
387
|
+
providers: [
|
|
388
|
+
chatGptProvider(),
|
|
389
|
+
claudeProvider(),
|
|
390
|
+
copilotProvider(),
|
|
391
|
+
grokProvider(),
|
|
392
|
+
openCodeGoProvider(),
|
|
393
|
+
openCodeZenProvider(),
|
|
394
|
+
],
|
|
395
|
+
});
|
|
405
396
|
```
|
|
406
397
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
const stream = chat({
|
|
412
|
-
adapter: openaiCompatibleText("MODEL_ID", {
|
|
413
|
-
baseURL: "http://127.0.0.1:4319/aisubs/grok/personal",
|
|
414
|
-
apiKey: process.env.AISUBS_API_KEY!,
|
|
415
|
-
}),
|
|
416
|
-
messages: [{ role: "user", content: "Hello" }],
|
|
417
|
-
});
|
|
398
|
+
Without a custom store, credentials are saved to
|
|
399
|
+
`~/.aisubs/credentials.json`. Select an account with its provider ID and a
|
|
400
|
+
local account name:
|
|
418
401
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
402
|
+
```js
|
|
403
|
+
const chatgpt = subscriptions.account("chatgpt", "personal");
|
|
404
|
+
const claude = subscriptions.account("claude", "team");
|
|
405
|
+
const copilot = subscriptions.account("copilot", "github");
|
|
406
|
+
const grok = subscriptions.account("grok", "personal");
|
|
407
|
+
const go = subscriptions.account("opencode-go", "team");
|
|
408
|
+
const zen = subscriptions.account("opencode-zen", "lab");
|
|
422
409
|
```
|
|
423
410
|
|
|
424
|
-
|
|
425
|
-
another package manager. Replace the provider, account, and model with values
|
|
426
|
-
from your dashboard.
|
|
411
|
+
Account names are 1–128 characters and cannot contain control characters.
|
|
427
412
|
|
|
428
413
|
</details>
|
|
429
414
|
|
|
430
415
|
<details>
|
|
431
|
-
<summary><strong>
|
|
416
|
+
<summary><strong>Browser, device-code, and API-key sign-in</strong></summary>
|
|
432
417
|
|
|
433
|
-
|
|
418
|
+
The default ChatGPT flow opens a browser. On a headless machine, request its
|
|
419
|
+
device-code flow explicitly:
|
|
434
420
|
|
|
435
|
-
```
|
|
436
|
-
|
|
421
|
+
```js
|
|
422
|
+
const login = await chatgpt.signIn({ mode: "device" });
|
|
423
|
+
console.log(login.prompt);
|
|
424
|
+
await login.wait();
|
|
437
425
|
```
|
|
438
426
|
|
|
439
|
-
|
|
440
|
-
|
|
427
|
+
Copilot uses device-code sign-in and can target a supported GitHub Enterprise
|
|
428
|
+
Cloud domain:
|
|
441
429
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
430
|
+
```js
|
|
431
|
+
const login = await copilot.signIn({ enterpriseDomain: "company.ghe.com" });
|
|
432
|
+
console.log(login.prompt);
|
|
433
|
+
await login.wait();
|
|
434
|
+
```
|
|
446
435
|
|
|
447
|
-
|
|
448
|
-
model: "MODEL_ID",
|
|
449
|
-
store: false,
|
|
450
|
-
stream: true,
|
|
451
|
-
input: "Hello",
|
|
452
|
-
});
|
|
436
|
+
OpenCode Go and Zen use API keys:
|
|
453
437
|
|
|
454
|
-
|
|
438
|
+
```js
|
|
439
|
+
const login = await go.signIn({ apiKey: process.env.OPENCODE_API_KEY });
|
|
440
|
+
await login.wait();
|
|
455
441
|
```
|
|
456
442
|
|
|
457
|
-
|
|
443
|
+
</details>
|
|
458
444
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
```
|
|
445
|
+
<details>
|
|
446
|
+
<summary><strong>Inspect account details and switch between accounts</strong></summary>
|
|
462
447
|
|
|
463
|
-
|
|
448
|
+
```js
|
|
449
|
+
const details = await chatgpt.details();
|
|
464
450
|
|
|
465
|
-
|
|
466
|
-
|
|
451
|
+
console.log(details.session); // connection state and safe account identity
|
|
452
|
+
console.log(details.credential); // expiry and refresh state, never token values
|
|
453
|
+
console.log(details.usage); // limits and reset information, or null
|
|
454
|
+
console.log(details.models); // available models, or null
|
|
467
455
|
```
|
|
468
456
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
from litellm import completion
|
|
472
|
-
|
|
473
|
-
response = completion(
|
|
474
|
-
model="openai/MODEL_ID",
|
|
475
|
-
api_base="http://127.0.0.1:4319/aisubs/grok/personal",
|
|
476
|
-
api_key=os.environ["AISUBS_API_KEY"],
|
|
477
|
-
messages=[{"role": "user", "content": "Hello"}],
|
|
478
|
-
stream=True,
|
|
479
|
-
)
|
|
457
|
+
`details()`, `getUsage()`, and `getModels()` never return access or refresh
|
|
458
|
+
tokens. Prefer `fetch()` or `proxy()` over handling a token directly.
|
|
480
459
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
```
|
|
460
|
+
Each named account keeps separate credentials, refresh state, usage, and model
|
|
461
|
+
data, so selection can happen at request time:
|
|
484
462
|
|
|
485
|
-
|
|
463
|
+
```js
|
|
464
|
+
const personal = subscriptions.account("chatgpt", "personal");
|
|
465
|
+
const work = subscriptions.account("chatgpt", "work");
|
|
486
466
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
-H "Authorization: Bearer $AISUBS_API_KEY" \
|
|
490
|
-
-H "Content-Type: application/json" \
|
|
491
|
-
-d '{"model":"MODEL_ID","store":false,"stream":true,"input":"Hello"}'
|
|
467
|
+
const selected = user.isWorkAccount ? work : personal;
|
|
468
|
+
const response = await selected.proxy("responses", requestOptions);
|
|
492
469
|
```
|
|
493
470
|
|
|
494
|
-
For Messages, use `x-api-key: $AISUBS_API_KEY` and
|
|
495
|
-
`anthropic-version: 2023-06-01`.
|
|
496
|
-
|
|
497
471
|
</details>
|
|
498
472
|
|
|
499
473
|
<details>
|
|
500
|
-
<summary><strong>Run
|
|
474
|
+
<summary><strong>Run the local HTTP server from Node.js</strong></summary>
|
|
501
475
|
|
|
502
|
-
|
|
476
|
+
This is the programmatic equivalent of `aisubs dashboard`. The API key is
|
|
477
|
+
created once and reused across restarts; delete or regenerate the key file only
|
|
478
|
+
when clients should receive a new key.
|
|
503
479
|
|
|
504
480
|
```js
|
|
505
|
-
import {
|
|
506
|
-
import {
|
|
481
|
+
import { homedir } from "node:os";
|
|
482
|
+
import { join } from "node:path";
|
|
483
|
+
import {
|
|
484
|
+
FileApiKeyStore,
|
|
485
|
+
FileCredentialStore,
|
|
486
|
+
chatGptProvider,
|
|
487
|
+
claudeProvider,
|
|
488
|
+
createSubscriptionAuth,
|
|
489
|
+
} from "aisubs";
|
|
507
490
|
import { createSubscriptionAuthServer } from "aisubs/http";
|
|
508
491
|
|
|
492
|
+
const directory = join(homedir(), ".aisubs");
|
|
493
|
+
const apiKey = await new FileApiKeyStore(join(directory, "api-key")).readOrCreate();
|
|
509
494
|
const auth = createSubscriptionAuth({
|
|
510
|
-
store: new FileCredentialStore("
|
|
511
|
-
providers: [chatGptProvider()],
|
|
495
|
+
store: new FileCredentialStore(join(directory, "credentials.json")),
|
|
496
|
+
providers: [chatGptProvider(), claudeProvider()],
|
|
512
497
|
});
|
|
513
498
|
|
|
514
|
-
const server = await createSubscriptionAuthServer({
|
|
515
|
-
|
|
516
|
-
apiKey: randomBytes(24).toString("hex"),
|
|
517
|
-
port: 4319,
|
|
518
|
-
});
|
|
519
|
-
|
|
520
|
-
console.log(server.url);
|
|
521
|
-
// await server.close();
|
|
499
|
+
const server = await createSubscriptionAuthServer({ auth, apiKey, port: 4319 });
|
|
500
|
+
console.log(`AISubs API: ${server.url}`);
|
|
522
501
|
```
|
|
523
502
|
|
|
524
|
-
|
|
503
|
+
A runnable version is available in [`examples/server.mjs`](./examples/server.mjs).
|
|
525
504
|
|
|
526
|
-
|
|
527
|
-
|
|
505
|
+
</details>
|
|
506
|
+
|
|
507
|
+
## Dashboard options
|
|
528
508
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
509
|
+
```text
|
|
510
|
+
aisubs dashboard [options]
|
|
511
|
+
|
|
512
|
+
--data-dir <path> State directory (default: ~/.aisubs)
|
|
513
|
+
--port <number> Local port (default: 4319; 0 chooses an available port)
|
|
514
|
+
--no-open Do not open the browser
|
|
515
|
+
--help Show help
|
|
532
516
|
```
|
|
533
517
|
|
|
534
|
-
|
|
535
|
-
|
|
518
|
+
`AISUBS_DATA_DIR` also changes the state directory. The built-in server binds
|
|
519
|
+
only to localhost.
|
|
536
520
|
|
|
537
|
-
|
|
521
|
+
## Local management API
|
|
538
522
|
|
|
539
|
-
|
|
540
|
-
<summary><strong>Local HTTP API</strong></summary>
|
|
523
|
+
These routes are for the dashboard and advanced integrations:
|
|
541
524
|
|
|
542
525
|
```text
|
|
543
526
|
GET /health
|
|
@@ -546,84 +529,57 @@ GET /v1/auth
|
|
|
546
529
|
GET /v1/auth/:provider
|
|
547
530
|
GET /v1/auth/:provider/accounts
|
|
548
531
|
POST /v1/auth/:provider/login
|
|
532
|
+
DELETE /v1/auth/:provider?account=work
|
|
549
533
|
GET /v1/logins/:loginId
|
|
550
534
|
DELETE /v1/logins/:loginId
|
|
551
|
-
GET /v1/auth/:provider/details?account=work
|
|
552
|
-
DELETE /v1/auth/:provider?account=work
|
|
553
|
-
POST /v1/fetch/:provider
|
|
554
535
|
GET /v1/usage/:provider?account=work
|
|
555
536
|
GET /v1/models/:provider?account=work
|
|
556
|
-
|
|
537
|
+
GET /v1/api-key # dashboard session only
|
|
538
|
+
POST /v1/api-key/regenerate # dashboard session only
|
|
539
|
+
* /aisubs/:provider/:account/v1/*
|
|
557
540
|
```
|
|
558
541
|
|
|
559
|
-
The
|
|
560
|
-
|
|
561
|
-
|
|
542
|
+
The provider and account routes require `Authorization: Bearer AISUBS_API_KEY`
|
|
543
|
+
or `x-api-key: AISUBS_API_KEY`. Regenerating the key immediately invalidates the
|
|
544
|
+
old key.
|
|
562
545
|
|
|
563
|
-
|
|
546
|
+
## Storage and security
|
|
564
547
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
-
|
|
569
|
-
-
|
|
570
|
-
-
|
|
571
|
-
-
|
|
572
|
-
- Usage is cached for 15 seconds; model catalogs are cached for five minutes.
|
|
573
|
-
- Sign-in, refresh, and sign-out clear the affected metadata cache.
|
|
574
|
-
- Provider credentials are added only after host allowlist validation.
|
|
575
|
-
- Local auth and control-key headers are removed before forwarding.
|
|
576
|
-
- Account APIs return safe summaries, never token values.
|
|
577
|
-
- Never expose provider credentials or `AISUBS_API_KEY` in browser code.
|
|
548
|
+
- Credentials: `~/.aisubs/credentials.json`.
|
|
549
|
+
- Persistent local API key: `~/.aisubs/api-key`.
|
|
550
|
+
- State directories and files use private permissions where the platform supports them.
|
|
551
|
+
- Provider credentials are attached only after provider-host allowlist validation.
|
|
552
|
+
- Local authorization, cookie, origin, and proxy headers are never forwarded.
|
|
553
|
+
- Account and model APIs never return provider access or refresh tokens.
|
|
554
|
+
- Keep AISubs on localhost and never put its API key in browser-delivered code.
|
|
578
555
|
|
|
579
|
-
|
|
556
|
+
## Development
|
|
580
557
|
|
|
581
|
-
|
|
582
|
-
<summary><strong>Local development</strong></summary>
|
|
583
|
-
|
|
584
|
-
From the package directory, run:
|
|
558
|
+
From the package directory:
|
|
585
559
|
|
|
586
560
|
```bash
|
|
587
|
-
nub
|
|
561
|
+
nub install
|
|
562
|
+
nub run check
|
|
588
563
|
```
|
|
589
564
|
|
|
590
|
-
|
|
591
|
-
recommended, but it is not required. The command builds the package once,
|
|
592
|
-
watches backend and dashboard changes, and opens the local dashboard. Pass
|
|
593
|
-
`-- --no-open` to keep the browser closed.
|
|
594
|
-
|
|
595
|
-
</details>
|
|
596
|
-
|
|
597
|
-
<details>
|
|
598
|
-
<summary><strong>Maintainer pre-publish check</strong></summary>
|
|
565
|
+
Package-manager equivalents:
|
|
599
566
|
|
|
600
567
|
```bash
|
|
601
|
-
|
|
602
|
-
|
|
568
|
+
npm install && npm run check
|
|
569
|
+
pnpm install && pnpm run check
|
|
570
|
+
bun install && bun run check
|
|
603
571
|
```
|
|
604
572
|
|
|
605
|
-
|
|
606
|
-
`
|
|
607
|
-
|
|
608
|
-
Confirm that the package contains `dist`, `examples`, `public`, `README.md`,
|
|
609
|
-
`LICENSE`, and the README logo asset. Test at least one real account for every
|
|
610
|
-
provider your release claims to support.
|
|
611
|
-
|
|
612
|
-
Runnable examples:
|
|
613
|
-
|
|
614
|
-
- [`examples/direct.mjs`](./examples/direct.mjs)
|
|
615
|
-
- [`examples/server.mjs`](./examples/server.mjs)
|
|
616
|
-
|
|
617
|
-
</details>
|
|
573
|
+
For the watch dashboard use `nub run dev`, `npm run dev`, `pnpm run dev`, or
|
|
574
|
+
`bun run dev`. Before publishing, also inspect the package with `nub pack --dry-run`,
|
|
575
|
+
`npm pack --dry-run`, `pnpm pack --dry-run`, or `bun pm pack --dry-run`.
|
|
618
576
|
|
|
619
|
-
##
|
|
577
|
+
## Provider terms
|
|
620
578
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
version, environment, steps to reproduce, expected and actual behavior, and
|
|
625
|
-
sanitized error output where relevant.
|
|
579
|
+
Provider subscriptions, OAuth clients, and model access are governed by each
|
|
580
|
+
provider's terms and may change. Use accounts you are authorized to use, discover
|
|
581
|
+
models at runtime, and pin the AISubs version your integration has tested.
|
|
626
582
|
|
|
627
583
|
## License
|
|
628
584
|
|
|
629
|
-
|
|
585
|
+
[MIT](./LICENSE)
|