docks-kit 0.14.1 → 0.14.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.
@@ -1,262 +0,0 @@
1
- import { spawnSync } from "node:child_process"
2
- import { createHash, randomBytes } from "node:crypto"
3
- import {
4
- chmodSync,
5
- existsSync,
6
- mkdirSync,
7
- readFileSync,
8
- renameSync,
9
- rmSync,
10
- statSync
11
- } from "node:fs"
12
- import { dirname, join } from "node:path"
13
-
14
- import { isObject, parseJson, type Json } from "./jq"
15
- import type { Ctx } from "./index"
16
- import { ExitError } from "./parseArgs"
17
- import { payloadText } from "../payload"
18
-
19
- const REPOSITORY = "DocksDocks/docks"
20
- const PLUGIN_ID = "session-relay@docks"
21
- const INSTALL_PATH = "~/.local/bin/session-relay"
22
- // Intel macOS was retired as of Session Relay 0.16.0: the parent release
23
- // publishes exactly three native binaries, so darwin/x64 hosts fail closed in
24
- // sessionRelayTarget before any manifest parse or download.
25
- const TARGETS = [
26
- "x86_64-unknown-linux-musl",
27
- "aarch64-unknown-linux-musl",
28
- "aarch64-apple-darwin"
29
- ] as const
30
-
31
- export type SessionRelayTarget = typeof TARGETS[number]
32
-
33
- export interface SessionRelayManifest {
34
- readonly kind: "managed-release"
35
- readonly policy: "exact"
36
- readonly verified: string
37
- readonly repository: typeof REPOSITORY
38
- readonly tag: string
39
- readonly plugin_id: typeof PLUGIN_ID
40
- readonly plugin_version: string
41
- readonly install_path: typeof INSTALL_PATH
42
- readonly assets: Readonly<Record<SessionRelayTarget, string>>
43
- }
44
-
45
- export interface SessionRelayInstallOps {
46
- readonly download: (url: string, destination: string) => boolean
47
- readonly chmod: (path: string, mode: number) => void
48
- readonly runVersion: (path: string) => { readonly ok: boolean; readonly stdout: string }
49
- readonly rename: (from: string, to: string) => void
50
- readonly uniqueSuffix: () => string
51
- }
52
-
53
- export interface SessionRelayInstallInput {
54
- readonly home: string
55
- readonly dryRun: boolean
56
- readonly platform: string
57
- readonly arch: string
58
- readonly manifestText: string
59
- readonly log: (line: string) => void
60
- readonly error?: (line: string) => void
61
- }
62
-
63
- function fail(input: SessionRelayInstallInput, message: string): never {
64
- input.error?.(message)
65
- const error = new ExitError(1)
66
- error.message = message
67
- throw error
68
- }
69
-
70
- function exactKeys(value: { [key: string]: Json }, expected: ReadonlyArray<string>, label: string): void {
71
- const actual = Object.keys(value).sort()
72
- const wanted = [...expected].sort()
73
- if (actual.length !== wanted.length || actual.some((key, index) => key !== wanted[index])) {
74
- throw new Error(`${label} violates the closed Session Relay manifest schema`)
75
- }
76
- }
77
-
78
- export function parseSessionRelayManifest(text: string): SessionRelayManifest {
79
- const value = parseJson(text)
80
- if (value === undefined || !isObject(value)) throw new Error("Session Relay manifest is not a JSON object")
81
- exactKeys(
82
- value,
83
- ["kind", "policy", "verified", "repository", "tag", "plugin_id", "plugin_version", "install_path", "assets"],
84
- "Session Relay manifest"
85
- )
86
- const version = value["verified"]
87
- if (typeof version !== "string" || !/^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/.test(version)) {
88
- throw new Error("Session Relay manifest verified must be a canonical stable SemVer core")
89
- }
90
- const expected = {
91
- kind: "managed-release",
92
- policy: "exact",
93
- verified: version,
94
- repository: REPOSITORY,
95
- tag: `session-relay--v${version}`,
96
- plugin_id: PLUGIN_ID,
97
- plugin_version: version,
98
- install_path: INSTALL_PATH
99
- } as const
100
- for (const [key, wanted] of Object.entries(expected)) {
101
- if (value[key] !== wanted) throw new Error(`Session Relay manifest ${key.replaceAll("_", " ")} must be ${wanted}`)
102
- }
103
- const assets = value["assets"]
104
- if (!isObject(assets)) throw new Error("Session Relay manifest assets must be an object")
105
- exactKeys(assets, TARGETS, "Session Relay manifest assets target set")
106
- const parsedAssets = {} as Record<SessionRelayTarget, string>
107
- for (const target of TARGETS) {
108
- const digest = assets[target]
109
- if (typeof digest !== "string" || !/^[0-9a-f]{64}$/.test(digest)) {
110
- throw new Error(`Session Relay manifest digest for ${target} must be 64 lowercase hex characters`)
111
- }
112
- parsedAssets[target] = digest
113
- }
114
- return { ...expected, assets: parsedAssets }
115
- }
116
-
117
- function manifestEntry(): string {
118
- const document = parseJson(payloadText("SoT/toolchain.json"))
119
- const tools = document !== undefined && isObject(document) ? document["tools"] : undefined
120
- const entry = tools !== undefined && isObject(tools) ? tools["session-relay"] : undefined
121
- if (entry === undefined) throw new Error("Embedded toolchain manifest has no session-relay entry")
122
- return JSON.stringify(entry)
123
- }
124
-
125
- export function sessionRelayTarget(platform: string, arch: string): SessionRelayTarget {
126
- if (platform === "linux" && arch === "x64") return "x86_64-unknown-linux-musl"
127
- if (platform === "linux" && arch === "arm64") return "aarch64-unknown-linux-musl"
128
- if (platform === "darwin" && arch === "arm64") return "aarch64-apple-darwin"
129
- throw new Error(`Unsupported host for Session Relay CLI: ${platform}/${arch}; supported: linux x64|arm64, darwin arm64`)
130
- }
131
-
132
- function trimOneLineEnding(text: string): string {
133
- if (text.endsWith("\r\n")) return text.slice(0, -2)
134
- if (text.endsWith("\n")) return text.slice(0, -1)
135
- return text
136
- }
137
-
138
- function exactVersion(ops: SessionRelayInstallOps, path: string, version: string): boolean {
139
- const result = ops.runVersion(path)
140
- return result.ok && trimOneLineEnding(result.stdout) === `session-relay ${version}`
141
- }
142
-
143
- function selectedChecksum(text: string, assetName: string): string {
144
- const selected = text.split("\n").filter((line) => line.endsWith(` ${assetName}`))
145
- if (selected.length !== 1 || !new RegExp(`^[0-9a-f]{64} ${assetName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`).test(selected[0]!)) {
146
- throw new Error(`SHA256SUMS must contain exactly one canonical row for ${assetName}`)
147
- }
148
- return selected[0]!.slice(0, 64)
149
- }
150
-
151
- const defaultOps: SessionRelayInstallOps = {
152
- download: (url, destination) => {
153
- const result = spawnSync("curl", ["-fL", "--retry", "2", "--connect-timeout", "10", "--output", destination, url], {
154
- stdio: "inherit"
155
- })
156
- return result.error === undefined && result.status === 0
157
- },
158
- chmod: chmodSync,
159
- runVersion: (path) => {
160
- const result = spawnSync(path, ["--version"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] })
161
- return { ok: result.error === undefined && result.status === 0, stdout: result.stdout ?? "" }
162
- },
163
- rename: renameSync,
164
- uniqueSuffix: () => `${process.pid}-${randomBytes(8).toString("hex")}`
165
- }
166
-
167
- export function installSessionRelayCli(
168
- input: SessionRelayInstallInput,
169
- ops: SessionRelayInstallOps = defaultOps
170
- ): void {
171
- let manifest: SessionRelayManifest
172
- let target: SessionRelayTarget
173
- try {
174
- manifest = parseSessionRelayManifest(input.manifestText)
175
- target = sessionRelayTarget(input.platform, input.arch)
176
- } catch (error) {
177
- fail(input, error instanceof Error ? error.message : String(error))
178
- }
179
-
180
- if (input.dryRun) {
181
- input.log(
182
- `[dry-run] ensure Session Relay CLI ${manifest.verified} from ${manifest.repository}@${manifest.tag} (${target}) -> ${manifest.install_path}`
183
- )
184
- return
185
- }
186
-
187
- const stable = join(input.home, ".local", "bin", "session-relay")
188
- if (existsSync(stable) && exactVersion(ops, stable, manifest.verified)) return
189
-
190
- const parent = dirname(stable)
191
- try {
192
- mkdirSync(parent, { recursive: true, mode: 0o755 })
193
- if (!statSync(parent).isDirectory()) {
194
- fail(input, `Session Relay install parent is not a directory: ${parent}`)
195
- }
196
- } catch (error) {
197
- if (error instanceof ExitError) throw error
198
- fail(input, `Cannot prepare Session Relay install directory ${parent}: ${error instanceof Error ? error.message : String(error)}`)
199
- }
200
-
201
- const suffix = ops.uniqueSuffix()
202
- const stage = join(parent, `.session-relay.stage-${suffix}`)
203
- const checksumFile = join(parent, `.session-relay.checksums-${suffix}`)
204
- const assetName = `session-relay-${target}`
205
- const baseUrl = `https://github.com/${manifest.repository}/releases/download/${manifest.tag}`
206
-
207
- try {
208
- if (!ops.download(`${baseUrl}/${assetName}`, stage)) fail(input, `Failed to download pinned Session Relay asset ${assetName}`)
209
- if (!ops.download(`${baseUrl}/SHA256SUMS`, checksumFile)) fail(input, "Failed to download pinned Session Relay SHA256SUMS")
210
-
211
- let checksumDigest: string
212
- try {
213
- checksumDigest = selectedChecksum(readFileSync(checksumFile, "utf8"), assetName)
214
- } catch (error) {
215
- fail(input, error instanceof Error ? error.message : String(error))
216
- }
217
- const sourceDigest = manifest.assets[target]
218
- if (checksumDigest !== sourceDigest) fail(input, `Session Relay source pin does not match SHA256SUMS for ${assetName}`)
219
- const downloadedDigest = createHash("sha256").update(readFileSync(stage)).digest("hex")
220
- if (downloadedDigest !== sourceDigest) fail(input, `Downloaded Session Relay checksum mismatch for ${assetName}`)
221
-
222
- try {
223
- ops.chmod(stage, 0o755)
224
- } catch (error) {
225
- fail(input, `Failed to chmod staged Session Relay CLI: ${error instanceof Error ? error.message : String(error)}`)
226
- }
227
- if (!exactVersion(ops, stage, manifest.verified)) {
228
- fail(input, `Staged Session Relay CLI did not report exact version session-relay ${manifest.verified}`)
229
- }
230
- try {
231
- ops.rename(stage, stable)
232
- } catch (error) {
233
- fail(input, `Failed to atomically replace Session Relay CLI: ${error instanceof Error ? error.message : String(error)}`)
234
- }
235
- input.log(`Session Relay CLI ready (${manifest.verified})`)
236
- } finally {
237
- try {
238
- rmSync(stage, { force: true })
239
- } catch {
240
- // A cleanup failure must not turn a successful atomic replacement into
241
- // an install failure or alter a pre-existing stable executable.
242
- }
243
- try {
244
- rmSync(checksumFile, { force: true })
245
- } catch {
246
- // Same failure-preservation rule as the staged executable above.
247
- }
248
- }
249
- }
250
-
251
- export function ensureSessionRelayCli(ctx: Ctx): number {
252
- installSessionRelayCli({
253
- home: ctx.home,
254
- dryRun: ctx.dryRun,
255
- platform: ctx.services.platform.raw(),
256
- arch: process.arch,
257
- manifestText: manifestEntry(),
258
- log: ctx.dryRun ? ctx.services.logger.echo : ctx.services.logger.change,
259
- error: ctx.services.logger.err
260
- })
261
- return 0
262
- }
@@ -1,94 +0,0 @@
1
- import { spawnSync } from "node:child_process"
2
-
3
- import { isObject, parseJson } from "./jq"
4
-
5
- const SESSION_RELAY_PLUGIN_ID = "session-relay@docks"
6
-
7
- export type SessionRelayReadinessReason =
8
- | "codex_cli_unavailable"
9
- | "plugin_list_failed"
10
- | "invalid_plugin_list"
11
- | "plugin_missing"
12
- | "plugin_ambiguous"
13
- | "plugin_not_installed"
14
- | "plugin_disabled"
15
-
16
- export interface SessionRelayReadiness {
17
- readonly schema: 1
18
- readonly state: "ready" | "unavailable"
19
- readonly reason: SessionRelayReadinessReason | null
20
- readonly version: string | null
21
- readonly installed: boolean
22
- readonly enabled: boolean
23
- readonly scope: "new_sessions"
24
- }
25
-
26
- export interface CodexPluginListProbe {
27
- readonly status: number | null
28
- readonly stdout: string
29
- readonly errorCode: string | null
30
- }
31
-
32
- function unavailable(
33
- reason: SessionRelayReadinessReason,
34
- fields: { version?: string | null; installed?: boolean; enabled?: boolean } = {}
35
- ): SessionRelayReadiness {
36
- return {
37
- schema: 1,
38
- state: "unavailable",
39
- reason,
40
- version: fields.version ?? null,
41
- installed: fields.installed ?? false,
42
- enabled: fields.enabled ?? false,
43
- scope: "new_sessions"
44
- }
45
- }
46
-
47
- export function classifySessionRelayReadiness(probe: CodexPluginListProbe): SessionRelayReadiness {
48
- if (probe.errorCode === "ENOENT") return unavailable("codex_cli_unavailable")
49
- if (probe.errorCode !== null || probe.status !== 0) return unavailable("plugin_list_failed")
50
-
51
- const value = parseJson(probe.stdout)
52
- if (value === undefined || !isObject(value) || !Array.isArray(value["installed"])) {
53
- return unavailable("invalid_plugin_list")
54
- }
55
-
56
- const matches = value["installed"].filter(
57
- (entry) => isObject(entry) && entry["pluginId"] === SESSION_RELAY_PLUGIN_ID
58
- )
59
- if (matches.length === 0) return unavailable("plugin_missing")
60
- if (matches.length !== 1) return unavailable("plugin_ambiguous")
61
-
62
- const row = matches[0]
63
- if (row === undefined || !isObject(row)) return unavailable("invalid_plugin_list")
64
- const version = row["version"]
65
- const installed = row["installed"]
66
- const enabled = row["enabled"]
67
- if (typeof version !== "string" || version.length === 0 || typeof installed !== "boolean" || typeof enabled !== "boolean") {
68
- return unavailable("invalid_plugin_list")
69
- }
70
- if (!installed) return unavailable("plugin_not_installed", { version, enabled })
71
- if (!enabled) return unavailable("plugin_disabled", { version, installed })
72
-
73
- return {
74
- schema: 1,
75
- state: "ready",
76
- reason: null,
77
- version,
78
- installed,
79
- enabled,
80
- scope: "new_sessions"
81
- }
82
- }
83
-
84
- export function sessionRelayReadiness(): SessionRelayReadiness {
85
- const result = spawnSync("codex", ["plugin", "list", "--json"], {
86
- encoding: "utf8",
87
- stdio: ["ignore", "pipe", "ignore"]
88
- })
89
- return classifySessionRelayReadiness({
90
- status: result.status,
91
- stdout: result.stdout ?? "",
92
- errorCode: (result.error as NodeJS.ErrnoException | undefined)?.code ?? null
93
- })
94
- }