aisubs 0.1.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/LICENSE +21 -0
- package/README.md +629 -0
- package/dashboard/public/aisubs-mark.svg +11 -0
- package/dist/account-key.d.ts +2 -0
- package/dist/account-key.js +11 -0
- package/dist/auth.d.ts +76 -0
- package/dist/auth.js +477 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +97 -0
- package/dist/dashboard/aisubs-mark.svg +11 -0
- package/dist/dashboard/assets/index-BMoILzPw.js +64 -0
- package/dist/dashboard/assets/index-DHqDdNVe.css +2 -0
- package/dist/dashboard/index.html +15 -0
- package/dist/dashboard/logos/anthropic.svg +3 -0
- package/dist/dashboard/logos/github-copilot.svg +3 -0
- package/dist/dashboard/logos/google.svg +3 -0
- package/dist/dashboard/logos/openai.svg +3 -0
- package/dist/dashboard/logos/opencode-dark.svg +18 -0
- package/dist/dashboard/logos/opencode-light.svg +18 -0
- package/dist/dashboard/logos/xai.svg +3 -0
- package/dist/dashboard.d.ts +18 -0
- package/dist/dashboard.js +139 -0
- package/dist/http.d.ts +22 -0
- package/dist/http.js +263 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +8 -0
- package/dist/providers/chatgpt.d.ts +7 -0
- package/dist/providers/chatgpt.js +402 -0
- package/dist/providers/claude.d.ts +7 -0
- package/dist/providers/claude.js +289 -0
- package/dist/providers/copilot.d.ts +6 -0
- package/dist/providers/copilot.js +466 -0
- package/dist/providers/grok.d.ts +7 -0
- package/dist/providers/grok.js +215 -0
- package/dist/providers/opencode.d.ts +4 -0
- package/dist/providers/opencode.js +147 -0
- package/dist/store.d.ts +18 -0
- package/dist/store.js +121 -0
- package/dist/types.d.ts +186 -0
- package/dist/types.js +1 -0
- package/dist/usage.d.ts +5 -0
- package/dist/usage.js +418 -0
- package/dist/utils.d.ts +11 -0
- package/dist/utils.js +68 -0
- package/examples/direct.mjs +38 -0
- package/examples/server.mjs +20 -0
- package/package.json +122 -0
- package/public/aisubs-chatgpt-account.png +0 -0
- package/public/aisubs-copilot-account.png +0 -0
- package/public/aisubs-dashboard.png +0 -0
- package/public/aisubs-grok-account.png +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AISubs contributors
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
<table>
|
|
2
|
+
<tr>
|
|
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>
|
|
8
|
+
</tr>
|
|
9
|
+
</table>
|
|
10
|
+
|
|
11
|
+
Connect once, then use your subscriptions through an SDK in your app, a local
|
|
12
|
+
API, cURL, or any compatible tool.
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
<img src="./public/aisubs-dashboard.png" alt="AISubs dashboard showing connected accounts" width="100%" />
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
<p align="center">
|
|
19
|
+
<img src="./public/aisubs-chatgpt-account.png" alt="AISubs ChatGPT account details showing plan, usage, and integration examples" width="33%" />
|
|
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.
|
|
25
|
+
|
|
26
|
+
## Quick start
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
nubx aisubs dashboard # Nub (recommended)
|
|
30
|
+
npx aisubs dashboard # npm
|
|
31
|
+
pnpm exec aisubs dashboard # pnpm
|
|
32
|
+
bunx aisubs dashboard # Bun
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Click **Add account**, choose a provider, complete sign-in, and give the
|
|
36
|
+
account a local name such as `personal` or `work`.
|
|
37
|
+
|
|
38
|
+
## The idea
|
|
39
|
+
|
|
40
|
+
1. Connect a provider account and give it a local name, such as `personal`.
|
|
41
|
+
2. Ask AISubs which models and request format that account supports.
|
|
42
|
+
3. Send the provider-native request through that account.
|
|
43
|
+
|
|
44
|
+
One provider can have many accounts:
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
ChatGPT / personal
|
|
48
|
+
ChatGPT / work
|
|
49
|
+
Claude / team
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
AISubs never silently switches accounts. Your application chooses the account
|
|
53
|
+
for each request.
|
|
54
|
+
|
|
55
|
+
## Providers
|
|
56
|
+
|
|
57
|
+
| Provider | ID | Sign-in | Request format |
|
|
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 |
|
|
65
|
+
|
|
66
|
+
Provider model lists and protocols can change. Discover models at runtime and
|
|
67
|
+
pin the AISubs version your application has tested.
|
|
68
|
+
|
|
69
|
+
## Fastest start: direct Node.js
|
|
70
|
+
|
|
71
|
+
This is the simplest integration. It needs no local server and no AISubs API
|
|
72
|
+
key.
|
|
73
|
+
|
|
74
|
+
### 1. Create AISubs once
|
|
75
|
+
|
|
76
|
+
```js
|
|
77
|
+
// subscriptions.js
|
|
78
|
+
import { chatGptProvider, createSubscriptionAuth } from "aisubs";
|
|
79
|
+
|
|
80
|
+
export const subscriptions = createSubscriptionAuth({
|
|
81
|
+
providers: [chatGptProvider()],
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
By default, credentials are stored at `~/.aisubs/credentials.json`.
|
|
86
|
+
|
|
87
|
+
Keep this object in trusted backend code. Do not send it to a browser.
|
|
88
|
+
|
|
89
|
+
### 2. Connect an account
|
|
90
|
+
|
|
91
|
+
```js
|
|
92
|
+
import { subscriptions } from "./subscriptions.js";
|
|
93
|
+
|
|
94
|
+
const account = subscriptions.account("chatgpt", "personal");
|
|
95
|
+
|
|
96
|
+
if (!(await account.status()).authenticated) {
|
|
97
|
+
const login = await account.signIn();
|
|
98
|
+
|
|
99
|
+
if (login.prompt.mode === "browser") {
|
|
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
|
+
}
|
|
106
|
+
|
|
107
|
+
await login.wait();
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 3. Discover a model and send a request
|
|
112
|
+
|
|
113
|
+
This example selects the first available ChatGPT model, so it does not depend
|
|
114
|
+
on a model ID that may change:
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
const catalog = await account.getModels();
|
|
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
|
+
);
|
|
123
|
+
|
|
124
|
+
const modelId = catalog.models.find((item) => item.selectable !== false)?.id;
|
|
125
|
+
if (!modelId) throw new Error("No ChatGPT model is available");
|
|
126
|
+
console.log("Using model:", modelId);
|
|
127
|
+
|
|
128
|
+
const response = await account.proxy("responses", {
|
|
129
|
+
method: "POST",
|
|
130
|
+
headers: { "content-type": "application/json" },
|
|
131
|
+
body: JSON.stringify({
|
|
132
|
+
model: modelId,
|
|
133
|
+
store: false,
|
|
134
|
+
stream: true,
|
|
135
|
+
input: "Hello from AISubs",
|
|
136
|
+
}),
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (!response.ok) throw new Error(await response.text());
|
|
140
|
+
if (response.body) {
|
|
141
|
+
for await (const chunk of response.body) process.stdout.write(Buffer.from(chunk));
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Here, `modelId` is the exact ID returned by `account.getModels()`. Model IDs
|
|
146
|
+
belong to the connected provider account and can change, so list them first
|
|
147
|
+
instead of copying a fixed ID from documentation.
|
|
148
|
+
|
|
149
|
+
`store: false` is a request option for the Responses API; it is unrelated to
|
|
150
|
+
AISubs credential storage and keeps response storage disabled. `stream: true`
|
|
151
|
+
asks for incremental output, which is why the example reads `response.body` in
|
|
152
|
+
chunks. Keep both options for ChatGPT subscription Responses requests.
|
|
153
|
+
|
|
154
|
+
AISubs refreshes expired credentials automatically and retries a provider `401`
|
|
155
|
+
once.
|
|
156
|
+
|
|
157
|
+
<details>
|
|
158
|
+
<summary><strong>Use another provider</strong></summary>
|
|
159
|
+
|
|
160
|
+
Add its provider factory when creating AISubs:
|
|
161
|
+
|
|
162
|
+
```js
|
|
163
|
+
import {
|
|
164
|
+
chatGptProvider,
|
|
165
|
+
claudeProvider,
|
|
166
|
+
copilotProvider,
|
|
167
|
+
createSubscriptionAuth,
|
|
168
|
+
grokProvider,
|
|
169
|
+
openCodeGoProvider,
|
|
170
|
+
openCodeZenProvider,
|
|
171
|
+
} from "aisubs";
|
|
172
|
+
|
|
173
|
+
const subscriptions = createSubscriptionAuth({
|
|
174
|
+
providers: [
|
|
175
|
+
chatGptProvider(),
|
|
176
|
+
claudeProvider(),
|
|
177
|
+
copilotProvider(),
|
|
178
|
+
grokProvider(),
|
|
179
|
+
openCodeGoProvider(),
|
|
180
|
+
openCodeZenProvider(),
|
|
181
|
+
],
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
To use a different file, pass a custom store:
|
|
186
|
+
|
|
187
|
+
```js
|
|
188
|
+
import { chatGptProvider, createSubscriptionAuth, FileCredentialStore } from "aisubs";
|
|
189
|
+
|
|
190
|
+
const subscriptions = createSubscriptionAuth({
|
|
191
|
+
store: new FileCredentialStore("./data/aisubs-credentials.json"),
|
|
192
|
+
providers: [chatGptProvider()],
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Then select the account by provider ID:
|
|
197
|
+
|
|
198
|
+
```js
|
|
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");
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
OpenCode uses an API key:
|
|
207
|
+
|
|
208
|
+
```js
|
|
209
|
+
const login = await go.signIn({ apiKey: process.env.OPENCODE_API_KEY });
|
|
210
|
+
await login.wait();
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
For ChatGPT on a headless machine, use `signIn({ mode: "device" })`. Copilot
|
|
214
|
+
also accepts `enterpriseDomain` for a supported GitHub Enterprise Cloud domain.
|
|
215
|
+
|
|
216
|
+
Provider request paths are:
|
|
217
|
+
|
|
218
|
+
| Model catalog endpoint | `account.proxy()` path |
|
|
219
|
+
| ---------------------- | --------------------------------- |
|
|
220
|
+
| `responses` | `responses` |
|
|
221
|
+
| `chat/completions` | `chat/completions` |
|
|
222
|
+
| `messages` | `messages` |
|
|
223
|
+
| `models/MODEL_ID` | `models/MODEL_ID:generateContent` |
|
|
224
|
+
|
|
225
|
+
Use `account.getModels()` first for Copilot, Grok, and OpenCode because one
|
|
226
|
+
provider can expose more than one request format.
|
|
227
|
+
|
|
228
|
+
</details>
|
|
229
|
+
|
|
230
|
+
<details>
|
|
231
|
+
<summary><strong>Read account, usage, and model information</strong></summary>
|
|
232
|
+
|
|
233
|
+
```js
|
|
234
|
+
const details = await account.details();
|
|
235
|
+
|
|
236
|
+
console.log(details.session); // connection state and safe account identity
|
|
237
|
+
console.log(details.credential); // expiry and refresh state, never token values
|
|
238
|
+
console.log(details.usage); // limits and reset information, or null
|
|
239
|
+
console.log(details.models); // available models, or null
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Useful methods:
|
|
243
|
+
|
|
244
|
+
| Method | Purpose |
|
|
245
|
+
| ---------------------------- | ------------------------------------------------------ |
|
|
246
|
+
| `account.status()` | Check whether the account is connected |
|
|
247
|
+
| `account.signIn(options?)` | Start a browser, device-code, or API-key login |
|
|
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 |
|
|
254
|
+
|
|
255
|
+
`details()`, `getUsage()`, and `getModels()` never return access or refresh
|
|
256
|
+
tokens. `getAccessToken()` exists for advanced backend integrations; keep its
|
|
257
|
+
result secret and prefer `fetch()` or `proxy()` when possible.
|
|
258
|
+
|
|
259
|
+
</details>
|
|
260
|
+
|
|
261
|
+
<details>
|
|
262
|
+
<summary><strong>Use multiple accounts</strong></summary>
|
|
263
|
+
|
|
264
|
+
```js
|
|
265
|
+
const personal = subscriptions.account("chatgpt", "personal");
|
|
266
|
+
const work = subscriptions.account("chatgpt", "work");
|
|
267
|
+
|
|
268
|
+
const selected = user.isWorkAccount ? work : personal;
|
|
269
|
+
const response = await selected.proxy("responses", requestOptions);
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Account names are 1–128 characters and cannot contain control characters.
|
|
273
|
+
Each account has separate credentials, refresh state, usage, and model data.
|
|
274
|
+
|
|
275
|
+
</details>
|
|
276
|
+
|
|
277
|
+
## Dashboard
|
|
278
|
+
|
|
279
|
+
### Requirements and installation
|
|
280
|
+
|
|
281
|
+
- [Node.js 24 or newer](https://nodejs.org/en/download/).
|
|
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.
|
|
289
|
+
|
|
290
|
+
Install AISubs in your project:
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
nub install aisubs # Nub (recommended)
|
|
294
|
+
npm install aisubs # npm
|
|
295
|
+
pnpm add aisubs # pnpm
|
|
296
|
+
bun add aisubs # Bun
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Use the dashboard when you want to connect accounts without writing login UI:
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
nubx aisubs dashboard # Nub (recommended)
|
|
303
|
+
npx aisubs dashboard # npm
|
|
304
|
+
pnpm exec aisubs dashboard # pnpm
|
|
305
|
+
bunx aisubs dashboard # Bun
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Then click **Add account**, choose a provider, finish sign-in, and choose a
|
|
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:
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
nubx aisubs dashboard \
|
|
320
|
+
--data-dir ./data/aisubs \
|
|
321
|
+
--port 0 \
|
|
322
|
+
--no-open
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Use the equivalent `npx`, `pnpm exec`, or `bunx` command if you use npm, pnpm,
|
|
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:
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
nubx aisubs dashboard # Nub (recommended)
|
|
343
|
+
npx aisubs dashboard # npm
|
|
344
|
+
pnpm exec aisubs dashboard # pnpm
|
|
345
|
+
bunx aisubs dashboard # Bun
|
|
346
|
+
export AISUBS_API_KEY="the-control-key-printed-by-aisubs"
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
The base URL chooses the provider and account. Append the provider's request
|
|
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
|
+
```
|
|
357
|
+
|
|
358
|
+
The account name is URL-decoded by AISubs, so URL-encode names containing
|
|
359
|
+
spaces or other URL characters. AISubs removes the control key before sending
|
|
360
|
+
the request to a provider.
|
|
361
|
+
|
|
362
|
+
<details>
|
|
363
|
+
<summary><strong>Vercel AI SDK</strong></summary>
|
|
364
|
+
|
|
365
|
+
For a `responses` model:
|
|
366
|
+
|
|
367
|
+
```bash
|
|
368
|
+
nub install ai @ai-sdk/openai
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
```js
|
|
372
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
373
|
+
import { streamText } from "ai";
|
|
374
|
+
|
|
375
|
+
const provider = createOpenAI({
|
|
376
|
+
baseURL: "http://127.0.0.1:4319/aisubs/chatgpt/personal",
|
|
377
|
+
apiKey: process.env.AISUBS_API_KEY,
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
const result = streamText({
|
|
381
|
+
model: provider.responses("MODEL_ID"),
|
|
382
|
+
prompt: "Hello",
|
|
383
|
+
providerOptions: { openai: { store: false } },
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
for await (const text of result.textStream) process.stdout.write(text);
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
For `chat/completions`, use `@ai-sdk/openai-compatible`. For `messages`, use
|
|
390
|
+
`@ai-sdk/anthropic`. For OpenCode Zen Gemini, use the model-specific
|
|
391
|
+
`models/MODEL_ID:generateContent` URL shown in the dashboard.
|
|
392
|
+
|
|
393
|
+
</details>
|
|
394
|
+
|
|
395
|
+
<details>
|
|
396
|
+
<summary><strong>TanStack AI</strong></summary>
|
|
397
|
+
|
|
398
|
+
TanStack AI can use AISubs through its OpenAI-compatible adapter:
|
|
399
|
+
|
|
400
|
+
```bash
|
|
401
|
+
nub install @tanstack/ai @tanstack/ai-openai # Nub (recommended)
|
|
402
|
+
npm install @tanstack/ai @tanstack/ai-openai # npm
|
|
403
|
+
pnpm add @tanstack/ai @tanstack/ai-openai # pnpm
|
|
404
|
+
bun add @tanstack/ai @tanstack/ai-openai # Bun
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
```ts
|
|
408
|
+
import { chat } from "@tanstack/ai";
|
|
409
|
+
import { openaiCompatibleText } from "@tanstack/ai-openai/compatible";
|
|
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
|
+
});
|
|
418
|
+
|
|
419
|
+
for await (const chunk of stream) {
|
|
420
|
+
if (chunk.type === "TEXT_MESSAGE_CONTENT") process.stdout.write(chunk.delta);
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
Use the equivalent `npm install`, `pnpm add`, or `bun add` command if you use
|
|
425
|
+
another package manager. Replace the provider, account, and model with values
|
|
426
|
+
from your dashboard.
|
|
427
|
+
|
|
428
|
+
</details>
|
|
429
|
+
|
|
430
|
+
<details>
|
|
431
|
+
<summary><strong>OpenAI, Anthropic, Python, or cURL</strong></summary>
|
|
432
|
+
|
|
433
|
+
OpenAI Responses:
|
|
434
|
+
|
|
435
|
+
```bash
|
|
436
|
+
nub install openai
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
```js
|
|
440
|
+
import OpenAI from "openai";
|
|
441
|
+
|
|
442
|
+
const client = new OpenAI({
|
|
443
|
+
baseURL: "http://127.0.0.1:4319/aisubs/chatgpt/personal",
|
|
444
|
+
apiKey: process.env.AISUBS_API_KEY,
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
const stream = await client.responses.create({
|
|
448
|
+
model: "MODEL_ID",
|
|
449
|
+
store: false,
|
|
450
|
+
stream: true,
|
|
451
|
+
input: "Hello",
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
for await (const event of stream) console.log(event);
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
Anthropic Messages uses `@anthropic-ai/sdk` with this base URL:
|
|
458
|
+
|
|
459
|
+
```text
|
|
460
|
+
http://127.0.0.1:4319/aisubs/claude/team
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
Chat Completions with LiteLLM:
|
|
464
|
+
|
|
465
|
+
```bash
|
|
466
|
+
pip install litellm
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
```python
|
|
470
|
+
import os
|
|
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
|
+
)
|
|
480
|
+
|
|
481
|
+
for event in response:
|
|
482
|
+
print(event)
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
cURL:
|
|
486
|
+
|
|
487
|
+
```bash
|
|
488
|
+
curl "http://127.0.0.1:4319/aisubs/chatgpt/personal/responses" \
|
|
489
|
+
-H "Authorization: Bearer $AISUBS_API_KEY" \
|
|
490
|
+
-H "Content-Type: application/json" \
|
|
491
|
+
-d '{"model":"MODEL_ID","store":false,"stream":true,"input":"Hello"}'
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
For Messages, use `x-api-key: $AISUBS_API_KEY` and
|
|
495
|
+
`anthropic-version: 2023-06-01`.
|
|
496
|
+
|
|
497
|
+
</details>
|
|
498
|
+
|
|
499
|
+
<details>
|
|
500
|
+
<summary><strong>Run a server from Node.js</strong></summary>
|
|
501
|
+
|
|
502
|
+
HTTP bridge without the dashboard:
|
|
503
|
+
|
|
504
|
+
```js
|
|
505
|
+
import { randomBytes } from "node:crypto";
|
|
506
|
+
import { chatGptProvider, createSubscriptionAuth, FileCredentialStore } from "aisubs";
|
|
507
|
+
import { createSubscriptionAuthServer } from "aisubs/http";
|
|
508
|
+
|
|
509
|
+
const auth = createSubscriptionAuth({
|
|
510
|
+
store: new FileCredentialStore("./data/aisubs-credentials.json"),
|
|
511
|
+
providers: [chatGptProvider()],
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
const server = await createSubscriptionAuthServer({
|
|
515
|
+
auth,
|
|
516
|
+
apiKey: randomBytes(24).toString("hex"),
|
|
517
|
+
port: 4319,
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
console.log(server.url);
|
|
521
|
+
// await server.close();
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
Dashboard inside a Node.js application:
|
|
525
|
+
|
|
526
|
+
```js
|
|
527
|
+
import { createSubscriptionAuthDashboardServer } from "aisubs/dashboard";
|
|
528
|
+
|
|
529
|
+
const dashboard = await createSubscriptionAuthDashboardServer({ auth });
|
|
530
|
+
console.log(dashboard.bootstrapUrl);
|
|
531
|
+
// await dashboard.close();
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
Both servers bind only to localhost. The programmatic HTTP server requires its
|
|
535
|
+
API key; the dashboard also provides a one-time browser link.
|
|
536
|
+
|
|
537
|
+
</details>
|
|
538
|
+
|
|
539
|
+
<details>
|
|
540
|
+
<summary><strong>Local HTTP API</strong></summary>
|
|
541
|
+
|
|
542
|
+
```text
|
|
543
|
+
GET /health
|
|
544
|
+
GET /v1/providers
|
|
545
|
+
GET /v1/auth
|
|
546
|
+
GET /v1/auth/:provider
|
|
547
|
+
GET /v1/auth/:provider/accounts
|
|
548
|
+
POST /v1/auth/:provider/login
|
|
549
|
+
GET /v1/logins/:loginId
|
|
550
|
+
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
|
+
GET /v1/usage/:provider?account=work
|
|
555
|
+
GET /v1/models/:provider?account=work
|
|
556
|
+
* /aisubs/:provider/:account/*
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
The dashboard's `/bootstrap` link is one-time. Other routes require the
|
|
560
|
+
control API key or dashboard session cookie. Login responses return an attempt
|
|
561
|
+
ID; poll `/v1/logins/:loginId` until it is complete, failed, or cancelled.
|
|
562
|
+
|
|
563
|
+
</details>
|
|
564
|
+
|
|
565
|
+
<details>
|
|
566
|
+
<summary><strong>Storage and security</strong></summary>
|
|
567
|
+
|
|
568
|
+
- Default credentials: `~/.aisubs/credentials.json`.
|
|
569
|
+
- Override the directory with `AISUBS_DATA_DIR` or `--data-dir`.
|
|
570
|
+
- `FileCredentialStore` creates private directories/files and uses file locks.
|
|
571
|
+
- `MemoryCredentialStore` is available for tests and temporary processes.
|
|
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.
|
|
578
|
+
|
|
579
|
+
</details>
|
|
580
|
+
|
|
581
|
+
<details>
|
|
582
|
+
<summary><strong>Local development</strong></summary>
|
|
583
|
+
|
|
584
|
+
From the package directory, run:
|
|
585
|
+
|
|
586
|
+
```bash
|
|
587
|
+
nub run dev
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
Equivalent commands are `pnpm dev`, `npm run dev`, and `bun run dev`. Nub is
|
|
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>
|
|
599
|
+
|
|
600
|
+
```bash
|
|
601
|
+
nub run check
|
|
602
|
+
nub pack --dry-run
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
With another package manager, use `pnpm check` / `pnpm pack`,
|
|
606
|
+
`npm run check` / `npm pack --dry-run`, or `bun run check` / `bun pm pack`.
|
|
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>
|
|
618
|
+
|
|
619
|
+
## Contributing and bug reports
|
|
620
|
+
|
|
621
|
+
Please read [`CONTRIBUTING.md`](./CONTRIBUTING.md) before opening an issue or
|
|
622
|
+
pull request. To report a reproducible bug, use the
|
|
623
|
+
[Bug report form](./.github/ISSUE_TEMPLATE/bug_report.yml) and include the
|
|
624
|
+
version, environment, steps to reproduce, expected and actual behavior, and
|
|
625
|
+
sanitized error output where relevant.
|
|
626
|
+
|
|
627
|
+
## License
|
|
628
|
+
|
|
629
|
+
AISubs is licensed under the [MIT License](./LICENSE).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 56 56">
|
|
2
|
+
<rect width="56" height="56" rx="14" fill="#e5e5e5"/>
|
|
3
|
+
<path
|
|
4
|
+
fill="#0f0f0f"
|
|
5
|
+
fill-rule="evenodd"
|
|
6
|
+
d="M11.25 25.12C11.34 24.55 11.74 22.98 12 22.38C12.26 21.77 12.21 21.23 13.38 20.12C14.54 19.02 20.49 14.16 21.75 13.12C23.01 12.09 23.27 11.86 23.88 11.5C24.48 11.14 26.18 10.34 26.75 10.12C27.32 9.91 28.1 9.79 28.62 9.75C29.15 9.71 30.61 9.71 31.12 9.75C31.64 9.79 32.41 9.96 32.88 10.12C33.34 10.29 34.45 10.72 35 11.12C35.55 11.53 37.01 12.81 37.5 13.5C37.99 14.19 38.9 16.16 39.12 16.88C39.35 17.59 39.08 19.04 39.38 19.5C39.67 19.96 41.15 20.38 41.62 20.75C42.1 21.12 43.05 22.19 43.38 22.62C43.7 23.06 44.16 23.8 44.38 24.38C44.59 24.95 45.03 26.77 45.12 27.38C45.22 27.98 45.2 28.84 45.12 29.38C45.05 29.91 44.78 31.23 44.5 31.88C44.22 32.52 44.1 33.46 42.75 34.75C41.4 36.04 34.78 41.47 33.25 42.62C31.72 43.78 30.59 44.12 30 44.38C29.41 44.63 28.93 44.7 28.38 44.75C27.82 44.8 26.18 44.91 25.38 44.75C24.57 44.59 22.41 43.83 21.62 43.38C20.84 42.92 19.4 41.65 18.88 41C18.35 40.35 17.5 38.77 17.25 38C17 37.23 17.04 35.15 16.75 34.62C16.46 34.1 15.29 33.94 14.88 33.62C14.46 33.31 13.6 32.44 13.25 32C12.9 31.57 12.24 30.59 12 30C11.76 29.41 11.34 27.71 11.25 27.12C11.16 26.54 11.16 25.7 11.25 25.12Z
|
|
7
|
+
M14.75 25.75C14.72 26.45 15.12 28.27 15.38 28.88C15.63 29.48 16.61 30.5 16.88 30.75C17.14 31 17.48 31.14 17.62 31C17.77 30.86 17.95 29.95 18.12 29.62C18.3 29.3 18.66 28.71 19.12 28.25C19.59 27.79 20.7 26.75 22 25.75C23.3 24.75 28.65 20.73 30 19.88C31.35 19.02 32.56 18.79 33.25 18.62C33.94 18.46 35.48 18.68 35.75 18.5C36.02 18.32 35.74 17.59 35.5 17.12C35.26 16.66 34.2 15.06 33.75 14.62C33.3 14.19 32.34 13.66 31.75 13.5C31.16 13.34 29.49 13.21 28.88 13.25C28.26 13.29 28.07 12.88 26.62 13.88C25.18 14.87 18.2 20.41 16.88 21.5C15.55 22.59 15.88 22.49 15.62 23C15.37 23.51 14.78 25.05 14.75 25.75Z
|
|
8
|
+
M20.75 32C20.68 32.16 20.5 32.23 20.75 32.25C21 32.27 22.38 32.24 22.88 32.12C23.37 32.01 23.63 32.13 24.88 31.25C26.12 30.36 32.06 25.73 33.25 24.75C34.44 23.77 34.51 23.45 34.75 23.12C34.99 22.8 35.35 22.14 35.25 22C35.15 21.86 34.31 21.89 33.88 22C33.44 22.11 32.98 21.96 31.62 22.88C30.27 23.79 23.86 28.66 22.62 29.62C21.39 30.59 21.6 30.59 21.38 30.88C21.15 31.16 20.82 31.84 20.75 32Z
|
|
9
|
+
M20.5 35.75C20.16 36.11 21.05 37.84 21.38 38.38C21.7 38.91 22.71 39.91 23.25 40.25C23.79 40.59 25.32 41.13 25.88 41.25C26.43 41.37 27.45 41.3 27.88 41.25C28.3 41.2 29.05 40.99 29.38 40.88C29.7 40.76 29.41 41.16 30.62 40.25C31.84 39.34 38.34 34.2 39.5 33.25C40.66 32.3 40.04 32.69 40.25 32.38C40.46 32.06 41.09 31.02 41.25 30.62C41.41 30.23 41.62 29.68 41.62 29.12C41.62 28.57 41.43 26.6 41.25 26C41.07 25.4 40.44 24.48 40.12 24.12C39.81 23.77 38.9 22.93 38.62 23C38.35 23.07 38.2 24.25 37.88 24.75C37.55 25.25 37.18 26.02 35.88 27.12C34.57 28.23 28.39 33.01 27 34C25.61 34.99 25.03 35.16 24.25 35.38C23.47 35.59 20.84 35.39 20.5 35.75Z"
|
|
10
|
+
/>
|
|
11
|
+
</svg>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function nextAccountKey(sessions, provider) {
|
|
2
|
+
const used = new Set(sessions
|
|
3
|
+
.filter((session) => session.provider === provider && session.authenticated)
|
|
4
|
+
.map((session) => session.accountKey));
|
|
5
|
+
if (!used.has("default"))
|
|
6
|
+
return "default";
|
|
7
|
+
let index = 2;
|
|
8
|
+
while (used.has(`account-${index}`))
|
|
9
|
+
index += 1;
|
|
10
|
+
return `account-${index}`;
|
|
11
|
+
}
|