@paperpod/cli 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/README.md +245 -0
- package/dist/cli.d.ts +22 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +812 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands.d.ts +106 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +1053 -0
- package/dist/commands.js.map +1 -0
- package/dist/config.d.ts +47 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +117 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/transport.d.ts +347 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +684 -0
- package/dist/transport.js.map +1 -0
- package/package.json +39 -0
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Transport
|
|
3
|
+
*
|
|
4
|
+
* Handles WebSocket connection to PaperPod server.
|
|
5
|
+
* Provides a simple request/response interface over WebSocket.
|
|
6
|
+
*
|
|
7
|
+
* Note: The server derives sandbox ID from the authenticated user's
|
|
8
|
+
* identity (email). No client-side session tracking needed.
|
|
9
|
+
*/
|
|
10
|
+
import WebSocket from "ws";
|
|
11
|
+
import { getToken, getApiUrl, DEFAULT_TIMEOUT } from "./config.js";
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Transport Class
|
|
14
|
+
// ============================================================================
|
|
15
|
+
export class PaperpodTransport {
|
|
16
|
+
ws = null;
|
|
17
|
+
sandboxId = null;
|
|
18
|
+
pendingRequests = new Map();
|
|
19
|
+
globalSuggestionHandler = null;
|
|
20
|
+
messageIdCounter = 0;
|
|
21
|
+
/**
|
|
22
|
+
* Connect to PaperPod WebSocket server
|
|
23
|
+
*
|
|
24
|
+
* The server derives the sandbox ID from your authenticated identity.
|
|
25
|
+
* Same token → same user → same sandbox.
|
|
26
|
+
*/
|
|
27
|
+
async connect(options = {}) {
|
|
28
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
29
|
+
return this.sandboxId;
|
|
30
|
+
}
|
|
31
|
+
const token = getToken();
|
|
32
|
+
if (!token) {
|
|
33
|
+
throw new Error("No token found. Set PAPERPOD_TOKEN env var or run: ppod login");
|
|
34
|
+
}
|
|
35
|
+
const url = getApiUrl();
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
const timeout = setTimeout(() => {
|
|
38
|
+
reject(new Error("Connection timeout"));
|
|
39
|
+
this.ws?.close();
|
|
40
|
+
}, options.timeout ?? DEFAULT_TIMEOUT);
|
|
41
|
+
this.ws = new WebSocket(url, {
|
|
42
|
+
headers: {
|
|
43
|
+
Authorization: `Bearer ${token}`,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
this.ws.on("open", () => {
|
|
47
|
+
// Wait for connected message
|
|
48
|
+
});
|
|
49
|
+
this.ws.on("message", (data) => {
|
|
50
|
+
try {
|
|
51
|
+
const message = JSON.parse(data.toString());
|
|
52
|
+
// Handle connection acknowledgment
|
|
53
|
+
if (message.type === "connected") {
|
|
54
|
+
clearTimeout(timeout);
|
|
55
|
+
this.sandboxId = message.data?.sessionId || null;
|
|
56
|
+
resolve(this.sandboxId);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
// Handle streaming output
|
|
60
|
+
if (message.type === "stdout" && message.id) {
|
|
61
|
+
const pending = this.pendingRequests.get(message.id);
|
|
62
|
+
if (pending?.onStdout) {
|
|
63
|
+
pending.onStdout(message.data?.text ?? "");
|
|
64
|
+
}
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (message.type === "stderr" && message.id) {
|
|
68
|
+
const pending = this.pendingRequests.get(message.id);
|
|
69
|
+
if (pending?.onStderr) {
|
|
70
|
+
pending.onStderr(message.data?.text ?? "");
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
// Handle suggestion messages (port detection, etc.)
|
|
75
|
+
if (message.type === "suggestion") {
|
|
76
|
+
const suggestionData = message.data;
|
|
77
|
+
if (suggestionData) {
|
|
78
|
+
// Try request-specific handler first, then global
|
|
79
|
+
if (message.id) {
|
|
80
|
+
const pending = this.pendingRequests.get(message.id);
|
|
81
|
+
if (pending?.onSuggestion) {
|
|
82
|
+
pending.onSuggestion(suggestionData);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Fall back to global handler
|
|
87
|
+
if (this.globalSuggestionHandler) {
|
|
88
|
+
this.globalSuggestionHandler(suggestionData);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// Handle result/exit/error responses
|
|
94
|
+
if (message.id && (message.type === "result" || message.type === "exit" || message.type === "error")) {
|
|
95
|
+
const pending = this.pendingRequests.get(message.id);
|
|
96
|
+
if (pending) {
|
|
97
|
+
this.pendingRequests.delete(message.id);
|
|
98
|
+
pending.resolve(message);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// Ignore parse errors
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
this.ws.on("error", (error) => {
|
|
107
|
+
clearTimeout(timeout);
|
|
108
|
+
reject(error);
|
|
109
|
+
});
|
|
110
|
+
this.ws.on("close", (code, reason) => {
|
|
111
|
+
clearTimeout(timeout);
|
|
112
|
+
// Reject all pending requests
|
|
113
|
+
for (const [id, pending] of this.pendingRequests) {
|
|
114
|
+
pending.reject(new Error(`Connection closed: ${code} ${reason}`));
|
|
115
|
+
this.pendingRequests.delete(id);
|
|
116
|
+
}
|
|
117
|
+
this.ws = null;
|
|
118
|
+
this.sandboxId = null;
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Disconnect from server
|
|
124
|
+
*/
|
|
125
|
+
disconnect() {
|
|
126
|
+
if (this.ws) {
|
|
127
|
+
this.ws.close();
|
|
128
|
+
this.ws = null;
|
|
129
|
+
this.sandboxId = null;
|
|
130
|
+
}
|
|
131
|
+
this.globalSuggestionHandler = null;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Set a global suggestion handler for background suggestions (like port detection)
|
|
135
|
+
*/
|
|
136
|
+
setSuggestionHandler(handler) {
|
|
137
|
+
this.globalSuggestionHandler = handler;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Send a message and wait for response
|
|
141
|
+
*/
|
|
142
|
+
async send(message, options = {}) {
|
|
143
|
+
await this.connect(options);
|
|
144
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
145
|
+
throw new Error("Not connected");
|
|
146
|
+
}
|
|
147
|
+
const id = `msg_${++this.messageIdCounter}_${Date.now()}`;
|
|
148
|
+
const messageWithId = { ...message, id };
|
|
149
|
+
return new Promise((resolve, reject) => {
|
|
150
|
+
const timeout = setTimeout(() => {
|
|
151
|
+
this.pendingRequests.delete(id);
|
|
152
|
+
reject(new Error("Request timeout"));
|
|
153
|
+
}, options.timeout ?? DEFAULT_TIMEOUT);
|
|
154
|
+
this.pendingRequests.set(id, {
|
|
155
|
+
resolve: (response) => {
|
|
156
|
+
clearTimeout(timeout);
|
|
157
|
+
resolve(response);
|
|
158
|
+
},
|
|
159
|
+
reject: (error) => {
|
|
160
|
+
clearTimeout(timeout);
|
|
161
|
+
reject(error);
|
|
162
|
+
},
|
|
163
|
+
onStdout: options.onStdout,
|
|
164
|
+
onStderr: options.onStderr,
|
|
165
|
+
onSuggestion: options.onSuggestion,
|
|
166
|
+
});
|
|
167
|
+
this.ws.send(JSON.stringify(messageWithId));
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Execute a command and return result
|
|
172
|
+
*/
|
|
173
|
+
async exec(command, options = {}) {
|
|
174
|
+
const response = await this.send({
|
|
175
|
+
type: "exec",
|
|
176
|
+
command,
|
|
177
|
+
stream: !!(options.onStdout || options.onStderr),
|
|
178
|
+
}, options);
|
|
179
|
+
// Handle exit message (streaming) or result message (non-streaming)
|
|
180
|
+
const data = response.data || {};
|
|
181
|
+
return {
|
|
182
|
+
exitCode: data.exitCode ?? 1,
|
|
183
|
+
stdout: data.stdout ?? "",
|
|
184
|
+
stderr: data.stderr ?? "",
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Write a file to the sandbox
|
|
189
|
+
*/
|
|
190
|
+
async writeFile(path, content, options = {}) {
|
|
191
|
+
const response = await this.send({
|
|
192
|
+
type: "write",
|
|
193
|
+
path,
|
|
194
|
+
content,
|
|
195
|
+
}, options);
|
|
196
|
+
if (response.data?.success === false) {
|
|
197
|
+
throw new Error(response.data.error || "Write failed");
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Read a file from the sandbox
|
|
202
|
+
*/
|
|
203
|
+
async readFile(path, options = {}) {
|
|
204
|
+
const response = await this.send({
|
|
205
|
+
type: "read",
|
|
206
|
+
path,
|
|
207
|
+
}, options);
|
|
208
|
+
if (response.data?.success === false) {
|
|
209
|
+
throw new Error(response.data.error || "Read failed");
|
|
210
|
+
}
|
|
211
|
+
return response.data?.content ?? "";
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* List directory contents
|
|
215
|
+
*/
|
|
216
|
+
async listDir(path, options = {}) {
|
|
217
|
+
const response = await this.send({
|
|
218
|
+
type: "list",
|
|
219
|
+
path,
|
|
220
|
+
}, options);
|
|
221
|
+
if (response.data?.success === false) {
|
|
222
|
+
throw new Error(response.data.error || "List failed");
|
|
223
|
+
}
|
|
224
|
+
return response.data?.files ?? [];
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Write multiple files at once
|
|
228
|
+
*/
|
|
229
|
+
async writeMany(files, options = {}) {
|
|
230
|
+
const response = await this.send({
|
|
231
|
+
type: "writeMany",
|
|
232
|
+
files,
|
|
233
|
+
}, options);
|
|
234
|
+
if (response.data?.success === false) {
|
|
235
|
+
throw new Error(response.data.error || "Write many failed");
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
written: response.data?.written ?? files.length,
|
|
239
|
+
failed: response.data?.failed ?? [],
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Send stdin input to a running process
|
|
244
|
+
*/
|
|
245
|
+
async stdin(processId, input, options = {}) {
|
|
246
|
+
const response = await this.send({
|
|
247
|
+
type: "stdin",
|
|
248
|
+
processId,
|
|
249
|
+
input,
|
|
250
|
+
}, options);
|
|
251
|
+
if (response.data?.success === false) {
|
|
252
|
+
throw new Error(response.data.error || "Stdin failed");
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Run code with rich output interpreter (charts, images)
|
|
257
|
+
*/
|
|
258
|
+
async interpret(code, options = {}) {
|
|
259
|
+
const response = await this.send({
|
|
260
|
+
type: "interpret",
|
|
261
|
+
code,
|
|
262
|
+
language: options.language ?? "python",
|
|
263
|
+
}, options);
|
|
264
|
+
if (response.data?.success === false) {
|
|
265
|
+
throw new Error(response.data.error || "Interpret failed");
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
results: response.data?.results ?? [],
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Get sandbox ID (assigned by server based on your identity)
|
|
273
|
+
*/
|
|
274
|
+
getSandboxId() {
|
|
275
|
+
return this.sandboxId;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Check if connected
|
|
279
|
+
*/
|
|
280
|
+
isConnected() {
|
|
281
|
+
return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
|
|
282
|
+
}
|
|
283
|
+
// ==========================================================================
|
|
284
|
+
// Port Exposure
|
|
285
|
+
// ==========================================================================
|
|
286
|
+
/**
|
|
287
|
+
* Expose a port and get a public preview URL
|
|
288
|
+
*/
|
|
289
|
+
async expose(port, options = {}) {
|
|
290
|
+
const response = await this.send({ type: "expose", port }, options);
|
|
291
|
+
if (response.data?.success === false) {
|
|
292
|
+
throw new Error(response.data.error || "Expose failed");
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
url: response.data?.url ?? "",
|
|
296
|
+
wsUrl: response.data?.wsUrl,
|
|
297
|
+
exposedAt: response.data?.exposedAt ?? new Date().toISOString(),
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
// ==========================================================================
|
|
301
|
+
// Browser Operations
|
|
302
|
+
// ==========================================================================
|
|
303
|
+
/**
|
|
304
|
+
* Take a screenshot of a URL
|
|
305
|
+
*/
|
|
306
|
+
async browserScreenshot(url, options = {}) {
|
|
307
|
+
const response = await this.send({
|
|
308
|
+
type: "browser_screenshot",
|
|
309
|
+
url,
|
|
310
|
+
fullPage: options.fullPage,
|
|
311
|
+
width: options.width,
|
|
312
|
+
height: options.height,
|
|
313
|
+
format: options.format,
|
|
314
|
+
}, options);
|
|
315
|
+
if (response.data?.success === false) {
|
|
316
|
+
throw new Error(response.data.error || "Screenshot failed");
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
image: response.data?.image ?? "",
|
|
320
|
+
format: response.data?.format ?? "png",
|
|
321
|
+
width: response.data?.width ?? 0,
|
|
322
|
+
height: response.data?.height ?? 0,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Generate PDF of a URL
|
|
327
|
+
*/
|
|
328
|
+
async browserPdf(url, options = {}) {
|
|
329
|
+
const response = await this.send({
|
|
330
|
+
type: "browser_pdf",
|
|
331
|
+
url,
|
|
332
|
+
format: options.format,
|
|
333
|
+
landscape: options.landscape,
|
|
334
|
+
}, options);
|
|
335
|
+
if (response.data?.success === false) {
|
|
336
|
+
throw new Error(response.data.error || "PDF generation failed");
|
|
337
|
+
}
|
|
338
|
+
return {
|
|
339
|
+
pdf: response.data?.pdf ?? "",
|
|
340
|
+
pages: response.data?.pages ?? 1,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Extract markdown from a URL
|
|
345
|
+
*/
|
|
346
|
+
async browserMarkdown(url, options = {}) {
|
|
347
|
+
const response = await this.send({ type: "browser_markdown", url }, options);
|
|
348
|
+
if (response.data?.success === false) {
|
|
349
|
+
throw new Error(response.data.error || "Markdown extraction failed");
|
|
350
|
+
}
|
|
351
|
+
return {
|
|
352
|
+
markdown: response.data?.markdown ?? "",
|
|
353
|
+
title: response.data?.title,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Scrape elements from a URL
|
|
358
|
+
*/
|
|
359
|
+
async browserScrape(url, selector, options = {}) {
|
|
360
|
+
const response = await this.send({
|
|
361
|
+
type: "browser_scrape",
|
|
362
|
+
url,
|
|
363
|
+
selector,
|
|
364
|
+
attributes: options.attributes,
|
|
365
|
+
limit: options.limit,
|
|
366
|
+
}, options);
|
|
367
|
+
if (response.data?.success === false) {
|
|
368
|
+
throw new Error(response.data.error || "Scrape failed");
|
|
369
|
+
}
|
|
370
|
+
return {
|
|
371
|
+
elements: response.data?.elements ?? [],
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Get rendered HTML content of a URL
|
|
376
|
+
*/
|
|
377
|
+
async browserContent(url, options = {}) {
|
|
378
|
+
const response = await this.send({ type: "browser_content", url }, options);
|
|
379
|
+
if (response.data?.success === false) {
|
|
380
|
+
throw new Error(response.data.error || "Content extraction failed");
|
|
381
|
+
}
|
|
382
|
+
return {
|
|
383
|
+
html: response.data?.html ?? "",
|
|
384
|
+
title: response.data?.title,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Start/stop browser tracing for performance recording
|
|
389
|
+
*/
|
|
390
|
+
async browserTrace(action, options = {}) {
|
|
391
|
+
const response = await this.send({
|
|
392
|
+
type: "browser_trace",
|
|
393
|
+
action,
|
|
394
|
+
sessionId: options.sessionId,
|
|
395
|
+
}, options);
|
|
396
|
+
if (response.data?.success === false) {
|
|
397
|
+
throw new Error(response.data.error || "Trace operation failed");
|
|
398
|
+
}
|
|
399
|
+
return {
|
|
400
|
+
traceData: response.data?.traceData,
|
|
401
|
+
sessionId: response.data?.sessionId,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Run Playwright assertions on a page
|
|
406
|
+
*/
|
|
407
|
+
async browserTest(url, assertions, options = {}) {
|
|
408
|
+
const response = await this.send({
|
|
409
|
+
type: "browser_test",
|
|
410
|
+
url,
|
|
411
|
+
assertions,
|
|
412
|
+
}, options);
|
|
413
|
+
if (response.data?.success === false) {
|
|
414
|
+
throw new Error(response.data.error || "Test execution failed");
|
|
415
|
+
}
|
|
416
|
+
return {
|
|
417
|
+
passed: response.data?.passed ?? false,
|
|
418
|
+
results: response.data?.results ?? [],
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* List active browser sessions
|
|
423
|
+
*/
|
|
424
|
+
async browserSessions(options = {}) {
|
|
425
|
+
const response = await this.send({ type: "browser_sessions" }, options);
|
|
426
|
+
if (response.data?.success === false) {
|
|
427
|
+
throw new Error(response.data.error || "Failed to list sessions");
|
|
428
|
+
}
|
|
429
|
+
return response.data?.sessions ?? [];
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Get browser usage limits
|
|
433
|
+
*/
|
|
434
|
+
async browserLimits(options = {}) {
|
|
435
|
+
const response = await this.send({ type: "browser_limits" }, options);
|
|
436
|
+
if (response.data?.success === false) {
|
|
437
|
+
throw new Error(response.data.error || "Failed to get limits");
|
|
438
|
+
}
|
|
439
|
+
return {
|
|
440
|
+
maxConcurrent: response.data?.maxConcurrent ?? 2,
|
|
441
|
+
currentActive: response.data?.currentActive ?? 0,
|
|
442
|
+
maxSessionDuration: response.data?.maxSessionDuration ?? 300000,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
// ==========================================================================
|
|
446
|
+
// AI Operations
|
|
447
|
+
// ==========================================================================
|
|
448
|
+
/**
|
|
449
|
+
* Generate text with LLM
|
|
450
|
+
*/
|
|
451
|
+
async aiGenerate(prompt, options = {}) {
|
|
452
|
+
const response = await this.send({
|
|
453
|
+
type: "ai_generate",
|
|
454
|
+
prompt,
|
|
455
|
+
model: options.model,
|
|
456
|
+
systemPrompt: options.systemPrompt,
|
|
457
|
+
maxTokens: options.maxTokens,
|
|
458
|
+
temperature: options.temperature,
|
|
459
|
+
}, options);
|
|
460
|
+
if (response.data?.success === false) {
|
|
461
|
+
throw new Error(response.data.error || "AI generation failed");
|
|
462
|
+
}
|
|
463
|
+
return {
|
|
464
|
+
// Server sends 'response', not 'text'
|
|
465
|
+
text: response.data?.response ?? response.data?.text ?? "",
|
|
466
|
+
model: response.data?.model ?? "unknown",
|
|
467
|
+
tokens: response.data?.neuronsUsed,
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Generate embeddings
|
|
472
|
+
*/
|
|
473
|
+
async aiEmbed(text, options = {}) {
|
|
474
|
+
const response = await this.send({
|
|
475
|
+
type: "ai_embed",
|
|
476
|
+
text,
|
|
477
|
+
model: options.model,
|
|
478
|
+
}, options);
|
|
479
|
+
if (response.data?.success === false) {
|
|
480
|
+
throw new Error(response.data.error || "Embedding failed");
|
|
481
|
+
}
|
|
482
|
+
return {
|
|
483
|
+
embeddings: response.data?.embeddings ?? [],
|
|
484
|
+
model: response.data?.model ?? "unknown",
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Generate images from text prompts
|
|
489
|
+
*/
|
|
490
|
+
async aiImage(prompt, options = {}) {
|
|
491
|
+
const response = await this.send({
|
|
492
|
+
type: "ai_image",
|
|
493
|
+
prompt,
|
|
494
|
+
model: options.model,
|
|
495
|
+
width: options.width,
|
|
496
|
+
height: options.height,
|
|
497
|
+
num_steps: options.numSteps,
|
|
498
|
+
}, options);
|
|
499
|
+
if (response.data?.success === false) {
|
|
500
|
+
throw new Error(response.data.error || "Image generation failed");
|
|
501
|
+
}
|
|
502
|
+
return {
|
|
503
|
+
image: response.data?.image ?? "",
|
|
504
|
+
model: response.data?.model ?? "unknown",
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Transcribe audio to text
|
|
509
|
+
*/
|
|
510
|
+
async aiTranscribe(audio, options = {}) {
|
|
511
|
+
const response = await this.send({
|
|
512
|
+
type: "ai_transcribe",
|
|
513
|
+
audio,
|
|
514
|
+
model: options.model,
|
|
515
|
+
}, options);
|
|
516
|
+
if (response.data?.success === false) {
|
|
517
|
+
throw new Error(response.data.error || "Transcription failed");
|
|
518
|
+
}
|
|
519
|
+
return {
|
|
520
|
+
text: response.data?.text ?? "",
|
|
521
|
+
model: response.data?.model ?? "unknown",
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
// ==========================================================================
|
|
525
|
+
// Agent Memory (Persistent Storage)
|
|
526
|
+
// ==========================================================================
|
|
527
|
+
/**
|
|
528
|
+
* Write to persistent memory
|
|
529
|
+
*/
|
|
530
|
+
async memoryWrite(path, content, options = {}) {
|
|
531
|
+
const response = await this.send({
|
|
532
|
+
type: "memory_write",
|
|
533
|
+
path,
|
|
534
|
+
content,
|
|
535
|
+
}, options);
|
|
536
|
+
if (response.data?.success === false) {
|
|
537
|
+
throw new Error(response.data.error || "Memory write failed");
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Read from persistent memory
|
|
542
|
+
*/
|
|
543
|
+
async memoryRead(path, options = {}) {
|
|
544
|
+
const response = await this.send({ type: "memory_read", path }, options);
|
|
545
|
+
if (response.data?.success === false) {
|
|
546
|
+
throw new Error(response.data.error || "Memory read failed");
|
|
547
|
+
}
|
|
548
|
+
return response.data?.content ?? "";
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* List persistent memory files
|
|
552
|
+
*/
|
|
553
|
+
async memoryList(prefix, options = {}) {
|
|
554
|
+
const response = await this.send({ type: "memory_list", prefix }, options);
|
|
555
|
+
if (response.data?.success === false) {
|
|
556
|
+
throw new Error(response.data.error || "Memory list failed");
|
|
557
|
+
}
|
|
558
|
+
return response.data?.files ?? [];
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Delete from persistent memory
|
|
562
|
+
*/
|
|
563
|
+
async memoryDelete(path, options = {}) {
|
|
564
|
+
const response = await this.send({ type: "memory_delete", path }, options);
|
|
565
|
+
if (response.data?.success === false) {
|
|
566
|
+
throw new Error(response.data.error || "Memory delete failed");
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Get memory usage
|
|
571
|
+
*/
|
|
572
|
+
async memoryUsage(options = {}) {
|
|
573
|
+
const response = await this.send({ type: "memory_usage" }, options);
|
|
574
|
+
if (response.data?.success === false) {
|
|
575
|
+
throw new Error(response.data.error || "Memory usage failed");
|
|
576
|
+
}
|
|
577
|
+
return {
|
|
578
|
+
usedBytes: response.data?.usedBytes ?? 0,
|
|
579
|
+
quotaBytes: response.data?.quotaBytes ?? 10 * 1024 * 1024,
|
|
580
|
+
fileCount: response.data?.fileCount ?? 0,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
// ==========================================================================
|
|
584
|
+
// Process Management
|
|
585
|
+
// ==========================================================================
|
|
586
|
+
/**
|
|
587
|
+
* Start a background process
|
|
588
|
+
*
|
|
589
|
+
* @param command - Shell command to run
|
|
590
|
+
* @param options.waitForSuggestions - Wait N ms for port detection suggestions (default: 0)
|
|
591
|
+
* @returns Process info and any detected suggestions
|
|
592
|
+
*/
|
|
593
|
+
async processStart(command, options = {}) {
|
|
594
|
+
const suggestions = [];
|
|
595
|
+
// Collect suggestions during the request
|
|
596
|
+
const response = await this.send({
|
|
597
|
+
type: "process",
|
|
598
|
+
action: "start",
|
|
599
|
+
command,
|
|
600
|
+
processId: options.processId,
|
|
601
|
+
env: options.env,
|
|
602
|
+
}, {
|
|
603
|
+
...options,
|
|
604
|
+
onSuggestion: (s) => suggestions.push(s),
|
|
605
|
+
});
|
|
606
|
+
if (response.data?.success === false) {
|
|
607
|
+
throw new Error(response.data.error || "Process start failed");
|
|
608
|
+
}
|
|
609
|
+
const result = {
|
|
610
|
+
processId: response.data?.processId ?? "",
|
|
611
|
+
pid: response.data?.pid,
|
|
612
|
+
suggestions,
|
|
613
|
+
};
|
|
614
|
+
// Wait for background suggestions (port detection happens ~1.5s after start)
|
|
615
|
+
if (options.waitForSuggestions && options.waitForSuggestions > 0) {
|
|
616
|
+
await new Promise((resolve) => {
|
|
617
|
+
const timeout = setTimeout(resolve, options.waitForSuggestions);
|
|
618
|
+
// Set up temporary global handler to catch late suggestions
|
|
619
|
+
const originalHandler = this.globalSuggestionHandler;
|
|
620
|
+
this.globalSuggestionHandler = (s) => {
|
|
621
|
+
suggestions.push(s);
|
|
622
|
+
// Also call original handler if exists
|
|
623
|
+
originalHandler?.(s);
|
|
624
|
+
};
|
|
625
|
+
// Clean up after timeout
|
|
626
|
+
setTimeout(() => {
|
|
627
|
+
this.globalSuggestionHandler = originalHandler;
|
|
628
|
+
}, options.waitForSuggestions + 100);
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
return result;
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* List running processes
|
|
635
|
+
*/
|
|
636
|
+
async processList(options = {}) {
|
|
637
|
+
const response = await this.send({ type: "process", action: "list" }, options);
|
|
638
|
+
if (response.data?.success === false) {
|
|
639
|
+
throw new Error(response.data.error || "Process list failed");
|
|
640
|
+
}
|
|
641
|
+
return response.data?.processes ?? [];
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Kill a process
|
|
645
|
+
*/
|
|
646
|
+
async processKill(processId, options = {}) {
|
|
647
|
+
const response = await this.send({
|
|
648
|
+
type: "process",
|
|
649
|
+
action: "kill",
|
|
650
|
+
processId,
|
|
651
|
+
}, options);
|
|
652
|
+
if (response.data?.success === false) {
|
|
653
|
+
throw new Error(response.data.error || "Process kill failed");
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Get balance
|
|
658
|
+
*/
|
|
659
|
+
async balance(options = {}) {
|
|
660
|
+
const response = await this.send({ type: "balance" }, options);
|
|
661
|
+
if (response.data?.success === false) {
|
|
662
|
+
throw new Error(response.data.error || "Balance check failed");
|
|
663
|
+
}
|
|
664
|
+
return {
|
|
665
|
+
balance: response.data?.balance ?? 0,
|
|
666
|
+
freeCredits: response.data?.freeCredits ?? 0,
|
|
667
|
+
paidCredits: response.data?.paidCredits ?? 0,
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
// ============================================================================
|
|
672
|
+
// Singleton Instance
|
|
673
|
+
// ============================================================================
|
|
674
|
+
let instance = null;
|
|
675
|
+
/**
|
|
676
|
+
* Get shared transport instance
|
|
677
|
+
*/
|
|
678
|
+
export function getTransport() {
|
|
679
|
+
if (!instance) {
|
|
680
|
+
instance = new PaperpodTransport();
|
|
681
|
+
}
|
|
682
|
+
return instance;
|
|
683
|
+
}
|
|
684
|
+
//# sourceMappingURL=transport.js.map
|