tt-help-cli-ycl 1.4.179 → 1.4.181

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": "tt-help-cli-ycl",
3
- "version": "1.4.179",
3
+ "version": "1.4.181",
4
4
  "description": "TikTok user & video data scraper - extract ttSeller, verified, locationCreated from HTML source",
5
5
  "type": "module",
6
6
  "bin": {
@@ -157,7 +157,7 @@ check("状态徽章=使用中", row1.includes("使用中"));
157
157
  check("使用者=机器:口", row1.includes("F-11:9222"));
158
158
  const poolSrc = await page.content();
159
159
  check("独立页无 cookie 明文", !poolSrc.includes("SECRET-SID-XYZ"));
160
- check("独立页重登录按钮已点亮(不再是置灰占位)", await page.$$eval("#content button:not([disabled])", (bs) => bs.some((b) => b.textContent.includes("重登录"))));
160
+ check("独立页检测按钮已点亮(重登录已改名)", await page.$$eval("#content button:not([disabled])", (bs) => bs.some((b) => b.textContent.trim() === "检测")));
161
161
  check("独立页含新增账号/批量检测按钮", await page.$$eval(".bar button", (bs) => {
162
162
  const t = bs.map((b) => b.textContent).join();
163
163
  return t.includes("新增账号") && t.includes("批量检测");
@@ -1,15 +1,17 @@
1
1
  /**
2
- * 协议桥单元测试 — parseProtocolUrl 白名单/参数校验 + 双平台模板生成
2
+ * 协议桥单元测试 — parseProtocolUrl 白名单/参数校验 + 双平台模板生成 + 单 token 别名路由
3
3
  *
4
4
  * 覆盖:
5
5
  * [1] URL 解析:合法样例(大小写 normalize / origin 归一 / 尾斜杠 / note 清洗)
6
6
  * [2] URL 拒绝:白名单外 action / 缺参 / 非 http server / username 非法 / scheme 不对
7
7
  * [3] 模板生成:AppleScript(osacompile 源)与 Windows 包装 cmd 的结构断言
8
+ * [4] 别名路由:protocol-handle 单 token(OS 拉起链路兼容;parseArgs 无参签名读 process.argv)
8
9
  *
9
10
  * 运行: node scripts/test-protocol-bridge.mjs
10
11
  */
11
12
  import { strict as assert } from "assert";
12
13
  import { parseProtocolUrl, buildAppleScript, buildWindowsCmd } from "../src/cli/protocol-bridge.js";
14
+ import { parseArgs } from "../src/lib/args.js";
13
15
 
14
16
  let pass = 0;
15
17
  let fail = 0;
@@ -101,31 +103,60 @@ check("AppleScript: 生成源可通过 osacompile 语法结构(do shell script
101
103
  const src = buildAppleScript({ nodePath: NODE, cliPath: CLI });
102
104
  const m = src.match(/do shell script ("(?:[^"\\]|\\.)*")/);
103
105
  assert.ok(m, "do shell script 后必须是合法 AppleScript 字符串字面量");
104
- // 拼接表达式收尾
105
106
  assert.ok(/& quoted form of theURL & "(?:[^"\\]|\\.)*"\s*\nend open location/.test(src), "拼接式必须完整闭合");
107
+ assert.ok(src.includes('protocol handle \\"%s\\"'), "URL 槽必须引号化(quoted form 引号会被生成侧 shell 消费)");
106
108
  });
107
109
  check("AppleScript: node 路径烧录且单引号包裹", () => {
108
110
  const src = buildAppleScript({ nodePath: NODE, cliPath: CLI });
109
111
  assert.ok(src.includes(`'${NODE}'`), "node 绝对路径必须烧录");
110
112
  assert.ok(src.includes(`'${CLI}'`), "cli.js 绝对路径必须烧录");
111
113
  });
112
- check("AppleScript: 路径含空格时仍单引号安全", () => {
113
- const src = buildAppleScript({ nodePath: "/Users/a b/n/node", cliPath: "/Users/a b/n/cli.js" });
114
- assert.ok(src.includes("'/Users/a b/n/node'"), "含空格路径必须在单引号内");
114
+ check("AppleScript: .command 用 protocol handle 两 token(对齐路由)", () => {
115
+ const src = buildAppleScript({ nodePath: NODE, cliPath: CLI });
116
+ assert.ok(src.includes('protocol handle \\"%s\\"'), "必须 protocol handle 且 URL 槽引号化");
117
+ assert.ok(!src.includes("protocol-handle"), "不得残留单 token 旧形态");
115
118
  });
116
- check("AppleScript: .command 退出码保持 + 按键防闪退", () => {
119
+ check("AppleScript: .command 退出码保持 + 按键防闪退 + 自清理", () => {
117
120
  const src = buildAppleScript({ nodePath: NODE, cliPath: CLI });
118
121
  assert.ok(src.includes("st=$?"), "必须捕获退出码");
119
122
  assert.ok(src.includes("read -n 1"), "必须按键保持窗口");
123
+ assert.ok(src.includes("rm -f"), "必须自清理临时 .command");
120
124
  });
121
- check("Windows cmd: 绝对路径 + %1 引号约定 + pause", () => {
125
+ check("Windows cmd: 绝对路径 + protocol handle %1 + pause", () => {
122
126
  const cmd = buildWindowsCmd({ nodePath: "C:\\n\\node.exe", cliPath: "C:\\p\\cli.js" });
123
127
  assert.ok(cmd.startsWith("@echo off"), "必须关回显");
124
- assert.ok(cmd.includes('"C:\\n\\node.exe" "C:\\p\\cli.js" protocol-handle %1'), "参数必须是单变量 %1");
128
+ assert.ok(cmd.includes('"C:\\n\\node.exe" "C:\\p\\cli.js" protocol handle "%~1"'), "URL 槽必须 %~1 再引号化");
129
+ assert.ok(!cmd.includes("protocol-handle"), "不得残留单 token 旧形态");
125
130
  assert.ok(cmd.includes("pause"), "必须 pause 防闪退");
126
131
  assert.ok(cmd.includes("exit /b %RC%"), "必须透传退出码");
127
132
  assert.ok(cmd.includes("\r\n"), "必须是 CRLF");
128
133
  });
129
134
 
135
+ // ── [4] 单 token 别名路由 ──
136
+ console.log("[4] 别名路由(parseArgs 无参签名,读 process.argv)");
137
+ check("protocol-handle 单 token → protocol/handle(OS 拉起链路兼容)", () => {
138
+ const oldArgv = process.argv;
139
+ process.argv = ["node", "tt-help", "protocol-handle", "tt-help://login-pool/add?server=http://x.io"];
140
+ try {
141
+ const p = parseArgs();
142
+ assert.equal(p.subcommand, "protocol");
143
+ assert.equal(p.action, "handle");
144
+ assert.equal(p.url, "tt-help://login-pool/add?server=http://x.io");
145
+ } finally {
146
+ process.argv = oldArgv;
147
+ }
148
+ });
149
+ check("protocol handle 两 token 仍正常", () => {
150
+ const oldArgv = process.argv;
151
+ process.argv = ["node", "tt-help", "protocol", "handle", "tt-help://login-pool/add?server=http://x.io"];
152
+ try {
153
+ const p = parseArgs();
154
+ assert.equal(p.subcommand, "protocol");
155
+ assert.equal(p.action, "handle");
156
+ } finally {
157
+ process.argv = oldArgv;
158
+ }
159
+ });
160
+
130
161
  console.log(`\n结果: ${pass} 通过, ${fail} 失败`);
131
162
  process.exit(fail ? 1 : 0);
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ // 验证模板输出的 .command 里 URL 是否带引号(模拟 printf 行为)
3
+ import { buildAppleScript, buildWindowsCmd } from "../src/cli/protocol-bridge.js";
4
+ import { execFileSync } from "child_process";
5
+ import { readFileSync } from "fs";
6
+ import { strict as assert } from "assert";
7
+
8
+ const NODE = "/Users/x/.nvm/versions/node/v24.14.1/bin/node";
9
+ const CLI = "/Users/x/pkg/cli.js";
10
+ const URL = "tt-help://login-pool/relogin?server=http%3A%2F%2F117.71.53.99%3A17301&username=asset698";
11
+
12
+ const src = buildAppleScript({ nodePath: NODE, cliPath: CLI });
13
+
14
+ // 从 AppleScript 源抽取 sh 命令(模拟 AppleScript 字符串求值:\" → ",\\ → \)
15
+ const m = src.match(/do shell script "((?:[^"\\]|\\.)*)" & quoted form of theURL & "((?:[^"\\]|\\.)*)"\n/);
16
+ assert.ok(m, "do shell script 拼接式结构完整");
17
+ const unesc = (s) => s.replace(/\\"/g, '"').replace(/\\\\/g, "\\");
18
+ const sh = unesc(m[1]) + "'" + URL + "'" + unesc(m[2]);
19
+
20
+ // 用 /bin/sh 真实执行 printf(macOS 上等价;这里在 Linux 模拟——printf POSIX 行为一致)
21
+ const out2 = execFileSync("/bin/sh", ["-c", sh.replace("open -a Terminal", "echo OPEN-TARGET:")], { encoding: "utf-8" });
22
+ const target = (out2.match(/OPEN-TARGET: (\S+)/) || [])[1];
23
+ assert.ok(target, "未取得 .command 路径");
24
+ const out = readFileSync(target, "utf-8");
25
+
26
+
27
+ // 断言:URL 必须带引号出现在 .command 里
28
+ assert.ok(out.includes(`protocol handle "${URL}"`), "URL 必须被双引号包裹");
29
+ assert.ok(out.includes(`"${NODE}"`), "node 路径必须被双引号包裹");
30
+ assert.ok(out.includes(`"${CLI}"`), "cli 路径必须被双引号包裹");
31
+ console.log("✓ macOS:URL/路径均已引号化(& 不再截断命令)");
32
+
33
+ const w = buildWindowsCmd({ nodePath: "C:\\n\\node.exe", cliPath: "C:\\p\\cli.js" });
34
+ assert.ok(w.includes('protocol handle "%~1"'), "Windows 必须用 %~1 再引号化");
35
+ console.log("✓ Windows:protocol handle \"%~1\"(引号恒在)");
@@ -8,13 +8,14 @@
8
8
  *
9
9
  * add: 起独立 Edge(临时目录+代理)→ 人工在窗口里登录 TikTok → 轮询 passport 检出
10
10
  * → 导出 cookies → POST /api/login-pool/import(幂等,同号跳过)
11
- * relogin: POST /prepare 取池内旧 cookies → 注入本机 Edge 试复活(快路径零操作)
12
- * → 会话已死则提示人工在窗口登录目标账号 → 身份校验 → POST /relogin 覆盖刷新
11
+ * relogin: 检测:POST /prepare 取池内旧 cookies → 注入本机 Edge 打开账号当前登录态
12
+ * → 人工核对/退出重登(机器不做任何自动判定)→ 回车提交 → 身份校验 → POST /relogin 覆盖刷新
13
13
  * check: 服务端无浏览器 HTTP 探测各号登录态(默认空闲+疑似),死号自动标疑似
14
14
  *
15
15
  * 协议桥(页面一键拉起本命令)为独立后续项,本命令即其执行端。
16
16
  */
17
17
  import { mkdtempSync, rmSync } from "fs";
18
+ import { createInterface } from "readline";
18
19
  import { tmpdir } from "os";
19
20
  import { join } from "path";
20
21
  import { chromium } from "playwright";
@@ -23,11 +24,37 @@ import { proxy as configuredProxy } from "../lib/constants.js";
23
24
 
24
25
  const POLL_INTERVAL_MS = 3000;
25
26
  const CDP_FALLBACK_NOTE = "(关掉窗口或 Ctrl+C 可中止)";
27
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
26
28
 
27
29
  function log(t) {
28
30
  console.error(`[登录池] ${t}`);
29
31
  }
30
32
 
33
+ /** 等待回车提交;stdin 关闭或超时则中止(不更新池)。返回 {promise, abort}:失败路径须 abort() 关闭监听,否则进程不退 */
34
+ function waitEnter(timeoutMs) {
35
+ let finish = null;
36
+ const promise = new Promise((resolve, reject) => {
37
+ const rl = createInterface({ input: process.stdin });
38
+ let done = false;
39
+ finish = (err) => {
40
+ if (done) return;
41
+ done = true;
42
+ clearTimeout(timer);
43
+ rl.close();
44
+ process.stdin.pause();
45
+ process.stdin.unref(); // 释放事件循环对 stdin 的引用:提交/中止后进程立即退出
46
+ err ? reject(err) : resolve();
47
+ };
48
+ const timer = setTimeout(
49
+ () => finish(new Error(`等待回车超时(${Math.round(timeoutMs / 1000)}s),已中止(不更新池)`)),
50
+ timeoutMs,
51
+ );
52
+ rl.once("line", () => finish(null));
53
+ rl.once("close", () => finish(new Error("stdin 已关闭,已中止(不更新池)")));
54
+ });
55
+ return { promise, abort: (msg) => finish && finish(new Error(msg || "已中止")) };
56
+ }
57
+
31
58
  /** HTTP POST,默认 30s 超时(check 场景调用方自带更长超时) */
32
59
  async function post(base, path, body, timeoutMs = 30 * 1000) {
33
60
  const res = await fetch(`${String(base).replace(/\/+$/, "")}/api/login-pool/${path}`, {
@@ -106,45 +133,66 @@ async function cmdAdd(opts) {
106
133
  }
107
134
  }
108
135
 
109
- /** relogin 子命令 */
136
+ /** relogin 子命令:检测——打开账号当前登录态,人工核对/退出重登后回车提交更新 */
110
137
  async function cmdRelogin(opts) {
111
138
  const { serverUrl, proxy, username, timeoutMs, dir } = opts;
112
139
  if (!username) throw new Error("用法: tt-help login-pool relogin <username>");
113
- const prep = await post(serverUrl, "prepare", { username });
114
- const oldCookies = prep.account.cookies;
115
- log(`目标 @${username}(池内 ${oldCookies.length} 条 cookies)。启动 Edge,先试旧登录态复活…`);
116
- const toPlaywrightCookies = (cookies) =>
117
- (cookies || []).map((c) => ({
118
- name: c.name,
119
- value: c.value,
120
- domain: c.domain || ".tiktok.com",
121
- path: c.path || "/",
122
- secure: c.secure !== false,
123
- httpOnly: c.httpOnly !== false,
124
- ...(typeof c.expires === "number" && c.expires > 0 ? { expires: c.expires } : {}),
125
- }));
126
- const session = await openEdgeSession({ proxy, dir });
140
+ // 尽早挂回车监听:fd0 在重活(Edge 启动/页面导航)期间可能被环境消费,迟挂会丢事件;
141
+ // 提前监听则回车随时可达,结果由下方 await 消费
142
+ const enter = waitEnter(timeoutMs);
143
+ enter.promise.catch(() => {}); // 防 unhandled rejection(真正消费在下方 await/abort)
144
+ let session = null;
127
145
  try {
128
- let id = await waitForLogin({ page: session.page, preCookies: toPlaywrightCookies(oldCookies), timeoutMs: 20000 });
129
- if (id.hasAccount && id.username === username) {
130
- log("✓ 旧登录态仍存活(快路径,零操作)");
131
- } else {
132
- // 死号分支:清掉死 cookies,提示人工在窗口登录目标账号
133
- log(`旧登录态已失效(${id.expired ? "会话过期" : "未登录"})。请在窗口里登录 @${username}…`);
134
- await session.context.clearCookies();
135
- await gotoTiktok(session.page);
136
- id = await waitForLogin({ page: session.page, timeoutMs });
146
+ const prep = await post(serverUrl, "prepare", { username });
147
+ const oldCookies = prep.account.cookies;
148
+ log(`目标 @${username}(池内 ${oldCookies.length} 条 cookies)。在窗口中打开当前登录态…`);
149
+ const toPlaywrightCookies = (cookies) =>
150
+ (cookies || []).map((c) => ({
151
+ name: c.name,
152
+ value: c.value,
153
+ domain: c.domain || ".tiktok.com",
154
+ path: c.path || "/",
155
+ secure: c.secure !== false,
156
+ httpOnly: c.httpOnly !== false,
157
+ ...(typeof c.expires === "number" && c.expires > 0 ? { expires: c.expires } : {}),
158
+ }));
159
+ session = await openEdgeSession({ proxy, dir });
160
+ await session.context.addCookies(toPlaywrightCookies(oldCookies));
161
+ await gotoTiktok(session.page);
162
+ log(`已在窗口打开 @${username}。可保持不动 / 退出重登 / 直接处理异常(人工操作)`);
163
+ log(
164
+ `操作完成后回这里按回车,提交最新登录态(${Math.round(timeoutMs / 1000)}s 无操作自动中止;关窗口=中止)…`,
165
+ );
166
+ await enter.promise;
167
+
168
+ // 读提交时刻的登录态:页面可能在加载,未检出时短暂重试
169
+ let id = null;
170
+ for (let i = 0; i < 3; i++) {
171
+ id = await whoAmI(session.page);
172
+ if (id.hasAccount) break;
173
+ if (i < 2) {
174
+ log("未检测到登录态,3 秒后重试…");
175
+ await sleep(3000);
176
+ }
177
+ }
178
+ if (!id || !id.hasAccount) {
179
+ enter.abort();
180
+ throw new Error("窗口当前未登录(登出后未重登?),已中止(不更新池)");
137
181
  }
138
182
  if (id.username !== username) {
183
+ enter.abort();
139
184
  throw new Error(`身份不符:窗口登录的是 @${id.username},目标是 @${username},已中止(不更新池)`);
140
185
  }
141
186
  const cookies = await session.context.cookies();
142
- const r = await post(serverUrl, "relogin", { username, cookies, userId: "cli-login-pool", note: "命令行重登录" });
187
+ const r = await post(serverUrl, "relogin", { username, cookies, userId: "cli-login-pool", note: "命令行检测更新" });
143
188
  log(
144
- `✓ @${username} 重登录完成: cookies 已覆盖刷新(${r.account.cookieCount} 条)${r.revived ? ",已停用账号复活回池" : r.wasSuspect ? ",疑似已清回池" : ""}`,
189
+ `✓ @${username} 检测完成: cookies 已覆盖刷新(${r.account.cookieCount} 条)${r.revived ? ",已停用账号复活回池" : r.wasSuspect ? ",疑似已清回池" : ""}`,
145
190
  );
191
+ } catch (e) {
192
+ enter.abort(); // 失败路径关闭回车监听,否则 readline 持有事件循环、进程不退
193
+ throw e;
146
194
  } finally {
147
- await session.cleanup();
195
+ if (session) await session.cleanup();
148
196
  }
149
197
  }
150
198
 
@@ -192,7 +240,7 @@ export async function handleLoginPool(parsed) {
192
240
  " tt-help login-pool check [--server URL] [--proxy URL] [--ids id1,id2] [--json]",
193
241
  "",
194
242
  " add 新登录收号:弹出 Edge 人工登录 TikTok,检出后自动入池(幂等)",
195
- " relogin 重登录:先试旧登录态复活(零操作),死了引导人工登录,身份校验后覆盖刷新",
243
+ " relogin 检测:窗口打开账号当前登录态,人工核对/退出重登后按回车提交,身份校验后覆盖刷新",
196
244
  " check 巡检:服务端 HTTP 探测各号登录态,死号自动标疑似",
197
245
  ].join("\n"),
198
246
  );
@@ -102,7 +102,7 @@ async function cmdHandle(rawUrl) {
102
102
  noProxy: false,
103
103
  note: p.note || null,
104
104
  dir: null,
105
- timeoutSec: 300,
105
+ timeoutSec: 600,
106
106
  ids: null,
107
107
  json: false,
108
108
  showHelp: false,
@@ -118,7 +118,7 @@ export function buildAppleScript({ nodePath, cliPath }) {
118
118
  // .command 内容: #!/bin/bash + 跑 node(不 exec,退出码非 0 时窗口必留)+ 按键保持
119
119
  const shellP1 =
120
120
  "t=$(mktemp ${TMPDIR:-/tmp}/tt-help-bridge.XXXXXX); f=\"$t.command\"; mv \"$t\" \"$f\"; " + // mktemp 要求 X 结尾,先随机后 mv 补 .command 后缀(实测:直接带后缀会退化成固定名,并发互踩)
121
- "printf '#!/bin/bash\\n%s %s protocol-handle %s\\nst=$?\\necho\\nrm -f \"$0\"\\nread -n 1 -s -r -p \"完成(退出码 $st)——按任意键关闭…\"\\nexit $st\\n' " +
121
+ "printf '#!/bin/bash\\n\"%s\" \"%s\" protocol handle \"%s\"\\nst=$?\\necho\\nrm -f \"$0\"\\nread -n 1 -s -r -p \"完成(退出码 $st)——按任意键关闭…\"\\nexit $st\\n' " +
122
122
  `${shQ(nodePath)} ${shQ(cliPath)} `;
123
123
  const shellP2 = ' > "$f"; chmod +x "$f"; /usr/bin/open -a Terminal "$f"';
124
124
  return (
@@ -204,7 +204,7 @@ export function buildWindowsCmd({ nodePath, cliPath }) {
204
204
  return [
205
205
  "@echo off",
206
206
  "title tt-help 登录池",
207
- `"${nodePath}" "${cliPath}" protocol-handle %1`,
207
+ `"${nodePath}" "${cliPath}" protocol handle "%~1"`,
208
208
  "set RC=%ERRORLEVEL%",
209
209
  "echo.",
210
210
  `pause`,
package/src/lib/args.js CHANGED
@@ -1175,7 +1175,7 @@ function parseLoginPoolArgs(args) {
1175
1175
  let noProxy = false;
1176
1176
  let note = null;
1177
1177
  let dir = null;
1178
- let timeoutSec = 300;
1178
+ let timeoutSec = 600;
1179
1179
  let ids = null;
1180
1180
  let json = false;
1181
1181
  let showHelp = false;
@@ -1193,7 +1193,7 @@ function parseLoginPoolArgs(args) {
1193
1193
  } else if (arg === "--dir" && i + 1 < args.length) {
1194
1194
  dir = args[++i];
1195
1195
  } else if (arg === "--timeout" && i + 1 < args.length) {
1196
- timeoutSec = parseInt(args[++i], 10) || 300;
1196
+ timeoutSec = parseInt(args[++i], 10) || 600;
1197
1197
  } else if (arg === "--ids" && i + 1 < args.length) {
1198
1198
  ids = args[++i].split(",").map((s) => s.trim()).filter(Boolean);
1199
1199
  } else if (arg === "--json") {
@@ -1806,6 +1806,11 @@ export function parseArgs() {
1806
1806
  return parseProtocolBridgeArgs(args.slice(1));
1807
1807
  }
1808
1808
 
1809
+ // protocol-handle 单 token 别名:协议处理器拉起链路的历史形态,兼容已注册机器
1810
+ if (args.length > 0 && args[0] === "protocol-handle") {
1811
+ return parseProtocolBridgeArgs(["handle", ...args.slice(1)]);
1812
+ }
1813
+
1809
1814
  if (args.length > 0 && args[0] === "comments") {
1810
1815
  return parseCommentsArgs(args.slice(1));
1811
1816
  }
@@ -294,13 +294,13 @@ const HELP_TEXT = [
294
294
  " tt-help login-pool <add|relogin|check> [选项]",
295
295
  " 账号中心:新登录收号 / 重登录 / 巡检(与网页 /login-pool 同一套账号库)",
296
296
  " add 弹出独立 Edge,人工登录 TikTok 后自动导出登录态入池(幂等)",
297
- " relogin <username> 先试旧登录态复活(零操作),失效则引导人工登录,覆盖刷新池内 cookies",
297
+ " relogin <username> 检测:窗口打开账号当前登录态,人工核对/退出重登后按回车提交,覆盖刷新池内 cookies",
298
298
  " 选项:",
299
299
  " --server <URL> 账号中心地址(watch 服务端)",
300
300
  " --proxy <地址> Edge/探测代理,默认配置文件 proxy;--no-proxy 直连",
301
301
  " --note <备注> add 备注信息",
302
302
  " --dir <路径> 自定义 Edge 用户目录(默认临时目录用后即清)",
303
- " --timeout <秒> 等待人工登录上限,默认 300",
303
+ " --timeout <秒> 等待人工操作上限,默认 600",
304
304
  " --ids <id1,id2> check 只探测指定账号",
305
305
  " --json check 输出 JSON",
306
306
  " 示例: tt-help login-pool add --server http://172.18.154.201:3011",
@@ -758,7 +758,7 @@ async function load() {
758
758
  (a.status === 'retired' ? '<button onclick="enable(\\'' + a.id + '\\',\\'' + esc(a.username) + '\\',false)">启用</button> ' : '') +
759
759
  (a.status !== 'retired' ? '<button onclick="disable(\\'' + a.id + '\\',\\'' + esc(a.username) + '\\',' + (a.status === 'leased') + ')">停用</button> ' : '') +
760
760
  (a.status === 'retired' ? '<button onclick="del(\\'' + a.id + '\\')">删除</button> ' : '') +
761
- '<button onclick="reloginDlg(\\'' + esc(a.username) + '\\')" title="一键拉起本机 tt-help 重登录:旧登录态自动复活(或人工登录),验证身份后覆盖刷新池内 cookies">重登录</button>' +
761
+ '<button onclick="reloginDlg(\\'' + esc(a.username) + '\\')" title="一键拉起本机 tt-help 检测:窗口打开账号当前登录态,可退出重登或保持,回终端按回车提交更新池内 cookies">检测</button>' +
762
762
  '</td>' +
763
763
  '<td><details data-acc="' + esc(a.id) + '" ' + (openHist.has(a.id) ? 'open' : '') + ' ontoggle="onHistToggle(this)"><summary class="muted">' + (a.history ? a.history.length : 0) + ' 条</summary>' +
764
764
  (a.history || []).slice(0, 8).map(h => '<div class="muted">' + fmt(h.at) + ' ' + esc(h.event) + ' ' + esc(h.detail) + '</div>').join('') + '</details></td>' +
@@ -809,7 +809,7 @@ function addCmdDlg() {
809
809
  function reloginDlg(name) {
810
810
  launchProto(protoHref('login-pool/relogin', { server: location.origin, username: name }),
811
811
  'tt-help login-pool relogin ' + name + ' --server ' + location.origin,
812
- '已拉起 tt-help 重登录 @' + name + ':看弹出窗口(旧登录态会先试自动复活),完成后点刷新');
812
+ '已拉起 tt-help 检测 @' + name + ':窗口中核对/退出重登,完成后回终端按回车提交,然后回来点刷新');
813
813
  }
814
814
  function setProgress(t) {
815
815
  const el = document.getElementById('msg');