aisubs 0.1.0 → 0.3.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 CHANGED
@@ -1,543 +1,618 @@
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
- Connect once, then use your subscriptions through an SDK in your app, a local
12
- API, cURL, or any compatible tool.
8
+ AISubs connects provider accounts once and exposes each account through a local,
9
+ account-scoped API. Every generative account has an OpenAI-compatible surface,
10
+ while native Responses, Anthropic Messages, Google `generateContent`, Realtime,
11
+ and provider-specific endpoints remain available. Use it from an SDK, cURL, or
12
+ an app that accepts a custom API base URL.
13
13
 
14
14
  <p align="center">
15
- <img src="./public/aisubs-dashboard.png" alt="AISubs dashboard showing connected accounts" width="100%" />
15
+ <img src="./public/aisubs-dashboard.png" alt="AI Subs dashboard with provider connections and local API access" width="100%" />
16
16
  </p>
17
17
 
18
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%" />
19
+ <img src="./public/aisubs-chatgpt-account.png" alt="AISubs ChatGPT demo account details" width="100%" />
22
20
  </p>
23
21
 
24
- > AISubs keeps credentials on your computer. It collects no telemetry, analytics, request logs, or activity history.
22
+ <p align="center">
23
+ <img src="./public/aisubs-copilot-account.png" alt="AISubs GitHub Copilot demo account details" width="100%" />
24
+ </p>
25
+
26
+ <p align="center">
27
+ <img src="./public/aisubs-grok-account.png" alt="AISubs Grok demo account details" width="100%" />
28
+ </p>
29
+
30
+ > Credentials, API keys, and requests remain on your computer. AISubs collects no
31
+ > telemetry or analytics. The dashboard keeps up to 200 redacted account request logs in memory for debugging.
25
32
 
26
33
  ## Quick start
27
34
 
35
+ AISubs requires [Node.js 24 or newer](https://nodejs.org/en/download/). Run it
36
+ without adding it to a project:
37
+
28
38
  ```bash
29
- nubx aisubs dashboard # Nub (recommended)
30
- npx aisubs dashboard # npm
31
- pnpm exec aisubs dashboard # pnpm
32
- bunx aisubs dashboard # Bun
39
+ nubx aisubs@latest dashboard # Nub
40
+ npx aisubs@latest dashboard # npm
41
+ pnpm dlx aisubs@latest dashboard # pnpm
42
+ bunx aisubs@latest dashboard # Bun
33
43
  ```
34
44
 
35
- Click **Add account**, choose a provider, complete sign-in, and give the
36
- account a local name such as `personal` or `work`.
45
+ The terminal prints only the local dashboard URL. Open it, click **Add account**,
46
+ and complete the provider sign-in. The dashboard manages the persistent local API
47
+ key: reveal it, copy it, or deliberately regenerate it there.
48
+
49
+ The default URL is `http://127.0.0.1:4319`. Credentials and the API key are stored
50
+ under `~/.aisubs` and reused on later starts.
37
51
 
38
- ## The idea
52
+ Supported subscription/account providers are ChatGPT, Claude, GitHub Copilot,
53
+ Grok, OpenCode Go, and OpenCode Zen. Google `generateContent` is a supported
54
+ wire protocol only for Google models surfaced by one of those connected
55
+ providers; AISubs does not connect or claim a Google subscription account.
39
56
 
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.
57
+ ## Use an account from any compatible app
43
58
 
44
- One provider can have many accounts:
59
+ Open an account in the dashboard and copy its base URL:
45
60
 
46
61
  ```text
47
- ChatGPT / personal
48
- ChatGPT / work
49
- Claude / team
62
+ http://127.0.0.1:4319/aisubs/PROVIDER/ACCOUNT/v1
50
63
  ```
51
64
 
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
+ Then configure the app with:
65
66
 
66
- Provider model lists and protocols can change. Discover models at runtime and
67
- pin the AISubs version your application has tested.
67
+ ```text
68
+ API base URL: the account URL copied from AISubs
69
+ API key: the persistent key shown on the AISubs dashboard
70
+ Model: an exact model ID shown for that account
71
+ ```
68
72
 
69
- ## Fastest start: direct Node.js
73
+ For apps configured with environment variables:
70
74
 
71
- This is the simplest integration. It needs no local server and no AISubs API
72
- key.
75
+ ```bash
76
+ export OPENAI_BASE_URL="http://127.0.0.1:4319/aisubs/grok/personal/v1"
77
+ export OPENAI_API_KEY="aisubs_..."
78
+ ```
73
79
 
74
- ### 1. Create AISubs once
80
+ AISubs removes its local key before forwarding a request and adds only the
81
+ selected account's provider credential.
82
+
83
+ ## Desktop app examples
84
+
85
+ Keep AISubs running, then give the app the three values from the connected
86
+ account: its base URL, the AISubs API key, and an exact model ID.
87
+
88
+ ### [Handy](https://handy.computer/download) (macOS)
89
+
90
+ Open **Post Process** and set:
91
+
92
+ | Handy field | Value |
93
+ | ----------- | -------------------------------------------------------- |
94
+ | Provider | `Custom` |
95
+ | Base URL | `http://127.0.0.1:4319/aisubs/chatgpt/default/v1` |
96
+ | API Key | Your `aisubs_...` key from the AISubs dashboard |
97
+ | Model | An exact Dashboard model ID, for example `gpt-5.6-terra` |
98
+
99
+ Click Handy’s refresh button beside **Model**, select the model, then choose a
100
+ prompt and test it. Replace `chatgpt/default` with the provider and account
101
+ segment copied from your AISubs dashboard when using a different account.
102
+
103
+ ### [Raycast](https://www.raycast.com/download) AI custom provider
104
+
105
+ Create or edit `~/.config/raycast/ai/providers.yaml` and add a provider like
106
+ this. Keep the API key private—use the value from your AISubs dashboard, never
107
+ one copied from an example or screenshot.
108
+
109
+ ```yaml
110
+ providers:
111
+ - id: ai-subs
112
+ name: AI Subs
113
+ base_url: http://127.0.0.1:4319/aisubs/chatgpt/default/v1
114
+ api_keys:
115
+ default: aisubs_REPLACE_WITH_YOUR_DASHBOARD_KEY
116
+ models:
117
+ - id: gpt-5.6-terra
118
+ name: AISubs GPT-5.6 Terra
119
+ context: 272000
120
+ description: ChatGPT subscription via AISubs
121
+ abilities:
122
+ vision:
123
+ supported: true
124
+ system_message:
125
+ supported: true
126
+ tools:
127
+ supported: true
128
+ reasoning_effort:
129
+ supported: true
130
+ ```
75
131
 
76
- ```js
77
- // subscriptions.js
78
- import { chatGptProvider, createSubscriptionAuth } from "aisubs";
132
+ Restart Raycast after saving. Replace the base URL, model ID, context, and
133
+ abilities with the values shown for your connected account. Do not enable an
134
+ ability that the selected model does not report.
135
+
136
+ [Cline](https://cline.bot/) also accepts a custom OpenAI-compatible endpoint;
137
+ use the same base URL, local AISubs key, and model ID. Exact settings labels can
138
+ change between client releases, so the account dashboard remains the source of
139
+ truth for the three values.
140
+
141
+ ### Compatibility contract
142
+
143
+ Use Chat Completions when an app offers only an “OpenAI-compatible” provider.
144
+ AISubs discovers the selected model's native protocol and translates the
145
+ request and response when necessary. Calling a model's native protocol remains
146
+ a pass-through, which preserves provider-specific fields and streaming events.
147
+
148
+ | Integration path | Example full URL | Request example |
149
+ | ---------------------------------- | -------------------------------------------------------------------------------------- | --------------------------------------- |
150
+ | OpenAI Responses | `http://127.0.0.1:4319/aisubs/chatgpt/default/v1/responses` | `POST /responses` |
151
+ | OpenAI-compatible Chat Completions | `http://127.0.0.1:4319/aisubs/chatgpt/default/v1/chat/completions` | `POST /chat/completions` |
152
+ | Anthropic Messages | `http://127.0.0.1:4319/aisubs/claude/default/v1/messages` | `POST /messages` |
153
+ | Google generateContent | `http://127.0.0.1:4319/aisubs/opencode-zen/default/v1/models/MODEL_ID:generateContent` | `POST /models/MODEL_ID:generateContent` |
154
+ | Model discovery | `http://127.0.0.1:4319/aisubs/chatgpt/default/v1/models` | `GET /models` |
155
+ | Model details | `http://127.0.0.1:4319/aisubs/chatgpt/default/v1/models/MODEL_ID` | `GET /models/MODEL_ID` |
156
+ | OpenAI Realtime | `ws://127.0.0.1:4319/aisubs/PROVIDER/ACCOUNT/v1/realtime?model=MODEL_ID` | WebSocket |
157
+
158
+ Replace the provider, account (`default`), and model ID with the values shown
159
+ for your connected account in the AISubs dashboard.
160
+
161
+ Cross-protocol translation covers text and system messages, streaming, function
162
+ tools and tool results, image/file/audio input where both protocols support it,
163
+ JSON-schema output, reasoning effort, stop conditions, and usage including
164
+ cached and reasoning tokens. If a feature has no safe equivalent, AISubs returns
165
+ an explicit `unsupported_feature` error instead of silently dropping it.
166
+
167
+ Provider-native routes—including embeddings, image/audio generation, batches,
168
+ files, and Realtime—are forwarded when that connected provider exposes them.
169
+ AISubs cannot add a capability that the provider, subscription, or selected
170
+ model does not have. Native requests preserve provider-specific cache controls;
171
+ translated requests preserve shared fields and cache-usage counters. Other than
172
+ Responses-to-Chat streaming, cross-protocol streams may be emitted after the
173
+ native response completes; use the native endpoint when event-level streaming
174
+ or provider-specific fields matter.
175
+
176
+ ## SDK examples
177
+
178
+ Set the key once for the shell running your client:
79
179
 
80
- export const subscriptions = createSubscriptionAuth({
81
- providers: [chatGptProvider()],
82
- });
180
+ ```bash
181
+ export AISUBS_API_KEY="aisubs_..."
83
182
  ```
84
183
 
85
- By default, credentials are stored at `~/.aisubs/credentials.json`.
184
+ <details>
185
+ <summary><strong>Official OpenAI JavaScript SDK: Responses and Chat Completions</strong></summary>
86
186
 
87
- Keep this object in trusted backend code. Do not send it to a browser.
187
+ Install:
88
188
 
89
- ### 2. Connect an account
189
+ ```bash
190
+ nub install openai
191
+ npm install openai
192
+ pnpm add openai
193
+ bun add openai
194
+ ```
90
195
 
91
196
  ```js
92
- import { subscriptions } from "./subscriptions.js";
93
-
94
- const account = subscriptions.account("chatgpt", "personal");
197
+ import OpenAI from "openai";
95
198
 
96
- if (!(await account.status()).authenticated) {
97
- const login = await account.signIn();
199
+ const client = new OpenAI({
200
+ baseURL: "http://127.0.0.1:4319/aisubs/chatgpt/personal/v1",
201
+ apiKey: process.env.AISUBS_API_KEY,
202
+ });
98
203
 
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
- }
204
+ const stream = await client.responses.create({
205
+ model: "MODEL_ID_FROM_DASHBOARD",
206
+ input: "Hello from AISubs",
207
+ store: false,
208
+ stream: true,
209
+ });
106
210
 
107
- await login.wait();
108
- }
211
+ for await (const event of stream) console.log(event);
109
212
  ```
110
213
 
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:
214
+ Chat Completions uses the same client with any generative model. AISubs
215
+ translates to the selected model's native protocol when needed:
115
216
 
116
217
  ```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
- }),
218
+ const response = await client.chat.completions.create({
219
+ model: "MODEL_ID_FROM_DASHBOARD",
220
+ messages: [{ role: "user", content: "Hello from AISubs" }],
137
221
  });
138
222
 
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
- }
223
+ console.log(response.choices[0]?.message.content);
143
224
  ```
144
225
 
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.
226
+ </details>
156
227
 
157
228
  <details>
158
- <summary><strong>Use another provider</strong></summary>
229
+ <summary><strong>Official Anthropic JavaScript SDK</strong></summary>
159
230
 
160
- Add its provider factory when creating AISubs:
231
+ Install:
161
232
 
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
- });
233
+ ```bash
234
+ nub install @anthropic-ai/sdk
235
+ npm install @anthropic-ai/sdk
236
+ pnpm add @anthropic-ai/sdk
237
+ bun add @anthropic-ai/sdk
183
238
  ```
184
239
 
185
- To use a different file, pass a custom store:
186
-
187
240
  ```js
188
- import { chatGptProvider, createSubscriptionAuth, FileCredentialStore } from "aisubs";
241
+ import Anthropic from "@anthropic-ai/sdk";
189
242
 
190
- const subscriptions = createSubscriptionAuth({
191
- store: new FileCredentialStore("./data/aisubs-credentials.json"),
192
- providers: [chatGptProvider()],
243
+ const client = new Anthropic({
244
+ baseURL: "http://127.0.0.1:4319/aisubs/claude/team",
245
+ apiKey: process.env.AISUBS_API_KEY,
193
246
  });
194
- ```
195
247
 
196
- Then select the account by provider ID:
248
+ const message = await client.messages.create({
249
+ model: "MODEL_ID_FROM_DASHBOARD",
250
+ max_tokens: 1024,
251
+ messages: [{ role: "user", content: "Hello from AISubs" }],
252
+ });
197
253
 
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");
254
+ console.log(message.content);
204
255
  ```
205
256
 
206
- OpenCode uses an API key:
257
+ </details>
207
258
 
208
- ```js
209
- const login = await go.signIn({ apiKey: process.env.OPENCODE_API_KEY });
210
- await login.wait();
259
+ <details>
260
+ <summary><strong>Vercel AI SDK</strong></summary>
261
+
262
+ For a Responses model:
263
+
264
+ ```bash
265
+ nub install ai @ai-sdk/openai
266
+ npm install ai @ai-sdk/openai
267
+ pnpm add ai @ai-sdk/openai
268
+ bun add ai @ai-sdk/openai
211
269
  ```
212
270
 
213
- For ChatGPT on a headless machine, use `signIn({ mode: "device" })`. Copilot
214
- also accepts `enterpriseDomain` for a supported GitHub Enterprise Cloud domain.
271
+ ```js
272
+ import { createOpenAI } from "@ai-sdk/openai";
273
+ import { streamText } from "ai";
215
274
 
216
- Provider request paths are:
275
+ const aisubs = createOpenAI({
276
+ baseURL: "http://127.0.0.1:4319/aisubs/chatgpt/personal/v1",
277
+ apiKey: process.env.AISUBS_API_KEY,
278
+ });
279
+
280
+ const result = streamText({
281
+ model: aisubs.responses("MODEL_ID_FROM_DASHBOARD"),
282
+ prompt: "Hello from AISubs",
283
+ providerOptions: { openai: { store: false } },
284
+ });
217
285
 
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` |
286
+ for await (const text of result.textStream) process.stdout.write(text);
287
+ ```
224
288
 
225
- Use `account.getModels()` first for Copilot, Grok, and OpenCode because one
226
- provider can expose more than one request format.
289
+ For the universal Chat Completions surface, install
290
+ `@ai-sdk/openai-compatible`, create the provider with the same account base URL,
291
+ and select the model with `provider("MODEL_ID")`.
227
292
 
228
293
  </details>
229
294
 
230
295
  <details>
231
- <summary><strong>Read account, usage, and model information</strong></summary>
296
+ <summary><strong>TanStack AI: Chat Completions</strong></summary>
232
297
 
233
- ```js
234
- const details = await account.details();
298
+ TanStack's generic compatibility adapter targets Chat Completions. AISubs
299
+ translates it to the selected model's native protocol.
235
300
 
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
301
+ ```bash
302
+ nub install @tanstack/ai @tanstack/ai-openai
303
+ npm install @tanstack/ai @tanstack/ai-openai
304
+ pnpm add @tanstack/ai @tanstack/ai-openai
305
+ bun add @tanstack/ai @tanstack/ai-openai
240
306
  ```
241
307
 
242
- Useful methods:
308
+ ```ts
309
+ import { chat } from "@tanstack/ai";
310
+ import { openaiCompatible } from "@tanstack/ai-openai/compatible";
243
311
 
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 |
312
+ const aisubs = openaiCompatible({
313
+ name: "aisubs",
314
+ baseURL: "http://127.0.0.1:4319/aisubs/grok/personal/v1",
315
+ apiKey: process.env.AISUBS_API_KEY!,
316
+ models: ["MODEL_ID_FROM_DASHBOARD"],
317
+ });
254
318
 
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.
319
+ const stream = chat({
320
+ adapter: aisubs("MODEL_ID_FROM_DASHBOARD"),
321
+ messages: [{ role: "user", content: "Hello from AISubs" }],
322
+ });
323
+
324
+ for await (const event of stream) console.log(event);
325
+ ```
258
326
 
259
327
  </details>
260
328
 
261
329
  <details>
262
- <summary><strong>Use multiple accounts</strong></summary>
330
+ <summary><strong>Python OpenAI SDK</strong></summary>
263
331
 
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);
332
+ ```bash
333
+ python -m pip install openai
270
334
  ```
271
335
 
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>
336
+ ```python
337
+ import os
338
+ from openai import OpenAI
276
339
 
277
- ## Dashboard
340
+ client = OpenAI(
341
+ base_url="http://127.0.0.1:4319/aisubs/grok/personal/v1",
342
+ api_key=os.environ["AISUBS_API_KEY"],
343
+ )
278
344
 
279
- ### Requirements and installation
345
+ response = client.chat.completions.create(
346
+ model="MODEL_ID_FROM_DASHBOARD",
347
+ messages=[{"role": "user", "content": "Hello from AISubs"}],
348
+ )
349
+ print(response.choices[0].message.content)
350
+ ```
280
351
 
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.
352
+ </details>
289
353
 
290
- Install AISubs in your project:
354
+ <details>
355
+ <summary><strong>cURL: Responses, Chat Completions, Anthropic, and Google</strong></summary>
291
356
 
292
357
  ```bash
293
- nub install aisubs # Nub (recommended)
294
- npm install aisubs # npm
295
- pnpm add aisubs # pnpm
296
- bun add aisubs # Bun
358
+ curl "http://127.0.0.1:4319/aisubs/chatgpt/personal/v1/responses" \
359
+ -H "Authorization: Bearer $AISUBS_API_KEY" \
360
+ -H "Content-Type: application/json" \
361
+ -d '{"model":"MODEL_ID_FROM_DASHBOARD","store":false,"input":"Hello"}'
297
362
  ```
298
363
 
299
- Use the dashboard when you want to connect accounts without writing login UI:
364
+ Chat Completions:
300
365
 
301
366
  ```bash
302
- nubx aisubs dashboard # Nub (recommended)
303
- npx aisubs dashboard # npm
304
- pnpm exec aisubs dashboard # pnpm
305
- bunx aisubs dashboard # Bun
367
+ curl "http://127.0.0.1:4319/aisubs/grok/personal/v1/chat/completions" \
368
+ -H "Authorization: Bearer $AISUBS_API_KEY" \
369
+ -H "Content-Type: application/json" \
370
+ -d '{"model":"MODEL_ID_FROM_DASHBOARD","messages":[{"role":"user","content":"Hello from AISubs"}]}'
306
371
  ```
307
372
 
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:
373
+ Anthropic Messages:
317
374
 
318
375
  ```bash
319
- nubx aisubs dashboard \
320
- --data-dir ./data/aisubs \
321
- --port 0 \
322
- --no-open
376
+ curl "http://127.0.0.1:4319/aisubs/claude/team/v1/messages" \
377
+ -H "x-api-key: $AISUBS_API_KEY" \
378
+ -H "anthropic-version: 2023-06-01" \
379
+ -H "Content-Type: application/json" \
380
+ -d '{"model":"MODEL_ID_FROM_DASHBOARD","max_tokens":1024,"messages":[{"role":"user","content":"Hello from AISubs"}]}'
323
381
  ```
324
382
 
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:
383
+ Google `generateContent`:
340
384
 
341
385
  ```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"
386
+ curl "http://127.0.0.1:4319/aisubs/opencode-zen/lab/v1/models/MODEL_ID_FROM_DASHBOARD:generateContent" \
387
+ -H "Authorization: Bearer $AISUBS_API_KEY" \
388
+ -H "Content-Type: application/json" \
389
+ -d '{"contents":[{"role":"user","parts":[{"text":"Hello from AISubs"}]}]}'
347
390
  ```
348
391
 
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
- ```
392
+ </details>
357
393
 
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.
394
+ ## Direct AISubs SDK
361
395
 
362
- <details>
363
- <summary><strong>Vercel AI SDK</strong></summary>
396
+ Use the in-process SDK when AISubs is part of your trusted Node.js backend. It
397
+ needs no local server, AISubs API key, or app-specific integration. Direct SDK
398
+ requests use the provider-native endpoint; the local server is the surface that
399
+ adds cross-protocol compatibility for third-party clients.
364
400
 
365
- For a `responses` model:
401
+ Install the package in a project:
366
402
 
367
403
  ```bash
368
- nub install ai @ai-sdk/openai
404
+ nub install aisubs
405
+ npm install aisubs
406
+ pnpm add aisubs
407
+ bun add aisubs
369
408
  ```
370
409
 
371
410
  ```js
372
- import { createOpenAI } from "@ai-sdk/openai";
373
- import { streamText } from "ai";
411
+ import { chatGptProvider, createSubscriptionAuth } from "aisubs";
374
412
 
375
- const provider = createOpenAI({
376
- baseURL: "http://127.0.0.1:4319/aisubs/chatgpt/personal",
377
- apiKey: process.env.AISUBS_API_KEY,
378
- });
413
+ const subscriptions = createSubscriptionAuth({ providers: [chatGptProvider()] });
414
+ const account = subscriptions.account("chatgpt", "personal");
379
415
 
380
- const result = streamText({
381
- model: provider.responses("MODEL_ID"),
382
- prompt: "Hello",
383
- providerOptions: { openai: { store: false } },
416
+ if (!(await account.status()).authenticated) {
417
+ const login = await account.signIn();
418
+ console.log(login.prompt);
419
+ await login.wait();
420
+ }
421
+
422
+ const catalog = await account.getModels();
423
+ const selected = catalog?.models.find((item) => item.selectable !== false);
424
+ if (!selected) throw new Error("No selectable model is available");
425
+
426
+ // ChatGPT models are native Responses models. Other providers report their
427
+ // native endpoint in selected.endpoints; use the matching native SDK adapter.
428
+ const model = selected.id;
429
+
430
+ const response = await account.proxy("responses", {
431
+ method: "POST",
432
+ headers: { "content-type": "application/json" },
433
+ body: JSON.stringify({ model, input: "Hello", store: false, stream: true }),
384
434
  });
385
435
 
386
- for await (const text of result.textStream) process.stdout.write(text);
436
+ if (!response.ok) throw new Error(await response.text());
437
+ for await (const chunk of response.body ?? []) process.stdout.write(Buffer.from(chunk));
387
438
  ```
388
439
 
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.
440
+ Available provider factories are `chatGptProvider()`, `claudeProvider()`,
441
+ `copilotProvider()`, `grokProvider()`, `openCodeGoProvider()`, and
442
+ `openCodeZenProvider()`.
392
443
 
393
- </details>
444
+ `getModels()` is cached for five minutes and `getUsage()` for 15 seconds by
445
+ default, with concurrent calls deduplicated. Account changes invalidate both.
446
+ Pass `modelsCacheTtlMs` or `usageCacheTtlMs` to `createSubscriptionAuth()` when
447
+ an in-process application needs different metadata freshness.
394
448
 
395
- <details>
396
- <summary><strong>TanStack AI</strong></summary>
449
+ Useful account methods:
397
450
 
398
- TanStack AI can use AISubs through its OpenAI-compatible adapter:
451
+ | Method | Purpose |
452
+ | -------------------- | ------------------------------------------------------ |
453
+ | `status()` | Check whether the account is connected |
454
+ | `signIn(options?)` | Start browser, device-code, or API-key sign-in |
455
+ | `signOut()` | Remove the account's locally stored credential |
456
+ | `getModels()` | Read the current provider model catalog |
457
+ | `getUsage()` | Read current provider usage when available |
458
+ | `details()` | Read safe identity, credential, usage, and model data |
459
+ | `fetch(url, init?)` | Send an authorized request to an allowed provider host |
460
+ | `proxy(path, init?)` | Send a provider-native request without handling tokens |
399
461
 
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
- ```
462
+ <details>
463
+ <summary><strong>Configure every provider and custom credential storage</strong></summary>
406
464
 
407
- ```ts
408
- import { chat } from "@tanstack/ai";
409
- import { openaiCompatibleText } from "@tanstack/ai-openai/compatible";
465
+ ```js
466
+ import {
467
+ FileCredentialStore,
468
+ chatGptProvider,
469
+ claudeProvider,
470
+ copilotProvider,
471
+ createSubscriptionAuth,
472
+ grokProvider,
473
+ openCodeGoProvider,
474
+ openCodeZenProvider,
475
+ } from "aisubs";
410
476
 
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" }],
477
+ const subscriptions = createSubscriptionAuth({
478
+ store: new FileCredentialStore("./data/aisubs-credentials.json"),
479
+ providers: [
480
+ chatGptProvider(),
481
+ claudeProvider(),
482
+ copilotProvider(),
483
+ grokProvider(),
484
+ openCodeGoProvider(),
485
+ openCodeZenProvider(),
486
+ ],
417
487
  });
488
+ ```
418
489
 
419
- for await (const chunk of stream) {
420
- if (chunk.type === "TEXT_MESSAGE_CONTENT") process.stdout.write(chunk.delta);
421
- }
490
+ Without a custom store, credentials are saved to
491
+ `~/.aisubs/credentials.json`. Select an account with its provider ID and a
492
+ local account name:
493
+
494
+ ```js
495
+ const chatgpt = subscriptions.account("chatgpt", "personal");
496
+ const claude = subscriptions.account("claude", "team");
497
+ const copilot = subscriptions.account("copilot", "github");
498
+ const grok = subscriptions.account("grok", "personal");
499
+ const go = subscriptions.account("opencode-go", "team");
500
+ const zen = subscriptions.account("opencode-zen", "lab");
422
501
  ```
423
502
 
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.
503
+ Account names are 1–128 characters and cannot contain control characters.
427
504
 
428
505
  </details>
429
506
 
430
507
  <details>
431
- <summary><strong>OpenAI, Anthropic, Python, or cURL</strong></summary>
508
+ <summary><strong>Browser, device-code, and API-key sign-in</strong></summary>
432
509
 
433
- OpenAI Responses:
510
+ The default ChatGPT flow opens a browser. On a headless machine, request its
511
+ device-code flow explicitly:
434
512
 
435
- ```bash
436
- nub install openai
513
+ ```js
514
+ const login = await chatgpt.signIn({ mode: "device" });
515
+ console.log(login.prompt);
516
+ await login.wait();
437
517
  ```
438
518
 
439
- ```js
440
- import OpenAI from "openai";
519
+ Copilot uses device-code sign-in and can target a supported GitHub Enterprise
520
+ Cloud domain:
441
521
 
442
- const client = new OpenAI({
443
- baseURL: "http://127.0.0.1:4319/aisubs/chatgpt/personal",
444
- apiKey: process.env.AISUBS_API_KEY,
445
- });
522
+ ```js
523
+ const login = await copilot.signIn({ enterpriseDomain: "company.ghe.com" });
524
+ console.log(login.prompt);
525
+ await login.wait();
526
+ ```
446
527
 
447
- const stream = await client.responses.create({
448
- model: "MODEL_ID",
449
- store: false,
450
- stream: true,
451
- input: "Hello",
452
- });
528
+ OpenCode Go and Zen use API keys:
453
529
 
454
- for await (const event of stream) console.log(event);
530
+ ```js
531
+ const login = await go.signIn({ apiKey: process.env.OPENCODE_API_KEY });
532
+ await login.wait();
455
533
  ```
456
534
 
457
- Anthropic Messages uses `@anthropic-ai/sdk` with this base URL:
535
+ </details>
458
536
 
459
- ```text
460
- http://127.0.0.1:4319/aisubs/claude/team
461
- ```
537
+ <details>
538
+ <summary><strong>Inspect account details and switch between accounts</strong></summary>
462
539
 
463
- Chat Completions with LiteLLM:
540
+ ```js
541
+ const details = await chatgpt.details();
464
542
 
465
- ```bash
466
- pip install litellm
543
+ console.log(details.session); // connection state and safe account identity
544
+ console.log(details.credential); // expiry and refresh state, never token values
545
+ console.log(details.usage); // limits and reset information, or null
546
+ console.log(details.models); // available models, or null
467
547
  ```
468
548
 
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
- )
549
+ `details()`, `getUsage()`, and `getModels()` never return access or refresh
550
+ tokens. Prefer `fetch()` or `proxy()` over handling a token directly.
480
551
 
481
- for event in response:
482
- print(event)
483
- ```
552
+ Each named account keeps separate credentials, refresh state, usage, and model
553
+ data, so selection can happen at request time:
484
554
 
485
- cURL:
555
+ ```js
556
+ const personal = subscriptions.account("chatgpt", "personal");
557
+ const work = subscriptions.account("chatgpt", "work");
486
558
 
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"}'
559
+ const selected = user.isWorkAccount ? work : personal;
560
+ const response = await selected.proxy("responses", requestOptions);
492
561
  ```
493
562
 
494
- For Messages, use `x-api-key: $AISUBS_API_KEY` and
495
- `anthropic-version: 2023-06-01`.
496
-
497
563
  </details>
498
564
 
499
565
  <details>
500
- <summary><strong>Run a server from Node.js</strong></summary>
566
+ <summary><strong>Run the local HTTP server from Node.js</strong></summary>
501
567
 
502
- HTTP bridge without the dashboard:
568
+ This is the programmatic equivalent of `aisubs dashboard`. The API key is
569
+ created once and reused across restarts; delete or regenerate the key file only
570
+ when clients should receive a new key.
503
571
 
504
572
  ```js
505
- import { randomBytes } from "node:crypto";
506
- import { chatGptProvider, createSubscriptionAuth, FileCredentialStore } from "aisubs";
573
+ import { homedir } from "node:os";
574
+ import { join } from "node:path";
575
+ import {
576
+ FileApiKeyStore,
577
+ FileCredentialStore,
578
+ chatGptProvider,
579
+ claudeProvider,
580
+ createSubscriptionAuth,
581
+ } from "aisubs";
507
582
  import { createSubscriptionAuthServer } from "aisubs/http";
508
583
 
584
+ const directory = join(homedir(), ".aisubs");
585
+ const apiKey = await new FileApiKeyStore(join(directory, "api-key")).readOrCreate();
509
586
  const auth = createSubscriptionAuth({
510
- store: new FileCredentialStore("./data/aisubs-credentials.json"),
511
- providers: [chatGptProvider()],
587
+ store: new FileCredentialStore(join(directory, "credentials.json")),
588
+ providers: [chatGptProvider(), claudeProvider()],
512
589
  });
513
590
 
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();
591
+ const server = await createSubscriptionAuthServer({ auth, apiKey, port: 4319 });
592
+ console.log(`AISubs API: ${server.url}`);
522
593
  ```
523
594
 
524
- Dashboard inside a Node.js application:
595
+ A runnable version is available in [`examples/server.mjs`](./examples/server.mjs).
525
596
 
526
- ```js
527
- import { createSubscriptionAuthDashboardServer } from "aisubs/dashboard";
597
+ </details>
598
+
599
+ ## Dashboard options
528
600
 
529
- const dashboard = await createSubscriptionAuthDashboardServer({ auth });
530
- console.log(dashboard.bootstrapUrl);
531
- // await dashboard.close();
601
+ ```text
602
+ aisubs dashboard [options]
603
+
604
+ --data-dir <path> State directory (default: ~/.aisubs)
605
+ --port <number> Local port (default: 4319; 0 chooses an available port)
606
+ --no-open Do not open the browser
607
+ --help Show help
532
608
  ```
533
609
 
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.
610
+ `AISUBS_DATA_DIR` also changes the state directory. The built-in server binds
611
+ only to localhost.
536
612
 
537
- </details>
613
+ ## Local management API
538
614
 
539
- <details>
540
- <summary><strong>Local HTTP API</strong></summary>
615
+ These routes are for the dashboard and advanced integrations:
541
616
 
542
617
  ```text
543
618
  GET /health
@@ -546,84 +621,60 @@ GET /v1/auth
546
621
  GET /v1/auth/:provider
547
622
  GET /v1/auth/:provider/accounts
548
623
  POST /v1/auth/:provider/login
624
+ DELETE /v1/auth/:provider?account=work
549
625
  GET /v1/logins/:loginId
550
626
  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
627
  GET /v1/usage/:provider?account=work
555
628
  GET /v1/models/:provider?account=work
556
- * /aisubs/:provider/:account/*
629
+ GET /v1/api-key # dashboard session only
630
+ POST /v1/api-key/regenerate # dashboard session only
631
+ * /aisubs/:provider/:account/v1/*
557
632
  ```
558
633
 
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.
634
+ The provider and account routes accept `Authorization: Bearer AISUBS_API_KEY`
635
+ or `x-api-key: AISUBS_API_KEY`. A Google-protocol client can instead put that
636
+ same local AISubs key in `x-goog-api-key` or `?key=`; this does not represent a
637
+ Google credential or subscription. Local credentials are removed from headers
638
+ and query strings before proxying. Regenerating the key immediately invalidates
639
+ the old key.
562
640
 
563
- </details>
641
+ ## Storage and security
564
642
 
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.
643
+ - Credentials: `~/.aisubs/credentials.json`.
644
+ - Persistent local API key: `~/.aisubs/api-key`.
645
+ - State directories and files use private permissions where the platform supports them.
646
+ - Provider credentials are attached only after provider-host allowlist validation.
647
+ - Local authorization, cookie, origin, and proxy headers are never forwarded.
648
+ - Account and model APIs never return provider access or refresh tokens.
649
+ - Keep AISubs on localhost and never put its API key in browser-delivered code.
578
650
 
579
- </details>
651
+ ## Development
580
652
 
581
- <details>
582
- <summary><strong>Local development</strong></summary>
583
-
584
- From the package directory, run:
653
+ From the package directory:
585
654
 
586
655
  ```bash
587
- nub run dev
656
+ nub install
657
+ nub run check
588
658
  ```
589
659
 
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>
660
+ Package-manager equivalents:
599
661
 
600
662
  ```bash
601
- nub run check
602
- nub pack --dry-run
663
+ npm install && npm run check
664
+ pnpm install && pnpm run check
665
+ bun install && bun run check
603
666
  ```
604
667
 
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>
668
+ For the watch dashboard use `nub run dev`, `npm run dev`, `pnpm run dev`, or
669
+ `bun run dev`. Before publishing, also inspect the package with `nub pack --dry-run`,
670
+ `npm pack --dry-run`, `pnpm pack --dry-run`, or `bun pm pack --dry-run`.
618
671
 
619
- ## Contributing and bug reports
672
+ ## Provider terms
620
673
 
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.
674
+ Provider subscriptions, OAuth clients, and model access are governed by each
675
+ provider's terms and may change. Use accounts you are authorized to use, discover
676
+ models at runtime, and pin the AISubs version your integration has tested.
626
677
 
627
678
  ## License
628
679
 
629
- AISubs is licensed under the [MIT License](./LICENSE).
680
+ [MIT](./LICENSE)