@douglas-agent/sandbank-boxlite 0.6.1
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 +120 -0
- package/dist/adapter.d.ts +19 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +259 -0
- package/dist/client.d.ts +7 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +294 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/local-client.d.ts +7 -0
- package/dist/local-client.d.ts.map +1 -0
- package/dist/local-client.js +863 -0
- package/dist/types.d.ts +138 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/package.json +41 -0
package/dist/client.js
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a BoxLite REST client for communicating with a BoxRun REST API.
|
|
3
|
+
* Used in remote mode.
|
|
4
|
+
*/
|
|
5
|
+
export function createBoxLiteRestClient(config) {
|
|
6
|
+
const { apiUrl } = config;
|
|
7
|
+
const prefix = config.prefix ?? '';
|
|
8
|
+
const baseUrl = apiUrl.replace(/\/$/, '') + '/v1';
|
|
9
|
+
// --- Token management ---
|
|
10
|
+
let token = config.apiToken ?? '';
|
|
11
|
+
let tokenExpiresAt = 0;
|
|
12
|
+
async function ensureToken() {
|
|
13
|
+
if (config.apiToken)
|
|
14
|
+
return config.apiToken;
|
|
15
|
+
if (!config.clientId || !config.clientSecret)
|
|
16
|
+
return '';
|
|
17
|
+
if (token && Date.now() < tokenExpiresAt)
|
|
18
|
+
return token;
|
|
19
|
+
const response = await fetch(`${baseUrl}/oauth/tokens`, {
|
|
20
|
+
method: 'POST',
|
|
21
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
22
|
+
body: new URLSearchParams({
|
|
23
|
+
grant_type: 'client_credentials',
|
|
24
|
+
client_id: config.clientId,
|
|
25
|
+
client_secret: config.clientSecret,
|
|
26
|
+
}),
|
|
27
|
+
});
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
const body = await response.text();
|
|
30
|
+
throw new Error(`BoxLite OAuth2 error ${response.status}: ${body}`);
|
|
31
|
+
}
|
|
32
|
+
const data = await response.json();
|
|
33
|
+
token = data.access_token;
|
|
34
|
+
tokenExpiresAt = Date.now() + (data.expires_in - 60) * 1000;
|
|
35
|
+
return token;
|
|
36
|
+
}
|
|
37
|
+
async function request(path, options = {}, rawResponse = false) {
|
|
38
|
+
const bearerToken = await ensureToken();
|
|
39
|
+
const url = prefix ? `${baseUrl}/${prefix}${path}` : `${baseUrl}${path}`;
|
|
40
|
+
const headers = {
|
|
41
|
+
'Content-Type': 'application/json',
|
|
42
|
+
...options.headers,
|
|
43
|
+
};
|
|
44
|
+
if (bearerToken) {
|
|
45
|
+
headers['Authorization'] = `Bearer ${bearerToken}`;
|
|
46
|
+
}
|
|
47
|
+
const response = await fetch(url, { ...options, headers });
|
|
48
|
+
if (rawResponse)
|
|
49
|
+
return response;
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
const body = await response.text();
|
|
52
|
+
throw new Error(`BoxLite API error ${response.status}: ${body}`);
|
|
53
|
+
}
|
|
54
|
+
const text = await response.text();
|
|
55
|
+
if (!text)
|
|
56
|
+
return {};
|
|
57
|
+
return JSON.parse(text);
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
async createBox(params) {
|
|
61
|
+
return request('/boxes', {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
body: JSON.stringify(params),
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
async getBox(boxId) {
|
|
67
|
+
return request(`/boxes/${boxId}`);
|
|
68
|
+
},
|
|
69
|
+
async listBoxes(status, pageSize) {
|
|
70
|
+
const params = new URLSearchParams();
|
|
71
|
+
if (status)
|
|
72
|
+
params.set('status', status);
|
|
73
|
+
if (pageSize)
|
|
74
|
+
params.set('page_size', String(pageSize));
|
|
75
|
+
const qs = params.toString();
|
|
76
|
+
const data = await request(`/boxes${qs ? `?${qs}` : ''}`);
|
|
77
|
+
if (Array.isArray(data))
|
|
78
|
+
return data;
|
|
79
|
+
return data.boxes ?? [];
|
|
80
|
+
},
|
|
81
|
+
async deleteBox(boxId, force = false) {
|
|
82
|
+
await request(`/boxes/${boxId}${force ? '?force=true' : ''}`, {
|
|
83
|
+
method: 'DELETE',
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
async startBox(boxId) {
|
|
87
|
+
await request(`/boxes/${boxId}/start`, { method: 'POST' });
|
|
88
|
+
},
|
|
89
|
+
async stopBox(boxId) {
|
|
90
|
+
await request(`/boxes/${boxId}/stop`, { method: 'POST' });
|
|
91
|
+
},
|
|
92
|
+
async exec(boxId, req) {
|
|
93
|
+
const execution = await request(`/boxes/${boxId}/exec`, {
|
|
94
|
+
method: 'POST',
|
|
95
|
+
body: JSON.stringify(req),
|
|
96
|
+
});
|
|
97
|
+
if (execution.exit_code !== null && execution.exit_code !== undefined) {
|
|
98
|
+
return {
|
|
99
|
+
stdout: execution.stdout ?? '',
|
|
100
|
+
stderr: execution.stderr ?? '',
|
|
101
|
+
exitCode: execution.exit_code,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const timeoutMs = (req.timeout_seconds ?? 300) * 1000;
|
|
105
|
+
const startTime = Date.now();
|
|
106
|
+
let pollInterval = 100;
|
|
107
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
108
|
+
await new Promise(r => setTimeout(r, pollInterval));
|
|
109
|
+
pollInterval = Math.min(pollInterval * 2, 2000);
|
|
110
|
+
const result = await request(`/boxes/${boxId}/exec/${execution.id}`);
|
|
111
|
+
if (result.exit_code !== null && result.exit_code !== undefined) {
|
|
112
|
+
return {
|
|
113
|
+
stdout: result.stdout ?? '',
|
|
114
|
+
stderr: result.stderr ?? '',
|
|
115
|
+
exitCode: result.exit_code,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
throw new Error('BoxLite exec timed out waiting for completion');
|
|
120
|
+
},
|
|
121
|
+
async execStream(boxId, req) {
|
|
122
|
+
const execution = await request(`/boxes/${boxId}/exec`, {
|
|
123
|
+
method: 'POST',
|
|
124
|
+
body: JSON.stringify(req),
|
|
125
|
+
});
|
|
126
|
+
const encoder = new TextEncoder();
|
|
127
|
+
const self = { request };
|
|
128
|
+
return new ReadableStream({
|
|
129
|
+
async start(controller) {
|
|
130
|
+
if (execution.exit_code !== null && execution.exit_code !== undefined) {
|
|
131
|
+
if (execution.stdout)
|
|
132
|
+
controller.enqueue(encoder.encode(execution.stdout));
|
|
133
|
+
if (execution.stderr)
|
|
134
|
+
controller.enqueue(encoder.encode(execution.stderr));
|
|
135
|
+
controller.close();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const timeoutMs = (req.timeout_seconds ?? 300) * 1000;
|
|
139
|
+
const startTime = Date.now();
|
|
140
|
+
let pollInterval = 100;
|
|
141
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
142
|
+
await new Promise(r => setTimeout(r, pollInterval));
|
|
143
|
+
pollInterval = Math.min(pollInterval * 2, 2000);
|
|
144
|
+
try {
|
|
145
|
+
const result = await self.request(`/boxes/${boxId}/exec/${execution.id}`);
|
|
146
|
+
if (result.exit_code !== null && result.exit_code !== undefined) {
|
|
147
|
+
if (result.stdout)
|
|
148
|
+
controller.enqueue(encoder.encode(result.stdout));
|
|
149
|
+
if (result.stderr)
|
|
150
|
+
controller.enqueue(encoder.encode(result.stderr));
|
|
151
|
+
controller.close();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
controller.error(err);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
controller.error(new Error('BoxLite exec stream timed out'));
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
},
|
|
164
|
+
async uploadFiles(boxId, path, tarData) {
|
|
165
|
+
const bearerToken = await ensureToken();
|
|
166
|
+
const url = `${baseUrl}${prefix ? `/${prefix}` : ''}/boxes/${boxId}/files?path=${encodeURIComponent(path)}`;
|
|
167
|
+
const response = await fetch(url, {
|
|
168
|
+
method: 'PUT',
|
|
169
|
+
headers: {
|
|
170
|
+
'Authorization': `Bearer ${bearerToken}`,
|
|
171
|
+
'Content-Type': 'application/x-tar',
|
|
172
|
+
},
|
|
173
|
+
body: tarData.buffer.slice(tarData.byteOffset, tarData.byteOffset + tarData.byteLength),
|
|
174
|
+
});
|
|
175
|
+
if (!response.ok) {
|
|
176
|
+
const body = await response.text();
|
|
177
|
+
throw new Error(`BoxLite API error ${response.status}: ${body}`);
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
async downloadFiles(boxId, path) {
|
|
181
|
+
const bearerToken = await ensureToken();
|
|
182
|
+
const url = `${baseUrl}${prefix ? `/${prefix}` : ''}/boxes/${boxId}/files?path=${encodeURIComponent(path)}`;
|
|
183
|
+
const response = await fetch(url, {
|
|
184
|
+
headers: {
|
|
185
|
+
'Authorization': `Bearer ${bearerToken}`,
|
|
186
|
+
'Accept': 'application/x-tar',
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
if (!response.ok) {
|
|
190
|
+
const body = await response.text();
|
|
191
|
+
throw new Error(`BoxLite API error ${response.status}: ${body}`);
|
|
192
|
+
}
|
|
193
|
+
if (!response.body) {
|
|
194
|
+
throw new Error('BoxLite download: no response body');
|
|
195
|
+
}
|
|
196
|
+
return response.body;
|
|
197
|
+
},
|
|
198
|
+
async createSnapshot(boxId, name) {
|
|
199
|
+
return request(`/boxes/${boxId}/snapshots`, {
|
|
200
|
+
method: 'POST',
|
|
201
|
+
body: JSON.stringify({ name }),
|
|
202
|
+
});
|
|
203
|
+
},
|
|
204
|
+
async restoreSnapshot(boxId, name) {
|
|
205
|
+
await request(`/boxes/${boxId}/snapshots/${encodeURIComponent(name)}/restore`, {
|
|
206
|
+
method: 'POST',
|
|
207
|
+
});
|
|
208
|
+
},
|
|
209
|
+
async listSnapshots(boxId) {
|
|
210
|
+
return request(`/boxes/${boxId}/snapshots`);
|
|
211
|
+
},
|
|
212
|
+
async deleteSnapshot(boxId, name) {
|
|
213
|
+
await request(`/boxes/${boxId}/snapshots/${encodeURIComponent(name)}`, {
|
|
214
|
+
method: 'DELETE',
|
|
215
|
+
});
|
|
216
|
+
},
|
|
217
|
+
async cloneBox(boxId, name) {
|
|
218
|
+
return request(`/boxes/${boxId}/clone`, {
|
|
219
|
+
method: 'POST',
|
|
220
|
+
body: JSON.stringify(name ? { name } : {}),
|
|
221
|
+
});
|
|
222
|
+
},
|
|
223
|
+
async exportBox(boxId) {
|
|
224
|
+
const response = await request(`/boxes/${boxId}/export`, { method: 'POST' }, true);
|
|
225
|
+
if (!response.ok) {
|
|
226
|
+
const body = await response.text();
|
|
227
|
+
throw new Error(`BoxLite API error ${response.status}: ${body}`);
|
|
228
|
+
}
|
|
229
|
+
if (!response.body)
|
|
230
|
+
throw new Error('BoxLite export: no response body');
|
|
231
|
+
return response.body;
|
|
232
|
+
},
|
|
233
|
+
async importBox(data) {
|
|
234
|
+
const bearerToken = await ensureToken();
|
|
235
|
+
const url = `${baseUrl}${prefix ? `/${prefix}` : ''}/boxes/import`;
|
|
236
|
+
const response = await fetch(url, {
|
|
237
|
+
method: 'POST',
|
|
238
|
+
headers: {
|
|
239
|
+
'Authorization': `Bearer ${bearerToken}`,
|
|
240
|
+
'Content-Type': 'application/octet-stream',
|
|
241
|
+
},
|
|
242
|
+
body: data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength),
|
|
243
|
+
});
|
|
244
|
+
if (!response.ok) {
|
|
245
|
+
const body = await response.text();
|
|
246
|
+
throw new Error(`BoxLite API error ${response.status}: ${body}`);
|
|
247
|
+
}
|
|
248
|
+
return response.json();
|
|
249
|
+
},
|
|
250
|
+
async execAsync(boxId, req) {
|
|
251
|
+
return request(`/boxes/${boxId}/exec`, {
|
|
252
|
+
method: 'POST',
|
|
253
|
+
body: JSON.stringify(req),
|
|
254
|
+
});
|
|
255
|
+
},
|
|
256
|
+
async getExecOutput(boxId, execId) {
|
|
257
|
+
const response = await request(`/boxes/${boxId}/exec/${execId}/output`, { headers: { 'Accept': 'text/event-stream' } }, true);
|
|
258
|
+
if (!response.ok) {
|
|
259
|
+
const body = await response.text();
|
|
260
|
+
throw new Error(`BoxLite API error ${response.status}: ${body}`);
|
|
261
|
+
}
|
|
262
|
+
if (!response.body)
|
|
263
|
+
throw new Error('BoxLite SSE: no response body');
|
|
264
|
+
return response.body;
|
|
265
|
+
},
|
|
266
|
+
async sendExecInput(boxId, execId, data) {
|
|
267
|
+
await request(`/boxes/${boxId}/exec/${execId}/input`, {
|
|
268
|
+
method: 'POST',
|
|
269
|
+
body: JSON.stringify({ data }),
|
|
270
|
+
});
|
|
271
|
+
},
|
|
272
|
+
async signalExec(boxId, execId, signal) {
|
|
273
|
+
await request(`/boxes/${boxId}/exec/${execId}/signal`, {
|
|
274
|
+
method: 'POST',
|
|
275
|
+
body: JSON.stringify({ signal }),
|
|
276
|
+
});
|
|
277
|
+
},
|
|
278
|
+
async resizeExec(boxId, execId, cols, rows) {
|
|
279
|
+
await request(`/boxes/${boxId}/exec/${execId}/resize`, {
|
|
280
|
+
method: 'POST',
|
|
281
|
+
body: JSON.stringify({ cols, rows }),
|
|
282
|
+
});
|
|
283
|
+
},
|
|
284
|
+
async getMetrics() {
|
|
285
|
+
return request('/metrics');
|
|
286
|
+
},
|
|
287
|
+
async getBoxMetrics(boxId) {
|
|
288
|
+
return request(`/boxes/${boxId}/metrics`);
|
|
289
|
+
},
|
|
290
|
+
async getConfig() {
|
|
291
|
+
return request('/config');
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { BoxLiteAdapter } from './adapter.js';
|
|
2
|
+
export type { BoxLiteAdapterConfig, BoxLiteRemoteConfig, BoxLiteLocalConfig, BoxLiteClient, BoxLiteBox, BoxLiteCreateParams, BoxLiteExecRequest, BoxLiteExecution, BoxLiteSnapshot, BoxLiteNetworkConfig, BoxLiteSecretSpec, BoxStatus, } from './types.js';
|
|
3
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC7C,YAAY,EACV,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,EAClB,aAAa,EACb,UAAU,EACV,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EACf,oBAAoB,EACpB,iBAAiB,EACjB,SAAS,GACV,MAAM,YAAY,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { BoxLiteClient, BoxLiteLocalConfig } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Create a BoxLite local client that communicates with the boxlite Python SDK
|
|
4
|
+
* via a JSON-line subprocess bridge.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createBoxLiteLocalClient(config: BoxLiteLocalConfig): BoxLiteClient;
|
|
7
|
+
//# sourceMappingURL=local-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-client.d.ts","sourceRoot":"","sources":["../src/local-client.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAEV,aAAa,EAGb,kBAAkB,EAEnB,MAAM,YAAY,CAAA;AAinBnB;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,kBAAkB,GAAG,aAAa,CA0RlF"}
|