device-center 0.2.0-beta.1 → 0.2.0-beta.3
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 +14 -4
- package/docs/agent-install.md +105 -0
- package/docs/device-profiles.md +56 -0
- package/docs/friend-trial.md +42 -0
- package/docs/hosted-access.md +1 -1
- package/docs/i18n.md +32 -0
- package/docs/npm.md +7 -7
- package/docs/onboarding.md +4 -4
- package/docs/remote-access.md +2 -2
- package/docs/roadmap.md +21 -31
- package/index.html +1 -0
- package/login.html +2 -2
- package/npm-shrinkwrap.json +2 -2
- package/package.json +7 -2
- package/server/connector.mjs +1 -1
- package/server/drivers/cloudLink.mjs +42 -2
- package/server/drivers/cloudRegistry.mjs +45 -2
- package/server/drivers/deviceProfiles.mjs +47 -0
- package/server/drivers/driverProtocol.mjs +3 -2
- package/server/drivers/inventory.mjs +21 -2
- package/server/index.mjs +20 -3
- package/src/api.js +4 -1
- package/src/device-state.js +20 -0
- package/src/i18n.css +61 -0
- package/src/i18n.js +174 -0
- package/src/install-prompt.js +19 -0
- package/src/login.css +4 -0
- package/src/login.js +14 -11
- package/src/main.js +230 -126
- package/src/messages.js +5880 -0
- package/src/onboarding.js +56 -7
- package/src/start.css +47 -0
- package/src/start.js +39 -0
- package/src/styles.css +23 -1
- package/start.html +46 -0
package/src/onboarding.js
CHANGED
|
@@ -1,18 +1,67 @@
|
|
|
1
|
+
import { localizedInstallPrompt } from './install-prompt.js';
|
|
2
|
+
import { normalizeLocale } from './i18n.js';
|
|
3
|
+
|
|
1
4
|
export const OFFICIAL_CONTROL_ORIGIN = 'https://devices.owenshen.top';
|
|
2
|
-
//
|
|
3
|
-
export const PUBLISHED_DRIVER_VERSION = '0.2.0-beta.
|
|
5
|
+
// Pin the CLI shipped with this release; never silently install a moving tag.
|
|
6
|
+
export const PUBLISHED_DRIVER_VERSION = '0.2.0-beta.3';
|
|
7
|
+
const inputError = (key, message) => Object.assign(new Error(message), { translationKey: key });
|
|
4
8
|
export function connectCommand(value, shareSsh = false) {
|
|
5
9
|
let url;
|
|
6
|
-
try { url = new URL(value); } catch { throw
|
|
10
|
+
try { url = new URL(value); } catch { throw inputError('error.https', '请输入完整的 HTTPS 域名,例如 https://devices.example.com'); }
|
|
7
11
|
if (url.protocol !== 'https:' || url.origin !== value || url.username || url.password ||
|
|
8
|
-
!/^(?:[a-z0-9.-]+|\[[a-f0-9:]+\])$/i.test(url.hostname)) throw
|
|
12
|
+
!/^(?:[a-z0-9.-]+|\[[a-f0-9:]+\])$/i.test(url.hostname)) throw inputError('error.origin', '请输入 HTTPS 控制面地址,不包含路径、账号或参数。');
|
|
9
13
|
// The allowlist excludes shell syntax. Quotes also work in POSIX shells and PowerShell.
|
|
10
14
|
return `npx --yes device-center@${PUBLISHED_DRIVER_VERSION} connect --cloud '${url.origin}'${shareSsh ? ' --share-ssh' : ''} --confirm`;
|
|
11
15
|
}
|
|
16
|
+
|
|
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', '安装模式或本机端口无效。');
|
|
20
|
+
const controlOrigin = mode === 'local' ? null : mode === 'official' ? OFFICIAL_CONTROL_ORIGIN : origin;
|
|
21
|
+
if (controlOrigin) connectCommand(controlOrigin); // Same strict HTTPS and shell-safe origin validation.
|
|
22
|
+
if (mode === 'self-hosted' && !controlOrigin) throw inputError('error.custom', '请输入自建控制面的 HTTPS 地址。');
|
|
23
|
+
if (origin !== undefined && mode !== 'self-hosted') throw inputError('error.selfOnly', '只有自建模式可以填写控制面地址。');
|
|
24
|
+
const cli = '"$HOME/.local/bin/device-center"';
|
|
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}`,
|
|
28
|
+
version: `${cli} --version`, bindingStatus: `${cli} status`,
|
|
29
|
+
serviceStatus: `${cli} service status --port ${port}`, servicePlan: `${cli} service plan --port ${port}`,
|
|
30
|
+
serviceInstall: `${cli} service install --port ${port} --confirm`,
|
|
31
|
+
foreground: `${cli} start --port ${port}`,
|
|
32
|
+
pairingPlan: controlOrigin ? `${cli} plan --cloud '${controlOrigin}' --port ${port}` : null,
|
|
33
|
+
connect: controlOrigin ? `${cli} connect --cloud '${controlOrigin}' --port ${port} --confirm` : null,
|
|
34
|
+
uninstallService: `${cli} service uninstall --port ${port} --confirm`,
|
|
35
|
+
} };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function agentInstallPrompt(options) {
|
|
39
|
+
const plan = agentInstallPlan(options), c = plan.commands;
|
|
40
|
+
if (options?.locale && normalizeLocale(options.locale) !== 'zh-CN') return localizedInstallPrompt(plan, options.locale);
|
|
41
|
+
return `请帮我在正在操作的这台电脑安装 Device Center,并接入这台电脑上的智能体。
|
|
42
|
+
|
|
43
|
+
我的选择:${plan.mode === 'local' ? '本机使用,无需账号,不申请云端绑定' : plan.mode === 'official' ? '使用 Device Center 官方托管控制面和加密中转' : '接入我已经部署的自建控制面'}。
|
|
44
|
+
${plan.controlOrigin ? `控制面:${plan.controlOrigin}。设备 Driver 仍在本机运行,身份私钥不托管到云端。\n` : ''}版本固定为 ${plan.version};${plan.background ? '安装用户级后台服务,登录系统后自动启动' : '仅前台运行,不安装后台服务'}。
|
|
45
|
+
|
|
46
|
+
请按以下顺序执行并核验实际结果,不要只给我命令:
|
|
47
|
+
1. 检查 macOS/Linux、Node.js >=24、npm 和当前智能体的 MCP 支持。Windows 原生 Driver 尚未支持,请说明限制并停止,不能假称安装成功。缺少 Node.js 时使用官方来源;遇到管理员权限或系统授权再让我处理。
|
|
48
|
+
2. 先检查已有 device-center、绑定、用户服务及 ${plan.url}/api/health,保留原配置。CLI 已在固定位置时可运行 ${c.bindingStatus} 和 ${c.serviceStatus};不要打开身份私钥文件。端口被其他程序使用时不要停止它,改用空闲的已知本机端口并同步后续地址。已有服务或绑定时先核对版本、控制面和启动方式,匹配则复用;冲突时解释并让我选择,不能覆盖、解绑或重复安装。
|
|
49
|
+
3. 对全新安装使用用户可写的固定位置,无需 sudo:
|
|
50
|
+
${c.install}
|
|
51
|
+
${c.version}
|
|
52
|
+
不修改全局 npm 配置或 shell 启动文件;后续使用上面的绝对命令路径。
|
|
53
|
+
4. ${plan.background ? `先查看并核对后台服务计划:\n ${c.servicePlan}\n 确认是用户级、只监听本机回环后安装:\n ${c.serviceInstall}\n 再核验:${c.serviceStatus}\n 若配置已存在或健康检查失败,保留现场并排查,不自动清理重装。Linux 需要可用的 systemd --user,macOS 需要当前登录用户的 launchd;不支持时如实提示可改用前台模式。` : `启动并保持前台进程:\n ${plan.controlOrigin ? '先在下一步完成 connect;它会在批准后启动本机 Driver。' : c.foreground}\n 关闭网页不会停止进程;关闭终端会停止回报。`}
|
|
54
|
+
5. ${plan.controlOrigin ? `查看云端回报范围,然后申请绑定:\n ${c.pairingPlan}\n ${c.connect}\n 请我在 ${plan.controlOrigin} 登录自己的账号,在本机终端核对完整公钥指纹并手动批准绑定。不要代我批准新增设备访问权限,不读取账号密码、cookie 或私钥。配对码留在本机终端,不粘贴到智能体对话、脚本或链接;若工具会把配对码自动记录进对话,让我仅在本机终端执行这条 connect 命令,你继续完成其他检查。批准后核验设备确实回报资源;不能仅凭本地 configured=true 声称云端连通。` : '不执行 connect、不生成设备身份或申请账号。若发现已有云端绑定,先说明 start/后台服务可能恢复它;不能把已有绑定伪装成本机专用模式。'}
|
|
55
|
+
6. 读取 ${plan.url}/api/connector/setup,使用当前智能体支持的官方 MCP 配置接口添加 Streamable HTTP 地址 ${plan.mcpUrl}。复用一致的配置,不覆盖其他 MCP。若当前工具不能配置,只让我粘贴这一个地址;不要编造已接通。
|
|
56
|
+
7. 核验 /api/health、/api/overview 的本机真实内存与磁盘,以及智能体实际调用 device_center_list_resources。最后报告版本、面板/MCP 地址、前台或后台状态、云端绑定结果(如适用)与尚未完成的步骤。后台卸载命令仅供我需要时使用:${c.uninstallService},默认保留数据和身份。
|
|
57
|
+
|
|
58
|
+
默认不导入或连接 SSH 主机、不分享 SSH、不扫描局域网、不开放入站端口、不配置隧道、不授予日志/命令权限。安装和配对不等于远程操作授权;后续访问目标设备需另行授权。
|
|
59
|
+
此说明固定使用 ${plan.version},包含设备说明与手动采集。已有旧版需先核对升级;不能假称未运行的设备已接通。`;
|
|
60
|
+
}
|
|
12
61
|
export function logAccessCommands({ manager, target, path, label = 'app' }) {
|
|
13
|
-
if (![manager, target].every(value => typeof value === 'string' && /^[a-f0-9]{64}$/.test(value)) || manager === target) throw
|
|
14
|
-
if (typeof path !== 'string' || !path.startsWith('/') || path.length > 4096 || /[\x00-\x1f\x7f]/.test(path)) throw
|
|
15
|
-
if (!/^[a-zA-Z0-9_-]{1,40}$/.test(label)) throw
|
|
62
|
+
if (![manager, target].every(value => typeof value === 'string' && /^[a-f0-9]{64}$/.test(value)) || manager === target) throw inputError('error.devices', '请选择两台不同的已绑定设备。');
|
|
63
|
+
if (typeof path !== 'string' || !path.startsWith('/') || path.length > 4096 || /[\x00-\x1f\x7f]/.test(path)) throw inputError('error.path', '填写目标设备上日志的绝对路径。');
|
|
64
|
+
if (!/^[a-zA-Z0-9_-]{1,40}$/.test(label)) throw inputError('error.label', '标签仅允许字母、数字、下划线或连字符。');
|
|
16
65
|
const quotedPath = "'" + path.replaceAll("'", "'\"'\"'") + "'";
|
|
17
66
|
return { trust: `device-center access trust --peer ${target} --confirm`, grant: `device-center access grant --peer ${manager} --log ${quotedPath} --label ${label} --minutes 60 --confirm` };
|
|
18
67
|
}
|
package/src/start.css
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
:root { color-scheme: light; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; color: #25392d; background: #f5f7f2; }
|
|
2
|
+
* { box-sizing: border-box; }
|
|
3
|
+
body { margin: 0; }
|
|
4
|
+
a { color: #2f6d4c; text-underline-offset: 3px; }
|
|
5
|
+
header, main, footer { width: min(860px, calc(100% - 40px)); margin: 0 auto; }
|
|
6
|
+
header { display: flex; justify-content: space-between; align-items: center; padding: 24px 0; gap: 16px; font-size: 13px; }
|
|
7
|
+
.brand { display: flex; align-items: center; gap: 10px; font-weight: 700; text-decoration: none; }
|
|
8
|
+
.brand span { color: #25392d; }
|
|
9
|
+
.hero { padding: 48px 0 38px; }
|
|
10
|
+
.eyebrow { font-size: 12px; color: #70816d; letter-spacing: .8px; }
|
|
11
|
+
h1 { margin: 18px 0 20px; font-size: clamp(34px, 6vw, 54px); line-height: 1.2; letter-spacing: -1.8px; }
|
|
12
|
+
.lead { max-width: 630px; font-size: 17px; line-height: 1.8; color: #62705e; }
|
|
13
|
+
.platform { font-size: 12px; color: #70816d; }
|
|
14
|
+
section:not(.hero) { padding: 26px 0; border-top: 1px solid #dae3d6; }
|
|
15
|
+
h2 { margin: 0 0 22px; font-size: 19px; line-height: 1.6; }
|
|
16
|
+
.step { display: inline-grid; place-items: center; width: 28px; height: 28px; margin-right: 7px; border-radius: 50%; background: #e3eddd; font-size: 13px; }
|
|
17
|
+
.choices, .checks { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
|
18
|
+
article { display: flex; flex-direction: column; padding: 22px; border: 1px solid #dce5d7; border-radius: 12px; background: #fff; }
|
|
19
|
+
h3 { margin: 0 0 10px; font-size: 16px; }
|
|
20
|
+
p, li { font-size: 14px; line-height: 1.85; color: #62705e; }
|
|
21
|
+
article p { flex: 1; margin: 0 0 20px; }
|
|
22
|
+
button { min-height: 42px; border: 1px solid #c9d8c4; border-radius: 8px; padding: 10px 14px; color: #2f6d4c; background: #f4f8ef; font: inherit; font-size: 13px; font-weight: 600; cursor: pointer; }
|
|
23
|
+
button.primary { color: white; background: #356c4c; border-color: #356c4c; }
|
|
24
|
+
button:disabled { opacity: .6; }
|
|
25
|
+
button:hover { filter: brightness(.96); }
|
|
26
|
+
:focus-visible { outline: 2px solid #5c965d; outline-offset: 3px; }
|
|
27
|
+
.hint { font-size: 12px; }
|
|
28
|
+
details { margin-top: 18px; font-size: 13px; }
|
|
29
|
+
summary { color: #436440; cursor: pointer; line-height: 1.8; }
|
|
30
|
+
.custom label { display: block; margin: 14px 0 8px; }
|
|
31
|
+
.custom-row { display: flex; gap: 10px; }
|
|
32
|
+
input, textarea { width: 100%; min-width: 0; border: 1px solid #cbd7c5; border-radius: 8px; padding: 10px; background: white; color: #35482f; font: inherit; }
|
|
33
|
+
textarea { margin-top: 12px; font-family: ui-monospace, monospace; font-size: 12px; line-height: 1.7; resize: vertical; }
|
|
34
|
+
.custom-row button { flex-shrink: 0; }
|
|
35
|
+
#copy-result { min-height: 24px; margin: 12px 0; font-size: 13px; }
|
|
36
|
+
.success { color: #2f6d4c; }
|
|
37
|
+
.error { color: #9e3f32; }
|
|
38
|
+
ol { padding-left: 24px; }
|
|
39
|
+
li { padding: 0 0 12px 5px; }
|
|
40
|
+
.checks div { padding: 10px 0; }
|
|
41
|
+
.checks p { margin: 0; }
|
|
42
|
+
code { overflow-wrap: anywhere; font-size: 12px; }
|
|
43
|
+
aside { margin: 10px 0 32px; padding: 20px 22px; border: 1px solid #dce5d7; border-radius: 10px; background: #edf2e8; font-size: 14px; }
|
|
44
|
+
aside p { margin: 10px 0 0; font-size: 12px; }
|
|
45
|
+
footer { display: flex; flex-wrap: wrap; gap: 14px 22px; padding: 14px 0 32px; font-size: 12px; }
|
|
46
|
+
footer span { width: 100%; color: #7b8875; }
|
|
47
|
+
@media (max-width: 560px) { .choices, .checks { grid-template-columns: 1fr; gap: 12px; } .hero { padding-top: 25px; } .custom-row { flex-direction: column; } header { font-size: 12px; } h2 { font-size: 17px; } }
|
package/src/start.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { agentInstallPrompt, agentInstallPlan, OFFICIAL_CONTROL_ORIGIN } from './onboarding.js';
|
|
2
|
+
import { initLanguage, getLocale, t } from './i18n.js';
|
|
3
|
+
|
|
4
|
+
const text = document.querySelector('#install-instructions');
|
|
5
|
+
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() } : {}) };
|
|
9
|
+
const link = document.querySelector('[data-hosted-dashboard]');
|
|
10
|
+
try {
|
|
11
|
+
const plan = agentInstallPlan(options);
|
|
12
|
+
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; }
|
|
16
|
+
}
|
|
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]');
|
|
22
|
+
if (!button || button.disabled) return;
|
|
23
|
+
const generation = ++copyGeneration;
|
|
24
|
+
mode = button.dataset.mode;
|
|
25
|
+
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. */ }
|
|
35
|
+
}
|
|
36
|
+
result.className = copied ? 'success' : '';
|
|
37
|
+
result.textContent = t(copied ? 'guide.copied' : 'guide.manual');
|
|
38
|
+
button.disabled = false;
|
|
39
|
+
});
|
package/src/styles.css
CHANGED
|
@@ -8,7 +8,7 @@ button:disabled { opacity: .55; cursor: wait; }
|
|
|
8
8
|
button:focus-visible, input:focus-visible, textarea:focus-visible, summary:focus-visible, a:focus-visible { outline: 3px solid #b5d7bf; outline-offset: 3px; }
|
|
9
9
|
.app-shell { height: 100vh; height: 100dvh; display: grid; grid-template-columns: 192px minmax(0, 1fr); overflow: hidden; }
|
|
10
10
|
.sidebar { min-height: 0; padding: 20px 14px; background: #fff; border-right: 1px solid var(--line); display: flex; flex-direction: column; overflow: auto; overscroll-behavior: contain; }
|
|
11
|
-
.sidebar
|
|
11
|
+
.sidebar .project-navigation { min-height: 0; flex: 1; overflow: auto; }
|
|
12
12
|
.brand { color: inherit; text-decoration: none; display: flex; align-items: center; gap: 10px; }
|
|
13
13
|
.brand-mark { width: 34px; height: 34px; flex: 0 0 auto; display: grid; place-items: center; color: white; background: #377c57; border-radius: 10px; }
|
|
14
14
|
.brand strong { display: block; font-size: 13px; }
|
|
@@ -35,6 +35,13 @@ h1 { margin: 0 0 4px; font-size: 24px; letter-spacing: -.6px; font-weight: 680;
|
|
|
35
35
|
.button-primary:hover { background: #1b6444; }
|
|
36
36
|
.button-light { color: #607167; border-color: #dfe7df; background: #fff; }
|
|
37
37
|
.button-light:hover { background: #f6f9f4; }
|
|
38
|
+
.heading-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; min-width: 0; }
|
|
39
|
+
.install-flow { margin: 14px 0; padding-left: 20px; color: #657660; font-size: 12px; line-height: 1.9; }
|
|
40
|
+
#agent-install-text { max-height: 170px; resize: vertical; margin-top: 10px; font-size: 11px; line-height: 1.7; color: #51644d; }
|
|
41
|
+
.install-copy { width: 100%; margin-top: 2px; }
|
|
42
|
+
.install-details { margin-top: 14px; color: #657660; font-size: 12px; }
|
|
43
|
+
#install-origin { width: 100%; border: 1px solid #dce4d8; padding: 9px 10px; border-radius: 7px; font: inherit; font-size: 12px; }
|
|
44
|
+
.install-mode-tabs { margin-bottom: 12px; }
|
|
38
45
|
.text-button { border: 0; background: none; color: #688a73; }
|
|
39
46
|
.icon-button { width: 32px; height: 32px; padding: 0; display: grid; place-items: center; border: 1px solid var(--line); background: #fff; border-radius: 8px; color: #839486; flex: 0 0 auto; }
|
|
40
47
|
.connection-card { background: #fff; border: 1px solid var(--line); border-radius: 12px; padding: 20px; box-shadow: 0 8px 26px #20352405; }
|
|
@@ -67,6 +74,19 @@ h1 { margin: 0 0 4px; font-size: 24px; letter-spacing: -.6px; font-weight: 680;
|
|
|
67
74
|
.device-card { flex: 0 0 auto; display: grid; grid-template-columns: minmax(200px, 1.1fr) minmax(220px, 1fr) 130px; align-items: center; gap: 16px; background: #fff; border: 1px solid var(--line); border-radius: 10px; padding: 12px 14px; }
|
|
68
75
|
.device-identity { min-width: 0; }
|
|
69
76
|
.device-actions { display: flex; flex-direction: column; gap: 7px; min-width: 0; }
|
|
77
|
+
.device-description { display: flex; align-items: center; flex-wrap: wrap; gap: 4px; margin: 7px 0 0 33px; min-width: 0; }
|
|
78
|
+
.device-type, .device-tag { border-radius: 4px; padding: 2px 5px; font-size: 10px; color: #59675c; background: #f1f4ef; max-width: 120px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
79
|
+
.device-type { color: #386842; background: #e9f3e6; }
|
|
80
|
+
.device-description p { flex-basis: 100%; margin: 2px 0 0; color: #69736a; font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
81
|
+
.collection-hint { font-size: 10px; color: #7b8178; line-height: 1.4; }
|
|
82
|
+
.profile-edit { align-self: flex-start; font-size: 11px; }
|
|
83
|
+
.profile-fields { display: grid; grid-template-columns: minmax(0, 2fr) minmax(105px, 1fr); gap: 12px; }
|
|
84
|
+
#device-profile-form .field-label { display: block; margin: 14px 0 0; }
|
|
85
|
+
#device-profile-form input, #device-profile-form select, #device-profile-form textarea { display: block; width: 100%; margin: 7px 0 0; border: 1px solid #dce4d8; border-radius: 7px; padding: 9px 10px; font: inherit; font-size: 12px; background: white; color: #344137; }
|
|
86
|
+
#device-profile-form textarea { resize: vertical; min-height: 64px; }
|
|
87
|
+
.profile-identity { margin: 16px 0; font-size: 11px; color: #6a7569; }
|
|
88
|
+
.profile-identity summary { cursor: pointer; }
|
|
89
|
+
.save-profile { margin-top: 5px; width: 100%; }
|
|
70
90
|
.driver-summary { grid-row: 2; display: flex; flex-wrap: wrap; gap: 8px; align-items: center; padding: 6px 0; }
|
|
71
91
|
.driver-summary strong { font-size: 11px; color: #407553; }
|
|
72
92
|
.driver-summary p { margin: 0; font-size: 11px; color: #6d7d70; line-height: 1.5; }
|
|
@@ -221,6 +241,8 @@ details p { font-size: 12px; color: #8da37c; line-height: 1.8; }
|
|
|
221
241
|
.status-help p { position: absolute; z-index: 3; right: 0; bottom: calc(100% + 8px); width: min(300px, calc(100vw - 28px)); max-height: 50dvh; overflow: auto; padding: 14px; margin: 0; font-size: 11px; color: #63765d; background: #fff; border: 1px solid var(--line); border-radius: 9px; box-shadow: 0 8px 30px #20352416; }
|
|
222
242
|
@media (max-width: 960px) { .device-card { grid-template-columns: minmax(0, 1fr) 128px; gap: 9px 16px; } .device-identity { grid-column: 1 / -1; } }
|
|
223
243
|
@media (max-width: 760px) {
|
|
244
|
+
.heading-actions { max-width: 140px; gap: 5px; }
|
|
245
|
+
.heading-actions > button { min-height: 30px; padding: 6px 8px; font-size: 11px; }
|
|
224
246
|
.app-shell { grid-template-columns: 140px minmax(0, 1fr); }
|
|
225
247
|
.sidebar { padding: 18px 10px; }
|
|
226
248
|
.brand { gap: 7px; } .brand-mark { width: 28px; height: 28px; } .brand strong { font-size: 11px; } .brand small { font-size: 9px; }
|
package/start.html
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<meta name="description" data-i18n-content="guide.lead" content="把自己的闲置电脑接入本机智能体。">
|
|
7
|
+
<title data-i18n="guide.title">开始试用 · Device Center</title>
|
|
8
|
+
<link rel="stylesheet" href="/src/start.css">
|
|
9
|
+
<link rel="stylesheet" href="/src/i18n.css">
|
|
10
|
+
</head>
|
|
11
|
+
<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
|
+
<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>
|
|
25
|
+
</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>
|
|
30
|
+
</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>
|
|
35
|
+
</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>
|
|
40
|
+
</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
|
+
</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>
|
|
44
|
+
<script type="module" src="/src/start.js"></script>
|
|
45
|
+
</body>
|
|
46
|
+
</html>
|