mslxdff 0.1.140 → 0.1.141

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mslxdff",
3
- "version": "0.1.140",
3
+ "version": "0.1.141",
4
4
  "description": "测试项目,请勿使用。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -119,7 +119,37 @@ export async function handleClineLogin(id, sub) {
119
119
  console.log(`✅ 登录成功! 账号: ${email}`);
120
120
  console.log(`🔑 refreshToken: ${rt.slice(0, 8)}…${rt.slice(-8)} (${rt.length} 字符)`);
121
121
  // 落盘到 state(供应商 id 恒为 cline;写盘前先跑一次性迁移,保证老用户不会再写出 clinebot)
122
- const { defaultStateFile, loadProviderKeys, saveProviderConfig, loadProviderConfig } = await import("../../../state.js");
122
+ // 同邮箱去重:refreshToken 每次 login 都换新串,裸串比对拦不住重复账号,必须按 email 归一。
123
+ // auths 存 uid=email 索引行(domain=cline.bot),keys 与 auths 双写;老 keys 无映射时用 refresh 反查兜底。
124
+ const { defaultStateFile, loadProviderKeys, saveProviderConfig, loadProviderConfig, loadProviderAuths } = await import("../../../state.js");
125
+ function normEmail(v) { return String(v || "").trim().toLowerCase(); }
126
+ function emailFromJson(j) {
127
+ try {
128
+ const d = j?.data && typeof j.data === "object" ? j.data : j;
129
+ const u = d?.userInfo && typeof d.userInfo === "object" ? d.userInfo : null;
130
+ const cand = u?.email || d?.email || d?.user?.email || u?.mail || d?.mail || "";
131
+ if (cand && String(cand).includes("@")) return normEmail(cand);
132
+ const m = String(JSON.stringify(j || {}).slice(0, 4000)).match(/[\w.+-]+@[\w-]+\.[\w.]+/);
133
+ return m ? normEmail(m[0]) : "";
134
+ } catch { return ""; }
135
+ }
136
+ async function resolveClineEmail(refreshToken) {
137
+ try {
138
+ const baseNoV1 = "https://api.cline.bot";
139
+ const res = await compatFetch(`${baseNoV1}/api/v1/auth/refresh`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ refreshToken, grantType: "refresh_token" }), ...extraOpts, signal: timeoutSignal(15000) });
140
+ if (!res.ok) return "";
141
+ return emailFromJson(await res.json().catch(() => null));
142
+ } catch { return ""; }
143
+ }
144
+ function upsertAuths(list, email, rt) {
145
+ const norm = normEmail(email);
146
+ if (!norm || norm === "unknown") return [...(list || [])];
147
+ const next = [...(list || [])];
148
+ const i = next.findIndex((a) => normEmail(a?.uid) === norm);
149
+ if (i >= 0) next[i] = { uid: next[i].uid, domain: "cline.bot", enterpriseId: "", refreshToken: rt };
150
+ else next.push({ uid: String(email).trim(), domain: "cline.bot", enterpriseId: "", refreshToken: rt });
151
+ return next;
152
+ }
123
153
  try {
124
154
  const { runStateMigrations } = await import("../../../state/migrations.js");
125
155
  const mig = await runStateMigrations({ file: defaultStateFile() });
@@ -128,12 +158,51 @@ export async function handleClineLogin(id, sub) {
128
158
  try {
129
159
  const pid = "cline";
130
160
  const cur = loadProviderKeys(pid);
161
+ const cfg = loadProviderConfig(pid) || { baseUrl: "", keys: [] };
162
+ const baseUrl = cfg.baseUrl || "https://api.cline.bot";
163
+ let auths = loadProviderAuths(pid) || [];
164
+ const norm = normEmail(email);
131
165
  if (cur.includes(rt)) {
132
- console.log(` ℹ️ ${pid} 已存在相同 token,跳过`);
166
+ auths = upsertAuths(auths, email, rt);
167
+ saveProviderConfig(pid, { baseUrl, keys: cur, auths });
168
+ console.log(` ℹ️ ${pid} 已存在相同 token,已补邮箱映射(现 ${cur.length} 个账号)`);
169
+ } else if (norm && norm !== "unknown") {
170
+ const ai = auths.findIndex((a) => normEmail(a?.uid) === norm);
171
+ if (ai >= 0) {
172
+ const oldRt = String(auths[ai]?.refreshToken || "");
173
+ const nextKeys = [...cur];
174
+ const at = oldRt ? nextKeys.indexOf(oldRt) : -1;
175
+ if (at >= 0) nextKeys[at] = rt;
176
+ else { const dup = nextKeys.indexOf(rt); if (dup < 0) nextKeys.push(rt); }
177
+ auths = upsertAuths(auths, email, rt);
178
+ saveProviderConfig(pid, { baseUrl, keys: [...new Set(nextKeys.filter(Boolean))], auths });
179
+ console.log(` ℹ️ ${email} 已存在,token 已替换(仍 ${nextKeys.length} 个账号,未追加)`);
180
+ } else {
181
+ let dupIdx = -1;
182
+ const resolved = [];
183
+ for (let i = 0; i < cur.length; i++) {
184
+ const em = await resolveClineEmail(cur[i]);
185
+ resolved[i] = em;
186
+ if (em && em === norm) { dupIdx = i; break; }
187
+ await new Promise((r) => setTimeout(r, 300));
188
+ }
189
+ if (dupIdx >= 0) {
190
+ const nextKeys = [...cur]; nextKeys[dupIdx] = rt;
191
+ for (let i = 0; i < cur.length; i++) if (resolved[i]) auths = upsertAuths(auths, resolved[i], i === dupIdx ? rt : cur[i]);
192
+ auths = upsertAuths(auths, email, rt);
193
+ saveProviderConfig(pid, { baseUrl, keys: [...new Set(nextKeys.filter(Boolean))], auths });
194
+ console.log(` ℹ️ ${email} 已存在(位置 ${dupIdx + 1}),token 已替换(仍 ${nextKeys.length} 个账号,未追加)`);
195
+ } else {
196
+ const nextKeys = [...new Set([...cur, rt].filter(Boolean))];
197
+ for (let i = 0; i < cur.length; i++) if (resolved[i]) auths = upsertAuths(auths, resolved[i], cur[i]);
198
+ auths = upsertAuths(auths, email, rt);
199
+ saveProviderConfig(pid, { baseUrl, keys: nextKeys, auths });
200
+ console.log(` ✅ 已写入 ${pid}(现 ${nextKeys.length} 个账号)`);
201
+ }
202
+ }
133
203
  } else {
134
- const cfg = loadProviderConfig(pid) || { baseUrl: "", keys: [] };
135
204
  const nextKeys = [...new Set([...(cfg.keys || cur), rt].filter(Boolean))];
136
- saveProviderConfig(pid, { baseUrl: cfg.baseUrl || "https://api.cline.bot", keys: nextKeys });
205
+ saveProviderConfig(pid, { baseUrl, keys: nextKeys, auths });
137
206
  console.log(` ✅ 已写入 ${pid}(现 ${nextKeys.length} 个账号)`);
138
207
  }
139
208
  } catch (e) {
@@ -144,6 +213,6 @@ export async function handleClineLogin(id, sub) {
144
213
  console.log(" mslxdff -restart 重启网关使新账号生效");
145
214
  console.log(" mslxdff -provider cline bench --json 测速 deepseek 是否 200");
146
215
  console.log(" mslxdff -chat 直接对话,模型选 deepseek/deepseek-v4-flash");
147
- console.log("\n多账号:重复 `mslxdff -provider cline login` 追加,二号自动做后备");
216
+ console.log("\n多账号:重复 `mslxdff -provider cline login` 追加(同邮箱自动替换不追加),二号自动做后备");
148
217
  process.exit(0);
149
218
  }
@@ -17,6 +17,14 @@ export async function handleProviderKeys(id, sub, rest, args) {
17
17
  if (a) console.log(` [${i + 1}] user=${a.userName || "(未识别)"} uid=${a.userId ? `${a.userId.slice(0, 8)}…` : "(空,请删了重登回填)"} domain=${a.domainId ? a.domainId.slice(0, 8) + "…" : "(空)"} (${k.length} chars)`);
18
18
  else console.log(` [${i + 1}] ${k.slice(0, 4)}…${k.slice(-4)} (${k.length} chars)`);
19
19
  }
20
+ } else if (id === "cline") {
21
+ const { loadProviderAuths } = await import("../../../state.js");
22
+ const auths = loadProviderAuths(id) || [];
23
+ const byRt = new Map(auths.map((a) => [String(a?.refreshToken || ""), String(a?.uid || "")]));
24
+ keys.forEach((k, i) => {
25
+ const em = byRt.get(String(k || "")) || "";
26
+ console.log(` [${i + 1}] ${k.slice(0, 4)}…${k.slice(-4)} (${k.length} chars)${em ? ` ${em}` : " (邮箱未映射,重登一次回填)"}`);
27
+ });
20
28
  } else keys.forEach((k, i) => console.log(` [${i + 1}] ${k.slice(0, 4)}…${k.slice(-4)} (${k.length} chars)`));
21
29
  console.log(` remove by: mslxdff -provider ${id} remove <seq> [seq...] | <key-value>`);
22
30
  } else {
@@ -1,5 +1,5 @@
1
1
  import { createKeyRing } from "../keyring.js";
2
- import { loadProviderKeys, loadProviderBaseUrl, loadProviderModelsPath, loadProviderChatPath, saveProviderConfig } from "../../state.js";
2
+ import { loadProviderKeys, loadProviderBaseUrl, loadProviderModelsPath, loadProviderChatPath, saveProviderConfig, loadProviderAuths } from "../../state.js";
3
3
  import { envInt, joinUrl, getUndici, createAgent, collectApiKeysGeneric, createChatRunner } from "../base.js";
4
4
  import { compatFetch } from "../../compat.js";
5
5
  import { joinModelId } from "../model-id.js";
@@ -68,8 +68,14 @@ export function createClineProvider({
68
68
  const idx = cur.indexOf(oldRefreshToken);
69
69
  if (idx >= 0) {
70
70
  const next = [...cur]; next[idx] = newRefreshToken;
71
- // 优先写 providerConfigs,兼容旧路径由 saveProviderConfig 处理
72
- saveProviderConfig(id, { baseUrl: resolvedBase, keys: next }, file ? { file } : {});
71
+ // 优先写 providerConfigs,兼容旧路径由 saveProviderConfig 处理;auths 邮箱映射同步新串防漂移
72
+ let auths;
73
+ try {
74
+ const list = loadProviderAuths(id, file ? { file } : {});
75
+ const ai = list.findIndex((a) => String(a?.refreshToken || "") === String(oldRefreshToken || ""));
76
+ if (ai >= 0) { auths = [...list]; auths[ai] = { ...auths[ai], refreshToken: newRefreshToken }; }
77
+ } catch {}
78
+ saveProviderConfig(id, { baseUrl: resolvedBase, keys: next, ...(auths ? { auths } : {}) }, file ? { file } : {});
73
79
  }
74
80
  // 只回写 cline 自己(旧 id 轮换回写已随 id 统一删除,见 .scratch/cline-unify/SPEC.md §4.1)
75
81
  } catch {}