@bulolo/hermes-link 0.3.4 → 0.3.5

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.
@@ -8885,10 +8885,29 @@ async function startLinkService(options) {
8885
8885
  });
8886
8886
  const rootRouter = new Router13();
8887
8887
  rootRouter.get("/pair", async (ctx) => {
8888
- const connectToken = typeof ctx.query.connect_token === "string" ? ctx.query.connect_token : "";
8889
- const sessionId = typeof ctx.query.session_id === "string" ? ctx.query.session_id : connectToken ? `ps_${connectToken.slice(0, 16)}` : "";
8890
- ctx.type = "text/html";
8891
- ctx.body = await buildPairingPage({ port: config.port, connectToken, sessionId, identity });
8888
+ const sessionId = typeof ctx.query.session_id === "string" ? ctx.query.session_id : "";
8889
+ if (!sessionId) {
8890
+ ctx.status = 400;
8891
+ ctx.type = "text/plain";
8892
+ ctx.body = "Missing session_id";
8893
+ return;
8894
+ }
8895
+ const session = await readPairingSession(sessionId, paths);
8896
+ if (!session) {
8897
+ ctx.status = 404;
8898
+ ctx.type = "text/plain";
8899
+ ctx.body = "Pairing session not found";
8900
+ return;
8901
+ }
8902
+ const claimed = await isPairingSessionClaimed(sessionId, paths);
8903
+ ctx.set("content-type", "text/html; charset=utf-8");
8904
+ ctx.set("cache-control", "no-store");
8905
+ ctx.body = await renderPairingPage({
8906
+ session,
8907
+ claimed,
8908
+ version: LINK_VERSION,
8909
+ linkId: identity.link_id ?? session.link_id
8910
+ });
8892
8911
  });
8893
8912
  rootRouter.get("/api/v1/status", async (ctx) => {
8894
8913
  await authenticateRequest(ctx, paths);
@@ -8980,151 +8999,208 @@ async function startLinkService(options) {
8980
8999
  };
8981
9000
  return { app, server, stop };
8982
9001
  }
8983
- async function buildPairingPage(options) {
8984
- const { port, connectToken, sessionId, identity } = options;
8985
- const qrPayload = connectToken ? JSON.stringify({
9002
+ function escapeHtml(s) {
9003
+ return s.replace(/&/gu, "&amp;").replace(/</gu, "&lt;").replace(/>/gu, "&gt;").replace(/"/gu, "&quot;");
9004
+ }
9005
+ function formatDate(iso) {
9006
+ const d = new Date(iso);
9007
+ if (!Number.isFinite(d.getTime())) return iso;
9008
+ return d.toLocaleString("zh-CN", { hour12: false });
9009
+ }
9010
+ async function renderPairingPage(options) {
9011
+ const { session, claimed, version, linkId } = options;
9012
+ const isExpired = !claimed && isPairingSessionExpired(session);
9013
+ const qrPayload = JSON.stringify({
8986
9014
  kind: "hermes_link_pairing",
8987
9015
  version: 1,
8988
- link_id: identity.link_id ?? "",
8989
- display_name: "Hermes Link",
8990
- session_id: sessionId,
8991
- code: connectToken,
8992
- preferred_urls: [`http://127.0.0.1:${port}`]
8993
- }) : "";
8994
- let qrHtml = "";
8995
- if (qrPayload) {
8996
- try {
8997
- const qrSvg = await QRCode.toString(qrPayload, {
8998
- type: "svg",
8999
- margin: 1,
9000
- width: 240,
9001
- errorCorrectionLevel: "M"
9002
- });
9003
- qrHtml = `<div class="qr">${qrSvg}</div>`;
9004
- } catch {
9005
- qrHtml = "";
9006
- }
9007
- }
9008
- const baseUrl = `http://127.0.0.1:${port}`;
9009
- return `<!DOCTYPE html>
9010
- <html lang="en">
9016
+ link_id: session.link_id,
9017
+ display_name: session.display_name,
9018
+ session_id: session.session_id,
9019
+ code: session.code,
9020
+ preferred_urls: session.preferred_urls
9021
+ });
9022
+ const qrSvg = await QRCode.toString(qrPayload, { type: "svg", margin: 1, width: 320, errorCorrectionLevel: "M" });
9023
+ const qrDataUri = `data:image/svg+xml;base64,${Buffer.from(qrSvg).toString("base64")}`;
9024
+ const currentUrl = session.local_api_url.replace(/\/+$/u, "");
9025
+ const expiresAtMs = Date.parse(session.expires_at);
9026
+ const statusLabel = claimed ? "\u5DF2\u5B8C\u6210\u914D\u5BF9" : isExpired ? "\u914D\u5BF9\u5DF2\u8FC7\u671F" : "\u7B49\u5F85 App \u626B\u7801";
9027
+ const statusHint = claimed ? "App \u5DF2\u5B8C\u6210\u914D\u5BF9\uFF0C\u8FD9\u4E2A\u9875\u9762\u53EF\u4EE5\u5173\u95ED\u3002" : isExpired ? "\u8FD9\u6B21\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C hermeslink pair\u3002" : "\u6253\u5F00 App \u626B\u7801\uFF0C\u6216\u8005\u590D\u5236\u914D\u5BF9\u7801\u624B\u52A8\u8F93\u5165\u3002";
9028
+ const statusPillLabel = claimed ? "\u5DF2\u626B\u7801" : isExpired ? "\u5DF2\u8FC7\u671F" : "\u7B49\u5F85\u4E2D";
9029
+ return `<!doctype html>
9030
+ <html lang="zh-CN">
9011
9031
  <head>
9012
- <meta charset="UTF-8" />
9013
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
9014
- <title>Hermes Link \u2014 Pairing</title>
9032
+ <meta charset="utf-8" />
9033
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
9034
+ <meta name="color-scheme" content="light dark" />
9035
+ <title>Hermes Link Pairing</title>
9015
9036
  <style>
9016
- * { box-sizing: border-box; margin: 0; padding: 0; }
9017
- body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #0f0f0f; color: #e5e5e5; display: flex; align-items: center; justify-content: center; min-height: 100vh; padding: 1rem; }
9018
- .card { background: #1a1a1a; border: 1px solid #2a2a2a; border-radius: 12px; padding: 2rem; max-width: 520px; width: 100%; }
9019
- h1 { font-size: 1.2rem; font-weight: 600; margin-bottom: 0.25rem; text-align: center; }
9020
- .subtitle { color: #6b7280; font-size: 0.85rem; text-align: center; margin-bottom: 1.5rem; }
9021
- .qr { display: flex; justify-content: center; margin-bottom: 1.5rem; }
9022
- .qr svg { width: 200px; height: 200px; background: #fff; border-radius: 8px; padding: 8px; }
9023
- .section { margin-bottom: 1.25rem; }
9024
- .label { font-size: 0.75rem; color: #6b7280; margin-bottom: 0.35rem; text-transform: uppercase; letter-spacing: 0.05em; }
9025
- .mono { font-family: monospace; background: #0f0f0f; border: 1px solid #2a2a2a; border-radius: 6px; padding: 0.6rem 0.75rem; font-size: 0.78rem; word-break: break-all; color: #7dd3fc; cursor: pointer; user-select: all; }
9026
- .mono:hover { border-color: #3b82f6; }
9027
- .divider { border: none; border-top: 1px solid #2a2a2a; margin: 1.5rem 0; }
9028
- button { background: #3b82f6; color: #fff; border: none; border-radius: 8px; padding: 0.7rem 1.5rem; font-size: 0.9rem; cursor: pointer; width: 100%; }
9029
- button:hover { background: #2563eb; }
9030
- button:disabled { background: #374151; cursor: not-allowed; color: #6b7280; }
9031
- .status { margin-top: 0.75rem; font-size: 0.85rem; padding: 0.5rem 0.75rem; border-radius: 6px; display: none; text-align: center; }
9032
- .status.success { color: #6ee7b7; background: #064e3b22; display: block; }
9033
- .status.error { color: #fca5a5; background: #7f1d1d22; display: block; }
9034
- .result-row { margin-top: 0.5rem; }
9035
- .tag { display: inline-block; font-size: 0.7rem; background: #1e3a5f; color: #93c5fd; border-radius: 4px; padding: 0.1rem 0.4rem; margin-bottom: 0.25rem; }
9037
+ :root {
9038
+ color-scheme: light dark;
9039
+ --bg: #f4f5f7;
9040
+ --panel: rgba(255,255,255,0.78);
9041
+ --panel-strong: rgba(255,255,255,0.94);
9042
+ --text: #151922;
9043
+ --muted: #5f6673;
9044
+ --line: rgba(21,25,34,0.12);
9045
+ --accent: #2d5cff;
9046
+ --accent-soft: rgba(45,92,255,0.12);
9047
+ --good: #0b8457;
9048
+ --shadow: 0 24px 90px rgba(18,24,38,0.12);
9049
+ }
9050
+ @media (prefers-color-scheme: dark) {
9051
+ :root {
9052
+ --bg: #0c1017;
9053
+ --panel: rgba(16,20,28,0.78);
9054
+ --panel-strong: rgba(16,20,28,0.94);
9055
+ --text: #eef2f8;
9056
+ --muted: #9ba4b3;
9057
+ --line: rgba(255,255,255,0.12);
9058
+ --accent: #8ab4ff;
9059
+ --accent-soft: rgba(138,180,255,0.12);
9060
+ --good: #67d7a7;
9061
+ --shadow: 0 24px 90px rgba(0,0,0,0.45);
9062
+ }
9063
+ }
9064
+ * { box-sizing: border-box; }
9065
+ body { margin: 0; min-height: 100vh; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; color: var(--text); background: linear-gradient(180deg, var(--bg) 0%, color-mix(in srgb, var(--bg) 88%, var(--accent) 12%) 100%); }
9066
+ .shell { min-height: 100vh; display: grid; place-items: center; padding: 28px 18px; }
9067
+ .panel { width: min(1040px, 100%); border: 1px solid var(--line); border-radius: 28px; background: var(--panel); box-shadow: var(--shadow); backdrop-filter: blur(18px); overflow: hidden; }
9068
+ .hero { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(320px, 390px); gap: 0; }
9069
+ .copy { padding: 34px 34px 30px; border-right: 1px solid var(--line); }
9070
+ .eyebrow { display: inline-flex; align-items: center; gap: 10px; padding: 8px 12px; border-radius: 999px; background: var(--accent-soft); color: var(--accent); font-size: 13px; font-weight: 600; }
9071
+ h1 { margin: 18px 0 12px; font-size: clamp(34px, 4vw, 52px); line-height: 1.02; }
9072
+ .subtitle { max-width: 42ch; margin: 0; color: var(--muted); font-size: 16px; line-height: 1.7; }
9073
+ .meta-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; margin-top: 26px; }
9074
+ .meta { padding: 16px 16px 15px; border-radius: 18px; background: var(--panel-strong); border: 1px solid var(--line); }
9075
+ .meta-label { display: block; color: var(--muted); font-size: 12px; line-height: 1.4; margin-bottom: 8px; }
9076
+ .meta-value { font-size: 15px; line-height: 1.5; word-break: break-word; }
9077
+ .steps { display: grid; gap: 10px; margin-top: 18px; }
9078
+ .step { display: flex; gap: 12px; align-items: flex-start; padding: 14px 16px; border: 1px solid var(--line); border-radius: 18px; background: var(--panel-strong); }
9079
+ .step-badge { flex: none; width: 26px; height: 26px; border-radius: 999px; display: grid; place-items: center; background: var(--accent-soft); color: var(--accent); font-size: 13px; font-weight: 600; }
9080
+ .step-title { font-size: 14px; line-height: 1.45; margin: 0; font-weight: 600; }
9081
+ .step-copy { margin: 3px 0 0; color: var(--muted); font-size: 13px; line-height: 1.55; }
9082
+ .hint { margin: 10px 0 0; color: var(--muted); font-size: 13px; line-height: 1.55; }
9083
+ .qr { padding: 26px 26px 30px; background: linear-gradient(180deg, rgba(255,255,255,0.16), rgba(255,255,255,0)); }
9084
+ .card { border: 1px solid var(--line); border-radius: 24px; background: var(--panel-strong); padding: 20px; }
9085
+ .status { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-bottom: 18px; }
9086
+ .status-title { margin: 0; font-size: 18px; line-height: 1.3; }
9087
+ .pill { display: inline-flex; align-items: center; justify-content: center; padding: 7px 11px; border-radius: 999px; background: rgba(11,132,87,0.12); color: var(--good); font-size: 12px; font-weight: 600; white-space: nowrap; }
9088
+ .qr-frame { display: grid; place-items: center; padding: 18px; border-radius: 24px; background: linear-gradient(180deg, rgba(45,92,255,0.06), rgba(45,92,255,0)); border: 1px solid var(--line); }
9089
+ .qr-frame img { width: min(100%, 300px); aspect-ratio: 1; display: block; border-radius: 18px; background: #fff; padding: 14px; }
9090
+ .code { margin-top: 16px; padding: 14px 16px; border-radius: 18px; border: 1px solid var(--line); background: rgba(0,0,0,0.03); font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 22px; letter-spacing: 0.18em; text-align: center; user-select: all; }
9091
+ .footer { display: flex; justify-content: space-between; gap: 18px; flex-wrap: wrap; padding-top: 16px; color: var(--muted); font-size: 13px; line-height: 1.55; }
9092
+ @media (max-width: 920px) { .hero { grid-template-columns: 1fr; } .copy { border-right: none; border-bottom: 1px solid var(--line); } }
9093
+ @media (max-width: 640px) { .copy, .qr { padding: 22px 18px; } .meta-grid { grid-template-columns: 1fr; } .status { align-items: flex-start; flex-direction: column; } .code { font-size: 18px; letter-spacing: 0.14em; } }
9036
9094
  </style>
9037
9095
  </head>
9038
9096
  <body>
9039
- <div class="card">
9040
- <h1>Hermes Link Pairing</h1>
9041
- <p class="subtitle">\u7AEF\u53E3 ${port} \xB7 ${identity.link_id ?? "\u672A\u5206\u914D"}</p>
9042
-
9043
- ${qrHtml}
9044
-
9045
- <div class="section">
9046
- <div class="label">App \u626B\u7801\u5185\u5BB9\uFF08JSON\uFF09</div>
9047
- <div class="mono" title="\u70B9\u51FB\u590D\u5236" onclick="copyText(this)">${qrPayload.replace(/</g, "&lt;")}</div>
9048
- </div>
9049
-
9050
- <div class="section">
9051
- <div class="label">Session ID</div>
9052
- <div class="mono" title="\u70B9\u51FB\u590D\u5236" onclick="copyText(this)">${sessionId}</div>
9053
- </div>
9054
-
9055
- <div class="section">
9056
- <div class="label">Claim Token\uFF08code\uFF09</div>
9057
- <div class="mono" title="\u70B9\u51FB\u590D\u5236" onclick="copyText(this)">${connectToken}</div>
9058
- </div>
9097
+ <main class="shell">
9098
+ <section class="panel">
9099
+ <div class="hero">
9100
+ <div class="copy">
9101
+ <span class="eyebrow">Hermes Link \xB7 ${escapeHtml(version)}</span>
9102
+ <h1>\u5728 App \u91CC\u5B8C\u6210\u8FD9\u6B21\u914D\u5BF9</h1>
9103
+ <p class="subtitle">\u8FD9\u4E0D\u662F\u4E00\u5F20\u5B64\u96F6\u96F6\u7684\u4E8C\u7EF4\u7801\u3002\u5B83\u540C\u65F6\u7ED9\u4F60\u914D\u5BF9\u72B6\u6001\u3001\u624B\u52A8\u7801\u3001\u6709\u6548\u671F\u548C\u5F53\u524D\u672C\u5730\u5730\u5740\uFF0C\u65B9\u4FBF Windows \u4F20\u7EDF\u63A7\u5236\u53F0\u3001\u8FDC\u7A0B\u7EC8\u7AEF\u548C\u6D4F\u89C8\u5668\u6253\u5F00\u65F6\u90FD\u80FD\u987A\u5229\u5B8C\u6210\u3002</p>
9104
+ <div class="meta-grid">
9105
+ <div class="meta">
9106
+ <span class="meta-label">\u672C\u5730\u5730\u5740</span>
9107
+ <div class="meta-value">${escapeHtml(currentUrl)}</div>
9108
+ </div>
9109
+ <div class="meta">
9110
+ <span class="meta-label">Link ID</span>
9111
+ <div class="meta-value">${escapeHtml(linkId)}</div>
9112
+ </div>
9113
+ <div class="meta">
9114
+ <span class="meta-label">\u914D\u5BF9\u7801</span>
9115
+ <div class="meta-value">${escapeHtml(session.code)}</div>
9116
+ </div>
9117
+ <div class="meta">
9118
+ <span class="meta-label">\u8FC7\u671F\u65F6\u95F4</span>
9119
+ <div class="meta-value">${escapeHtml(formatDate(session.expires_at))}</div>
9120
+ </div>
9121
+ </div>
9122
+ <div class="steps">
9123
+ <div class="step">
9124
+ <div class="step-badge">1</div>
9125
+ <div>
9126
+ <p class="step-title">\u5728 App \u91CC\u6253\u5F00"\u8FDE\u63A5 Hermes Link"</p>
9127
+ <p class="step-copy">\u5148\u767B\u5F55 HermesPilot \u8D26\u53F7\uFF0C\u518D\u8D70\u626B\u7801\u6216\u624B\u52A8\u7801\u3002\u914D\u5BF9\u6210\u529F\u540E\uFF0CApp \u4F1A\u81EA\u52A8\u5207\u5230\u8FD9\u53F0 Link\u3002</p>
9128
+ </div>
9129
+ </div>
9130
+ <div class="step">
9131
+ <div class="step-badge">2</div>
9132
+ <div>
9133
+ <p class="step-title">\u626B\u4E8C\u7EF4\u7801\uFF0C\u6216\u76F4\u63A5\u8F93\u5165\u914D\u5BF9\u7801</p>
9134
+ <p class="step-copy">\u5982\u679C\u626B\u7801\u4E0D\u65B9\u4FBF\uFF0CApp \u91CC\u4E5F\u53EF\u4EE5\u76F4\u63A5\u8F93\u5165\u8FD9\u4E2A\u9875\u9762\u663E\u793A\u7684\u914D\u5BF9\u7801\u3002</p>
9135
+ </div>
9136
+ </div>
9137
+ <div class="step">
9138
+ <div class="step-badge">3</div>
9139
+ <div>
9140
+ <p class="step-title">\u914D\u5BF9\u6210\u529F\u540E\uFF0C\u8FD9\u4E2A\u9875\u9762\u4F1A\u81EA\u52A8\u53D8\u6210\u5DF2\u5B8C\u6210\u72B6\u6001</p>
9141
+ <p class="step-copy" id="statusHint">${escapeHtml(statusHint)}</p>
9142
+ </div>
9143
+ </div>
9144
+ </div>
9145
+ <p class="hint">\u53EF\u5728\u7EC8\u7AEF\u7EE7\u7EED\u4FDD\u7559\u8FD9\u4E2A\u9875\u9762\uFF0C\u65B9\u4FBF\u7A0D\u540E\u6838\u5BF9\u72B6\u6001\uFF1B\u5982\u679C\u914D\u5BF9\u5DF2\u7ECF\u6210\u529F\uFF0C\u9875\u9762\u4E0D\u4F1A\u518D\u8981\u6C42\u91CD\u65B0\u626B\u7801\u3002</p>
9146
+ </div>
9147
+ <div class="qr">
9148
+ <div class="card">
9149
+ <div class="status">
9150
+ <h2 class="status-title" id="statusTitle">${escapeHtml(statusLabel)}</h2>
9151
+ <span class="pill" id="statusPill">${escapeHtml(statusPillLabel)}</span>
9152
+ </div>
9153
+ <div class="qr-frame">
9154
+ <img src="${qrDataUri}" alt="Hermes Link pairing QR code" />
9155
+ </div>
9156
+ <div class="code">${escapeHtml(session.code)}</div>
9157
+ <div class="footer">
9158
+ <span>Local API: ${escapeHtml(currentUrl)}</span>
9159
+ <span>Relay / Server \u4F1A\u540C\u65F6\u51C6\u5907\u53EF\u7528\u8DEF\u7531</span>
9160
+ </div>
9161
+ </div>
9162
+ </div>
9163
+ </div>
9164
+ </section>
9165
+ </main>
9166
+ <script>
9167
+ const sessionId = ${JSON.stringify(session.session_id)};
9168
+ const expiresAtMs = ${Number.isFinite(expiresAtMs) ? String(expiresAtMs) : "Number.NaN"};
9169
+ const initialClaimed = ${JSON.stringify(claimed)};
9170
+ const statusTitle = document.querySelector('#statusTitle');
9171
+ const statusPill = document.querySelector('#statusPill');
9172
+ const statusHintEl = document.querySelector('#statusHint');
9173
+ let refreshTimer = null;
9059
9174
 
9060
- <div class="section">
9061
- <div class="label">\u914D\u5BF9\u63A5\u53E3\uFF08App \u8C03\u7528\uFF09</div>
9062
- <div class="mono">POST ${baseUrl}/api/v1/pairing/claim</div>
9063
- </div>
9175
+ const stopPolling = () => { if (refreshTimer !== null) { clearInterval(refreshTimer); refreshTimer = null; } };
9064
9176
 
9065
- <hr class="divider" />
9177
+ const markClaimed = () => {
9178
+ statusTitle.textContent = '\u5DF2\u5B8C\u6210\u914D\u5BF9';
9179
+ statusPill.textContent = '\u5DF2\u626B\u7801';
9180
+ statusHintEl.textContent = 'App \u5DF2\u5B8C\u6210\u914D\u5BF9\uFF0C\u8FD9\u4E2A\u9875\u9762\u53EF\u4EE5\u5173\u95ED\u3002';
9181
+ stopPolling();
9182
+ };
9066
9183
 
9067
- <div class="label" style="margin-bottom:0.75rem">\u6D4F\u89C8\u5668\u5FEB\u901F\u914D\u5BF9</div>
9068
- <button id="btn" onclick="pairBrowser()">\u5728\u6B64\u8BBE\u5907\u4E0A\u914D\u5BF9</button>
9069
- <div class="status" id="status"></div>
9070
- <div id="results"></div>
9071
- </div>
9184
+ const markExpired = () => {
9185
+ statusTitle.textContent = '\u914D\u5BF9\u5DF2\u8FC7\u671F';
9186
+ statusPill.textContent = '\u5DF2\u8FC7\u671F';
9187
+ statusHintEl.textContent = '\u8FD9\u6B21\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C hermeslink pair\u3002';
9188
+ stopPolling();
9189
+ };
9072
9190
 
9073
- <script>
9074
- ${sessionId ? `
9075
- let pollTimer = setInterval(async () => {
9191
+ const refresh = async () => {
9192
+ if (Number.isFinite(expiresAtMs) && Date.now() >= expiresAtMs) { markExpired(); return; }
9076
9193
  try {
9077
- const res = await fetch('/api/v1/pairing/session?session_id=${sessionId}');
9078
- const data = await res.json();
9079
- if (data.ok && data.session?.claimed) {
9080
- clearInterval(pollTimer);
9081
- showStatus('success', 'App \u5DF2\u5B8C\u6210\u914D\u5BF9 \u2713');
9082
- }
9194
+ const response = await fetch('/api/v1/pairing/session?session_id=' + encodeURIComponent(sessionId), { headers: { accept: 'application/json' } });
9195
+ if (response.status === 404) { markExpired(); return; }
9196
+ if (!response.ok) return;
9197
+ const payload = await response.json();
9198
+ if (payload?.session?.claimed) markClaimed();
9083
9199
  } catch {}
9084
- }, 2000);
9085
- ` : ""}
9086
-
9087
- async function pairBrowser() {
9088
- const btn = document.getElementById('btn');
9089
- btn.disabled = true;
9090
- btn.textContent = '\u914D\u5BF9\u4E2D...';
9091
- try {
9092
- const res = await fetch('/api/v1/auth/device-session', {
9093
- method: 'POST',
9094
- headers: { 'Authorization': 'Bearer ${connectToken}', 'Content-Type': 'application/json' },
9095
- body: JSON.stringify({ device_label: navigator.userAgent.slice(0, 64), device_platform: 'web' })
9096
- });
9097
- const data = await res.json();
9098
- if (res.ok && data.access_token) {
9099
- btn.textContent = '\u5DF2\u914D\u5BF9';
9100
- showStatus('success', '\u914D\u5BF9\u6210\u529F\uFF01');
9101
- const results = document.getElementById('results');
9102
- results.innerHTML = \`
9103
- <div class="result-row"><span class="tag">access_token \xB7 2h</span><div class="mono" onclick="copyText(this)">\${data.access_token.token}</div></div>
9104
- <div class="result-row" style="margin-top:0.5rem"><span class="tag">refresh_token \xB7 90days</span><div class="mono" onclick="copyText(this)">\${data.refresh_token.token}</div></div>
9105
- \`;
9106
- } else {
9107
- throw new Error(data.error?.message || JSON.stringify(data));
9108
- }
9109
- } catch (e) {
9110
- btn.disabled = false;
9111
- btn.textContent = '\u5728\u6B64\u8BBE\u5907\u4E0A\u914D\u5BF9';
9112
- showStatus('error', '\u914D\u5BF9\u5931\u8D25: ' + e.message);
9113
- }
9114
- }
9115
-
9116
- function showStatus(type, msg) {
9117
- const el = document.getElementById('status');
9118
- el.className = 'status ' + type;
9119
- el.textContent = msg;
9120
- }
9200
+ };
9121
9201
 
9122
- function copyText(el) {
9123
- navigator.clipboard.writeText(el.textContent.trim()).then(() => {
9124
- const orig = el.style.borderColor;
9125
- el.style.borderColor = '#10b981';
9126
- setTimeout(() => el.style.borderColor = orig, 800);
9127
- }).catch(() => {});
9202
+ if (!initialClaimed) {
9203
+ refreshTimer = setInterval(refresh, 2000);
9128
9204
  }
9129
9205
  </script>
9130
9206
  </body>
package/dist/cli/index.js CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  saveConfig,
22
22
  startLinkService,
23
23
  writeJsonFile
24
- } from "../chunk-ELQBIHDQ.js";
24
+ } from "../chunk-EYXOHOAC.js";
25
25
  import "../chunk-NP3Y2NVF.js";
26
26
 
27
27
  // src/cli/index.ts
@@ -273,8 +273,8 @@ async function runPairingPreflight(options) {
273
273
  preferredUrls
274
274
  };
275
275
  }
276
- function buildLocalPairingPageUrl(baseUrl, sessionId, connectToken) {
277
- const qs = new URLSearchParams({ session_id: sessionId, connect_token: connectToken });
276
+ function buildLocalPairingPageUrl(baseUrl, sessionId, _connectToken) {
277
+ const qs = new URLSearchParams({ session_id: sessionId });
278
278
  return `${baseUrl}/pair?${qs.toString()}`;
279
279
  }
280
280
 
package/dist/http/app.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  startLinkService
3
- } from "../chunk-ELQBIHDQ.js";
3
+ } from "../chunk-EYXOHOAC.js";
4
4
  import "../chunk-NP3Y2NVF.js";
5
5
  export {
6
6
  startLinkService
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bulolo/hermes-link",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Provides full client API, multi-device auth and conversation management for Hermes Agent, with LAN and internet connectivity.",
5
5
  "author": "Bulolo",
6
6
  "license": "MIT",
@@ -75,4 +75,4 @@
75
75
  "typescript": "^5.7.2",
76
76
  "vitest": "^2.1.8"
77
77
  }
78
- }
78
+ }