sleev 0.0.12 → 0.0.15
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 -38
- package/bin/sleev.js +84 -0
- package/package.json +27 -28
- package/postinstall.mjs +66 -0
- package/dist/apps/claude.js +0 -151
- package/dist/apps/codex.js +0 -208
- package/dist/apps/opencode-config.js +0 -268
- package/dist/apps/opencode.js +0 -158
- package/dist/apps.js +0 -148
- package/dist/auth/browser.js +0 -80
- package/dist/auth/command.js +0 -109
- package/dist/auth/index.js +0 -2
- package/dist/auth/login.js +0 -55
- package/dist/auth/loopback.js +0 -134
- package/dist/auth/schema.js +0 -46
- package/dist/auth/validation.js +0 -27
- package/dist/cli.js +0 -69
- 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/product.js +0 -27
- package/dist/providers.js +0 -30
- package/dist/terminal.js +0 -55
package/README.md
CHANGED
|
@@ -1,49 +1,30 @@
|
|
|
1
1
|
# sleev
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Local Sleeve proxy daemon and setup TUI.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This repository is intentionally early. The first milestone is the local
|
|
6
|
+
installer shell:
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
- launch a TUI
|
|
9
|
+
- persist local config under platform-standard user directories
|
|
10
|
+
- prepare daemon lifecycle commands
|
|
11
|
+
- prepare harness setup commands
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
Published npm releases include native binaries, so end users do not need Bun
|
|
14
|
+
installed to run the TUI. Development and release builds still use Bun to compile
|
|
15
|
+
the OpenTUI app.
|
|
10
16
|
|
|
11
17
|
```bash
|
|
12
|
-
|
|
18
|
+
npm install
|
|
19
|
+
npm run check
|
|
20
|
+
npm test
|
|
21
|
+
npm run build
|
|
22
|
+
npm run build:native
|
|
13
23
|
```
|
|
14
24
|
|
|
15
|
-
Sleev will:
|
|
16
|
-
|
|
17
|
-
1. Open your browser.
|
|
18
|
-
2. Ask you to sign in or create an account.
|
|
19
|
-
3. Return you to the terminal after login.
|
|
20
|
-
4. Ask which supported local apps you want to use with Sleev.
|
|
21
|
-
|
|
22
|
-
After that, your local apps can use Sleev without logging in again.
|
|
23
|
-
|
|
24
|
-
## Logout
|
|
25
|
-
|
|
26
|
-
Run:
|
|
27
|
-
|
|
28
25
|
```bash
|
|
29
|
-
|
|
26
|
+
sleev
|
|
27
|
+
sleev paths
|
|
28
|
+
sleev status
|
|
29
|
+
sleev daemon status
|
|
30
30
|
```
|
|
31
|
-
|
|
32
|
-
This removes your local Sleev login from this machine.
|
|
33
|
-
|
|
34
|
-
## Already Logged In
|
|
35
|
-
|
|
36
|
-
If you need to replace your current local login, run:
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
npx sleev auth login --force
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
## Requirements
|
|
43
|
-
|
|
44
|
-
- Node.js 20 or newer
|
|
45
|
-
- A browser you can sign in with
|
|
46
|
-
|
|
47
|
-
## Notes
|
|
48
|
-
|
|
49
|
-
Your login is stored locally on your machine. Do not share your Sleev auth file.
|
package/bin/sleev.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
const forwardedSignals = ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
7
|
+
function run(target) {
|
|
8
|
+
const child = spawn(target, process.argv.slice(2), {
|
|
9
|
+
stdio: "inherit",
|
|
10
|
+
});
|
|
11
|
+
child.on("error", (error) => {
|
|
12
|
+
console.error(error.message);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
});
|
|
15
|
+
const forwarders = new Map();
|
|
16
|
+
for (const signal of forwardedSignals) {
|
|
17
|
+
const forwarder = () => {
|
|
18
|
+
try {
|
|
19
|
+
child.kill(signal);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// The child may have already exited.
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
forwarders.set(signal, forwarder);
|
|
26
|
+
process.on(signal, forwarder);
|
|
27
|
+
}
|
|
28
|
+
child.on("exit", (code, signal) => {
|
|
29
|
+
for (const [forwardedSignal, forwarder] of forwarders) {
|
|
30
|
+
process.removeListener(forwardedSignal, forwarder);
|
|
31
|
+
}
|
|
32
|
+
if (signal) {
|
|
33
|
+
process.kill(process.pid, signal);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
process.exit(typeof code === "number" ? code : 0);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
const platformMap = {
|
|
40
|
+
darwin: "darwin",
|
|
41
|
+
linux: "linux",
|
|
42
|
+
win32: "windows",
|
|
43
|
+
};
|
|
44
|
+
const archMap = {
|
|
45
|
+
arm64: "arm64",
|
|
46
|
+
x64: "x64",
|
|
47
|
+
};
|
|
48
|
+
function packageNames() {
|
|
49
|
+
const platform = platformMap[process.platform] ?? process.platform;
|
|
50
|
+
const arch = archMap[process.arch] ?? process.arch;
|
|
51
|
+
return [`sleev-${platform}-${arch}`];
|
|
52
|
+
}
|
|
53
|
+
function findBinary(startDir) {
|
|
54
|
+
const binary = process.platform === "win32" ? "sleev.exe" : "sleev";
|
|
55
|
+
let current = startDir;
|
|
56
|
+
for (;;) {
|
|
57
|
+
const modules = join(current, "node_modules");
|
|
58
|
+
if (existsSync(modules)) {
|
|
59
|
+
for (const name of packageNames()) {
|
|
60
|
+
const candidate = join(modules, name, "bin", binary);
|
|
61
|
+
if (existsSync(candidate))
|
|
62
|
+
return candidate;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const parent = dirname(current);
|
|
66
|
+
if (parent === current)
|
|
67
|
+
return undefined;
|
|
68
|
+
current = parent;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const envPath = process.env.SLEEV_BIN_PATH?.trim();
|
|
72
|
+
const scriptDir = dirname(realpathSync(fileURLToPath(import.meta.url)));
|
|
73
|
+
const resolved = envPath || findBinary(scriptDir);
|
|
74
|
+
if (!resolved) {
|
|
75
|
+
console.error([
|
|
76
|
+
"Sleev could not find the native binary for this platform.",
|
|
77
|
+
`Tried: ${packageNames().join(", ")}`,
|
|
78
|
+
"This usually means npm skipped optional dependencies or this platform is not supported yet.",
|
|
79
|
+
"Try reinstalling without --ignore-scripts/--omit=optional, or set SLEEV_BIN_PATH to a local binary.",
|
|
80
|
+
].join("\n"));
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
run(resolved);
|
|
84
|
+
//# sourceMappingURL=bin.js.map
|
package/package.json
CHANGED
|
@@ -1,40 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sleev",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.0.15",
|
|
4
|
+
"description": "Local Sleeve proxy daemon and setup TUI for coding harnesses.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/sleev3/cli.git"
|
|
8
|
+
},
|
|
5
9
|
"type": "module",
|
|
6
10
|
"bin": {
|
|
7
|
-
"sleev": "
|
|
8
|
-
|
|
9
|
-
"exports": {
|
|
10
|
-
".": "./dist/index.js"
|
|
11
|
+
"sleev": "./bin/sleev.js",
|
|
12
|
+
"sleeved": "./bin/sleev.js"
|
|
11
13
|
},
|
|
12
|
-
"files": [
|
|
13
|
-
"dist",
|
|
14
|
-
"README.md"
|
|
15
|
-
],
|
|
16
14
|
"scripts": {
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
"postinstall": "node ./postinstall.mjs"
|
|
16
|
+
},
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"optionalDependencies": {
|
|
21
|
+
"sleev-linux-x64": "0.0.15",
|
|
22
|
+
"sleev-linux-arm64": "0.0.15",
|
|
23
|
+
"sleev-darwin-x64": "0.0.15",
|
|
24
|
+
"sleev-darwin-arm64": "0.0.15",
|
|
25
|
+
"sleev-windows-x64": "0.0.15",
|
|
26
|
+
"sleev-windows-arm64": "0.0.15"
|
|
23
27
|
},
|
|
24
28
|
"keywords": [
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
29
|
+
"sleeve",
|
|
30
|
+
"proxy",
|
|
31
|
+
"tui",
|
|
32
|
+
"claude-code",
|
|
33
|
+
"codex"
|
|
28
34
|
],
|
|
29
|
-
"license": "
|
|
35
|
+
"license": "UNLICENSED",
|
|
30
36
|
"engines": {
|
|
31
|
-
"node": ">=
|
|
32
|
-
},
|
|
33
|
-
"devDependencies": {
|
|
34
|
-
"@types/node": "^24.12.2",
|
|
35
|
-
"typescript": "~6.0.2"
|
|
36
|
-
},
|
|
37
|
-
"dependencies": {
|
|
38
|
-
"jsonc-parser": "^3.3.1"
|
|
37
|
+
"node": ">=22"
|
|
39
38
|
}
|
|
40
39
|
}
|
package/postinstall.mjs
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
if (process.env.SLEEV_SKIP_POSTINSTALL) process.exit(0);
|
|
9
|
+
|
|
10
|
+
const platformMap = {
|
|
11
|
+
darwin: "darwin",
|
|
12
|
+
linux: "linux",
|
|
13
|
+
win32: "windows",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const archMap = {
|
|
17
|
+
arm64: "arm64",
|
|
18
|
+
x64: "x64",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const platform = platformMap[process.platform] ?? process.platform;
|
|
22
|
+
const arch = archMap[process.arch] ?? process.arch;
|
|
23
|
+
const packageName = `sleev-${platform}-${arch}`;
|
|
24
|
+
const binary = process.platform === "win32" ? "sleev.exe" : "sleev";
|
|
25
|
+
|
|
26
|
+
function findBinary(startDir) {
|
|
27
|
+
let current = startDir;
|
|
28
|
+
for (;;) {
|
|
29
|
+
const candidate = join(
|
|
30
|
+
current,
|
|
31
|
+
"node_modules",
|
|
32
|
+
packageName,
|
|
33
|
+
"bin",
|
|
34
|
+
binary,
|
|
35
|
+
);
|
|
36
|
+
if (existsSync(candidate)) return candidate;
|
|
37
|
+
|
|
38
|
+
const parent = dirname(current);
|
|
39
|
+
if (parent === current) return undefined;
|
|
40
|
+
current = parent;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const scriptDir = dirname(realpathSync(fileURLToPath(import.meta.url)));
|
|
45
|
+
const resolved = process.env.SLEEV_BIN_PATH || findBinary(scriptDir);
|
|
46
|
+
|
|
47
|
+
if (!resolved) {
|
|
48
|
+
console.error(
|
|
49
|
+
[
|
|
50
|
+
`Sleev could not find ${packageName}.`,
|
|
51
|
+
"This package is installed as an optional dependency that matches your platform.",
|
|
52
|
+
"Try reinstalling without --ignore-scripts/--omit=optional, or set SLEEV_SKIP_POSTINSTALL=1 to skip this check.",
|
|
53
|
+
].join("\n"),
|
|
54
|
+
);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const result = spawnSync(resolved, ["paths"], {
|
|
59
|
+
stdio: "ignore",
|
|
60
|
+
timeout: 5000,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (result.status !== 0) {
|
|
64
|
+
console.error(`Sleev native binary verification failed: ${resolved}`);
|
|
65
|
+
process.exit(result.status ?? 1);
|
|
66
|
+
}
|
package/dist/apps/claude.js
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
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';
|
|
10
|
-
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;
|
|
15
|
-
}
|
|
16
|
-
export async function patchClaude(auth, path = claudePath()) {
|
|
17
|
-
const existing = await readJson(path);
|
|
18
|
-
const config = patchConfig(existing, auth);
|
|
19
|
-
await writeJson(path, config);
|
|
20
|
-
return { path };
|
|
21
|
-
}
|
|
22
|
-
export async function unpatchClaude(path = claudePath()) {
|
|
23
|
-
const existing = await readExistingJson(path);
|
|
24
|
-
if (!existing)
|
|
25
|
-
return;
|
|
26
|
-
const env = object(existing.env);
|
|
27
|
-
if (typeof env.ANTHROPIC_CUSTOM_HEADERS !== 'string')
|
|
28
|
-
return;
|
|
29
|
-
if (!patched(env))
|
|
30
|
-
return;
|
|
31
|
-
await writeJson(path, removeBackup({ ...existing, env: restoreEnv(env, backup(existing)) }));
|
|
32
|
-
}
|
|
33
|
-
export function claudePath(env = process.env) {
|
|
34
|
-
return join(env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude'), 'settings.json');
|
|
35
|
-
}
|
|
36
|
-
function patchConfig(input, auth) {
|
|
37
|
-
const env = object(input.env);
|
|
38
|
-
const previous = backup(input) ?? backupEnv(env);
|
|
39
|
-
return {
|
|
40
|
-
...withBackup(input, previous),
|
|
41
|
-
env: {
|
|
42
|
-
...env,
|
|
43
|
-
[BASE]: trim(auth.proxyUrl),
|
|
44
|
-
[CUSTOM]: headers(auth),
|
|
45
|
-
},
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
function headers(auth) {
|
|
49
|
-
return [
|
|
50
|
-
`sleeve-token: ${auth.token}`,
|
|
51
|
-
'sleeve-provider: anthropic',
|
|
52
|
-
`sleeve-harness: ${HARNESS}`,
|
|
53
|
-
].join('\n');
|
|
54
|
-
}
|
|
55
|
-
function backup(input) {
|
|
56
|
-
const sleev = object(input.sleev);
|
|
57
|
-
const claude = object(sleev.claudeCode);
|
|
58
|
-
if (!('previousEnv' in claude))
|
|
59
|
-
return null;
|
|
60
|
-
return object(claude.previousEnv);
|
|
61
|
-
}
|
|
62
|
-
function backupEnv(env) {
|
|
63
|
-
if (patched(env))
|
|
64
|
-
return {};
|
|
65
|
-
const previous = {};
|
|
66
|
-
if (BASE in env)
|
|
67
|
-
previous[BASE] = env[BASE];
|
|
68
|
-
if (CUSTOM in env)
|
|
69
|
-
previous[CUSTOM] = env[CUSTOM];
|
|
70
|
-
return previous;
|
|
71
|
-
}
|
|
72
|
-
function withBackup(input, previous) {
|
|
73
|
-
const sleev = object(input.sleev);
|
|
74
|
-
return {
|
|
75
|
-
...input,
|
|
76
|
-
sleev: {
|
|
77
|
-
...sleev,
|
|
78
|
-
claudeCode: {
|
|
79
|
-
previousEnv: previous,
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
function removeBackup(input) {
|
|
85
|
-
const sleev = object(input.sleev);
|
|
86
|
-
const { claudeCode: _claude, ...rest } = sleev;
|
|
87
|
-
if (Object.keys(rest).length > 0)
|
|
88
|
-
return { ...input, sleev: rest };
|
|
89
|
-
const { sleev: _sleev, ...config } = input;
|
|
90
|
-
return config;
|
|
91
|
-
}
|
|
92
|
-
function restoreEnv(env, previous) {
|
|
93
|
-
const { [BASE]: _base, [CUSTOM]: _headers, ...rest } = env;
|
|
94
|
-
const restored = { ...rest };
|
|
95
|
-
if (!previous)
|
|
96
|
-
return restored;
|
|
97
|
-
if (BASE in previous)
|
|
98
|
-
restored[BASE] = previous[BASE];
|
|
99
|
-
if (CUSTOM in previous)
|
|
100
|
-
restored[CUSTOM] = previous[CUSTOM];
|
|
101
|
-
return restored;
|
|
102
|
-
}
|
|
103
|
-
function patched(env) {
|
|
104
|
-
const value = env[CUSTOM];
|
|
105
|
-
if (typeof value !== 'string')
|
|
106
|
-
return false;
|
|
107
|
-
return value.includes(`sleeve-harness: ${HARNESS}`) || value.includes(`sleeve-harness: ${OLD_HARNESS}`);
|
|
108
|
-
}
|
|
109
|
-
async function writeJson(path, config) {
|
|
110
|
-
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
111
|
-
await writeFile(path, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
|
|
112
|
-
await chmod(path, 0o600);
|
|
113
|
-
}
|
|
114
|
-
async function readJson(path) {
|
|
115
|
-
try {
|
|
116
|
-
const raw = await readFile(path, 'utf8');
|
|
117
|
-
const parsed = JSON.parse(raw);
|
|
118
|
-
return object(parsed);
|
|
119
|
-
}
|
|
120
|
-
catch (err) {
|
|
121
|
-
if (code(err) === 'ENOENT')
|
|
122
|
-
return {};
|
|
123
|
-
throw err;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
async function readExistingJson(path) {
|
|
127
|
-
try {
|
|
128
|
-
const raw = await readFile(path, 'utf8');
|
|
129
|
-
const parsed = JSON.parse(raw);
|
|
130
|
-
return object(parsed);
|
|
131
|
-
}
|
|
132
|
-
catch (err) {
|
|
133
|
-
if (code(err) === 'ENOENT')
|
|
134
|
-
return null;
|
|
135
|
-
throw err;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
function object(value) {
|
|
139
|
-
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
140
|
-
return {};
|
|
141
|
-
return value;
|
|
142
|
-
}
|
|
143
|
-
function trim(url) {
|
|
144
|
-
return url.replace(/\/+$/, '');
|
|
145
|
-
}
|
|
146
|
-
function code(err) {
|
|
147
|
-
if (typeof err !== 'object' || err === null || !('code' in err))
|
|
148
|
-
return undefined;
|
|
149
|
-
const value = err.code;
|
|
150
|
-
return typeof value === 'string' ? value : undefined;
|
|
151
|
-
}
|
package/dist/apps/codex.js
DELETED
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
// Codex setup owns global TOML patching for the OpenAI-compatible proxy provider.
|
|
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 PROVIDER = 'sleev';
|
|
7
|
-
const HARNESS = 'codex cli';
|
|
8
|
-
const OLD_HARNESS = 'codex';
|
|
9
|
-
export async function setupCodex(auth, path = codexPath()) {
|
|
10
|
-
const result = await patchCodex(auth, path);
|
|
11
|
-
success(process.stdout, 'Configured Codex');
|
|
12
|
-
field(process.stdout, 'config', result.path);
|
|
13
|
-
return result;
|
|
14
|
-
}
|
|
15
|
-
export async function patchCodex(auth, path = codexPath()) {
|
|
16
|
-
const existing = await readText(path);
|
|
17
|
-
const config = patchCodexConfig(existing, auth);
|
|
18
|
-
await writeText(path, config);
|
|
19
|
-
return { path };
|
|
20
|
-
}
|
|
21
|
-
export async function unpatchCodex(path = codexPath()) {
|
|
22
|
-
const existing = await readText(path);
|
|
23
|
-
if (!existing)
|
|
24
|
-
return;
|
|
25
|
-
const config = unpatchCodexConfig(existing);
|
|
26
|
-
await writeText(path, config);
|
|
27
|
-
}
|
|
28
|
-
export function codexPath(env = process.env) {
|
|
29
|
-
return join(env.CODEX_HOME || join(homedir(), '.codex'), 'config.toml');
|
|
30
|
-
}
|
|
31
|
-
export function patchCodexConfig(input, auth) {
|
|
32
|
-
const previous = previousProvider(input);
|
|
33
|
-
const stale = removeTable(removeTable(input.trimEnd(), table(PROVIDER)), 'sleev.codex');
|
|
34
|
-
const clean = removeRootKey(stale, 'model_provider');
|
|
35
|
-
const root = insertRootKey(clean.trimEnd(), 'model_provider', PROVIDER);
|
|
36
|
-
const meta = previous ? `\n\n[sleev.codex]\nprevious_model_provider = "${toml(previous)}"` : '';
|
|
37
|
-
return `${patchProvider(root, auth).trimEnd()}${meta}\n`;
|
|
38
|
-
}
|
|
39
|
-
export function unpatchCodexConfig(input) {
|
|
40
|
-
const previous = backup(input);
|
|
41
|
-
const provider = rootString(input, 'model_provider') ?? 'openai';
|
|
42
|
-
const clean = removeTable(removeTable(removeRootKey(input.trimEnd(), 'model_provider'), table(PROVIDER)), 'sleev.codex');
|
|
43
|
-
const restored = previous ? insertRootKey(clean.trimEnd(), 'model_provider', previous) : clean;
|
|
44
|
-
const config = provider === PROVIDER ? restored : removeSleevKeys(input, provider);
|
|
45
|
-
return `${config.trimEnd()}\n`;
|
|
46
|
-
}
|
|
47
|
-
function insertRootKey(input, key, value) {
|
|
48
|
-
const line = `${key} = "${toml(value)}"`;
|
|
49
|
-
if (input.length === 0)
|
|
50
|
-
return line;
|
|
51
|
-
const lines = input.split('\n');
|
|
52
|
-
const index = lines.findIndex((item) => /^\s*\[/.test(item));
|
|
53
|
-
if (index === -1)
|
|
54
|
-
return `${input}\n${line}`;
|
|
55
|
-
return [...lines.slice(0, index), line, '', ...lines.slice(index)].join('\n');
|
|
56
|
-
}
|
|
57
|
-
function removeRootKey(input, key) {
|
|
58
|
-
const lines = input.split('\n');
|
|
59
|
-
const next = [];
|
|
60
|
-
let root = true;
|
|
61
|
-
let skipped = false;
|
|
62
|
-
for (const line of lines) {
|
|
63
|
-
if (/^\s*\[/.test(line))
|
|
64
|
-
root = false;
|
|
65
|
-
if (root && new RegExp(`^\\s*${escape(key)}\\s*=`).test(line)) {
|
|
66
|
-
skipped = true;
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
if (skipped && root && /^\s*$/.test(line)) {
|
|
70
|
-
skipped = false;
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
skipped = false;
|
|
74
|
-
next.push(line);
|
|
75
|
-
}
|
|
76
|
-
return next.join('\n');
|
|
77
|
-
}
|
|
78
|
-
function removeTable(input, name) {
|
|
79
|
-
const lines = input.split('\n');
|
|
80
|
-
const next = [];
|
|
81
|
-
let skip = false;
|
|
82
|
-
const header = new RegExp(`^\\s*\\[\\s*${escape(name)}(?:\\.|\\s*\\])`);
|
|
83
|
-
for (const line of lines) {
|
|
84
|
-
if (header.test(line)) {
|
|
85
|
-
skip = true;
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
if (skip && /^\s*\[/.test(line))
|
|
89
|
-
skip = header.test(line);
|
|
90
|
-
if (!skip)
|
|
91
|
-
next.push(line);
|
|
92
|
-
}
|
|
93
|
-
return next.join('\n');
|
|
94
|
-
}
|
|
95
|
-
function patchProvider(input, auth) {
|
|
96
|
-
return `${input.trimEnd()}\n\n[${table(PROVIDER)}]\nname = "Sleev"\n${providerKeys(auth).join('\n')}`;
|
|
97
|
-
}
|
|
98
|
-
function providerKeys(auth) {
|
|
99
|
-
return [
|
|
100
|
-
`base_url = "${toml(baseUrl(auth.proxyUrl))}"`,
|
|
101
|
-
'wire_api = "responses"',
|
|
102
|
-
'',
|
|
103
|
-
`[${table(PROVIDER)}.http_headers]`,
|
|
104
|
-
`"sleeve-token" = "${toml(auth.token)}"`,
|
|
105
|
-
'"sleeve-provider" = "openai"',
|
|
106
|
-
`"sleeve-harness" = "${HARNESS}"`,
|
|
107
|
-
];
|
|
108
|
-
}
|
|
109
|
-
function removeSleevKeys(input, provider) {
|
|
110
|
-
const lines = input.split('\n');
|
|
111
|
-
const header = new RegExp(`^\\s*\\[\\s*${escape(table(provider))}\\s*\\]\\s*(#.*)?$`);
|
|
112
|
-
const index = lines.findIndex((line) => header.test(line));
|
|
113
|
-
if (index === -1)
|
|
114
|
-
return input;
|
|
115
|
-
const current = [];
|
|
116
|
-
for (let i = index + 1; i < lines.length; i += 1) {
|
|
117
|
-
const line = lines[i] ?? '';
|
|
118
|
-
if (/^\s*\[/.test(line))
|
|
119
|
-
break;
|
|
120
|
-
current.push(line);
|
|
121
|
-
}
|
|
122
|
-
if (!current.some((line) => owned(line)))
|
|
123
|
-
return input;
|
|
124
|
-
return lines
|
|
125
|
-
.filter((line, i) => {
|
|
126
|
-
const inside = i > index && !/^\s*\[/.test(line);
|
|
127
|
-
if (!inside)
|
|
128
|
-
return true;
|
|
129
|
-
return !/^\s*(base_url|wire_api|http_headers)\s*=/.test(line);
|
|
130
|
-
})
|
|
131
|
-
.join('\n');
|
|
132
|
-
}
|
|
133
|
-
function previousProvider(input) {
|
|
134
|
-
const current = rootString(input, 'model_provider');
|
|
135
|
-
return backup(input) ?? (current && current !== PROVIDER ? current : null);
|
|
136
|
-
}
|
|
137
|
-
function backup(input) {
|
|
138
|
-
return tableString(input, 'sleev.codex', 'previous_model_provider');
|
|
139
|
-
}
|
|
140
|
-
function tableString(input, name, key) {
|
|
141
|
-
const lines = input.split('\n');
|
|
142
|
-
const header = new RegExp(`^\\s*\\[\\s*${escape(name)}\\s*\\]\\s*(#.*)?$`);
|
|
143
|
-
const index = lines.findIndex((line) => header.test(line));
|
|
144
|
-
if (index === -1)
|
|
145
|
-
return null;
|
|
146
|
-
for (let i = index + 1; i < lines.length; i += 1) {
|
|
147
|
-
const line = lines[i] ?? '';
|
|
148
|
-
if (/^\s*\[/.test(line))
|
|
149
|
-
return null;
|
|
150
|
-
const match = new RegExp(`^\\s*${escape(key)}\\s*=\\s*(["'])([^"']+)\\1`).exec(line);
|
|
151
|
-
if (match?.[2])
|
|
152
|
-
return match[2];
|
|
153
|
-
}
|
|
154
|
-
return null;
|
|
155
|
-
}
|
|
156
|
-
function rootString(input, key) {
|
|
157
|
-
const lines = input.split('\n');
|
|
158
|
-
for (const line of lines) {
|
|
159
|
-
if (/^\s*\[/.test(line))
|
|
160
|
-
return null;
|
|
161
|
-
const match = new RegExp(`^\\s*${escape(key)}\\s*=\\s*(["'])([^"']+)\\1`).exec(line);
|
|
162
|
-
if (match?.[2])
|
|
163
|
-
return match[2];
|
|
164
|
-
}
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
async function readText(path) {
|
|
168
|
-
try {
|
|
169
|
-
return await readFile(path, 'utf8');
|
|
170
|
-
}
|
|
171
|
-
catch (err) {
|
|
172
|
-
if (code(err) === 'ENOENT')
|
|
173
|
-
return '';
|
|
174
|
-
throw err;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
function toml(value) {
|
|
178
|
-
return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
|
|
179
|
-
}
|
|
180
|
-
function escape(value) {
|
|
181
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
182
|
-
}
|
|
183
|
-
function trim(url) {
|
|
184
|
-
return url.replace(/\/+$/, '');
|
|
185
|
-
}
|
|
186
|
-
function baseUrl(url) {
|
|
187
|
-
const clean = trim(url);
|
|
188
|
-
if (clean.endsWith('/v1'))
|
|
189
|
-
return clean;
|
|
190
|
-
return `${clean}/v1`;
|
|
191
|
-
}
|
|
192
|
-
function table(provider) {
|
|
193
|
-
return `model_providers.${provider}`;
|
|
194
|
-
}
|
|
195
|
-
function owned(line) {
|
|
196
|
-
return line.includes(`"sleeve-harness" = "${HARNESS}"`) || line.includes(`"sleeve-harness" = "${OLD_HARNESS}"`);
|
|
197
|
-
}
|
|
198
|
-
async function writeText(path, config) {
|
|
199
|
-
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
200
|
-
await writeFile(path, config, { mode: 0o600 });
|
|
201
|
-
await chmod(path, 0o600);
|
|
202
|
-
}
|
|
203
|
-
function code(err) {
|
|
204
|
-
if (typeof err !== 'object' || err === null || !('code' in err))
|
|
205
|
-
return undefined;
|
|
206
|
-
const value = err.code;
|
|
207
|
-
return typeof value === 'string' ? value : undefined;
|
|
208
|
-
}
|