@goodandready/dsh-subscriptions 0.4.15 → 0.4.17
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 +175 -209
- package/README.ru.md +196 -0
- package/README.zh.md +123 -0
- package/lib/accounts.js +19 -5
- package/lib/adapter.js +28 -7
- package/lib/client.js +153 -3
- package/lib/index.js +352 -12
- package/lib/loopback.js +66 -0
- package/lib/mask.js +21 -0
- package/lib/messages.js +8 -1
- package/lib/ollama.js +70 -0
- package/lib/proxy.js +79 -0
- package/lib/rotate.js +28 -3
- package/lib/stream-rotate.js +4 -3
- package/lib/subscriptions.js +8 -5
- package/lib/vendors/codex.js +46 -1
- package/lib/vendors/grok.js +2 -0
- package/package.json +9 -2
package/README.md
CHANGED
|
@@ -1,233 +1,199 @@
|
|
|
1
|
-
# dsh-subscriptions
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
# 📦 @goodandready/dsh-subscriptions
|
|
2
|
+
|
|
3
|
+
<div align="center">
|
|
4
|
+
|
|
5
|
+
<h3>Personal AI Subscription Bridge, Multi-Account Pool Rotation & Zero-Leak OAuth for DeepSeek Harness</h3>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://www.npmjs.com/package/@goodandready/dsh-subscriptions"><img src="https://img.shields.io/npm/v/@goodandready/dsh-subscriptions.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
|
|
9
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-10b981.svg?style=for-the-badge&color=10b981&labelColor=064e3b" alt="license"></a>
|
|
10
|
+
<a href="https://github.com/topics/dsh-plugin"><img src="https://img.shields.io/badge/DSH-Plugin-8b5cf6.svg?style=for-the-badge&labelColor=2e1065" alt="DSH Plugin"></a>
|
|
11
|
+
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/Node-20%2B-f59e0b.svg?style=for-the-badge&labelColor=451a03" alt="Node version"></a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
<a href="https://goodandready.app/"><img src="https://img.shields.io/badge/All_Author_Projects-goodandready.app-ff4500.svg?style=for-the-badge&logo=rocket&logoColor=white&labelColor=1a1a2e" alt="All Author Projects"></a>
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
<p align="center">
|
|
19
|
+
<a href="README.md"><b>🇬🇧 English</b></a> •
|
|
20
|
+
<a href="README.ru.md"><b>🇷🇺 Русский</b></a> •
|
|
21
|
+
<a href="README.zh.md"><b>🇨🇳 中文说明</b></a>
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## ⚡ Overview
|
|
29
|
+
|
|
30
|
+
**`dsh-subscriptions`** bridges your paid personal AI subscriptions directly into **DeepSeek Harness** as first-class LLM providers.
|
|
31
|
+
|
|
32
|
+
Instead of burning expensive pay-as-you-go API credits for everyday agent tasks, `dsh-subscriptions` allows you to authenticate your existing web subscriptions via standard OAuth PKCE. It features **multi-account rotation pools** (automatically switching accounts when a rate limit or cooldown is reached), **preemptive quota switching**, and an **in-process Cordis service (`ctx.subscriptions`)** that safely powers sibling plugins like [`dsh-image-gen`](https://github.com/GooDAnDReaDY/dsh-image-gen) and [`dsh-grok-xsearch`](https://github.com/GooDAnDReaDY/dsh-grok-xsearch) with zero token leakage.
|
|
33
|
+
|
|
34
|
+
```mermaid
|
|
35
|
+
graph LR
|
|
36
|
+
subgraph DSHCore [DeepSeek Harness Session]
|
|
37
|
+
Agent[🤖 DSH Agent Execution] --> Router{Provider Router}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
subgraph SubscriptionsCore [dsh-subscriptions Engine]
|
|
41
|
+
Router --> Pool{Multi-Account Vendor Pool}
|
|
42
|
+
Pool -->|Account #1| Acc1[👤 Primary Account: Active]
|
|
43
|
+
Pool -->|Account #2| Acc2[👤 Secondary Account: Standby]
|
|
44
|
+
Pool -->|Account #3| Acc3[👤 Fallback Account: Cooldown]
|
|
45
|
+
Acc1 -->|HTTP 429 / Quota Limit| Rotate[Smart Quota & Cooldown Rotator]
|
|
46
|
+
Rotate -->|Switches Traffic| Acc2
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
subgraph VendorBridges [4 Upstream Vendor Bridges]
|
|
50
|
+
Acc1 --> B1[ChatGPT / Codex Backend]
|
|
51
|
+
Acc1 --> B2[Claude Pro / Max Protocol]
|
|
52
|
+
Acc1 --> B3[xAI / Grok Subscriptions]
|
|
53
|
+
Acc1 --> B4[Google Cloud Code Assist / Antigravity]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
subgraph EcosystemBridge [In-Process Cordis Service: ctx.subscriptions]
|
|
57
|
+
Pool --> ImgGen[dsh-image-gen: Zero-Cost Image Drawing]
|
|
58
|
+
Pool --> XSearch[dsh-grok-xsearch: Live Twitter Search]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
style DSHCore fill:#1e1e2e,stroke:#89b4fa,stroke-width:2px,color:#cdd6f4
|
|
62
|
+
style SubscriptionsCore fill:#181825,stroke:#cba6f7,stroke-width:2px,color:#cdd6f4
|
|
63
|
+
style VendorBridges fill:#11111b,stroke:#a6e3a1,stroke-width:2px,color:#cdd6f4
|
|
64
|
+
style EcosystemBridge fill:#181825,stroke:#f38ba8,stroke-width:2px,color:#cdd6f4
|
|
15
65
|
```
|
|
16
66
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
dsh plugin --profile web add file:/path/to/dsh-subscriptions
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
After a `file:` install, remove then add again if you added new files under
|
|
24
|
-
`lib/` — pnpm reuses the previous copy otherwise.
|
|
25
|
-
|
|
26
|
-
## Settings
|
|
27
|
-
|
|
28
|
-
Settings live on a collapsible card in **Settings → Plugins → Plugin settings**
|
|
29
|
-
(not a sidebar entry). Click **Show** to expand it.
|
|
30
|
-
|
|
31
|
-
1. Pick a provider block and click **Connect**. Complete sign-in in the browser.
|
|
32
|
-
2. **+ Add account** adds another account slot for the same provider; every
|
|
33
|
-
account participates in rotation.
|
|
34
|
-
3. **Disconnect** removes that account's token; **Reconnect** repeats OAuth on
|
|
35
|
-
the same slot. The × button disconnects and drops the slot.
|
|
36
|
-
4. If the provider redirects to a localhost or vendor URL that this host cannot
|
|
37
|
-
receive, paste the full redirected URL (or the `code` value) into the
|
|
38
|
-
account row and click **Submit code**.
|
|
39
|
-
5. Logged-in providers appear in the session model picker.
|
|
40
|
-
|
|
41
|
-
Leave **Use this Web UI origin as OAuth redirect_uri** off unless you registered
|
|
42
|
-
your own OAuth client for this origin. Vendor CLI clients typically require
|
|
43
|
-
their published redirect URI plus the paste step.
|
|
44
|
-
|
|
45
|
-
## Accounts and rotation
|
|
46
|
-
|
|
47
|
-
Each provider holds any number of account slots (`CODEX_OAUTH_1`,
|
|
48
|
-
`CODEX_OAUTH_2`, …). On `RATE_LIMIT`, `QUOTA`, or HTTP 429 the plugin cools the
|
|
49
|
-
account down (`cooldownMs`, default 30 minutes) and retries the same request on
|
|
50
|
-
the next account of the same provider — never a different provider.
|
|
51
|
-
|
|
52
|
-
Beyond error-driven rotation, accounts are proactively skipped when:
|
|
53
|
-
|
|
54
|
-
- usage is at 100% for the current window (vendor-reported), or
|
|
55
|
-
- remaining quota fraction is at or below `switchAtRemaining`
|
|
56
|
-
(default `0.01` = 1%), or
|
|
57
|
-
- the window resets within one minute (no point spending the tail).
|
|
58
|
-
|
|
59
|
-
When every account is below threshold, the request still goes out on the first
|
|
60
|
-
exhausted account — a refusal beats silence.
|
|
61
|
-
|
|
62
|
-
## Quota visibility
|
|
63
|
-
|
|
64
|
-
Vendors that report usage expose named windows (Claude `5h`/`7d apps`,
|
|
65
|
-
Codex primary/secondary, Grok credits). Each window renders as a progress bar
|
|
66
|
-
with percent; colors follow theme variables (normal / warning ≥70% / exhausted
|
|
67
|
-
100%). The last known snapshot persists across harness restarts and refreshes
|
|
68
|
-
on the next successful request.
|
|
69
|
-
|
|
70
|
-
The plugin also estimates remaining requests for the primary window
|
|
71
|
-
(`≈ N (5h)`) once enough request history exists — hidden when data is thin.
|
|
72
|
-
|
|
73
|
-
### Limit notifications
|
|
74
|
-
|
|
75
|
-
When a window crosses 70%, 90%, or 100%, the plugin logs a warning and emits a
|
|
76
|
-
`subscriptions.limit-notice` event (provider, ref, window id, usedPercent,
|
|
77
|
-
threshold). Each threshold fires once per window until it resets. Toggle with
|
|
78
|
-
`notifyLimits` in Config.
|
|
79
|
-
|
|
80
|
-
## Background maintenance
|
|
81
|
-
|
|
82
|
-
Two timers run while the plugin is loaded:
|
|
83
|
-
|
|
84
|
-
- **Token refresh ahead**: expiring tokens are refreshed before they are needed
|
|
85
|
-
(`refreshAheadMs`, default 5 min). Failures back off via `refreshRetryMs`
|
|
86
|
-
(default 10 min) and mark the card as *reconnect required*.
|
|
87
|
-
- **Health probe loop**: every `probeIntervalMin` minutes (default 15, 0
|
|
88
|
-
disables) each connected account gets a cheap vendor check. Dead accounts
|
|
89
|
-
surface in the card; probes never set cooldown.
|
|
90
|
-
|
|
91
|
-
Both timers clean up on plugin dispose.
|
|
92
|
-
|
|
93
|
-
## HTTP proxy
|
|
67
|
+
---
|
|
94
68
|
|
|
95
|
-
|
|
96
|
-
API on behalf of a logged-in account — through the same allowlist, rotation,
|
|
97
|
-
and quota accounting as model traffic. Same-origin only; no token ever appears
|
|
98
|
-
in a response or log. Paths outside the per-provider allowlist get 403.
|
|
69
|
+
## ✨ Key Features & Capabilities
|
|
99
70
|
|
|
100
|
-
|
|
71
|
+
### 1. 🌐 4 Supported Built-in Subscription Vendors
|
|
101
72
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
73
|
+
| Vendor Key | Subscription Tier | Protocol & Features |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| `codex` | ChatGPT Plus / Pro | Codex streaming responses, tool calling & image drawing (`/backend-api/codex/...`) |
|
|
76
|
+
| `claude` | Claude Pro / Max | Native Claude Messages protocol, usage tracking (`/v1/messages`, `/api/oauth/...`) |
|
|
77
|
+
| `grok` | xAI / X Premium | Real-time reasoning responses, billing checks & social search |
|
|
78
|
+
| `antigravity` | Google Cloud Code Assist | Antigravity engine (`/v1/loadCodeAssist`, `/v1/streamGenerateContent`) |
|
|
79
|
+
|
|
80
|
+
*Custom vendors can also be dynamically registered via the `createVendorFromProfile` factory.*
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
### 2. 🔄 Multi-Account Rotation & Rate-Limit Mitigation (`rotate.js`, `ratelimit.js`)
|
|
85
|
+
* **Multi-Account Pooling**: Attach multiple accounts per vendor (e.g. `CODEX_OAUTH_1`, `CODEX_OAUTH_2`, `CODEX_OAUTH_3`).
|
|
86
|
+
* **Automatic 429 Failover**: When an account encounters a rate limit (`HTTP 429`, `RATE_LIMIT`, `QUOTA_EXCEEDED`), traffic instantly fails over to the next healthy account in the pool.
|
|
87
|
+
* **Preemptive Quota Switching (`switchAtRemaining`)**: Automatically rotates to the next account before hitting zero if the rate-limit window reset is imminent.
|
|
88
|
+
* **Dynamic Cooldown Calculation**: Parses upstream headers (`Retry-After`, `x-ratelimit-reset`, ISO dates, epoch timestamps) and auto-restores cooled-down accounts when their window resets.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
### 3. 🔒 Zero-Leak Credential Security & Headless OAuth
|
|
93
|
+
* **Zero Token Leakage**: OAuth tokens are **never** returned over HTTP API endpoints or rendered in the Web UI. The UI only receives masked account labels, connection health, and quota bars.
|
|
94
|
+
* **Secure Host Storage**: Tokens reside in encrypted `$DSH_HOME/.credentials.yaml` managed by the host credentials service.
|
|
95
|
+
* **Headless / Remote Login Fallback**: If running DSH on a headless server over SSH where browser popups cannot redirect to `localhost`, simply paste the redirected callback URL or authorization code directly into the account card.
|
|
96
|
+
* **Proactive Background Token Refresh**: Access tokens are refreshed automatically before expiration.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
### 4. 🧩 In-Process Cordis Service (`ctx.subscriptions`)
|
|
101
|
+
Sibling plugins can tap into subscription capabilities directly in memory via Cordis:
|
|
102
|
+
```javascript
|
|
103
|
+
// Example in dsh-image-gen or custom plugins:
|
|
104
|
+
const res = await ctx.subscriptions.request('codex', '/backend-api/codex/images/generations', {
|
|
105
|
+
method: 'POST',
|
|
106
|
+
body: JSON.stringify({ prompt: 'Cyberpunk landscape', size: '1024x1024' }),
|
|
107
|
+
})
|
|
105
108
|
```
|
|
109
|
+
* **Zero Overhead**: Eliminates intermediate HTTP loops and keeps auth tokens strictly in-memory.
|
|
110
|
+
* **Strict Path Allowlist (`ALLOWLIST`)**: Restricts calls to verified vendor endpoints, preventing SSRF vulnerabilities.
|
|
106
111
|
|
|
107
|
-
|
|
112
|
+
---
|
|
108
113
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
# export (returns an encrypted DSHE1 payload)
|
|
114
|
-
curl -X POST https://<host>:3080/dsh-subscriptions/export \
|
|
115
|
-
-H 'Content-Type: application/json' -d '{"passphrase":"strong-passphrase"}'
|
|
116
|
-
|
|
117
|
-
# import
|
|
118
|
-
curl -X POST https://<host>:3080/dsh-subscriptions/import \
|
|
119
|
-
-H 'Content-Type: application/json' \
|
|
120
|
-
-d '{"passphrase":"strong-passphrase","payload":"DSHE1:…"}'
|
|
121
|
-
```
|
|
114
|
+
### 5. 🔐 Login Without a Browser: Loopback & Device Code (`v0.4.9`)
|
|
115
|
+
* **Automatic Loopback Callback (`autoLoopback`, on by default)**: For vendors whose OAuth redirect URI is a loopback address (Codex `:1455`, Grok `:56121`), the plugin spins up a temporary local HTTP server and catches the callback by itself — no URL pasting needed. The paste fallback always stays available.
|
|
116
|
+
* **Device Code Login (Codex)**: On fully headless machines (no browser on any reachable host), use the **Device login** button in the Codex account card. The plugin requests a short user code from `auth.openai.com`, you open `https://auth.openai.com/codex/device` on any device, enter the code, and the plugin completes the standard PKCE exchange automatically.
|
|
117
|
+
* **Classic Fallbacks Intact**: Web-origin redirect (`useWebCallback`) and manual paste of the redirected URL / authorization code remain available for custom OAuth clients.
|
|
122
118
|
|
|
123
|
-
|
|
124
|
-
(
|
|
125
|
-
a
|
|
119
|
+
### 6. 🌍 Per-Account HTTP/SOCKS Proxy (`v0.4.9`)
|
|
120
|
+
* **Individual Proxy per Account (`proxyUrl`)**: Every account slot accepts its own proxy URL (`http://`, `https://`, `socks5://[user:pass@]host:port`). All requests for that account — OAuth token refresh, vendor checks, model requests — are routed through it. Empty = direct connection.
|
|
121
|
+
* **One-Click Proxy Check**: The account card has a **Check proxy** button: it performs a real request to the vendor base URL through the configured proxy and shows the round-trip latency or the failure reason.
|
|
122
|
+
* **Request History Timings**: Every recorded request now carries its duration (`ms`) in the history store, so you can compare direct vs proxied latency over time.
|
|
126
123
|
|
|
127
|
-
|
|
124
|
+
### 7. 🕶️ Privacy Masking & Diagnostics Report (`v0.4.9`)
|
|
125
|
+
* **Privacy Masking (`privacyMask`)**: One toggle in the settings card masks personal data across the whole UI: emails render as `j***n@example.com` everywhere (account lists, status labels, check results). Designed for screen sharing and streaming. Server-side masking means labels never leak through API responses either; the underlying account data is never overwritten.
|
|
126
|
+
* **Anonymized Diagnostics Report**: The settings card has a **Generate diagnostics report** block: one click fetches an anonymized report (plugin/runtime versions, OS, per-vendor health counters, aggregate HTTP status counts, last ≥400 errors with timings, non-secret settings) and copies it to the clipboard. Tokens, emails, credential refs and proxy URLs are strictly excluded (verified by tests).
|
|
127
|
+
* **Issue-Ready**: The same block links to the project issue tracker, so a bug report is: generate → paste → submit.
|
|
128
128
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
### 8. 🔌 HTTP API (added in `v0.4.9`)
|
|
130
|
+
| Route | Method | Purpose |
|
|
131
|
+
|---|---|---|
|
|
132
|
+
| `/dsh-subscriptions/diagnostics` | GET | Anonymized diagnostics report (no secrets, no tokens, no proxy URLs) |
|
|
133
|
+
| `/dsh-subscriptions/proxy-check` | POST | Latency check of a slot's proxy against its vendor base URL |
|
|
134
|
+
| `/dsh-subscriptions/oauth/device/start` | POST | Begin Codex device-code login (returns user code + verification URL) |
|
|
135
|
+
| `/dsh-subscriptions/oauth/device/poll` | POST | Poll device-code authorization status |
|
|
132
136
|
|
|
133
|
-
|
|
137
|
+
---
|
|
134
138
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
139
|
+
### 9. 🦛 Local Ollama Gateway & Seamless Fallback (`v0.4.17`)
|
|
140
|
+
* **Native Provider (`ollama`)**: When a local Ollama is reachable at `ollamaBaseUrl` (default `http://127.0.0.1:11434`), it appears in the native DSH model picker with the models discovered from `/api/tags`. No API key needed.
|
|
141
|
+
* **Seamless Quota Fallback (`ollamaFallback`, on by default)**: When every account of a provider is exhausted (or unreachable) and nothing has been streamed yet, the chat continues on a local model (`ollamaFallbackModel`, or the first model from `/api/tags`). The fallback is logged and recorded in request history as `kind: fallback`.
|
|
142
|
+
* **Free ($0) Emergency Path**: Works with no internet and no quota — ideal for offline demos.
|
|
138
143
|
|
|
139
|
-
|
|
144
|
+
### 10. ⚡ Reasoning Effort, Verbosity & Fast Mode (`v0.4.17`)
|
|
145
|
+
* **Reasoning Effort**: Codex models advertise their supported effort levels from the live catalog; the native picker validates and the chosen effort is transmitted as `reasoning.effort` in the Codex `/responses` protocol. Grok forwards effort with its own catalog-aware filtering.
|
|
146
|
+
* **Verbosity (`codexVerbosity`)**: `low` / `medium` / `high` is sent as `text.verbosity` for Codex reasoning models. Empty = protocol default.
|
|
147
|
+
* **Fast Mode (`codexFastMode`)**: Sends `service_tier: priority` (1.5x speed billing tier) with every Codex request. The active-subscription chip shows a `⚡` prefix while enabled.
|
|
140
148
|
|
|
141
|
-
|
|
142
|
-
|
|
149
|
+
### 11. 🚦 Family-Scoped Cooldowns & Model Filtering (`v0.4.17`)
|
|
150
|
+
* **Reasoning vs Standard**: A 429 on a reasoning model (claude `*thinking*`, grok `*reasoning*`, all codex models) cools down only the reasoning family of that account — standard models on the same account keep working immediately. Legacy cooldowns (from older versions) still block the whole account until expiry.
|
|
151
|
+
* **Hide Deprecated Models (`hideDeprecatedModels`)**: Filters `test`/`preview`/`dev`/`alpha`/`beta`/`legacy` model ids out of the native picker (applies to live catalogs and the static fallback).
|
|
143
152
|
|
|
144
|
-
|
|
153
|
+
---
|
|
145
154
|
|
|
146
|
-
|
|
155
|
+
## 📦 Quick Installation
|
|
147
156
|
|
|
157
|
+
```bash
|
|
158
|
+
dsh plugin --profile web add @goodandready/dsh-subscriptions
|
|
148
159
|
```
|
|
149
|
-
/login <provider> # start OAuth for codex|claude|grok|antigravity (opens the vendor page)
|
|
150
|
-
/login status # insert the connected providers into the composer
|
|
151
|
-
/logout <provider> # disconnect that provider
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
## /subscriptions page
|
|
155
|
-
|
|
156
|
-
A localhost-only summary page at `/dsh-subscriptions/subscriptions` lists every account slot,
|
|
157
|
-
connection status, usage percent, remaining quota and reset time across all
|
|
158
|
-
providers. Requests from non-loopback hosts get 403.
|
|
159
|
-
|
|
160
|
-
## Import a token directly
|
|
161
|
-
|
|
162
|
-
In any account card, paste an existing refresh token (or API key) and click
|
|
163
|
-
**Import token** to sign in without the browser OAuth round trip. The token is
|
|
164
|
-
written straight to the host credentials store via
|
|
165
|
-
`POST /dsh-subscriptions/import-token { provider, index, refreshToken }`.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
## Credential names
|
|
169
160
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
161
|
+
> [!IMPORTANT]
|
|
162
|
+
> Restart DSH Web UI after installation (`systemctl --user restart dsh-web`) and navigate to **Settings → Subscriptions** to link your accounts.
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## ⚙️ Configuration Reference (`settings.yaml`)
|
|
167
|
+
|
|
168
|
+
```yaml
|
|
169
|
+
dsh-subscriptions:
|
|
170
|
+
switchAtRemaining: 1
|
|
171
|
+
cooldownMs: 60000
|
|
172
|
+
autoLoopback: true # v0.4.9: catch loopback OAuth callbacks automatically
|
|
173
|
+
privacyMask: false # v0.4.9: mask emails and account identifiers in the UI
|
|
174
|
+
ollamaBaseUrl: http://127.0.0.1:11434 # v0.4.17: local Ollama gateway
|
|
175
|
+
ollamaFallback: true # v0.4.17: seamless fallback when all accounts are exhausted
|
|
176
|
+
ollamaFallbackModel: '' # v0.4.17: e.g. qwen2.5-coder; empty = first model from /api/tags
|
|
177
|
+
hideDeprecatedModels: false # v0.4.17: filter test/preview/beta/legacy model ids
|
|
178
|
+
codexVerbosity: '' # v0.4.17: low | medium | high (text.verbosity)
|
|
179
|
+
codexFastMode: false # v0.4.17: service_tier priority (1.5x speed tier)
|
|
180
|
+
# Per-slot fields (v0.4.9): expiresAt (ms epoch), proxyUrl (http/https/socks5://)
|
|
181
|
+
accounts:
|
|
182
|
+
codex:
|
|
183
|
+
- ref: CODEX_OAUTH_1
|
|
184
|
+
label: "Work Pro Account"
|
|
185
|
+
- ref: CODEX_OAUTH_2
|
|
186
|
+
label: "Personal Plus Account"
|
|
187
|
+
claude:
|
|
188
|
+
- ref: CLAUDE_OAUTH_1
|
|
189
|
+
label: "Claude Max"
|
|
190
|
+
grok:
|
|
191
|
+
- ref: GROK_OAUTH_1
|
|
192
|
+
label: "X Premium"
|
|
175
193
|
```
|
|
176
194
|
|
|
177
|
-
|
|
178
|
-
Settings GET never returns access or refresh tokens — only
|
|
179
|
-
`{ configured, label, usagePercent, cooldownUntil, ref }`.
|
|
180
|
-
|
|
181
|
-
## Providers
|
|
182
|
-
|
|
183
|
-
| Key | Subscription | Default OAuth client |
|
|
184
|
-
|---|---|---|
|
|
185
|
-
| `codex` | ChatGPT / Codex | Vendor-public Codex CLI client id |
|
|
186
|
-
| `claude` | Claude Pro/Max | Vendor-public Claude Code client id |
|
|
187
|
-
| `grok` | xAI / SuperGrok | Vendor-public Grok CLI client id |
|
|
188
|
-
| `antigravity` | Google Antigravity | Your OAuth client id + secret in Config |
|
|
189
|
-
|
|
190
|
-
Override `codexClientId`, `claudeClientId`, and the other empty Config fields
|
|
191
|
-
if you register your own OAuth app.
|
|
192
|
-
|
|
193
|
-
Live requests use the vendor subscription surfaces, not API-key hosts:
|
|
194
|
-
Codex `chatgpt.com/backend-api/codex/responses`, Claude Messages with the
|
|
195
|
-
OAuth beta header, Grok `cli-chat-proxy.grok.com` with CLI identity headers,
|
|
196
|
-
and Antigravity Cloud Code Assist (`loadCodeAssist` then
|
|
197
|
-
`streamGenerateContent`). Usage endpoints, when they answer, feed the skip
|
|
198
|
-
logic above. If a live model list fails, the built-in catalog is used.
|
|
199
|
-
|
|
200
|
-
Antigravity uses a confidential Google OAuth client: set `antigravityClientId`
|
|
201
|
-
and `antigravityClientSecret` in plugin Config (Settings) — nothing is baked
|
|
202
|
-
into the repository.
|
|
203
|
-
|
|
204
|
-
Default model catalogs are built-in lists you can replace with
|
|
205
|
-
`codexModels`, `claudeModels`, `grokModels`, `antigravityModels`
|
|
206
|
-
in the plugin Config.
|
|
207
|
-
|
|
208
|
-
### Config reference
|
|
209
|
-
|
|
210
|
-
| Key | Default | Meaning |
|
|
211
|
-
|---|---|---|
|
|
212
|
-
| `cooldownMs` | 1800000 | Cooldown after RATE_LIMIT/QUOTA/429 |
|
|
213
|
-
| `switchAtRemaining` | 0.01 | Skip account when remaining ≤ this (fraction <1 or absolute ≥1); 0 disables |
|
|
214
|
-
| `refreshAheadMs` | 300000 | Refresh tokens expiring within this window |
|
|
215
|
-
| `refreshRetryMs` | 600000 | Backoff after a failed background refresh |
|
|
216
|
-
| `probeIntervalMin` | 15 | Account health-check interval, minutes; 0 disables |
|
|
217
|
-
| `notifyLimits` | true | Emit notices when usage crosses 70/90/100% |
|
|
218
|
-
| `useWebCallback` | false | Use Web UI origin as OAuth redirect_uri |
|
|
219
|
-
| `<vendor>Models` | built-in | Replace default model catalog per vendor |
|
|
220
|
-
|
|
221
|
-
## Identity
|
|
222
|
-
|
|
223
|
-
These three names must match:
|
|
224
|
-
|
|
225
|
-
| Place | Value |
|
|
226
|
-
|---|---|
|
|
227
|
-
| `package.json` `name` | `@goodandready/dsh-subscriptions` |
|
|
228
|
-
| `cordis.patch.yml` `name:` | `@goodandready/dsh-subscriptions` |
|
|
229
|
-
| `lib/client.js` loader `id` | `@goodandready/dsh-subscriptions` |
|
|
195
|
+
---
|
|
230
196
|
|
|
231
|
-
## License
|
|
197
|
+
## 📄 License
|
|
232
198
|
|
|
233
|
-
MIT
|
|
199
|
+
MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
|
package/README.ru.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# 📦 @goodandready/dsh-subscriptions
|
|
2
|
+
|
|
3
|
+
<div align="center">
|
|
4
|
+
|
|
5
|
+
<h3>Мост персональных подписок на ИИ, ротация пула аккаунтов и безопасный OAuth без утечки токенов для DeepSeek Harness</h3>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://www.npmjs.com/package/@goodandready/dsh-subscriptions"><img src="https://img.shields.io/npm/v/@goodandready/dsh-subscriptions.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
|
|
9
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-10b981.svg?style=for-the-badge&color=10b981&labelColor=064e3b" alt="license"></a>
|
|
10
|
+
<a href="https://github.com/topics/dsh-plugin"><img src="https://img.shields.io/badge/DSH-Plugin-8b5cf6.svg?style=for-the-badge&labelColor=2e1065" alt="DSH Plugin"></a>
|
|
11
|
+
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/Node-20%2B-f59e0b.svg?style=for-the-badge&labelColor=451a03" alt="Node version"></a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
<a href="https://goodandready.app/"><img src="https://img.shields.io/badge/Все_проекты_автора-goodandready.app-ff4500.svg?style=for-the-badge&logo=rocket&logoColor=white&labelColor=1a1a2e" alt="Все проекты автора"></a>
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
<p align="center">
|
|
19
|
+
<a href="README.md"><b>🇬🇧 English</b></a> •
|
|
20
|
+
<a href="README.ru.md"><b>🇷🇺 Русский</b></a> •
|
|
21
|
+
<a href="README.zh.md"><b>🇨🇳 中文说明</b></a>
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## ⚡ Обзор
|
|
29
|
+
|
|
30
|
+
**`dsh-subscriptions`** подключает ваши платные персональные подписки на нейросети напрямую в **DeepSeek Harness** в качестве полноценных LLM-провайдеров.
|
|
31
|
+
|
|
32
|
+
Вместо поминутной оплаты дорогих API-ключей для повседневных задач агента, плагин позволяет авторизовать ваши существующие веб-подписки через безопасный протокол OAuth PKCE. Поддерживаются **пулы из нескольких аккаунтов** с автоматической ротацией при исчерпании лимитов, **упреждающее переключение квот** и **внутрипроцессный сервис Cordis (`ctx.subscriptions`)**, который питает соседние плагины (например, [`dsh-image-gen`](https://github.com/GooDAnDReaDY/dsh-image-gen) и [`dsh-grok-xsearch`](https://github.com/GooDAnDReaDY/dsh-grok-xsearch)) без утечки токенов в сеть.
|
|
33
|
+
|
|
34
|
+
```mermaid
|
|
35
|
+
graph LR
|
|
36
|
+
subgraph DSHCore [Сессия диалога DeepSeek Harness]
|
|
37
|
+
Agent[🤖 Выполнение задач агентом] --> Router{Маршрутизатор провайдеров}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
subgraph SubscriptionsCore [Ядро dsh-subscriptions]
|
|
41
|
+
Router --> Pool{Пул аккаунтов вендора}
|
|
42
|
+
Pool -->|Аккаунт 1| Acc1[👤 Основной аккаунт: Активен]
|
|
43
|
+
Pool -->|Аккаунт 2| Acc2[👤 Второй аккаунт: Ожидание]
|
|
44
|
+
Pool -->|Аккаунт 3| Acc3[👤 Запасной аккаунт: Cooldown]
|
|
45
|
+
Acc1 -->|HTTP 429 / Превышение квоты| Rotate[Умный ротатор квот и задержек]
|
|
46
|
+
Rotate -->|Перенаправление трафика| Acc2
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
subgraph VendorBridges [4 Адаптера вендоров]
|
|
50
|
+
Acc1 --> B1[Бэкенд ChatGPT / Codex]
|
|
51
|
+
Acc1 --> B2[Протокол Claude Pro / Max]
|
|
52
|
+
Acc1 --> B3[Подписки xAI / Grok]
|
|
53
|
+
Acc1 --> B4[Google Cloud Code Assist / Antigravity]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
subgraph EcosystemBridge [Сервис Cordis: ctx.subscriptions]
|
|
57
|
+
Pool --> ImgGen[dsh-image-gen: Бесплатная генерация картинок]
|
|
58
|
+
Pool --> XSearch[dsh-grok-xsearch: Поиск в X Twitter]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
style DSHCore fill:#1e1e2e,stroke:#89b4fa,stroke-width:2px,color:#cdd6f4
|
|
62
|
+
style SubscriptionsCore fill:#181825,stroke:#cba6f7,stroke-width:2px,color:#cdd6f4
|
|
63
|
+
style VendorBridges fill:#11111b,stroke:#a6e3a1,stroke-width:2px,color:#cdd6f4
|
|
64
|
+
style EcosystemBridge fill:#181825,stroke:#f38ba8,stroke-width:2px,color:#cdd6f4
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## ✨ Ключевые возможности
|
|
70
|
+
|
|
71
|
+
### 1. 🌐 4 Поддерживаемых встроенных вендора подписок
|
|
72
|
+
|
|
73
|
+
| Ключ вендора | Тариф подписки | Протокол и возможности |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| `codex` | ChatGPT Plus / Pro | Стриминг Codex, вызов инструментов и генерация картинок (`/backend-api/codex/...`) |
|
|
76
|
+
| `claude` | Claude Pro / Max | Нативный протокол Claude Messages, трекинг расхода (`/v1/messages`, `/api/oauth/...`) |
|
|
77
|
+
| `grok` | xAI / X Premium | Ответы с рассуждениями, проверка баланса и поиск в соцсети |
|
|
78
|
+
| `antigravity` | Google Cloud Code Assist | Движок Antigravity (`/v1/loadCodeAssist`, `/v1/streamGenerateContent`) |
|
|
79
|
+
|
|
80
|
+
*Также поддерживается регистрация кастомных вендоров через фабрику профилей `createVendorFromProfile`.*
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
### 2. 🔄 Ротация аккаунтов и защита от лимитов (`rotate.js`, `ratelimit.js`)
|
|
85
|
+
* **Пулы из нескольких аккаунтов**: привязка нескольких аккаунтов на вендора (`CODEX_OAUTH_1`, `CODEX_OAUTH_2`...).
|
|
86
|
+
* **Автоматическое переключение при 429**: при превышении лимита запросов трафик мгновенно переключается на следующий свободный аккаунт.
|
|
87
|
+
* **Упреждающее переключение квот (`switchAtRemaining`)**: смена аккаунта до падения в ошибку, если окно сброса близко.
|
|
88
|
+
* **Динамический расчёт Cooldown**: парсинг заголовков `Retry-After`, `x-ratelimit-reset`, дат ISO и миллисекундных меток с автоматическим возвратом остывших аккаунтов в строй.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
### 3. 🔒 Безопасность и авторизация на headless-серверах
|
|
93
|
+
* **Нулевая утечка токенов**: токен OAuth **никогда** не отдаётся в браузер или через публичный HTTP API. В интерфейсе видны только статус, имя и остаток квоты.
|
|
94
|
+
* **Хранилище хоста**: токены шифруются в `$DSH_HOME/.credentials.yaml`.
|
|
95
|
+
* **Вход без браузера (SSH / Remote)**: если сервер запущен удалённо, авторизацию можно завершить, просто вставив итоговый redirect URL или authorization code в карточку аккаунта.
|
|
96
|
+
* **Фоновое продление токенов**: плагин автоматически обновляет токены до истечения их срока действия.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
### 4. 🧩 Внутрипроцессный сервис Cordis (`ctx.subscriptions`)
|
|
101
|
+
Другие плагины могут использовать подключенные подписки напрямую в памяти:
|
|
102
|
+
```javascript
|
|
103
|
+
// Пример вызова из dsh-image-gen:
|
|
104
|
+
const res = await ctx.subscriptions.request('codex', '/backend-api/codex/images/generations', {
|
|
105
|
+
method: 'POST',
|
|
106
|
+
body: JSON.stringify({ prompt: 'Cyberpunk landscape', size: '1024x1024' }),
|
|
107
|
+
})
|
|
108
|
+
```
|
|
109
|
+
* **Нулевой оверхед**: прямое общение без лишних HTTP-запросов.
|
|
110
|
+
* **Белый список путей (`ALLOWLIST`)**: строгий контроль вызываемых эндпоинтов для защиты от SSRF.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
### 5. 🔐 Вход без браузера: loopback и device-код (`v0.4.9`)
|
|
115
|
+
* **Автоматический loopback-callback (`autoLoopback`, включён по умолчанию)**: если redirect_uri вендора — loopback-адрес (Codex `:1455`, Grok `:56121`), плагин сам поднимает временный локальный HTTP-сервер и ловит callback без ручной вставки ссылки. Ручная вставка остаётся как запасной путь.
|
|
116
|
+
* **Вход по коду устройства (Codex)**: на полностью headless-машинах нажмите **Device login** в карточке аккаунта Codex: плагин запросит короткий код на `auth.openai.com`, вы открываете `https://auth.openai.com/codex/device` с любого устройства, вводите код — плагин сам завершает стандартный PKCE-обмен.
|
|
117
|
+
* **Классические варианты на месте**: redirect на origin веб-интерфейса (`useWebCallback`) и ручная вставка адреса/кода доступны как раньше.
|
|
118
|
+
|
|
119
|
+
### 6. 🌍 Индивидуальный HTTP/SOCKS-прокси на аккаунт (`v0.4.9`)
|
|
120
|
+
* **Прокси на слот (`proxyUrl`)**: каждый аккаунт принимает свой адрес `http://`, `https://`, `socks5://[user:pass@]host:port`. Через него идут все запросы аккаунта — обновление токенов, проверки вендора, запросы моделей. Пусто = прямое соединение.
|
|
121
|
+
* **Проверка в один клик**: кнопка **Check proxy** в карточке аккаунта делает реальный запрос к базовому URL вендора через прокси и показывает задержку или причину отказа.
|
|
122
|
+
* **Тайминги в истории**: каждая запись истории запросов теперь содержит длительность (`ms`) — удобно сравнивать прямое соединение и прокси.
|
|
123
|
+
|
|
124
|
+
### 7. 🕶️ Режим приватности и диагностический отчёт (`v0.4.9`)
|
|
125
|
+
* **Маскирование (`privacyMask`)**: один тумблер в карточке настроек скрывает персональные данные во всём интерфейсе: email отображается как `j***n@example.com` (списки аккаунтов, статусы, результаты проверок). Маскирование выполняется на сервере — личные данные не утекут и через ответы API; сами данные аккаунтов при этом не перезаписываются. Задумано для демонстраций экрана и стримов.
|
|
126
|
+
* **Анонимизированный диагностический отчёт**: блок **Generate diagnostics report**: один клик — отчёт (версии плагина/рантайма, ОС, счётчики здоровья по вендорам, агрегаты HTTP-статусов, последние ошибки ≥400 с таймингами, не-секретные настройки) скачан и скопирован в буфер. Токены, email, имена учётных записей и адреса прокси исключены (покрыто тестами).
|
|
127
|
+
* **Готово для issue**: рядом ссылка на трекер задач — баг-репорт это «сгенерировать → вставить → отправить».
|
|
128
|
+
|
|
129
|
+
### 8. 🔌 HTTP API (добавлено в `v0.4.9`)
|
|
130
|
+
| Маршрут | Метод | Назначение |
|
|
131
|
+
|---|---|---|
|
|
132
|
+
| `/dsh-subscriptions/diagnostics` | GET | Анонимизированный диагностический отчёт (без секретов, токенов и адресов прокси) |
|
|
133
|
+
| `/dsh-subscriptions/proxy-check` | POST | Проверка задержки прокси слота через базовый URL вендора |
|
|
134
|
+
| `/dsh-subscriptions/oauth/device/start` | POST | Начать вход Codex по коду устройства (возвращает код и адрес подтверждения) |
|
|
135
|
+
| `/dsh-subscriptions/oauth/device/poll` | POST | Опрос статуса авторизации по коду устройства |
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
### 9. 🦛 Локальный шлюз Ollama и бесшовный фолбэк (`v0.4.17`)
|
|
140
|
+
* **Нативный провайдер (`ollama`)**: если локальный Ollama доступен по `ollamaBaseUrl` (по умолчанию `http://127.0.0.1:11434`), он появляется в нативном селекторе моделей DSH с моделями из `/api/tags`. Ключ не нужен.
|
|
141
|
+
* **Бесшовный фолбэк (`ollamaFallback`, включён по умолчанию)**: когда все аккаунты провайдера исчерпаны (или недоступны) и ещё ничего не выведено, чат продолжается на локальной модели (`ollamaFallbackModel`, либо первая из `/api/tags`). Фолбэк пишется в журнал и в историю запросов как `kind: fallback`.
|
|
142
|
+
* **Бесплатный ($0) аварийный путь**: работает без интернета и без квот — удобно для офлайн-демо.
|
|
143
|
+
|
|
144
|
+
### 10. ⚡ Effort, Verbosity и Fast Mode (`v0.4.17`)
|
|
145
|
+
* **Уровень рассуждений (Effort)**: модели Codex объявляют поддерживаемые уровни из живого каталога; нативный селектор валидирует выбор, и выбранный уровень передаётся как `reasoning.effort` в протокол `/responses`. Grok передаёт effort со своей каталог-осознанной фильтрацией.
|
|
146
|
+
* **Детализация (`codexVerbosity`)**: `low` / `medium` / `high` передаётся как `text.verbosity` для reasoning-моделей Codex. Пусто = дефолт протокола.
|
|
147
|
+
* **Fast Mode (`codexFastMode`)**: c каждым Codex-запросом передаётся `service_tier: priority` (платный скоростной тир 1.5x). В чипе активной подписки при включённом режиме отображается `⚡`.
|
|
148
|
+
|
|
149
|
+
### 11. 🚦 Кулдауны по семействам и фильтрация моделей (`v0.4.17`)
|
|
150
|
+
* **Reasoning vs Standard**: 429 на reasoning-модели (claude `*thinking*`, grok `*reasoning*`, все codex-модели) охлаждает только семейство reasoning этого аккаунта — standard-модели того же аккаунта продолжают работать. Legacy-кулдауны (от старых версий) по-прежнему блокируют весь аккаунт до истечения.
|
|
151
|
+
* **Скрыть устаревшие модели (`hideDeprecatedModels`)**: убирает из нативного селектора id с `test`/`preview`/`dev`/`alpha`/`beta`/`legacy` (действует и на живые каталоги, и на статичный фолбэк).
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## 📦 Быстрая установка
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
dsh plugin --profile web add @goodandready/dsh-subscriptions
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## ⚙️ Пример конфигурации (`settings.yaml`)
|
|
164
|
+
|
|
165
|
+
```yaml
|
|
166
|
+
dsh-subscriptions:
|
|
167
|
+
switchAtRemaining: 1
|
|
168
|
+
cooldownMs: 60000
|
|
169
|
+
autoLoopback: true # v0.4.9: автоматически ловить loopback-callback
|
|
170
|
+
privacyMask: false # v0.4.9: маскировать email и учётные записи в UI
|
|
171
|
+
ollamaBaseUrl: http://127.0.0.1:11434 # v0.4.17: локальный шлюз Ollama
|
|
172
|
+
ollamaFallback: true # v0.4.17: бесшовный фолбэк при исчерпании всех аккаунтов
|
|
173
|
+
ollamaFallbackModel: '' # v0.4.17: например qwen2.5-coder; пусто = первая модель из /api/tags
|
|
174
|
+
hideDeprecatedModels: false # v0.4.17: фильтровать test/preview/beta/legacy id моделей
|
|
175
|
+
codexVerbosity: '' # v0.4.17: low | medium | high (text.verbosity)
|
|
176
|
+
codexFastMode: false # v0.4.17: service_tier priority (скоростной тир 1.5x)
|
|
177
|
+
# Поля слота (v0.4.9): expiresAt (ms), proxyUrl (http/https/socks5://)
|
|
178
|
+
accounts:
|
|
179
|
+
codex:
|
|
180
|
+
- ref: CODEX_OAUTH_1
|
|
181
|
+
label: "Рабочий Pro-аккаунт"
|
|
182
|
+
- ref: CODEX_OAUTH_2
|
|
183
|
+
label: "Личный Plus-аккаунт"
|
|
184
|
+
claude:
|
|
185
|
+
- ref: CLAUDE_OAUTH_1
|
|
186
|
+
label: "Claude Max"
|
|
187
|
+
grok:
|
|
188
|
+
- ref: GROK_OAUTH_1
|
|
189
|
+
label: "X Premium"
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## 📄 Лицензия
|
|
195
|
+
|
|
196
|
+
MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
|