@plaud-ai/cli 0.1.5 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +665 -82
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -2,21 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import "dotenv/config";
|
|
5
|
-
import { Command as
|
|
5
|
+
import { Command as Command13 } from "commander";
|
|
6
6
|
|
|
7
7
|
// src/commands/login.ts
|
|
8
8
|
import { Command } from "commander";
|
|
9
|
-
import { createServer } from "
|
|
9
|
+
import { createServer as createServer2 } from "net";
|
|
10
10
|
import open from "open";
|
|
11
11
|
import chalk2 from "chalk";
|
|
12
12
|
import ora from "ora";
|
|
13
13
|
|
|
14
|
-
// src/config.ts
|
|
15
|
-
import { readFileSync, existsSync } from "fs";
|
|
16
|
-
import { homedir as homedir2 } from "os";
|
|
17
|
-
import { join as join2 } from "path";
|
|
18
|
-
import { parse } from "yaml";
|
|
19
|
-
|
|
20
14
|
// ../shared/dist/oauth.js
|
|
21
15
|
import { randomBytes, createHash } from "crypto";
|
|
22
16
|
|
|
@@ -61,6 +55,9 @@ function generateCodeVerifier() {
|
|
|
61
55
|
function generateCodeChallenge(verifier) {
|
|
62
56
|
return createHash("sha256").update(verifier).digest("base64url");
|
|
63
57
|
}
|
|
58
|
+
function generateState() {
|
|
59
|
+
return randomBytes(16).toString("base64url");
|
|
60
|
+
}
|
|
64
61
|
var OAuth = class {
|
|
65
62
|
config;
|
|
66
63
|
tokenStore;
|
|
@@ -77,17 +74,19 @@ var OAuth = class {
|
|
|
77
74
|
createAuthorizationRequest() {
|
|
78
75
|
const codeVerifier = generateCodeVerifier();
|
|
79
76
|
const codeChallenge = generateCodeChallenge(codeVerifier);
|
|
77
|
+
const state = generateState();
|
|
80
78
|
const params = new URLSearchParams({
|
|
81
79
|
client_id: this.config.clientId,
|
|
82
80
|
redirect_uri: this.config.redirectUri,
|
|
83
81
|
response_type: "code",
|
|
84
82
|
code_challenge: codeChallenge,
|
|
85
|
-
code_challenge_method: "S256"
|
|
83
|
+
code_challenge_method: "S256",
|
|
84
|
+
state
|
|
86
85
|
});
|
|
87
86
|
return {
|
|
88
87
|
url: `${this.authorizationUrl}?${params.toString()}`,
|
|
89
88
|
codeVerifier,
|
|
90
|
-
state
|
|
89
|
+
state
|
|
91
90
|
};
|
|
92
91
|
}
|
|
93
92
|
/**
|
|
@@ -96,7 +95,7 @@ var OAuth = class {
|
|
|
96
95
|
getAuthorizationUrl() {
|
|
97
96
|
return this.createAuthorizationRequest().url;
|
|
98
97
|
}
|
|
99
|
-
async exchangeCode(code, codeVerifier) {
|
|
98
|
+
async exchangeCode(code, codeVerifier, state) {
|
|
100
99
|
const basicAuth = Buffer.from(`${this.config.clientId}:${this.config.clientSecret}`).toString("base64");
|
|
101
100
|
const body = {
|
|
102
101
|
code,
|
|
@@ -105,6 +104,9 @@ var OAuth = class {
|
|
|
105
104
|
if (codeVerifier) {
|
|
106
105
|
body.code_verifier = codeVerifier;
|
|
107
106
|
}
|
|
107
|
+
if (state) {
|
|
108
|
+
body.state = state;
|
|
109
|
+
}
|
|
108
110
|
const res = await fetch(this.tokenUrl, {
|
|
109
111
|
method: "POST",
|
|
110
112
|
headers: {
|
|
@@ -182,16 +184,18 @@ var PlaudClient = class {
|
|
|
182
184
|
oauth;
|
|
183
185
|
apiBase;
|
|
184
186
|
extraHeaders;
|
|
187
|
+
staticToken;
|
|
185
188
|
constructor(config) {
|
|
186
189
|
this.oauth = new OAuth(config);
|
|
187
190
|
this.apiBase = config.apiBase ?? DEFAULT_API_BASE;
|
|
188
191
|
this.extraHeaders = config.extraHeaders ?? {};
|
|
192
|
+
this.staticToken = config.staticToken;
|
|
189
193
|
}
|
|
190
194
|
get auth() {
|
|
191
195
|
return this.oauth;
|
|
192
196
|
}
|
|
193
197
|
async request(path, init) {
|
|
194
|
-
const token = await this.oauth.getAccessToken();
|
|
198
|
+
const token = this.staticToken ?? await this.oauth.getAccessToken();
|
|
195
199
|
if (!token) {
|
|
196
200
|
throw new Error("Not authenticated. Please login first.");
|
|
197
201
|
}
|
|
@@ -238,7 +242,128 @@ var PlaudClient = class {
|
|
|
238
242
|
}
|
|
239
243
|
};
|
|
240
244
|
|
|
245
|
+
// ../shared/dist/oauth-callback-server.js
|
|
246
|
+
import { createServer } from "http";
|
|
247
|
+
var SUCCESS_HTML = '<!doctype html><html><head><meta charset="utf-8"><title>Plaud</title></head><body style="font-family:system-ui;padding:2rem;text-align:center;"><h1>Authorization successful!</h1><p>You can close this tab.</p></body></html>';
|
|
248
|
+
var NEUTRAL_HTML = '<!doctype html><html><head><meta charset="utf-8"><title>Plaud</title></head><body style="font-family:system-ui;padding:2rem;text-align:center;"><h1>Continue authorization in the original window.</h1><p>This page can be closed.</p></body></html>';
|
|
249
|
+
function errorHtml(message) {
|
|
250
|
+
const escaped = message.replace(/[&<>]/g, (c) => ({ "&": "&", "<": "<", ">": ">" })[c]);
|
|
251
|
+
return '<!doctype html><html><head><meta charset="utf-8"><title>Plaud</title></head><body style="font-family:system-ui;padding:2rem;text-align:center;"><h1>Authorization failed</h1><pre style="white-space:pre-wrap;">' + escaped + "</pre></body></html>";
|
|
252
|
+
}
|
|
253
|
+
var CORS_HEADERS = {
|
|
254
|
+
"Access-Control-Allow-Origin": "*",
|
|
255
|
+
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
|
256
|
+
"Access-Control-Allow-Headers": "*"
|
|
257
|
+
};
|
|
258
|
+
function runOAuthCallback(opts) {
|
|
259
|
+
const { port, expectedState, exchangeCode, timeoutMs = 12e4, onListening, postSuccessDelayMs = 1500 } = opts;
|
|
260
|
+
return new Promise((resolve) => {
|
|
261
|
+
let settled = false;
|
|
262
|
+
let exchangeStarted = false;
|
|
263
|
+
let exchangeSucceeded = false;
|
|
264
|
+
let timeoutId = null;
|
|
265
|
+
let closeTimeoutId = null;
|
|
266
|
+
const server = createServer((req, res) => {
|
|
267
|
+
if (req.method === "OPTIONS") {
|
|
268
|
+
res.writeHead(204, CORS_HEADERS);
|
|
269
|
+
res.end();
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const reqUrl = new URL(req.url ?? "/", `http://localhost:${port}`);
|
|
273
|
+
if (reqUrl.pathname !== "/auth/callback") {
|
|
274
|
+
res.writeHead(404, CORS_HEADERS);
|
|
275
|
+
res.end();
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const params = reqUrl.searchParams;
|
|
279
|
+
const error = params.get("error");
|
|
280
|
+
const state = params.get("state");
|
|
281
|
+
const code = params.get("code");
|
|
282
|
+
if (error) {
|
|
283
|
+
const desc = params.get("error_description") ?? error;
|
|
284
|
+
res.writeHead(400, { "Content-Type": "text/html", ...CORS_HEADERS });
|
|
285
|
+
res.end(errorHtml(`Authorization denied: ${desc}`));
|
|
286
|
+
finalize({ status: "denied", error: new Error(desc) });
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (!state || state !== expectedState) {
|
|
290
|
+
respondNeutral(res);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (exchangeSucceeded) {
|
|
294
|
+
respondSuccess(res);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (!code) {
|
|
298
|
+
respondNeutral(res);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (exchangeStarted) {
|
|
302
|
+
respondNeutral(res);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
exchangeStarted = true;
|
|
306
|
+
exchangeCode(code).then(() => {
|
|
307
|
+
exchangeSucceeded = true;
|
|
308
|
+
respondSuccess(res);
|
|
309
|
+
finalize({ status: "success" });
|
|
310
|
+
}, (err) => {
|
|
311
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
312
|
+
res.writeHead(500, { "Content-Type": "text/html", ...CORS_HEADERS });
|
|
313
|
+
res.end(errorHtml(e.message));
|
|
314
|
+
finalize({ status: "exchange-failed", error: e });
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
server.on("error", (err) => {
|
|
318
|
+
if (settled)
|
|
319
|
+
return;
|
|
320
|
+
const message = err.code === "EADDRINUSE" ? `port ${port} is in use \u2014 another \`plaud login\` may still be running. Wait a few seconds and retry.` : `callback server error: ${err.message}`;
|
|
321
|
+
finalize({ status: "listen-failed", error: new Error(message) }, true);
|
|
322
|
+
});
|
|
323
|
+
timeoutId = setTimeout(() => {
|
|
324
|
+
finalize({ status: "timeout" }, true);
|
|
325
|
+
}, timeoutMs);
|
|
326
|
+
server.listen(port, () => {
|
|
327
|
+
onListening?.();
|
|
328
|
+
});
|
|
329
|
+
function respondSuccess(res) {
|
|
330
|
+
res.writeHead(200, { "Content-Type": "text/html", ...CORS_HEADERS });
|
|
331
|
+
res.end(SUCCESS_HTML);
|
|
332
|
+
}
|
|
333
|
+
function respondNeutral(res) {
|
|
334
|
+
res.writeHead(200, { "Content-Type": "text/html", ...CORS_HEADERS });
|
|
335
|
+
res.end(NEUTRAL_HTML);
|
|
336
|
+
}
|
|
337
|
+
function finalize(result, immediate = false) {
|
|
338
|
+
if (settled)
|
|
339
|
+
return;
|
|
340
|
+
settled = true;
|
|
341
|
+
if (timeoutId) {
|
|
342
|
+
clearTimeout(timeoutId);
|
|
343
|
+
timeoutId = null;
|
|
344
|
+
}
|
|
345
|
+
const close = () => {
|
|
346
|
+
try {
|
|
347
|
+
server.closeAllConnections?.();
|
|
348
|
+
} catch {
|
|
349
|
+
}
|
|
350
|
+
server.close(() => resolve(result));
|
|
351
|
+
};
|
|
352
|
+
if (immediate || result.status !== "success") {
|
|
353
|
+
close();
|
|
354
|
+
} else {
|
|
355
|
+
closeTimeoutId = setTimeout(close, postSuccessDelayMs);
|
|
356
|
+
closeTimeoutId.unref?.();
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
241
362
|
// src/config.ts
|
|
363
|
+
import { readFileSync, existsSync } from "fs";
|
|
364
|
+
import { homedir as homedir2 } from "os";
|
|
365
|
+
import { join as join2 } from "path";
|
|
366
|
+
import { parse } from "yaml";
|
|
242
367
|
function loadCliConfig() {
|
|
243
368
|
const configPath = join2(homedir2(), ".plaud", "cli.yaml");
|
|
244
369
|
if (!existsSync(configPath)) return {};
|
|
@@ -306,66 +431,99 @@ function isTimeoutError(err) {
|
|
|
306
431
|
}
|
|
307
432
|
|
|
308
433
|
// src/commands/login.ts
|
|
309
|
-
var
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
434
|
+
var CALLBACK_PORT = 8199;
|
|
435
|
+
var LOGIN_TIMEOUT_MS = 12e4;
|
|
436
|
+
function probeCallbackPort(port) {
|
|
437
|
+
return new Promise((resolve) => {
|
|
438
|
+
const probe = createServer2();
|
|
439
|
+
probe.once("error", (err) => resolve(err));
|
|
440
|
+
probe.listen(port, "127.0.0.1", () => {
|
|
441
|
+
probe.close(() => resolve(null));
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
}
|
|
314
445
|
var loginCommand = new Command("login").description("Authenticate with Plaud via OAuth").action(async () => {
|
|
315
446
|
const client2 = getClient();
|
|
316
447
|
try {
|
|
317
448
|
const token = await client2.auth.getAccessToken();
|
|
318
449
|
if (token) {
|
|
319
|
-
|
|
320
|
-
|
|
450
|
+
try {
|
|
451
|
+
await client2.getCurrentUser();
|
|
452
|
+
console.log(chalk2.yellow("Already logged in. Run `plaud logout` first to switch accounts."));
|
|
453
|
+
return;
|
|
454
|
+
} catch (err) {
|
|
455
|
+
if (isAuthError(err)) {
|
|
456
|
+
console.log(chalk2.yellow("Existing credentials are no longer valid. Starting fresh login..."));
|
|
457
|
+
await client2.auth.logout();
|
|
458
|
+
} else if (isNetworkError(err)) {
|
|
459
|
+
printError("UNREACHABLE", "Cannot reach Plaud servers to verify login state. Check your network.", err);
|
|
460
|
+
process.exit(ExitCode.UNREACHABLE);
|
|
461
|
+
} else if (isTimeoutError(err)) {
|
|
462
|
+
printError("TIMEOUT", "Login state check timed out.");
|
|
463
|
+
process.exit(ExitCode.TIMEOUT);
|
|
464
|
+
} else {
|
|
465
|
+
printError("FETCH_FAILED", "Failed to verify login state.", err);
|
|
466
|
+
process.exit(ExitCode.ERROR);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
321
469
|
}
|
|
322
470
|
} catch {
|
|
323
471
|
await client2.auth.logout();
|
|
324
472
|
}
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
res.end();
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
const code = reqUrl.searchParams.get("code");
|
|
340
|
-
if (!code) {
|
|
341
|
-
res.writeHead(400, CORS_HEADERS);
|
|
342
|
-
res.end("Invalid callback: missing code");
|
|
343
|
-
spinner.stop();
|
|
344
|
-
printError("AUTH_FAILED", "Authentication failed: missing authorization code");
|
|
345
|
-
server.close(() => process.exit(ExitCode.AUTH_FAILED));
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
try {
|
|
349
|
-
await client2.auth.exchangeCode(code, codeVerifier);
|
|
350
|
-
res.writeHead(200, { "Content-Type": "text/html", ...CORS_HEADERS });
|
|
351
|
-
res.end("<h1>Authentication successful!</h1><p>You can close this tab.</p>");
|
|
352
|
-
spinner.succeed("Logged in successfully!");
|
|
353
|
-
} catch (err) {
|
|
354
|
-
res.writeHead(500, CORS_HEADERS);
|
|
355
|
-
res.end("Token exchange failed");
|
|
356
|
-
spinner.stop();
|
|
357
|
-
printError("AUTH_FAILED", "Authentication failed.", err);
|
|
358
|
-
} finally {
|
|
359
|
-
server.closeAllConnections();
|
|
360
|
-
server.close(() => process.exit(0));
|
|
473
|
+
const portError = await probeCallbackPort(CALLBACK_PORT);
|
|
474
|
+
if (portError) {
|
|
475
|
+
if (portError.code === "EADDRINUSE") {
|
|
476
|
+
printError(
|
|
477
|
+
"PORT_IN_USE",
|
|
478
|
+
`OAuth callback port ${CALLBACK_PORT} is already in use. Another Plaud process is likely holding it (e.g. \`plaud-mcp http\` or another \`plaud login\`). The OAuth redirect_uri is fixed to localhost:${CALLBACK_PORT}, so login cannot proceed until the port is free.`
|
|
479
|
+
);
|
|
480
|
+
console.error(chalk2.gray(` Find the process: lsof -nP -iTCP:${CALLBACK_PORT} -sTCP:LISTEN`));
|
|
481
|
+
console.error(chalk2.gray(` Then stop it and retry \`plaud login\`.`));
|
|
482
|
+
} else {
|
|
483
|
+
printError("PORT_PROBE_FAILED", `Could not bind callback port ${CALLBACK_PORT}.`, portError);
|
|
361
484
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
485
|
+
process.exit(ExitCode.ERROR);
|
|
486
|
+
}
|
|
487
|
+
const { url, codeVerifier, state } = client2.auth.createAuthorizationRequest();
|
|
488
|
+
const spinner = ora("Waiting for browser authentication...").start();
|
|
489
|
+
const result = await runOAuthCallback({
|
|
490
|
+
port: CALLBACK_PORT,
|
|
491
|
+
expectedState: state,
|
|
492
|
+
timeoutMs: LOGIN_TIMEOUT_MS,
|
|
493
|
+
exchangeCode: async (code) => {
|
|
494
|
+
await client2.auth.exchangeCode(code, codeVerifier, state);
|
|
495
|
+
},
|
|
496
|
+
onListening: () => {
|
|
497
|
+
console.log(chalk2.blue(`
|
|
365
498
|
Opening browser for authentication...
|
|
366
499
|
`));
|
|
367
|
-
|
|
500
|
+
open(url).catch(() => {
|
|
501
|
+
console.log(chalk2.yellow(`Could not open browser. Open this URL manually:
|
|
502
|
+
${url}`));
|
|
503
|
+
});
|
|
504
|
+
}
|
|
368
505
|
});
|
|
506
|
+
switch (result.status) {
|
|
507
|
+
case "success":
|
|
508
|
+
spinner.succeed("Logged in successfully!");
|
|
509
|
+
process.exit(ExitCode.OK);
|
|
510
|
+
case "timeout":
|
|
511
|
+
spinner.stop();
|
|
512
|
+
printError("AUTH_FAILED", "Authentication timed out after 2 minutes.");
|
|
513
|
+
process.exit(ExitCode.TIMEOUT);
|
|
514
|
+
case "denied":
|
|
515
|
+
spinner.stop();
|
|
516
|
+
printError("AUTH_FAILED", "Authentication was denied.", result.error);
|
|
517
|
+
process.exit(ExitCode.AUTH_FAILED);
|
|
518
|
+
case "exchange-failed":
|
|
519
|
+
spinner.stop();
|
|
520
|
+
printError("AUTH_FAILED", "Authentication failed.", result.error);
|
|
521
|
+
process.exit(ExitCode.AUTH_FAILED);
|
|
522
|
+
case "listen-failed":
|
|
523
|
+
spinner.stop();
|
|
524
|
+
printError("AUTH_FAILED", "Could not start callback server.", result.error);
|
|
525
|
+
process.exit(ExitCode.ERROR);
|
|
526
|
+
}
|
|
369
527
|
});
|
|
370
528
|
|
|
371
529
|
// src/commands/logout.ts
|
|
@@ -424,6 +582,42 @@ var meCommand = new Command3("me").description("Show current authenticated user
|
|
|
424
582
|
import { Command as Command4 } from "commander";
|
|
425
583
|
import chalk5 from "chalk";
|
|
426
584
|
import ora3 from "ora";
|
|
585
|
+
|
|
586
|
+
// src/format.ts
|
|
587
|
+
function formatDuration(ms) {
|
|
588
|
+
if (!ms || ms < 0) return "-";
|
|
589
|
+
const totalSeconds = Math.floor(ms / 1e3);
|
|
590
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
591
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
592
|
+
const seconds = totalSeconds % 60;
|
|
593
|
+
if (hours > 0) return `${hours}h${String(minutes).padStart(2, "0")}m`;
|
|
594
|
+
if (minutes > 0) return `${minutes}m${String(seconds).padStart(2, "0")}s`;
|
|
595
|
+
return `${seconds}s`;
|
|
596
|
+
}
|
|
597
|
+
function formatDate(iso) {
|
|
598
|
+
if (!iso) return "-";
|
|
599
|
+
const d = new Date(iso);
|
|
600
|
+
if (Number.isNaN(d.getTime())) return iso;
|
|
601
|
+
const y = d.getFullYear();
|
|
602
|
+
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
603
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
604
|
+
return `${y}-${m}-${day}`;
|
|
605
|
+
}
|
|
606
|
+
function formatTime(ms) {
|
|
607
|
+
const totalSeconds = Math.floor(ms / 1e3);
|
|
608
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
609
|
+
const seconds = totalSeconds % 60;
|
|
610
|
+
return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// src/commands/list-files.ts
|
|
614
|
+
var ID_WIDTH = 34;
|
|
615
|
+
var NAME_WIDTH = 36;
|
|
616
|
+
var DATE_WIDTH = 12;
|
|
617
|
+
function truncate(s, width) {
|
|
618
|
+
if (s.length <= width) return s.padEnd(width);
|
|
619
|
+
return s.slice(0, width - 1) + "\u2026";
|
|
620
|
+
}
|
|
427
621
|
var listFilesCommand = new Command4("files").description("List your Plaud recordings").option("-p, --page <number>", "Page number", "1").option("-s, --page-size <number>", "Page size", "20").action(async (opts) => {
|
|
428
622
|
const page = parseInt(opts.page);
|
|
429
623
|
const pageSize = parseInt(opts.pageSize);
|
|
@@ -443,10 +637,15 @@ var listFilesCommand = new Command4("files").description("List your Plaud record
|
|
|
443
637
|
console.log(chalk5.bold(`
|
|
444
638
|
Files on this page: ${result.data.length}
|
|
445
639
|
`));
|
|
640
|
+
const header = ` ${chalk5.bold("ID".padEnd(ID_WIDTH))} ${chalk5.bold("NAME".padEnd(NAME_WIDTH))} ${chalk5.bold("DATE".padEnd(DATE_WIDTH))} ${chalk5.bold("DURATION")}`;
|
|
641
|
+
console.log(header);
|
|
642
|
+
console.log(chalk5.gray(" " + "\u2500".repeat(ID_WIDTH + NAME_WIDTH + DATE_WIDTH + 16)));
|
|
446
643
|
for (const file of result.data) {
|
|
447
|
-
const
|
|
448
|
-
const
|
|
449
|
-
|
|
644
|
+
const id = chalk5.cyan(file.id.padEnd(ID_WIDTH));
|
|
645
|
+
const name = truncate(file.name ?? "", NAME_WIDTH);
|
|
646
|
+
const date = chalk5.gray(formatDate(file.created_at).padEnd(DATE_WIDTH));
|
|
647
|
+
const duration = chalk5.gray(formatDuration(file.duration));
|
|
648
|
+
console.log(` ${id} ${name} ${date} ${duration}`);
|
|
450
649
|
}
|
|
451
650
|
console.log(chalk5.gray(`
|
|
452
651
|
Page ${result.page}`));
|
|
@@ -473,12 +672,6 @@ Page ${result.page}`));
|
|
|
473
672
|
import { Command as Command5 } from "commander";
|
|
474
673
|
import chalk6 from "chalk";
|
|
475
674
|
import ora4 from "ora";
|
|
476
|
-
function formatDuration(ms) {
|
|
477
|
-
const totalSeconds = Math.floor(ms / 1e3);
|
|
478
|
-
const minutes = Math.floor(totalSeconds / 60);
|
|
479
|
-
const seconds = totalSeconds % 60;
|
|
480
|
-
return minutes > 0 ? `${minutes}min ${seconds}sec` : `${seconds}sec`;
|
|
481
|
-
}
|
|
482
675
|
var getFileCommand = new Command5("file").description("Get details of a specific Plaud recording").argument("<file_id>", "The file ID to retrieve").action(async (fileId) => {
|
|
483
676
|
const client2 = getClient();
|
|
484
677
|
const spinner = ora4("Fetching file...").start();
|
|
@@ -494,7 +687,7 @@ var getFileCommand = new Command5("file").description("Get details of a specific
|
|
|
494
687
|
console.log(` ${chalk6.cyan("name")}: ${file.name}`);
|
|
495
688
|
console.log(` ${chalk6.cyan("created_at")}: ${file.created_at}`);
|
|
496
689
|
console.log(` ${chalk6.cyan("start_at")}: ${file.start_at ?? "-"}`);
|
|
497
|
-
console.log(` ${chalk6.cyan("duration")}: ${
|
|
690
|
+
console.log(` ${chalk6.cyan("duration")}: ${formatDuration(file.duration)}`);
|
|
498
691
|
console.log(` ${chalk6.cyan("serial_number")}: ${file.serial_number ?? "-"}`);
|
|
499
692
|
console.log(` ${chalk6.cyan("audio")}: ${file.presigned_url ? chalk6.green("available") : chalk6.gray("unavailable")}`);
|
|
500
693
|
console.log(` ${chalk6.cyan("transcript")}: ${hasTranscript ? chalk6.green("available") : chalk6.gray("unavailable")}`);
|
|
@@ -565,12 +758,6 @@ import { Command as Command7 } from "commander";
|
|
|
565
758
|
import chalk8 from "chalk";
|
|
566
759
|
import ora6 from "ora";
|
|
567
760
|
import { writeFile as writeFile2 } from "fs/promises";
|
|
568
|
-
function formatTime(ms) {
|
|
569
|
-
const totalSeconds = Math.floor(ms / 1e3);
|
|
570
|
-
const minutes = Math.floor(totalSeconds / 60);
|
|
571
|
-
const seconds = totalSeconds % 60;
|
|
572
|
-
return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
|
|
573
|
-
}
|
|
574
761
|
var transcriptCommand = new Command7("transcript").description("Get the transcript for a Plaud recording").argument("<file_id>", "The file ID to retrieve transcript for").option("-o, --output <file>", "Save transcript to a file").action(async (fileId, opts) => {
|
|
575
762
|
const client2 = getClient();
|
|
576
763
|
const spinner = ora6("Fetching transcript...").start();
|
|
@@ -667,16 +854,408 @@ Summary: ${file.name}
|
|
|
667
854
|
});
|
|
668
855
|
|
|
669
856
|
// src/commands/version.ts
|
|
857
|
+
import { Command as Command10 } from "commander";
|
|
858
|
+
import chalk11 from "chalk";
|
|
859
|
+
import { readFile as readFile2, writeFile as writeFile4, mkdir as mkdir2 } from "fs/promises";
|
|
860
|
+
import { homedir as homedir3 } from "os";
|
|
861
|
+
import { join as join3, dirname } from "path";
|
|
862
|
+
|
|
863
|
+
// src/commands/update.ts
|
|
670
864
|
import { Command as Command9 } from "commander";
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
865
|
+
import chalk10 from "chalk";
|
|
866
|
+
import ora8 from "ora";
|
|
867
|
+
var PKG_NAME = "@plaud-ai/cli";
|
|
868
|
+
var REGISTRY = "https://registry.npmjs.org";
|
|
869
|
+
async function fetchLatestVersion(timeoutMs = 5e3) {
|
|
870
|
+
const ctrl = new AbortController();
|
|
871
|
+
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
872
|
+
try {
|
|
873
|
+
const res = await fetch(`${REGISTRY}/${encodeURIComponent(PKG_NAME)}/latest`, { signal: ctrl.signal });
|
|
874
|
+
if (!res.ok) return null;
|
|
875
|
+
const json = await res.json();
|
|
876
|
+
return json.version ?? null;
|
|
877
|
+
} catch {
|
|
878
|
+
return null;
|
|
879
|
+
} finally {
|
|
880
|
+
clearTimeout(t);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
function isNewer(current, latest) {
|
|
884
|
+
const c = current.split(".").map(Number);
|
|
885
|
+
const l = latest.split(".").map(Number);
|
|
886
|
+
for (let i = 0; i < Math.max(c.length, l.length); i++) {
|
|
887
|
+
const cv = c[i] ?? 0;
|
|
888
|
+
const lv = l[i] ?? 0;
|
|
889
|
+
if (lv > cv) return true;
|
|
890
|
+
if (lv < cv) return false;
|
|
891
|
+
}
|
|
892
|
+
return false;
|
|
893
|
+
}
|
|
894
|
+
var updateCommand = new Command9("update").description("Check npm for the latest Plaud CLI and print the upgrade command").action(async () => {
|
|
895
|
+
const current = "0.2.4";
|
|
896
|
+
const spinner = ora8("Checking npm for latest version...").start();
|
|
897
|
+
const latest = await fetchLatestVersion();
|
|
898
|
+
spinner.stop();
|
|
899
|
+
if (!latest) {
|
|
900
|
+
printError("UNREACHABLE", "Could not reach npm registry to check for updates.");
|
|
901
|
+
process.exit(ExitCode.UNREACHABLE);
|
|
902
|
+
}
|
|
903
|
+
if (!isNewer(current, latest)) {
|
|
904
|
+
console.log(chalk10.green(`You're on the latest version (${current}).`));
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
console.log(chalk10.yellow(`A newer version is available: ${current} \u2192 ${latest}`));
|
|
908
|
+
console.log();
|
|
909
|
+
console.log("Run this command to upgrade:");
|
|
910
|
+
console.log();
|
|
911
|
+
console.log(chalk10.bold(` npm install -g ${PKG_NAME}@latest`));
|
|
912
|
+
console.log();
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
// src/commands/version.ts
|
|
916
|
+
var CACHE_PATH = join3(homedir3(), ".plaud", "version-check.json");
|
|
917
|
+
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
918
|
+
async function readCache() {
|
|
919
|
+
try {
|
|
920
|
+
const raw = await readFile2(CACHE_PATH, "utf-8");
|
|
921
|
+
const parsed = JSON.parse(raw);
|
|
922
|
+
if (typeof parsed.checked_at !== "number" || typeof parsed.latest !== "string") return null;
|
|
923
|
+
return parsed;
|
|
924
|
+
} catch {
|
|
925
|
+
return null;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
async function writeCache(entry) {
|
|
929
|
+
try {
|
|
930
|
+
await mkdir2(dirname(CACHE_PATH), { recursive: true });
|
|
931
|
+
await writeFile4(CACHE_PATH, JSON.stringify(entry), "utf-8");
|
|
932
|
+
} catch {
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
async function checkForUpdate(current) {
|
|
936
|
+
const cached = await readCache();
|
|
937
|
+
const now = Date.now();
|
|
938
|
+
let latest = null;
|
|
939
|
+
if (cached && now - cached.checked_at < CACHE_TTL_MS) {
|
|
940
|
+
latest = cached.latest;
|
|
941
|
+
} else {
|
|
942
|
+
latest = await fetchLatestVersion(2e3);
|
|
943
|
+
if (latest) await writeCache({ checked_at: now, latest });
|
|
944
|
+
}
|
|
945
|
+
if (!latest) return null;
|
|
946
|
+
return isNewer(current, latest) ? latest : null;
|
|
947
|
+
}
|
|
948
|
+
var versionCommand = new Command10("version").description("Show CLI version information").action(async () => {
|
|
949
|
+
const current = "0.2.4";
|
|
950
|
+
console.log(`plaud ${current}`);
|
|
951
|
+
if ("4b84277") console.log(`commit ${"4b84277"}`);
|
|
952
|
+
if ("2026-05-07T11:34:37.562Z") console.log(`built ${"2026-05-07T11:34:37.562Z"}`);
|
|
953
|
+
if (current === "unknown") return;
|
|
954
|
+
const newer = await checkForUpdate(current);
|
|
955
|
+
if (newer) {
|
|
956
|
+
console.log();
|
|
957
|
+
console.log(chalk11.yellow(`A newer version is available: ${current} \u2192 ${newer}`));
|
|
958
|
+
console.log(chalk11.gray(`Run \`plaud update\` for upgrade instructions.`));
|
|
959
|
+
}
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
// src/commands/search.ts
|
|
963
|
+
import { Command as Command11 } from "commander";
|
|
964
|
+
import chalk12 from "chalk";
|
|
965
|
+
import ora9 from "ora";
|
|
966
|
+
var MAX_PAGES = 5;
|
|
967
|
+
var PAGE_SIZE = 100;
|
|
968
|
+
function parseDate(s) {
|
|
969
|
+
if (!s) return null;
|
|
970
|
+
const d = new Date(s);
|
|
971
|
+
return Number.isNaN(d.getTime()) ? null : d.getTime();
|
|
972
|
+
}
|
|
973
|
+
var searchCommand = new Command11("search").description("Search recordings by name keyword (client-side, scans up to 500 most recent recordings)").argument("<keyword>", "Case-insensitive substring to match against recording names").option("--from <date>", "Start date inclusive, YYYY-MM-DD").option("--to <date>", "End date inclusive, YYYY-MM-DD").option("--max <n>", "Maximum matches to display", "50").action(async (keyword, opts) => {
|
|
974
|
+
const max = parseInt(opts.max);
|
|
975
|
+
if (isNaN(max) || max < 1 || max > 500) {
|
|
976
|
+
printError("INVALID_ARGS", "--max must be a number between 1 and 500");
|
|
977
|
+
process.exit(ExitCode.ERROR);
|
|
978
|
+
}
|
|
979
|
+
const from = parseDate(opts.from);
|
|
980
|
+
const toRaw = parseDate(opts.to);
|
|
981
|
+
const to = toRaw !== null ? toRaw + 24 * 60 * 60 * 1e3 - 1 : null;
|
|
982
|
+
if (opts.from && from === null) {
|
|
983
|
+
printError("INVALID_ARGS", `Invalid --from date: ${opts.from}`);
|
|
984
|
+
process.exit(ExitCode.ERROR);
|
|
985
|
+
}
|
|
986
|
+
if (opts.to && toRaw === null) {
|
|
987
|
+
printError("INVALID_ARGS", `Invalid --to date: ${opts.to}`);
|
|
988
|
+
process.exit(ExitCode.ERROR);
|
|
989
|
+
}
|
|
990
|
+
const client2 = getClient();
|
|
991
|
+
const spinner = ora9(`Searching for "${keyword}"...`).start();
|
|
992
|
+
const q = keyword.toLowerCase();
|
|
993
|
+
try {
|
|
994
|
+
const matches = [];
|
|
995
|
+
let scanned = 0;
|
|
996
|
+
let truncated = false;
|
|
997
|
+
for (let page = 1; page <= MAX_PAGES; page++) {
|
|
998
|
+
spinner.text = `Searching for "${keyword}"... (page ${page})`;
|
|
999
|
+
const result = await client2.listFiles(page, PAGE_SIZE);
|
|
1000
|
+
scanned += result.data.length;
|
|
1001
|
+
for (const file of result.data) {
|
|
1002
|
+
if (!(file.name ?? "").toLowerCase().includes(q)) continue;
|
|
1003
|
+
if (from !== null || to !== null) {
|
|
1004
|
+
const created = parseDate(file.created_at);
|
|
1005
|
+
if (created === null) continue;
|
|
1006
|
+
if (from !== null && created < from) continue;
|
|
1007
|
+
if (to !== null && created > to) continue;
|
|
1008
|
+
}
|
|
1009
|
+
matches.push(file);
|
|
1010
|
+
if (matches.length >= max) break;
|
|
1011
|
+
}
|
|
1012
|
+
if (matches.length >= max) break;
|
|
1013
|
+
if (result.data.length < PAGE_SIZE) break;
|
|
1014
|
+
if (page === MAX_PAGES) truncated = true;
|
|
1015
|
+
}
|
|
1016
|
+
spinner.stop();
|
|
1017
|
+
if (matches.length === 0) {
|
|
1018
|
+
console.log(chalk12.yellow(`No recordings matched "${keyword}" in ${scanned} scanned.`));
|
|
1019
|
+
if (truncated) {
|
|
1020
|
+
console.log(chalk12.gray(`(Scanned first ${MAX_PAGES * PAGE_SIZE}; narrow the window with --from/--to if your target is older.)`));
|
|
1021
|
+
}
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
console.log(chalk12.bold(`
|
|
1025
|
+
Matched ${matches.length}${truncated ? `+` : ""} of ${scanned} scanned
|
|
1026
|
+
`));
|
|
1027
|
+
for (const file of matches) {
|
|
1028
|
+
const id = chalk12.cyan(file.id);
|
|
1029
|
+
const date = chalk12.gray(formatDate(file.created_at));
|
|
1030
|
+
const duration = chalk12.gray(formatDuration(file.duration));
|
|
1031
|
+
console.log(` ${id} ${file.name ?? ""} ${date} ${duration}`);
|
|
1032
|
+
}
|
|
1033
|
+
if (truncated) {
|
|
1034
|
+
console.log(chalk12.gray(`
|
|
1035
|
+
(Truncated at ${MAX_PAGES} pages; results beyond the most recent ${MAX_PAGES * PAGE_SIZE} not scanned.)`));
|
|
1036
|
+
}
|
|
1037
|
+
console.log();
|
|
1038
|
+
} catch (err) {
|
|
1039
|
+
spinner.stop();
|
|
1040
|
+
if (isAuthError(err)) {
|
|
1041
|
+
printError("AUTH_FAILED", "Token invalid or expired. Run `plaud login`.");
|
|
1042
|
+
process.exit(ExitCode.AUTH_FAILED);
|
|
1043
|
+
}
|
|
1044
|
+
if (isNetworkError(err)) {
|
|
1045
|
+
printError("UNREACHABLE", "Cannot reach Plaud servers. Check your network.", err);
|
|
1046
|
+
process.exit(ExitCode.UNREACHABLE);
|
|
1047
|
+
}
|
|
1048
|
+
if (isTimeoutError(err)) {
|
|
1049
|
+
printError("TIMEOUT", "Request timed out.");
|
|
1050
|
+
process.exit(ExitCode.TIMEOUT);
|
|
1051
|
+
}
|
|
1052
|
+
printError("FETCH_FAILED", "Failed to search files.", err);
|
|
1053
|
+
process.exit(ExitCode.ERROR);
|
|
1054
|
+
}
|
|
1055
|
+
});
|
|
1056
|
+
|
|
1057
|
+
// src/commands/recent.ts
|
|
1058
|
+
import { Command as Command12 } from "commander";
|
|
1059
|
+
import chalk13 from "chalk";
|
|
1060
|
+
import ora10 from "ora";
|
|
1061
|
+
var MAX_PAGES2 = 3;
|
|
1062
|
+
var PAGE_SIZE2 = 100;
|
|
1063
|
+
var recentCommand = new Command12("recent").description("List recordings from the last N days (default 7)").option("-d, --days <n>", "Number of days to include", "7").action(async (opts) => {
|
|
1064
|
+
const days = parseInt(opts.days);
|
|
1065
|
+
if (isNaN(days) || days < 1 || days > 365) {
|
|
1066
|
+
printError("INVALID_ARGS", "--days must be a number between 1 and 365");
|
|
1067
|
+
process.exit(ExitCode.ERROR);
|
|
1068
|
+
}
|
|
1069
|
+
const from = Date.now() - days * 24 * 60 * 60 * 1e3;
|
|
1070
|
+
const client2 = getClient();
|
|
1071
|
+
const spinner = ora10(`Fetching recordings from the last ${days} days...`).start();
|
|
1072
|
+
try {
|
|
1073
|
+
const matches = [];
|
|
1074
|
+
for (let page = 1; page <= MAX_PAGES2; page++) {
|
|
1075
|
+
const result = await client2.listFiles(page, PAGE_SIZE2);
|
|
1076
|
+
let allOlder = true;
|
|
1077
|
+
let hasValidTimestamps = false;
|
|
1078
|
+
for (const file of result.data) {
|
|
1079
|
+
const created = new Date(file.created_at).getTime();
|
|
1080
|
+
if (Number.isNaN(created)) continue;
|
|
1081
|
+
hasValidTimestamps = true;
|
|
1082
|
+
if (created >= from) {
|
|
1083
|
+
matches.push(file);
|
|
1084
|
+
allOlder = false;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
if (hasValidTimestamps && allOlder && result.data.length > 0) break;
|
|
1088
|
+
if (result.data.length < PAGE_SIZE2) break;
|
|
1089
|
+
}
|
|
1090
|
+
spinner.stop();
|
|
1091
|
+
if (matches.length === 0) {
|
|
1092
|
+
console.log(chalk13.yellow(`No recordings in the last ${days} days.`));
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
console.log(chalk13.bold(`
|
|
1096
|
+
Recordings in the last ${days} days: ${matches.length}
|
|
1097
|
+
`));
|
|
1098
|
+
for (const file of matches) {
|
|
1099
|
+
const id = chalk13.cyan(file.id);
|
|
1100
|
+
const date = chalk13.gray(formatDate(file.created_at));
|
|
1101
|
+
const duration = chalk13.gray(formatDuration(file.duration));
|
|
1102
|
+
console.log(` ${id} ${file.name ?? ""} ${date} ${duration}`);
|
|
1103
|
+
}
|
|
1104
|
+
console.log();
|
|
1105
|
+
} catch (err) {
|
|
1106
|
+
spinner.stop();
|
|
1107
|
+
if (isAuthError(err)) {
|
|
1108
|
+
printError("AUTH_FAILED", "Token invalid or expired. Run `plaud login`.");
|
|
1109
|
+
process.exit(ExitCode.AUTH_FAILED);
|
|
1110
|
+
}
|
|
1111
|
+
if (isNetworkError(err)) {
|
|
1112
|
+
printError("UNREACHABLE", "Cannot reach Plaud servers. Check your network.", err);
|
|
1113
|
+
process.exit(ExitCode.UNREACHABLE);
|
|
1114
|
+
}
|
|
1115
|
+
if (isTimeoutError(err)) {
|
|
1116
|
+
printError("TIMEOUT", "Request timed out.");
|
|
1117
|
+
process.exit(ExitCode.TIMEOUT);
|
|
1118
|
+
}
|
|
1119
|
+
printError("FETCH_FAILED", "Failed to fetch recent files.", err);
|
|
1120
|
+
process.exit(ExitCode.ERROR);
|
|
1121
|
+
}
|
|
1122
|
+
});
|
|
1123
|
+
var todayCommand = new Command12("today").description("List recordings created today").action(async () => {
|
|
1124
|
+
const start = /* @__PURE__ */ new Date();
|
|
1125
|
+
start.setHours(0, 0, 0, 0);
|
|
1126
|
+
const startMs = start.getTime();
|
|
1127
|
+
const client2 = getClient();
|
|
1128
|
+
const spinner = ora10("Fetching today's recordings...").start();
|
|
1129
|
+
try {
|
|
1130
|
+
const result = await client2.listFiles(1, 50);
|
|
1131
|
+
spinner.stop();
|
|
1132
|
+
const matches = result.data.filter((f) => {
|
|
1133
|
+
const t = new Date(f.created_at).getTime();
|
|
1134
|
+
return !Number.isNaN(t) && t >= startMs;
|
|
1135
|
+
});
|
|
1136
|
+
if (matches.length === 0) {
|
|
1137
|
+
console.log(chalk13.yellow("No recordings created today."));
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
console.log(chalk13.bold(`
|
|
1141
|
+
Today's recordings: ${matches.length}
|
|
1142
|
+
`));
|
|
1143
|
+
for (const file of matches) {
|
|
1144
|
+
const id = chalk13.cyan(file.id);
|
|
1145
|
+
const date = chalk13.gray(formatDate(file.created_at));
|
|
1146
|
+
const duration = chalk13.gray(formatDuration(file.duration));
|
|
1147
|
+
console.log(` ${id} ${file.name ?? ""} ${date} ${duration}`);
|
|
1148
|
+
}
|
|
1149
|
+
console.log();
|
|
1150
|
+
} catch (err) {
|
|
1151
|
+
spinner.stop();
|
|
1152
|
+
if (isAuthError(err)) {
|
|
1153
|
+
printError("AUTH_FAILED", "Token invalid or expired. Run `plaud login`.");
|
|
1154
|
+
process.exit(ExitCode.AUTH_FAILED);
|
|
1155
|
+
}
|
|
1156
|
+
if (isNetworkError(err)) {
|
|
1157
|
+
printError("UNREACHABLE", "Cannot reach Plaud servers. Check your network.", err);
|
|
1158
|
+
process.exit(ExitCode.UNREACHABLE);
|
|
1159
|
+
}
|
|
1160
|
+
if (isTimeoutError(err)) {
|
|
1161
|
+
printError("TIMEOUT", "Request timed out.");
|
|
1162
|
+
process.exit(ExitCode.TIMEOUT);
|
|
1163
|
+
}
|
|
1164
|
+
printError("FETCH_FAILED", "Failed to fetch today's files.", err);
|
|
1165
|
+
process.exit(ExitCode.ERROR);
|
|
1166
|
+
}
|
|
675
1167
|
});
|
|
676
1168
|
|
|
1169
|
+
// src/commands/wizard.ts
|
|
1170
|
+
import chalk14 from "chalk";
|
|
1171
|
+
import { select, input, confirm } from "@inquirer/prompts";
|
|
1172
|
+
import { spawnSync } from "child_process";
|
|
1173
|
+
function runSelf(args) {
|
|
1174
|
+
const result = spawnSync(process.execPath, [process.argv[1] ?? "", ...args], {
|
|
1175
|
+
stdio: "inherit"
|
|
1176
|
+
});
|
|
1177
|
+
return result.status ?? 0;
|
|
1178
|
+
}
|
|
1179
|
+
async function ensureLoggedIn() {
|
|
1180
|
+
const client2 = getClient();
|
|
1181
|
+
try {
|
|
1182
|
+
const token = await client2.auth.getAccessToken();
|
|
1183
|
+
if (token) return true;
|
|
1184
|
+
} catch {
|
|
1185
|
+
}
|
|
1186
|
+
const go = await confirm({ message: "You are not logged in. Open the browser to log in now?", default: true });
|
|
1187
|
+
if (!go) return false;
|
|
1188
|
+
const code = runSelf(["login"]);
|
|
1189
|
+
return code === 0;
|
|
1190
|
+
}
|
|
1191
|
+
async function runWizard() {
|
|
1192
|
+
console.log(chalk14.bold("\nWelcome to Plaud\n"));
|
|
1193
|
+
try {
|
|
1194
|
+
const ok = await ensureLoggedIn();
|
|
1195
|
+
if (!ok) {
|
|
1196
|
+
console.log(chalk14.gray("Exiting. Run `plaud login` when ready."));
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
const action = await select({
|
|
1200
|
+
message: "What do you want to do?",
|
|
1201
|
+
choices: [
|
|
1202
|
+
{ name: "Browse my recordings", value: "browse" },
|
|
1203
|
+
{ name: "Search by name", value: "search" },
|
|
1204
|
+
{ name: "Recent recordings (7 days)", value: "recent" },
|
|
1205
|
+
{ name: "Today's recordings", value: "today" },
|
|
1206
|
+
{ name: "Read a transcript", value: "transcript" },
|
|
1207
|
+
{ name: "Read an AI summary", value: "summary" },
|
|
1208
|
+
{ name: "Who am I logged in as", value: "me" },
|
|
1209
|
+
{ name: "Log out", value: "logout" },
|
|
1210
|
+
{ name: "Quit", value: "quit" }
|
|
1211
|
+
]
|
|
1212
|
+
});
|
|
1213
|
+
switch (action) {
|
|
1214
|
+
case "browse":
|
|
1215
|
+
runSelf(["files"]);
|
|
1216
|
+
break;
|
|
1217
|
+
case "search": {
|
|
1218
|
+
const keyword = await input({ message: "Keyword to search for:", validate: (v) => v.trim().length > 0 || "Enter a keyword" });
|
|
1219
|
+
runSelf(["search", keyword.trim()]);
|
|
1220
|
+
break;
|
|
1221
|
+
}
|
|
1222
|
+
case "recent":
|
|
1223
|
+
runSelf(["recent"]);
|
|
1224
|
+
break;
|
|
1225
|
+
case "today":
|
|
1226
|
+
runSelf(["today"]);
|
|
1227
|
+
break;
|
|
1228
|
+
case "transcript": {
|
|
1229
|
+
const id = await input({ message: "File ID:", validate: (v) => v.trim().length > 0 || "Enter a file ID" });
|
|
1230
|
+
runSelf(["transcript", id.trim()]);
|
|
1231
|
+
break;
|
|
1232
|
+
}
|
|
1233
|
+
case "summary": {
|
|
1234
|
+
const id = await input({ message: "File ID:", validate: (v) => v.trim().length > 0 || "Enter a file ID" });
|
|
1235
|
+
runSelf(["summary", id.trim()]);
|
|
1236
|
+
break;
|
|
1237
|
+
}
|
|
1238
|
+
case "me":
|
|
1239
|
+
runSelf(["me"]);
|
|
1240
|
+
break;
|
|
1241
|
+
case "logout":
|
|
1242
|
+
runSelf(["logout"]);
|
|
1243
|
+
break;
|
|
1244
|
+
case "quit":
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
} catch (err) {
|
|
1248
|
+
const name = err?.name;
|
|
1249
|
+
if (name === "ExitPromptError") return;
|
|
1250
|
+
throw err;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
|
|
677
1254
|
// src/index.ts
|
|
678
|
-
var program = new
|
|
679
|
-
program.name("plaud").description("Plaud CLI - manage your Plaud recordings")
|
|
1255
|
+
var program = new Command13();
|
|
1256
|
+
program.name("plaud").description("Plaud CLI - manage your Plaud recordings").action(async () => {
|
|
1257
|
+
await runWizard();
|
|
1258
|
+
});
|
|
680
1259
|
program.addCommand(loginCommand);
|
|
681
1260
|
program.addCommand(logoutCommand);
|
|
682
1261
|
program.addCommand(meCommand);
|
|
@@ -685,5 +1264,9 @@ program.addCommand(getFileCommand);
|
|
|
685
1264
|
program.addCommand(audioCommand);
|
|
686
1265
|
program.addCommand(transcriptCommand);
|
|
687
1266
|
program.addCommand(summaryCommand);
|
|
1267
|
+
program.addCommand(searchCommand);
|
|
1268
|
+
program.addCommand(recentCommand);
|
|
1269
|
+
program.addCommand(todayCommand);
|
|
1270
|
+
program.addCommand(updateCommand);
|
|
688
1271
|
program.addCommand(versionCommand);
|
|
689
|
-
program.
|
|
1272
|
+
program.parseAsync();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plaud-ai/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"plaud": "dist/index.js"
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"clean": "rm -rf dist"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
+
"@inquirer/prompts": "^7.2.0",
|
|
17
18
|
"chalk": "^5.4.0",
|
|
18
19
|
"commander": "^13.0.0",
|
|
19
20
|
"dotenv": "^17.3.1",
|