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/dist/auth/command.js
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
// Auth command flows for the CLI dispatcher.
|
|
2
|
-
import { selectApps } from '../apps.js';
|
|
3
|
-
import { authPath, clearAuth, createAuthFile, readAuth, writeAuth } from '../credentials/index.js';
|
|
4
|
-
import { setupHarnesses, unpatchHarnesses } from '../harnesses.js';
|
|
5
|
-
import { proxyUrl } from '../product.js';
|
|
6
|
-
import { field, line, note, success } from '../terminal.js';
|
|
7
|
-
import { login as browserLogin } from './login.js';
|
|
8
|
-
import { validateAuth } from './validation.js';
|
|
9
|
-
export function commandRuntime() {
|
|
10
|
-
return {
|
|
11
|
-
output: process.stdout,
|
|
12
|
-
authPath,
|
|
13
|
-
readAuth,
|
|
14
|
-
writeAuth,
|
|
15
|
-
clearAuth,
|
|
16
|
-
createAuthFile,
|
|
17
|
-
login: browserLogin,
|
|
18
|
-
validateAuth,
|
|
19
|
-
selectApps: () => selectApps(),
|
|
20
|
-
setupHarnesses,
|
|
21
|
-
unpatchHarnesses,
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
export async function loginCommand(options = {}, runtime = commandRuntime()) {
|
|
25
|
-
const path = runtime.authPath();
|
|
26
|
-
const existing = await readLocalAuth(path, runtime);
|
|
27
|
-
const local = existing ? canonicalAuth(existing) : null;
|
|
28
|
-
const state = local && !options.force ? await runtime.validateAuth(local) : 'invalid';
|
|
29
|
-
if (state === 'valid') {
|
|
30
|
-
note(runtime.output, 'Using existing valid local auth');
|
|
31
|
-
}
|
|
32
|
-
if (existing && options.force) {
|
|
33
|
-
note(runtime.output, 'Replacing existing local auth. The previous remote Sleeve token is not revoked');
|
|
34
|
-
}
|
|
35
|
-
if (existing && state === 'invalid' && !options.force) {
|
|
36
|
-
note(runtime.output, 'Existing local auth is expired or revoked. Creating a new token');
|
|
37
|
-
}
|
|
38
|
-
if (existing && state === 'unknown' && !options.force) {
|
|
39
|
-
note(runtime.output, 'Could not verify existing local auth. Creating a new token');
|
|
40
|
-
}
|
|
41
|
-
const useLocal = state === 'valid' && local;
|
|
42
|
-
const auth = useLocal ? local : runtime.createAuthFile(await runtime.login());
|
|
43
|
-
const changed = useLocal && existing?.proxyUrl !== auth.proxyUrl;
|
|
44
|
-
const written = useLocal && !changed ? { path } : await runtime.writeAuth(auth, path);
|
|
45
|
-
if (changed)
|
|
46
|
-
note(runtime.output, 'Updated local auth to use the current Sleev proxy');
|
|
47
|
-
line(runtime.output);
|
|
48
|
-
success(runtime.output, useLocal ? 'Already logged in' : 'Logged in');
|
|
49
|
-
field(runtime.output, 'credential', written.path);
|
|
50
|
-
field(runtime.output, 'proxy', auth.proxyUrl);
|
|
51
|
-
note(runtime.output, 'Keep auth.json and patched app configs private. They contain your live Sleeve token');
|
|
52
|
-
line(runtime.output);
|
|
53
|
-
const apps = await runtime.selectApps();
|
|
54
|
-
const harnesses = await runtime.setupHarnesses(auth, apps);
|
|
55
|
-
success(runtime.output, 'Setup complete');
|
|
56
|
-
field(runtime.output, 'applications configured to use sleev', harnesses.label);
|
|
57
|
-
return 0;
|
|
58
|
-
}
|
|
59
|
-
async function readLocalAuth(path, runtime) {
|
|
60
|
-
try {
|
|
61
|
-
return await runtime.readAuth(path);
|
|
62
|
-
}
|
|
63
|
-
catch (err) {
|
|
64
|
-
if (!recoverableAuthError(err))
|
|
65
|
-
throw err;
|
|
66
|
-
note(runtime.output, 'Replacing invalid local auth file');
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
export async function logoutCommand(runtime = commandRuntime()) {
|
|
71
|
-
const path = runtime.authPath();
|
|
72
|
-
const existing = await readLogoutAuth(path, runtime);
|
|
73
|
-
const cleanup = await runtime.unpatchHarnesses();
|
|
74
|
-
await runtime.clearAuth(path);
|
|
75
|
-
if (!existing) {
|
|
76
|
-
note(runtime.output, 'No local auth file found');
|
|
77
|
-
return 0;
|
|
78
|
-
}
|
|
79
|
-
success(runtime.output, 'Removed local auth');
|
|
80
|
-
field(runtime.output, 'removed', path);
|
|
81
|
-
note(runtime.output, 'Remote Sleeve token is not revoked and remains active');
|
|
82
|
-
if (cleanup.failures[0])
|
|
83
|
-
note(runtime.output, `Some harness config cleanup failed: ${cleanup.failures.join('; ')}`);
|
|
84
|
-
return 0;
|
|
85
|
-
}
|
|
86
|
-
function recoverableAuthError(err) {
|
|
87
|
-
if (err instanceof SyntaxError)
|
|
88
|
-
return true;
|
|
89
|
-
if (!(err instanceof Error))
|
|
90
|
-
return false;
|
|
91
|
-
return err.message.startsWith('Sleev auth ');
|
|
92
|
-
}
|
|
93
|
-
function canonicalAuth(auth) {
|
|
94
|
-
const proxy = proxyUrl();
|
|
95
|
-
if (auth.proxyUrl === proxy)
|
|
96
|
-
return auth;
|
|
97
|
-
return { ...auth, proxyUrl: proxy };
|
|
98
|
-
}
|
|
99
|
-
async function readLogoutAuth(path, runtime) {
|
|
100
|
-
try {
|
|
101
|
-
return await runtime.readAuth(path);
|
|
102
|
-
}
|
|
103
|
-
catch (err) {
|
|
104
|
-
if (!recoverableAuthError(err))
|
|
105
|
-
throw err;
|
|
106
|
-
note(runtime.output, 'Removing invalid local auth file');
|
|
107
|
-
return { version: 1, token: '', label: null, proxyUrl: '', createdAt: '' };
|
|
108
|
-
}
|
|
109
|
-
}
|
package/dist/auth/index.js
DELETED
package/dist/auth/login.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
// Browser login orchestration for `sleev auth login`.
|
|
2
|
-
import { randomBytes } from 'node:crypto';
|
|
3
|
-
import { hostname } from 'node:os';
|
|
4
|
-
import { webOrigin as defaultWebOrigin } from '../product.js';
|
|
5
|
-
import { brand, field, line, note, step, success } from '../terminal.js';
|
|
6
|
-
import { openBrowser } from './browser.js';
|
|
7
|
-
import { startLoopback } from './loopback.js';
|
|
8
|
-
const LOGIN_TIMEOUT_MS = 10 * 60 * 1000;
|
|
9
|
-
export async function login(options = {}) {
|
|
10
|
-
const output = options.output ?? process.stdout;
|
|
11
|
-
brand(output);
|
|
12
|
-
return runHandoff({ ...options, mode: options.mode ?? 'signup', output });
|
|
13
|
-
}
|
|
14
|
-
async function runHandoff(options) {
|
|
15
|
-
const output = options.output;
|
|
16
|
-
const mode = options.mode;
|
|
17
|
-
const origin = options.webOrigin ?? defaultWebOrigin();
|
|
18
|
-
const opener = options.opener ?? openBrowser;
|
|
19
|
-
const state = randomBytes(32).toString('base64url');
|
|
20
|
-
const loopback = await startLoopback({
|
|
21
|
-
state,
|
|
22
|
-
webOrigin: origin,
|
|
23
|
-
timeoutMs: options.timeoutMs ?? LOGIN_TIMEOUT_MS,
|
|
24
|
-
});
|
|
25
|
-
const url = loginUrl(origin, mode, state, loopback.callback);
|
|
26
|
-
const action = mode === 'signup' ? 'create your account' : 'log in';
|
|
27
|
-
step(output, `Opening browser to ${action}`);
|
|
28
|
-
field(output, 'url', url);
|
|
29
|
-
try {
|
|
30
|
-
const opened = await opener(url);
|
|
31
|
-
if (!opened) {
|
|
32
|
-
note(output, 'Could not open a browser automatically. Open the URL above instead');
|
|
33
|
-
}
|
|
34
|
-
note(output, 'Waiting for the browser confirmation...');
|
|
35
|
-
const payload = await loopback.wait;
|
|
36
|
-
success(output, 'Browser login confirmed');
|
|
37
|
-
return {
|
|
38
|
-
token: payload.token,
|
|
39
|
-
label: payload.label,
|
|
40
|
-
proxyUrl: payload.proxyUrl,
|
|
41
|
-
createdAt: (options.now ?? (() => new Date()))().toISOString(),
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
finally {
|
|
45
|
-
await loopback.close();
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
function loginUrl(origin, mode, state, callback) {
|
|
49
|
-
const url = new URL('/cli/login', origin);
|
|
50
|
-
url.searchParams.set('mode', mode);
|
|
51
|
-
url.searchParams.set('state', state);
|
|
52
|
-
url.searchParams.set('callback', callback);
|
|
53
|
-
url.searchParams.set('device', hostname().slice(0, 80));
|
|
54
|
-
return url.toString();
|
|
55
|
-
}
|
package/dist/auth/loopback.js
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
// Temporary loopback server that receives the browser login handoff.
|
|
2
|
-
import { createServer } from 'node:http';
|
|
3
|
-
import { parseHandoff } from './schema.js';
|
|
4
|
-
const MAX_BODY_BYTES = 16 * 1024;
|
|
5
|
-
export async function startLoopback(options) {
|
|
6
|
-
let settled = false;
|
|
7
|
-
let finish = () => undefined;
|
|
8
|
-
let fail = () => undefined;
|
|
9
|
-
const wait = new Promise((resolve, reject) => {
|
|
10
|
-
finish = resolve;
|
|
11
|
-
fail = reject;
|
|
12
|
-
});
|
|
13
|
-
const server = createServer(async (req, res) => {
|
|
14
|
-
try {
|
|
15
|
-
await handleRequest(req, res, options, (payload) => {
|
|
16
|
-
if (settled)
|
|
17
|
-
return;
|
|
18
|
-
settled = true;
|
|
19
|
-
finish(payload);
|
|
20
|
-
}, (err) => {
|
|
21
|
-
if (settled)
|
|
22
|
-
return;
|
|
23
|
-
settled = true;
|
|
24
|
-
fail(err);
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
catch (err) {
|
|
28
|
-
sendJson(res, 400, { error: err instanceof Error ? err.message : 'Invalid request.' }, options.webOrigin);
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
await listen(server);
|
|
32
|
-
const address = server.address();
|
|
33
|
-
if (!address || typeof address === 'string') {
|
|
34
|
-
await closeServer(server);
|
|
35
|
-
throw new Error('Could not bind local login callback server.');
|
|
36
|
-
}
|
|
37
|
-
const timeout = setTimeout(() => {
|
|
38
|
-
if (settled)
|
|
39
|
-
return;
|
|
40
|
-
settled = true;
|
|
41
|
-
fail(new Error('Timed out waiting for browser login.'));
|
|
42
|
-
void closeServer(server);
|
|
43
|
-
}, options.timeoutMs);
|
|
44
|
-
wait.finally(() => clearTimeout(timeout)).catch(() => undefined);
|
|
45
|
-
return {
|
|
46
|
-
callback: `http://127.0.0.1:${address.port}/callback`,
|
|
47
|
-
wait,
|
|
48
|
-
close: () => closeServer(server),
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
async function handleRequest(req, res, options, accept, reject) {
|
|
52
|
-
if (req.method === 'OPTIONS') {
|
|
53
|
-
handleOptions(req, res, options.webOrigin);
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
if (req.method !== 'POST' || req.url !== '/callback') {
|
|
57
|
-
sendJson(res, 404, { error: 'Not found.' }, options.webOrigin);
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
if (!hasAllowedOrigin(req, options.webOrigin)) {
|
|
61
|
-
sendJson(res, 403, { error: 'Origin is not allowed.' }, options.webOrigin);
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
const body = await readBody(req);
|
|
65
|
-
const payload = parseHandoff(JSON.parse(body));
|
|
66
|
-
if (payload.state !== options.state) {
|
|
67
|
-
sendJson(res, 403, { error: 'Login state did not match.' }, options.webOrigin);
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
accept(payload);
|
|
71
|
-
sendJson(res, 200, { ok: true }, options.webOrigin);
|
|
72
|
-
}
|
|
73
|
-
function handleOptions(req, res, origin) {
|
|
74
|
-
if (!hasAllowedOrigin(req, origin)) {
|
|
75
|
-
sendJson(res, 403, { error: 'Origin is not allowed.' }, origin);
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
writeCors(res, origin);
|
|
79
|
-
res.writeHead(204);
|
|
80
|
-
res.end();
|
|
81
|
-
}
|
|
82
|
-
function hasAllowedOrigin(req, origin) {
|
|
83
|
-
const actual = req.headers.origin;
|
|
84
|
-
return !actual || actual === origin;
|
|
85
|
-
}
|
|
86
|
-
function readBody(req) {
|
|
87
|
-
return new Promise((resolve, reject) => {
|
|
88
|
-
const chunks = [];
|
|
89
|
-
let size = 0;
|
|
90
|
-
req.on('data', (chunk) => {
|
|
91
|
-
size += chunk.length;
|
|
92
|
-
if (size > MAX_BODY_BYTES) {
|
|
93
|
-
reject(new Error('Login callback body is too large.'));
|
|
94
|
-
req.destroy();
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
chunks.push(chunk);
|
|
98
|
-
});
|
|
99
|
-
req.once('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
|
100
|
-
req.once('error', () => reject(new Error('Could not read login callback.')));
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
function sendJson(res, status, body, origin) {
|
|
104
|
-
if (res.writableEnded)
|
|
105
|
-
return;
|
|
106
|
-
writeCors(res, origin);
|
|
107
|
-
res.writeHead(status, { 'content-type': 'application/json; charset=utf-8' });
|
|
108
|
-
res.end(JSON.stringify(body));
|
|
109
|
-
}
|
|
110
|
-
function writeCors(res, origin) {
|
|
111
|
-
res.setHeader('access-control-allow-origin', origin);
|
|
112
|
-
res.setHeader('access-control-allow-methods', 'POST, OPTIONS');
|
|
113
|
-
res.setHeader('access-control-allow-headers', 'content-type');
|
|
114
|
-
}
|
|
115
|
-
function listen(server) {
|
|
116
|
-
return new Promise((resolve, reject) => {
|
|
117
|
-
server.once('error', reject);
|
|
118
|
-
server.listen(0, '127.0.0.1', () => {
|
|
119
|
-
server.removeListener('error', reject);
|
|
120
|
-
resolve();
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
function closeServer(server) {
|
|
125
|
-
if (!server.listening)
|
|
126
|
-
return Promise.resolve();
|
|
127
|
-
return new Promise((resolve, reject) => {
|
|
128
|
-
server.close((err) => {
|
|
129
|
-
if (err)
|
|
130
|
-
reject(err);
|
|
131
|
-
resolve();
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
}
|
package/dist/auth/schema.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
// Validation for the browser-to-CLI auth handoff payload.
|
|
2
|
-
export function parseHandoff(input) {
|
|
3
|
-
if (!isRecord(input))
|
|
4
|
-
throw new Error('Login callback payload must be an object.');
|
|
5
|
-
const state = stringField(input, 'state');
|
|
6
|
-
const token = stringField(input, 'token');
|
|
7
|
-
const label = nullableStringField(input, 'label');
|
|
8
|
-
const proxyUrl = stringField(input, 'proxyUrl');
|
|
9
|
-
if (!state)
|
|
10
|
-
throw new Error('Login callback state is missing.');
|
|
11
|
-
if (!token.startsWith('slv_live_'))
|
|
12
|
-
throw new Error('Login callback token is invalid.');
|
|
13
|
-
validateUrl(proxyUrl, 'Login callback proxy URL');
|
|
14
|
-
return { state, token, label, proxyUrl };
|
|
15
|
-
}
|
|
16
|
-
function stringField(input, key) {
|
|
17
|
-
const value = input[key];
|
|
18
|
-
if (typeof value !== 'string')
|
|
19
|
-
throw new Error(`${key} must be a string.`);
|
|
20
|
-
return value;
|
|
21
|
-
}
|
|
22
|
-
function nullableStringField(input, key) {
|
|
23
|
-
const value = input[key];
|
|
24
|
-
if (value === null || typeof value === 'undefined')
|
|
25
|
-
return null;
|
|
26
|
-
if (typeof value !== 'string')
|
|
27
|
-
throw new Error(`${key} must be a string or null.`);
|
|
28
|
-
return value;
|
|
29
|
-
}
|
|
30
|
-
function validateUrl(raw, name) {
|
|
31
|
-
const url = parseUrl(raw, name);
|
|
32
|
-
if (url.protocol !== 'https:' && url.protocol !== 'http:') {
|
|
33
|
-
throw new Error(`${name} must use http or https.`);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
function parseUrl(raw, name) {
|
|
37
|
-
try {
|
|
38
|
-
return new URL(raw);
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
throw new Error(`${name} must be a valid URL.`);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
function isRecord(input) {
|
|
45
|
-
return typeof input === 'object' && input !== null && !Array.isArray(input);
|
|
46
|
-
}
|
package/dist/auth/validation.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
// Auth validation owns Sleeve token checks against the proxy.
|
|
2
|
-
export async function validateAuth(auth) {
|
|
3
|
-
const base = trim(auth.proxyUrl);
|
|
4
|
-
try {
|
|
5
|
-
const res = await fetch(`${base}/messages`, {
|
|
6
|
-
method: 'POST',
|
|
7
|
-
headers: {
|
|
8
|
-
'content-type': 'application/json',
|
|
9
|
-
'sleeve-token': auth.token,
|
|
10
|
-
'sleeve-provider': 'anthropic',
|
|
11
|
-
'sleeve-harness': 'claude code',
|
|
12
|
-
},
|
|
13
|
-
body: '{}',
|
|
14
|
-
});
|
|
15
|
-
if (res.status === 401)
|
|
16
|
-
return 'invalid';
|
|
17
|
-
if (res.status === 400)
|
|
18
|
-
return 'valid';
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
return 'unknown';
|
|
22
|
-
}
|
|
23
|
-
return 'unknown';
|
|
24
|
-
}
|
|
25
|
-
function trim(url) {
|
|
26
|
-
return url.replace(/\/+$/, '');
|
|
27
|
-
}
|
package/dist/cli.js
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Thin command dispatcher for the `sleev` executable.
|
|
3
|
-
import { existsSync, realpathSync } from 'node:fs';
|
|
4
|
-
import { pathToFileURL } from 'node:url';
|
|
5
|
-
import { loginCommand, logoutCommand } from './auth/command.js';
|
|
6
|
-
import { failure } from './terminal.js';
|
|
7
|
-
const HELP = `sleev\n\nUsage:\n sleev login [--force]\n sleev install [--force]\n sleev auth login [--force]\n sleev auth <command>\n\nCommands:\n login Open the Sleev web app when needed and store a local auth token\n install Alias for login\n auth login Open the Sleev web app when needed and store a local auth token\n auth logout Delete the local auth file\n`;
|
|
8
|
-
const LOGIN_HELP = `Usage:\n sleev login [--force]\n sleev install [--force]\n sleev auth login [--force]\n`;
|
|
9
|
-
const AUTH_HELP = `Usage:\n sleev auth login [--force]\n sleev auth logout\n`;
|
|
10
|
-
export async function main(argv = process.argv.slice(2)) {
|
|
11
|
-
if (argv.length === 0 || argv[0] === '--help' || argv[0] === '-h') {
|
|
12
|
-
process.stdout.write(HELP);
|
|
13
|
-
return 0;
|
|
14
|
-
}
|
|
15
|
-
if (argv[0] === 'login' || argv[0] === 'install') {
|
|
16
|
-
if (argv[1] === '--help' || argv[1] === '-h') {
|
|
17
|
-
process.stdout.write(LOGIN_HELP);
|
|
18
|
-
return 0;
|
|
19
|
-
}
|
|
20
|
-
return login(argv[0], argv.slice(1), LOGIN_HELP);
|
|
21
|
-
}
|
|
22
|
-
if (argv[0] !== 'auth') {
|
|
23
|
-
process.stderr.write(`Unknown command: ${argv[0]}\n\n${HELP}`);
|
|
24
|
-
return 1;
|
|
25
|
-
}
|
|
26
|
-
if (argv[1] === '--help' || argv[1] === '-h') {
|
|
27
|
-
process.stdout.write(AUTH_HELP);
|
|
28
|
-
return 0;
|
|
29
|
-
}
|
|
30
|
-
const command = argv[1];
|
|
31
|
-
if (command !== 'login' && command !== 'logout') {
|
|
32
|
-
process.stderr.write(`Unknown auth command: ${command ?? ''}\n\n${AUTH_HELP}`);
|
|
33
|
-
return 1;
|
|
34
|
-
}
|
|
35
|
-
if (command === 'logout') {
|
|
36
|
-
if (argv.length !== 2)
|
|
37
|
-
return usageError(`auth ${command} does not accept flags yet.`, AUTH_HELP);
|
|
38
|
-
return logoutCommand();
|
|
39
|
-
}
|
|
40
|
-
return login(`auth ${command}`, argv.slice(2), AUTH_HELP);
|
|
41
|
-
}
|
|
42
|
-
function login(command, flags, help) {
|
|
43
|
-
if (flags.some((flag) => flag !== '--force'))
|
|
44
|
-
return usageError(`Unknown ${command} flag.`, help);
|
|
45
|
-
return loginCommand({ force: flags.includes('--force') });
|
|
46
|
-
}
|
|
47
|
-
function usageError(message, help) {
|
|
48
|
-
process.stderr.write(`${message}\n\n${help}`);
|
|
49
|
-
return 1;
|
|
50
|
-
}
|
|
51
|
-
if (isEntry()) {
|
|
52
|
-
main().then((code) => {
|
|
53
|
-
process.exitCode = code;
|
|
54
|
-
}, (err) => {
|
|
55
|
-
const message = err instanceof Error ? err.message : 'Command failed.';
|
|
56
|
-
failure(process.stderr, message);
|
|
57
|
-
process.exitCode = 1;
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
function isEntry() {
|
|
61
|
-
const entry = process.argv[1];
|
|
62
|
-
if (!entry)
|
|
63
|
-
return false;
|
|
64
|
-
if (import.meta.url === pathToFileURL(entry).href)
|
|
65
|
-
return true;
|
|
66
|
-
if (!existsSync(entry))
|
|
67
|
-
return false;
|
|
68
|
-
return realpathSync(new URL(import.meta.url)) === realpathSync(entry);
|
|
69
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
// Validation for the on-disk Sleev auth file.
|
|
2
|
-
export function createAuthFile(input) {
|
|
3
|
-
const parsed = parseAuthInput(input);
|
|
4
|
-
return { version: 1, ...parsed };
|
|
5
|
-
}
|
|
6
|
-
export function parseAuthInput(input) {
|
|
7
|
-
if (!isRecord(input))
|
|
8
|
-
throw new Error('Sleev auth input must be an object.');
|
|
9
|
-
return parseAuthRecord(input);
|
|
10
|
-
}
|
|
11
|
-
export function parseAuthFile(input) {
|
|
12
|
-
if (!isRecord(input))
|
|
13
|
-
throw new Error('Sleev auth file must be an object.');
|
|
14
|
-
const version = input.version;
|
|
15
|
-
if (version !== 1)
|
|
16
|
-
throw new Error('Sleev auth file version is unsupported.');
|
|
17
|
-
return { version, ...parseAuthInput(input) };
|
|
18
|
-
}
|
|
19
|
-
function parseAuthRecord(input) {
|
|
20
|
-
if (!isRecord(input))
|
|
21
|
-
throw new Error('Sleev auth input must be an object.');
|
|
22
|
-
const token = stringField(input, 'token');
|
|
23
|
-
const label = nullableStringField(input, 'label');
|
|
24
|
-
const proxyUrl = stringField(input, 'proxyUrl');
|
|
25
|
-
const createdAt = stringField(input, 'createdAt');
|
|
26
|
-
if (!token.startsWith('slv_live_'))
|
|
27
|
-
throw new Error('Sleev auth token is invalid.');
|
|
28
|
-
validateUrl(proxyUrl, 'Sleev auth proxy URL');
|
|
29
|
-
if (Number.isNaN(Date.parse(createdAt)))
|
|
30
|
-
throw new Error('Sleev auth createdAt is invalid.');
|
|
31
|
-
return { token, label, proxyUrl, createdAt };
|
|
32
|
-
}
|
|
33
|
-
function stringField(input, key) {
|
|
34
|
-
const value = input[key];
|
|
35
|
-
if (typeof value !== 'string')
|
|
36
|
-
throw new Error(`${key} must be a string.`);
|
|
37
|
-
return value;
|
|
38
|
-
}
|
|
39
|
-
function nullableStringField(input, key) {
|
|
40
|
-
const value = input[key];
|
|
41
|
-
if (value === null || typeof value === 'undefined')
|
|
42
|
-
return null;
|
|
43
|
-
if (typeof value !== 'string')
|
|
44
|
-
throw new Error(`${key} must be a string or null.`);
|
|
45
|
-
return value;
|
|
46
|
-
}
|
|
47
|
-
function validateUrl(raw, name) {
|
|
48
|
-
const url = parseUrl(raw, name);
|
|
49
|
-
if (url.protocol !== 'https:' && url.protocol !== 'http:') {
|
|
50
|
-
throw new Error(`${name} must use http or https.`);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
function parseUrl(raw, name) {
|
|
54
|
-
try {
|
|
55
|
-
return new URL(raw);
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
throw new Error(`${name} must be a valid URL.`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
function isRecord(input) {
|
|
62
|
-
return typeof input === 'object' && input !== null && !Array.isArray(input);
|
|
63
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
// Credential storage owns auth.json path resolution, validation, and safe writes.
|
|
2
|
-
import { chmod, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
3
|
-
import { dirname, join } from 'node:path';
|
|
4
|
-
import { homedir } from 'node:os';
|
|
5
|
-
import { parseAuthFile } from './schema.js';
|
|
6
|
-
export function authPath(env = process.env, platform = process.platform) {
|
|
7
|
-
return join(configRoot(env, platform), 'sleev', 'auth.json');
|
|
8
|
-
}
|
|
9
|
-
export async function readAuth(path = authPath()) {
|
|
10
|
-
try {
|
|
11
|
-
const raw = await readFile(path, 'utf8');
|
|
12
|
-
return parseAuthFile(JSON.parse(raw));
|
|
13
|
-
}
|
|
14
|
-
catch (err) {
|
|
15
|
-
if (errorCode(err) === 'ENOENT')
|
|
16
|
-
return null;
|
|
17
|
-
throw err;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
export async function writeAuth(auth, path = authPath()) {
|
|
21
|
-
const checked = parseAuthFile(auth);
|
|
22
|
-
const dir = dirname(path);
|
|
23
|
-
const tmp = join(dir, `.auth.${process.pid}.${Date.now()}.tmp`);
|
|
24
|
-
const body = `${JSON.stringify(checked, null, 2)}\n`;
|
|
25
|
-
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
26
|
-
await writeFile(tmp, body, { mode: 0o600 });
|
|
27
|
-
await rename(tmp, path);
|
|
28
|
-
await chmod(path, 0o600);
|
|
29
|
-
return { path };
|
|
30
|
-
}
|
|
31
|
-
export async function clearAuth(path = authPath()) {
|
|
32
|
-
try {
|
|
33
|
-
await rm(path);
|
|
34
|
-
}
|
|
35
|
-
catch (err) {
|
|
36
|
-
if (errorCode(err) === 'ENOENT')
|
|
37
|
-
return;
|
|
38
|
-
throw err;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
function configRoot(env, platform) {
|
|
42
|
-
if (platform === 'win32') {
|
|
43
|
-
return env.APPDATA || join(homedir(), 'AppData', 'Roaming');
|
|
44
|
-
}
|
|
45
|
-
return env.XDG_CONFIG_HOME || join(homedir(), '.config');
|
|
46
|
-
}
|
|
47
|
-
function errorCode(err) {
|
|
48
|
-
if (typeof err !== 'object' || err === null || !('code' in err))
|
|
49
|
-
return undefined;
|
|
50
|
-
const code = err.code;
|
|
51
|
-
return typeof code === 'string' ? code : undefined;
|
|
52
|
-
}
|
package/dist/harnesses.js
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
// Harness orchestration owns setup and cleanup across supported local tools.
|
|
2
|
-
import { setupClaude, unpatchClaude } from './apps/claude.js';
|
|
3
|
-
import { setupCodex, unpatchCodex } from './apps/codex.js';
|
|
4
|
-
import { availableOpenCodeProviders, setupOpenCode, unpatchOpenCode } from './apps/opencode.js';
|
|
5
|
-
export const HARNESSES = [
|
|
6
|
-
{
|
|
7
|
-
id: 'opencode',
|
|
8
|
-
label: 'opencode',
|
|
9
|
-
command: 'opencode',
|
|
10
|
-
available: async () => {
|
|
11
|
-
try {
|
|
12
|
-
return (await availableOpenCodeProviders()).length > 0;
|
|
13
|
-
}
|
|
14
|
-
catch {
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
},
|
|
18
|
-
setup: setupOpenCode,
|
|
19
|
-
unpatch: unpatchOpenCode,
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
id: 'claude-code',
|
|
23
|
-
label: 'claude code',
|
|
24
|
-
command: 'claude',
|
|
25
|
-
setup: setupClaude,
|
|
26
|
-
unpatch: unpatchClaude,
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
id: 'codex',
|
|
30
|
-
label: 'codex',
|
|
31
|
-
command: 'codex',
|
|
32
|
-
setup: setupCodex,
|
|
33
|
-
unpatch: unpatchCodex,
|
|
34
|
-
},
|
|
35
|
-
];
|
|
36
|
-
export async function setupHarnesses(auth, apps) {
|
|
37
|
-
for (const harness of HARNESSES) {
|
|
38
|
-
if (!apps.includes(harness.id))
|
|
39
|
-
continue;
|
|
40
|
-
await harness.setup(auth);
|
|
41
|
-
}
|
|
42
|
-
return { label: harnessLabels(apps) };
|
|
43
|
-
}
|
|
44
|
-
export async function unpatchHarnesses() {
|
|
45
|
-
const failures = [];
|
|
46
|
-
await Promise.all(HARNESSES.map(async (harness) => {
|
|
47
|
-
try {
|
|
48
|
-
await harness.unpatch();
|
|
49
|
-
}
|
|
50
|
-
catch (err) {
|
|
51
|
-
failures.push(`${harness.label}: ${message(err)}`);
|
|
52
|
-
}
|
|
53
|
-
}));
|
|
54
|
-
return { failures };
|
|
55
|
-
}
|
|
56
|
-
export function harnessLabels(ids) {
|
|
57
|
-
if (ids.length === 0)
|
|
58
|
-
return 'none';
|
|
59
|
-
const labels = ids.map((id) => HARNESSES.find((harness) => harness.id === id)?.label ?? id);
|
|
60
|
-
return labels.join(', ');
|
|
61
|
-
}
|
|
62
|
-
function message(err) {
|
|
63
|
-
if (err instanceof Error)
|
|
64
|
-
return err.message;
|
|
65
|
-
return String(err);
|
|
66
|
-
}
|
package/dist/index.js
DELETED
package/dist/product.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
// Product defaults and environment escape hatches for local development.
|
|
2
|
-
const DEFAULT_WEB_ORIGIN = 'https://sleev.ai';
|
|
3
|
-
const DEFAULT_PROXY_URL = 'https://api.sleev.ai';
|
|
4
|
-
export function webOrigin(env = process.env) {
|
|
5
|
-
const raw = env.SLEEV_WEB_ORIGIN?.trim();
|
|
6
|
-
if (!raw)
|
|
7
|
-
return DEFAULT_WEB_ORIGIN;
|
|
8
|
-
const url = parseUrl(raw, 'SLEEV_WEB_ORIGIN');
|
|
9
|
-
if (url.protocol !== 'https:' && url.protocol !== 'http:') {
|
|
10
|
-
throw new Error('SLEEV_WEB_ORIGIN must use http or https.');
|
|
11
|
-
}
|
|
12
|
-
if (url.pathname !== '/' || url.search || url.hash) {
|
|
13
|
-
throw new Error('SLEEV_WEB_ORIGIN must be an origin without a path.');
|
|
14
|
-
}
|
|
15
|
-
return url.origin;
|
|
16
|
-
}
|
|
17
|
-
export function proxyUrl() {
|
|
18
|
-
return DEFAULT_PROXY_URL;
|
|
19
|
-
}
|
|
20
|
-
function parseUrl(raw, name) {
|
|
21
|
-
try {
|
|
22
|
-
return new URL(raw);
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
throw new Error(`${name} must be a valid URL.`);
|
|
26
|
-
}
|
|
27
|
-
}
|