device-center 0.2.0-beta.3 → 0.2.0-beta.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.
Files changed (49) hide show
  1. package/CONTRIBUTING.md +2 -0
  2. package/Dockerfile +2 -1
  3. package/README.md +24 -7
  4. package/SECURITY.md +8 -2
  5. package/bin/device-center.mjs +6 -5
  6. package/docs/agent-install.md +43 -9
  7. package/docs/driver-binding.md +14 -4
  8. package/docs/friend-trial.md +5 -5
  9. package/docs/hosted-access.md +1 -1
  10. package/docs/npm.md +13 -9
  11. package/docs/onboarding.md +5 -3
  12. package/docs/remote-access.md +2 -2
  13. package/docs/roadmap.md +13 -1
  14. package/docs/security-and-sync.md +4 -0
  15. package/docs/troubleshooting.md +11 -0
  16. package/docs/windows-acceptance.md +57 -0
  17. package/favicon.svg +4 -0
  18. package/index.html +1 -0
  19. package/login.html +2 -2
  20. package/npm-shrinkwrap.json +2 -2
  21. package/package.json +4 -2
  22. package/scripts/access.mjs +0 -1
  23. package/scripts/driver.mjs +27 -11
  24. package/scripts/service.mjs +42 -16
  25. package/server/cloudAccess.mjs +7 -1
  26. package/server/drivers/access.mjs +15 -14
  27. package/server/drivers/browserPairing.mjs +46 -0
  28. package/server/drivers/cloudLink.mjs +19 -20
  29. package/server/drivers/cloudRegistry.mjs +23 -6
  30. package/server/drivers/privateFiles.mjs +57 -0
  31. package/server/drivers/windowsSecurity.mjs +119 -0
  32. package/server/drivers/windowsService.mjs +77 -0
  33. package/server/index.mjs +30 -7
  34. package/server/unifiedAccess.mjs +24 -4
  35. package/src/api.js +4 -0
  36. package/src/install-prompt.js +2 -2
  37. package/src/install-walkthrough.js +49 -0
  38. package/src/login-flow.js +37 -0
  39. package/src/login.css +3 -1
  40. package/src/login.js +20 -11
  41. package/src/main.js +20 -12
  42. package/src/messages.js +1745 -162
  43. package/src/onboarding.js +60 -17
  44. package/src/pair.css +23 -0
  45. package/src/pair.js +60 -0
  46. package/src/pairing-handoff.js +30 -0
  47. package/src/start.css +265 -45
  48. package/src/start.js +155 -26
  49. package/start.html +143 -27
package/src/start.js CHANGED
@@ -1,39 +1,168 @@
1
- import { agentInstallPrompt, agentInstallPlan, OFFICIAL_CONTROL_ORIGIN } from './onboarding.js';
1
+ import { agentInstallPrompt, agentInstallPlan, terminalInstallScript, OFFICIAL_CONTROL_ORIGIN } from './onboarding.js';
2
2
  import { initLanguage, getLocale, t } from './i18n.js';
3
+ import { installWalkthroughMarkup } from './install-walkthrough.js';
3
4
 
4
5
  const text = document.querySelector('#install-instructions');
5
6
  const result = document.querySelector('#copy-result');
6
- let mode = 'local';
7
- function instructions() {
8
- const options = { locale: getLocale(), mode, ...(mode === 'self-hosted' ? { origin: document.querySelector('#custom-origin').value.trim() } : {}) };
7
+ const customOrigin = document.querySelector('#custom-origin');
8
+ // Shared links only preselect UI choices; strict plan validation still gates copying.
9
+ const guideParams = new URLSearchParams(location.search);
10
+ let mode = ['local', 'official', 'self-hosted'].includes(guideParams.get('mode')) ? guideParams.get('mode') : 'local';
11
+ let method = guideParams.get('method') === 'manual' ? 'manual' : 'agent';
12
+ let os = guideParams.get('os') === 'windows' ? 'windows' : 'posix';
13
+ let pairingMethod = 'browser';
14
+ customOrigin.value = mode === 'self-hosted' ? guideParams.get('origin') ?? '' : '';
15
+ for (const input of document.querySelectorAll('input[name="install-mode"]')) input.checked = input.value === mode;
16
+ for (const input of document.querySelectorAll('input[name="install-method"]')) input.checked = input.value === method;
17
+ for (const input of document.querySelectorAll('input[name="install-os"]')) input.checked = input.value === os;
18
+ if (mode === 'self-hosted') document.querySelector('.custom').open = true;
19
+ const scene = document.querySelector('.scene');
20
+ const motionToggle = document.querySelector('.motion-toggle');
21
+ motionToggle.addEventListener('click', () => {
22
+ const paused = scene.dataset.paused !== 'true';
23
+ scene.dataset.paused = String(paused);
24
+ const key = paused ? 'guide.visual.playMotion' : 'guide.visual.pauseMotion';
25
+ motionToggle.dataset.i18nAriaLabel = key;
26
+ motionToggle.dataset.i18nTitle = key;
27
+ motionToggle.setAttribute('aria-label', t(key));
28
+ motionToggle.setAttribute('title', t(key));
29
+ });
30
+ let sceneFrame = null;
31
+ scene.addEventListener('pointermove', event => {
32
+ if (event.pointerType !== 'mouse' || scene.dataset.paused === 'true' || matchMedia('(prefers-reduced-motion: reduce)').matches) return;
33
+ if (sceneFrame !== null) cancelAnimationFrame(sceneFrame);
34
+ const box = scene.getBoundingClientRect();
35
+ const x = (event.clientX - box.left) / box.width - .5;
36
+ const y = (event.clientY - box.top) / box.height - .5;
37
+ sceneFrame = requestAnimationFrame(() => {
38
+ scene.style.setProperty('--scene-rx', `${-y * 4}deg`);
39
+ scene.style.setProperty('--scene-ry', `${x * 6}deg`);
40
+ sceneFrame = null;
41
+ });
42
+ });
43
+ scene.addEventListener('pointerleave', () => {
44
+ if (sceneFrame !== null) cancelAnimationFrame(sceneFrame);
45
+ sceneFrame = null;
46
+ scene.style.setProperty('--scene-rx', '0deg');
47
+ scene.style.setProperty('--scene-ry', '0deg');
48
+ });
49
+ const copyButton = document.querySelector('#copy-instructions');
50
+ const nextAction = document.querySelector('#next-action');
51
+ let copied = false;
52
+ let copyGeneration = 0;
53
+ let instructionsValid = false;
54
+ let walkthroughStep = 0;
55
+ function translatedText(selector, key) {
56
+ const element = document.querySelector(selector);
57
+ element.dataset.i18n = key;
58
+ element.textContent = t(key);
59
+ }
60
+ function updateGuidance() {
61
+ const manual = method === 'manual';
62
+ translatedText('#copy-label', manual ? 'guide.manual.copy' : { local: 'guide.localCopy', official: 'guide.hostedCopy', 'self-hosted': 'guide.customCopy' }[mode]);
63
+ translatedText('#copy-hint', manual ? 'guide.manual.copyHint' : 'guide.focus.copyHint');
64
+ translatedText('#instruction-preview', manual ? 'guide.manual.preview' : 'guide.preview');
65
+ const fieldLabel = manual ? 'guide.manual.scriptLabel' : 'guide.instructions';
66
+ text.dataset.i18nAriaLabel = fieldLabel;
67
+ text.setAttribute('aria-label', t(fieldLabel));
68
+ document.querySelector('#manual-options').hidden = !manual;
69
+ translatedText('#manual-prerequisites', os === 'windows' ? 'guide.windowsPrerequisites' : 'guide.manual.prerequisites');
70
+ document.querySelector('#pairing-options').hidden = !manual || mode === 'local';
71
+ translatedText('[data-hosted-dashboard]', walkthroughStep === 2 ? 'walk.openDevices' : 'guide.manual.openPanel');
72
+ nextAction.dataset.copied = String(copied);
73
+ }
74
+ const errorKey = error => error.translationKey ?? 'error.custom';
75
+ function refreshInstructions() {
76
+ const options = { locale: getLocale(), mode, os, ...(mode === 'self-hosted' ? { origin: customOrigin.value.trim() } : {}) };
9
77
  const link = document.querySelector('[data-hosted-dashboard]');
78
+ document.querySelector('#custom-fields').hidden = mode !== 'self-hosted';
79
+ updateGuidance();
80
+ let plan;
10
81
  try {
11
- const plan = agentInstallPlan(options);
82
+ plan = agentInstallPlan(options);
83
+ text.value = method === 'manual' ? terminalInstallScript({ ...options, browser: pairingMethod === 'browser' }) : agentInstallPrompt(options);
84
+ instructionsValid = true;
85
+ customOrigin.removeAttribute('aria-invalid');
12
86
  const url = new URL('/login', plan.controlOrigin ?? OFFICIAL_CONTROL_ORIGIN);
13
- url.searchParams.set('lang', getLocale()); link.href = url.href; link.hidden = false;
14
- return agentInstallPrompt(options);
15
- } catch (error) { link.hidden = true; throw error; }
87
+ url.searchParams.set('lang', getLocale());
88
+ link.href = url.href;
89
+ link.hidden = mode === 'local' || !copied || (walkthroughStep !== 2 && !(walkthroughStep === 1 && method === 'manual' && pairingMethod === 'headless'));
90
+ } catch (error) {
91
+ instructionsValid = false;
92
+ text.value = '';
93
+ link.hidden = true;
94
+ if (mode === 'self-hosted') customOrigin.setAttribute('aria-invalid', 'true');
95
+ result.textContent = t(errorKey(error));
96
+ result.className = 'error';
97
+ }
98
+ document.querySelector('#install-walkthrough').innerHTML = installWalkthroughMarkup({ method, mode, os, pairingMethod, step: walkthroughStep, copied, controlOrigin: plan?.controlOrigin, localUrl: plan?.url, locale: getLocale() });
99
+ copyButton.disabled = !instructionsValid;
16
100
  }
17
- const errorKey = error => error.translationKey ?? 'error.custom';
18
- let copyGeneration = 0;
19
- initLanguage({ onChange: () => { copyGeneration++; result.textContent = ''; try { text.value = instructions(); } catch (error) { text.value = ''; result.textContent = t(errorKey(error)); result.className = 'error'; } } });
20
- document.addEventListener('click', async event => {
21
- const button = event.target.closest('button[data-mode]');
101
+ function resetCopyState(resetStep = true) {
102
+ copyGeneration++;
103
+ copied = false;
104
+ if (resetStep) walkthroughStep = 0;
105
+ result.textContent = '';
106
+ result.className = '';
107
+ refreshInstructions();
108
+ }
109
+ initLanguage({ onChange: () => resetCopyState(false) });
110
+ document.querySelector('#install-walkthrough').addEventListener('click', event => {
111
+ const button = event.target.closest('[data-walk-step]');
22
112
  if (!button || button.disabled) return;
113
+ const step = Number(button.dataset.walkStep);
114
+ if (!Number.isInteger(step) || step < 0 || step > 2) return;
115
+ walkthroughStep = step;
116
+ refreshInstructions();
117
+ nextAction.scrollIntoView({ block: 'start', behavior: matchMedia('(prefers-reduced-motion: reduce)').matches ? 'auto' : 'smooth' });
118
+ document.querySelector(`[data-walk-step="${walkthroughStep}"][aria-current="step"]`)?.focus({ preventScroll: true });
119
+ });
120
+ document.querySelector('.mode-options').addEventListener('change', event => {
121
+ if (!event.target.matches('input[name="install-mode"]')) return;
122
+ mode = event.target.value;
123
+ resetCopyState();
124
+ });
125
+ document.querySelector('.method-options').addEventListener('change', event => {
126
+ if (!event.target.matches('input[name="install-method"]')) return;
127
+ method = event.target.value;
128
+ resetCopyState();
129
+ });
130
+ document.querySelector('#pairing-options').addEventListener('change', event => {
131
+ if (!event.target.matches('input[name="pairing-method"]')) return;
132
+ pairingMethod = event.target.value;
133
+ resetCopyState();
134
+ });
135
+ document.querySelector('#os-options').addEventListener('change', event => {
136
+ if (!event.target.matches('input[name="install-os"]')) return;
137
+ os = event.target.value;
138
+ resetCopyState();
139
+ });
140
+ customOrigin.addEventListener('input', () => {
141
+ if (mode === 'self-hosted') resetCopyState();
142
+ });
143
+ copyButton.addEventListener('click', async () => {
144
+ if (!instructionsValid || copyButton.disabled) return;
23
145
  const generation = ++copyGeneration;
24
- mode = button.dataset.mode;
146
+ const prompt = text.value;
147
+ copyButton.disabled = true;
148
+ let success = false;
25
149
  try {
26
- text.value = instructions();
27
- } catch (error) { text.value = ''; result.textContent = t(errorKey(error)); result.className = 'error'; return; }
28
- button.disabled = true;
29
- let copied = false;
30
- try { if (navigator.clipboard?.writeText) { await navigator.clipboard.writeText(text.value); copied = true; } } catch { /* Keep manual copy available. */ }
31
- if (generation !== copyGeneration) { button.disabled = false; return; }
32
- if (!copied) {
33
- document.querySelector('#instruction-details').open = true; text.focus(); text.select();
34
- try { copied = document.execCommand('copy'); } catch { /* Selection is the fallback. */ }
150
+ if (navigator.clipboard?.writeText) { await navigator.clipboard.writeText(prompt); success = true; }
151
+ } catch { /* Keep manual copy available. */ }
152
+ // Any changed installation choice invalidates this asynchronous result.
153
+ if (generation !== copyGeneration) return;
154
+ if (!success) {
155
+ document.querySelector('#instruction-details').open = true;
156
+ text.focus(); text.select();
157
+ try { success = document.execCommand('copy'); } catch { /* Selection is the fallback. */ }
158
+ }
159
+ copied = success;
160
+ if (success) walkthroughStep = 0;
161
+ result.className = success ? 'success' : '';
162
+ result.textContent = t(success ? method === 'manual' ? 'guide.manual.copied' : 'guide.focus.copied' : 'guide.manual');
163
+ refreshInstructions();
164
+ if (success) {
165
+ nextAction.scrollIntoView({ block: 'nearest', behavior: matchMedia('(prefers-reduced-motion: reduce)').matches ? 'auto' : 'smooth' });
166
+ document.querySelector('#walkthrough-title').focus({ preventScroll: true });
35
167
  }
36
- result.className = copied ? 'success' : '';
37
- result.textContent = t(copied ? 'guide.copied' : 'guide.manual');
38
- button.disabled = false;
39
168
  });
package/start.html CHANGED
@@ -3,44 +3,160 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width,initial-scale=1">
6
- <meta name="description" data-i18n-content="guide.lead" content="把自己的闲置电脑接入本机智能体。">
6
+ <meta name="description" data-i18n-content="guide.visual.lead" content="让本机 Agent 查看其他设备的状态与授权日志。">
7
7
  <title data-i18n="guide.title">开始试用 · Device Center</title>
8
+ <link rel="icon" href="/favicon.svg" type="image/svg+xml">
8
9
  <link rel="stylesheet" href="/src/start.css">
9
10
  <link rel="stylesheet" href="/src/i18n.css">
10
11
  </head>
11
12
  <body class="start-page">
12
- <header><a class="brand" href="/start" data-language-link>DC <span>Device Center</span></a><div class="header-actions"><div class="language-picker" data-language-picker></div><a href="/" data-language-link data-i18n="guide.open">进入设备面板 ↗</a></div></header>
13
+ <svg class="icon-library" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"><defs>
14
+ <symbol id="icon-copy" viewBox="0 0 24 24"><rect x="8" y="8" width="12" height="13" rx="2"/><path d="M16 8V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h3"/></symbol>
15
+ <symbol id="icon-computer" viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="13" rx="2"/><path d="M8 21h8m-4-5v5M7 8l3 2-3 2m6 0h4"/></symbol>
16
+ <symbol id="icon-cloud" viewBox="0 0 24 24"><path d="M6 18h12a4 4 0 0 0 .5-8A6.5 6.5 0 0 0 6 8.5 4.8 4.8 0 0 0 6 18Z"/><path d="m9 12 3-3 3 3m-3-3v6"/></symbol>
17
+ <symbol id="icon-shield" viewBox="0 0 24 24"><path d="m12 3 8 3v6c0 4-5 7-8 9-3-2-8-5-8-9V6Z"/><path d="m8 12 3 3 5-6"/></symbol>
18
+ <symbol id="icon-log" viewBox="0 0 24 24"><rect x="5" y="3" width="14" height="18" rx="2"/><path d="M9 8h6m-6 4h6m-6 4h3"/></symbol>
19
+ <symbol id="icon-memory" viewBox="0 0 24 24"><rect x="5" y="5" width="14" height="14" rx="2"/><path d="M9 9h6v6H9ZM9 2v3m6-3v3M9 19v3m6-3v3M2 9h3m-3 6h3m14-6h3m-3 6h3"/></symbol>
20
+ <symbol id="icon-disk" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="3"/><path d="M3 14h18m-5 3h1m-10 0h1"/></symbol>
21
+ <symbol id="icon-arrow" viewBox="0 0 24 24"><path d="M4 12h16m-6-6 6 6-6 6"/></symbol>
22
+ </defs></svg>
23
+ <header>
24
+ <a class="brand" href="/start" data-language-link><span class="brand-mark" aria-hidden="true"><svg viewBox="0 0 32 32"><path d="M4 22V5h17v17H4Zm7 5h17V10H11v17Z"/></svg></span><span>Device Center</span></a>
25
+ <div class="header-actions"><div class="language-picker" data-language-picker></div><span class="returning-user" data-i18n="guide.focus.alreadyInstalled">已接入?</span><a class="panel-link" href="/" data-language-link data-i18n-aria-label="guide.open" aria-label="进入设备面板"><span data-i18n="guide.visual.dashboard">设备面板</span><svg class="icon" aria-hidden="true"><use href="#icon-arrow"/></svg></a></div>
26
+ </header>
13
27
  <main>
14
- <section class="hero">
15
- <p class="eyebrow" data-i18n="guide.badge">公开试用 · 0.2.0-beta.3 · MIT</p>
16
- <h1 data-i18n="guide.hero">让你的 Agent 看见自己的闲置电脑。</h1>
17
- <p class="lead" data-i18n="guide.lead">查看设备资源、记住每台电脑的用途,并在单独授权后读取指定日志。把安装交给本机 Agent,从一台电脑开始。</p>
18
- <p class="platform" data-i18n="guide.platform">macOS / Linux · Node.js 24+ · Windows 原生版暂未支持</p>
19
- </section>
20
- <section aria-labelledby="install-title">
21
- <h2 id="install-title"><span class="step">1</span> <span data-i18n="guide.install">选一种方式,复制给本机 Agent</span></h2>
22
- <div class="choices">
23
- <article><h3 data-i18n="guide.local">本机使用</h3><p data-i18n="guide.localDesc">本机面板和 MCP,无需账号。适合先了解自己的电脑与已有设备资源。</p><button data-mode="local" data-i18n="guide.localCopy">复制本机安装说明</button></article>
24
- <article><h3 data-i18n="guide.hosted">官方托管</h3><p data-i18n="guide.hostedDesc">用自己的账号管理设备,使用官方加密中转。Driver 和私钥仍留在设备上。</p><button class="primary" data-mode="official" data-i18n="guide.hostedCopy">复制托管安装说明</button></article>
28
+ <section class="hero" aria-labelledby="hero-title">
29
+ <div class="hero-copy">
30
+ <p class="eyebrow"><span class="section-index" data-i18n="guide.focus.overview">产品介绍</span><span data-i18n="guide.visual.yourDevices">用好你自己的设备</span></p>
31
+ <h1 id="hero-title"><span data-i18n="guide.visual.heroLine1">少切电脑。</span><span data-i18n="guide.visual.heroLine2">少搬日志。</span></h1>
32
+ <p class="lead" data-i18n="guide.visual.lead">让本机 Agent 查看其他设备的状态与授权日志。</p>
33
+ <div class="hero-actions"><a class="button primary hero-cta" href="#install-title"><span data-i18n="guide.visual.begin">开始接入</span><svg class="icon" aria-hidden="true"><use href="#icon-arrow"/></svg></a><span class="cta-hint" data-i18n="guide.focus.firstTry">第一次用?先选本机,无需账号。</span></div>
34
+ <p class="platform" data-i18n="guide.platform">macOS / Linux / Windows 11 预览 · Node.js 24+</p>
35
+ <p class="release-note"><span class="release-dot" aria-hidden="true"></span><span data-i18n="guide.badge">开发预览 · 0.2.0-beta.5 · MIT</span></p>
25
36
  </div>
26
- <p class="hint" data-i18n="guide.paste">粘贴给正在目标电脑上运行、能安装软件的 Agent。它会检查已有配置、安装固定版本、配置用户级后台服务及 MCP,再核验真实结果。网页本身不会安装软件。</p>
27
- <details class="custom"><summary data-i18n="guide.custom">已有自己的控制面?</summary><label for="custom-origin" data-i18n="guide.address">自建控制面的 HTTPS 地址</label><div class="custom-row"><input id="custom-origin" type="url" placeholder="https://devices.example.com" autocomplete="off" spellcheck="false"><button data-mode="self-hosted" data-i18n="guide.customCopy">复制自建接入说明</button></div><p class="hint" data-i18n="guide.customDesc">连接已经部署的服务;不会替你部署服务器或迁移原有绑定。</p></details>
28
- <p id="copy-result" role="status" aria-live="polite"></p>
29
- <details id="instruction-details"><summary data-i18n="guide.preview">查看或手动复制完整说明</summary><textarea id="install-instructions" aria-label="Agent 安装说明" data-i18n-aria-label="guide.instructions" rows="9" readonly spellcheck="false"></textarea></details>
37
+ <figure class="scene" data-paused="false">
38
+ <figcaption class="scene-caption"><span><span class="caption-cross" aria-hidden="true">+</span><span data-i18n="guide.focus.example">工作原理示意 · 无需操作</span></span><button class="motion-toggle" type="button" data-i18n-aria-label="guide.visual.pauseMotion" data-i18n-title="guide.visual.pauseMotion" aria-label="暂停动效" title="暂停动效"><svg class="icon pause-icon" aria-hidden="true" viewBox="0 0 24 24"><path d="M8 5v14M16 5v14"/></svg><svg class="icon play-icon" aria-hidden="true" viewBox="0 0 24 24"><path d="m8 5 11 7-11 7Z"/></svg></button></figcaption>
39
+ <div class="scene-stage"><svg class="hardware-art" viewBox="0 0 640 510" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
40
+ <defs>
41
+ <linearGradient id="metal-top" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#939395"/><stop offset=".18" stop-color="#505054"/><stop offset=".7" stop-color="#37373b"/><stop offset="1" stop-color="#747478"/></linearGradient>
42
+ <linearGradient id="metal-left" x1="0" y1="0" x2="0" y2="1"><stop stop-color="#39393d"/><stop offset=".3" stop-color="#232326"/><stop offset="1" stop-color="#111113"/></linearGradient>
43
+ <linearGradient id="metal-right" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#1a1a1d"/><stop offset="1" stop-color="#080809"/></linearGradient>
44
+ <linearGradient id="copper-top" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#ffab76"/><stop offset=".45" stop-color="#f5773b"/><stop offset="1" stop-color="#bd4e27"/></linearGradient>
45
+ <radialGradient id="floor-fade"><stop stop-color="white"/><stop offset=".75" stop-color="#666"/><stop offset="1" stop-color="black"/></radialGradient>
46
+ <radialGradient id="copper-light"><stop stop-color="#fa7138" stop-opacity=".16"/><stop offset="1" stop-color="#fa7138" stop-opacity="0"/></radialGradient>
47
+ <mask id="floor-mask"><rect width="640" height="510" fill="url(#floor-fade)"/></mask>
48
+ <pattern id="deck-grid" width="56" height="32" patternUnits="userSpaceOnUse"><path d="M28 0 56 16 28 32 0 16Z" stroke="#39393b" stroke-width=".55" fill="none"/></pattern>
49
+ <filter id="unit-shadow" x="-60%" y="-100%" width="220%" height="300%"><feGaussianBlur stdDeviation="11"/></filter>
50
+ <filter id="wire-glow" x="-100%" y="-100%" width="300%" height="300%"><feGaussianBlur stdDeviation="3"/></filter>
51
+ </defs>
52
+ <ellipse cx="335" cy="320" rx="260" ry="180" fill="url(#copper-light)"/>
53
+ <g mask="url(#floor-mask)"><path d="M-10 280 330 82 680 284 330 510Z" fill="url(#deck-grid)"/><path d="M31 318 330 145 628 318 330 490Z" fill="none" stroke="#515154" stroke-opacity=".3" stroke-width=".7"/></g>
54
+ <g class="graph-wires" fill="none" stroke-linejoin="round" stroke-linecap="round">
55
+ <path d="M293 368 348 336 348 274 388 251" stroke="#ff763d" stroke-width="9" opacity=".12" filter="url(#wire-glow)"/>
56
+ <path d="M220 431 286 469 370 420 421 420" stroke="#ff763d" stroke-width="9" opacity=".1" filter="url(#wire-glow)"/>
57
+ <path d="M293 368 348 336 348 274 388 251" stroke="#78442d" stroke-width="3"/>
58
+ <path d="M220 431 286 469 370 420 421 420" stroke="#78442d" stroke-width="3"/>
59
+ <path class="signal signal-one" d="M293 368 348 336 348 274 388 251" stroke="#ff9a63" stroke-width="2"/>
60
+ <path class="signal signal-two" d="M220 431 286 469 370 420 421 420" stroke="#ff9a63" stroke-width="2"/>
61
+ <circle cx="348" cy="336" r="4" fill="#0b0b0c" stroke="#c56538" stroke-width="1.5"/>
62
+ <circle cx="286" cy="469" r="4" fill="#0b0b0c" stroke="#c56538" stroke-width="1.5"/>
63
+ </g>
64
+ <g class="hardware hardware-server"><polygon points="432.0,179.0 504.7,221.0 432.0,263.0 359.3,221.0" fill="#000" stroke="none" stroke-width=".7" filter="url(#unit-shadow)"/><polygon points="432.0,72.0 492.6,107.0 432.0,142.0 371.4,107.0" fill="url(#metal-top)" stroke="#58585b" stroke-width=".7" /><polygon points="371.4,107.0 432.0,142.0 432.0,250.0 371.4,215.0" fill="url(#metal-left)" stroke="#58585b" stroke-width=".7" /><polygon points="492.6,107.0 432.0,142.0 432.0,250.0 492.6,215.0" fill="url(#metal-right)" stroke="#58585b" stroke-width=".7" /><polygon points="432.0,77.7 482.2,106.7 432.0,135.7 381.8,106.7" fill="#262629" stroke="#737378" stroke-width=".7" /><ellipse cx="432.0" cy="81.0" rx="1.5" ry=".8" fill="#a0a0a2"/><ellipse cx="475.3" cy="106.0" rx="1.5" ry=".8" fill="#a0a0a2"/><ellipse cx="432.0" cy="131.0" rx="1.5" ry=".8" fill="#a0a0a2"/><ellipse cx="388.7" cy="106.0" rx="1.5" ry=".8" fill="#a0a0a2"/><path d="M371.4 211.0 L432.0 246.0 L492.6 211.0" stroke="#6d6d71" stroke-opacity=".5" stroke-width=".5" fill="none"/><path d="M371.4 111.0 L432.0 146.0 L492.6 111.0" stroke="#6d6d71" stroke-opacity=".5" stroke-width=".5" fill="none"/><polygon points="377.4,181.5 425.9,209.5 425.9,228.5 377.4,200.5" fill="#101012" stroke="#444447" stroke-width=".7" /><path d="M383.5 199.0 L383.5 191.0" stroke="#59595d" stroke-width=".65"/><path d="M387.0 201.0 L387.0 193.0" stroke="#59595d" stroke-width=".65"/><path d="M390.4 203.0 L390.4 195.0" stroke="#59595d" stroke-width=".65"/><path d="M393.9 205.0 L393.9 197.0" stroke="#59595d" stroke-width=".65"/><path d="M397.4 207.0 L397.4 199.0" stroke="#59595d" stroke-width=".65"/><path d="M400.8 209.0 L400.8 201.0" stroke="#59595d" stroke-width=".65"/><path d="M404.3 211.0 L404.3 203.0" stroke="#59595d" stroke-width=".65"/><path d="M407.8 213.0 L407.8 205.0" stroke="#59595d" stroke-width=".65"/><path d="M411.2 215.0 L411.2 207.0" stroke="#59595d" stroke-width=".65"/><path d="M414.7 217.0 L414.7 209.0" stroke="#59595d" stroke-width=".65"/><path d="M418.1 219.0 L418.1 211.0" stroke="#59595d" stroke-width=".65"/><ellipse cx="421.6" cy="216.0" rx="1.6" ry="1" fill="#db976c"/><polygon points="377.4,151.5 425.9,179.5 425.9,198.5 377.4,170.5" fill="#101012" stroke="#444447" stroke-width=".7" /><path d="M383.5 169.0 L383.5 161.0" stroke="#59595d" stroke-width=".65"/><path d="M387.0 171.0 L387.0 163.0" stroke="#59595d" stroke-width=".65"/><path d="M390.4 173.0 L390.4 165.0" stroke="#59595d" stroke-width=".65"/><path d="M393.9 175.0 L393.9 167.0" stroke="#59595d" stroke-width=".65"/><path d="M397.4 177.0 L397.4 169.0" stroke="#59595d" stroke-width=".65"/><path d="M400.8 179.0 L400.8 171.0" stroke="#59595d" stroke-width=".65"/><path d="M404.3 181.0 L404.3 173.0" stroke="#59595d" stroke-width=".65"/><path d="M407.8 183.0 L407.8 175.0" stroke="#59595d" stroke-width=".65"/><path d="M411.2 185.0 L411.2 177.0" stroke="#59595d" stroke-width=".65"/><path d="M414.7 187.0 L414.7 179.0" stroke="#59595d" stroke-width=".65"/><path d="M418.1 189.0 L418.1 181.0" stroke="#59595d" stroke-width=".65"/><ellipse cx="421.6" cy="186.0" rx="1.6" ry="1" fill="#db976c"/><polygon points="377.4,121.5 425.9,149.5 425.9,168.5 377.4,140.5" fill="#101012" stroke="#444447" stroke-width=".7" /><path d="M383.5 139.0 L383.5 131.0" stroke="#59595d" stroke-width=".65"/><path d="M387.0 141.0 L387.0 133.0" stroke="#59595d" stroke-width=".65"/><path d="M390.4 143.0 L390.4 135.0" stroke="#59595d" stroke-width=".65"/><path d="M393.9 145.0 L393.9 137.0" stroke="#59595d" stroke-width=".65"/><path d="M397.4 147.0 L397.4 139.0" stroke="#59595d" stroke-width=".65"/><path d="M400.8 149.0 L400.8 141.0" stroke="#59595d" stroke-width=".65"/><path d="M404.3 151.0 L404.3 143.0" stroke="#59595d" stroke-width=".65"/><path d="M407.8 153.0 L407.8 145.0" stroke="#59595d" stroke-width=".65"/><path d="M411.2 155.0 L411.2 147.0" stroke="#59595d" stroke-width=".65"/><path d="M414.7 157.0 L414.7 149.0" stroke="#59595d" stroke-width=".65"/><path d="M418.1 159.0 L418.1 151.0" stroke="#59595d" stroke-width=".65"/><ellipse cx="421.6" cy="156.0" rx="1.6" ry="1" fill="#db976c"/><path d="M481.4 204.5 L481.4 130.5" stroke="#070708" stroke-width="1.5"/><path d="M477.9 206.5 L477.9 132.5" stroke="#070708" stroke-width="1.5"/><path d="M474.4 208.5 L474.4 134.5" stroke="#070708" stroke-width="1.5"/><path d="M471.0 210.5 L471.0 136.5" stroke="#070708" stroke-width="1.5"/><path d="M467.5 212.5 L467.5 138.5" stroke="#070708" stroke-width="1.5"/><path d="M464.0 214.5 L464.0 140.5" stroke="#070708" stroke-width="1.5"/><path d="M460.6 216.5 L460.6 142.5" stroke="#070708" stroke-width="1.5"/><path d="M457.1 218.5 L457.1 144.5" stroke="#070708" stroke-width="1.5"/><path d="M453.6 220.5 L453.6 146.5" stroke="#070708" stroke-width="1.5"/><path d="M450.2 222.5 L450.2 148.5" stroke="#070708" stroke-width="1.5"/><path d="M446.7 224.5 L446.7 150.5" stroke="#070708" stroke-width="1.5"/><path d="M443.3 226.5 L443.3 152.5" stroke="#070708" stroke-width="1.5"/><path d="M439.8 228.5 L439.8 154.5" stroke="#070708" stroke-width="1.5"/></g><g class="hardware hardware-agent"><polygon points="218.0,335.0 323.7,396.0 218.0,457.0 112.3,396.0" fill="#000" stroke="none" stroke-width=".7" filter="url(#unit-shadow)"/><polygon points="218.0,281.0 311.5,335.0 218.0,389.0 124.5,335.0" fill="url(#metal-top)" stroke="#58585b" stroke-width=".7" /><polygon points="124.5,335.0 218.0,389.0 218.0,444.0 124.5,390.0" fill="url(#metal-left)" stroke="#58585b" stroke-width=".7" /><polygon points="311.5,335.0 218.0,389.0 218.0,444.0 311.5,390.0" fill="url(#metal-right)" stroke="#58585b" stroke-width=".7" /><polygon points="218.0,286.7 301.1,334.7 218.0,382.7 134.9,334.7" fill="#262629" stroke="#737378" stroke-width=".7" /><ellipse cx="218.0" cy="290.0" rx="1.5" ry=".8" fill="#a0a0a2"/><ellipse cx="294.2" cy="334.0" rx="1.5" ry=".8" fill="#a0a0a2"/><ellipse cx="218.0" cy="378.0" rx="1.5" ry=".8" fill="#a0a0a2"/><ellipse cx="141.8" cy="334.0" rx="1.5" ry=".8" fill="#a0a0a2"/><path d="M124.5 386.0 L218.0 440.0 L311.5 386.0" stroke="#6d6d71" stroke-opacity=".5" stroke-width=".5" fill="none"/><path d="M124.5 339.0 L218.0 393.0 L311.5 339.0" stroke="#6d6d71" stroke-opacity=".5" stroke-width=".5" fill="none"/><polygon points="218.0,304.0 270.0,334.0 218.0,364.0 166.0,334.0" fill="url(#copper-top)" stroke="#fc9f6c" stroke-width=".7" /><polyline points="201.5,324.5 217.1,315.5 232.7,324.5 217.1,333.5" fill="none" stroke="#321b13" stroke-width="3.5" stroke-linejoin="round"/><polyline points="202.4,341.0 218.0,332.0 233.6,341.0 218.0,350.0" fill="none" stroke="#321b13" stroke-width="3.5" stroke-linejoin="round"/><path d="M139.2 384.5 L139.2 369.5" stroke="#0a0a0b" stroke-width="2"/><path d="M144.4 387.5 L144.4 372.5" stroke="#0a0a0b" stroke-width="2"/><path d="M149.6 390.5 L149.6 375.5" stroke="#0a0a0b" stroke-width="2"/><path d="M154.8 393.5 L154.8 378.5" stroke="#0a0a0b" stroke-width="2"/><path d="M160.0 396.5 L160.0 381.5" stroke="#0a0a0b" stroke-width="2"/><path d="M165.2 399.5 L165.2 384.5" stroke="#0a0a0b" stroke-width="2"/><path d="M170.4 402.5 L170.4 387.5" stroke="#0a0a0b" stroke-width="2"/><path d="M175.6 405.5 L175.6 390.5" stroke="#0a0a0b" stroke-width="2"/><path d="M180.8 408.5 L180.8 393.5" stroke="#0a0a0b" stroke-width="2"/><path d="M186.0 411.5 L186.0 396.5" stroke="#0a0a0b" stroke-width="2"/><path d="M191.2 414.5 L191.2 399.5" stroke="#0a0a0b" stroke-width="2"/><path d="M196.3 417.5 L196.3 402.5" stroke="#0a0a0b" stroke-width="2"/><path d="M201.5 420.5 L201.5 405.5" stroke="#0a0a0b" stroke-width="2"/><path d="M206.7 423.5 L206.7 408.5" stroke="#0a0a0b" stroke-width="2"/><polygon points="298.5,374.5 276.9,387.0 276.9,398.0 298.5,385.5" fill="#070708" stroke="#343437" stroke-width=".7" /><ellipse cx="269.1" cy="396.5" rx="2.5" ry="1.5" fill="#ff8a4c"/></g><g class="hardware hardware-mini"><polygon points="470.0,389.0 557.5,439.5 493.4,476.5 405.9,426.0" fill="#000" stroke="none" stroke-width=".7" filter="url(#unit-shadow)"/><polygon points="470.0,358.0 545.3,401.5 493.4,431.5 418.0,388.0" fill="url(#metal-top)" stroke="#58585b" stroke-width=".7" /><polygon points="418.0,388.0 493.4,431.5 493.4,463.5 418.0,420.0" fill="url(#metal-left)" stroke="#58585b" stroke-width=".7" /><polygon points="545.3,401.5 493.4,431.5 493.4,463.5 545.3,433.5" fill="url(#metal-right)" stroke="#58585b" stroke-width=".7" /><polygon points="470.0,363.7 535.0,401.2 493.4,425.2 428.4,387.7" fill="#262629" stroke="#737378" stroke-width=".7" /><ellipse cx="470.0" cy="367.0" rx="1.5" ry=".8" fill="#a0a0a2"/><ellipse cx="528.0" cy="400.5" rx="1.5" ry=".8" fill="#a0a0a2"/><ellipse cx="493.4" cy="420.5" rx="1.5" ry=".8" fill="#a0a0a2"/><ellipse cx="435.4" cy="387.0" rx="1.5" ry=".8" fill="#a0a0a2"/><path d="M418.0 416.0 L493.4 459.5 L545.3 429.5" stroke="#6d6d71" stroke-opacity=".5" stroke-width=".5" fill="none"/><path d="M418.0 392.0 L493.4 435.5 L545.3 405.5" stroke="#6d6d71" stroke-opacity=".5" stroke-width=".5" fill="none"/><path d="M432.8 419.5 L432.8 408.5" stroke="#0b0b0d" stroke-width="1.8"/><path d="M437.1 422.0 L437.1 411.0" stroke="#0b0b0d" stroke-width="1.8"/><path d="M441.4 424.5 L441.4 413.5" stroke="#0b0b0d" stroke-width="1.8"/><path d="M445.8 427.0 L445.8 416.0" stroke="#0b0b0d" stroke-width="1.8"/><path d="M450.1 429.5 L450.1 418.5" stroke="#0b0b0d" stroke-width="1.8"/><path d="M454.4 432.0 L454.4 421.0" stroke="#0b0b0d" stroke-width="1.8"/><path d="M458.7 434.5 L458.7 423.5" stroke="#0b0b0d" stroke-width="1.8"/><path d="M463.1 437.0 L463.1 426.0" stroke="#0b0b0d" stroke-width="1.8"/><path d="M467.4 439.5 L467.4 428.5" stroke="#0b0b0d" stroke-width="1.8"/><path d="M471.7 442.0 L471.7 431.0" stroke="#0b0b0d" stroke-width="1.8"/><path d="M476.1 444.5 L476.1 433.5" stroke="#0b0b0d" stroke-width="1.8"/><path d="M480.4 447.0 L480.4 436.0" stroke="#0b0b0d" stroke-width="1.8"/><polygon points="473.5,372.0 519.4,398.5 489.9,415.5 444.0,389.0" fill="#333337" stroke="#5e5e62" stroke-width=".7" /><ellipse cx="532.4" cy="428.0" rx="2" ry="1.2" fill="#bcbcc0"/></g><g fill="none" stroke="#77777b" stroke-width=".65" opacity=".65"><path d="M145 322 109 301H30"/><circle cx="145" cy="322" r="2"/><path d="M492 157 549 124h54"/><circle cx="492" cy="157" r="2"/><path d="m515 458 22 13h49"/></g>
65
+ <g fill="#747477" font-family="monospace" font-size="8" letter-spacing="1"><text x="50" y="415">DC / 01</text><text x="540" y="195">02</text><text x="540" y="490">03</text></g>
66
+ <g stroke="#57575b" stroke-width=".65"><path d="M27 120h8m-4-4v8M598 370h8m-4-4v8M353 64h8m-4-4v8"/></g>
67
+ </svg>
68
+ <div class="graph-label graph-label-agent"><strong data-i18n="guide.visual.myAgent">本机 Agent</strong><span data-i18n="guide.visual.myComputer">你正在用的电脑</span></div>
69
+ <div class="graph-label graph-label-target"><strong data-i18n="guide.visual.otherDevice">自己的设备</strong><span data-i18n="guide.visual.homeServer">例如:家里的服务器</span></div>
70
+ </div>
71
+ <div class="scene-query"><span class="query-symbol" aria-hidden="true">&gt;_</span><blockquote data-i18n="guide.visual.request">“帮我看看家里服务器的日志。”</blockquote><span class="query-arrow" aria-hidden="true">↵</span></div>
72
+ <div class="scene-resources"><span><svg class="icon" aria-hidden="true"><use href="#icon-memory"/></svg><span data-i18n="panel.8">内存</span></span><span><svg class="icon" aria-hidden="true"><use href="#icon-disk"/></svg><span data-i18n="panel.9">磁盘</span></span><span><svg class="icon" aria-hidden="true"><use href="#icon-log"/></svg><span data-i18n="guide.visual.log">授权日志</span></span></div>
73
+ </figure>
30
74
  </section>
31
- <section aria-labelledby="pair-title">
32
- <h2 id="pair-title"><span class="step">2</span> <span data-i18n="guide.pair">使用托管时,登录并认领自己的电脑</span></h2>
33
- <ol><li><span data-i18n="guide.pair1">按 Agent 提示打开设备面板,使用自己的 App Services/个人站账号登录。没有账号时在账号页注册。</span> <a href="https://devices.owenshen.top/login" data-hosted-dashboard data-i18n="guide.loginLink">打开云端设备面板</a></li><li data-i18n="guide.pair2">在面板输入本机终端显示的五分钟配对码,核对完整公钥指纹后批准。密码、私钥和配对码不要发给别人或贴到 Agent 对话。</li><li data-i18n="guide.pair3">在另一台自己的电脑重复安装并绑定同一账号。没有真实回报的设备会显示未知或未回报。</li></ol>
34
- <p class="hint" data-i18n="guide.localHint">本机模式跳过此步。绑定只允许已说明的资源回报;读取日志需要目标设备另外授权。</p>
75
+ <div class="scenarios" aria-label="使用场景" data-i18n-aria-label="guide.visual.scenarios">
76
+ <div><svg class="icon" aria-hidden="true"><use href="#icon-log"/></svg><p><strong data-i18n="guide.visual.debug">服务报错时</strong><span data-i18n="guide.visual.debugBody">让 Agent 读取已授权日志</span></p></div>
77
+ <div><svg class="icon" aria-hidden="true"><use href="#icon-memory"/></svg><p><strong data-i18n="guide.visual.save">买云资源前</strong><span data-i18n="guide.visual.saveBody">先看看已有电脑的资源</span></p></div>
78
+ <div><svg class="icon" aria-hidden="true"><use href="#icon-cloud"/></svg><p><strong data-i18n="guide.visual.away">出门在外时</strong><span data-i18n="guide.visual.awayBody">设备在线,就能查看回报状态</span></p></div>
79
+ </div>
80
+ <section class="install-section" aria-labelledby="install-title">
81
+ <div class="section-heading">
82
+ <p class="eyebrow"><span class="action-marker" aria-hidden="true">↘</span><span data-i18n="guide.focus.start">开始操作</span></p>
83
+ <h2 id="install-title" tabindex="-1" data-i18n="guide.focus.startTitle">接入,从这里开始</h2>
84
+ <p class="section-intro" data-i18n="guide.focus.startBody">选好使用方式,再选交给 Agent 或手动安装。目标设备没有 Agent 也能接入。</p>
85
+ <p class="section-note" data-i18n="guide.visual.noInstall">复制不会执行安装</p>
86
+ </div>
87
+ <div class="action-workbench">
88
+ <fieldset class="mode-options">
89
+ <legend><span class="action-number" aria-hidden="true">01</span><span data-i18n="guide.focus.select">选择使用方式</span></legend>
90
+ <div class="choices">
91
+ <label class="mode-choice">
92
+ <input type="radio" name="install-mode" value="local" checked>
93
+ <span class="choice-icon"><svg class="icon" aria-hidden="true"><use href="#icon-computer"/></svg></span>
94
+ <span class="choice-copy"><strong data-i18n="guide.local">本机使用</strong><span data-i18n="guide.visual.localBody">本机面板 + MCP,无需账号。</span></span>
95
+ </label>
96
+ <label class="mode-choice">
97
+ <input type="radio" name="install-mode" value="official">
98
+ <span class="choice-icon"><svg class="icon" aria-hidden="true"><use href="#icon-cloud"/></svg></span>
99
+ <span class="choice-copy"><strong data-i18n="guide.hosted">官方托管</strong><span data-i18n="guide.visual.hostedBody">用自己的账号绑定设备,使用加密中转。</span></span>
100
+ </label>
101
+ </div>
102
+ <details class="custom">
103
+ <summary data-i18n="guide.custom">已有自己的控制面?</summary>
104
+ <label class="self-hosted-choice"><input type="radio" name="install-mode" value="self-hosted"><span data-i18n="guide.focus.useSelfHosted">使用我的自建服务</span></label>
105
+ <div id="custom-fields" hidden>
106
+ <label for="custom-origin" data-i18n="guide.address">自建控制面的 HTTPS 地址</label>
107
+ <input id="custom-origin" type="url" placeholder="https://devices.example.com" aria-describedby="copy-result" autocomplete="off" spellcheck="false">
108
+ <p class="hint" data-i18n="guide.customDesc">连接已经部署的服务;不会替你部署服务器或迁移原有绑定。</p>
109
+ </div>
110
+ </details>
111
+ </fieldset>
112
+ <div class="copy-step">
113
+ <fieldset class="pairing-options" id="os-options">
114
+ <legend data-i18n="guide.os.title">目标电脑的系统</legend>
115
+ <div class="pairing-choices">
116
+ <label><input type="radio" name="install-os" value="posix" checked><span>macOS / Linux</span></label>
117
+ <label><input type="radio" name="install-os" value="windows"><span>Windows 11</span></label>
118
+ </div>
119
+ </fieldset>
120
+ <p class="hint" data-i18n="guide.windowsCandidate">Windows 11 预览版 · 使用 PowerShell 安装,按图示核对绑定与后台状态。</p>
121
+ <fieldset class="method-options">
122
+ <legend class="action-heading"><span class="action-number" aria-hidden="true">02</span><span data-i18n="guide.method.title">选择安装方式</span></legend>
123
+ <div class="method-switch">
124
+ <label><input type="radio" name="install-method" value="agent" checked><span data-i18n="guide.method.agent">交给 Agent</span></label>
125
+ <label><input type="radio" name="install-method" value="manual"><span data-i18n="guide.method.manual">手动安装</span></label>
126
+ </div>
127
+ </fieldset>
128
+ <div id="manual-options" hidden>
129
+ <p class="hint manual-prerequisites" id="manual-prerequisites" data-i18n="guide.manual.prerequisites">Node.js 24+ · 全新安装;已有安装会停止并保留配置。</p>
130
+ <fieldset class="pairing-options" id="pairing-options" hidden>
131
+ <legend data-i18n="guide.manual.browserQuestion">目标设备能打开浏览器吗?</legend>
132
+ <div class="pairing-choices">
133
+ <label><input type="radio" name="pairing-method" value="browser" checked><span data-i18n="guide.manual.hasBrowser">有浏览器</span></label>
134
+ <label><input type="radio" name="pairing-method" value="headless"><span data-i18n="guide.manual.noBrowser">无浏览器 / 服务器</span></label>
135
+ </div>
136
+ </fieldset>
137
+ </div>
138
+ <button class="primary" id="copy-instructions" type="button"><svg class="icon" aria-hidden="true"><use href="#icon-copy"/></svg><span id="copy-label" data-i18n="guide.localCopy">复制本机安装说明</span><svg class="icon copy-arrow" aria-hidden="true"><use href="#icon-arrow"/></svg></button>
139
+ <p class="copy-hint" id="copy-hint" data-i18n="guide.focus.copyHint">这是给 Agent 的说明,请粘贴到 Agent 对话中,而不是终端。</p>
140
+ <p id="copy-result" role="status" aria-live="polite" aria-atomic="true"></p>
141
+ <details id="instruction-details"><summary id="instruction-preview" data-i18n="guide.preview">查看或手动复制完整说明</summary><textarea id="install-instructions" aria-label="Agent 安装说明" data-i18n-aria-label="guide.instructions" rows="9" readonly spellcheck="false"></textarea></details>
142
+ </div>
143
+ <div class="next-action" id="next-action" data-copied="false">
144
+ <h3 class="action-heading" id="walkthrough-title" tabindex="-1"><span class="action-number" aria-hidden="true">03</span><span data-i18n="walk.title">复制后,照图操作</span></h3>
145
+ <p class="walkthrough-disclaimer" data-i18n="walk.disclaimer">以下是操作示意;请在对应电脑的真实窗口操作。</p>
146
+ <div id="install-walkthrough"></div>
147
+ <a class="binding-link" href="https://devices.owenshen.top/login" data-hosted-dashboard data-i18n="guide.focus.openHosted" hidden>安装后,打开面板完成本人绑定 ↗</a>
148
+ </div>
149
+ </div>
35
150
  </section>
36
- <section aria-labelledby="try-title">
37
- <h2 id="try-title"><span class="step">3</span> <span data-i18n="guide.try">试用时看这四件事</span></h2>
38
- <div class="checks"><div><h3 data-i18n="guide.resources">资源真实吗?</h3><p data-i18n="guide.resourcesBody">分别查看内存、磁盘和最近采集时间。</p></div><div><h3 data-i18n="guide.names">设备容易辨认吗?</h3><p data-i18n="guide.namesBody">在“设备说明”填写名称、用途、标签。服务清单由你登记。</p></div><div><h3 data-i18n="guide.refresh">更新状态顺畅吗?</h3><p data-i18n="guide.refreshBody">点“采集一次”;休眠或停止的 Driver 不会被伪装在线。旧 Driver 需要更新。</p></div><div><h3 data-i18n="guide.agent">Agent 能读清单吗?</h3><p data-i18n="guide.agentBody">让本机 Agent 调用 device_center_list_resources,核对设备名称与用途。</p></div></div>
39
- <details><summary data-i18n="guide.faq">遇到问题先查什么?</summary><p data-i18n="guide.faqBody">确认 Node.js 为 24+、Driver 正在运行、电脑已唤醒、网络及系统时间正常。</p><p data-i18n="guide.feedback">反馈时说明系统、版本、所选模式、失败步骤和去掉敏感信息的错误。</p></details>
151
+ <p class="trust-line"><svg class="icon" aria-hidden="true"><use href="#icon-shield"/></svg><span data-i18n="guide.visual.trust">私钥留在设备上 · 读取日志需要另外授权</span></p>
152
+ <section class="more-section" aria-label="详细说明" data-i18n-aria-label="guide.visual.details">
153
+ <details><summary data-i18n="guide.visual.pairDetails">安装和绑定的详细步骤</summary><div class="detail-content"><p data-i18n="guide.paste"></p><ol><li data-i18n="guide.pair1"></li><li data-i18n="guide.pair2"></li><li data-i18n="guide.pair3"></li></ol><p class="hint" data-i18n="guide.localHint"></p></div></details>
154
+ <details><summary data-i18n="guide.try">试用时看这四件事</summary><div class="detail-content checks"><div><h3 data-i18n="guide.resources"></h3><p data-i18n="guide.resourcesBody"></p></div><div><h3 data-i18n="guide.names"></h3><p data-i18n="guide.namesBody"></p></div><div><h3 data-i18n="guide.refresh"></h3><p data-i18n="guide.refreshBody"></p></div><div><h3 data-i18n="guide.agent"></h3><p data-i18n="guide.agentBody"></p></div></div></details>
155
+ <details><summary data-i18n="guide.scope">这版的范围</summary><div class="detail-content"><p data-i18n="guide.scopeBody"></p><p data-i18n="guide.quota"></p></div></details>
156
+ <details><summary data-i18n="guide.faq">遇到问题先查什么?</summary><div class="detail-content"><p data-i18n="guide.faqBody"></p><p data-i18n="guide.feedback"></p></div></details>
40
157
  </section>
41
- <aside><strong data-i18n="guide.scope">这版的范围</strong><p data-i18n="guide.scopeBody">支持真实资源、设备说明、手动采集和经单独授权的日志尾读。</p><p data-i18n="guide.quota">官方中转为试用额度:每账号每日 100 MiB(UTC),每方向 16 KiB/s;不是无限带宽服务。</p></aside>
42
158
  </main>
43
- <footer><a href="/install/agent.md" data-i18n="guide.full">Agent 完整安装指南(中文)</a><a href="/install/trial.md" data-i18n="guide.markdown">文字版试用指南(中文)</a><a href="https://www.npmjs.com/package/device-center/v/0.2.0-beta.3" target="_blank" rel="noopener noreferrer" data-i18n="guide.source">MIT npm 源码包 ↗</a><span data-i18n="guide.footer">本机安装 · 私钥留在设备 · 操作单独授权</span></footer>
159
+ <footer><a href="/install/agent.md" data-i18n="guide.full">Agent 完整安装指南(中文)</a><a href="/install/trial.md" data-i18n="guide.markdown">文字版试用指南(中文)</a><a href="https://www.npmjs.com/package/device-center/v/0.2.0-beta.5" target="_blank" rel="noopener noreferrer" data-i18n="guide.source">MIT npm 源码包 ↗</a><span data-i18n="guide.footer">本机安装 · 私钥留在设备 · 操作单独授权</span></footer>
44
160
  <script type="module" src="/src/start.js"></script>
45
161
  </body>
46
162
  </html>