device-center 0.2.0-beta.6 → 0.2.0-beta.7

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/src/onboarding.js CHANGED
@@ -3,7 +3,7 @@ import { normalizeLocale } from './i18n.js';
3
3
 
4
4
  export const OFFICIAL_CONTROL_ORIGIN = 'https://devices.owenshen.top';
5
5
  // Publish this exact CLI before deploying the matching guide.
6
- export const PUBLISHED_DRIVER_VERSION = '0.2.0-beta.6';
6
+ export const PUBLISHED_DRIVER_VERSION = '0.2.0-beta.7';
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;
@@ -16,7 +16,7 @@ export function connectCommand(value, shareSsh = false) {
16
16
 
17
17
  // A reproducible handoff, not an installer. Reading/copying it has no machine effects.
18
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', '安装模式、系统或本机端口无效。');
19
+ if (!['local', 'official', 'self-hosted'].includes(mode) || !['posix', 'macos', 'linux', '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 地址。');
@@ -69,7 +69,7 @@ export function terminalInstallScript({ browser = true, ...options } = {}) {
69
69
  const connect = c.connect && (browser ? c.connect : c.connect.replace(' --browser', ''));
70
70
  return `(
71
71
  set -eu
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
+ case "$(uname -s)" in ${plan.os === 'macos' ? 'Darwin' : plan.os === 'linux' ? 'Linux' : 'Darwin|Linux'}) ;; *) printf '%s\\n' 'Choose the Windows PowerShell installer for Windows; this block is macOS / Linux only.'; exit 1 ;; esac
73
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; }
74
74
  node -e 'if (Number(process.versions.node.split(".")[0]) < 24) { console.error("Node.js 24+ required"); process.exit(1); }'
75
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
package/src/pair.css CHANGED
@@ -20,4 +20,28 @@ button:focus-visible, a:focus-visible { outline: 2px solid #517543; outline-offs
20
20
  .error { color: #a13d32; }
21
21
  .status-mark { font-size: 32px; margin: 4px 0 20px; color: #517543; }
22
22
  footer { text-align: center; padding: 20px 16px; color: #777e72; font-size: 12px; }
23
+ main.cli-management { width: min(940px, calc(100% - 32px)); margin: 22px auto; }
24
+ .cli-setup h2 { font-size: 18px; margin: 28px 0 8px; }
25
+ .cli-setup-destination { padding: 12px 16px; border: 1px solid #e1e5dc; background: #f6f7f2; font-size: 12px; overflow-wrap: anywhere; }
26
+ .cli-setup-destination span { display: block; margin-bottom: 4px; color: #7c8577; }
27
+ .cli-setup-choices { display: flex; gap: 8px; margin: 20px 0 16px; }
28
+ .cli-setup-choices button { width: auto; flex: 1; margin: 0; background: #f3f4ee; color: #687563; border: 1px solid #dce2d6; }
29
+ .cli-setup-choices button[aria-pressed="true"] { background: #263d2e; color: white; border-color: #263d2e; }
30
+ .cli-setup-os { display: flex; align-items: center; gap: 14px; font-size: 12px; color: #65745f; }
31
+ .cli-setup-os select { padding: 8px 12px; border: 1px solid #dce2d6; background: white; color: #35472f; border-radius: 3px; }
32
+ .cli-setup-flow { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: 12px; list-style: none; padding: 0; margin: 24px 0; }
33
+ .cli-setup-flow li { padding: 18px 14px; border: 1px solid #e2e7dc; background: #fafbf8; }
34
+ .cli-setup-flow svg { display: block; width: 32px; height: 24px; fill: none; stroke: #6b8b60; stroke-width: 1.4; margin-bottom: 14px; }
35
+ .cli-setup-flow strong { display: block; font-size: 13px; margin-bottom: 7px; color: #374d30; }
36
+ .cli-setup-flow span { font-size: 12px; line-height: 1.8; color: #7e8877; }
37
+ .cli-setup-scope { border-left: 3px solid #c6d7bb; padding-left: 14px; font-size: 12px; }
38
+ .cli-setup textarea { width: 100%; padding: 14px; border: 1px solid #dce2d6; border-radius: 3px; background: #fbfcf8; font: 12px/1.8 ui-monospace, Consolas, monospace; color: #43533b; resize: vertical; }
39
+ .cli-setup summary, .cli-grants summary { cursor: pointer; color: #526a48; font-size: 13px; }
40
+ .cli-setup-preview { margin-top: 20px; }
41
+ .cli-setup-preview textarea, .cli-setup-existing textarea { margin-top: 12px; }
42
+ .cli-setup-copy-status { min-height: 22px; margin: 8px 0; color: #547c40; font-size: 12px; }
43
+ .cli-setup-existing { margin: 20px 0; }
44
+ .cli-setup-links { display: flex; flex-wrap: wrap; gap: 10px 24px; font-size: 12px; color: #687f5c; }
45
+ .cli-grants { margin-top: 32px; padding-top: 22px; border-top: 1px solid #e3e6dc; }
46
+ @media (max-width: 640px) { .cli-setup-flow { grid-template-columns: 1fr; gap: 8px; } .cli-setup-flow li { display: grid; grid-template-columns: 32px 1fr; gap: 0 14px; } .cli-setup-flow svg { grid-row: span 2; margin: 2px 0; } .cli-setup-os { flex-wrap: wrap; } }
23
47
  @media (prefers-reduced-motion: no-preference) { main { animation: appear .2s ease-out; } @keyframes appear { from { opacity: 0; transform: translateY(5px); } } }
package/src/start.css CHANGED
@@ -1,3 +1,4 @@
1
+ @import url('./install-walkthrough.css');
1
2
  :root { color-scheme: dark; font-family: "Helvetica Neue", "Arial", "PingFang SC", "Hiragino Sans GB", sans-serif; color: #eeece8; background: #0c0c0e; font-synthesis: none; --accent: #ff8254; --line: #2b2b2e; --muted: #9a999f; --mono: "SFMono-Regular", Consolas, "Liberation Mono", monospace; }
2
3
  * { box-sizing: border-box; }
3
4
  html { scroll-behavior: smooth; scroll-padding-top: 108px; }
@@ -110,7 +111,7 @@ h3 { margin: 0; color: #e8e1db; font-size: 16px; font-weight: 500; line-height:
110
111
  .copy-step { margin-top: 23px; padding-top: 21px; border-top: 1px solid #363034; }
111
112
  .copy-step .action-heading { margin-bottom: 13px; }
112
113
  .method-options, .pairing-options { min-width: 0; padding: 0; margin: 0; border: 0; }
113
- .method-switch { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 4px; padding: 4px; margin-bottom: 16px; border: 1px solid #373337; border-radius: 5px; background: #111113; }
114
+ .method-switch { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 4px; padding: 4px; margin-bottom: 16px; border: 1px solid #373337; border-radius: 5px; background: #111113; }
114
115
  .method-switch label { position: relative; min-width: 0; cursor: pointer; }
115
116
  .method-switch input { position: absolute; width: 1px; height: 1px; padding: 0; margin: 0; opacity: 0; }
116
117
  .method-switch span { display: block; padding: 9px 12px; border: 1px solid transparent; border-radius: 3px; color: #a79892; font-size: 13px; text-align: center; line-height: 1.6; }
@@ -140,85 +141,7 @@ h3 { margin: 0; color: #e8e1db; font-size: 16px; font-weight: 500; line-height:
140
141
  .next-action[data-copied="true"] { border-color: #bc7a56; background: #241b16; }
141
142
  .next-action[data-copied="true"] .action-heading, .next-action[data-copied="true"] .action-number { color: #ffc29b; }
142
143
  .binding-link { display: inline-block; margin: 13px 0 0; color: #f4b991; font-size: 12px; line-height: 1.7; }
143
- .walkthrough-tabs { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 5px; margin: 0 0 16px; padding: 0; list-style: none; }
144
- .walkthrough-tabs li { min-width: 0; }
145
- .walkthrough-tabs button { width: 100%; min-height: 38px; display: flex; align-items: center; justify-content: center; gap: 7px; padding: 7px; border: 1px solid #493a33; border-radius: 3px; background: transparent; color: #a9968a; font: inherit; font-size: 12px; line-height: 1.5; cursor: pointer; }
146
- .walkthrough-tabs button span { color: #86756a; font: 10px var(--mono); }
147
- .walkthrough-tabs button:hover { border-color: #bc7a56; color: #f4c8ab; }
148
- .walkthrough-tabs button[aria-current="step"] { background: #3d2a20; border-color: #c48d68; color: #ffcfb1; }
149
- .walkthrough-tabs button[aria-current="step"] span { color: #ffc29b; }
150
- .walkthrough-caption { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; margin-bottom: 10px; color: #f4dfd0; font-size: 14px; line-height: 1.7; }
151
- .walkthrough-caption small { flex-shrink: 0; color: #a48c7c; font: 11px var(--mono); }
152
- .walkthrough-picture { overflow: hidden; padding: 16px 18px 14px; border: 1px solid #3b302a; border-radius: 5px; background: #121011; }
153
- .walkthrough-picture svg { width: 22px; height: 22px; flex-shrink: 0; fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }
154
- .picture-transfer { display: flex; align-items: center; justify-content: center; gap: 18px; margin-bottom: 16px; color: #dfb394; font-size: 12px; line-height: 1.7; }
155
- .picture-transfer > span { display: flex; align-items: center; gap: 8px; min-width: 0; }
156
- .picture-transfer > svg { width: 28px; color: #b58160; }
157
- .picture-window { min-width: 0; border: 1px solid #5a4940; border-radius: 7px; background: #1b1818; box-shadow: 0 8px 20px #0003; }
158
- .picture-titlebar { display: flex; align-items: center; gap: 5px; min-height: 28px; padding: 6px 12px; border-bottom: 1px solid #3e3430; background: #252020; border-radius: 6px 6px 0 0; }
159
- .picture-titlebar > i { flex-shrink: 0; width: 6px; height: 6px; border: 1px solid #887369; border-radius: 50%; }
160
- .picture-titlebar > i:first-child { background: #956e5e; }
161
- .picture-titlebar > span { min-width: 0; margin-left: 9px; color: #baa599; font-size: 10px; line-height: 1.6; }
162
- .picture-address { display: block; overflow-wrap: anywhere; font: 10px var(--mono); }
163
- .picture-terminal { padding: 22px 18px 16px; }
164
- .picture-paste-line { display: flex; align-items: center; gap: 12px; position: relative; color: #ffb98a; font: 12px var(--mono); }
165
- .picture-paste-line > div { flex: 1; padding: 14px 12px; border: 1px dashed #db9365; border-radius: 3px; background: #35261e; color: #ffcfaf; line-height: 1.8; }
166
- .picture-caret { display: inline-block; height: 15px; margin-left: 9px; border-left: 2px solid #ffa875; vertical-align: middle; }
167
- .walkthrough-picture .picture-pointer { position: absolute; right: 6px; bottom: -16px; width: 25px; height: 30px; fill: #f5e9df; stroke: #151112; stroke-width: 1.8; transform: rotate(-9deg); }
168
- .picture-keyline { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; margin-top: 26px; color: #c0a596; font-size: 10px; line-height: 1.8; }
169
- .picture-keyline strong { color: #ffd5b9; font-weight: 400; }
170
- .walkthrough-picture kbd { display: inline-block; margin-left: 3px; padding: 2px 6px; border: 1px solid #655045; border-bottom-width: 2px; border-radius: 3px; background: #28201d; font: 10px var(--mono); }
171
- .picture-launch { display: flex; flex-wrap: wrap; gap: 5px 18px; margin-top: 15px; color: #a38c7e; font-size: 10px; line-height: 1.8; }
172
- .picture-chat { padding: 22px 18px 16px; }
173
- .picture-chat-message { width: 88%; margin-left: auto; padding: 21px 15px; border: 1px dashed #d08b64; border-radius: 9px 9px 2px 9px; background: #37271e; color: #ffd3b5; font-size: 13px; line-height: 1.8; }
174
- .picture-send { position: relative; display: flex; align-items: center; justify-content: flex-end; gap: 10px; margin-top: 19px; padding-right: 9px; color: #d8bca9; font-size: 11px; }
175
- .picture-send b { display: grid; place-items: center; width: 29px; height: 29px; background: #e8b08b; border-radius: 50%; color: #241912; font-size: 19px; font-weight: 400; }
176
- .picture-send .picture-pointer { bottom: -9px; right: 0; width: 20px; }
177
- .picture-auto-open { margin-bottom: 14px; color: #c8a88f; text-align: center; font-size: 11px; line-height: 1.7; }
178
- .picture-pair { padding: 12px 16px; }
179
- .picture-signin { display: flex; align-items: baseline; gap: 9px; color: #d4b7a4; font-size: 11px; line-height: 1.7; }
180
- .picture-signin > span { color: #c29170; font: 9px var(--mono); }
181
- .picture-signin + .picture-signin { margin-top: 8px; }
182
- .picture-fingerprints { display: grid; grid-template-columns: auto minmax(0, 1fr) 20px; gap: 6px 12px; padding: 10px; margin: 10px 0 13px; border: 1px solid #65503f; border-radius: 3px; background: #29211c; font-size: 10px; }
183
- .picture-fingerprints > span { color: #bca08b; }
184
- .picture-fingerprints code { overflow-wrap: anywhere; color: #ffd1a9; font: 10px/1.7 var(--mono); }
185
- .picture-fingerprints > b { grid-column: 3; grid-row: 1 / span 2; align-self: center; justify-self: center; color: #e9b07d; font-size: 20px; font-weight: 400; line-height: .7; }
186
- .picture-approval { position: relative; width: fit-content; max-width: 100%; margin: 0 0 5px auto; padding: 8px 12px; border: 1px solid #c58c63; border-radius: 3px; background: #4a3222; color: #ffcfaa; font-size: 11px; line-height: 1.7; }
187
- .picture-approval .picture-pointer { right: -7px; bottom: -15px; width: 21px; }
188
- .headless-transfer { gap: 13px; font-size: 11px; }
189
- .headless-transfer > span:first-child { flex-direction: column; align-items: flex-start; gap: 6px; }
190
- .headless-transfer code { padding: 4px 7px; border: 1px dashed #a57958; color: #ffd0a9; font: 10px/1.7 var(--mono); }
191
- .picture-code-entry { margin: 10px 0 12px; color: #b49a89; font-size: 10px; }
192
- .picture-code-entry > div { position: relative; margin-top: 6px; padding: 9px 12px; border: 1px dashed #b7825f; border-radius: 3px; color: #ffd1b0; background: #30231c; }
193
- .picture-install { padding: 21px 20px; }
194
- .picture-install-command { margin-bottom: 19px; color: #e4c5af; font: 13px var(--mono); }
195
- .picture-install-command > span { color: #ac856a; }
196
- .picture-install-row { display: flex; align-items: baseline; gap: 12px; margin: 10px 0; color: #cfb5a3; font-size: 12px; }
197
- .picture-install-row > span { color: #a7866f; font: 10px var(--mono); }
198
- .picture-install-track { display: flex; gap: 5px; margin-top: 21px; }
199
- .picture-install-track i { flex: 1; height: 3px; background: #8b644b; }
200
- .picture-install-track i:nth-child(2) { background: #5e473a; }
201
- .picture-install-track i:nth-child(3) { background: #392d26; }
202
- .picture-dashboard { padding: 20px 18px; }
203
- .picture-device { display: grid; grid-template-columns: 24px minmax(0, 1fr); align-items: center; gap: 4px 12px; color: #d4aa8a; }
204
- .picture-device svg { grid-row: span 2; }
205
- .picture-device strong { color: #f1d1b9; font-size: 13px; font-weight: 500; }
206
- .picture-device > span { color: #a38b7a; font-size: 10px; }
207
- .picture-metrics { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; margin: 24px 0 20px; }
208
- .picture-metrics > div { display: grid; gap: 8px; }
209
- .picture-metrics span { color: #b29c8c; font-size: 11px; }
210
- .picture-metrics b { color: #ceb39e; font: 14px var(--mono); }
211
- .picture-metrics i { height: 3px; background: #4b3b30; }
212
- .picture-report-time { position: relative; padding: 10px 12px; border: 1px dashed #9f7555; color: #ebc3a2; font-size: 11px; line-height: 1.8; }
213
- .picture-report-time .picture-pointer { right: 3px; bottom: -14px; width: 21px; }
214
- .walkthrough-description { margin: 12px 0 0; color: #c5ad9d; font-size: 12px; line-height: 1.9; }
215
- .walkthrough-copy-first { margin: 8px 0 0; color: #aa9382; font-size: 11px; line-height: 1.8; }
216
- .walkthrough-navigation { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-top: 12px; padding-top: 10px; border-top: 1px solid #49362b; }
217
- .walkthrough-navigation button { min-height: 30px; padding: 4px 0; border: 0; background: transparent; color: #f3bd94; font: inherit; font-size: 12px; line-height: 1.7; cursor: pointer; }
218
- .walkthrough-navigation button:hover { color: #ffe0c6; text-decoration: underline; text-underline-offset: 4px; }
219
- .walkthrough-navigation button:disabled { color: #766155; cursor: default; text-decoration: none; }
220
- .walkthrough-tabs button:focus-visible, .walkthrough-navigation button:focus-visible, #walkthrough-title:focus-visible { outline: 2px solid #ffc29b; outline-offset: 4px; }
221
- @media (max-width: 700px) { .walkthrough-tabs { gap: 4px; margin-bottom: 16px; } .walkthrough-tabs button { flex-direction: column; gap: 4px; padding: 9px 5px; font-size: 11px; } .walkthrough-caption { font-size: 13px; gap: 8px; } .walkthrough-picture { padding: 17px 12px 15px; } .picture-terminal { padding: 23px 12px 15px; } .picture-paste-line { gap: 8px; font-size: 11px; } .picture-paste-line > div { padding: 12px 10px; } .picture-transfer { gap: 13px; font-size: 11px; } .picture-titlebar { padding: 8px; } .picture-titlebar > span { margin-left: 5px; } .picture-keyline { gap: 9px; } .picture-pair { padding: 15px 12px; } .picture-fingerprints { gap: 6px 8px; padding: 10px; } .picture-fingerprints code { font-size: 9px; } .picture-dashboard { padding: 18px 12px; } .picture-metrics { gap: 18px; } .walkthrough-description { font-size: 11px; } .headless-transfer { gap: 8px; font-size: 10px; } .headless-transfer code { font-size: 9px; } }
144
+
222
145
  summary { color: #a2938b; cursor: pointer; font-size: 11px; line-height: 1.8; list-style: none; }
223
146
  summary::-webkit-details-marker { display: none; }
224
147
  summary::before { content: "+"; display: inline-block; margin-right: 9px; color: #7e716a; font: 14px var(--mono); }
@@ -265,3 +188,10 @@ footer span { width: 100%; color: #8c807a; }
265
188
  @media (max-width: 1000px) { section.install-section { gap: 30px; } .choices { grid-template-columns: 1fr; } .mode-choice { padding: 14px; } .section-intro { font-size: 13px; } }
266
189
  @media (max-width: 700px) { .returning-user { display: none; } .cta-hint { max-width: 150px; font-size: 11px; } section.install-section { grid-template-columns: 1fr; gap: 25px; padding: 34px 0 30px; } .section-heading .eyebrow { margin-bottom: 10px; } .section-heading h2 { font-size: 26px; } .section-intro { max-width: 380px; margin-top: 11px; font-size: 13px; } .section-note { margin-top: 8px; } .choices { gap: 10px; } .mode-choice { padding: 15px; } .choice-copy strong { font-size: 15px; } .choice-copy > span { margin-top: 2px; font-size: 12px; } .copy-step { margin-top: 20px; padding-top: 20px; } .next-action { padding: 17px 15px; margin-top: 20px; } .action-heading, .mode-options legend { font-size: 14px; gap: 10px; } .copy-hint { font-size: 11px; } }
267
190
  @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } *, *::before, *::after { animation: none !important; transition: none !important; } .motion-toggle { display: none; } .scene-stage { transform: none !important; } }
191
+
192
+ .package-architecture { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin: 16px 0; color: #e8e1db; }
193
+ .package-architecture select { padding: 8px; color: #e8e1db; background: #191719; border: 1px solid #74503d; border-radius: 4px; }
194
+ .package-download { margin: 16px 0; padding-bottom: 12px; border-bottom: 1px solid #373337; }
195
+ .package-download .primary { display: flex; align-items: center; justify-content: center; min-height: 44px; padding: 10px 16px; border-radius: 4px; font-size: 14px; text-decoration: none; }
196
+ .package-digest { display: block; overflow-wrap: anywhere; font-size: 11px; line-height: 1.6; color: #a79892; }
197
+ @media (max-width: 480px) { .method-switch span { padding: 9px 4px; font-size: 12px; } }
package/src/start.js CHANGED
@@ -1,4 +1,6 @@
1
- import { agentInstallPrompt, agentInstallPlan, terminalInstallScript, OFFICIAL_CONTROL_ORIGIN } from './onboarding.js';
1
+ import { renderPackageChoices } from './install-packages.js';
2
+ import { agentInstallPlan, terminalInstallScript, OFFICIAL_CONTROL_ORIGIN } from './onboarding.js';
3
+ import { agentSetupPrompt } from './agent-setup.js';
2
4
  import { initLanguage, getLocale, t } from './i18n.js';
3
5
  import { installWalkthroughMarkup } from './install-walkthrough.js';
4
6
 
@@ -8,8 +10,13 @@ const customOrigin = document.querySelector('#custom-origin');
8
10
  // Shared links only preselect UI choices; strict plan validation still gates copying.
9
11
  const guideParams = new URLSearchParams(location.search);
10
12
  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 method = ['manual', 'package'].includes(guideParams.get('method')) ? guideParams.get('method') : 'agent';
14
+ let os = ['windows', 'macos', 'linux'].includes(guideParams.get('os')) ? guideParams.get('os') : navigator.userAgent.includes('Linux') ? 'linux' : navigator.userAgent.includes('Windows') ? 'windows' : 'macos';
15
+ let packageArch = os === 'macos' ? 'arm64' : 'x64';
16
+ document.querySelector('#package-arch').value = packageArch;
17
+ let packages = [];
18
+ let packagesLoading = true;
19
+ let packagesFailed = false;
13
20
  let pairingMethod = 'browser';
14
21
  customOrigin.value = mode === 'self-hosted' ? guideParams.get('origin') ?? '' : '';
15
22
  for (const input of document.querySelectorAll('input[name="install-mode"]')) input.checked = input.value === mode;
@@ -59,6 +66,12 @@ function translatedText(selector, key) {
59
66
  }
60
67
  function updateGuidance() {
61
68
  const manual = method === 'manual';
69
+ document.querySelector('#windows-candidate').hidden = os !== 'windows';
70
+ document.querySelector('#package-options').hidden = method !== 'package';
71
+ document.querySelector('#copy-options').hidden = method === 'package';
72
+ document.querySelector('#next-action').hidden = method === 'package';
73
+ translatedText('#package-run', os === 'windows' ? 'guide.package.windows' : os === 'macos' ? 'guide.package.macos' : 'guide.package.linux');
74
+ renderPackageChoices(document.querySelector('#package-downloads'), { packages, os, arch: packageArch, loading: packagesLoading, failed: packagesFailed });
62
75
  translatedText('#copy-label', manual ? 'guide.manual.copy' : { local: 'guide.localCopy', official: 'guide.hostedCopy', 'self-hosted': 'guide.customCopy' }[mode]);
63
76
  translatedText('#copy-hint', manual ? 'guide.manual.copyHint' : 'guide.focus.copyHint');
64
77
  translatedText('#instruction-preview', manual ? 'guide.manual.preview' : 'guide.preview');
@@ -80,7 +93,7 @@ function refreshInstructions() {
80
93
  let plan;
81
94
  try {
82
95
  plan = agentInstallPlan(options);
83
- text.value = method === 'manual' ? terminalInstallScript({ ...options, browser: pairingMethod === 'browser' }) : agentInstallPrompt(options);
96
+ text.value = method === 'manual' ? terminalInstallScript({ ...options, browser: pairingMethod === 'browser' }) : agentSetupPrompt({ ...options, role: mode === 'local' ? 'manager' : 'device' });
84
97
  instructionsValid = true;
85
98
  customOrigin.removeAttribute('aria-invalid');
86
99
  const url = new URL('/login', plan.controlOrigin ?? OFFICIAL_CONTROL_ORIGIN);
@@ -95,7 +108,7 @@ function refreshInstructions() {
95
108
  result.textContent = t(errorKey(error));
96
109
  result.className = 'error';
97
110
  }
98
- document.querySelector('#install-walkthrough').innerHTML = installWalkthroughMarkup({ method, mode, os, pairingMethod, step: walkthroughStep, copied, controlOrigin: plan?.controlOrigin, localUrl: plan?.url, locale: getLocale() });
111
+ document.querySelector('#install-walkthrough').innerHTML = installWalkthroughMarkup({ method: method === 'package' ? 'manual' : method, mode, os: os === 'windows' ? 'windows' : 'posix', pairingMethod, step: walkthroughStep, copied, controlOrigin: plan?.controlOrigin, localUrl: plan?.url, locale: getLocale() });
99
112
  copyButton.disabled = !instructionsValid;
100
113
  }
101
114
  function resetCopyState(resetStep = true) {
@@ -135,6 +148,8 @@ document.querySelector('#pairing-options').addEventListener('change', event => {
135
148
  document.querySelector('#os-options').addEventListener('change', event => {
136
149
  if (!event.target.matches('input[name="install-os"]')) return;
137
150
  os = event.target.value;
151
+ packageArch = os === 'macos' ? 'arm64' : 'x64';
152
+ document.querySelector('#package-arch').value = packageArch;
138
153
  resetCopyState();
139
154
  });
140
155
  customOrigin.addEventListener('input', () => {
@@ -166,3 +181,11 @@ copyButton.addEventListener('click', async () => {
166
181
  document.querySelector('#walkthrough-title').focus({ preventScroll: true });
167
182
  }
168
183
  });
184
+
185
+ document.querySelector('#package-arch').addEventListener('change', event => { packageArch = event.target.value; updateGuidance(); });
186
+ fetch('/install/packages', { credentials: 'omit' }).then(async response => {
187
+ if (!response.ok) throw new Error('installer_catalog_unavailable');
188
+ const body = await response.json();
189
+ if (!Array.isArray(body.packages)) throw new Error('invalid_installer_catalog');
190
+ packages = body.packages;
191
+ }).catch(() => { packagesFailed = true; }).finally(() => { packagesLoading = false; updateGuidance(); });
package/src/styles.css CHANGED
@@ -271,3 +271,27 @@ details p { font-size: 12px; color: #8da37c; line-height: 1.8; }
271
271
  .device-actions { flex-wrap: wrap; } .device-footer { flex: 1 1 80px; white-space: nowrap; } .ssh-check-button { flex: 0 0 auto; }
272
272
  .empty-state { padding: 22px 12px; } .add-modal { padding: 19px; } .method-tabs button { font-size: 11px; } .connection-check { flex-wrap: wrap; }
273
273
  }
274
+
275
+ /* The panel uses the same instruction component as /start; illustrations are inert. */
276
+ .panel-install-modal { width: min(1040px, 100%); }
277
+ .panel-install-layout { display: grid; grid-template-columns: minmax(260px, .8fr) minmax(0, 1.2fr); gap: 26px; align-items: start; }
278
+ .panel-install-options { min-width: 0; }
279
+ .panel-install-options .field-label { display: block; margin: 16px 0 8px; }
280
+ .panel-install-options select { width: 100%; min-height: 38px; padding: 8px 10px; border: 1px solid #dce4d8; border-radius: 7px; color: #344137; background: #fff; font: inherit; font-size: 12px; }
281
+ .panel-install-options .panel-install-methods { margin: 12px 0; }
282
+ .panel-install-options .panel-install-methods button { flex: 1; }
283
+ .install-destination { padding: 11px 13px; border: 1px solid var(--line); border-radius: 8px; background: #f5f8f3; }
284
+ .install-destination span { display: block; color: #78857b; font-size: 10px; margin-bottom: 5px; }
285
+ .install-destination strong { display: block; color: #366548; font-size: 12px; overflow-wrap: anywhere; }
286
+ .install-target-hint { color: #356447; background: #edf5ed; border-left: 3px solid #7aa384; padding: 10px 12px; margin: 12px 0; font-size: 12px; line-height: 1.8; }
287
+ .install-options-details { margin: 14px 0; color: #657660; font-size: 11px; }
288
+ .install-options-details summary { cursor: pointer; }
289
+ .panel-install-links { display: flex; flex-direction: column; align-items: flex-start; gap: 12px; margin-top: 18px; font-size: 11px; }
290
+ .panel-install-links a { color: var(--green); text-underline-offset: 3px; }
291
+ .panel-install-links button { font-size: 11px; padding: 0; text-align: left; }
292
+ .panel-walkthrough { --mono: "SFMono-Regular", Consolas, monospace; min-width: 0; border-radius: 10px; padding: 18px; background: #151213; color: #eeece8; }
293
+ .panel-walkthrough h3 { margin: 0 0 7px; font-size: 14px; font-weight: 600; }
294
+ .panel-walkthrough > .walkthrough-disclaimer { margin: 0 0 17px; font-size: 10px; color: #aa9382; line-height: 1.7; }
295
+ .panel-walkthrough button:disabled { cursor: default; }
296
+ .guide-navigation { margin-top: 14px; }
297
+ @media (max-width: 760px) { .panel-install-layout { grid-template-columns: 1fr; gap: 22px; } .panel-install-modal { padding: 20px; } .panel-walkthrough { padding: 14px; } }
package/start.html CHANGED
@@ -32,7 +32,7 @@
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
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>
35
+ <p class="release-note"><span class="release-dot" aria-hidden="true"></span><span data-i18n="guide.badge">开发预览 · 0.2.0-beta.7 · 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>
@@ -113,18 +113,31 @@
113
113
  <fieldset class="pairing-options" id="os-options">
114
114
  <legend data-i18n="guide.os.title">目标电脑的系统</legend>
115
115
  <div class="pairing-choices">
116
- <label><input type="radio" name="install-os" value="posix" checked><span>macOS / Linux</span></label>
116
+ <label><input type="radio" name="install-os" value="macos" checked><span>macOS</span></label>
117
+ <label><input type="radio" name="install-os" value="linux"><span>Linux</span></label>
117
118
  <label><input type="radio" name="install-os" value="windows"><span>Windows 11</span></label>
118
119
  </div>
119
120
  </fieldset>
120
- <p class="hint" data-i18n="guide.windowsCandidate">Windows 11 预览版 · 使用 PowerShell 安装,按图示核对绑定与后台状态。</p>
121
+ <p class="hint" id="windows-candidate" data-i18n="guide.windowsCandidate" hidden>Windows 11 预览版 · 安装后核对绑定与后台状态。</p>
121
122
  <fieldset class="method-options">
122
123
  <legend class="action-heading"><span class="action-number" aria-hidden="true">02</span><span data-i18n="guide.method.title">选择安装方式</span></legend>
123
124
  <div class="method-switch">
124
125
  <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
+ <label><input type="radio" name="install-method" value="package"><span data-i18n="guide.method.package">下载安装包</span></label>
127
+ <label><input type="radio" name="install-method" value="manual"><span data-i18n="guide.method.script">复制脚本</span></label>
126
128
  </div>
127
129
  </fieldset>
130
+ <div id="package-options" hidden>
131
+ <p class="hint" data-i18n="guide.package.description">内含程序、运行环境和依赖,无需系统 Node.js 或 npm 下载。</p>
132
+ <label class="package-architecture"><span data-i18n="guide.package.arch">处理器架构</span>
133
+ <select id="package-arch"><option value="arm64">ARM64 / Apple Silicon</option><option value="x64">x64 / Intel / AMD</option></select>
134
+ </label>
135
+ <div id="package-downloads" aria-live="polite"></div>
136
+ <p class="hint" id="package-run"></p>
137
+ <p class="hint" data-i18n="guide.package.confirmations">在安装向导中确认本机、官方或已有自建服务,再确认后台运行与本人配对。</p>
138
+ <a href="/install/offline.md" data-i18n="guide.package.details">查看安装包说明</a>
139
+ </div>
140
+ <div id="copy-options">
128
141
  <div id="manual-options" hidden>
129
142
  <p class="hint manual-prerequisites" id="manual-prerequisites" data-i18n="guide.manual.prerequisites">Node.js 24+ · 全新安装;已有安装会停止并保留配置。</p>
130
143
  <fieldset class="pairing-options" id="pairing-options" hidden>
@@ -139,6 +152,7 @@
139
152
  <p class="copy-hint" id="copy-hint" data-i18n="guide.focus.copyHint">这是给 Agent 的说明,请粘贴到 Agent 对话中,而不是终端。</p>
140
153
  <p id="copy-result" role="status" aria-live="polite" aria-atomic="true"></p>
141
154
  <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>
155
+ </div>
142
156
  </div>
143
157
  <div class="next-action" id="next-action" data-copied="false">
144
158
  <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>
@@ -156,7 +170,7 @@
156
170
  <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>
157
171
  </section>
158
172
  </main>
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>
173
+ <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.7" target="_blank" rel="noopener noreferrer" data-i18n="guide.source">MIT npm 源码包 ↗</a><span data-i18n="guide.footer">本机安装 · 私钥留在设备 · 操作单独授权</span></footer>
160
174
  <script type="module" src="/src/start.js"></script>
161
175
  </body>
162
176
  </html>