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/CHANGELOG.md +17 -0
- package/README.md +478 -427
- package/dist/auth.d.ts +2 -0
- package/dist/auth.js +23 -2
- package/dist/cli.js +10 -7
- package/dist/compatibility.d.ts +14 -0
- package/dist/compatibility.js +1521 -0
- package/dist/dashboard/assets/index-CEDww1hA.css +2 -0
- package/dist/dashboard/assets/index-DrnM3oWy.js +84 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/dashboard.d.ts +5 -2
- package/dist/dashboard.js +200 -75
- package/dist/http.d.ts +9 -6
- package/dist/http.js +258 -150
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/providers/chatgpt.js +2 -0
- package/dist/providers/claude.js +2 -0
- package/dist/providers/copilot.js +0 -2
- package/dist/realtime.d.ts +6 -0
- package/dist/realtime.js +163 -0
- package/dist/store.d.ts +6 -0
- package/dist/store.js +51 -2
- package/examples/direct.mjs +31 -8
- package/examples/server.mjs +17 -5
- package/package.json +9 -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,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
|
-
|
|
12
|
-
API
|
|
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="
|
|
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
|
|
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
|
-
>
|
|
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
|
|
30
|
-
npx aisubs dashboard # npm
|
|
31
|
-
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
|
-
|
|
36
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
59
|
+
Open an account in the dashboard and copy its base URL:
|
|
45
60
|
|
|
46
61
|
```text
|
|
47
|
-
|
|
48
|
-
ChatGPT / work
|
|
49
|
-
Claude / team
|
|
62
|
+
http://127.0.0.1:4319/aisubs/PROVIDER/ACCOUNT/v1
|
|
50
63
|
```
|
|
51
64
|
|
|
52
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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
|
-
|
|
73
|
+
For apps configured with environment variables:
|
|
70
74
|
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
});
|
|
180
|
+
```bash
|
|
181
|
+
export AISUBS_API_KEY="aisubs_..."
|
|
83
182
|
```
|
|
84
183
|
|
|
85
|
-
|
|
184
|
+
<details>
|
|
185
|
+
<summary><strong>Official OpenAI JavaScript SDK: Responses and Chat Completions</strong></summary>
|
|
86
186
|
|
|
87
|
-
|
|
187
|
+
Install:
|
|
88
188
|
|
|
89
|
-
|
|
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
|
|
93
|
-
|
|
94
|
-
const account = subscriptions.account("chatgpt", "personal");
|
|
197
|
+
import OpenAI from "openai";
|
|
95
198
|
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
108
|
-
}
|
|
211
|
+
for await (const event of stream) console.log(event);
|
|
109
212
|
```
|
|
110
213
|
|
|
111
|
-
|
|
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
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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>
|
|
229
|
+
<summary><strong>Official Anthropic JavaScript SDK</strong></summary>
|
|
159
230
|
|
|
160
|
-
|
|
231
|
+
Install:
|
|
161
232
|
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
|
241
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
189
242
|
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
257
|
+
</details>
|
|
207
258
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
214
|
-
|
|
271
|
+
```js
|
|
272
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
273
|
+
import { streamText } from "ai";
|
|
215
274
|
|
|
216
|
-
|
|
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
|
-
|
|
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
|
-
|
|
226
|
-
provider
|
|
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>
|
|
296
|
+
<summary><strong>TanStack AI: Chat Completions</strong></summary>
|
|
232
297
|
|
|
233
|
-
|
|
234
|
-
|
|
298
|
+
TanStack's generic compatibility adapter targets Chat Completions. AISubs
|
|
299
|
+
translates it to the selected model's native protocol.
|
|
235
300
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
308
|
+
```ts
|
|
309
|
+
import { chat } from "@tanstack/ai";
|
|
310
|
+
import { openaiCompatible } from "@tanstack/ai-openai/compatible";
|
|
243
311
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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>
|
|
330
|
+
<summary><strong>Python OpenAI SDK</strong></summary>
|
|
263
331
|
|
|
264
|
-
```
|
|
265
|
-
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
</details>
|
|
336
|
+
```python
|
|
337
|
+
import os
|
|
338
|
+
from openai import OpenAI
|
|
276
339
|
|
|
277
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
354
|
+
<details>
|
|
355
|
+
<summary><strong>cURL: Responses, Chat Completions, Anthropic, and Google</strong></summary>
|
|
291
356
|
|
|
292
357
|
```bash
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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
|
-
|
|
364
|
+
Chat Completions:
|
|
300
365
|
|
|
301
366
|
```bash
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
-
|
|
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
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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
|
-
|
|
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
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
363
|
-
|
|
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
|
-
|
|
401
|
+
Install the package in a project:
|
|
366
402
|
|
|
367
403
|
```bash
|
|
368
|
-
nub install
|
|
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 {
|
|
373
|
-
import { streamText } from "ai";
|
|
411
|
+
import { chatGptProvider, createSubscriptionAuth } from "aisubs";
|
|
374
412
|
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
apiKey: process.env.AISUBS_API_KEY,
|
|
378
|
-
});
|
|
413
|
+
const subscriptions = createSubscriptionAuth({ providers: [chatGptProvider()] });
|
|
414
|
+
const account = subscriptions.account("chatgpt", "personal");
|
|
379
415
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
prompt
|
|
383
|
-
|
|
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
|
-
|
|
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
|
-
|
|
390
|
-
|
|
391
|
-
`
|
|
440
|
+
Available provider factories are `chatGptProvider()`, `claudeProvider()`,
|
|
441
|
+
`copilotProvider()`, `grokProvider()`, `openCodeGoProvider()`, and
|
|
442
|
+
`openCodeZenProvider()`.
|
|
392
443
|
|
|
393
|
-
|
|
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
|
-
|
|
396
|
-
<summary><strong>TanStack AI</strong></summary>
|
|
449
|
+
Useful account methods:
|
|
397
450
|
|
|
398
|
-
|
|
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
|
-
|
|
401
|
-
|
|
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
|
-
```
|
|
408
|
-
import {
|
|
409
|
-
|
|
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
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
-
|
|
420
|
-
|
|
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
|
-
|
|
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>
|
|
508
|
+
<summary><strong>Browser, device-code, and API-key sign-in</strong></summary>
|
|
432
509
|
|
|
433
|
-
|
|
510
|
+
The default ChatGPT flow opens a browser. On a headless machine, request its
|
|
511
|
+
device-code flow explicitly:
|
|
434
512
|
|
|
435
|
-
```
|
|
436
|
-
|
|
513
|
+
```js
|
|
514
|
+
const login = await chatgpt.signIn({ mode: "device" });
|
|
515
|
+
console.log(login.prompt);
|
|
516
|
+
await login.wait();
|
|
437
517
|
```
|
|
438
518
|
|
|
439
|
-
|
|
440
|
-
|
|
519
|
+
Copilot uses device-code sign-in and can target a supported GitHub Enterprise
|
|
520
|
+
Cloud domain:
|
|
441
521
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
-
|
|
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
|
-
|
|
530
|
+
```js
|
|
531
|
+
const login = await go.signIn({ apiKey: process.env.OPENCODE_API_KEY });
|
|
532
|
+
await login.wait();
|
|
455
533
|
```
|
|
456
534
|
|
|
457
|
-
|
|
535
|
+
</details>
|
|
458
536
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
```
|
|
537
|
+
<details>
|
|
538
|
+
<summary><strong>Inspect account details and switch between accounts</strong></summary>
|
|
462
539
|
|
|
463
|
-
|
|
540
|
+
```js
|
|
541
|
+
const details = await chatgpt.details();
|
|
464
542
|
|
|
465
|
-
|
|
466
|
-
|
|
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
|
-
|
|
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
|
-
)
|
|
549
|
+
`details()`, `getUsage()`, and `getModels()` never return access or refresh
|
|
550
|
+
tokens. Prefer `fetch()` or `proxy()` over handling a token directly.
|
|
480
551
|
|
|
481
|
-
|
|
482
|
-
|
|
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
|
-
|
|
555
|
+
```js
|
|
556
|
+
const personal = subscriptions.account("chatgpt", "personal");
|
|
557
|
+
const work = subscriptions.account("chatgpt", "work");
|
|
486
558
|
|
|
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"}'
|
|
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
|
|
566
|
+
<summary><strong>Run the local HTTP server from Node.js</strong></summary>
|
|
501
567
|
|
|
502
|
-
|
|
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 {
|
|
506
|
-
import {
|
|
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("
|
|
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
|
-
|
|
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
|
-
|
|
595
|
+
A runnable version is available in [`examples/server.mjs`](./examples/server.mjs).
|
|
525
596
|
|
|
526
|
-
|
|
527
|
-
|
|
597
|
+
</details>
|
|
598
|
+
|
|
599
|
+
## Dashboard options
|
|
528
600
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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
|
-
|
|
535
|
-
|
|
610
|
+
`AISUBS_DATA_DIR` also changes the state directory. The built-in server binds
|
|
611
|
+
only to localhost.
|
|
536
612
|
|
|
537
|
-
|
|
613
|
+
## Local management API
|
|
538
614
|
|
|
539
|
-
|
|
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
|
-
|
|
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
|
|
560
|
-
|
|
561
|
-
|
|
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
|
-
|
|
641
|
+
## Storage and security
|
|
564
642
|
|
|
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.
|
|
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
|
-
|
|
651
|
+
## Development
|
|
580
652
|
|
|
581
|
-
|
|
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
|
|
656
|
+
nub install
|
|
657
|
+
nub run check
|
|
588
658
|
```
|
|
589
659
|
|
|
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>
|
|
660
|
+
Package-manager equivalents:
|
|
599
661
|
|
|
600
662
|
```bash
|
|
601
|
-
|
|
602
|
-
|
|
663
|
+
npm install && npm run check
|
|
664
|
+
pnpm install && pnpm run check
|
|
665
|
+
bun install && bun run check
|
|
603
666
|
```
|
|
604
667
|
|
|
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>
|
|
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
|
-
##
|
|
672
|
+
## Provider terms
|
|
620
673
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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
|
-
|
|
680
|
+
[MIT](./LICENSE)
|