dsh-cherry-bridge 0.1.11 → 0.2.1
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/README.en.md +15 -8
- package/README.md +13 -6
- package/dist/index.d.mts +32 -3
- package/dist/index.mjs +1563 -168
- package/docs/e2e-checklist.md +50 -2
- package/lib/client.js +311 -31
- package/package.json +4 -3
- package/src/AGENTS.md +949 -0
- package/src/connection.ts +287 -11
- package/src/deviceStore.ts +518 -6
- package/src/diagnostics.ts +30 -0
- package/src/httpApi.ts +351 -23
- package/src/index.ts +203 -8
- package/src/pairing.ts +504 -112
- package/src/rpc/jsonRpcClient.ts +19 -1
- package/src/subscriptions.ts +10 -2
- package/src/text.ts +158 -0
- package/src/tools.ts +329 -23
- package/ui/AGENTS.md +44 -0
- package/ui/src/App.tsx +26 -10
- package/ui/src/DeviceCard.tsx +141 -4
- package/ui/src/PairingWizard.tsx +80 -5
- package/ui/src/ToolScopePanel.tsx +19 -6
- package/ui/src/api.ts +111 -17
- package/ui/src/i18n.ts +49 -0
- package/ui/src/styles.css +24 -0
- package/ui/src/types.ts +16 -1
- package/src/urlHost.ts +0 -15
package/ui/src/i18n.ts
CHANGED
|
@@ -86,11 +86,24 @@ const zh: Dict = {
|
|
|
86
86
|
'device.field.name': '设备名称',
|
|
87
87
|
'device.field.hosts': '主机地址',
|
|
88
88
|
'device.hosts.hint': '多个候选地址用逗号分隔(整体替换)',
|
|
89
|
+
'device.field.ports': '每地址端口',
|
|
90
|
+
'device.ports.hint': '部分候选地址的端口与上方的端口不同;在「地址维护」里改动端口会把所有候选地址改为同一端口。',
|
|
91
|
+
'device.ports.current': '当前端口映射:',
|
|
92
|
+
'device.port.hint': '保持当前值会保留各地址自己的端口;改成新值会把所有候选地址重指到该端口。',
|
|
89
93
|
'device.save.name': '保存名称',
|
|
90
94
|
'device.save.addresses': '保存地址',
|
|
91
95
|
'device.remove.confirm': '确认移除',
|
|
92
96
|
'device.remove.warning': '移除后本机将不再连接该设备,凭据保留在对方一侧。',
|
|
93
97
|
'device.cancel': '取消',
|
|
98
|
+
// -- forgotten addresses (issue #204) --------------------------------------
|
|
99
|
+
'device.forget': '忘记',
|
|
100
|
+
'device.remember': '恢复',
|
|
101
|
+
'device.field.forgotten': '已忘记地址',
|
|
102
|
+
'device.forgotten.hint':
|
|
103
|
+
'已忘记的地址不会再被连接;「恢复」只解除标记,桌面下次广播该地址时会重新加入。',
|
|
104
|
+
'device.error.lastAddress': '不能忘记最后一个候选地址——先补一个地址,再忘记这个',
|
|
105
|
+
// The page-wide request ceiling (`api.ts`) reached a card action.
|
|
106
|
+
'device.error.timeout': '请求长时间没有回应,已中止——请稍后重试',
|
|
94
107
|
'device.error.nameRequired': '设备名称不能为空',
|
|
95
108
|
'device.error.hostsRequired': '至少需要一个主机地址',
|
|
96
109
|
'device.error.portInvalid': '端口必须是 1..65535 之间的整数',
|
|
@@ -125,7 +138,17 @@ const zh: Dict = {
|
|
|
125
138
|
// keep matching.
|
|
126
139
|
'wizard.failed': '配对失败:{stage}',
|
|
127
140
|
'wizard.unknownError': '未知错误',
|
|
141
|
+
// NO VERDICT WORDING. This is the run deadline, not a stage: the pairing may
|
|
142
|
+
// have been approved while its frame was lost, so it points at the list right
|
|
143
|
+
// below (which the settle refreshes) instead of asserting an outcome.
|
|
144
|
+
// The page-wide request ceiling (`api.ts`): the device list gave up waiting.
|
|
145
|
+
'app.error.timeout': '请求长时间没有回应,已中止——请确认宿主仍在运行;若反复出现,请重启宿主后再试',
|
|
146
|
+
'wizard.timedOut':
|
|
147
|
+
'没有等到配对结果——若桌面端已批准,下面的设备列表里会出现该设备;否则请在 Cherry Studio 重新生成配对码后重试',
|
|
128
148
|
'wizard.error.payloadRequired': '请先粘贴扫码页显示的配对信息',
|
|
149
|
+
// The pairing POST never answered. Names the LIKELY cause (a wedged host) and
|
|
150
|
+
// nothing about pairing itself, so the user does not go re-scanning a QR code.
|
|
151
|
+
'wizard.error.timeout': '配对请求迟迟没有回应,已中止——请重试;若反复出现,宿主可能已卡住,重启后再试',
|
|
129
152
|
// The stages the wizard narrates while a run is in flight.
|
|
130
153
|
'wizard.stage.validate': '正在校验配对信息…',
|
|
131
154
|
'wizard.stage.connect': '正在连接 Cherry Studio…',
|
|
@@ -178,6 +201,9 @@ const zh: Dict = {
|
|
|
178
201
|
// A refusal whose code this panel has no copy for: the server's own message is
|
|
179
202
|
// the best available explanation, so it is interpolated verbatim.
|
|
180
203
|
'scope.saveFailed': '保存失败:{cause}',
|
|
204
|
+
// The page-wide request ceiling (`api.ts`), rendered where the server's own
|
|
205
|
+
// message would go: there is no server message when nothing answered.
|
|
206
|
+
'scope.error.timeout': '请求长时间没有回应,已中止——请稍后重试',
|
|
181
207
|
'scope.serverSaid': '服务端返回 {code}(HTTP {status})。',
|
|
182
208
|
'scope.degraded.install': '隐藏层应用曾失败(install):相关 agent 的隐藏层未生效或无法确认,启用范围对它可能不适用',
|
|
183
209
|
'scope.degraded.workspace-list': '注册表列表当前读不到',
|
|
@@ -236,11 +262,26 @@ const en: Dict = {
|
|
|
236
262
|
'device.field.name': 'Device name',
|
|
237
263
|
'device.field.hosts': 'Host addresses',
|
|
238
264
|
'device.hosts.hint': 'Separate candidate addresses with commas (the whole list is replaced)',
|
|
265
|
+
'device.field.ports': 'Ports per address',
|
|
266
|
+
'device.ports.hint':
|
|
267
|
+
'Some candidate addresses dial a different port than the one above; changing the port in address maintenance moves every candidate address to that one port.',
|
|
268
|
+
'device.ports.current': 'Current port map:',
|
|
269
|
+
'device.port.hint':
|
|
270
|
+
'Keeping the current value preserves each address’s own port; a new value re-points every candidate address.',
|
|
239
271
|
'device.save.name': 'Save name',
|
|
240
272
|
'device.save.addresses': 'Save addresses',
|
|
241
273
|
'device.remove.confirm': 'Confirm removal',
|
|
242
274
|
'device.remove.warning': 'This machine will stop connecting to the device. Its credentials stay on the peer.',
|
|
243
275
|
'device.cancel': 'Cancel',
|
|
276
|
+
// -- forgotten addresses (issue #204) --------------------------------------
|
|
277
|
+
'device.forget': 'Forget',
|
|
278
|
+
'device.remember': 'Restore',
|
|
279
|
+
'device.field.forgotten': 'Forgotten addresses',
|
|
280
|
+
'device.forgotten.hint':
|
|
281
|
+
'Forgotten addresses are never dialed. Restore only lifts the mark — the address comes back the next time the desktop advertises it.',
|
|
282
|
+
'device.error.lastAddress': 'The last candidate address cannot be forgotten. Add one first.',
|
|
283
|
+
// See the zh entry: the page-wide request ceiling.
|
|
284
|
+
'device.error.timeout': 'The request stopped answering and was abandoned. Try again in a moment.',
|
|
244
285
|
'device.error.nameRequired': 'Device name cannot be empty',
|
|
245
286
|
'device.error.hostsRequired': 'At least one host address is required',
|
|
246
287
|
'device.error.portInvalid': 'Port must be an integer between 1 and 65535',
|
|
@@ -267,7 +308,14 @@ const en: Dict = {
|
|
|
267
308
|
'wizard.succeeded': 'Paired',
|
|
268
309
|
'wizard.failed': 'Pairing failed: {stage}',
|
|
269
310
|
'wizard.unknownError': 'Unknown error',
|
|
311
|
+
// See the zh entry: the run deadline is not a verdict.
|
|
312
|
+
// See the zh entry: the page-wide request ceiling.
|
|
313
|
+
'app.error.timeout': 'The request stopped answering and was abandoned. Check that the host is still running; if it keeps happening, restart it and try again.',
|
|
314
|
+
'wizard.timedOut':
|
|
315
|
+
'No pairing result arrived. If the desktop approved it, the device appears in the list below; otherwise generate a new pairing code in Cherry Studio and try again.',
|
|
270
316
|
'wizard.error.payloadRequired': 'Paste the pairing payload shown on the scan page first',
|
|
317
|
+
// See the zh entry: the cause is the host, not the payload.
|
|
318
|
+
'wizard.error.timeout': 'The pairing request stopped answering and was abandoned. Try again; if it keeps happening the host may be stuck — restart it and retry.',
|
|
271
319
|
'wizard.stage.validate': 'Validating the pairing payload…',
|
|
272
320
|
'wizard.stage.connect': 'Connecting to Cherry Studio…',
|
|
273
321
|
'wizard.stage.claim': 'Exchanging credentials…',
|
|
@@ -317,6 +365,7 @@ const en: Dict = {
|
|
|
317
365
|
'scope.savedPruned': 'Saved. The server pruned these stale selections: {list}.',
|
|
318
366
|
'scope.reread': 'Reload',
|
|
319
367
|
'scope.saveFailed': 'Save failed: {cause}',
|
|
368
|
+
'scope.error.timeout': 'The request stopped answering and was abandoned. Try again in a moment.',
|
|
320
369
|
'scope.serverSaid': 'The server answered {code} (HTTP {status}).',
|
|
321
370
|
'scope.degraded.install':
|
|
322
371
|
'Applying a hidden layer failed earlier (install): the affected agent’s hidden layer is not in effect or cannot be confirmed, so the tool scope may not apply to it',
|
package/ui/src/styles.css
CHANGED
|
@@ -227,6 +227,30 @@
|
|
|
227
227
|
overflow-wrap: anywhere;
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
+
/* A per-host action list (issue #204): the address, then its own control. The
|
|
231
|
+
buttons stay small so a record with a dozen candidates stays ONE fact row
|
|
232
|
+
instead of a column of chrome. */
|
|
233
|
+
.cherry-address-list {
|
|
234
|
+
display: flex;
|
|
235
|
+
flex-wrap: wrap;
|
|
236
|
+
gap: 4px 10px;
|
|
237
|
+
margin: 0;
|
|
238
|
+
padding: 0;
|
|
239
|
+
list-style: none;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.cherry-address-list li {
|
|
243
|
+
display: inline-flex;
|
|
244
|
+
align-items: center;
|
|
245
|
+
gap: 6px;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.cherry-address-list button {
|
|
249
|
+
padding: 0 6px;
|
|
250
|
+
font-size: 11px;
|
|
251
|
+
line-height: 1.6;
|
|
252
|
+
}
|
|
253
|
+
|
|
230
254
|
.cherry-inline-form {
|
|
231
255
|
display: flex;
|
|
232
256
|
flex-direction: column;
|
package/ui/src/types.ts
CHANGED
|
@@ -23,7 +23,14 @@ export const API_BASE = '/cherry-remote/api'
|
|
|
23
23
|
/** The five device states the connection layer reports. */
|
|
24
24
|
export type DeviceState = 'connected' | 'connecting' | 'offline' | 'disabled' | 'identity-mismatch'
|
|
25
25
|
|
|
26
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* One device exactly as `GET /devices` returns it. Never carries a credential.
|
|
28
|
+
*
|
|
29
|
+
* `ports` is the per-HOST dial port map: each candidate address dials its own
|
|
30
|
+
* port, and the record-level `port` is the fallback for a host the map does not
|
|
31
|
+
* carry (a stored v4 record has an entry for every host, so the card's `??` arm
|
|
32
|
+
* only fires on a malformed map). Required — `GET /devices` always emits it.
|
|
33
|
+
*/
|
|
27
34
|
export interface DeviceSummary {
|
|
28
35
|
deviceId: string
|
|
29
36
|
displayName: string
|
|
@@ -31,6 +38,14 @@ export interface DeviceSummary {
|
|
|
31
38
|
enabled: boolean
|
|
32
39
|
hosts: string[]
|
|
33
40
|
port: number
|
|
41
|
+
ports: Record<string, number>
|
|
42
|
+
/**
|
|
43
|
+
* The addresses the operator has FORGOTTEN (issues #185/#204): not dialed, and
|
|
44
|
+
* not re-learned from the desktop's own advertisement until 恢复 lifts the
|
|
45
|
+
* mark. Required — `GET /devices` emits an empty list rather than omitting the
|
|
46
|
+
* key — so the card branches on `length`, never on presence.
|
|
47
|
+
*/
|
|
48
|
+
forgottenHosts: string[]
|
|
34
49
|
}
|
|
35
50
|
|
|
36
51
|
/** A `PATCH /devices/:id` body; every field optional, `hosts` is whole-replace. */
|
package/src/urlHost.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Renders a host as a URL authority component. The device archive and pairing
|
|
3
|
-
* flow store BARE hosts (plan-mandated normalization), so a bare IPv6 literal
|
|
4
|
-
* must be bracketed before it is spliced into a URL — `ws://::1:35543/...`
|
|
5
|
-
* is not a valid URL and `new WebSocket(...)` throws synchronously on it.
|
|
6
|
-
*
|
|
7
|
-
* Shared home for the dial sites: both `connection.ts` (per-device dial) and
|
|
8
|
-
* `pairing.ts` (candidate dial) splice hosts into URLs and must bracket the
|
|
9
|
-
* same way.
|
|
10
|
-
*/
|
|
11
|
-
export function urlHost(host: string): string {
|
|
12
|
-
// A colon can only mean IPv6 here: a `host:port` pair never reaches this
|
|
13
|
-
// layer (the port travels separately).
|
|
14
|
-
return host.includes(':') ? `[${host}]` : host
|
|
15
|
-
}
|