magpie-plugin-msmoli 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +509 -2
- package/README.zh-CN.md +358 -0
- package/bin/sync.mjs +1128 -0
- package/index.mjs +1440 -0
- package/package.json +45 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MisonL
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,510 @@
|
|
|
1
|
-
#
|
|
1
|
+
# magpie-plugin-msmoli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A [magpie](https://github.com/yetone/magpie) plugin that claims ModelScope's
|
|
4
|
+
(魔搭) daily **魔粒** credits for you — one check-in a day instead of opening the
|
|
5
|
+
site — and shows the balance and today's spend on the account's usage card.
|
|
6
|
+
|
|
7
|
+
## What 魔粒 is
|
|
8
|
+
|
|
9
|
+
ModelScope's API-Inference is free, paid in 魔粒:
|
|
10
|
+
|
|
11
|
+
| Grant | Amount | Expires |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| Sign-in, per day | 200 | 24 hours |
|
|
14
|
+
| Aliyun account bound, per day | 50 | 24 hours |
|
|
15
|
+
| Email / profile / homepage completed | 50 each | 90 days, once |
|
|
16
|
+
|
|
17
|
+
Daily grants are **24 hours only**. They do not accumulate: what you don't
|
|
18
|
+
spend today is gone. A flagship model costs 2 魔粒 per call (mainstream 1,
|
|
19
|
+
lightweight 0.5), so the daily 250 is roughly 125 calls.
|
|
20
|
+
|
|
21
|
+
## Purpose, responsibility, and risk
|
|
22
|
+
|
|
23
|
+
**What it is for.** A personal utility: it claims the daily 魔粒 grant of the
|
|
24
|
+
ModelScope (魔搭) account *you* sign in, and does nothing else. It is not an
|
|
25
|
+
official ModelScope product; it is not affiliated with ModelScope, Alibaba or
|
|
26
|
+
magpie, and it collects nothing about you — the token stays in magpie's own
|
|
27
|
+
`plugin-auth.json`, and every request goes to the site the account signed in
|
|
28
|
+
to.
|
|
29
|
+
|
|
30
|
+
**The risk you are taking.** ModelScope has no check-in endpoint. The daily
|
|
31
|
+
grant exists for a *real* website sign-in, and this plugin obtains it by
|
|
32
|
+
calling `login/info` once a day with the browser's session cookie — a script
|
|
33
|
+
posing as the browser, not a button the official client offers. ModelScope's
|
|
34
|
+
user agreement forbids plugins that interfere with the service (4.1.2(1)) and
|
|
35
|
+
"刷分" with third-party tools (4.1.2(5)), and lets the platform freeze a
|
|
36
|
+
user's benefits or blacklist them for abuse (2.3.1 and the 魔粒 rules). An
|
|
37
|
+
account is real-name verified and often tied to Aliyun, so what is at stake is
|
|
38
|
+
the account, not the 250 credits that expire the same day. No account has been
|
|
39
|
+
reported frozen for running this, and no one can promise one never will be —
|
|
40
|
+
whether the service's detection would ever notice is not something this
|
|
41
|
+
project can answer. Whether to run it is a decision about your own account.
|
|
42
|
+
|
|
43
|
+
**Responsibility.** You are responsible for complying with ModelScope's terms
|
|
44
|
+
when you use this. The software is provided under the MIT licence, without
|
|
45
|
+
warranty of any kind; the maintainer is not liable for a suspended or frozen
|
|
46
|
+
account, lost credits, or anything else that follows from running it. See
|
|
47
|
+
[LICENSE](LICENSE).
|
|
48
|
+
|
|
49
|
+
**Not in the magpie market, and it will not be submitted.** magpie's community
|
|
50
|
+
market declined to list this package for exactly the reason above
|
|
51
|
+
([plugins#714](https://github.com/magpie-community/plugins/pull/714)), and the
|
|
52
|
+
project has decided not to submit it again. It stays installable by name from
|
|
53
|
+
npm.
|
|
54
|
+
|
|
55
|
+
## Install
|
|
56
|
+
|
|
57
|
+
Needs magpie itself (<https://usemagpie.ai>). On macOS, use `magpie --version` to check it's on PATH; if not, use the app's full path:
|
|
58
|
+
`/Applications/magpie.app/Contents/MacOS/magpie`.
|
|
59
|
+
|
|
60
|
+
### Install from npm
|
|
61
|
+
|
|
62
|
+
The package is on npm as `magpie-plugin-msmoli` (latest 0.1.0). Installs into
|
|
63
|
+
magpie's own plugin folder, and `magpie plugin update` follows the versions.
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
magpie plugin add magpie-plugin-msmoli
|
|
67
|
+
printf 'ms-your-token\n' | magpie plugin login msmoli
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Install via GitHub URL (no clone, auto-updates)
|
|
71
|
+
|
|
72
|
+
This installs into magpie's own plugin folder (`~/.config/magpie/plugins` for a regular install, or `data/plugins` for the portable version), so it updates with `magpie plugin update`.
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
magpie plugin add github:MisonL/magpie-plugin-msmoli
|
|
76
|
+
printf 'ms-your-token\n' | magpie plugin login msmoli
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The token is stored in `plugin-auth.json`; one login works regardless of the form you used to install.
|
|
80
|
+
|
|
81
|
+
### Install from a clone
|
|
82
|
+
|
|
83
|
+
Keeps your own copy, which is what you want if you plan to change it. magpie
|
|
84
|
+
references the folder where it lies rather than copying it, so it loads only
|
|
85
|
+
while that folder is there, and an update means pulling and adding it again.
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
git clone https://github.com/MisonL/magpie-plugin-msmoli.git
|
|
89
|
+
cd magpie-plugin-msmoli
|
|
90
|
+
magpie plugin add "$PWD"
|
|
91
|
+
printf 'ms-your-token\n' | magpie plugin login msmoli
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Either form ends the same way: a `msmoli` row under Settings → Usage → Plugins.
|
|
95
|
+
The sign-in is kept per provider id (`msmoli`) rather than per folder, so
|
|
96
|
+
switching between the two forms (or moving the clone) keeps the token — no
|
|
97
|
+
second login.
|
|
98
|
+
|
|
99
|
+
### International site (modelscope.ai)
|
|
100
|
+
|
|
101
|
+
The plugin now provides a second provider for the international site:
|
|
102
|
+
`msmoli-intl` (ModelScope 魔粒(国际版)). The same `ms-` token format, the same
|
|
103
|
+
daily grant rules, **but tokens are site-specific** — a token from `modelscope.cn`
|
|
104
|
+
answers 401 on `modelscope.ai`, and vice versa.
|
|
105
|
+
|
|
106
|
+
The sign-in and check-in work the same way:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
printf 'ms-your-intl-token\n' | magpie plugin login msmoli-intl
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The check-in window (`msmoli.json`) is shared between both providers — it is
|
|
113
|
+
a personal preference, not a per-site setting. To turn on the international
|
|
114
|
+
check-in, set `msmoli-intl` in your plugin-checkins instead of or alongside
|
|
115
|
+
`msmoli` (see "Turn the check-in on" below, substituting the provider id).
|
|
116
|
+
|
|
117
|
+
### Already have ModelScope set up in magpie? Sync that key to the plugin
|
|
118
|
+
|
|
119
|
+
If you added ModelScope earlier just for chat, its key is already in
|
|
120
|
+
`providers.json`. This package ships a small CLI that logs the same token into
|
|
121
|
+
the plugin, so you don't have to find it again.
|
|
122
|
+
|
|
123
|
+
It syncs the domestic site only, because the built-in preset it reads is the
|
|
124
|
+
`modelscope.cn` one. The international site has no preset; sign `msmoli-intl`
|
|
125
|
+
in by hand (`magpie plugin login msmoli-intl`).
|
|
126
|
+
|
|
127
|
+
Installed from GitHub, magpie keeps its own copy in its plugin folder:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
node ~/.config/magpie/plugins/node_modules/magpie-plugin-msmoli/bin/sync.mjs --status # see both sides
|
|
131
|
+
node ~/.config/magpie/plugins/node_modules/magpie-plugin-msmoli/bin/sync.mjs --to-plugin # sync
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Installed from a clone, run the file in the clone — magpie references that
|
|
135
|
+
folder and copies nothing (`./bin/sync.mjs`; on Windows or WSL, where the
|
|
136
|
+
file may not carry an executable bit, run it as `node bin/sync.mjs`). Portable
|
|
137
|
+
installs keep the same layout under their `data` folder; if detection misses,
|
|
138
|
+
`--dir <magpie's config folder>` and `--magpie <path to the magpie binary>` say
|
|
139
|
+
where things are: for a portable install that is its `data` folder, not the
|
|
140
|
+
folder the executable sits in. `--magpie` takes a path, absolute or relative —
|
|
141
|
+
a bare `magpie` is refused, because that one is resolved through `PATH` and the
|
|
142
|
+
first `magpie` on it would be handed the token. `npx magpie-plugin-msmoli` runs
|
|
143
|
+
the CLI from npm (published as 0.1.0) — npm 12 blocks `npx github:…` by
|
|
144
|
+
default, so don't start from that.
|
|
145
|
+
|
|
146
|
+
`--dir` says where to *read*; magpie's CLI has no such flag, so a write always
|
|
147
|
+
lands in the binary's own folder. Given both and they disagree, the tool stops
|
|
148
|
+
before writing anything rather than reading one magpie and writing another.
|
|
149
|
+
"Disagree" means the folders are different places, not that the strings differ:
|
|
150
|
+
a shell completes a directory with a trailing slash, `/private/tmp` and `/tmp`
|
|
151
|
+
are one folder on a Mac, and `data/.` is `data` — each names the binary's own
|
|
152
|
+
folder and is allowed.
|
|
153
|
+
|
|
154
|
+
It reads magpie's config but never writes it: the writes are magpie's own
|
|
155
|
+
(`magpie plugin login`, `magpie plugin logout`, `magpie provider key`), and the
|
|
156
|
+
token is masked in everything it prints, failures included. When the plugin
|
|
157
|
+
already uses the same key it changes nothing; when the two keys differ it
|
|
158
|
+
refuses, because a second `login` would add a second account — pass `--force`
|
|
159
|
+
to replace a single existing account. `--to-preset` goes the other way (needs
|
|
160
|
+
`--force`: that key is what chat uses). If more than one provider could be the
|
|
161
|
+
built-in side (say, a relay of the same host), the tool refuses to guess and
|
|
162
|
+
`--from <id>` picks one; `--dry-run` prints the commands without running them.
|
|
163
|
+
A value flag given twice (`--dir a --dir b`) is refused, not silently last-wins,
|
|
164
|
+
and `--dir=<folder>` is read as well. `--from` and a stray argument are echoed
|
|
165
|
+
back masked when the value looks like a key — a token pasted into the wrong
|
|
166
|
+
position is never printed whole, whatever its shape. That covers the shapes
|
|
167
|
+
`ms-…` does not: a GitHub or Slack token, an AWS secret (which carries `/`), a
|
|
168
|
+
UUID, a hex digest, a base64 body — long, or short enough that only its trailing
|
|
169
|
+
`=` (or a `+` beside a case mix) gives it away. The judgement runs
|
|
170
|
+
word by word,
|
|
171
|
+
so a key with a space or a newline pasted onto it — the shape a copy out of a
|
|
172
|
+
terminal or a chat window leaves — or quoted inside a line is still the key it
|
|
173
|
+
is. A word that is one key is masked whole, ends and all, including one split with
|
|
174
|
+
separators — a hex digest with dashes, a key written as `AbCdEfGh.IjKlMnOp…` —
|
|
175
|
+
and an AWS secret, whose two parts of 12+ characters with mixed case mark it. An
|
|
176
|
+
invisible character dropped inside a key (a zero-width space or `\x01`, the
|
|
177
|
+
shape a copy out of a chat window leaves) does not save it either: the
|
|
178
|
+
control/format characters sitting between key-alphabet characters are removed
|
|
179
|
+
before masking, so the two halves are rejoined into the one key they are —
|
|
180
|
+
each half alone falls under the length floor and would have leaked. A
|
|
181
|
+
word that starts like a path (`/`, `~`, `.`, a drive letter, a UNC root) keeps
|
|
182
|
+
its shape, and only the key-like run inside it is masked, so `--dir
|
|
183
|
+
/tmp/ghp_16C7…` still names the folder it read; a key pasted in *behind* a path
|
|
184
|
+
prefix (`~/keys/sk-ant-…`) reads as part of that path, which is what keeping a
|
|
185
|
+
path's shape costs. The folder is reported
|
|
186
|
+
the same way on every line that names it, and magpie's own output goes through
|
|
187
|
+
the same door — a key of another provider in the child's error message is
|
|
188
|
+
masked there too, not only the `ms-` shape. A provider id and an account name
|
|
189
|
+
are read out of magpie's own files and go through the same door: a key pasted
|
|
190
|
+
into either field is masked on every line the tool names it.
|
|
191
|
+
|
|
192
|
+
Turn the check-in on: Settings → Usage → Plugins → the `msmoli` row, or in
|
|
193
|
+
`settings.json` (in magpie's own folder, `~/.config/magpie` for an installed
|
|
194
|
+
magpie):
|
|
195
|
+
|
|
196
|
+
```json
|
|
197
|
+
{
|
|
198
|
+
"pluginCheckins": {
|
|
199
|
+
"msmoli": true
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
The token comes from <https://modelscope.cn/my/myaccesstoken>. The same `ms-`
|
|
205
|
+
token is both the inference `Authorization: Bearer` and the web session cookie,
|
|
206
|
+
so nothing else needs configuring. For the international provider
|
|
207
|
+
(`msmoli-intl`), the token comes from <https://modelscope.ai/my/myaccesstoken>
|
|
208
|
+
instead — the two sites' tokens do not interchange.
|
|
209
|
+
|
|
210
|
+
From then on magpie claims it once a Beijing day per signed-in account, retries
|
|
211
|
+
later that day if it fails, and reports the outcome on the account's usage card:
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
● msmoli API key …a1b2 · 魔搭社区 魔粒 1% (今日用 2 / 领 250 · 余额 249.5) ↻14h24m tomorrow 09:31 249.5 魔粒可用 left
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## How the check-in works
|
|
218
|
+
|
|
219
|
+
ModelScope publishes no check-in endpoint. What it does have is a side effect:
|
|
220
|
+
`GET /api/v1/users/login/info` counts as a sign-in, and the first one of the
|
|
221
|
+
day grants `daily_active` 200 and `aliyun_bindlogin` 50. Repeat it the same day
|
|
222
|
+
and nothing more is granted.
|
|
223
|
+
|
|
224
|
+
A hook that just calls it can't tell "claimed now" from "claimed hours ago", so
|
|
225
|
+
this one reads the day's ledger around it:
|
|
226
|
+
|
|
227
|
+
1. read today's `EARN` records — is `daily_active` there?
|
|
228
|
+
2. touch `login/info`
|
|
229
|
+
3. read the ledger again
|
|
230
|
+
|
|
231
|
+
A `claimed` needs a grant the ledger did not already show around the touch. If
|
|
232
|
+
the touch went through and the ledger still shows nothing, the plugin asks
|
|
233
|
+
magpie to look again half an hour later (`until`), once — from Beijing noon on
|
|
234
|
+
magpie would otherwise settle `inactive` for the day, and a grant written a few
|
|
235
|
+
seconds late was recorded as "the account may not be in the campaign". Before
|
|
236
|
+
noon magpie's own provisional re-ask (every 30 minutes) already covers it.
|
|
237
|
+
|
|
238
|
+
Four outcomes follow:
|
|
239
|
+
|
|
240
|
+
| Outcome | Meaning |
|
|
241
|
+
|---|---|
|
|
242
|
+
| `done` | already claimed today, with the day's total |
|
|
243
|
+
| `claimed` | this run claimed it, with the amount — a grant the ledger did not already show, so a row that was there before the touch is never counted as this run's |
|
|
244
|
+
| `inactive` | the sign-in went through but nothing was granted (from noon on, asked once more in 30 minutes before it stands for the day) |
|
|
245
|
+
| `failed` | nothing was claimed — including "not yet time", explained below |
|
|
246
|
+
|
|
247
|
+
The endpoints it uses, all on `modelscope.cn` with `Cookie: m_session_id=ms-…`
|
|
248
|
+
(a browser UA and a `Referer` are enough — no CSRF token):
|
|
249
|
+
|
|
250
|
+
| Purpose | Path |
|
|
251
|
+
|---|---|
|
|
252
|
+
| Sign-in / check-in trigger | `GET /api/v1/users/login/info` |
|
|
253
|
+
| Balance | `GET /openapi/v1/magicubes/balance` |
|
|
254
|
+
| Ledger | `GET /openapi/v1/magicubes/transactions?page=1&page_size=50` |
|
|
255
|
+
|
|
256
|
+
`type` in the ledger has been seen as `EARN` and `SPEND_CONFIRM`; there is
|
|
257
|
+
also an `EXPIRE`, which this plugin ignores. Rows are aggregated per day, type,
|
|
258
|
+
`rule_key` and `model_tier` — a day's spending shows one row per model tier it
|
|
259
|
+
used — with a `count` on each, and the list is sorted newest first. The ordering
|
|
260
|
+
is what makes the single page of 50 enough: today's rows come before any older
|
|
261
|
+
one. Measured on a live account that is 3–7 rows a day, so a page reaches back
|
|
262
|
+
about a fortnight rather than 50 days; what the read would not survive is a
|
|
263
|
+
single day with more than 50 rows. Asking for a larger page does not help —
|
|
264
|
+
the server answers `page_size=200` with 50.
|
|
265
|
+
|
|
266
|
+
### When it claims: the check-in window
|
|
267
|
+
|
|
268
|
+
The claim is held back until a time of day, by default somewhere in
|
|
269
|
+
**08:30–10:00 Beijing time** — an account's own minute in that window,
|
|
270
|
+
picked from a hash of the day and the account, so it doesn't move between
|
|
271
|
+
the host's retries. It makes the traffic look like a person opening the
|
|
272
|
+
site in the morning rather than at the stroke of midnight.
|
|
273
|
+
|
|
274
|
+
Waiting costs nothing: daily grants **expire 24 hours after they land**
|
|
275
|
+
(a grant at 15:35 expires at 15:35 the next day), so claiming at 9am leaves
|
|
276
|
+
the credits good until 9am tomorrow.
|
|
277
|
+
|
|
278
|
+
Set it in `msmoli.json` in magpie's own folder (`~/.config/magpie` for an
|
|
279
|
+
installed magpie, the data folder for a portable one):
|
|
280
|
+
|
|
281
|
+
```json
|
|
282
|
+
{ "checkinWindow": ["08:30", "10:00"] }
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
One string works the same way: `{ "checkinWindow": "08:30-10:00" }`.
|
|
286
|
+
|
|
287
|
+
`false` turns the window off (claim whenever magpie asks); so does `"off"`.
|
|
288
|
+
An unusable value — `08:30`–`08:00`, a date that isn't a clock time — falls
|
|
289
|
+
back to the default rather than never claiming. An end past **23:00** is
|
|
290
|
+
capped there: the host does not ask at the target's minute — its own look runs
|
|
291
|
+
every 30 minutes on a phase of its own, so the first ask past a target can come
|
|
292
|
+
up to half an hour late, and the retry after a failure half an hour after that.
|
|
293
|
+
A target needs both to still land on the same day. Capping at 23:00 leaves any
|
|
294
|
+
target that room; later would leave some with one ask, or none. A window that sits entirely past 23:00 falls back to the
|
|
295
|
+
default window too, since there would be nowhere left to put the target. The
|
|
296
|
+
file is read on every ask, so a change takes effect without restarting
|
|
297
|
+
magpie.
|
|
298
|
+
|
|
299
|
+
How it fits magpie's schedule: magpie asks once when the Beijing day turns,
|
|
300
|
+
then every 30 minutes — always after a `failed`, and also after a
|
|
301
|
+
`done`/`inactive`/`ineligible` it hasn't seen a claim for yet, up to noon
|
|
302
|
+
Beijing (a provisional answer: the vendor's day may not have turned; from noon
|
|
303
|
+
on the plugin's own `until` buys the same re-ask when the ledger lagged). So
|
|
304
|
+
before the window's moment the plugin answers `failed` with the time it will
|
|
305
|
+
run, and sends **no request at all**. `failed` is still the one outcome that
|
|
306
|
+
guarantees a re-ask; the others only get one while the day is unproven.
|
|
307
|
+
|
|
308
|
+
What that costs, since a plugin has no other way to defer: until the claim
|
|
309
|
+
actually runs the account card shows a red dot and magpie's own "check-in
|
|
310
|
+
failed; magpie tries again later" wording, each of those asks rewrites
|
|
311
|
+
magpie's `plugin-checkin.json`, and `magpie accounts checkin` run in that
|
|
312
|
+
period exits non-zero. Turning the window off removes all three.
|
|
313
|
+
|
|
314
|
+
How long that is depends on when magpie next asks, not on the target alone.
|
|
315
|
+
Measured on a real day: the target was 09:01, magpie asked at 09:00:57 (three
|
|
316
|
+
seconds early, so `failed`), and the claim landed on the next ask at 09:31 —
|
|
317
|
+
about 9.5 hours from midnight, rather than the 8.5 the window's 08:30 start
|
|
318
|
+
alone suggests. That is also why the card names the cadence instead of a
|
|
319
|
+
countdown: the host keeps the line until its next ask, half an hour later, so
|
|
320
|
+
"约 8 分钟后" would be wrong for most of the time it is on screen.
|
|
321
|
+
|
|
322
|
+
### Upstream
|
|
323
|
+
|
|
324
|
+
magpie gives a plugin no timer, no clock, and no say in when it is called:
|
|
325
|
+
`auth.checkin(getAuth, info)` returns an outcome and the host decides when
|
|
326
|
+
to ask again. Everything above is worked around within that contract, and
|
|
327
|
+
the awkward parts (proving "not yet" as a failure, whole-day granularity for
|
|
328
|
+
the account card) want a line in
|
|
329
|
+
[this issue](https://github.com/yetone/magpie/issues/3245) proposing timers and
|
|
330
|
+
`recheck` for plugins.
|
|
331
|
+
|
|
332
|
+
Two consequences worth knowing, both from the same missing timer: a window's
|
|
333
|
+
target is only reached by the host's own asks, so one that falls while the
|
|
334
|
+
machine is asleep is claimed on the next ask after it wakes (the day is lost
|
|
335
|
+
only if that happens past midnight); and the plugin cannot retry on its own
|
|
336
|
+
between asks — the window's cap (above) is what buys the retry.
|
|
337
|
+
|
|
338
|
+
### More than one account
|
|
339
|
+
|
|
340
|
+
The plugin is written per account, not per token. magpie keeps each signed-in
|
|
341
|
+
token under its own key — the first as `msmoli`, later ones as `msmoli#xxxxxx`
|
|
342
|
+
— and asks the check-in once per account, handing each its own token through
|
|
343
|
+
`getAuth`. So with two tokens:
|
|
344
|
+
|
|
345
|
+
- each account gets **its own target minute** that day (the hash is over the
|
|
346
|
+
day and that account's token), so the two normally claim at different
|
|
347
|
+
minutes — a collision is possible, but only costs both a shared minute;
|
|
348
|
+
- each account's check-in is recorded, retried and shown separately in
|
|
349
|
+
`plugin-checkin.json` and on the card;
|
|
350
|
+
- the name read at check-in is cached per token, so one account's nickname
|
|
351
|
+
never appears on another's row.
|
|
352
|
+
|
|
353
|
+
Add a second one the same way, then pick which to use:
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
printf 'ms-the-second-token\n' | magpie plugin login msmoli
|
|
357
|
+
magpie accounts # both rows, each with its own 魔粒 window
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
The card names an account by the name ModelScope gives it. An api-type
|
|
361
|
+
sign-in has no such name, so the check-in writes the nickname back: the same
|
|
362
|
+
sign-in entry, the token still stored once, now with `accountId` beside it.
|
|
363
|
+
|
|
364
|
+
It happens on the first check-in that actually runs in the host process, so a
|
|
365
|
+
fresh install shows `API key …a1b2` until then. An account whose check-in is
|
|
366
|
+
already settled for the day is not asked again — magpie reuses the record — so
|
|
367
|
+
the name appears with the next day's check-in rather than on upgrade.
|
|
368
|
+
|
|
369
|
+
If you would rather choose the name yourself, magpie's own alias wins over it:
|
|
370
|
+
|
|
371
|
+
```bash
|
|
372
|
+
magpie accounts alias msmoli "API key …a1b2" my-main
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
The second word is the account as the card shows it *now*: before the first
|
|
376
|
+
check-in that is the key's tail, afterwards the ModelScope nickname. An account
|
|
377
|
+
you have named is left alone — the check-in won't write its nickname over your
|
|
378
|
+
alias — so either label works, and the alias outranks the nickname from then on.
|
|
379
|
+
|
|
380
|
+
And the daily grant belongs to the ModelScope account, not to the key: the
|
|
381
|
+
claim is idempotent server-side per account per day, so a second token for the
|
|
382
|
+
same ModelScope user gets `done` rather than another 250.
|
|
383
|
+
|
|
384
|
+
## Why its own provider, with no models
|
|
385
|
+
|
|
386
|
+
The plugin registers `msmoli` rather than the built-in `modelscope` preset, for
|
|
387
|
+
two reasons:
|
|
388
|
+
|
|
389
|
+
1. magpie namespaces a plugin id that collides with a preset into
|
|
390
|
+
`<id>-plugin`, so a plugin that named itself `modelscope` would not be the
|
|
391
|
+
same provider anyway — it would only sit next to the preset and invite people
|
|
392
|
+
to take one for the other.
|
|
393
|
+
2. **Safety.** magpie's gateway passes over an account whose usage window reads
|
|
394
|
+
as used up. If a check-in plugin served the provider that inference actually
|
|
395
|
+
routes through, reporting 100% would take the whole chat path out of magpie.
|
|
396
|
+
|
|
397
|
+
So chat keeps using the `modelscope` preset with your API key in
|
|
398
|
+
`providers.json`, and this plugin only carries the sign-in its check-in runs as.
|
|
399
|
+
|
|
400
|
+
### Two ModelScope rows, and why that is fine
|
|
401
|
+
|
|
402
|
+
The providers page then shows two: `ModelScope` (the preset, with your models)
|
|
403
|
+
and `ModelScope 魔粒` (this plugin, with none). They are separate providers that
|
|
404
|
+
happen to share one token, and they do not fight:
|
|
405
|
+
|
|
406
|
+
| | `modelscope` preset | `msmoli` (this plugin) |
|
|
407
|
+
|---|---|---|
|
|
408
|
+
| Serves chat models | yes | no — `magpie models` never lists it |
|
|
409
|
+
| Its usage card | none (the preset has no balance endpoint) | 魔粒 balance and today's spend |
|
|
410
|
+
| Check-in | not built in for ModelScope | this plugin, once a day |
|
|
411
|
+
| Reachable by the gateway | yes | no, so a card reading 100% cannot route around it |
|
|
412
|
+
|
|
413
|
+
The token being in both places is how magpie is built: a preset keeps its key
|
|
414
|
+
in `providers.json`, a plugin keeps its sign-in in `plugin-auth.json`. Entering
|
|
415
|
+
the same `ms-` token in both is expected. Nothing reads the plugin's usage card
|
|
416
|
+
to decide where chat goes — that is the point of the separate id.
|
|
417
|
+
|
|
418
|
+
The preset's empty card has one consequence worth knowing. ModelScope's
|
|
419
|
+
inference API has no balance endpoint, so magpie cannot see that the key's 魔粒
|
|
420
|
+
are gone; the gateway learns it from the vendor's own `429 insufficient balance`
|
|
421
|
+
and then rests the key for 30 minutes. Until then each request spends one failed
|
|
422
|
+
try on it first. The balance is on the account, and `modelscope.cn` does answer
|
|
423
|
+
it, so the preset can be told: in its editor, Balance URL
|
|
424
|
+
`https://modelscope.cn/openapi/v1/magicubes/balance` and Balance field
|
|
425
|
+
`data.available_balance` (the same token answers it — `magpie quota modelscope`
|
|
426
|
+
then reads a number instead of `no balance endpoint known for it`).
|
|
427
|
+
|
|
428
|
+
The one real overlap to know about: **both rows cost nothing extra**, but if you
|
|
429
|
+
remove the plugin's sign-in, chat is unaffected, and if you remove the preset,
|
|
430
|
+
the check-in keeps working. They fail independently.
|
|
431
|
+
|
|
432
|
+
If you sign in the international site as well, that adds one more row,
|
|
433
|
+
`msmoli-intl` (ModelScope 魔粒(国际版)). It has every property in the table
|
|
434
|
+
above — no models, its own card, its own check-in — and fails independently of
|
|
435
|
+
both the preset and the domestic plugin row.
|
|
436
|
+
|
|
437
|
+
## Privacy
|
|
438
|
+
|
|
439
|
+
`login/info` responds with `Email` and `GitlabAccessToken` among other fields.
|
|
440
|
+
This plugin reads `NickName` / `Name` and nothing else; the rest is dropped as
|
|
441
|
+
soon as the response is parsed. The token lives only in magpie's own
|
|
442
|
+
`plugin-auth.json` — the plugin never writes it anywhere, never logs it, never
|
|
443
|
+
sends it anywhere but the site the account was signed in for (`modelscope.cn`
|
|
444
|
+
for `msmoli`, `modelscope.ai` for `msmoli-intl`).
|
|
445
|
+
|
|
446
|
+
The one thing it writes is that nickname, as `accountId` on the sign-in it
|
|
447
|
+
already has, so magpie's card can name the account. It goes through the host's
|
|
448
|
+
own `client.auth.set`, which replaces the sign-in entry — so the plugin hands
|
|
449
|
+
back what the host gave it, with the name added, and the token is still
|
|
450
|
+
stored once.
|
|
451
|
+
|
|
452
|
+
It also reads `providers.json`, magpie's own list of providers, for one thing:
|
|
453
|
+
whether you already named this account yourself. If you have, it writes
|
|
454
|
+
nothing — your name stays, and the nickname is not put over it.
|
|
455
|
+
|
|
456
|
+
## Scope and limits
|
|
457
|
+
|
|
458
|
+
**ModelScope's terms and the risks of running this are stated above, in
|
|
459
|
+
[Purpose, responsibility, and risk](#purpose-responsibility-and-risk).**
|
|
460
|
+
|
|
461
|
+
- One account's own daily grant, idempotent: a check-in reads the ledger, touches
|
|
462
|
+
the sign-in endpoint, and reads the ledger again — three requests. A day already
|
|
463
|
+
claimed (a retry, a later ask) answers `done` in two; a touch the ledger has not
|
|
464
|
+
caught up with reads it once more, four. Once a day.
|
|
465
|
+
Asks that arrive before the window open send nothing at all.
|
|
466
|
+
The usage read makes two requests and never touches the sign-in endpoint, so the
|
|
467
|
+
menu bar's polling cannot turn into check-ins.
|
|
468
|
+
- Every request gives up after 8 seconds, and a response body is read to at most
|
|
469
|
+
256 KiB; neither can hold the host or grow without bound. The timeout covers the
|
|
470
|
+
body as well as the request: a proxy that stops mid-reply is cut at the same
|
|
471
|
+
8 seconds, and says so rather than passing the runtime's own word for it
|
|
472
|
+
through (`terminated` on Node, a sentence about `verbose: true` on Bun). A whole
|
|
473
|
+
check-in gives up after 25 seconds and a whole usage read after 18, because
|
|
474
|
+
magpie runs every provider's check-ins under one two-minute budget and asks
|
|
475
|
+
accounts one at a time — a hook cut short answers `failed`, which the host
|
|
476
|
+
retries in half an hour, and that retry reads the ledger before claiming, so
|
|
477
|
+
nothing is lost.
|
|
478
|
+
- **A body it does not understand fails; it never reads as zero.** No `data`, a
|
|
479
|
+
`data` that is a list rather than an object, a balance with no
|
|
480
|
+
`available_balance` or one that is not a number — each is a failure with the
|
|
481
|
+
reason, because "0 魔粒可用" is a definite answer that would be false. The same
|
|
482
|
+
goes for the server's own text: a message or a grant's title carrying a key —
|
|
483
|
+
this account's token, or one of another vendor's, which this plugin never held
|
|
484
|
+
and therefore cannot compare against — has it masked before the text reaches
|
|
485
|
+
the card or magpie's log. What counts as a key there is the same rule `--from`
|
|
486
|
+
uses, so a ledger title or a server message of ordinary words is untouched.
|
|
487
|
+
- `MAGPIE_MSMOLI_CONFIG` overrides the config file's path. It exists so the test
|
|
488
|
+
suite never touches a real file; it works in production too.
|
|
489
|
+
- The other 170 魔粒 a day come from liking 10 items (20), posting AIGC works
|
|
490
|
+
(100) and AIGC models (50). None of those have an API, and automating them
|
|
491
|
+
means driving the web UI, so this plugin does not touch them.
|
|
492
|
+
|
|
493
|
+
## Troubleshooting
|
|
494
|
+
|
|
495
|
+
| Symptom | Fix |
|
|
496
|
+
|---|---|
|
|
497
|
+
| `not signed in` | `printf 'ms-token\n' \| magpie plugin login msmoli` |
|
|
498
|
+
| No 魔粒 window on the card, or no check-in | restart magpie (the plugin host caches) |
|
|
499
|
+
| `failed` with HTTP 401 | the token was revoked, or it belongs to the other site — `modelscope.cn` and `modelscope.ai` do not share tokens. The card names which site refused (国内站 / 国际站). Make a new token on that site and sign in again |
|
|
500
|
+
| `failed` with HTTP 403 | ModelScope's risk control refused the request; the token is fine. It retries in 30 minutes |
|
|
501
|
+
| Before the window the card reads failed, with a line naming the claim time: `09:56 后自动领取(magpie 每半小时问一次,无需操作)` | expected — the card leads with when it will claim; see "When it claims" above |
|
|
502
|
+
| Always `inactive` | the sign-in works but nothing shows in today's ledger: the account may not be eligible, or the grant hasn't been written yet (a claim made elsewhere shows as `done`). Before Beijing noon magpie asks again every 30 minutes; from noon on the plugin asks once more itself, half an hour later ("半小时后再核对一次"), and only the second `inactive` stands for the day |
|
|
503
|
+
| Chat through the `modelscope` preset answers `429 insufficient balance` in the Routing log | the account's daily 250 魔粒 are spent — a flagship call costs 2, so ~125 calls run the day dry and the vendor refuses the rest. Not this plugin: it registers no models. If you want magpie to *see* the balance on the preset's card too, give the preset the same reading — Balance URL `https://modelscope.cn/openapi/v1/magicubes/balance`, Balance field `data.available_balance` in its editor (the same token answers it; read `1.50` on 2026-10-28). Whether the gateway then holds a drained key before spending a request on it is not verified — the safe path is still the 429's own 30-minute rest |
|
|
504
|
+
| Two accounts of one ModelScope nickname, and only one of them checks in | magpie before 2026-10-20 kept a plugin's check-in per display name rather than per sign-in, so namesakes collapsed into one. Update magpie: the fix landed upstream on 2026-10-20 (magpie-community/plugins#254) |
|
|
505
|
+
| Edits to this plugin do nothing | path plugins are referenced in place; restart magpie |
|
|
506
|
+
| Installed both ways by accident, and `magpie plugin list` says `msmoli` is "served by" one of them | that is magpie keeping two copies of one provider id. Remove one: `magpie plugin rm github:MisonL/magpie-plugin-msmoli` or `magpie plugin rm <path>`. magpie files your alias with the package it was set against, so removing a copy can take the display name with it — it comes back if you add that package again, or you set the alias once more |
|
|
507
|
+
|
|
508
|
+
## License
|
|
509
|
+
|
|
510
|
+
MIT
|