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 CHANGED
@@ -1,49 +1,30 @@
1
1
  # sleev
2
2
 
3
- The Sleev command-line tool.
3
+ Local Sleeve proxy daemon and setup TUI.
4
4
 
5
- Use it to sign in to Sleev from your terminal and connect supported local apps.
5
+ This repository is intentionally early. The first milestone is the local
6
+ installer shell:
6
7
 
7
- ## Login
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
- Run:
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
- npx sleev auth login
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
- npx sleev auth logout
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.12",
4
- "description": "Sleev command-line tools.",
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": "dist/cli.js"
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
- "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
18
- "build": "npm run clean && tsc -p tsconfig.json",
19
- "dev": "npm run build && node dist/cli.js",
20
- "typecheck": "tsc -p tsconfig.json --noEmit",
21
- "test": "npm run build && node --test test/*.test.mjs",
22
- "prepack": "npm run build"
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
- "sleev",
26
- "cli",
27
- "auth"
29
+ "sleeve",
30
+ "proxy",
31
+ "tui",
32
+ "claude-code",
33
+ "codex"
28
34
  ],
29
- "license": "MIT",
35
+ "license": "UNLICENSED",
30
36
  "engines": {
31
- "node": ">=20.0.0"
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
  }
@@ -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
+ }
@@ -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
- }
@@ -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
- }