@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.
@@ -0,0 +1,1053 @@
1
+ /**
2
+ * CLI Commands
3
+ *
4
+ * Command implementations for the ppod CLI.
5
+ */
6
+ import * as fs from "fs";
7
+ import * as readline from "readline";
8
+ import { getTransport } from "./transport.js";
9
+ import { getToken, saveToken, clearToken } from "./config.js";
10
+ // ============================================================================
11
+ // ASCII Art
12
+ // ============================================================================
13
+ const PAPERPOD_WELCOME = `
14
+ ██████╗ █████╗ ██████╗ ███████╗██████╗ ██████╗ ██████╗ ██████╗
15
+ ██╔══██╗██╔══██╗██╔══██╗██╔════╝██╔══██╗██╔══██╗██╔═══██╗██╔══██╗
16
+ ██████╔╝███████║██████╔╝█████╗ ██████╔╝██████╔╝██║ ██║██║ ██║
17
+ ██╔═══╝ ██╔══██║██╔═══╝ ██╔══╝ ██╔══██╗██╔═══╝ ██║ ██║██║ ██║
18
+ ██║ ██║ ██║██║ ███████╗██║ ██║██║ ╚██████╔╝██████╔╝
19
+ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═════╝ ╚═════╝
20
+
21
+ Welcome to PaperPod! Token saved to ~/.paperpod/config.json
22
+
23
+ Get started:
24
+ ppod exec "echo Hello, World!"
25
+ ppod exec "python -c 'print(1+1)'"
26
+ ppod status
27
+ ppod help
28
+
29
+ Docs: https://paperpod.dev/docs
30
+ `;
31
+ const PAPERPOD_LOGIN_HELP = `
32
+ ██████╗ █████╗ ██████╗ ███████╗██████╗ ██████╗ ██████╗ ██████╗
33
+ ██╔══██╗██╔══██╗██╔══██╗██╔════╝██╔══██╗██╔══██╗██╔═══██╗██╔══██╗
34
+ ██████╔╝███████║██████╔╝█████╗ ██████╔╝██████╔╝██║ ██║██║ ██║
35
+ ██╔═══╝ ██╔══██║██╔═══╝ ██╔══╝ ██╔══██╗██╔═══╝ ██║ ██║██║ ██║
36
+ ██║ ██║ ██║██║ ███████╗██║ ██║██║ ╚██████╔╝██████╔╝
37
+ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═════╝ ╚═════╝
38
+
39
+ To get your PaperPod token:
40
+
41
+ 1. Request a magic link:
42
+ curl -X POST https://paperpod.dev/login \\
43
+ -H "Content-Type: application/json" \\
44
+ -d '{"email": "you@example.com"}'
45
+
46
+ 2. Check your email and click the magic link
47
+
48
+ 3. Copy the pp_sess_... token from the success page
49
+
50
+ 4. Run: ppod login pp_sess_...
51
+
52
+ Docs: https://paperpod.dev/docs
53
+ `;
54
+ export async function execCommand(command, options = {}) {
55
+ const transport = getTransport();
56
+ const transportOpts = {
57
+ timeout: options.timeout,
58
+ };
59
+ // Stream output if --stream or by default
60
+ if (options.stream !== false) {
61
+ transportOpts.onStdout = (text) => process.stdout.write(text);
62
+ transportOpts.onStderr = (text) => process.stderr.write(text);
63
+ }
64
+ try {
65
+ const result = await transport.exec(command, transportOpts);
66
+ if (options.json) {
67
+ console.log(JSON.stringify(result, null, 2));
68
+ }
69
+ else if (!options.stream) {
70
+ // Non-streaming mode: print output after completion
71
+ if (result.stdout)
72
+ process.stdout.write(result.stdout);
73
+ if (result.stderr)
74
+ process.stderr.write(result.stderr);
75
+ }
76
+ return result.exitCode;
77
+ }
78
+ catch (err) {
79
+ if (options.json) {
80
+ console.log(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }));
81
+ }
82
+ else {
83
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
84
+ }
85
+ return 1;
86
+ }
87
+ finally {
88
+ // Close connection after single command (unless in session mode)
89
+ transport.disconnect();
90
+ }
91
+ }
92
+ export async function writeCommand(remotePath, localPath, options = {}) {
93
+ const transport = getTransport();
94
+ try {
95
+ let content;
96
+ if (localPath) {
97
+ // Read from local file
98
+ if (!fs.existsSync(localPath)) {
99
+ console.error(`Error: File not found: ${localPath}`);
100
+ return 1;
101
+ }
102
+ content = fs.readFileSync(localPath, "utf-8");
103
+ }
104
+ else {
105
+ // Read from stdin
106
+ content = await readStdin();
107
+ }
108
+ await transport.writeFile(remotePath, content, { timeout: options.timeout });
109
+ console.log(`Written: ${remotePath}`);
110
+ return 0;
111
+ }
112
+ catch (err) {
113
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
114
+ return 1;
115
+ }
116
+ finally {
117
+ transport.disconnect();
118
+ }
119
+ }
120
+ export async function readCommand(remotePath, options = {}) {
121
+ const transport = getTransport();
122
+ try {
123
+ const content = await transport.readFile(remotePath, { timeout: options.timeout });
124
+ if (options.output) {
125
+ fs.writeFileSync(options.output, content);
126
+ console.error(`Written to: ${options.output}`);
127
+ }
128
+ else {
129
+ process.stdout.write(content);
130
+ }
131
+ return 0;
132
+ }
133
+ catch (err) {
134
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
135
+ return 1;
136
+ }
137
+ finally {
138
+ transport.disconnect();
139
+ }
140
+ }
141
+ export async function listCommand(remotePath, options = {}) {
142
+ const transport = getTransport();
143
+ try {
144
+ const files = await transport.listDir(remotePath, { timeout: options.timeout });
145
+ if (options.json) {
146
+ console.log(JSON.stringify(files, null, 2));
147
+ }
148
+ else {
149
+ for (const file of files) {
150
+ const prefix = file.isDirectory ? "d" : "-";
151
+ const size = file.size.toString().padStart(10);
152
+ console.log(`${prefix} ${size} ${file.name}`);
153
+ }
154
+ }
155
+ return 0;
156
+ }
157
+ catch (err) {
158
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
159
+ return 1;
160
+ }
161
+ finally {
162
+ transport.disconnect();
163
+ }
164
+ }
165
+ // ============================================================================
166
+ // Login Command
167
+ // ============================================================================
168
+ export async function loginCommand(token) {
169
+ if (token) {
170
+ // Token provided as argument
171
+ saveToken(token);
172
+ console.log(PAPERPOD_WELCOME);
173
+ return 0;
174
+ }
175
+ // Show how to get a token first
176
+ console.log(PAPERPOD_LOGIN_HELP);
177
+ // Interactive mode - prompt for token
178
+ const rl = readline.createInterface({
179
+ input: process.stdin,
180
+ output: process.stdout,
181
+ });
182
+ return new Promise((resolve) => {
183
+ rl.question("Enter your PaperPod token (or press Enter to exit): ", (answer) => {
184
+ rl.close();
185
+ if (answer.trim()) {
186
+ saveToken(answer.trim());
187
+ console.log("\n✓ Token saved to ~/.paperpod/config.json\n");
188
+ console.log(" Get started: ppod exec \"echo Hello, World!\"");
189
+ resolve(0);
190
+ }
191
+ else {
192
+ console.log("No token provided. Run 'ppod login <token>' when you have one.");
193
+ resolve(1);
194
+ }
195
+ });
196
+ });
197
+ }
198
+ // ============================================================================
199
+ // Logout Command
200
+ // ============================================================================
201
+ export async function logoutCommand() {
202
+ clearToken();
203
+ console.log("Token cleared from ~/.paperpod/config.json");
204
+ return 0;
205
+ }
206
+ // ============================================================================
207
+ // Status Command
208
+ // ============================================================================
209
+ export async function statusCommand() {
210
+ const token = getToken();
211
+ console.log("PaperPod CLI Status");
212
+ console.log("-------------------");
213
+ console.log(`Token: ${token ? "Set" : "Not set"}`);
214
+ if (token) {
215
+ const source = process.env.PAPERPOD_TOKEN ? "PAPERPOD_TOKEN env var" : "~/.paperpod/config.json";
216
+ console.log(`Source: ${source}`);
217
+ }
218
+ // Test connection
219
+ if (token) {
220
+ console.log("\nTesting connection...");
221
+ const transport = getTransport();
222
+ try {
223
+ const sandboxId = await transport.connect({ timeout: 10000 });
224
+ console.log(`Connected! Sandbox: ${sandboxId}`);
225
+ transport.disconnect();
226
+ }
227
+ catch (err) {
228
+ console.error(`Connection failed: ${err instanceof Error ? err.message : String(err)}`);
229
+ return 1;
230
+ }
231
+ }
232
+ return 0;
233
+ }
234
+ export async function exposeCommand(port, options = {}) {
235
+ const transport = getTransport();
236
+ try {
237
+ const result = await transport.expose(port, { timeout: options.timeout });
238
+ if (options.json) {
239
+ console.log(JSON.stringify(result, null, 2));
240
+ }
241
+ else {
242
+ console.log(`✓ Port ${port} exposed`);
243
+ console.log(` URL: ${result.url}`);
244
+ if (result.wsUrl) {
245
+ console.log(` WebSocket: ${result.wsUrl}`);
246
+ }
247
+ }
248
+ return 0;
249
+ }
250
+ catch (err) {
251
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
252
+ return 1;
253
+ }
254
+ finally {
255
+ transport.disconnect();
256
+ }
257
+ }
258
+ /**
259
+ * Helper to wrap a browser operation with tracing
260
+ */
261
+ async function withTrace(transport, traceOutput, operation) {
262
+ if (!traceOutput) {
263
+ return { result: await operation() };
264
+ }
265
+ // Start tracing
266
+ await transport.browserTrace("start", {});
267
+ console.log("📍 Tracing enabled...");
268
+ try {
269
+ // Run the operation
270
+ const result = await operation();
271
+ // Stop tracing and get data
272
+ const traceResult = await transport.browserTrace("stop", {});
273
+ if (traceResult.traceData) {
274
+ const traceBuffer = Buffer.from(traceResult.traceData, "base64");
275
+ fs.writeFileSync(traceOutput, traceBuffer);
276
+ console.log(`📍 Trace saved to ${traceOutput}`);
277
+ }
278
+ return { result, traceData: traceResult.traceData };
279
+ }
280
+ catch (err) {
281
+ // Try to stop tracing even on error
282
+ try {
283
+ await transport.browserTrace("stop", {});
284
+ }
285
+ catch {
286
+ // Ignore trace stop errors
287
+ }
288
+ throw err;
289
+ }
290
+ }
291
+ export async function screenshotCommand(url, options = {}) {
292
+ const transport = getTransport();
293
+ try {
294
+ const { result } = await withTrace(transport, options.trace, () => transport.browserScreenshot(url, {
295
+ timeout: options.timeout,
296
+ fullPage: options.fullPage,
297
+ width: options.width,
298
+ height: options.height,
299
+ format: options.format,
300
+ }));
301
+ if (options.output) {
302
+ // Write base64 image to file
303
+ const buffer = Buffer.from(result.image, "base64");
304
+ fs.writeFileSync(options.output, buffer);
305
+ console.log(`Screenshot saved to ${options.output}`);
306
+ }
307
+ else if (options.json) {
308
+ console.log(JSON.stringify({ ...result, image: `[${result.image.length} bytes base64]` }, null, 2));
309
+ }
310
+ else {
311
+ console.log(`Screenshot: ${result.width}x${result.height} ${result.format}`);
312
+ console.log(`Use -o <file> to save the image`);
313
+ }
314
+ return 0;
315
+ }
316
+ catch (err) {
317
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
318
+ return 1;
319
+ }
320
+ finally {
321
+ transport.disconnect();
322
+ }
323
+ }
324
+ export async function pdfCommand(url, options = {}) {
325
+ const transport = getTransport();
326
+ try {
327
+ const { result } = await withTrace(transport, options.trace, () => transport.browserPdf(url, {
328
+ timeout: options.timeout,
329
+ format: options.paperFormat,
330
+ landscape: options.landscape,
331
+ }));
332
+ if (options.output) {
333
+ const buffer = Buffer.from(result.pdf, "base64");
334
+ fs.writeFileSync(options.output, buffer);
335
+ console.log(`PDF saved to ${options.output} (${result.pages} pages)`);
336
+ }
337
+ else if (options.json) {
338
+ console.log(JSON.stringify({ pages: result.pages, pdf: `[${result.pdf.length} bytes base64]` }, null, 2));
339
+ }
340
+ else {
341
+ console.log(`PDF generated: ${result.pages} pages`);
342
+ console.log(`Use -o <file> to save the PDF`);
343
+ }
344
+ return 0;
345
+ }
346
+ catch (err) {
347
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
348
+ return 1;
349
+ }
350
+ finally {
351
+ transport.disconnect();
352
+ }
353
+ }
354
+ export async function scrapeCommand(url, selector, options = {}) {
355
+ const transport = getTransport();
356
+ try {
357
+ const { result } = await withTrace(transport, options.trace, () => transport.browserScrape(url, selector, {
358
+ timeout: options.timeout,
359
+ limit: options.limit,
360
+ }));
361
+ if (options.json) {
362
+ console.log(JSON.stringify(result, null, 2));
363
+ }
364
+ else {
365
+ console.log(`Found ${result.elements.length} elements matching "${selector}":`);
366
+ for (const el of result.elements) {
367
+ console.log(` - ${el.text.slice(0, 100)}${el.text.length > 100 ? "..." : ""}`);
368
+ }
369
+ }
370
+ return 0;
371
+ }
372
+ catch (err) {
373
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
374
+ return 1;
375
+ }
376
+ finally {
377
+ transport.disconnect();
378
+ }
379
+ }
380
+ export async function markdownCommand(url, options = {}) {
381
+ const transport = getTransport();
382
+ try {
383
+ const { result } = await withTrace(transport, options.trace, () => transport.browserMarkdown(url, {
384
+ timeout: options.timeout,
385
+ }));
386
+ if (options.json) {
387
+ console.log(JSON.stringify(result, null, 2));
388
+ }
389
+ else if (options.output) {
390
+ fs.writeFileSync(options.output, result.markdown);
391
+ console.log(`✓ Markdown saved to ${options.output}`);
392
+ }
393
+ else {
394
+ console.log(result.markdown);
395
+ }
396
+ return 0;
397
+ }
398
+ catch (err) {
399
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
400
+ return 1;
401
+ }
402
+ finally {
403
+ transport.disconnect();
404
+ }
405
+ }
406
+ export async function contentCommand(url, options = {}) {
407
+ const transport = getTransport();
408
+ try {
409
+ const { result } = await withTrace(transport, options.trace, () => transport.browserContent(url, {
410
+ timeout: options.timeout,
411
+ }));
412
+ if (options.json) {
413
+ console.log(JSON.stringify(result, null, 2));
414
+ }
415
+ else if (options.output) {
416
+ fs.writeFileSync(options.output, result.html);
417
+ console.log(`✓ HTML saved to ${options.output}`);
418
+ }
419
+ else {
420
+ console.log(result.html);
421
+ }
422
+ return 0;
423
+ }
424
+ catch (err) {
425
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
426
+ return 1;
427
+ }
428
+ finally {
429
+ transport.disconnect();
430
+ }
431
+ }
432
+ export async function traceCommand(action, options = {}) {
433
+ const transport = getTransport();
434
+ try {
435
+ const result = await transport.browserTrace(action, {
436
+ timeout: options.timeout,
437
+ sessionId: options.sessionId,
438
+ });
439
+ if (options.json) {
440
+ console.log(JSON.stringify(result, null, 2));
441
+ }
442
+ else if (action === "start") {
443
+ console.log(`✓ Tracing started${result.sessionId ? ` (session: ${result.sessionId})` : ""}`);
444
+ }
445
+ else if (action === "stop" && result.traceData) {
446
+ if (options.output) {
447
+ const traceBuffer = Buffer.from(result.traceData, "base64");
448
+ fs.writeFileSync(options.output, traceBuffer);
449
+ console.log(`✓ Trace saved to ${options.output}`);
450
+ }
451
+ else {
452
+ console.log(`Trace data: ${result.traceData.length} bytes (base64)`);
453
+ console.log(`Use -o trace.zip to save`);
454
+ }
455
+ }
456
+ return 0;
457
+ }
458
+ catch (err) {
459
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
460
+ return 1;
461
+ }
462
+ finally {
463
+ transport.disconnect();
464
+ }
465
+ }
466
+ export async function testCommand(url, assertionsJson, options = {}) {
467
+ const transport = getTransport();
468
+ try {
469
+ let assertions;
470
+ try {
471
+ assertions = JSON.parse(assertionsJson);
472
+ }
473
+ catch {
474
+ console.error("Error: Assertions must be valid JSON array");
475
+ console.error('Example: \'[{"type":"visible","selector":"h1"},{"type":"text","selector":"h1","expected":"Hello"}]\'');
476
+ return 1;
477
+ }
478
+ const result = await transport.browserTest(url, assertions, {
479
+ timeout: options.timeout,
480
+ });
481
+ if (options.json) {
482
+ console.log(JSON.stringify(result, null, 2));
483
+ }
484
+ else {
485
+ console.log(`Test ${result.passed ? "PASSED ✓" : "FAILED ✗"}`);
486
+ for (const r of result.results) {
487
+ const status = r.passed ? "✓" : "✗";
488
+ console.log(` ${status} ${r.assertion}${r.error ? `: ${r.error}` : ""}`);
489
+ }
490
+ }
491
+ return result.passed ? 0 : 1;
492
+ }
493
+ catch (err) {
494
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
495
+ return 1;
496
+ }
497
+ finally {
498
+ transport.disconnect();
499
+ }
500
+ }
501
+ export async function browserSessionsCommand(options = {}) {
502
+ const transport = getTransport();
503
+ try {
504
+ const sessions = await transport.browserSessions({
505
+ timeout: options.timeout,
506
+ });
507
+ if (options.json) {
508
+ console.log(JSON.stringify(sessions, null, 2));
509
+ }
510
+ else if (sessions.length === 0) {
511
+ console.log("No active browser sessions");
512
+ }
513
+ else {
514
+ console.log("Active browser sessions:");
515
+ for (const s of sessions) {
516
+ console.log(` ${s.sessionId} - ${s.url ?? "no URL"} (created: ${s.createdAt})`);
517
+ }
518
+ }
519
+ return 0;
520
+ }
521
+ catch (err) {
522
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
523
+ return 1;
524
+ }
525
+ finally {
526
+ transport.disconnect();
527
+ }
528
+ }
529
+ export async function browserLimitsCommand(options = {}) {
530
+ const transport = getTransport();
531
+ try {
532
+ const limits = await transport.browserLimits({
533
+ timeout: options.timeout,
534
+ });
535
+ if (options.json) {
536
+ console.log(JSON.stringify(limits, null, 2));
537
+ }
538
+ else {
539
+ console.log("Browser limits:");
540
+ console.log(` Max concurrent sessions: ${limits.maxConcurrent}`);
541
+ console.log(` Currently active: ${limits.currentActive}`);
542
+ console.log(` Max session duration: ${limits.maxSessionDuration / 1000}s`);
543
+ }
544
+ return 0;
545
+ }
546
+ catch (err) {
547
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
548
+ return 1;
549
+ }
550
+ finally {
551
+ transport.disconnect();
552
+ }
553
+ }
554
+ export async function aiGenerateCommand(prompt, options = {}) {
555
+ const transport = getTransport();
556
+ try {
557
+ const result = await transport.aiGenerate(prompt, {
558
+ timeout: options.timeout,
559
+ model: options.model,
560
+ systemPrompt: options.systemPrompt,
561
+ maxTokens: options.maxTokens,
562
+ temperature: options.temperature,
563
+ });
564
+ if (options.json) {
565
+ console.log(JSON.stringify(result, null, 2));
566
+ }
567
+ else {
568
+ console.log(result.text);
569
+ }
570
+ return 0;
571
+ }
572
+ catch (err) {
573
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
574
+ return 1;
575
+ }
576
+ finally {
577
+ transport.disconnect();
578
+ }
579
+ }
580
+ export async function aiEmbedCommand(text, options = {}) {
581
+ const transport = getTransport();
582
+ try {
583
+ const result = await transport.aiEmbed(text, {
584
+ timeout: options.timeout,
585
+ model: options.model,
586
+ });
587
+ if (options.json) {
588
+ console.log(JSON.stringify(result, null, 2));
589
+ }
590
+ else {
591
+ console.log(`Model: ${result.model}`);
592
+ console.log(`Dimensions: ${result.embeddings[0]?.length ?? 0}`);
593
+ console.log(`Embeddings: ${result.embeddings.length} vector(s)`);
594
+ // Show first few dimensions as preview
595
+ if (result.embeddings[0]) {
596
+ const preview = result.embeddings[0].slice(0, 5).map(n => n.toFixed(4)).join(", ");
597
+ console.log(`Preview: [${preview}, ...]`);
598
+ }
599
+ }
600
+ return 0;
601
+ }
602
+ catch (err) {
603
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
604
+ return 1;
605
+ }
606
+ finally {
607
+ transport.disconnect();
608
+ }
609
+ }
610
+ export async function aiImageCommand(prompt, options = {}) {
611
+ const transport = getTransport();
612
+ try {
613
+ const result = await transport.aiImage(prompt, {
614
+ timeout: options.timeout ?? 120000, // Images take longer
615
+ model: options.model,
616
+ width: options.width,
617
+ height: options.height,
618
+ });
619
+ // Check for empty image and warn user
620
+ if (!result.image || result.image.length === 0) {
621
+ console.error("⚠ Warning: Image generation returned empty data");
622
+ console.error(" This may be a temporary issue with the image model.");
623
+ console.error(" Try again or use a different model with --model <name>");
624
+ if (options.json) {
625
+ console.log(JSON.stringify({ ...result, warning: "Image data empty" }, null, 2));
626
+ }
627
+ return 1;
628
+ }
629
+ if (options.output) {
630
+ const imageBuffer = Buffer.from(result.image, "base64");
631
+ fs.writeFileSync(options.output, imageBuffer);
632
+ console.log(`✓ Image saved to ${options.output} (${imageBuffer.length} bytes)`);
633
+ }
634
+ else if (options.json) {
635
+ console.log(JSON.stringify(result, null, 2));
636
+ }
637
+ else {
638
+ console.log(`Model: ${result.model}`);
639
+ console.log(`Image: ${result.image.length} bytes (base64)`);
640
+ console.log(`Use -o filename.png to save`);
641
+ }
642
+ return 0;
643
+ }
644
+ catch (err) {
645
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
646
+ return 1;
647
+ }
648
+ finally {
649
+ transport.disconnect();
650
+ }
651
+ }
652
+ export async function aiTranscribeCommand(audioPath, options = {}) {
653
+ const transport = getTransport();
654
+ try {
655
+ if (!fs.existsSync(audioPath)) {
656
+ console.error(`Error: File not found: ${audioPath}`);
657
+ return 1;
658
+ }
659
+ const audioBuffer = fs.readFileSync(audioPath);
660
+ const audioBase64 = audioBuffer.toString("base64");
661
+ const result = await transport.aiTranscribe(audioBase64, {
662
+ timeout: options.timeout ?? 120000, // Transcription takes longer
663
+ model: options.model,
664
+ });
665
+ if (options.json) {
666
+ console.log(JSON.stringify(result, null, 2));
667
+ }
668
+ else {
669
+ console.log(result.text);
670
+ }
671
+ return 0;
672
+ }
673
+ catch (err) {
674
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
675
+ return 1;
676
+ }
677
+ finally {
678
+ transport.disconnect();
679
+ }
680
+ }
681
+ export async function interpretCommand(code, options = {}) {
682
+ const transport = getTransport();
683
+ try {
684
+ const result = await transport.interpret(code, {
685
+ timeout: options.timeout,
686
+ language: options.language,
687
+ });
688
+ if (options.json) {
689
+ console.log(JSON.stringify(result, null, 2));
690
+ }
691
+ else {
692
+ for (const item of result.results) {
693
+ if (item.type === "text" && item.text) {
694
+ console.log(item.text);
695
+ }
696
+ else if (item.type === "image" && item.data) {
697
+ console.log(`[Image: ${item.mimeType ?? "image/png"}, ${item.data.length} bytes base64]`);
698
+ }
699
+ else if (item.type === "error" && item.text) {
700
+ console.error(`Error: ${item.text}`);
701
+ }
702
+ }
703
+ }
704
+ return 0;
705
+ }
706
+ catch (err) {
707
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
708
+ return 1;
709
+ }
710
+ finally {
711
+ transport.disconnect();
712
+ }
713
+ }
714
+ export async function memoryWriteCommand(path, localPath, options = {}) {
715
+ const transport = getTransport();
716
+ try {
717
+ let content;
718
+ if (localPath) {
719
+ if (!fs.existsSync(localPath)) {
720
+ console.error(`Error: File not found: ${localPath}`);
721
+ return 1;
722
+ }
723
+ content = fs.readFileSync(localPath, "utf-8");
724
+ }
725
+ else {
726
+ content = await readStdin();
727
+ }
728
+ await transport.memoryWrite(path, content, { timeout: options.timeout });
729
+ console.log(`✓ Written to memory: ${path}`);
730
+ return 0;
731
+ }
732
+ catch (err) {
733
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
734
+ return 1;
735
+ }
736
+ finally {
737
+ transport.disconnect();
738
+ }
739
+ }
740
+ export async function memoryReadCommand(path, options = {}) {
741
+ const transport = getTransport();
742
+ try {
743
+ const content = await transport.memoryRead(path, { timeout: options.timeout });
744
+ if (options.output) {
745
+ fs.writeFileSync(options.output, content);
746
+ console.log(`Memory content written to ${options.output}`);
747
+ }
748
+ else {
749
+ process.stdout.write(content);
750
+ }
751
+ return 0;
752
+ }
753
+ catch (err) {
754
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
755
+ return 1;
756
+ }
757
+ finally {
758
+ transport.disconnect();
759
+ }
760
+ }
761
+ export async function memoryListCommand(prefix, options = {}) {
762
+ const transport = getTransport();
763
+ try {
764
+ const files = await transport.memoryList(prefix, { timeout: options.timeout });
765
+ if (options.json) {
766
+ console.log(JSON.stringify(files, null, 2));
767
+ }
768
+ else {
769
+ if (files.length === 0) {
770
+ console.log("No files in memory");
771
+ }
772
+ else {
773
+ console.log(`Memory files${prefix ? ` (prefix: ${prefix})` : ""}:`);
774
+ for (const file of files) {
775
+ const size = file.size.toString().padStart(10);
776
+ console.log(` ${size} ${file.path}`);
777
+ }
778
+ }
779
+ }
780
+ return 0;
781
+ }
782
+ catch (err) {
783
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
784
+ return 1;
785
+ }
786
+ finally {
787
+ transport.disconnect();
788
+ }
789
+ }
790
+ export async function memoryDeleteCommand(path, options = {}) {
791
+ const transport = getTransport();
792
+ try {
793
+ await transport.memoryDelete(path, { timeout: options.timeout });
794
+ console.log(`✓ Deleted from memory: ${path}`);
795
+ return 0;
796
+ }
797
+ catch (err) {
798
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
799
+ return 1;
800
+ }
801
+ finally {
802
+ transport.disconnect();
803
+ }
804
+ }
805
+ export async function memoryUsageCommand(options = {}) {
806
+ const transport = getTransport();
807
+ try {
808
+ const usage = await transport.memoryUsage({ timeout: options.timeout });
809
+ if (options.json) {
810
+ console.log(JSON.stringify(usage, null, 2));
811
+ }
812
+ else {
813
+ const usedMB = (usage.usedBytes / 1024 / 1024).toFixed(2);
814
+ const quotaMB = (usage.quotaBytes / 1024 / 1024).toFixed(0);
815
+ const pct = ((usage.usedBytes / usage.quotaBytes) * 100).toFixed(1);
816
+ console.log(`Memory usage: ${usedMB} MB / ${quotaMB} MB (${pct}%)`);
817
+ console.log(`Files: ${usage.fileCount}`);
818
+ }
819
+ return 0;
820
+ }
821
+ catch (err) {
822
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
823
+ return 1;
824
+ }
825
+ finally {
826
+ transport.disconnect();
827
+ }
828
+ }
829
+ export async function processStartCommand(command, options = {}) {
830
+ const transport = getTransport();
831
+ try {
832
+ // Wait 2s for port detection suggestions (server detects after ~1.5s)
833
+ const result = await transport.processStart(command, {
834
+ timeout: options.timeout,
835
+ processId: options.processId,
836
+ waitForSuggestions: 2000,
837
+ });
838
+ if (options.json) {
839
+ console.log(JSON.stringify(result, null, 2));
840
+ }
841
+ else {
842
+ console.log(`✓ Process started: ${result.processId}`);
843
+ if (result.pid) {
844
+ console.log(` PID: ${result.pid}`);
845
+ }
846
+ // Show port detection suggestions
847
+ for (const suggestion of result.suggestions) {
848
+ if (suggestion.category === "port_detected" && suggestion.action?.port) {
849
+ console.log(`\n💡 Detected server on port ${suggestion.action.port}`);
850
+ console.log(` Run: ppod expose ${suggestion.action.port}`);
851
+ }
852
+ }
853
+ }
854
+ return 0;
855
+ }
856
+ catch (err) {
857
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
858
+ return 1;
859
+ }
860
+ finally {
861
+ transport.disconnect();
862
+ }
863
+ }
864
+ export async function processListCommand(options = {}) {
865
+ const transport = getTransport();
866
+ try {
867
+ const processes = await transport.processList({ timeout: options.timeout });
868
+ if (options.json) {
869
+ console.log(JSON.stringify(processes, null, 2));
870
+ }
871
+ else {
872
+ if (processes.length === 0) {
873
+ console.log("No running processes");
874
+ }
875
+ else {
876
+ console.log("Running processes:");
877
+ for (const proc of processes) {
878
+ console.log(` ${proc.processId} [${proc.status}] ${proc.command}`);
879
+ }
880
+ }
881
+ }
882
+ return 0;
883
+ }
884
+ catch (err) {
885
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
886
+ return 1;
887
+ }
888
+ finally {
889
+ transport.disconnect();
890
+ }
891
+ }
892
+ export async function processKillCommand(processId, options = {}) {
893
+ const transport = getTransport();
894
+ try {
895
+ await transport.processKill(processId, { timeout: options.timeout });
896
+ console.log(`✓ Process killed: ${processId}`);
897
+ return 0;
898
+ }
899
+ catch (err) {
900
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
901
+ return 1;
902
+ }
903
+ finally {
904
+ transport.disconnect();
905
+ }
906
+ }
907
+ // ============================================================================
908
+ // Balance Command
909
+ // ============================================================================
910
+ export async function balanceCommand(options = {}) {
911
+ const transport = getTransport();
912
+ try {
913
+ const result = await transport.balance({ timeout: options.timeout });
914
+ if (options.json) {
915
+ console.log(JSON.stringify(result, null, 2));
916
+ }
917
+ else {
918
+ const total = (result.balance / 1_000_000).toFixed(2);
919
+ const free = (result.freeCredits / 1_000_000).toFixed(2);
920
+ const paid = (result.paidCredits / 1_000_000).toFixed(2);
921
+ console.log(`Balance: $${total}`);
922
+ console.log(` Free credits: $${free}`);
923
+ console.log(` Paid credits: $${paid}`);
924
+ }
925
+ return 0;
926
+ }
927
+ catch (err) {
928
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
929
+ return 1;
930
+ }
931
+ finally {
932
+ transport.disconnect();
933
+ }
934
+ }
935
+ // ============================================================================
936
+ // Help Command
937
+ // ============================================================================
938
+ const VERSION = "0.2.0";
939
+ const HELP_TEXT = `
940
+ ██████╗ █████╗ ██████╗ ███████╗██████╗ ██████╗ ██████╗ ██████╗
941
+ ██╔══██╗██╔══██╗██╔══██╗██╔════╝██╔══██╗██╔══██╗██╔═══██╗██╔══██╗
942
+ ██████╔╝███████║██████╔╝█████╗ ██████╔╝██████╔╝██║ ██║██║ ██║
943
+ ██╔═══╝ ██╔══██║██╔═══╝ ██╔══╝ ██╔══██╗██╔═══╝ ██║ ██║██║ ██║
944
+ ██║ ██║ ██║██║ ███████╗██║ ██║██║ ╚██████╔╝██████╔╝
945
+ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═════╝ ╚═════╝
946
+ cli v${VERSION}
947
+
948
+ USAGE:
949
+ ppod <command> [options]
950
+
951
+ COMMANDS:
952
+ Sandbox:
953
+ exec <command> Execute a shell command
954
+ write <path> [file] Write file (stdin if no file)
955
+ read <path> Read file from sandbox
956
+ ls [path] List directory contents
957
+
958
+ Processes:
959
+ ps List running processes
960
+ start <command> Start background process
961
+ kill <id> Kill a process
962
+
963
+ Ports:
964
+ expose <port> Expose port → get public URL
965
+
966
+ Browser (use browser: prefix or short form):
967
+ browser:screenshot <url> Take screenshot (-o file.png)
968
+ browser:pdf <url> Generate PDF (-o file.pdf)
969
+ browser:scrape <url> [sel] Scrape elements (default: body)
970
+ browser:markdown <url> Extract markdown
971
+ browser:content <url> Get rendered HTML
972
+ browser:trace start|stop Browser tracing
973
+ browser:test <url> <json> Run assertions
974
+ browser:sessions List active sessions
975
+ browser:limits Check browser limits
976
+
977
+ AI:
978
+ ai <prompt> Generate text with LLM
979
+ ai:embed <text> Generate embeddings
980
+ ai:image <prompt> Generate image (-o file.png)
981
+ ai:transcribe <audio> Transcribe audio to text
982
+
983
+ Code:
984
+ interpret <code> Rich output (charts, images)
985
+
986
+ Memory (persistent):
987
+ mem:write <path> [file] Write to memory
988
+ mem:read <path> Read from memory
989
+ mem:ls [prefix] List memory files
990
+ mem:rm <path> Delete from memory
991
+ mem:usage Check quota
992
+
993
+ Account:
994
+ login [token] Save auth token
995
+ logout Clear token
996
+ status Connection status
997
+ balance Check credit balance
998
+
999
+ OPTIONS:
1000
+ --timeout <ms> Command timeout (default: 60000)
1001
+ --json Output as JSON
1002
+ --no-stream Disable streaming (exec only)
1003
+ -o, --output <file> Save output to file
1004
+ -h, --help Show help for a command
1005
+ --trace <file> Capture browser trace (browser:* commands)
1006
+
1007
+ EXAMPLES:
1008
+ ppod exec "python train.py"
1009
+ ppod start "node server.js" && ppod expose 3000
1010
+ ppod browser:screenshot https://example.com -o shot.png
1011
+ ppod browser:scrape https://example.com ".article"
1012
+ ppod ai "Write a haiku about coding"
1013
+ ppod ai:image "A sunset over mountains" -o sunset.png
1014
+ ppod mem:write state.json ./local.json
1015
+
1016
+ # Per-command help:
1017
+ ppod browser:screenshot --help
1018
+ ppod ai --help
1019
+
1020
+ UPDATE:
1021
+ npm update -g @paperpod/cli
1022
+
1023
+ ENVIRONMENT:
1024
+ PAPERPOD_TOKEN Auth token (overrides config)
1025
+ PAPERPOD_API_URL API URL (default: wss://paperpod.dev/ws)
1026
+
1027
+ Docs: https://paperpod.dev/docs
1028
+ `;
1029
+ export function helpCommand() {
1030
+ console.log(HELP_TEXT);
1031
+ return 0;
1032
+ }
1033
+ export function versionCommand() {
1034
+ console.log(`ppod v${VERSION}`);
1035
+ return 0;
1036
+ }
1037
+ // ============================================================================
1038
+ // Helpers
1039
+ // ============================================================================
1040
+ async function readStdin() {
1041
+ return new Promise((resolve, reject) => {
1042
+ let data = "";
1043
+ process.stdin.setEncoding("utf-8");
1044
+ process.stdin.on("data", (chunk) => {
1045
+ data += chunk;
1046
+ });
1047
+ process.stdin.on("end", () => {
1048
+ resolve(data);
1049
+ });
1050
+ process.stdin.on("error", reject);
1051
+ });
1052
+ }
1053
+ //# sourceMappingURL=commands.js.map