@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
package/dist/commands.js
ADDED
|
@@ -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
|