sleev 0.0.12 → 0.0.13

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.
Files changed (143) hide show
  1. package/README.md +19 -39
  2. package/dist/auth/browser.d.ts +12 -0
  3. package/dist/auth/browser.js +33 -24
  4. package/dist/auth/browser.js.map +1 -0
  5. package/dist/auth/firebase-tokens.d.ts +39 -0
  6. package/dist/auth/firebase-tokens.js +173 -0
  7. package/dist/auth/firebase-tokens.js.map +1 -0
  8. package/dist/auth/firebase.d.ts +17 -0
  9. package/dist/auth/firebase.js +55 -0
  10. package/dist/auth/firebase.js.map +1 -0
  11. package/dist/auth/login.d.ts +22 -0
  12. package/dist/auth/login.js +31 -28
  13. package/dist/auth/login.js.map +1 -0
  14. package/dist/auth/loopback.d.ts +19 -0
  15. package/dist/auth/loopback.js +114 -44
  16. package/dist/auth/loopback.js.map +1 -0
  17. package/dist/auth/schema.d.ts +26 -0
  18. package/dist/auth/schema.js +59 -17
  19. package/dist/auth/schema.js.map +1 -0
  20. package/dist/auth/token.d.ts +19 -0
  21. package/dist/auth/token.js +53 -0
  22. package/dist/auth/token.js.map +1 -0
  23. package/dist/auth/verify.d.ts +6 -0
  24. package/dist/auth/verify.js +22 -0
  25. package/dist/auth/verify.js.map +1 -0
  26. package/dist/cli.d.ts +8 -0
  27. package/dist/cli.js +245 -59
  28. package/dist/cli.js.map +1 -0
  29. package/dist/config/paths.d.ts +12 -0
  30. package/dist/config/paths.js +34 -0
  31. package/dist/config/paths.js.map +1 -0
  32. package/dist/config/store.d.ts +63 -0
  33. package/dist/config/store.js +270 -0
  34. package/dist/config/store.js.map +1 -0
  35. package/dist/control/local-installations.d.ts +13 -0
  36. package/dist/control/local-installations.js +37 -0
  37. package/dist/control/local-installations.js.map +1 -0
  38. package/dist/daemon/config.d.ts +10 -0
  39. package/dist/daemon/config.js +52 -0
  40. package/dist/daemon/config.js.map +1 -0
  41. package/dist/daemon/controller.d.ts +29 -0
  42. package/dist/daemon/controller.js +34 -0
  43. package/dist/daemon/controller.js.map +1 -0
  44. package/dist/daemon/health.d.ts +8 -0
  45. package/dist/daemon/health.js +47 -0
  46. package/dist/daemon/health.js.map +1 -0
  47. package/dist/daemon/start.d.ts +5 -0
  48. package/dist/daemon/start.js +24 -0
  49. package/dist/daemon/start.js.map +1 -0
  50. package/dist/daemon/systemd.d.ts +35 -0
  51. package/dist/daemon/systemd.js +181 -0
  52. package/dist/daemon/systemd.js.map +1 -0
  53. package/dist/gateway/acquire.d.ts +14 -0
  54. package/dist/gateway/acquire.js +112 -0
  55. package/dist/gateway/acquire.js.map +1 -0
  56. package/dist/gateway/manifest.d.ts +26 -0
  57. package/dist/gateway/manifest.js +98 -0
  58. package/dist/gateway/manifest.js.map +1 -0
  59. package/dist/gateway/platform.d.ts +5 -0
  60. package/dist/gateway/platform.js +27 -0
  61. package/dist/gateway/platform.js.map +1 -0
  62. package/dist/harness/claude.d.ts +8 -0
  63. package/dist/{apps → harness}/claude.js +28 -28
  64. package/dist/harness/claude.js.map +1 -0
  65. package/dist/harness/codex.d.ts +10 -0
  66. package/dist/{apps → harness}/codex.js +50 -45
  67. package/dist/harness/codex.js.map +1 -0
  68. package/dist/harness/index.d.ts +13 -0
  69. package/dist/harness/index.js +63 -0
  70. package/dist/harness/index.js.map +1 -0
  71. package/dist/harness/opencode-config.d.ts +8 -0
  72. package/dist/{apps → harness}/opencode-config.js +116 -83
  73. package/dist/harness/opencode-config.js.map +1 -0
  74. package/dist/harness/opencode.d.ts +14 -0
  75. package/dist/harness/opencode.js +50 -0
  76. package/dist/harness/opencode.js.map +1 -0
  77. package/dist/harness/providers.d.ts +8 -0
  78. package/dist/harness/providers.js +30 -0
  79. package/dist/harness/providers.js.map +1 -0
  80. package/dist/harness/types.d.ts +14 -0
  81. package/dist/harness/types.js +2 -0
  82. package/dist/harness/types.js.map +1 -0
  83. package/dist/product.d.ts +12 -0
  84. package/dist/product.js +53 -10
  85. package/dist/product.js.map +1 -0
  86. package/dist/tui/clipboard.d.ts +1 -0
  87. package/dist/tui/clipboard.js +48 -0
  88. package/dist/tui/clipboard.js.map +1 -0
  89. package/dist/tui/openTuiApp.d.ts +1 -0
  90. package/dist/tui/openTuiApp.js +1436 -0
  91. package/dist/tui/openTuiApp.js.map +1 -0
  92. package/dist/tui/pages/flow-screen.d.ts +50 -0
  93. package/dist/tui/pages/flow-screen.js +435 -0
  94. package/dist/tui/pages/flow-screen.js.map +1 -0
  95. package/dist/tui/pages/footer.d.ts +7 -0
  96. package/dist/tui/pages/footer.js +14 -0
  97. package/dist/tui/pages/footer.js.map +1 -0
  98. package/dist/tui/pages/home-screen.d.ts +19 -0
  99. package/dist/tui/pages/home-screen.js +89 -0
  100. package/dist/tui/pages/home-screen.js.map +1 -0
  101. package/dist/tui/pages/index.d.ts +17 -0
  102. package/dist/tui/pages/index.js +11 -0
  103. package/dist/tui/pages/index.js.map +1 -0
  104. package/dist/tui/pages/local-setup-screen.d.ts +23 -0
  105. package/dist/tui/pages/local-setup-screen.js +134 -0
  106. package/dist/tui/pages/local-setup-screen.js.map +1 -0
  107. package/dist/tui/pages/location-card.d.ts +19 -0
  108. package/dist/tui/pages/location-card.js +116 -0
  109. package/dist/tui/pages/location-card.js.map +1 -0
  110. package/dist/tui/pages/menu-screen.d.ts +24 -0
  111. package/dist/tui/pages/menu-screen.js +117 -0
  112. package/dist/tui/pages/menu-screen.js.map +1 -0
  113. package/dist/tui/pages/session-grid.d.ts +23 -0
  114. package/dist/tui/pages/session-grid.js +552 -0
  115. package/dist/tui/pages/session-grid.js.map +1 -0
  116. package/dist/tui/pages/setup-screen.d.ts +15 -0
  117. package/dist/tui/pages/setup-screen.js +80 -0
  118. package/dist/tui/pages/setup-screen.js.map +1 -0
  119. package/dist/tui/pages/terminal-log.d.ts +14 -0
  120. package/dist/tui/pages/terminal-log.js +104 -0
  121. package/dist/tui/pages/terminal-log.js.map +1 -0
  122. package/dist/tui/pages/welcome-screen.d.ts +16 -0
  123. package/dist/tui/pages/welcome-screen.js +80 -0
  124. package/dist/tui/pages/welcome-screen.js.map +1 -0
  125. package/dist/tui/saved-tokens.d.ts +10 -0
  126. package/dist/tui/saved-tokens.js +104 -0
  127. package/dist/tui/saved-tokens.js.map +1 -0
  128. package/dist/tui/theme.d.ts +25 -0
  129. package/dist/tui/theme.js +27 -0
  130. package/dist/tui/theme.js.map +1 -0
  131. package/package.json +55 -38
  132. package/dist/apps/opencode.js +0 -158
  133. package/dist/apps.js +0 -148
  134. package/dist/auth/command.js +0 -109
  135. package/dist/auth/index.js +0 -2
  136. package/dist/auth/validation.js +0 -27
  137. package/dist/credentials/index.js +0 -3
  138. package/dist/credentials/schema.js +0 -63
  139. package/dist/credentials/store.js +0 -52
  140. package/dist/harnesses.js +0 -66
  141. package/dist/index.js +0 -3
  142. package/dist/providers.js +0 -30
  143. package/dist/terminal.js +0 -55
@@ -0,0 +1,181 @@
1
+ // systemd --user backend (Linux): the daemon runs as a user service that
2
+ // starts on login and (with linger) on boot, supervised without sleeved.
3
+ // This is the product path on Linux.
4
+ import { spawn } from "node:child_process";
5
+ import fs from "node:fs/promises";
6
+ import os from "node:os";
7
+ import path from "node:path";
8
+ import { writeGatewayConfig } from "./config.js";
9
+ import { probeHealth } from "./health.js";
10
+ import { resolveGatewayBinary } from "./start.js";
11
+ const UNIT_NAME = "sleeve-gateway.service";
12
+ const defaultRun = (cmd, args) => new Promise((resolve) => {
13
+ const child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
14
+ let stdout = "";
15
+ let stderr = "";
16
+ child.stdout?.on("data", (d) => (stdout += d.toString()));
17
+ child.stderr?.on("data", (d) => (stderr += d.toString()));
18
+ child.once("error", () => resolve({ code: 127, stdout, stderr: "spawn failed" }));
19
+ child.once("exit", (code) => resolve({ code: code ?? 1, stdout, stderr }));
20
+ });
21
+ export async function systemctlAvailable(run = defaultRun) {
22
+ if (os.platform() !== "linux")
23
+ return false;
24
+ const { code } = await run("systemctl", ["--user", "--version"]);
25
+ return code === 0;
26
+ }
27
+ function userUnitPath(env) {
28
+ const base = env.XDG_CONFIG_HOME?.trim() || path.join(os.homedir(), ".config");
29
+ return path.join(base, "systemd", "user", UNIT_NAME);
30
+ }
31
+ // Pure: the unit file content. Paths are quoted so spaces survive systemd's
32
+ // ExecStart tokenisation.
33
+ export function renderUnit(binPath, configPath, host, port) {
34
+ const exec = [
35
+ q(binPath),
36
+ "--local-installation",
37
+ "--config",
38
+ q(configPath),
39
+ "--host",
40
+ host,
41
+ "--port",
42
+ String(port),
43
+ ].join(" ");
44
+ return [
45
+ "[Unit]",
46
+ "Description=Sleeve local gateway",
47
+ "After=network-online.target",
48
+ "Wants=network-online.target",
49
+ "",
50
+ "[Service]",
51
+ "Type=simple",
52
+ `ExecStart=${exec}`,
53
+ "Restart=on-failure",
54
+ "RestartSec=2",
55
+ "",
56
+ "[Install]",
57
+ "WantedBy=default.target",
58
+ "",
59
+ ].join("\n");
60
+ }
61
+ function q(value) {
62
+ return `"${value.replace(/"/g, '\\"')}"`;
63
+ }
64
+ const defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
65
+ export class SystemdUserController {
66
+ backend = "systemd";
67
+ config;
68
+ env;
69
+ run;
70
+ fetchImpl;
71
+ sleep;
72
+ readinessTimeoutMs;
73
+ constructor(config, env = process.env, deps = {}) {
74
+ this.config = config;
75
+ this.env = env;
76
+ this.run = deps.run ?? defaultRun;
77
+ this.fetchImpl = deps.fetchImpl ?? fetch;
78
+ this.sleep = deps.sleep ?? defaultSleep;
79
+ this.readinessTimeoutMs = deps.readinessTimeoutMs ?? 10_000;
80
+ }
81
+ get address() {
82
+ return `http://${this.config.proxy.host}:${this.config.proxy.port}`;
83
+ }
84
+ async sc(args) {
85
+ return this.run("systemctl", ["--user", ...args]);
86
+ }
87
+ // Writes the unit + the gateway config, then reloads systemd. The unit
88
+ // is regenerated every time so a changed binary path/port is picked up.
89
+ async install() {
90
+ const binPath = resolveGatewayBinary(this.config, this.env);
91
+ const configPath = await writeGatewayConfig(this.config);
92
+ const unitPath = userUnitPath(this.env);
93
+ await fs.mkdir(path.dirname(unitPath), { recursive: true });
94
+ await fs.writeFile(unitPath, renderUnit(binPath, configPath, this.config.proxy.host, this.config.proxy.port), { mode: 0o644 });
95
+ await this.sc(["daemon-reload"]);
96
+ }
97
+ async waitHealthy() {
98
+ const deadline = Date.now() + this.readinessTimeoutMs;
99
+ while (Date.now() < deadline) {
100
+ await this.sleep(250);
101
+ const health = await probeHealth(this.config.proxy.host, this.config.proxy.port, 1000, this.fetchImpl);
102
+ if (health)
103
+ return;
104
+ }
105
+ const { stdout } = await this.sc(["is-active", UNIT_NAME]).catch(() => ({ stdout: "", code: 1, stderr: "" }));
106
+ throw new Error(`Gateway service did not become healthy within ` +
107
+ `${this.readinessTimeoutMs}ms (unit ${stdout.trim() || "inactive"}).`);
108
+ }
109
+ async start() {
110
+ const existing = await this.status();
111
+ if (existing.healthy)
112
+ return existing;
113
+ await this.install();
114
+ // enable --now: start immediately and on every login. Linger lets
115
+ // it also start at boot before any login; best-effort because it
116
+ // can require elevated privileges.
117
+ const enabled = await this.sc(["enable", "--now", UNIT_NAME]);
118
+ if (enabled.code !== 0) {
119
+ throw new Error(`systemctl --user enable --now failed: ${enabled.stderr.trim()}`);
120
+ }
121
+ await this.run("loginctl", [
122
+ "enable-linger",
123
+ os.userInfo().username,
124
+ ]).catch(() => undefined);
125
+ await this.waitHealthy();
126
+ return this.status();
127
+ }
128
+ async stop() {
129
+ await this.sc(["stop", UNIT_NAME]);
130
+ return this.status();
131
+ }
132
+ async restart() {
133
+ // Reinstall first so a changed config/binary/port is applied.
134
+ await this.install();
135
+ const r = await this.sc(["restart", UNIT_NAME]);
136
+ if (r.code !== 0) {
137
+ throw new Error(`systemctl --user restart failed: ${r.stderr.trim()}`);
138
+ }
139
+ await this.waitHealthy();
140
+ return this.status();
141
+ }
142
+ async status() {
143
+ const active = await this.sc(["is-active", UNIT_NAME]);
144
+ const running = active.stdout.trim() === "active";
145
+ const health = await probeHealth(this.config.proxy.host, this.config.proxy.port, 1500, this.fetchImpl);
146
+ let pid = null;
147
+ const show = await this.sc([
148
+ "show",
149
+ "-p",
150
+ "MainPID",
151
+ "--value",
152
+ UNIT_NAME,
153
+ ]);
154
+ const parsed = Number.parseInt(show.stdout.trim(), 10);
155
+ if (Number.isInteger(parsed) && parsed > 0)
156
+ pid = parsed;
157
+ return {
158
+ backend: this.backend,
159
+ running: running || health !== null,
160
+ healthy: health !== null,
161
+ configured: health?.configured ?? null,
162
+ version: health?.version ?? null,
163
+ pid,
164
+ address: this.address,
165
+ detail: health !== null
166
+ ? null
167
+ : `unit ${active.stdout.trim() || "inactive"}`,
168
+ };
169
+ }
170
+ async teardown() {
171
+ await this.sc(["disable", "--now", UNIT_NAME]);
172
+ try {
173
+ await fs.unlink(userUnitPath(this.env));
174
+ }
175
+ catch {
176
+ /* already gone */
177
+ }
178
+ await this.sc(["daemon-reload"]);
179
+ }
180
+ }
181
+ //# sourceMappingURL=systemd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"systemd.js","sourceRoot":"","sources":["../../src/daemon/systemd.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { type GatewayPlatform } from "./platform.js";
2
+ export type InstalledGateway = {
3
+ binPath: string;
4
+ version: string;
5
+ platform: GatewayPlatform;
6
+ };
7
+ export type AcquireDeps = {
8
+ fetchImpl?: typeof fetch;
9
+ extract?: (archive: string, destDir: string, kind: "tar.gz" | "zip") => Promise<void>;
10
+ currentVersion?: string | null;
11
+ platform?: GatewayPlatform;
12
+ env?: NodeJS.ProcessEnv;
13
+ };
14
+ export declare function acquireGateway(deps?: AcquireDeps): Promise<InstalledGateway>;
@@ -0,0 +1,112 @@
1
+ // Downloads, verifies, and installs the packaged gateway binary the channel
2
+ // manifest points at. The CLI never builds from source; this is how the
3
+ // `SLEEVE_GATEWAY_BIN`-shaped seam gets populated for real users.
4
+ import { createHash } from "node:crypto";
5
+ import { spawn } from "node:child_process";
6
+ import fs from "node:fs/promises";
7
+ import os from "node:os";
8
+ import path from "node:path";
9
+ import { resolveSleevedPaths } from "../config/paths.js";
10
+ import { archivedBinaryCandidates, archiveExtension, currentPlatformKey, installedBinaryName, } from "./platform.js";
11
+ import { resolveLatestRelease, } from "./manifest.js";
12
+ function gatewayInstallDir(version) {
13
+ return path.join(resolveSleevedPaths().dataDir, "gateway", version);
14
+ }
15
+ async function pathExists(p) {
16
+ try {
17
+ await fs.access(p);
18
+ return true;
19
+ }
20
+ catch {
21
+ return false;
22
+ }
23
+ }
24
+ async function defaultExtract(archive, destDir, _kind) {
25
+ // `tar` extracts both gzip tarballs and (via libarchive/bsdtar, present
26
+ // on macOS and Windows 10+) zips. Linux ships GNU tar but only ever
27
+ // receives .tar.gz, so this is safe across the three built targets.
28
+ //
29
+ // Defense-in-depth: the archive is integrity-pinned upstream, but it is
30
+ // still attacker-influenced if the manifest is compromised. GNU tar (and
31
+ // bsdtar) already strip a leading `/` and refuse `..` members by default,
32
+ // so extraction cannot escape destDir without an explicit -P/--absolute-
33
+ // names; we never pass one. (An earlier `--no-absolute-filenames` flag
34
+ // was not a real GNU tar option — tar rejected it with exit 64 before
35
+ // extracting anything.)
36
+ await new Promise((resolve, reject) => {
37
+ const child = spawn("tar", ["-xf", archive, "-C", destDir], {
38
+ stdio: "ignore",
39
+ });
40
+ child.once("error", reject);
41
+ child.once("exit", (code) => code === 0
42
+ ? resolve()
43
+ : reject(new Error(`tar exited with code ${code ?? "null"}`)));
44
+ });
45
+ }
46
+ function sha256(buffer) {
47
+ return createHash("sha256").update(buffer).digest("hex");
48
+ }
49
+ // Resolves latest, and unless it's already installed, downloads + verifies +
50
+ // installs it. Returns the path the daemon should be launched from.
51
+ export async function acquireGateway(deps = {}) {
52
+ const fetchImpl = deps.fetchImpl ?? fetch;
53
+ const extract = deps.extract ?? defaultExtract;
54
+ const platform = deps.platform ?? currentPlatformKey();
55
+ const release = await resolveLatestRelease({
56
+ fetchImpl,
57
+ platform,
58
+ env: deps.env,
59
+ });
60
+ const binPath = path.join(gatewayInstallDir(release.version), installedBinaryName(platform));
61
+ // Skip when the resolved version is already installed and present.
62
+ if (deps.currentVersion === release.version &&
63
+ (await pathExists(binPath))) {
64
+ return { binPath, version: release.version, platform };
65
+ }
66
+ await installArtifact(release.artifact, {
67
+ fetchImpl,
68
+ extract,
69
+ archiveKind: archiveExtension(platform),
70
+ archivedNames: archivedBinaryCandidates(platform),
71
+ binPath,
72
+ });
73
+ return { binPath, version: release.version, platform };
74
+ }
75
+ async function installArtifact(artifact, opts) {
76
+ const response = await opts.fetchImpl(artifact.url);
77
+ if (!response.ok) {
78
+ throw new Error(`Failed to download gateway binary (${artifact.url}): HTTP ${response.status}.`);
79
+ }
80
+ const buffer = Buffer.from(await response.arrayBuffer());
81
+ if (buffer.byteLength !== artifact.size) {
82
+ throw new Error(`Downloaded gateway archive size ${buffer.byteLength} != expected ${artifact.size}.`);
83
+ }
84
+ const digest = sha256(buffer);
85
+ if (digest !== artifact.sha256) {
86
+ throw new Error(`Downloaded gateway archive sha256 mismatch (got ${digest}).`);
87
+ }
88
+ const work = await fs.mkdtemp(path.join(os.tmpdir(), "sleeve-gw-"));
89
+ try {
90
+ const archivePath = path.join(work, `archive.${opts.archiveKind === "zip" ? "zip" : "tar.gz"}`);
91
+ await fs.writeFile(archivePath, buffer);
92
+ await opts.extract(archivePath, work, opts.archiveKind);
93
+ let extracted = null;
94
+ for (const name of opts.archivedNames) {
95
+ const candidate = path.join(work, name);
96
+ if (await pathExists(candidate)) {
97
+ extracted = candidate;
98
+ break;
99
+ }
100
+ }
101
+ if (extracted === null) {
102
+ throw new Error(`Gateway archive did not contain any of: ${opts.archivedNames.join(", ")}.`);
103
+ }
104
+ await fs.mkdir(path.dirname(opts.binPath), { recursive: true });
105
+ await fs.copyFile(extracted, opts.binPath);
106
+ await fs.chmod(opts.binPath, 0o755);
107
+ }
108
+ finally {
109
+ await fs.rm(work, { recursive: true, force: true });
110
+ }
111
+ }
112
+ //# sourceMappingURL=acquire.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acquire.js","sourceRoot":"","sources":["../../src/gateway/acquire.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import { type GatewayPlatform } from "./platform.js";
2
+ export type ArtifactRef = {
3
+ url: string;
4
+ sha256: string;
5
+ size: number;
6
+ };
7
+ export type ReleaseManifest = {
8
+ channel: string;
9
+ version: string;
10
+ commit: string;
11
+ publishedAt: string;
12
+ artifacts: Partial<Record<GatewayPlatform, ArtifactRef>>;
13
+ };
14
+ export declare function manifestUrl(base: string, channel: string): string;
15
+ export declare function parseManifest(value: unknown): ReleaseManifest;
16
+ export type ResolvedRelease = {
17
+ version: string;
18
+ platform: GatewayPlatform;
19
+ artifact: ArtifactRef;
20
+ };
21
+ export declare function resolveLatestRelease(deps?: {
22
+ fetchImpl?: typeof fetch;
23
+ platform?: GatewayPlatform;
24
+ env?: NodeJS.ProcessEnv;
25
+ }): Promise<ResolvedRelease>;
26
+ export declare function assertTrustedArtifactUrl(artifactUrl: string, baseUrl: string): void;
@@ -0,0 +1,98 @@
1
+ // Reads the channel manifest — the always-latest pointer published by the
2
+ // gateway release workflow at <base>/gateway/<channel>.json.
3
+ import { gatewayUpdates } from "../product.js";
4
+ import { currentPlatformKey } from "./platform.js";
5
+ export function manifestUrl(base, channel) {
6
+ return `${base.replace(/\/+$/, "")}/gateway/${channel}.json`;
7
+ }
8
+ // Strict parse: the manifest is remote input, so validate every field we
9
+ // rely on rather than trusting its shape.
10
+ export function parseManifest(value) {
11
+ if (!isRecord(value)) {
12
+ throw new Error("Gateway manifest is not a JSON object.");
13
+ }
14
+ const channel = str(value.channel, "channel");
15
+ const version = str(value.version, "version");
16
+ const commit = str(value.commit, "commit");
17
+ const publishedAt = str(value.publishedAt, "publishedAt");
18
+ if (!isRecord(value.artifacts)) {
19
+ throw new Error("Gateway manifest is missing 'artifacts'.");
20
+ }
21
+ const artifacts = {};
22
+ for (const platform of ["linux-x64"]) {
23
+ const entry = value.artifacts[platform];
24
+ if (entry === undefined)
25
+ continue;
26
+ if (!isRecord(entry)) {
27
+ throw new Error(`Gateway manifest artifact ${platform} is invalid.`);
28
+ }
29
+ const size = entry.size;
30
+ if (typeof size !== "number" || !Number.isInteger(size) || size <= 0) {
31
+ throw new Error(`Gateway manifest artifact ${platform} has an invalid size.`);
32
+ }
33
+ artifacts[platform] = {
34
+ url: str(entry.url, `artifacts.${platform}.url`),
35
+ sha256: str(entry.sha256, `artifacts.${platform}.sha256`),
36
+ size,
37
+ };
38
+ }
39
+ return { channel, version, commit, publishedAt, artifacts };
40
+ }
41
+ // Fetches the channel manifest and resolves the artifact for this host.
42
+ export async function resolveLatestRelease(deps = {}) {
43
+ const fetchImpl = deps.fetchImpl ?? fetch;
44
+ const platform = deps.platform ?? currentPlatformKey();
45
+ const { baseUrl, channel } = gatewayUpdates(deps.env);
46
+ const url = manifestUrl(baseUrl, channel);
47
+ const response = await fetchImpl(url);
48
+ if (!response.ok) {
49
+ throw new Error(`Could not fetch gateway manifest (${url}): HTTP ${response.status}.`);
50
+ }
51
+ const manifest = parseManifest(await response.json().catch(() => null));
52
+ const artifact = manifest.artifacts[platform];
53
+ if (!artifact) {
54
+ throw new Error(`Gateway release ${manifest.version} has no ${platform} build.`);
55
+ }
56
+ // The manifest is remote input and the artifact it points at is later
57
+ // executed. size/sha256 come from the same manifest, so that check is
58
+ // self-referential — pin the download to the trusted release base so a
59
+ // tampered manifest cannot redirect it off-origin.
60
+ assertTrustedArtifactUrl(artifact.url, baseUrl);
61
+ return { version: manifest.version, platform, artifact };
62
+ }
63
+ // Throws unless `artifactUrl` lives under `baseUrl`: same origin, same
64
+ // scheme, and within the base path. URL parsing normalizes `..`/`.`
65
+ // segments, so a path-traversal pathname fails the prefix check.
66
+ export function assertTrustedArtifactUrl(artifactUrl, baseUrl) {
67
+ let artifact;
68
+ try {
69
+ artifact = new URL(artifactUrl);
70
+ }
71
+ catch {
72
+ throw new Error(`Gateway manifest artifact URL is not a valid URL: ${artifactUrl}.`);
73
+ }
74
+ // baseUrl is already validated by gatewayUpdates(); http is only its
75
+ // scheme for a local dev override.
76
+ const base = new URL(baseUrl);
77
+ if (base.protocol === "https:" && artifact.protocol !== "https:") {
78
+ throw new Error(`Gateway manifest artifact URL must use https: ${artifactUrl}.`);
79
+ }
80
+ const basePath = base.pathname.replace(/\/+$/, "");
81
+ const withinBase = artifact.origin === base.origin &&
82
+ (artifact.pathname === basePath ||
83
+ artifact.pathname.startsWith(`${basePath}/`));
84
+ if (!withinBase) {
85
+ throw new Error(`Gateway manifest artifact URL ${artifactUrl} is not under the ` +
86
+ `trusted release base ${baseUrl}.`);
87
+ }
88
+ }
89
+ function isRecord(value) {
90
+ return typeof value === "object" && value !== null && !Array.isArray(value);
91
+ }
92
+ function str(value, field) {
93
+ if (typeof value !== "string" || !value) {
94
+ throw new Error(`Gateway manifest field '${field}' is missing.`);
95
+ }
96
+ return value;
97
+ }
98
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/gateway/manifest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export type GatewayPlatform = "linux-x64";
2
+ export declare function currentPlatformKey(platform?: NodeJS.Platform, arch?: string): GatewayPlatform;
3
+ export declare function archiveExtension(_platform: GatewayPlatform): "tar.gz";
4
+ export declare function archivedBinaryCandidates(platform: GatewayPlatform): string[];
5
+ export declare function installedBinaryName(_platform: GatewayPlatform): string;
@@ -0,0 +1,27 @@
1
+ // Maps the host to a published gateway artifact. Linux-only for now: the
2
+ // release workflow builds just linux-x64. macOS/Windows are planned; until
3
+ // then non-Linux hosts fail clearly rather than silently mis-resolving.
4
+ import os from "node:os";
5
+ export function currentPlatformKey(platform = os.platform(), arch = os.arch()) {
6
+ if (platform === "linux" && arch === "x64")
7
+ return "linux-x64";
8
+ throw new Error(`No Sleeve gateway binary is available for ${platform}-${arch}. ` +
9
+ "Only linux-x64 is supported right now (macOS/Windows planned).");
10
+ }
11
+ // Archive container the workflow publishes (tar.gz on Linux).
12
+ export function archiveExtension(_platform) {
13
+ return "tar.gz";
14
+ }
15
+ // Candidate names of the executable *inside* the archive, in priority order.
16
+ // Releases publish `sleeve-gateway-linux-x64` going forward; older published
17
+ // releases still contain `sleeve-proxy-linux-x64` (the channel manifest only
18
+ // ever points at the latest, but we stay tolerant across the rename so
19
+ // acquisition keeps working during the transition).
20
+ export function archivedBinaryCandidates(platform) {
21
+ return [`sleeve-gateway-${platform}`, `sleeve-proxy-${platform}`];
22
+ }
23
+ // Installed executable filename (platform-stable; version lives in the path).
24
+ export function installedBinaryName(_platform) {
25
+ return "sleeve-gateway";
26
+ }
27
+ //# sourceMappingURL=platform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform.js","sourceRoot":"","sources":["../../src/gateway/platform.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,2EAA2E;AAC3E,wEAAwE;AACxE,OAAO,EAAE,MAAM,SAAS,CAAC;AAIzB,MAAM,UAAU,kBAAkB,CAC9B,WAA4B,EAAE,CAAC,QAAQ,EAAE,EACzC,OAAe,EAAE,CAAC,IAAI,EAAE;IAExB,IAAI,QAAQ,KAAK,OAAO,IAAI,IAAI,KAAK,KAAK;QAAE,OAAO,WAAW,CAAC;IAC/D,MAAM,IAAI,KAAK,CACX,6CAA6C,QAAQ,IAAI,IAAI,IAAI;QAC7D,gEAAgE,CACvE,CAAC;AACN,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,gBAAgB,CAAC,SAA0B;IACvD,OAAO,QAAQ,CAAC;AACpB,CAAC;AAED,6EAA6E;AAC7E,6EAA6E;AAC7E,6EAA6E;AAC7E,uEAAuE;AACvE,oDAAoD;AACpD,MAAM,UAAU,wBAAwB,CAAC,QAAyB;IAC9D,OAAO,CAAC,kBAAkB,QAAQ,EAAE,EAAE,gBAAgB,QAAQ,EAAE,CAAC,CAAC;AACtE,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,mBAAmB,CAAC,SAA0B;IAC1D,OAAO,gBAAgB,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { HarnessAuth } from "./types.js";
2
+ export type ClaudePatchResult = {
3
+ path: string;
4
+ };
5
+ export declare function setupClaude(auth: HarnessAuth, path?: string): Promise<ClaudePatchResult>;
6
+ export declare function patchClaude(auth: HarnessAuth, path?: string): Promise<ClaudePatchResult>;
7
+ export declare function unpatchClaude(path?: string): Promise<void>;
8
+ export declare function claudePath(env?: NodeJS.ProcessEnv): string;
@@ -1,17 +1,13 @@
1
1
  // Claude Code setup owns global settings patching for the Anthropic-compatible proxy.
2
- import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises';
3
- import { homedir } from 'node:os';
4
- import { dirname, join } from 'node:path';
5
- import { field, success } from '../terminal.js';
6
- const BASE = 'ANTHROPIC_BASE_URL';
7
- const CUSTOM = 'ANTHROPIC_CUSTOM_HEADERS';
8
- const HARNESS = 'claude code';
9
- const OLD_HARNESS = 'claude-code';
2
+ import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join } from "node:path";
5
+ const BASE = "ANTHROPIC_BASE_URL";
6
+ const CUSTOM = "ANTHROPIC_CUSTOM_HEADERS";
7
+ const HARNESS = "claude code";
8
+ const OLD_HARNESS = "claude-code";
10
9
  export async function setupClaude(auth, path = claudePath()) {
11
- const result = await patchClaude(auth, path);
12
- success(process.stdout, 'Configured Claude Code');
13
- field(process.stdout, 'config', result.path);
14
- return result;
10
+ return patchClaude(auth, path);
15
11
  }
16
12
  export async function patchClaude(auth, path = claudePath()) {
17
13
  const existing = await readJson(path);
@@ -24,14 +20,14 @@ export async function unpatchClaude(path = claudePath()) {
24
20
  if (!existing)
25
21
  return;
26
22
  const env = object(existing.env);
27
- if (typeof env.ANTHROPIC_CUSTOM_HEADERS !== 'string')
23
+ if (typeof env.ANTHROPIC_CUSTOM_HEADERS !== "string")
28
24
  return;
29
25
  if (!patched(env))
30
26
  return;
31
27
  await writeJson(path, removeBackup({ ...existing, env: restoreEnv(env, backup(existing)) }));
32
28
  }
33
29
  export function claudePath(env = process.env) {
34
- return join(env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude'), 'settings.json');
30
+ return join(env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude"), "settings.json");
35
31
  }
36
32
  function patchConfig(input, auth) {
37
33
  const env = object(input.env);
@@ -48,14 +44,14 @@ function patchConfig(input, auth) {
48
44
  function headers(auth) {
49
45
  return [
50
46
  `sleeve-token: ${auth.token}`,
51
- 'sleeve-provider: anthropic',
47
+ "sleeve-provider: anthropic",
52
48
  `sleeve-harness: ${HARNESS}`,
53
- ].join('\n');
49
+ ].join("\n");
54
50
  }
55
51
  function backup(input) {
56
52
  const sleev = object(input.sleev);
57
53
  const claude = object(sleev.claudeCode);
58
- if (!('previousEnv' in claude))
54
+ if (!("previousEnv" in claude))
59
55
  return null;
60
56
  return object(claude.previousEnv);
61
57
  }
@@ -102,50 +98,54 @@ function restoreEnv(env, previous) {
102
98
  }
103
99
  function patched(env) {
104
100
  const value = env[CUSTOM];
105
- if (typeof value !== 'string')
101
+ if (typeof value !== "string")
106
102
  return false;
107
- return value.includes(`sleeve-harness: ${HARNESS}`) || value.includes(`sleeve-harness: ${OLD_HARNESS}`);
103
+ return (value.includes(`sleeve-harness: ${HARNESS}`) ||
104
+ value.includes(`sleeve-harness: ${OLD_HARNESS}`));
108
105
  }
109
106
  async function writeJson(path, config) {
110
107
  await mkdir(dirname(path), { recursive: true, mode: 0o700 });
111
- await writeFile(path, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
108
+ await writeFile(path, `${JSON.stringify(config, null, 2)}\n`, {
109
+ mode: 0o600,
110
+ });
112
111
  await chmod(path, 0o600);
113
112
  }
114
113
  async function readJson(path) {
115
114
  try {
116
- const raw = await readFile(path, 'utf8');
115
+ const raw = await readFile(path, "utf8");
117
116
  const parsed = JSON.parse(raw);
118
117
  return object(parsed);
119
118
  }
120
119
  catch (err) {
121
- if (code(err) === 'ENOENT')
120
+ if (code(err) === "ENOENT")
122
121
  return {};
123
122
  throw err;
124
123
  }
125
124
  }
126
125
  async function readExistingJson(path) {
127
126
  try {
128
- const raw = await readFile(path, 'utf8');
127
+ const raw = await readFile(path, "utf8");
129
128
  const parsed = JSON.parse(raw);
130
129
  return object(parsed);
131
130
  }
132
131
  catch (err) {
133
- if (code(err) === 'ENOENT')
132
+ if (code(err) === "ENOENT")
134
133
  return null;
135
134
  throw err;
136
135
  }
137
136
  }
138
137
  function object(value) {
139
- if (typeof value !== 'object' || value === null || Array.isArray(value))
138
+ if (typeof value !== "object" || value === null || Array.isArray(value))
140
139
  return {};
141
140
  return value;
142
141
  }
143
142
  function trim(url) {
144
- return url.replace(/\/+$/, '');
143
+ return url.replace(/\/+$/, "");
145
144
  }
146
145
  function code(err) {
147
- if (typeof err !== 'object' || err === null || !('code' in err))
146
+ if (typeof err !== "object" || err === null || !("code" in err))
148
147
  return undefined;
149
148
  const value = err.code;
150
- return typeof value === 'string' ? value : undefined;
149
+ return typeof value === "string" ? value : undefined;
151
150
  }
151
+ //# sourceMappingURL=claude.js.map