dsh-connect-minimaxcode 0.4.2 → 0.5.1

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/lib/client.js CHANGED
@@ -18,7 +18,15 @@
18
18
  const BUNDLE_NAME = 'dsh-connect-minimaxcode';
19
19
  const NAMESPACE = 'dsh-connect-minimaxcode';
20
20
  const STATUS_PATH = '/plugins/dsh-connect-minimaxcode/status';
21
+ const ACTION_PATH = '/plugins/dsh-connect-minimaxcode/action';
21
22
  const POLL_INTERVAL_MS = 20000;
23
+ /**
24
+ * The provider id the host side registers with `llm.registerAdapter`.
25
+ *
26
+ * Duplicated here on purpose: this half is a classic script that cannot
27
+ * import the host's module, and the id is the one the projection reports.
28
+ */
29
+ const MINIMAX_PROVIDER_ID = 'minimaxcode';
22
30
  const zh = {
23
31
  title: 'MiniMax Code 积分',
24
32
  balance: '积分 {total}',
@@ -36,9 +44,28 @@
36
44
  expiringIn: '{amount} 积分 · {date} 到期',
37
45
  expiringNone: '7 天内没有积分到期',
38
46
  expiringSoon: '30 天内到期 {amount} 积分',
39
- nextExpiry: '最近一笔到期',
47
+ nextExpiry: '最近到期日',
40
48
  lots: '到期批次',
41
49
  noLots: '没有待到期的积分批次',
50
+ loginTitle: '登录',
51
+ loginPlugin: '已在插件内登录',
52
+ loginDesktop: '使用 MiniMax Code 桌面 App 的登录态',
53
+ loginSignedOut: '未登录',
54
+ loginPending: '等待你在浏览器里确认',
55
+ loginCode: '设备码',
56
+ loginOpen: '打开授权页面',
57
+ loginPolled: '我已授权,检查一下',
58
+ loginStart: '登录 MiniMax Code',
59
+ signinTitle: '每日签到',
60
+ signinToday: '第 {day} 天 · {points} 积分',
61
+ signinClaimed: '今日已签到',
62
+ signinClaimable: '今日可签到',
63
+ signinUnavailable: '签到信息不可用',
64
+ signinCycle: '一轮共 {points} 积分',
65
+ signinLast: '上次签到 {day},{points} 积分',
66
+ signinAuto: '每天自动签到',
67
+ signinClaim: '签到',
68
+ claimableToday: '今日可签到',
42
69
  };
43
70
  const en = {
44
71
  title: 'MiniMax Code credits',
@@ -60,6 +87,25 @@
60
87
  nextExpiry: 'Next expiry',
61
88
  lots: 'Expiring grants',
62
89
  noLots: 'No grant is waiting to expire',
90
+ loginTitle: 'Sign-in',
91
+ loginPlugin: 'Signed in through the plugin',
92
+ loginDesktop: 'Using the MiniMax Code desktop app sign-in',
93
+ loginSignedOut: 'Not signed in',
94
+ loginPending: 'Waiting for you to confirm in the browser',
95
+ loginCode: 'Device code',
96
+ loginOpen: 'Open the authorization page',
97
+ loginPolled: 'I authorized it — check',
98
+ loginStart: 'Sign in to MiniMax Code',
99
+ signinTitle: 'Daily check-in',
100
+ signinToday: 'Day {day} · {points} credits',
101
+ signinClaimed: 'Claimed today',
102
+ signinClaimable: 'Claimable today',
103
+ signinUnavailable: 'Check-in information unavailable',
104
+ signinCycle: '{points} credits per cycle',
105
+ signinLast: 'Last claim on {day}, {points} credits',
106
+ signinAuto: 'Claim daily on its own',
107
+ signinClaim: 'Claim',
108
+ claimableToday: 'Claimable today',
63
109
  };
64
110
  const loader = globalThis.__ModuleLoader__;
65
111
  if (loader === undefined)
@@ -144,7 +190,7 @@
144
190
  function useStatus() {
145
191
  const [status, setStatus] = React.useState(undefined);
146
192
  React.useEffect(() => subscribe(setStatus), []);
147
- return status;
193
+ return [status, () => { void load(); }];
148
194
  }
149
195
  /** A row of label and value. */
150
196
  function row(label, value) {
@@ -162,9 +208,9 @@
162
208
  ? row(t('expiringSoon', { amount: credits(credits_.expiringSoon, 2) }), '')
163
209
  : null, next !== undefined ? row(t('nextExpiry'), day(next)) : null);
164
210
  }
165
- /** The pill in the composer's dock. */
211
+ /** The pill itself; the gate decides whether it is drawn at all. */
166
212
  function CreditPill(props) {
167
- const status = useStatus();
213
+ const [status] = useStatus();
168
214
  const [open, setOpen] = React.useState(false);
169
215
  const t = props.t ?? ((key, params) => {
170
216
  const template = zh[key] ?? key;
@@ -192,6 +238,107 @@
192
238
  onClick: () => setOpen(!open),
193
239
  }, label), open ? detail : null);
194
240
  }
241
+ /**
242
+ * The pill, only while this plugin's own provider is selected.
243
+ *
244
+ * The balance belongs to the provider the next request will actually
245
+ * use, so another connector's model means this balance is the wrong
246
+ * number to show: the gate hides the readout and — because the pill is
247
+ * the only subscriber — the polling stops with it. The projection comes
248
+ * from the host's own `modelSelection`, which is what the model picker
249
+ * writes, so the readout cannot disagree with the model in effect.
250
+ *
251
+ * A host without the projection, or a session where the picker has not
252
+ * chosen yet, keeps the pill: "I do not know" must not hide a balance
253
+ * that is true either way.
254
+ */
255
+ function CreditPillGate(props) {
256
+ const projection = props.useProjection?.('modelSelection');
257
+ const provider = (projection?.next ?? projection?.lastUsed)?.provider;
258
+ if (provider !== undefined && provider !== MINIMAX_PROVIDER_ID)
259
+ return null;
260
+ return h(CreditPill, props);
261
+ }
262
+ /** Ask the host to do something, and let the card read the result. */
263
+ async function act(action, body) {
264
+ try {
265
+ await fetch(`${ACTION_PATH}?action=${encodeURIComponent(action)}`, {
266
+ method: 'POST',
267
+ headers: { 'content-type': 'application/json' },
268
+ body: JSON.stringify(body ?? {}),
269
+ });
270
+ }
271
+ catch {
272
+ // The card shows the state the host reports; a failed request is
273
+ // reported there too.
274
+ }
275
+ // Read the state the host produced, so the card shows what happened.
276
+ void load();
277
+ }
278
+ /**
279
+ * The sign-in block.
280
+ *
281
+ * The plugin can sign in on its own: the person authorizes in their own
282
+ * browser, and the plugin never sees a password. When the desktop app
283
+ * is already signed in, that credential is used and this block says so.
284
+ */
285
+ function loginBlock(t, login, refresh) {
286
+ if (login === undefined)
287
+ return null;
288
+ if (login.state === 'signed-in') {
289
+ return h('div', { style: sectionBoxStyle, 'data-minimax-login': true }, row(t('loginTitle'), login.source === 'plugin' ? t('loginPlugin') : t('loginDesktop')));
290
+ }
291
+ if (login.state === 'pending') {
292
+ return h('div', { style: sectionBoxStyle, 'data-minimax-login': true }, row(t('loginTitle'), t('loginPending')), login.userCode !== undefined && login.userCode.length > 0
293
+ ? row(t('loginCode'), login.userCode)
294
+ : null, login.verificationUriComplete !== undefined && login.verificationUriComplete.length > 0
295
+ ? h('a', {
296
+ href: login.verificationUriComplete,
297
+ target: '_blank',
298
+ rel: 'noreferrer',
299
+ style: linkStyle,
300
+ }, t('loginOpen'))
301
+ : null, h('button', { type: 'button', style: actionButtonStyle, onClick: () => { void act('login-poll'); } }, t('loginPolled')));
302
+ }
303
+ return h('div', { style: sectionBoxStyle, 'data-minimax-login': true }, row(t('loginTitle'), t('loginSignedOut')), h('button', { type: 'button', style: actionButtonStyle, onClick: () => { void act('login-start'); } }, t('loginStart')));
304
+ }
305
+ /**
306
+ * The check-in block.
307
+ *
308
+ * The cycle is seven days; the card shows which day it is and what the
309
+ * day is worth, and lets the person claim it — or leave it to the
310
+ * plugin, once a day, and only when the server says the day can be
311
+ * claimed.
312
+ */
313
+ function signinBlock(t, signin, refresh) {
314
+ if (signin === undefined)
315
+ return null;
316
+ return h('div', { style: sectionBoxStyle, 'data-minimax-signin': true }, h('div', { style: sectionTitleStyle }, t('signinTitle')), signin.today === undefined
317
+ ? h('div', { style: mutedStyle }, t('signinUnavailable'))
318
+ : h('div', { style: rowStyle }, h('span', null, t('signinToday', { day: signin.today.dayNo, points: signin.today.points })), h('span', null, signin.today.claimed ? t('signinClaimed') : signin.today.claimable ? t('signinClaimable') : t('signinUnavailable'))), (signin.days ?? []).length === 0
319
+ ? null
320
+ : h('div', { style: dayRowStyle }, ...(signin.days ?? []).map(day_ => h('span', {
321
+ key: `day-${day_.dayNo}`,
322
+ title: `${day_.dayNo}: ${day_.points}`,
323
+ style: {
324
+ ...dayDotStyle,
325
+ ...day_.isToday ? dayTodayStyle : {},
326
+ ...day_.status === 3 ? dayClaimedStyle : {},
327
+ },
328
+ }, String(day_.dayNo)))), signin.cyclePoints !== undefined
329
+ ? h('div', { style: mutedStyle }, t('signinCycle', { points: signin.cyclePoints }))
330
+ : null, signin.lastClaimDay !== undefined
331
+ ? h('div', { style: mutedStyle }, t('signinLast', { day: signin.lastClaimDay, points: signin.lastClaimPoints ?? 0 }))
332
+ : null, h('div', { style: rowStyle }, h('label', { style: checkboxStyle }, h('input', {
333
+ type: 'checkbox',
334
+ checked: signin.autoCheckin === true,
335
+ onChange: (event) => {
336
+ void act('settings', { autoCheckin: event.target?.checked === true });
337
+ },
338
+ }), h('span', null, t('signinAuto'))), signin.today?.claimable === true
339
+ ? h('button', { type: 'button', style: actionButtonStyle, onClick: () => { void act('claim'); } }, t('signinClaim'))
340
+ : null), signin.error !== undefined ? h('div', { style: mutedStyle }, signin.error) : null);
341
+ }
195
342
  /**
196
343
  * The card on the plugin's own page.
197
344
  *
@@ -199,29 +346,31 @@
199
346
  * is the full card, where the expiry detail belongs.
200
347
  */
201
348
  function PluginPage(props) {
202
- const status = useStatus();
349
+ const [status, refresh] = useStatus();
203
350
  const t = props.t ?? ((key, params) => {
204
351
  const template = zh[key] ?? key;
205
352
  return template.replace(/\{(\w+)\}/g, (_match, name) => String(params?.[name] ?? ''));
206
353
  });
207
354
  const credits_ = status?.credits;
208
355
  const usage = status?.usage;
356
+ const signin = status?.signin;
357
+ const login = status?.login;
209
358
  const available = credits_?.state === 'ok' && credits_.total !== undefined;
210
359
  if (props.view === 'summary') {
211
360
  if (!available)
212
361
  return h('span', null, t('unavailable'));
213
362
  return h('span', { 'data-minimax-credits-summary': true }, t('balance', { total: credits(credits_.total, 2) }), credits_.expiring !== undefined && credits_.expiring > 0
214
363
  ? ` · ${t('expiringIn', { amount: credits(credits_.expiring, 2), date: day(credits_.nextExpiryAtMs) })}`
215
- : '');
364
+ : '', signin?.today?.claimable === true ? ` · ${t('claimableToday')}` : '');
216
365
  }
217
366
  if (!available) {
218
- return h('div', { style: cardStyle, 'data-minimax-credits-card': true }, h('div', { style: cardTitleStyle }, t('title')), h('div', { style: rowStyle }, h('span', null, t('unavailable')), h('span', null, credits_?.state === 'expired' ? t('expired') : '')));
367
+ return h('div', { style: cardStyle, 'data-minimax-credits-card': true }, h('div', { style: cardTitleStyle }, t('title')), h('div', { style: rowStyle }, h('span', null, t('unavailable')), h('span', null, credits_?.state === 'expired' ? t('expired') : '')), loginBlock(t, login, refresh));
219
368
  }
220
- return h('div', { style: cardStyle, 'data-minimax-credits-card': true }, h('div', { style: cardTitleStyle }, t('title')), h('div', { style: bigNumberStyle }, t('balance', { total: credits(credits_.total, 2) })), row(t('free'), credits(credits_.free, 3)), row(t('purchased'), credits(credits_.purchased, 3)), expiryBlock(t, credits_), h('div', { style: sectionTitleStyle }, t('lots')), (credits_.upcoming ?? []).length === 0
369
+ return h('div', { style: cardStyle, 'data-minimax-credits-card': true }, h('div', { style: cardTitleStyle }, t('title')), h('div', { style: bigNumberStyle }, t('balance', { total: credits(credits_.total, 2) })), row(t('free'), credits(credits_.free, 3)), row(t('purchased'), credits(credits_.purchased, 3)), expiryBlock(t, credits_), signinBlock(t, signin, refresh), h('div', { style: sectionTitleStyle }, t('lots')), (credits_.upcoming ?? []).length === 0
221
370
  ? h('div', { style: mutedStyle }, t('noLots'))
222
371
  : h('ul', { style: listStyle }, ...(credits_.upcoming ?? []).map((lot, index) => h('li', { key: `${lot.expireAtMs}-${index}`, style: rowStyle }, h('span', null, day(lot.expireAtMs)), h('span', null, credits(lot.amount, 2))))), usage !== undefined
223
372
  ? h('div', { style: usageBoxStyle }, row(t('tokens', { count: tokens(usage.inputTokens + usage.outputTokens + usage.cacheReadTokens) }), ''), row(t('cost', { amount: money(usage.estimatedCny) }), ''), usage.requests > 0 ? row(t('requests', { count: usage.requests }), usage.lastModel ?? '') : null)
224
- : null, credits_.updatedAtMs !== undefined
373
+ : null, loginBlock(t, login, refresh), credits_.updatedAtMs !== undefined
225
374
  ? h('div', { style: mutedStyle }, t('updated', { time: new Date(credits_.updatedAtMs).toLocaleTimeString() }))
226
375
  : null);
227
376
  }
@@ -283,6 +432,42 @@
283
432
  const sectionTitleStyle = { marginTop: '8px', fontWeight: 600 };
284
433
  const mutedStyle = { opacity: 0.7, fontSize: '12px' };
285
434
  const listStyle = { listStyle: 'none', margin: 0, padding: 0 };
435
+ const sectionBoxStyle = {
436
+ marginTop: '10px',
437
+ paddingTop: '8px',
438
+ borderTop: '1px solid var(--dsh-panel-border, currentColor)',
439
+ display: 'flex',
440
+ flexDirection: 'column',
441
+ gap: '6px',
442
+ };
443
+ const actionButtonStyle = {
444
+ appearance: 'none',
445
+ background: 'transparent',
446
+ border: '1px solid var(--dsh-panel-border, currentColor)',
447
+ borderRadius: '6px',
448
+ color: 'inherit',
449
+ cursor: 'pointer',
450
+ font: 'inherit',
451
+ fontSize: '12px',
452
+ padding: '2px 10px',
453
+ width: 'fit-content',
454
+ };
455
+ const linkStyle = { color: 'inherit', fontSize: '12px' };
456
+ const checkboxStyle = { display: 'inline-flex', alignItems: 'center', gap: '6px', fontSize: '12px' };
457
+ const dayRowStyle = { display: 'flex', gap: '6px', flexWrap: 'wrap' };
458
+ const dayDotStyle = {
459
+ display: 'inline-flex',
460
+ alignItems: 'center',
461
+ justifyContent: 'center',
462
+ width: '22px',
463
+ height: '22px',
464
+ borderRadius: '999px',
465
+ border: '1px solid var(--dsh-panel-border, currentColor)',
466
+ fontSize: '11px',
467
+ opacity: 0.6,
468
+ };
469
+ const dayTodayStyle = { opacity: 1, fontWeight: 600 };
470
+ const dayClaimedStyle = { background: 'var(--dsh-panel-border, currentColor)', opacity: 0.9 };
286
471
  const usageBoxStyle = {
287
472
  marginTop: '8px',
288
473
  paddingTop: '6px',
@@ -295,14 +480,17 @@
295
480
  const context = ctx;
296
481
  // Visible text goes through the host's locale service.
297
482
  context.effect(() => context.locale.register(NAMESPACE, { zh, en }), `${BUNDLE_NAME}: locale`);
298
- // The composer dock is the strip under the input box, which is where
299
- // the host itself renders its activity and usage pills.
300
- context.slots.inject('conversation.composer.dock', () => context.slots.register({
301
- name: 'conversation.composer.dock',
483
+ // The tool row to the left of the composer — beside the model
484
+ // picker — rather than the ambient strip below it: the balance
485
+ // belongs to the provider that is selected, so it sits where that
486
+ // selection is made, and the gate hides it when another connector's
487
+ // model is in effect.
488
+ context.slots.inject('conversation.input.left', () => context.slots.register({
489
+ name: 'conversation.input.left',
302
490
  id: 'minimax-credits',
303
- order: 2,
491
+ order: 4,
304
492
  locale: NAMESPACE,
305
- }, CreditPill));
493
+ }, CreditPillGate));
306
494
  // The plugin's own page, keyed by the package name, where the
307
495
  // balance and the expiry schedule are spelled out.
308
496
  context.slots.inject('plugins.bundle.config', () => context.slots.register({
package/lib/index.d.ts CHANGED
@@ -475,9 +475,306 @@ declare class UsageMeter {
475
475
  reset(): void;
476
476
  }
477
477
  //#endregion
478
+ //#region src/credentials.d.ts
479
+ /** The file the plugin's own credential lives in. */
480
+ declare const PLUGIN_CREDENTIAL_FILENAME = ".minimaxcode-auth.json";
481
+ /** The file the plugin's own settings and session live in. */
482
+ declare const PLUGIN_STATE_FILENAME = ".minimaxcode-state.json";
483
+ /** The credential the plugin obtained for itself. */
484
+ interface PluginCredential {
485
+ /** The document's shape. */
486
+ schemaVersion: number;
487
+ /** The OAuth client the token was issued to. */
488
+ clientId: string;
489
+ /** The scopes the token carries. */
490
+ scopes: string[];
491
+ /** The audience the token was issued for. */
492
+ audience: string;
493
+ /** The `mmoat_` bearer token. */
494
+ accessToken: string;
495
+ /** The refresh token, when the server issued one. */
496
+ refreshToken?: string;
497
+ /** Expiry in milliseconds since the epoch. */
498
+ expiresAtMs?: number;
499
+ /** When the token was obtained, in milliseconds since the epoch. */
500
+ obtainedAtMs?: number;
501
+ }
502
+ /** A device login waiting for the person to authorize it. */
503
+ interface DeviceLoginSession {
504
+ /** The device code the token endpoint is polled with. */
505
+ deviceCode: string;
506
+ /** The PKCE verifier that goes with the device code. */
507
+ codeVerifier: string;
508
+ /** The code the person types on the account page. */
509
+ userCode: string;
510
+ /** Where to authorize. */
511
+ verificationUri: string;
512
+ /** Where to authorize, with the code filled in. */
513
+ verificationUriComplete: string;
514
+ /** When the device code stops working, in milliseconds since the epoch. */
515
+ expiresAtMs: number;
516
+ }
517
+ /** The plugin's own settings. */
518
+ interface PluginState {
519
+ /** The document's shape. */
520
+ schemaVersion: number;
521
+ /** Whether the plugin claims the daily grant on its own. */
522
+ autoCheckin: boolean;
523
+ /** The day the last claim happened, in the account's time zone. */
524
+ lastClaimDay?: string;
525
+ /** When the last claim happened, in milliseconds since the epoch. */
526
+ lastClaimAtMs?: number;
527
+ /** The points the last claim granted. */
528
+ lastClaimPoints?: number;
529
+ /** A device login waiting to be authorized. */
530
+ deviceLogin?: DeviceLoginSession;
531
+ }
532
+ /**
533
+ * The Harness home.
534
+ *
535
+ * The host resolves it the same way: the `DSH_HOME` environment variable when
536
+ * it is set, otherwise `~/.dsh`. The plugin resolves it on its own rather
537
+ * than importing the host's helper, so it also works in a plain command line
538
+ * where the host's packages are not installed.
539
+ */
540
+ declare function dshHome(env?: NodeJS.ProcessEnv): string;
541
+ /** Where the plugin's credential lives. */
542
+ declare function pluginCredentialPath(env?: NodeJS.ProcessEnv): string;
543
+ /** Where the plugin's state lives. */
544
+ declare function pluginStatePath(env?: NodeJS.ProcessEnv): string;
545
+ /** Read the plugin's own credential. */
546
+ declare function readPluginCredential(env?: NodeJS.ProcessEnv): Promise<PluginCredential | undefined>;
547
+ /** Write the plugin's own credential. */
548
+ declare function writePluginCredential(credential: PluginCredential, env?: NodeJS.ProcessEnv): Promise<void>;
549
+ /** Read the plugin's own state. */
550
+ declare function readPluginState(env?: NodeJS.ProcessEnv): Promise<PluginState>;
551
+ /** Write the plugin's own state. */
552
+ declare function writePluginState(state: PluginState, env?: NodeJS.ProcessEnv): Promise<void>;
553
+ //#endregion
554
+ //#region src/login.d.ts
555
+ /** The endpoint that issues device codes. */
556
+ declare const DEVICE_CODE_PATH = "/oauth2/device/code";
557
+ /** The device authorization grant type. */
558
+ declare const DEVICE_CODE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
559
+ /** How a device login is going. */
560
+ type DeviceLoginState =
561
+ /** The person has not authorized it yet. */
562
+ {
563
+ state: 'pending';
564
+ } |
565
+ /** The authorization server asked the client to slow down. */
566
+ {
567
+ state: 'slow-down';
568
+ } |
569
+ /** The device code expired before the person authorized it. */
570
+ {
571
+ state: 'expired';
572
+ message?: string;
573
+ } |
574
+ /** The person declined. */
575
+ {
576
+ state: 'denied';
577
+ message?: string;
578
+ } |
579
+ /** The grant arrived. */
580
+ {
581
+ state: 'signed-in';
582
+ credential: PluginCredential;
583
+ };
584
+ /**
585
+ * Make a PKCE verifier and its S256 challenge.
586
+ *
587
+ * The verifier is 43 characters of base64url entropy, which is what RFC 7636
588
+ * asks for, and it never leaves this process.
589
+ */
590
+ declare function makePkce(): {
591
+ verifier: string;
592
+ challenge: string;
593
+ };
594
+ /**
595
+ * Ask for a device code.
596
+ *
597
+ * @returns The session the person authorizes, or `undefined` when the server
598
+ * would not issue one.
599
+ */
600
+ declare function startDeviceLogin(region: MinimaxRegion, fetchImpl?: typeof fetch, now?: number): Promise<DeviceLoginSession | undefined>;
601
+ /**
602
+ * Poll for the grant.
603
+ *
604
+ * The authorization server answers `400` while the person has not authorized
605
+ * yet, with `authorization_pending` in the body; that is a normal answer, not
606
+ * an error.
607
+ */
608
+ declare function pollDeviceLogin(session: DeviceLoginSession, region: MinimaxRegion, fetchImpl?: typeof fetch, now?: number): Promise<DeviceLoginState>;
609
+ /**
610
+ * Exchange the stored refresh token for a fresh access token.
611
+ *
612
+ * The authorization server rotates the refresh token, so the new pair
613
+ * replaces the old one wherever it is kept.
614
+ */
615
+ declare function refreshPluginCredential(credential: PluginCredential, region: MinimaxRegion, fetchImpl?: typeof fetch, now?: number): Promise<PluginCredential>;
616
+ //#endregion
617
+ //#region src/signin.d.ts
618
+ /** The claim endpoint. */
619
+ declare const SIGNIN_CLAIM_PATH = "/minimax-cloud/api/v1/signin/claim";
620
+ /** The cycle endpoint, which reports the panel. */
621
+ declare const SIGNIN_PANEL_PATH = "/minimax-cloud/api/v1/signin/status";
622
+ /** Per-day status, as the desktop app enumerates it. */
623
+ declare const SIGNIN_DAY_STATUS: {
624
+ /** The day has not come yet. */
625
+ readonly upcoming: 1;
626
+ /** The day is today and can be claimed. */
627
+ readonly claimable: 2;
628
+ /** The day is claimed. */
629
+ readonly claimed: 3;
630
+ /** The day does not grant anything. */
631
+ readonly disabled: 4;
632
+ };
633
+ /** The status of one day in the cycle. */
634
+ type SigninDayStatus = (typeof SIGNIN_DAY_STATUS)[keyof typeof SIGNIN_DAY_STATUS];
635
+ /** How many days a cycle runs. */
636
+ declare const SIGNIN_CYCLE_DAYS = 7;
637
+ /** The outcome of a claim. */
638
+ declare const SIGNIN_CLAIM_RESULT: {
639
+ /** The grant was credited. */
640
+ readonly credited: 1;
641
+ /** Today was already claimed; nothing was granted. */
642
+ readonly alreadyClaimed: 2;
643
+ };
644
+ /** The outcome of a claim. */
645
+ type SigninClaimResult = (typeof SIGNIN_CLAIM_RESULT)[keyof typeof SIGNIN_CLAIM_RESULT];
646
+ /** One day of the cycle. */
647
+ interface SigninDay {
648
+ /** Which day of the cycle this is, 1 to 7. */
649
+ dayNo: number;
650
+ /** Points the day grants. */
651
+ points: number;
652
+ /** Extra points on top, when the day carries a bonus. */
653
+ bonusPoints: number;
654
+ /** The day's status. */
655
+ status: SigninDayStatus;
656
+ /** Whether this is today. */
657
+ isToday: boolean;
658
+ }
659
+ /** The cycle as the server reports it. */
660
+ interface SigninPanel {
661
+ /** Which cycle the account is in. */
662
+ scene: number;
663
+ /** The days of the cycle. */
664
+ days: SigninDay[];
665
+ }
666
+ /** The result of claiming today's grant. */
667
+ interface SigninClaim {
668
+ /** The server's identifier for the claim. */
669
+ claimId: string;
670
+ /** Whether the grant was credited or the day was already claimed. */
671
+ result: SigninClaimResult;
672
+ /** Which day of the cycle was claimed. */
673
+ dayNo: number;
674
+ /** Points granted. */
675
+ points: number;
676
+ /** Extra points granted. */
677
+ bonusPoints: number;
678
+ /** When the granted points expire, in milliseconds since the epoch. */
679
+ expireAtMs: number;
680
+ /** The cycle after the claim. */
681
+ panel?: SigninPanel;
682
+ }
683
+ /**
684
+ * The time zone the server should treat the request as coming from.
685
+ *
686
+ * The server wants an IANA name; an offset or a missing value is refused.
687
+ * The system's own name is the right answer, since "today" should be the
688
+ * user's today.
689
+ */
690
+ declare function timezoneId(): string;
691
+ /**
692
+ * Read the cycle out of a panel response.
693
+ *
694
+ * The server marks exactly one day as today and at most one day as claimable;
695
+ * a panel that says otherwise is not something to act on, so it is refused
696
+ * rather than half-believed.
697
+ */
698
+ declare function parseSigninPanel(document: unknown): SigninPanel | undefined;
699
+ /** Read the result of a claim. */
700
+ declare function parseSigninClaim(document: unknown): SigninClaim | undefined;
701
+ /**
702
+ * Read the cycle.
703
+ *
704
+ * @returns The panel, or `undefined` when the account cannot read it.
705
+ */
706
+ declare function fetchSigninPanel(credential: OAuthCredential, region: MinimaxRegion, fetchImpl?: typeof fetch, timezone?: string): Promise<SigninPanel | undefined>;
707
+ /**
708
+ * Claim today's grant.
709
+ *
710
+ * This is the only request in the plugin that changes something upstream. It
711
+ * grants the day's points, which expire thirty days later.
712
+ *
713
+ * @returns What the server answered, or `undefined` when it refused.
714
+ */
715
+ declare function claimSignin(credential: OAuthCredential, region: MinimaxRegion, fetchImpl?: typeof fetch, timezone?: string): Promise<SigninClaim | undefined>;
716
+ /** The day's key in the account's own time zone, `YYYY-MM-DD`. */
717
+ declare function localDay(now?: number, timezone?: string): string;
718
+ //#endregion
478
719
  //#region src/status-route.d.ts
479
720
  /** Route this plugin mounts, matching its settings card. */
480
721
  declare const MINIMAX_STATUS_PATH = "/plugins/dsh-connect-minimaxcode/status";
722
+ /** Route the card posts to when it wants something done rather than read. */
723
+ declare const MINIMAX_ACTION_PATH = "/plugins/dsh-connect-minimaxcode/action";
724
+ /** The daily check-in, as the card reads it. */
725
+ interface SigninStatus {
726
+ /** Whether the cycle could be read. */
727
+ state: 'ok' | 'unavailable';
728
+ /** Today's day in the cycle. */
729
+ today?: {
730
+ /** Which day of the cycle it is, 1 to 7. */
731
+ dayNo: number;
732
+ /** Points the day grants. */
733
+ points: number;
734
+ /** The day's status, as the upstream numbers it. */
735
+ status: number;
736
+ /** Whether the day can be claimed. */
737
+ claimable: boolean;
738
+ /** Whether the day is already claimed. */
739
+ claimed: boolean;
740
+ };
741
+ /** The days of the cycle. */
742
+ days?: {
743
+ dayNo: number;
744
+ points: number;
745
+ status: number;
746
+ isToday: boolean;
747
+ }[];
748
+ /** Points the whole cycle is worth. */
749
+ cyclePoints?: number;
750
+ /** Whether the plugin claims the day's grant on its own. */
751
+ autoCheckin: boolean;
752
+ /** The day the last claim happened. */
753
+ lastClaimDay?: string;
754
+ /** When the last claim happened, in milliseconds since the epoch. */
755
+ lastClaimAtMs?: number;
756
+ /** Points the last claim granted. */
757
+ lastClaimPoints?: number;
758
+ /** Whether the last claim was credited, or the day was already claimed. */
759
+ lastClaimResult?: number;
760
+ /** Why the cycle or the claim could not be read. */
761
+ error?: string;
762
+ }
763
+ /** How the plugin is signed in, as the card reads it. */
764
+ interface LoginStatus {
765
+ /** Whether the plugin has a usable credential. */
766
+ state: 'signed-in' | 'signed-out' | 'pending';
767
+ /** Where the credential came from. */
768
+ source?: 'plugin' | 'desktop-app';
769
+ /** The code to type on the account page, while a sign-in is waiting. */
770
+ userCode?: string;
771
+ /** Where to authorize. */
772
+ verificationUri?: string;
773
+ /** Where to authorize, with the code filled in. */
774
+ verificationUriComplete?: string;
775
+ /** When the device code stops working, in milliseconds since the epoch. */
776
+ expiresAtMs?: number;
777
+ }
481
778
  /** The account's credit balance, as the desktop app shows it. */
482
779
  interface CreditStatus {
483
780
  /** Whether the balance could be read. */
@@ -552,6 +849,10 @@ interface StatusDocument {
552
849
  credits?: CreditStatus;
553
850
  /** What this host's requests have used. */
554
851
  usage?: UsageStatus;
852
+ /** The daily check-in, when it could be read. */
853
+ signin?: SigninStatus;
854
+ /** How the plugin is signed in. */
855
+ login?: LoginStatus;
555
856
  }
556
857
  /** Inputs the document is derived from. */
557
858
  interface StatusDeps {
@@ -574,6 +875,10 @@ interface StatusDeps {
574
875
  credits?: CreditStatus;
575
876
  /** What this host's requests have used. */
576
877
  usage?: UsageStatus;
878
+ /** The daily check-in, when the account API could be reached. */
879
+ signin?: SigninStatus;
880
+ /** How the plugin is signed in. */
881
+ login?: LoginStatus;
577
882
  }
578
883
  /**
579
884
  * Build the card's status document.
@@ -646,4 +951,4 @@ declare const inject: string[];
646
951
  */
647
952
  declare function apply(ctx: Context): void;
648
953
  //#endregion
649
- export { type AuthState, type CatalogModel, type CatalogSource, type Credential, type CreditSummary, EMPTY_USAGE, FALLBACK_MODELS, MEMBERSHIP_PATH, MINIMAXCODE_PROVIDER_ID, MINIMAX_AUTH_FILENAME, MINIMAX_DATA_DIR, MINIMAX_GATEWAYS, MINIMAX_OAUTH_CLIENT_ID, MINIMAX_REGIONS, MINIMAX_STATUS_PATH, MINIMAX_STREAM_IDLE_TIMEOUT_MS, MODEL_PRICES, MinimaxCatalog, MinimaxCredits, type MinimaxRegion, type ModelUsage, type OAuthCredential, type OAuthState, type StatusDocument, type TokenClaims, UsageMeter, apply, authPath, bearerToken, catalogUrl, chatBaseUrl, createMinimaxCodeAdapter, dataDir, daysRemaining, decodeClaims, ensureOAuthCredential, estimateCny, expiresInMs, expiryLevel, fetchCreditSummary, inject, name, parseAuthDocument, parseCatalog, parseCreditSummary, parseOAuthDocument, priceFor, readCredential, readOAuthCredential, refreshOAuthCredential, statusDocument, writeOAuthCredential };
954
+ export { type AuthState, type CatalogModel, type CatalogSource, type Credential, type CreditSummary, DEVICE_CODE_GRANT, DEVICE_CODE_PATH, type DeviceLoginState, EMPTY_USAGE, FALLBACK_MODELS, MEMBERSHIP_PATH, MINIMAXCODE_PROVIDER_ID, MINIMAX_ACTION_PATH, MINIMAX_AUTH_FILENAME, MINIMAX_DATA_DIR, MINIMAX_GATEWAYS, MINIMAX_OAUTH_CLIENT_ID, MINIMAX_REGIONS, MINIMAX_STATUS_PATH, MINIMAX_STREAM_IDLE_TIMEOUT_MS, MODEL_PRICES, MinimaxCatalog, MinimaxCredits, type MinimaxRegion, type ModelUsage, type OAuthCredential, type OAuthState, PLUGIN_CREDENTIAL_FILENAME, PLUGIN_STATE_FILENAME, type PluginCredential, type PluginState, SIGNIN_CLAIM_PATH, SIGNIN_CLAIM_RESULT, SIGNIN_CYCLE_DAYS, SIGNIN_DAY_STATUS, SIGNIN_PANEL_PATH, type SigninClaim, type SigninDay, type SigninPanel, type StatusDocument, type TokenClaims, UsageMeter, apply, authPath, bearerToken, catalogUrl, chatBaseUrl, claimSignin, createMinimaxCodeAdapter, dataDir, daysRemaining, decodeClaims, dshHome, ensureOAuthCredential, estimateCny, expiresInMs, expiryLevel, fetchCreditSummary, fetchSigninPanel, inject, localDay, makePkce, name, parseAuthDocument, parseCatalog, parseCreditSummary, parseOAuthDocument, parseSigninClaim, parseSigninPanel, pluginCredentialPath, pluginStatePath, pollDeviceLogin, priceFor, readCredential, readOAuthCredential, readPluginCredential, readPluginState, refreshOAuthCredential, refreshPluginCredential, startDeviceLogin, statusDocument, timezoneId, writeOAuthCredential, writePluginCredential, writePluginState };