oversky 0.1.0
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/dist/auth.d.ts +26 -0
- package/dist/auth.js +221 -0
- package/dist/auth.js.map +1 -0
- package/dist/config.d.ts +20 -0
- package/dist/config.js +52 -0
- package/dist/config.js.map +1 -0
- package/dist/connection.d.ts +14 -0
- package/dist/connection.js +93 -0
- package/dist/connection.js.map +1 -0
- package/dist/executor.d.ts +20 -0
- package/dist/executor.js +187 -0
- package/dist/executor.js.map +1 -0
- package/dist/heartbeat.d.ts +10 -0
- package/dist/heartbeat.js +27 -0
- package/dist/heartbeat.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +172 -0
- package/dist/index.js.map +1 -0
- package/dist/permissions.d.ts +17 -0
- package/dist/permissions.js +60 -0
- package/dist/permissions.js.map +1 -0
- package/dist/registry.d.ts +10 -0
- package/dist/registry.js +23 -0
- package/dist/registry.js.map +1 -0
- package/dist/security.d.ts +18 -0
- package/dist/security.js +181 -0
- package/dist/security.js.map +1 -0
- package/dist/tools/bash.d.ts +15 -0
- package/dist/tools/bash.js +58 -0
- package/dist/tools/bash.js.map +1 -0
- package/dist/tools/edit.d.ts +19 -0
- package/dist/tools/edit.js +49 -0
- package/dist/tools/edit.js.map +1 -0
- package/dist/tools/glob.d.ts +13 -0
- package/dist/tools/glob.js +34 -0
- package/dist/tools/glob.js.map +1 -0
- package/dist/tools/grep.d.ts +21 -0
- package/dist/tools/grep.js +95 -0
- package/dist/tools/grep.js.map +1 -0
- package/dist/tools/read.d.ts +14 -0
- package/dist/tools/read.js +31 -0
- package/dist/tools/read.js.map +1 -0
- package/dist/tools/write.d.ts +14 -0
- package/dist/tools/write.js +47 -0
- package/dist/tools/write.js.map +1 -0
- package/dist/ui.d.ts +12 -0
- package/dist/ui.js +77 -0
- package/dist/ui.js.map +1 -0
- package/package.json +67 -0
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare function getToken(): string | null;
|
|
2
|
+
export declare function saveToken(token: string, expiresAt?: number): void;
|
|
3
|
+
export declare function removeToken(): void;
|
|
4
|
+
/**
|
|
5
|
+
* Start a local HTTP server to receive the OAuth callback token.
|
|
6
|
+
* Opens the browser to the server's daemon auth page, which redirects
|
|
7
|
+
* back to localhost with the JWT.
|
|
8
|
+
*/
|
|
9
|
+
export declare function loginFlow(serverUrl: string): Promise<string>;
|
|
10
|
+
/**
|
|
11
|
+
* Prompt the user for a token directly in the terminal (fallback).
|
|
12
|
+
*/
|
|
13
|
+
export declare function loginPrompt(): Promise<string>;
|
|
14
|
+
/**
|
|
15
|
+
* Device authorization flow — like `gh auth login`.
|
|
16
|
+
* 1. Request a one-time device code from the server
|
|
17
|
+
* 2. Print a URL for the user to open in their browser
|
|
18
|
+
* 3. Poll until the user approves the code in the browser
|
|
19
|
+
* 4. Receive and save the daemon JWT
|
|
20
|
+
*/
|
|
21
|
+
export declare function loginWithUrl(serverUrl: string): Promise<string>;
|
|
22
|
+
/**
|
|
23
|
+
* Login via email/password against the API's /api/auth/login endpoint.
|
|
24
|
+
* Works with local dev servers without needing the /auth/daemon OAuth route.
|
|
25
|
+
*/
|
|
26
|
+
export declare function loginWithCredentials(serverUrl: string): Promise<string>;
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import http from 'node:http';
|
|
4
|
+
import { exec } from 'node:child_process';
|
|
5
|
+
import { createInterface } from 'node:readline';
|
|
6
|
+
import { getConfigDir } from './config.js';
|
|
7
|
+
const AUTH_PATH = path.join(getConfigDir(), 'auth.json');
|
|
8
|
+
export function getToken() {
|
|
9
|
+
try {
|
|
10
|
+
if (fs.existsSync(AUTH_PATH)) {
|
|
11
|
+
const raw = fs.readFileSync(AUTH_PATH, 'utf-8');
|
|
12
|
+
const data = JSON.parse(raw);
|
|
13
|
+
if (data.expiresAt && Date.now() > data.expiresAt) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
return data.token || null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// Fall through
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
export function saveToken(token, expiresAt) {
|
|
25
|
+
const dir = getConfigDir();
|
|
26
|
+
if (!fs.existsSync(dir)) {
|
|
27
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
const data = { token };
|
|
30
|
+
if (expiresAt) {
|
|
31
|
+
data.expiresAt = expiresAt;
|
|
32
|
+
}
|
|
33
|
+
fs.writeFileSync(AUTH_PATH, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
34
|
+
fs.chmodSync(AUTH_PATH, 0o600);
|
|
35
|
+
}
|
|
36
|
+
export function removeToken() {
|
|
37
|
+
try {
|
|
38
|
+
if (fs.existsSync(AUTH_PATH)) {
|
|
39
|
+
fs.unlinkSync(AUTH_PATH);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Ignore
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Start a local HTTP server to receive the OAuth callback token.
|
|
48
|
+
* Opens the browser to the server's daemon auth page, which redirects
|
|
49
|
+
* back to localhost with the JWT.
|
|
50
|
+
*/
|
|
51
|
+
export async function loginFlow(serverUrl) {
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
const server = http.createServer((req, res) => {
|
|
54
|
+
const url = new URL(req.url || '/', `http://localhost`);
|
|
55
|
+
const token = url.searchParams.get('token');
|
|
56
|
+
const expiresAt = url.searchParams.get('expiresAt');
|
|
57
|
+
if (token) {
|
|
58
|
+
saveToken(token, expiresAt ? parseInt(expiresAt, 10) : undefined);
|
|
59
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
60
|
+
res.end('<html><body><h2>Login successful! You can close this tab.</h2></body></html>');
|
|
61
|
+
server.close();
|
|
62
|
+
resolve(token);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
66
|
+
res.end('Missing token');
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
server.listen(0, '127.0.0.1', () => {
|
|
70
|
+
const addr = server.address();
|
|
71
|
+
if (!addr || typeof addr === 'string') {
|
|
72
|
+
server.close();
|
|
73
|
+
reject(new Error('Failed to start callback server'));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const callbackUrl = `http://127.0.0.1:${addr.port}/callback`;
|
|
77
|
+
const loginUrl = `${serverUrl}/auth/daemon?callback=${encodeURIComponent(callbackUrl)}`;
|
|
78
|
+
console.log(`\nOpen this URL in your browser to log in:\n ${loginUrl}\n`);
|
|
79
|
+
// Try to open browser
|
|
80
|
+
const openCmd = process.platform === 'darwin'
|
|
81
|
+
? 'open'
|
|
82
|
+
: process.platform === 'win32'
|
|
83
|
+
? 'start'
|
|
84
|
+
: 'xdg-open';
|
|
85
|
+
exec(`${openCmd} "${loginUrl}"`, () => {
|
|
86
|
+
// Ignore errors - user can open manually
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
// Timeout after 5 minutes
|
|
90
|
+
setTimeout(() => {
|
|
91
|
+
server.close();
|
|
92
|
+
reject(new Error('Login timed out after 5 minutes'));
|
|
93
|
+
}, 300_000);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Prompt the user for a token directly in the terminal (fallback).
|
|
98
|
+
*/
|
|
99
|
+
export async function loginPrompt() {
|
|
100
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
101
|
+
return new Promise((resolve) => {
|
|
102
|
+
rl.question('Paste your JWT token: ', (token) => {
|
|
103
|
+
rl.close();
|
|
104
|
+
if (token.trim()) {
|
|
105
|
+
saveToken(token.trim());
|
|
106
|
+
resolve(token.trim());
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
resolve('');
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Device authorization flow — like `gh auth login`.
|
|
116
|
+
* 1. Request a one-time device code from the server
|
|
117
|
+
* 2. Print a URL for the user to open in their browser
|
|
118
|
+
* 3. Poll until the user approves the code in the browser
|
|
119
|
+
* 4. Receive and save the daemon JWT
|
|
120
|
+
*/
|
|
121
|
+
export async function loginWithUrl(serverUrl) {
|
|
122
|
+
// Step 1: Get a device code
|
|
123
|
+
const codeRes = await fetch(`${serverUrl}/api/daemons/device-code`, {
|
|
124
|
+
method: 'POST',
|
|
125
|
+
headers: { 'Content-Type': 'application/json' },
|
|
126
|
+
});
|
|
127
|
+
if (!codeRes.ok) {
|
|
128
|
+
const body = await codeRes.text();
|
|
129
|
+
throw new Error(`Failed to get device code (${codeRes.status}): ${body}`);
|
|
130
|
+
}
|
|
131
|
+
const { code, verificationUrl, expiresIn } = await codeRes.json();
|
|
132
|
+
// Step 2: Show the URL to the user
|
|
133
|
+
console.log('');
|
|
134
|
+
console.log(` Open this URL in your browser to log in:\n`);
|
|
135
|
+
console.log(` ${verificationUrl}\n`);
|
|
136
|
+
console.log(` Or go to ${serverUrl}/login/device and enter code: ${code}\n`);
|
|
137
|
+
console.log(` Waiting for approval...`);
|
|
138
|
+
// Try to open browser automatically
|
|
139
|
+
const openCmd = process.platform === 'darwin'
|
|
140
|
+
? 'open'
|
|
141
|
+
: process.platform === 'win32'
|
|
142
|
+
? 'start'
|
|
143
|
+
: 'xdg-open';
|
|
144
|
+
exec(`${openCmd} "${verificationUrl}"`, () => { });
|
|
145
|
+
// Step 3: Poll for approval
|
|
146
|
+
const pollInterval = 2000; // 2s
|
|
147
|
+
const deadline = Date.now() + expiresIn * 1000;
|
|
148
|
+
while (Date.now() < deadline) {
|
|
149
|
+
await new Promise((r) => setTimeout(r, pollInterval));
|
|
150
|
+
try {
|
|
151
|
+
const statusRes = await fetch(`${serverUrl}/api/daemons/device-code/${encodeURIComponent(code)}/status`);
|
|
152
|
+
if (!statusRes.ok) {
|
|
153
|
+
if (statusRes.status === 404) {
|
|
154
|
+
throw new Error('Device code expired. Please try again.');
|
|
155
|
+
}
|
|
156
|
+
continue; // Transient error, keep polling
|
|
157
|
+
}
|
|
158
|
+
const data = await statusRes.json();
|
|
159
|
+
if (data.status === 'approved' && data.token) {
|
|
160
|
+
const expiresAt = data.expiresAt ? new Date(data.expiresAt).getTime() : undefined;
|
|
161
|
+
saveToken(data.token, expiresAt);
|
|
162
|
+
return data.token;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
if (err instanceof Error && err.message.includes('expired')) {
|
|
167
|
+
throw err;
|
|
168
|
+
}
|
|
169
|
+
// Transient network error, keep polling
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
throw new Error('Login timed out. Please try again.');
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Login via email/password against the API's /api/auth/login endpoint.
|
|
176
|
+
* Works with local dev servers without needing the /auth/daemon OAuth route.
|
|
177
|
+
*/
|
|
178
|
+
export async function loginWithCredentials(serverUrl) {
|
|
179
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
180
|
+
const ask = (question) => new Promise((resolve) => rl.question(question, resolve));
|
|
181
|
+
try {
|
|
182
|
+
const email = await ask(' Email: ');
|
|
183
|
+
const password = await ask(' Password: ');
|
|
184
|
+
rl.close();
|
|
185
|
+
console.log(' Authenticating...');
|
|
186
|
+
const res = await fetch(`${serverUrl}/api/auth/login`, {
|
|
187
|
+
method: 'POST',
|
|
188
|
+
headers: { 'Content-Type': 'application/json' },
|
|
189
|
+
body: JSON.stringify({ email: email.trim(), password: password.trim() }),
|
|
190
|
+
});
|
|
191
|
+
if (!res.ok) {
|
|
192
|
+
const body = await res.text();
|
|
193
|
+
throw new Error(`Login failed (${res.status}): ${body}`);
|
|
194
|
+
}
|
|
195
|
+
const data = await res.json();
|
|
196
|
+
if (!data.token) {
|
|
197
|
+
throw new Error('No token in login response');
|
|
198
|
+
}
|
|
199
|
+
// Now use the session token to get a long-lived daemon token
|
|
200
|
+
const daemonRes = await fetch(`${serverUrl}/api/daemons/token`, {
|
|
201
|
+
method: 'POST',
|
|
202
|
+
headers: {
|
|
203
|
+
'Content-Type': 'application/json',
|
|
204
|
+
Authorization: `Bearer ${data.token}`,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
if (daemonRes.ok) {
|
|
208
|
+
const daemonData = await daemonRes.json();
|
|
209
|
+
saveToken(daemonData.token, new Date(daemonData.expiresAt).getTime());
|
|
210
|
+
return daemonData.token;
|
|
211
|
+
}
|
|
212
|
+
// Fallback: use the session token directly if daemon token endpoint fails
|
|
213
|
+
saveToken(data.token);
|
|
214
|
+
return data.token;
|
|
215
|
+
}
|
|
216
|
+
catch (err) {
|
|
217
|
+
rl.close();
|
|
218
|
+
throw err;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface PermissionsConfig {
|
|
2
|
+
autoApproveReads: boolean;
|
|
3
|
+
autoApproveWrites: boolean;
|
|
4
|
+
autoApproveBash: boolean;
|
|
5
|
+
blockedPaths: string[];
|
|
6
|
+
allowedWritePaths: string[];
|
|
7
|
+
}
|
|
8
|
+
export interface LimitsConfig {
|
|
9
|
+
maxFileReadSize: number;
|
|
10
|
+
bashTimeout: number;
|
|
11
|
+
maxConcurrentTools: number;
|
|
12
|
+
}
|
|
13
|
+
export interface DaemonConfig {
|
|
14
|
+
serverUrl: string;
|
|
15
|
+
permissions: PermissionsConfig;
|
|
16
|
+
limits: LimitsConfig;
|
|
17
|
+
}
|
|
18
|
+
export declare function loadConfig(): DaemonConfig;
|
|
19
|
+
export declare function saveConfig(config: DaemonConfig): void;
|
|
20
|
+
export declare function getConfigDir(): string;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
const CONFIG_DIR = path.join(os.homedir(), '.oversky');
|
|
5
|
+
const CONFIG_PATH = path.join(CONFIG_DIR, 'daemon.json');
|
|
6
|
+
const DEFAULT_CONFIG = {
|
|
7
|
+
// TODO: Change to https://oversky.ai once custom domain is configured
|
|
8
|
+
serverUrl: 'https://desxylhmg82bz.cloudfront.net',
|
|
9
|
+
permissions: {
|
|
10
|
+
autoApproveReads: true,
|
|
11
|
+
autoApproveWrites: false,
|
|
12
|
+
autoApproveBash: false,
|
|
13
|
+
blockedPaths: ['.env', '.env.*', '*.pem', '*.key', '.ssh/'],
|
|
14
|
+
allowedWritePaths: ['src/**', 'tests/**'],
|
|
15
|
+
},
|
|
16
|
+
limits: {
|
|
17
|
+
maxFileReadSize: 524288, // 500KB
|
|
18
|
+
bashTimeout: 120000, // 120s
|
|
19
|
+
maxConcurrentTools: 5,
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
function ensureConfigDir() {
|
|
23
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
24
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function loadConfig() {
|
|
28
|
+
try {
|
|
29
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
30
|
+
const raw = fs.readFileSync(CONFIG_PATH, 'utf-8');
|
|
31
|
+
const parsed = JSON.parse(raw);
|
|
32
|
+
return {
|
|
33
|
+
...DEFAULT_CONFIG,
|
|
34
|
+
...parsed,
|
|
35
|
+
permissions: { ...DEFAULT_CONFIG.permissions, ...parsed.permissions },
|
|
36
|
+
limits: { ...DEFAULT_CONFIG.limits, ...parsed.limits },
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Fall through to default
|
|
42
|
+
}
|
|
43
|
+
return { ...DEFAULT_CONFIG };
|
|
44
|
+
}
|
|
45
|
+
export function saveConfig(config) {
|
|
46
|
+
ensureConfigDir();
|
|
47
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
48
|
+
}
|
|
49
|
+
export function getConfigDir() {
|
|
50
|
+
return CONFIG_DIR;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AAsBzB,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,UAAU,CAAC,CAAC;AACvD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AAEzD,MAAM,cAAc,GAAiB;IACnC,sEAAsE;IACtE,SAAS,EAAE,sCAAsC;IACjD,WAAW,EAAE;QACX,gBAAgB,EAAE,IAAI;QACtB,iBAAiB,EAAE,KAAK;QACxB,eAAe,EAAE,KAAK;QACtB,YAAY,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;QAC3D,iBAAiB,EAAE,CAAC,QAAQ,EAAE,UAAU,CAAC;KAC1C;IACD,MAAM,EAAE;QACN,eAAe,EAAE,MAAM,EAAE,QAAQ;QACjC,WAAW,EAAE,MAAM,EAAE,OAAO;QAC5B,kBAAkB,EAAE,CAAC;KACtB;CACF,CAAC;AAEF,SAAS,eAAe;IACtB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,IAAI,CAAC;QACH,IAAI,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YAC/B,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;YAClD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC/B,OAAO;gBACL,GAAG,cAAc;gBACjB,GAAG,MAAM;gBACT,WAAW,EAAE,EAAE,GAAG,cAAc,CAAC,WAAW,EAAE,GAAG,MAAM,CAAC,WAAW,EAAE;gBACrE,MAAM,EAAE,EAAE,GAAG,cAAc,CAAC,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE;aACvD,CAAC;QACJ,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,0BAA0B;IAC5B,CAAC;IACD,OAAO,EAAE,GAAG,cAAc,EAAE,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAoB;IAC7C,eAAe,EAAE,CAAC;IAClB,EAAE,CAAC,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;AACjF,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,OAAO,UAAU,CAAC;AACpB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Socket } from 'socket.io-client';
|
|
2
|
+
import { type DaemonConfig } from './config.js';
|
|
3
|
+
export interface DaemonConnection {
|
|
4
|
+
socket: Socket;
|
|
5
|
+
daemonId: string;
|
|
6
|
+
disconnect: () => void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Connect to the OverSky server and register as a daemon.
|
|
10
|
+
* Handles reconnection with exponential backoff, heartbeat, and tool dispatch.
|
|
11
|
+
*
|
|
12
|
+
* Exported for library use (e.g., embedding in a desktop app).
|
|
13
|
+
*/
|
|
14
|
+
export declare function connectDaemon(serverUrl: string, token: string, cwd: string, config: DaemonConfig): DaemonConnection;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { io } from 'socket.io-client';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import { generateDaemonId, getDeviceName } from './registry.js';
|
|
4
|
+
import { startHeartbeat, stopHeartbeat } from './heartbeat.js';
|
|
5
|
+
import { executeToolRequest } from './executor.js';
|
|
6
|
+
import { showConnecting, showConnected, showDisconnected, showReconnecting, showError, showInfo, } from './ui.js';
|
|
7
|
+
const SUPPORTED_TOOLS = ['bash', 'read', 'write', 'edit', 'glob', 'grep'];
|
|
8
|
+
const VERSION = '0.1.0';
|
|
9
|
+
/**
|
|
10
|
+
* Connect to the OverSky server and register as a daemon.
|
|
11
|
+
* Handles reconnection with exponential backoff, heartbeat, and tool dispatch.
|
|
12
|
+
*
|
|
13
|
+
* Exported for library use (e.g., embedding in a desktop app).
|
|
14
|
+
*/
|
|
15
|
+
export function connectDaemon(serverUrl, token, cwd, config) {
|
|
16
|
+
const daemonId = generateDaemonId(cwd);
|
|
17
|
+
const deviceName = getDeviceName();
|
|
18
|
+
showConnecting(serverUrl);
|
|
19
|
+
const socket = io(serverUrl, {
|
|
20
|
+
path: '/ws/agentic',
|
|
21
|
+
auth: { token },
|
|
22
|
+
transports: ['websocket', 'polling'],
|
|
23
|
+
reconnection: true,
|
|
24
|
+
reconnectionDelay: 1000,
|
|
25
|
+
reconnectionDelayMax: 5000,
|
|
26
|
+
reconnectionAttempts: 50,
|
|
27
|
+
});
|
|
28
|
+
// --- Connection lifecycle ---
|
|
29
|
+
socket.on('connect', () => {
|
|
30
|
+
showConnected();
|
|
31
|
+
// Register with the server
|
|
32
|
+
socket.emit('daemon:register', {
|
|
33
|
+
daemonId,
|
|
34
|
+
deviceName,
|
|
35
|
+
capabilities: SUPPORTED_TOOLS,
|
|
36
|
+
cwd,
|
|
37
|
+
platform: os.platform(),
|
|
38
|
+
arch: os.arch(),
|
|
39
|
+
version: VERSION,
|
|
40
|
+
nodeVersion: process.version,
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
socket.on('daemon:registered', (data) => {
|
|
44
|
+
showInfo(`Registered as ${data.daemonId}`);
|
|
45
|
+
startHeartbeat(socket, daemonId);
|
|
46
|
+
});
|
|
47
|
+
socket.on('disconnect', (reason) => {
|
|
48
|
+
stopHeartbeat();
|
|
49
|
+
showDisconnected(reason);
|
|
50
|
+
});
|
|
51
|
+
socket.io.on('reconnect_attempt', (attempt) => {
|
|
52
|
+
showReconnecting(attempt);
|
|
53
|
+
});
|
|
54
|
+
socket.io.on('reconnect', () => {
|
|
55
|
+
showConnected();
|
|
56
|
+
// Re-register after reconnection
|
|
57
|
+
socket.emit('daemon:register', {
|
|
58
|
+
daemonId,
|
|
59
|
+
deviceName,
|
|
60
|
+
capabilities: SUPPORTED_TOOLS,
|
|
61
|
+
cwd,
|
|
62
|
+
platform: os.platform(),
|
|
63
|
+
arch: os.arch(),
|
|
64
|
+
version: VERSION,
|
|
65
|
+
nodeVersion: process.version,
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
socket.io.on('reconnect_failed', () => {
|
|
69
|
+
showError('Reconnection failed after maximum attempts');
|
|
70
|
+
});
|
|
71
|
+
socket.on('connect_error', (err) => {
|
|
72
|
+
showError(`Connection error: ${err.message}`);
|
|
73
|
+
});
|
|
74
|
+
// --- Tool execution ---
|
|
75
|
+
socket.on('daemon:tool:request', async (data) => {
|
|
76
|
+
const response = await executeToolRequest(data, config, cwd);
|
|
77
|
+
socket.emit('daemon:tool:response', response);
|
|
78
|
+
});
|
|
79
|
+
// --- Error handling ---
|
|
80
|
+
socket.on('error:unauthorized', (data) => {
|
|
81
|
+
showError(`Unauthorized: ${data.event}${data.sessionId ? ` (session: ${data.sessionId})` : ''}`);
|
|
82
|
+
});
|
|
83
|
+
socket.on('error:validation', (data) => {
|
|
84
|
+
showError(`Validation error: ${data.event} - ${data.message}`);
|
|
85
|
+
});
|
|
86
|
+
// --- Cleanup ---
|
|
87
|
+
const disconnect = () => {
|
|
88
|
+
stopHeartbeat();
|
|
89
|
+
socket.disconnect();
|
|
90
|
+
};
|
|
91
|
+
return { socket, daemonId, disconnect };
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=connection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type DaemonConfig } from './config.js';
|
|
2
|
+
export interface ToolRequest {
|
|
3
|
+
toolName: string;
|
|
4
|
+
toolInput: Record<string, unknown>;
|
|
5
|
+
toolUseId: string;
|
|
6
|
+
sessionId: string;
|
|
7
|
+
timeout?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface ToolResponse {
|
|
10
|
+
toolUseId: string;
|
|
11
|
+
sessionId: string;
|
|
12
|
+
result: unknown;
|
|
13
|
+
error: string | null;
|
|
14
|
+
duration: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Dispatch a tool request to the appropriate executor.
|
|
18
|
+
* Handles permission checking, execution, timing, and error wrapping.
|
|
19
|
+
*/
|
|
20
|
+
export declare function executeToolRequest(request: ToolRequest, config: DaemonConfig, cwd: string): Promise<ToolResponse>;
|
package/dist/executor.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { checkPermission } from './permissions.js';
|
|
2
|
+
import { showToolExecution, showToolResult, showPermissionDenied } from './ui.js';
|
|
3
|
+
import { executeBash } from './tools/bash.js';
|
|
4
|
+
import { executeRead } from './tools/read.js';
|
|
5
|
+
import { executeWrite } from './tools/write.js';
|
|
6
|
+
import { executeEdit } from './tools/edit.js';
|
|
7
|
+
import { executeGlob } from './tools/glob.js';
|
|
8
|
+
import { executeGrep } from './tools/grep.js';
|
|
9
|
+
/** Active executions for concurrency limiting */
|
|
10
|
+
let activeCount = 0;
|
|
11
|
+
/** Wrap a promise with a timeout. Rejects if the promise doesn't settle in time. */
|
|
12
|
+
function withTimeout(promise, ms, message) {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
const timer = setTimeout(() => reject(new Error(message)), ms);
|
|
15
|
+
promise.then((val) => { clearTimeout(timer); resolve(val); }, (err) => { clearTimeout(timer); reject(err); });
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Dispatch a tool request to the appropriate executor.
|
|
20
|
+
* Handles permission checking, execution, timing, and error wrapping.
|
|
21
|
+
*/
|
|
22
|
+
export async function executeToolRequest(request, config, cwd) {
|
|
23
|
+
const start = Date.now();
|
|
24
|
+
const { toolName, toolInput, toolUseId, sessionId } = request;
|
|
25
|
+
// Concurrency check
|
|
26
|
+
if (activeCount >= config.limits.maxConcurrentTools) {
|
|
27
|
+
return {
|
|
28
|
+
toolUseId,
|
|
29
|
+
sessionId,
|
|
30
|
+
result: null,
|
|
31
|
+
error: `Too many concurrent tool executions (max ${config.limits.maxConcurrentTools})`,
|
|
32
|
+
duration: Date.now() - start,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
// Permission check
|
|
36
|
+
const decision = await checkPermission({ toolName, toolInput }, config, cwd);
|
|
37
|
+
if (decision === 'deny') {
|
|
38
|
+
showPermissionDenied(toolName);
|
|
39
|
+
return {
|
|
40
|
+
toolUseId,
|
|
41
|
+
sessionId,
|
|
42
|
+
result: null,
|
|
43
|
+
error: 'Permission denied by user',
|
|
44
|
+
duration: Date.now() - start,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
activeCount++;
|
|
48
|
+
showToolExecution(toolName, describeTool(toolName, toolInput));
|
|
49
|
+
try {
|
|
50
|
+
// Apply timeout to all tools (Bash uses its own timeout internally,
|
|
51
|
+
// but non-Bash tools need an outer timeout to prevent indefinite hangs)
|
|
52
|
+
const timeout = request.timeout || (toolName.toLowerCase() === 'bash' ? config.limits.bashTimeout : 30_000);
|
|
53
|
+
const rawResult = await withTimeout(dispatch(toolName, toolInput, config, cwd, request.timeout), timeout, `${toolName} timed out after ${timeout}ms`);
|
|
54
|
+
const result = normalizeResult(toolName, rawResult);
|
|
55
|
+
const duration = Date.now() - start;
|
|
56
|
+
showToolResult(toolName, duration, true);
|
|
57
|
+
return { toolUseId, sessionId, result, error: null, duration };
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
const duration = Date.now() - start;
|
|
61
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
62
|
+
showToolResult(toolName, duration, false);
|
|
63
|
+
return { toolUseId, sessionId, result: null, error: errorMsg, duration };
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
activeCount--;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Route to the correct tool executor.
|
|
71
|
+
*/
|
|
72
|
+
async function dispatch(toolName, toolInput, config, cwd, timeout) {
|
|
73
|
+
// SDK sends PascalCase tool names (Bash, Read, Write, etc.)
|
|
74
|
+
// Normalize to lowercase for matching
|
|
75
|
+
switch (toolName.toLowerCase()) {
|
|
76
|
+
case 'bash':
|
|
77
|
+
return executeBash({
|
|
78
|
+
command: toolInput.command,
|
|
79
|
+
cwd: toolInput.cwd,
|
|
80
|
+
timeout: timeout || config.limits.bashTimeout,
|
|
81
|
+
}, cwd, config.limits.bashTimeout);
|
|
82
|
+
case 'read':
|
|
83
|
+
return executeRead({
|
|
84
|
+
file_path: toolInput.file_path,
|
|
85
|
+
offset: toolInput.offset,
|
|
86
|
+
limit: toolInput.limit,
|
|
87
|
+
}, cwd, config.limits.maxFileReadSize);
|
|
88
|
+
case 'write':
|
|
89
|
+
return executeWrite({
|
|
90
|
+
file_path: toolInput.file_path,
|
|
91
|
+
content: toolInput.content,
|
|
92
|
+
}, cwd);
|
|
93
|
+
case 'edit':
|
|
94
|
+
return executeEdit({
|
|
95
|
+
file_path: toolInput.file_path,
|
|
96
|
+
old_string: toolInput.old_string,
|
|
97
|
+
new_string: toolInput.new_string,
|
|
98
|
+
replace_all: toolInput.replace_all,
|
|
99
|
+
}, cwd);
|
|
100
|
+
case 'glob':
|
|
101
|
+
return executeGlob({
|
|
102
|
+
pattern: toolInput.pattern,
|
|
103
|
+
path: toolInput.path,
|
|
104
|
+
}, cwd);
|
|
105
|
+
case 'grep':
|
|
106
|
+
return executeGrep({
|
|
107
|
+
pattern: toolInput.pattern,
|
|
108
|
+
path: toolInput.path,
|
|
109
|
+
glob: toolInput.glob,
|
|
110
|
+
output_mode: toolInput.output_mode,
|
|
111
|
+
'-i': toolInput['-i'],
|
|
112
|
+
'-n': toolInput['-n'],
|
|
113
|
+
'-C': toolInput['-C'],
|
|
114
|
+
'-A': toolInput['-A'],
|
|
115
|
+
'-B': toolInput['-B'],
|
|
116
|
+
head_limit: toolInput.head_limit,
|
|
117
|
+
}, cwd);
|
|
118
|
+
default:
|
|
119
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Normalize tool results to a string so the server can interpolate them
|
|
124
|
+
* directly into `_denyTool()` output without getting "[object Object]".
|
|
125
|
+
*/
|
|
126
|
+
function normalizeResult(toolName, result) {
|
|
127
|
+
if (typeof result === 'string') {
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
if (result == null) {
|
|
131
|
+
return '';
|
|
132
|
+
}
|
|
133
|
+
const r = result;
|
|
134
|
+
switch (toolName.toLowerCase()) {
|
|
135
|
+
case 'bash': {
|
|
136
|
+
// BashOutput: { output, exitCode, timedOut? }
|
|
137
|
+
let text = r.output || '';
|
|
138
|
+
if (r.timedOut) {
|
|
139
|
+
text += '\n[Command timed out]';
|
|
140
|
+
}
|
|
141
|
+
if (r.exitCode !== 0 && r.exitCode != null) {
|
|
142
|
+
text += `\n[Exit code: ${r.exitCode}]`;
|
|
143
|
+
}
|
|
144
|
+
return text;
|
|
145
|
+
}
|
|
146
|
+
case 'read':
|
|
147
|
+
// ReadOutput: { content, totalLines, truncated? }
|
|
148
|
+
return r.content || '';
|
|
149
|
+
case 'write':
|
|
150
|
+
// WriteOutput: { file_path, bytes, created }
|
|
151
|
+
return `${r.created ? 'Created' : 'Updated'} ${r.file_path} (${r.bytes} bytes)`;
|
|
152
|
+
case 'edit':
|
|
153
|
+
// EditOutput: { file_path, replacements, lineRange }
|
|
154
|
+
return `Edited ${r.file_path}: ${r.replacements} replacement(s) at lines ${r.lineRange?.start}-${r.lineRange?.end}`;
|
|
155
|
+
case 'glob': {
|
|
156
|
+
// GlobOutput: { files, count }
|
|
157
|
+
const files = r.files;
|
|
158
|
+
return files?.length ? files.join('\n') : 'No files found';
|
|
159
|
+
}
|
|
160
|
+
case 'grep':
|
|
161
|
+
// GrepOutput: { output, matchCount, tool }
|
|
162
|
+
return r.output || 'No matches found';
|
|
163
|
+
default:
|
|
164
|
+
return JSON.stringify(result, null, 2);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function describeTool(toolName, toolInput) {
|
|
168
|
+
switch (toolName.toLowerCase()) {
|
|
169
|
+
case 'bash': {
|
|
170
|
+
const cmd = (toolInput.command || '');
|
|
171
|
+
return cmd.length > 60 ? cmd.slice(0, 60) + '...' : cmd;
|
|
172
|
+
}
|
|
173
|
+
case 'read':
|
|
174
|
+
return toolInput.file_path;
|
|
175
|
+
case 'write':
|
|
176
|
+
return toolInput.file_path;
|
|
177
|
+
case 'edit':
|
|
178
|
+
return toolInput.file_path;
|
|
179
|
+
case 'glob':
|
|
180
|
+
return toolInput.pattern;
|
|
181
|
+
case 'grep':
|
|
182
|
+
return `/${toolInput.pattern}/`;
|
|
183
|
+
default:
|
|
184
|
+
return toolName;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=executor.js.map
|