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 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
- # Temporary Holding Version
1
+ # magpie-plugin-msmoli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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