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.
- package/README.md +19 -39
- package/dist/auth/browser.d.ts +12 -0
- package/dist/auth/browser.js +33 -24
- package/dist/auth/browser.js.map +1 -0
- package/dist/auth/firebase-tokens.d.ts +39 -0
- package/dist/auth/firebase-tokens.js +173 -0
- package/dist/auth/firebase-tokens.js.map +1 -0
- package/dist/auth/firebase.d.ts +17 -0
- package/dist/auth/firebase.js +55 -0
- package/dist/auth/firebase.js.map +1 -0
- package/dist/auth/login.d.ts +22 -0
- package/dist/auth/login.js +31 -28
- package/dist/auth/login.js.map +1 -0
- package/dist/auth/loopback.d.ts +19 -0
- package/dist/auth/loopback.js +114 -44
- package/dist/auth/loopback.js.map +1 -0
- package/dist/auth/schema.d.ts +26 -0
- package/dist/auth/schema.js +59 -17
- package/dist/auth/schema.js.map +1 -0
- package/dist/auth/token.d.ts +19 -0
- package/dist/auth/token.js +53 -0
- package/dist/auth/token.js.map +1 -0
- package/dist/auth/verify.d.ts +6 -0
- package/dist/auth/verify.js +22 -0
- package/dist/auth/verify.js.map +1 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +245 -59
- package/dist/cli.js.map +1 -0
- package/dist/config/paths.d.ts +12 -0
- package/dist/config/paths.js +34 -0
- package/dist/config/paths.js.map +1 -0
- package/dist/config/store.d.ts +63 -0
- package/dist/config/store.js +270 -0
- package/dist/config/store.js.map +1 -0
- package/dist/control/local-installations.d.ts +13 -0
- package/dist/control/local-installations.js +37 -0
- package/dist/control/local-installations.js.map +1 -0
- package/dist/daemon/config.d.ts +10 -0
- package/dist/daemon/config.js +52 -0
- package/dist/daemon/config.js.map +1 -0
- package/dist/daemon/controller.d.ts +29 -0
- package/dist/daemon/controller.js +34 -0
- package/dist/daemon/controller.js.map +1 -0
- package/dist/daemon/health.d.ts +8 -0
- package/dist/daemon/health.js +47 -0
- package/dist/daemon/health.js.map +1 -0
- package/dist/daemon/start.d.ts +5 -0
- package/dist/daemon/start.js +24 -0
- package/dist/daemon/start.js.map +1 -0
- package/dist/daemon/systemd.d.ts +35 -0
- package/dist/daemon/systemd.js +181 -0
- package/dist/daemon/systemd.js.map +1 -0
- package/dist/gateway/acquire.d.ts +14 -0
- package/dist/gateway/acquire.js +112 -0
- package/dist/gateway/acquire.js.map +1 -0
- package/dist/gateway/manifest.d.ts +26 -0
- package/dist/gateway/manifest.js +98 -0
- package/dist/gateway/manifest.js.map +1 -0
- package/dist/gateway/platform.d.ts +5 -0
- package/dist/gateway/platform.js +27 -0
- package/dist/gateway/platform.js.map +1 -0
- package/dist/harness/claude.d.ts +8 -0
- package/dist/{apps → harness}/claude.js +28 -28
- package/dist/harness/claude.js.map +1 -0
- package/dist/harness/codex.d.ts +10 -0
- package/dist/{apps → harness}/codex.js +50 -45
- package/dist/harness/codex.js.map +1 -0
- package/dist/harness/index.d.ts +13 -0
- package/dist/harness/index.js +63 -0
- package/dist/harness/index.js.map +1 -0
- package/dist/harness/opencode-config.d.ts +8 -0
- package/dist/{apps → harness}/opencode-config.js +116 -83
- package/dist/harness/opencode-config.js.map +1 -0
- package/dist/harness/opencode.d.ts +14 -0
- package/dist/harness/opencode.js +50 -0
- package/dist/harness/opencode.js.map +1 -0
- package/dist/harness/providers.d.ts +8 -0
- package/dist/harness/providers.js +30 -0
- package/dist/harness/providers.js.map +1 -0
- package/dist/harness/types.d.ts +14 -0
- package/dist/harness/types.js +2 -0
- package/dist/harness/types.js.map +1 -0
- package/dist/product.d.ts +12 -0
- package/dist/product.js +53 -10
- package/dist/product.js.map +1 -0
- package/dist/tui/clipboard.d.ts +1 -0
- package/dist/tui/clipboard.js +48 -0
- package/dist/tui/clipboard.js.map +1 -0
- package/dist/tui/openTuiApp.d.ts +1 -0
- package/dist/tui/openTuiApp.js +1436 -0
- package/dist/tui/openTuiApp.js.map +1 -0
- package/dist/tui/pages/flow-screen.d.ts +50 -0
- package/dist/tui/pages/flow-screen.js +435 -0
- package/dist/tui/pages/flow-screen.js.map +1 -0
- package/dist/tui/pages/footer.d.ts +7 -0
- package/dist/tui/pages/footer.js +14 -0
- package/dist/tui/pages/footer.js.map +1 -0
- package/dist/tui/pages/home-screen.d.ts +19 -0
- package/dist/tui/pages/home-screen.js +89 -0
- package/dist/tui/pages/home-screen.js.map +1 -0
- package/dist/tui/pages/index.d.ts +17 -0
- package/dist/tui/pages/index.js +11 -0
- package/dist/tui/pages/index.js.map +1 -0
- package/dist/tui/pages/local-setup-screen.d.ts +23 -0
- package/dist/tui/pages/local-setup-screen.js +134 -0
- package/dist/tui/pages/local-setup-screen.js.map +1 -0
- package/dist/tui/pages/location-card.d.ts +19 -0
- package/dist/tui/pages/location-card.js +116 -0
- package/dist/tui/pages/location-card.js.map +1 -0
- package/dist/tui/pages/menu-screen.d.ts +24 -0
- package/dist/tui/pages/menu-screen.js +117 -0
- package/dist/tui/pages/menu-screen.js.map +1 -0
- package/dist/tui/pages/session-grid.d.ts +23 -0
- package/dist/tui/pages/session-grid.js +552 -0
- package/dist/tui/pages/session-grid.js.map +1 -0
- package/dist/tui/pages/setup-screen.d.ts +15 -0
- package/dist/tui/pages/setup-screen.js +80 -0
- package/dist/tui/pages/setup-screen.js.map +1 -0
- package/dist/tui/pages/terminal-log.d.ts +14 -0
- package/dist/tui/pages/terminal-log.js +104 -0
- package/dist/tui/pages/terminal-log.js.map +1 -0
- package/dist/tui/pages/welcome-screen.d.ts +16 -0
- package/dist/tui/pages/welcome-screen.js +80 -0
- package/dist/tui/pages/welcome-screen.js.map +1 -0
- package/dist/tui/saved-tokens.d.ts +10 -0
- package/dist/tui/saved-tokens.js +104 -0
- package/dist/tui/saved-tokens.js.map +1 -0
- package/dist/tui/theme.d.ts +25 -0
- package/dist/tui/theme.js +27 -0
- package/dist/tui/theme.js.map +1 -0
- package/package.json +55 -38
- package/dist/apps/opencode.js +0 -158
- package/dist/apps.js +0 -148
- package/dist/auth/command.js +0 -109
- package/dist/auth/index.js +0 -2
- package/dist/auth/validation.js +0 -27
- package/dist/credentials/index.js +0 -3
- package/dist/credentials/schema.js +0 -63
- package/dist/credentials/store.js +0 -52
- package/dist/harnesses.js +0 -66
- package/dist/index.js +0 -3
- package/dist/providers.js +0 -30
- 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
|
|
3
|
-
import { homedir } from
|
|
4
|
-
import { dirname, join } from
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
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
|
-
|
|
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 !==
|
|
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(),
|
|
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
|
-
|
|
47
|
+
"sleeve-provider: anthropic",
|
|
52
48
|
`sleeve-harness: ${HARNESS}`,
|
|
53
|
-
].join(
|
|
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 (!(
|
|
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 !==
|
|
101
|
+
if (typeof value !== "string")
|
|
106
102
|
return false;
|
|
107
|
-
return value.includes(`sleeve-harness: ${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`, {
|
|
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,
|
|
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) ===
|
|
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,
|
|
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) ===
|
|
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 !==
|
|
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 !==
|
|
146
|
+
if (typeof err !== "object" || err === null || !("code" in err))
|
|
148
147
|
return undefined;
|
|
149
148
|
const value = err.code;
|
|
150
|
-
return typeof value ===
|
|
149
|
+
return typeof value === "string" ? value : undefined;
|
|
151
150
|
}
|
|
151
|
+
//# sourceMappingURL=claude.js.map
|