device-center 0.2.0-beta.4 → 0.2.0-beta.6
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/CONTRIBUTING.md +1 -1
- package/README.md +25 -8
- package/SECURITY.md +14 -2
- package/bin/device-center.mjs +29 -5
- package/docs/agent-install.md +5 -5
- package/docs/cli.md +97 -0
- package/docs/driver-binding.md +5 -5
- package/docs/friend-trial.md +5 -5
- package/docs/hosted-access.md +1 -1
- package/docs/npm.md +12 -10
- package/docs/onboarding.md +4 -4
- package/docs/remote-access.md +2 -2
- package/docs/roadmap.md +13 -1
- package/docs/security-and-sync.md +2 -0
- package/docs/troubleshooting.md +11 -0
- package/docs/windows-acceptance.md +57 -0
- package/login.html +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +4 -2
- package/scripts/access.mjs +0 -1
- package/scripts/driver.mjs +2 -3
- package/scripts/service.mjs +42 -16
- package/server/cli/commands.mjs +181 -0
- package/server/cloudAccess.mjs +1 -1
- package/server/drivers/access.mjs +15 -14
- package/server/drivers/browserPairing.mjs +9 -4
- package/server/drivers/cliClient.mjs +87 -0
- package/server/drivers/cliManagement.mjs +119 -0
- package/server/drivers/cloudLink.mjs +19 -20
- package/server/drivers/cloudRegistry.mjs +6 -0
- package/server/drivers/privateFiles.mjs +57 -0
- package/server/drivers/windowsSecurity.mjs +119 -0
- package/server/drivers/windowsService.mjs +77 -0
- package/server/index.mjs +36 -8
- package/server/unifiedAccess.mjs +24 -4
- package/src/api.js +2 -0
- package/src/cli-approval.js +49 -0
- package/src/cli-handoff.js +19 -0
- package/src/install-prompt.js +2 -2
- package/src/install-walkthrough.js +6 -6
- package/src/login-flow.js +37 -0
- package/src/login.css +3 -1
- package/src/login.js +15 -10
- package/src/main.js +7 -6
- package/src/messages.js +359 -117
- package/src/onboarding.js +35 -15
- package/src/pairing-handoff.js +6 -2
- package/src/start.js +10 -2
- package/src/styles.css +1 -0
- package/start.html +12 -4
package/src/onboarding.js
CHANGED
|
@@ -2,8 +2,8 @@ import { localizedInstallPrompt } from './install-prompt.js';
|
|
|
2
2
|
import { normalizeLocale } from './i18n.js';
|
|
3
3
|
|
|
4
4
|
export const OFFICIAL_CONTROL_ORIGIN = 'https://devices.owenshen.top';
|
|
5
|
-
//
|
|
6
|
-
export const PUBLISHED_DRIVER_VERSION = '0.2.0-beta.
|
|
5
|
+
// Publish this exact CLI before deploying the matching guide.
|
|
6
|
+
export const PUBLISHED_DRIVER_VERSION = '0.2.0-beta.6';
|
|
7
7
|
const inputError = (key, message) => Object.assign(new Error(message), { translationKey: key });
|
|
8
8
|
export function connectCommand(value, shareSsh = false) {
|
|
9
9
|
let url;
|
|
@@ -15,16 +15,16 @@ export function connectCommand(value, shareSsh = false) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
// A reproducible handoff, not an installer. Reading/copying it has no machine effects.
|
|
18
|
-
export function agentInstallPlan({ mode = 'local', origin, port = 5174, background = true } = {}) {
|
|
19
|
-
if (!['local', 'official', 'self-hosted'].includes(mode) || !Number.isInteger(port) || port < 1024 || port > 65535 || typeof background !== 'boolean') throw inputError('error.install', '
|
|
18
|
+
export function agentInstallPlan({ mode = 'local', origin, port = 5174, background = true, os = 'posix' } = {}) {
|
|
19
|
+
if (!['local', 'official', 'self-hosted'].includes(mode) || !['posix', 'windows'].includes(os) || !Number.isInteger(port) || port < 1024 || port > 65535 || typeof background !== 'boolean') throw inputError('error.install', '安装模式、系统或本机端口无效。');
|
|
20
20
|
const controlOrigin = mode === 'local' ? null : mode === 'official' ? OFFICIAL_CONTROL_ORIGIN : origin;
|
|
21
21
|
if (controlOrigin) connectCommand(controlOrigin); // Same strict HTTPS and shell-safe origin validation.
|
|
22
22
|
if (mode === 'self-hosted' && !controlOrigin) throw inputError('error.custom', '请输入自建控制面的 HTTPS 地址。');
|
|
23
23
|
if (origin !== undefined && mode !== 'self-hosted') throw inputError('error.selfOnly', '只有自建模式可以填写控制面地址。');
|
|
24
|
-
const cli = '"$HOME/.local/bin/device-center"';
|
|
24
|
+
const cli = os === 'windows' ? '& "$HOME\\AppData\\Local\\Programs\\Device Center\\cli\\device-center.cmd"' : '"$HOME/.local/bin/device-center"';
|
|
25
25
|
const url = `http://127.0.0.1:${port}`;
|
|
26
|
-
return { mode, version: PUBLISHED_DRIVER_VERSION, controlOrigin, background, url, mcpUrl: `${url}/mcp`, commands: {
|
|
27
|
-
install: `npm install --global --prefix "$HOME/.local" --ignore-scripts device-center@${PUBLISHED_DRIVER_VERSION}`,
|
|
26
|
+
return { mode, os, version: PUBLISHED_DRIVER_VERSION, controlOrigin, background, url, mcpUrl: `${url}/mcp`, commands: {
|
|
27
|
+
install: os === 'windows' ? `npm.cmd install --global --prefix "$HOME\\AppData\\Local\\Programs\\Device Center\\cli" --ignore-scripts device-center@${PUBLISHED_DRIVER_VERSION}` : `npm install --global --prefix "$HOME/.local" --ignore-scripts device-center@${PUBLISHED_DRIVER_VERSION}`,
|
|
28
28
|
version: `${cli} --version`, bindingStatus: `${cli} status`,
|
|
29
29
|
serviceStatus: `${cli} service status --port ${port}`, servicePlan: `${cli} service plan --port ${port}`,
|
|
30
30
|
serviceInstall: `${cli} service install --port ${port} --confirm`,
|
|
@@ -44,13 +44,13 @@ export function agentInstallPrompt(options) {
|
|
|
44
44
|
${plan.controlOrigin ? `控制面:${plan.controlOrigin}。设备 Driver 仍在本机运行,身份私钥不托管到云端。\n` : ''}版本固定为 ${plan.version};${plan.background ? '安装用户级后台服务,登录系统后自动启动' : '仅前台运行,不安装后台服务'}。
|
|
45
45
|
|
|
46
46
|
请按以下顺序执行并核验实际结果,不要只给我命令:
|
|
47
|
-
1.
|
|
47
|
+
1. 检查${plan.os === 'windows' ? ' Windows 11、PowerShell 5.1+(普通用户窗口,非 WSL)、Node.js >=24、npm.cmd' : ' macOS/Linux、Node.js >=24、npm'}和当前智能体的 MCP 支持。目标系统与我的选择不符时停止并解释。缺少 Node.js 时使用官方来源;遇到管理员权限或系统授权再让我处理。
|
|
48
48
|
2. 先检查已有 device-center、绑定、用户服务及 ${plan.url}/api/health,保留原配置。CLI 已在固定位置时可运行 ${c.bindingStatus} 和 ${c.serviceStatus};不要打开身份私钥文件。端口被其他程序使用时不要停止它,改用空闲的已知本机端口并同步后续地址。已有服务或绑定时先核对版本、控制面和启动方式,匹配则复用;冲突时解释并让我选择,不能覆盖、解绑或重复安装。
|
|
49
49
|
3. 对全新安装使用用户可写的固定位置,无需 sudo:
|
|
50
50
|
${c.install}
|
|
51
51
|
${c.version}
|
|
52
|
-
不修改全局 npm 配置或 shell 启动文件;后续使用上面的绝对命令路径。
|
|
53
|
-
4. ${plan.background ? `先查看并核对后台服务计划:\n ${c.servicePlan}\n 确认是用户级、只监听本机回环后安装:\n ${c.serviceInstall}\n 再核验:${c.serviceStatus}\n
|
|
52
|
+
${plan.os === 'windows' ? '在 PowerShell 中逐步执行,检查每个原生命令的 $LASTEXITCODE,非零立即停止;不运行 npm.ps1、不修改执行策略。' : ''}不修改全局 npm 配置或 shell 启动文件;后续使用上面的绝对命令路径。
|
|
53
|
+
4. ${plan.background ? `先查看并核对后台服务计划:\n ${c.servicePlan}\n 确认是用户级、只监听本机回环后安装:\n ${c.serviceInstall}\n 再核验:${c.serviceStatus}\n 若配置已存在或健康检查失败,保留现场并排查,不自动清理重装。${plan.os === 'windows' ? 'Windows 使用当前用户的任务计划程序,只在登录后运行;不要求 AD、管理员、密码或系统服务。组策略阻止时提示前台模式,不提权或绕过。身份由本机 DPAPI 当前用户加密,NTFS 目录限当前用户和 SYSTEM;不将身份文件复制到其他用户或电脑。注销、休眠和断网不能持续回报。' : 'Linux 需要可用的 systemd --user,macOS 需要当前登录用户的 launchd;不支持时如实提示可改用前台模式。'}` : `启动并保持前台进程:\n ${plan.controlOrigin ? '先在下一步完成 connect;它会在批准后启动本机 Driver。' : c.foreground}\n 关闭网页不会停止进程;关闭终端会停止回报。`}
|
|
54
54
|
5. ${plan.controlOrigin ? `查看云端回报范围,然后申请绑定:\n ${c.pairingPlan}\n ${c.connect}\n 由你执行 --browser 接入并保持等待进程;它自动打开这台电脑的浏览器,临时登记凭据不输出到对话。只请我在打开的页面登录自己的账号,核对上面输出的完整公钥指纹并批准一次。不要再让我去终端执行同一命令,也不要要求我回对话回复“已批准”:你持续观察进程,批准后自动核验 firstReport.verified=true,并继续 MCP 配置。不要代我批准新增设备访问权限,不读取账号密码、cookie 或私钥,不尝试获取浏览器链接。不能仅凭本地 configured=true 声称云端连通。若此电脑没有浏览器或打开失败,再说明原因,让我在本机终端运行去掉 --browser 的 connect;配对码留在本机终端,不贴进对话。若控制面不支持新版浏览器交接,先提示升级控制面,不自动降级输出凭据。` : '不执行 connect、不生成设备身份或申请账号。若发现已有云端绑定,先说明 start/后台服务可能恢复它;不能把已有绑定伪装成本机专用模式。'}
|
|
55
55
|
6. 读取 ${plan.url}/api/connector/setup,使用当前智能体支持的官方 MCP 配置接口添加 Streamable HTTP 地址 ${plan.mcpUrl}。复用一致的配置,不覆盖其他 MCP。若当前工具不能配置,只让我粘贴这一个地址;不要编造已接通。
|
|
56
56
|
7. 核验 /api/health、/api/overview 的本机真实内存与磁盘,以及智能体实际调用 device_center_list_resources。最后报告版本、面板/MCP 地址、前台或后台状态、云端绑定结果(如适用)与尚未完成的步骤。后台卸载命令仅供我需要时使用:${c.uninstallService},默认保留数据和身份。
|
|
@@ -64,11 +64,12 @@ ${plan.controlOrigin ? `控制面:${plan.controlOrigin}。设备 Driver 仍在
|
|
|
64
64
|
export function terminalInstallScript({ browser = true, ...options } = {}) {
|
|
65
65
|
if (typeof browser !== 'boolean') throw inputError('error.install', '浏览器接入选项无效。');
|
|
66
66
|
const plan = agentInstallPlan(options), c = plan.commands;
|
|
67
|
+
if (plan.os === 'windows') return windowsInstallScript(plan, { browser });
|
|
67
68
|
// Headless targets use the same pairing protocol, with the code shown only at runtime.
|
|
68
69
|
const connect = c.connect && (browser ? c.connect : c.connect.replace(' --browser', ''));
|
|
69
70
|
return `(
|
|
70
71
|
set -eu
|
|
71
|
-
case "$(uname -s)" in Darwin|Linux) ;; *) printf '%s\\n' '
|
|
72
|
+
case "$(uname -s)" in Darwin|Linux) ;; *) printf '%s\\n' 'Choose the Windows PowerShell installer for Windows; this block is macOS / Linux only.'; exit 1 ;; esac
|
|
72
73
|
command -v node >/dev/null 2>&1 && command -v npm >/dev/null 2>&1 || { printf '%s\\n' 'Install Node.js 24+ and npm from nodejs.org, then retry.'; exit 1; }
|
|
73
74
|
node -e 'if (Number(process.versions.node.split(".")[0]) < 24) { console.error("Node.js 24+ required"); process.exit(1); }'
|
|
74
75
|
if command -v device-center >/dev/null 2>&1 || [ -e "$HOME/.local/bin/device-center" ] || [ -L "$HOME/.local/bin/device-center" ] || [ -e "$HOME/.local/lib/node_modules/device-center" ]; then
|
|
@@ -81,10 +82,29 @@ ${plan.background ? `${c.servicePlan}\n${c.serviceInstall}\n${c.serviceStatus}`
|
|
|
81
82
|
${plan.controlOrigin ? `${c.pairingPlan}\n${connect}` : plan.background ? `printf '%s\\n' 'Panel: ${plan.url}' 'MCP: ${plan.mcpUrl}'` : c.foreground}
|
|
82
83
|
)`;
|
|
83
84
|
}
|
|
84
|
-
export function
|
|
85
|
+
export function windowsInstallScript(plan, { browser = true } = {}) {
|
|
86
|
+
const c = plan.commands;
|
|
87
|
+
const checked = command => `${command}\nif ($LASTEXITCODE -ne 0) { throw 'Device Center command failed. Keep this window and inspect the error; no automatic reinstall.' }`;
|
|
88
|
+
const connect = c.connect && (browser ? c.connect : c.connect.replace(' --browser', ''));
|
|
89
|
+
return `& {
|
|
90
|
+
$ErrorActionPreference = 'Stop'
|
|
91
|
+
if ([Environment]::OSVersion.Platform -ne 'Win32NT' -or $PSVersionTable.PSVersion -lt [Version]'5.1') { throw 'Use Windows 11 PowerShell 5.1+ (not WSL).' }
|
|
92
|
+
if (-not (Get-Command node.exe -ErrorAction SilentlyContinue) -or -not (Get-Command npm.cmd -ErrorAction SilentlyContinue)) { throw 'Install Node.js 24+ from nodejs.org, reopen PowerShell, then retry.' }
|
|
93
|
+
node.exe -e 'if (parseInt(process.versions.node) < 24) process.exit(1)'
|
|
94
|
+
if ($LASTEXITCODE -ne 0) { throw 'Node.js 24+ required.' }
|
|
95
|
+
$prefix = Join-Path $HOME 'AppData\\Local\\Programs\\Device Center\\cli'
|
|
96
|
+
if ((Get-Command device-center -ErrorAction SilentlyContinue) -or (Test-Path -LiteralPath $prefix)) { throw 'Device Center already exists. Use status and service status first; reuse it or explicitly upgrade. This script did not change it.' }
|
|
97
|
+
${checked(c.install)}
|
|
98
|
+
${checked(c.version)}
|
|
99
|
+
${plan.background ? [c.servicePlan, c.serviceInstall, c.serviceStatus].map(checked).join('\n') : ''}
|
|
100
|
+
${plan.controlOrigin ? [c.pairingPlan, connect].map(checked).join('\n') : plan.background ? `Write-Host 'Panel: ${plan.url}' 'MCP: ${plan.mcpUrl}'` : checked(c.foreground)}
|
|
101
|
+
}`;
|
|
102
|
+
}
|
|
103
|
+
export function logAccessCommands({ manager, target, path, label = 'app', os = 'posix', managerOs = 'posix' }) {
|
|
85
104
|
if (![manager, target].every(value => typeof value === 'string' && /^[a-f0-9]{64}$/.test(value)) || manager === target) throw inputError('error.devices', '请选择两台不同的已绑定设备。');
|
|
86
|
-
if (typeof path !== 'string' || !path.startsWith('/') || path.length > 4096 || /[\x00-\x1f\x7f]/.test(path)) throw inputError('error.path', '填写目标设备上日志的绝对路径。');
|
|
105
|
+
if (![os, managerOs].every(value => ['posix', 'windows'].includes(value)) || typeof path !== 'string' || !(os === 'windows' ? /^[A-Za-z]:\\/.test(path) && !/[<>:"|?*]/.test(path.slice(2)) : path.startsWith('/')) || path.length > 4096 || /[\x00-\x1f\x7f]/.test(path)) throw inputError('error.path', '填写目标设备上日志的绝对路径。');
|
|
87
106
|
if (!/^[a-zA-Z0-9_-]{1,40}$/.test(label)) throw inputError('error.label', '标签仅允许字母、数字、下划线或连字符。');
|
|
88
|
-
const quotedPath = "'" + path.replaceAll("'", "'\"'\"'") + "'";
|
|
89
|
-
|
|
107
|
+
const quotedPath = "'" + path.replaceAll("'", os === 'windows' ? "''" : "'\"'\"'") + "'";
|
|
108
|
+
const cli = system => system === 'windows' ? '& "$HOME\\AppData\\Local\\Programs\\Device Center\\cli\\device-center.cmd"' : 'device-center';
|
|
109
|
+
return { trust: `${cli(managerOs)} access trust --peer ${target} --confirm`, grant: `${cli(os)} access grant --peer ${manager} --log ${quotedPath} --label ${label} --minutes 60 --confirm` };
|
|
90
110
|
}
|
package/src/pairing-handoff.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { pendingCli, clearCli } from './cli-handoff.js';
|
|
1
2
|
const KEY = 'device-center-pairing-v1';
|
|
2
3
|
const safeStorage = () => { try { return window.sessionStorage; } catch { return null; } };
|
|
3
4
|
const valid = (value, now) => value && /^[a-f0-9]{64}$/.test(value.ticket ?? '') && Number.isSafeInteger(value.expiresAt) && value.expiresAt > now && value.expiresAt <= now + 330_000;
|
|
@@ -21,10 +22,13 @@ export function capturePairing({ location = window.location, history = window.hi
|
|
|
21
22
|
if ([...fragment.keys()].sort().join(',') !== 'expires,ticket') return null;
|
|
22
23
|
const value = { ticket: fragment.get('ticket'), expiresAt: Number(fragment.get('expires')) };
|
|
23
24
|
if (!valid(value, now)) return null;
|
|
25
|
+
clearCli({ storage });
|
|
24
26
|
try { storage.setItem(KEY, JSON.stringify(value)); } catch { /* A signed-in user can continue in memory. */ }
|
|
25
27
|
return value;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
|
-
export function loginDestination(locale) {
|
|
29
|
-
|
|
30
|
+
export function loginDestination(locale, { search = globalThis.window?.location?.search ?? '' } = {}) {
|
|
31
|
+
// Only this fixed management page can be selected without an active handoff.
|
|
32
|
+
// Never accept an arbitrary return URL from the login query.
|
|
33
|
+
return `${pendingCli() ? '/cli' : pendingPairing() ? '/pair' : new URLSearchParams(search).get('continue') === 'cli' ? '/cli' : '/'}?lang=${encodeURIComponent(locale)}`;
|
|
30
34
|
}
|
package/src/start.js
CHANGED
|
@@ -9,10 +9,12 @@ const customOrigin = document.querySelector('#custom-origin');
|
|
|
9
9
|
const guideParams = new URLSearchParams(location.search);
|
|
10
10
|
let mode = ['local', 'official', 'self-hosted'].includes(guideParams.get('mode')) ? guideParams.get('mode') : 'local';
|
|
11
11
|
let method = guideParams.get('method') === 'manual' ? 'manual' : 'agent';
|
|
12
|
+
let os = guideParams.get('os') === 'windows' ? 'windows' : 'posix';
|
|
12
13
|
let pairingMethod = 'browser';
|
|
13
14
|
customOrigin.value = mode === 'self-hosted' ? guideParams.get('origin') ?? '' : '';
|
|
14
15
|
for (const input of document.querySelectorAll('input[name="install-mode"]')) input.checked = input.value === mode;
|
|
15
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;
|
|
16
18
|
if (mode === 'self-hosted') document.querySelector('.custom').open = true;
|
|
17
19
|
const scene = document.querySelector('.scene');
|
|
18
20
|
const motionToggle = document.querySelector('.motion-toggle');
|
|
@@ -64,13 +66,14 @@ function updateGuidance() {
|
|
|
64
66
|
text.dataset.i18nAriaLabel = fieldLabel;
|
|
65
67
|
text.setAttribute('aria-label', t(fieldLabel));
|
|
66
68
|
document.querySelector('#manual-options').hidden = !manual;
|
|
69
|
+
translatedText('#manual-prerequisites', os === 'windows' ? 'guide.windowsPrerequisites' : 'guide.manual.prerequisites');
|
|
67
70
|
document.querySelector('#pairing-options').hidden = !manual || mode === 'local';
|
|
68
71
|
translatedText('[data-hosted-dashboard]', walkthroughStep === 2 ? 'walk.openDevices' : 'guide.manual.openPanel');
|
|
69
72
|
nextAction.dataset.copied = String(copied);
|
|
70
73
|
}
|
|
71
74
|
const errorKey = error => error.translationKey ?? 'error.custom';
|
|
72
75
|
function refreshInstructions() {
|
|
73
|
-
const options = { locale: getLocale(), mode, ...(mode === 'self-hosted' ? { origin: customOrigin.value.trim() } : {}) };
|
|
76
|
+
const options = { locale: getLocale(), mode, os, ...(mode === 'self-hosted' ? { origin: customOrigin.value.trim() } : {}) };
|
|
74
77
|
const link = document.querySelector('[data-hosted-dashboard]');
|
|
75
78
|
document.querySelector('#custom-fields').hidden = mode !== 'self-hosted';
|
|
76
79
|
updateGuidance();
|
|
@@ -92,7 +95,7 @@ function refreshInstructions() {
|
|
|
92
95
|
result.textContent = t(errorKey(error));
|
|
93
96
|
result.className = 'error';
|
|
94
97
|
}
|
|
95
|
-
document.querySelector('#install-walkthrough').innerHTML = installWalkthroughMarkup({ method, mode, pairingMethod, step: walkthroughStep, copied, controlOrigin: plan?.controlOrigin, localUrl: plan?.url, locale: getLocale() });
|
|
98
|
+
document.querySelector('#install-walkthrough').innerHTML = installWalkthroughMarkup({ method, mode, os, pairingMethod, step: walkthroughStep, copied, controlOrigin: plan?.controlOrigin, localUrl: plan?.url, locale: getLocale() });
|
|
96
99
|
copyButton.disabled = !instructionsValid;
|
|
97
100
|
}
|
|
98
101
|
function resetCopyState(resetStep = true) {
|
|
@@ -129,6 +132,11 @@ document.querySelector('#pairing-options').addEventListener('change', event => {
|
|
|
129
132
|
pairingMethod = event.target.value;
|
|
130
133
|
resetCopyState();
|
|
131
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
|
+
});
|
|
132
140
|
customOrigin.addEventListener('input', () => {
|
|
133
141
|
if (mode === 'self-hosted') resetCopyState();
|
|
134
142
|
});
|
package/src/styles.css
CHANGED
|
@@ -18,6 +18,7 @@ button:focus-visible, input:focus-visible, textarea:focus-visible, summary:focus
|
|
|
18
18
|
.nav-item.active { color: #327451; background: #eef5ed; border-color: #e0ebe0; }
|
|
19
19
|
.nav-item small { margin-left: auto; color: #8c9e91; font-size: 10px; }
|
|
20
20
|
.nav-item:hover { background: #f3f7f2; }
|
|
21
|
+
a.nav-item { text-decoration: none; }
|
|
21
22
|
.nav-empty { margin: 9px 11px; font-size: 11px; color: #859180; }
|
|
22
23
|
.sidebar-footer { margin-top: auto; padding: 22px 9px 0; color: #839087; font-size: 10px; }
|
|
23
24
|
.sidebar-footer button { display: block; margin-top: 12px; padding: 0; font-size: 10px; }
|
package/start.html
CHANGED
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
<h1 id="hero-title"><span data-i18n="guide.visual.heroLine1">少切电脑。</span><span data-i18n="guide.visual.heroLine2">少搬日志。</span></h1>
|
|
32
32
|
<p class="lead" data-i18n="guide.visual.lead">让本机 Agent 查看其他设备的状态与授权日志。</p>
|
|
33
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 · Node.js 24
|
|
35
|
-
<p class="release-note"><span class="release-dot" aria-hidden="true"></span><span data-i18n="guide.badge">开发预览 · 0.2.0-beta.
|
|
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.6 · MIT</span></p>
|
|
36
36
|
</div>
|
|
37
37
|
<figure class="scene" data-paused="false">
|
|
38
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>
|
|
@@ -110,6 +110,14 @@
|
|
|
110
110
|
</details>
|
|
111
111
|
</fieldset>
|
|
112
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>
|
|
113
121
|
<fieldset class="method-options">
|
|
114
122
|
<legend class="action-heading"><span class="action-number" aria-hidden="true">02</span><span data-i18n="guide.method.title">选择安装方式</span></legend>
|
|
115
123
|
<div class="method-switch">
|
|
@@ -118,7 +126,7 @@
|
|
|
118
126
|
</div>
|
|
119
127
|
</fieldset>
|
|
120
128
|
<div id="manual-options" hidden>
|
|
121
|
-
<p class="hint manual-prerequisites" data-i18n="guide.manual.prerequisites">
|
|
129
|
+
<p class="hint manual-prerequisites" id="manual-prerequisites" data-i18n="guide.manual.prerequisites">Node.js 24+ · 全新安装;已有安装会停止并保留配置。</p>
|
|
122
130
|
<fieldset class="pairing-options" id="pairing-options" hidden>
|
|
123
131
|
<legend data-i18n="guide.manual.browserQuestion">目标设备能打开浏览器吗?</legend>
|
|
124
132
|
<div class="pairing-choices">
|
|
@@ -148,7 +156,7 @@
|
|
|
148
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>
|
|
149
157
|
</section>
|
|
150
158
|
</main>
|
|
151
|
-
<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.
|
|
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.6" target="_blank" rel="noopener noreferrer" data-i18n="guide.source">MIT npm 源码包 ↗</a><span data-i18n="guide.footer">本机安装 · 私钥留在设备 · 操作单独授权</span></footer>
|
|
152
160
|
<script type="module" src="/src/start.js"></script>
|
|
153
161
|
</body>
|
|
154
162
|
</html>
|