dsh-zen-remote 2.3.11 → 2.4.0
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/CHANGELOG.md +39 -78
- package/README.md +94 -84
- package/README.zh-CN.md +90 -81
- package/bin/zen-remote.mjs +5 -0
- package/docs/interface.md +6 -6
- package/docs/remote-access.en.md +6 -4
- package/docs/remote-access.md +6 -4
- package/dsh-push.mjs +96 -10
- package/lib/adapters/dsh-better-sidebar/index.js +7 -3
- package/lib/adapters/dsh-better-sidebar/index.js.map +1 -1
- package/lib/admin-routes.js +10 -2
- package/lib/admin-routes.js.map +1 -1
- package/lib/cli/args.js +251 -0
- package/lib/cli/args.js.map +1 -0
- package/lib/cli/bootstrap.js +449 -0
- package/lib/cli/bootstrap.js.map +1 -0
- package/lib/cli/command-setup.js +484 -0
- package/lib/cli/command-setup.js.map +1 -0
- package/lib/cli/commands-client.js +359 -0
- package/lib/cli/commands-client.js.map +1 -0
- package/lib/cli/commands-host.js +1108 -0
- package/lib/cli/commands-host.js.map +1 -0
- package/lib/cli/commands-status.js +279 -0
- package/lib/cli/commands-status.js.map +1 -0
- package/lib/cli/config-writer.js +334 -0
- package/lib/cli/config-writer.js.map +1 -0
- package/lib/cli/context.js +10 -0
- package/lib/cli/context.js.map +1 -0
- package/lib/cli/i18n.js +620 -0
- package/lib/cli/i18n.js.map +1 -0
- package/lib/cli/ipc-client.js +160 -0
- package/lib/cli/ipc-client.js.map +1 -0
- package/lib/cli/ipc-server.js +464 -0
- package/lib/cli/ipc-server.js.map +1 -0
- package/lib/cli/main.js +245 -0
- package/lib/cli/main.js.map +1 -0
- package/lib/cli/qr-terminal.js +48 -0
- package/lib/cli/qr-terminal.js.map +1 -0
- package/lib/client-data/qr.js +286 -0
- package/lib/client-data/qr.js.map +1 -0
- package/lib/client-routes.js +308 -25
- package/lib/client-routes.js.map +1 -1
- package/lib/client.js +1013 -229
- package/lib/config.js +50 -12
- package/lib/config.js.map +1 -1
- package/lib/debug-report.js +28 -1
- package/lib/debug-report.js.map +1 -1
- package/lib/fetch-route-intercept.js +334 -26
- package/lib/fetch-route-intercept.js.map +1 -1
- package/lib/index.js +564 -363
- package/lib/index.js.map +1 -1
- package/lib/intercept.js +1833 -280
- package/lib/intercept.js.map +1 -1
- package/lib/lan-gate-server.cjs +36 -10
- package/lib/merge-streams.js +103 -11
- package/lib/merge-streams.js.map +1 -1
- package/lib/plugin-route-intercept.js +93 -63
- package/lib/plugin-route-intercept.js.map +1 -1
- package/lib/relay-access.js +8 -1
- package/lib/relay-access.js.map +1 -1
- package/lib/relay-client.js +133 -4
- package/lib/relay-client.js.map +1 -1
- package/lib/relay-pool.js +275 -0
- package/lib/relay-pool.js.map +1 -0
- package/lib/relay-server.js +269 -17
- package/lib/relay-server.js.map +1 -1
- package/lib/restart-fields.js +6 -4
- package/lib/restart-fields.js.map +1 -1
- package/lib/share-store.js +65 -27
- package/lib/share-store.js.map +1 -1
- package/lib/state-store.js +400 -0
- package/lib/state-store.js.map +1 -0
- package/lib/types/adapters/dsh-better-sidebar/index.d.ts +3 -2
- package/lib/types/adapters/dsh-better-sidebar/index.d.ts.map +1 -1
- package/lib/types/admin-routes.d.ts.map +1 -1
- package/lib/types/cli/args.d.ts +78 -0
- package/lib/types/cli/args.d.ts.map +1 -0
- package/lib/types/cli/bootstrap.d.ts +170 -0
- package/lib/types/cli/bootstrap.d.ts.map +1 -0
- package/lib/types/cli/command-setup.d.ts +72 -0
- package/lib/types/cli/command-setup.d.ts.map +1 -0
- package/lib/types/cli/commands-client.d.ts +36 -0
- package/lib/types/cli/commands-client.d.ts.map +1 -0
- package/lib/types/cli/commands-host.d.ts +82 -0
- package/lib/types/cli/commands-host.d.ts.map +1 -0
- package/lib/types/cli/commands-status.d.ts +46 -0
- package/lib/types/cli/commands-status.d.ts.map +1 -0
- package/lib/types/cli/config-writer.d.ts +92 -0
- package/lib/types/cli/config-writer.d.ts.map +1 -0
- package/lib/types/cli/context.d.ts +39 -0
- package/lib/types/cli/context.d.ts.map +1 -0
- package/lib/types/cli/i18n.d.ts +32 -0
- package/lib/types/cli/i18n.d.ts.map +1 -0
- package/lib/types/cli/ipc-client.d.ts +83 -0
- package/lib/types/cli/ipc-client.d.ts.map +1 -0
- package/lib/types/cli/ipc-server.d.ts +113 -0
- package/lib/types/cli/ipc-server.d.ts.map +1 -0
- package/lib/types/cli/main.d.ts +57 -0
- package/lib/types/cli/main.d.ts.map +1 -0
- package/lib/types/cli/qr-terminal.d.ts +17 -0
- package/lib/types/cli/qr-terminal.d.ts.map +1 -0
- package/lib/types/client/RemoteStatusIcon.d.ts.map +1 -1
- package/lib/types/client/current-session-report.d.ts +4 -0
- package/lib/types/client/current-session-report.d.ts.map +1 -1
- package/lib/types/client/effects/model-group-side.d.ts.map +1 -1
- package/lib/types/client/effects/remote-session.d.ts.map +1 -1
- package/lib/types/client/locales.d.ts +47 -3
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/settings/SettingsSection.d.ts.map +1 -1
- package/lib/types/client/settings/settings-css.d.ts +1 -1
- package/lib/types/client/settings/settings-css.d.ts.map +1 -1
- package/lib/types/client/styles/remote-session.css.d.ts +1 -1
- package/lib/types/client/styles/remote-session.css.d.ts.map +1 -1
- package/lib/types/client/wizard/GatewayWizard.d.ts.map +1 -1
- package/lib/types/client/wizard/cards/ProxyCard.d.ts.map +1 -1
- package/lib/types/client/wizard/wizard-css.d.ts +1 -1
- package/lib/types/client/wizard/wizard-css.d.ts.map +1 -1
- package/lib/types/client-data/model-group-side.d.ts +23 -2
- package/lib/types/client-data/model-group-side.d.ts.map +1 -1
- package/lib/types/client-data/remote-status.d.ts +47 -1
- package/lib/types/client-data/remote-status.d.ts.map +1 -1
- package/lib/types/client-data/settings-form.d.ts +85 -1
- package/lib/types/client-data/settings-form.d.ts.map +1 -1
- package/lib/types/client-data/wizard.d.ts +56 -1
- package/lib/types/client-data/wizard.d.ts.map +1 -1
- package/lib/types/client-routes.d.ts +25 -1
- package/lib/types/client-routes.d.ts.map +1 -1
- package/lib/types/config.d.ts +40 -14
- package/lib/types/config.d.ts.map +1 -1
- package/lib/types/debug-report.d.ts.map +1 -1
- package/lib/types/fetch-route-intercept.d.ts +66 -8
- package/lib/types/fetch-route-intercept.d.ts.map +1 -1
- package/lib/types/index.d.ts +9 -2
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/intercept.d.ts +72 -10
- package/lib/types/intercept.d.ts.map +1 -1
- package/lib/types/merge-streams.d.ts +79 -1
- package/lib/types/merge-streams.d.ts.map +1 -1
- package/lib/types/plugin-route-intercept.d.ts +25 -9
- package/lib/types/plugin-route-intercept.d.ts.map +1 -1
- package/lib/types/relay-access.d.ts +3 -1
- package/lib/types/relay-access.d.ts.map +1 -1
- package/lib/types/relay-client.d.ts +23 -2
- package/lib/types/relay-client.d.ts.map +1 -1
- package/lib/types/relay-pool.d.ts +148 -0
- package/lib/types/relay-pool.d.ts.map +1 -0
- package/lib/types/relay-server.d.ts +30 -3
- package/lib/types/relay-server.d.ts.map +1 -1
- package/lib/types/restart-fields.d.ts +5 -5
- package/lib/types/restart-fields.d.ts.map +1 -1
- package/lib/types/share-store.d.ts.map +1 -1
- package/lib/types/state-store.d.ts +179 -0
- package/lib/types/state-store.d.ts.map +1 -0
- package/lib/types/upload-limits.d.ts +15 -0
- package/lib/types/upload-limits.d.ts.map +1 -1
- package/lib/types/virtual-id.d.ts +21 -0
- package/lib/types/virtual-id.d.ts.map +1 -1
- package/lib/types/wizard/probe.d.ts.map +1 -1
- package/lib/types/wizard/proxy-snippets.d.ts +3 -0
- package/lib/types/wizard/proxy-snippets.d.ts.map +1 -1
- package/lib/types/wizard/routes.d.ts +1 -0
- package/lib/types/wizard/routes.d.ts.map +1 -1
- package/lib/types/wizard/status.d.ts +9 -0
- package/lib/types/wizard/status.d.ts.map +1 -1
- package/lib/types/wizard/verify.d.ts +3 -0
- package/lib/types/wizard/verify.d.ts.map +1 -1
- package/lib/upload-limits.js +15 -0
- package/lib/upload-limits.js.map +1 -1
- package/lib/virtual-id.js +60 -0
- package/lib/virtual-id.js.map +1 -1
- package/lib/wizard/probe.js +46 -1
- package/lib/wizard/probe.js.map +1 -1
- package/lib/wizard/proxy-snippets.js +14 -1
- package/lib/wizard/proxy-snippets.js.map +1 -1
- package/lib/wizard/routes.js +23 -4
- package/lib/wizard/routes.js.map +1 -1
- package/lib/wizard/status.js +21 -0
- package/lib/wizard/status.js.map +1 -1
- package/lib/wizard/verify.js +31 -8
- package/lib/wizard/verify.js.map +1 -1
- package/package.json +8 -4
- package/pwa/icons/badge-96.png +0 -0
- package/pwa/icons/badge.svg +1 -0
- package/pwa/manifest.json +2 -1
- package/pwa/sw.js +3 -1
- package/scripts/check-debug-report.mjs +41 -1
- package/scripts/check-settings-form.mjs +14 -3
- package/scripts/check-settings-view.mjs +247 -0
- package/scripts/check-wizard-ui.mjs +37 -0
- package/src/adapters/dsh-better-sidebar/index.ts +7 -3
- package/src/admin-routes.ts +10 -2
- package/src/cli/args.ts +289 -0
- package/src/cli/bootstrap.ts +569 -0
- package/src/cli/command-setup.ts +540 -0
- package/src/cli/commands-client.ts +369 -0
- package/src/cli/commands-host.ts +1127 -0
- package/src/cli/commands-status.ts +296 -0
- package/src/cli/config-writer.ts +354 -0
- package/src/cli/context.ts +38 -0
- package/src/cli/i18n.ts +645 -0
- package/src/cli/ipc-client.ts +216 -0
- package/src/cli/ipc-server.ts +531 -0
- package/src/cli/main.ts +285 -0
- package/src/cli/qr-terminal.ts +49 -0
- package/src/client/RemoteStatusIcon.tsx +4 -1
- package/src/client/current-session-report.ts +28 -4
- package/src/client/effects/model-group-side.ts +19 -5
- package/src/client/effects/remote-session.ts +26 -6
- package/src/client/locales.ts +96 -6
- package/src/client/settings/SettingsSection.tsx +287 -92
- package/src/client/settings/settings-css.ts +18 -0
- package/src/client/styles/remote-session.css.ts +15 -1
- package/src/client/wizard/GatewayWizard.tsx +470 -109
- package/src/client/wizard/cards/ProxyCard.tsx +2 -1
- package/src/client/wizard/wizard-css.ts +23 -0
- package/src/client-data/model-group-side.ts +37 -10
- package/src/client-data/remote-status.ts +112 -17
- package/src/client-data/settings-form.ts +164 -8
- package/src/client-data/wizard.ts +112 -2
- package/src/client-routes.ts +334 -26
- package/src/config.ts +62 -16
- package/src/debug-report.ts +27 -1
- package/src/fetch-route-intercept.ts +381 -32
- package/src/index.ts +566 -367
- package/src/intercept.ts +1866 -286
- package/src/merge-streams.ts +155 -10
- package/src/plugin-route-intercept.ts +120 -71
- package/src/relay-access.ts +14 -3
- package/src/relay-client.ts +163 -5
- package/src/relay-pool.ts +387 -0
- package/src/relay-server.ts +312 -16
- package/src/restart-fields.ts +6 -4
- package/src/share-store.ts +59 -26
- package/src/state-store.ts +428 -0
- package/src/upload-limits.ts +16 -0
- package/src/virtual-id.ts +57 -0
- package/src/wizard/probe.ts +54 -2
- package/src/wizard/proxy-snippets.ts +17 -2
- package/src/wizard/routes.ts +24 -5
- package/src/wizard/status.ts +28 -0
- package/src/wizard/verify.ts +33 -11
- package/AGENTS.md +0 -185
- package/docs/adr/0001-plugin-bridge-forwards-plugin-routes.md +0 -15
- package/docs/adr/0002-relay-emulates-websocket.md +0 -8
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI-2 IPC client: the CLI half of the local control channel that
|
|
3
|
+
* `src/cli/ipc-server.ts` serves inside the DSH plugin process. Discovery
|
|
4
|
+
* walks the same candidate socket paths the server binds (or a single
|
|
5
|
+
* explicit `--socket` path), pings each existing one with a short 200ms
|
|
6
|
+
* budget, and sorts the answers into three buckets the command layer can
|
|
7
|
+
* speak about: LIVE instances whose `cliProtocolVersion` matches, sockets
|
|
8
|
+
* speaking a DIFFERENT protocol version (the upgrade-prompt case), and paths
|
|
9
|
+
* that refused us with EACCES / EPERM (the "DSH runs as another user, try
|
|
10
|
+
* `sudo -E`" case).
|
|
11
|
+
*
|
|
12
|
+
* The request helper is a thin http-over-Unix-socket (Windows: named pipe)
|
|
13
|
+
* round trip. Transport errors REJECT — a dead socket is a real failure the
|
|
14
|
+
* caller must see — while HTTP error statuses resolve normally: the IPC
|
|
15
|
+
* protocol answers every failure as JSON (`{ ok: false, error: … }`), so a
|
|
16
|
+
* 409 wrong-role or 409 env-shadowed is data, not an exception.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { request as httpRequest } from 'node:http'
|
|
20
|
+
import type { IncomingHttpHeaders } from 'node:http'
|
|
21
|
+
import { statSync } from 'node:fs'
|
|
22
|
+
import { CLI_CONFIG_ROUTE, CLI_PING_ROUTE, CLI_PROTOCOL_VERSION, candidateSocketPaths } from './ipc-server.js'
|
|
23
|
+
import { CLIENT_DEBUG_REPORT_ROUTE, CLIENT_STATUS_ROUTE } from '../client-routes.js'
|
|
24
|
+
|
|
25
|
+
/** What a live plugin instance answers `GET /ping` with. Mirrors the body
|
|
26
|
+
* `src/cli/ipc-server.ts` builds (minus `ok`). */
|
|
27
|
+
export interface CliPingResponse {
|
|
28
|
+
ok: true
|
|
29
|
+
cliProtocolVersion: number
|
|
30
|
+
pluginVersion: string
|
|
31
|
+
pid: number
|
|
32
|
+
role: 'host' | 'client' | 'peer'
|
|
33
|
+
serves: boolean
|
|
34
|
+
connects: boolean
|
|
35
|
+
serverName: string
|
|
36
|
+
profileName?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type DiscoveredLive = CliPingResponse & { socketPath: string }
|
|
40
|
+
|
|
41
|
+
export interface DiscoveredSockets {
|
|
42
|
+
/** Version-matching live instances, one entry per answering socket. */
|
|
43
|
+
live: DiscoveredLive[]
|
|
44
|
+
/** Sockets that answered but speak another `cliProtocolVersion`. */
|
|
45
|
+
versionMismatch: { socketPath: string; cliProtocolVersion: number | undefined }[]
|
|
46
|
+
/** Paths that refused the probe (or even the stat) with EACCES / EPERM —
|
|
47
|
+
* the cross-user DSH case. */
|
|
48
|
+
permissionDenied: string[]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface DiscoverCliSocketsOptions {
|
|
52
|
+
dshHome: string
|
|
53
|
+
/** Probe ONLY this path instead of the candidate walk. */
|
|
54
|
+
explicitSocket?: string
|
|
55
|
+
/** Bind-shape override (tests pin both platforms). */
|
|
56
|
+
platform?: NodeJS.Platform
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface CliIpcRequestResult {
|
|
60
|
+
status: number
|
|
61
|
+
body: unknown
|
|
62
|
+
headers: IncomingHttpHeaders
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface CliIpcClient {
|
|
66
|
+
readonly socketPath: string
|
|
67
|
+
ping(): Promise<CliPingResponse>
|
|
68
|
+
getStatus(): Promise<unknown>
|
|
69
|
+
getDebugReport(): Promise<unknown>
|
|
70
|
+
writeConfig(input: { set?: Record<string, unknown>; unset?: readonly string[] }): Promise<CliIpcRequestResult>
|
|
71
|
+
request(method: string, path: string, body?: unknown, timeoutMs?: number): Promise<CliIpcRequestResult>
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Discovery ping budget: finding out whether anything answers must stay
|
|
75
|
+
* fast — up to 8 candidates × 200ms is the worst case, and every absent
|
|
76
|
+
* path skips the probe entirely. */
|
|
77
|
+
const PING_TIMEOUT_MS = 200
|
|
78
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 15_000
|
|
79
|
+
const DEBUG_REPORT_TIMEOUT_MS = 30_000
|
|
80
|
+
|
|
81
|
+
function errnoCode(error: unknown): string | undefined {
|
|
82
|
+
if (!(error instanceof Error)) return undefined
|
|
83
|
+
const code = (error as { code?: unknown }).code
|
|
84
|
+
return typeof code === 'string' ? code : undefined
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function isDenied(error: unknown): boolean {
|
|
88
|
+
const code = errnoCode(error)
|
|
89
|
+
return code === 'EACCES' || code === 'EPERM'
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface IpcAnswer {
|
|
93
|
+
status: number
|
|
94
|
+
headers: IncomingHttpHeaders
|
|
95
|
+
text: string
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** One http round trip over the socket. Rejects on transport errors; HTTP
|
|
99
|
+
* error statuses are answers, not failures. */
|
|
100
|
+
function ipcRequest(socketPath: string, method: string, route: string, body?: unknown, timeoutMs?: number): Promise<IpcAnswer> {
|
|
101
|
+
return new Promise((resolve, reject) => {
|
|
102
|
+
const payload = body === undefined ? undefined : JSON.stringify(body)
|
|
103
|
+
const headers: Record<string, string> = {}
|
|
104
|
+
if (payload !== undefined) {
|
|
105
|
+
headers['content-type'] = 'application/json'
|
|
106
|
+
headers['content-length'] = String(Buffer.byteLength(payload))
|
|
107
|
+
}
|
|
108
|
+
const req = httpRequest({ socketPath, method, path: route, headers, agent: false, timeout: timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS }, (res) => {
|
|
109
|
+
const chunks: Buffer[] = []
|
|
110
|
+
res.on('data', (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk as Uint8Array)))
|
|
111
|
+
res.on('end', () => resolve({ status: res.statusCode ?? 0, headers: res.headers, text: Buffer.concat(chunks).toString('utf8') }))
|
|
112
|
+
res.on('error', reject)
|
|
113
|
+
})
|
|
114
|
+
req.on('timeout', () => {
|
|
115
|
+
req.destroy()
|
|
116
|
+
reject(new Error(`IPC request timed out after ${timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS}ms: ${method} ${route}`))
|
|
117
|
+
})
|
|
118
|
+
req.on('error', reject)
|
|
119
|
+
if (payload !== undefined) req.write(payload)
|
|
120
|
+
req.end()
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
type ProbeVerdict =
|
|
125
|
+
| { kind: 'live'; ping: CliPingResponse }
|
|
126
|
+
| { kind: 'mismatch'; reportedVersion: number | undefined }
|
|
127
|
+
| { kind: 'denied' }
|
|
128
|
+
| { kind: 'absent' }
|
|
129
|
+
| { kind: 'dead' }
|
|
130
|
+
|
|
131
|
+
async function probeCandidate(socketPath: string, probeEvenIfAbsent: boolean): Promise<ProbeVerdict> {
|
|
132
|
+
let statDenied = false
|
|
133
|
+
try {
|
|
134
|
+
statSync(socketPath)
|
|
135
|
+
} catch (error) {
|
|
136
|
+
if (isDenied(error)) statDenied = true
|
|
137
|
+
else if (!probeEvenIfAbsent) return { kind: 'absent' }
|
|
138
|
+
// A missing explicit path is still probed below — the connect error tells
|
|
139
|
+
// the same story, and ENOENT is not a permission problem.
|
|
140
|
+
}
|
|
141
|
+
if (statDenied) return { kind: 'denied' }
|
|
142
|
+
|
|
143
|
+
let answer: IpcAnswer
|
|
144
|
+
try {
|
|
145
|
+
answer = await ipcRequest(socketPath, 'GET', CLI_PING_ROUTE, undefined, PING_TIMEOUT_MS)
|
|
146
|
+
} catch (error) {
|
|
147
|
+
return isDenied(error) ? { kind: 'denied' } : { kind: 'dead' }
|
|
148
|
+
}
|
|
149
|
+
if (answer.status !== 200) return { kind: 'dead' }
|
|
150
|
+
let parsed: unknown
|
|
151
|
+
try {
|
|
152
|
+
parsed = JSON.parse(answer.text)
|
|
153
|
+
} catch {
|
|
154
|
+
return { kind: 'dead' }
|
|
155
|
+
}
|
|
156
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) return { kind: 'dead' }
|
|
157
|
+
const body = parsed as { ok?: unknown; cliProtocolVersion?: unknown }
|
|
158
|
+
if (body.ok !== true) return { kind: 'dead' }
|
|
159
|
+
const reported = typeof body.cliProtocolVersion === 'number' ? body.cliProtocolVersion : undefined
|
|
160
|
+
if (reported !== CLI_PROTOCOL_VERSION) return { kind: 'mismatch', reportedVersion: reported }
|
|
161
|
+
return { kind: 'live', ping: parsed as CliPingResponse }
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Walk the CLI socket candidates under `dshHome` (or only `explicitSocket`)
|
|
166
|
+
* and classify every answering socket. Absent candidates are skipped without
|
|
167
|
+
* a probe; a dead file that merely squats a candidate name simply answers
|
|
168
|
+
* nothing and lands nowhere.
|
|
169
|
+
*/
|
|
170
|
+
export async function discoverCliSockets(options: DiscoverCliSocketsOptions): Promise<DiscoveredSockets> {
|
|
171
|
+
const paths = options.explicitSocket !== undefined
|
|
172
|
+
? [options.explicitSocket]
|
|
173
|
+
: candidateSocketPaths(options.dshHome, options.platform ?? process.platform)
|
|
174
|
+
const found: DiscoveredSockets = { live: [], versionMismatch: [], permissionDenied: [] }
|
|
175
|
+
for (const socketPath of paths) {
|
|
176
|
+
const verdict = await probeCandidate(socketPath, options.explicitSocket !== undefined)
|
|
177
|
+
if (verdict.kind === 'live') found.live.push({ ...verdict.ping, socketPath })
|
|
178
|
+
else if (verdict.kind === 'mismatch') found.versionMismatch.push({ socketPath, cliProtocolVersion: verdict.reportedVersion })
|
|
179
|
+
else if (verdict.kind === 'denied') found.permissionDenied.push(socketPath)
|
|
180
|
+
}
|
|
181
|
+
return found
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function parseBody(text: string): unknown {
|
|
185
|
+
try {
|
|
186
|
+
return JSON.parse(text)
|
|
187
|
+
} catch {
|
|
188
|
+
return text
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* A client bound to one socket path. `request` is the generic escape hatch
|
|
194
|
+
* for routes added in later CLI phases; the named methods cover the routes
|
|
195
|
+
* this phase already drives.
|
|
196
|
+
*/
|
|
197
|
+
export function createCliIpcClient(socketPath: string): CliIpcClient {
|
|
198
|
+
const request = async (method: string, path: string, body?: unknown, timeoutMs?: number): Promise<CliIpcRequestResult> => {
|
|
199
|
+
const answer = await ipcRequest(socketPath, method, path, body, timeoutMs)
|
|
200
|
+
return { status: answer.status, body: parseBody(answer.text), headers: answer.headers }
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
socketPath,
|
|
204
|
+
ping: async () => {
|
|
205
|
+
const result = await request('GET', CLI_PING_ROUTE, undefined, PING_TIMEOUT_MS)
|
|
206
|
+
return result.body as CliPingResponse
|
|
207
|
+
},
|
|
208
|
+
getStatus: async () => (await request('GET', CLIENT_STATUS_ROUTE)).body,
|
|
209
|
+
getDebugReport: async () => (await request('GET', CLIENT_DEBUG_REPORT_ROUTE, undefined, DEBUG_REPORT_TIMEOUT_MS)).body,
|
|
210
|
+
writeConfig: async (input) => await request('POST', CLI_CONFIG_ROUTE, {
|
|
211
|
+
...(input.set === undefined ? {} : { set: input.set }),
|
|
212
|
+
...(input.unset === undefined ? {} : { unset: input.unset }),
|
|
213
|
+
}),
|
|
214
|
+
request,
|
|
215
|
+
}
|
|
216
|
+
}
|