@plaud-ai/cli 0.2.0 → 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 +204 -56
- package/package.json +8 -12
package/dist/index.js
CHANGED
|
@@ -6,17 +6,11 @@ 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
|
|
|
@@ -248,7 +242,128 @@ var PlaudClient = class {
|
|
|
248
242
|
}
|
|
249
243
|
};
|
|
250
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
|
+
|
|
251
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";
|
|
252
367
|
function loadCliConfig() {
|
|
253
368
|
const configPath = join2(homedir2(), ".plaud", "cli.yaml");
|
|
254
369
|
if (!existsSync(configPath)) return {};
|
|
@@ -316,66 +431,99 @@ function isTimeoutError(err) {
|
|
|
316
431
|
}
|
|
317
432
|
|
|
318
433
|
// src/commands/login.ts
|
|
319
|
-
var
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
+
}
|
|
324
445
|
var loginCommand = new Command("login").description("Authenticate with Plaud via OAuth").action(async () => {
|
|
325
446
|
const client2 = getClient();
|
|
326
447
|
try {
|
|
327
448
|
const token = await client2.auth.getAccessToken();
|
|
328
449
|
if (token) {
|
|
329
|
-
|
|
330
|
-
|
|
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
|
+
}
|
|
331
469
|
}
|
|
332
470
|
} catch {
|
|
333
471
|
await client2.auth.logout();
|
|
334
472
|
}
|
|
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);
|
|
484
|
+
}
|
|
485
|
+
process.exit(ExitCode.ERROR);
|
|
486
|
+
}
|
|
335
487
|
const { url, codeVerifier, state } = client2.auth.createAuthorizationRequest();
|
|
336
488
|
const spinner = ora("Waiting for browser authentication...").start();
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
}
|
|
343
|
-
const reqUrl = new URL(req.url, `http://localhost:8199`);
|
|
344
|
-
if (reqUrl.pathname !== "/auth/callback") {
|
|
345
|
-
res.writeHead(404, CORS_HEADERS);
|
|
346
|
-
res.end();
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
const code = reqUrl.searchParams.get("code");
|
|
350
|
-
if (!code) {
|
|
351
|
-
res.writeHead(400, CORS_HEADERS);
|
|
352
|
-
res.end("Invalid callback: missing code");
|
|
353
|
-
spinner.stop();
|
|
354
|
-
printError("AUTH_FAILED", "Authentication failed: missing authorization code");
|
|
355
|
-
server.close(() => process.exit(ExitCode.AUTH_FAILED));
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
try {
|
|
489
|
+
const result = await runOAuthCallback({
|
|
490
|
+
port: CALLBACK_PORT,
|
|
491
|
+
expectedState: state,
|
|
492
|
+
timeoutMs: LOGIN_TIMEOUT_MS,
|
|
493
|
+
exchangeCode: async (code) => {
|
|
359
494
|
await client2.auth.exchangeCode(code, codeVerifier, state);
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
} catch (err) {
|
|
364
|
-
res.writeHead(500, CORS_HEADERS);
|
|
365
|
-
res.end("Token exchange failed");
|
|
366
|
-
spinner.stop();
|
|
367
|
-
printError("AUTH_FAILED", "Authentication failed.", err);
|
|
368
|
-
} finally {
|
|
369
|
-
server.closeAllConnections();
|
|
370
|
-
server.close(() => process.exit(0));
|
|
371
|
-
}
|
|
372
|
-
});
|
|
373
|
-
server.listen(8199, () => {
|
|
374
|
-
console.log(chalk2.blue(`
|
|
495
|
+
},
|
|
496
|
+
onListening: () => {
|
|
497
|
+
console.log(chalk2.blue(`
|
|
375
498
|
Opening browser for authentication...
|
|
376
499
|
`));
|
|
377
|
-
|
|
500
|
+
open(url).catch(() => {
|
|
501
|
+
console.log(chalk2.yellow(`Could not open browser. Open this URL manually:
|
|
502
|
+
${url}`));
|
|
503
|
+
});
|
|
504
|
+
}
|
|
378
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
|
+
}
|
|
379
527
|
});
|
|
380
528
|
|
|
381
529
|
// src/commands/logout.ts
|
|
@@ -744,7 +892,7 @@ function isNewer(current, latest) {
|
|
|
744
892
|
return false;
|
|
745
893
|
}
|
|
746
894
|
var updateCommand = new Command9("update").description("Check npm for the latest Plaud CLI and print the upgrade command").action(async () => {
|
|
747
|
-
const current = "0.2.
|
|
895
|
+
const current = "0.2.4";
|
|
748
896
|
const spinner = ora8("Checking npm for latest version...").start();
|
|
749
897
|
const latest = await fetchLatestVersion();
|
|
750
898
|
spinner.stop();
|
|
@@ -798,10 +946,10 @@ async function checkForUpdate(current) {
|
|
|
798
946
|
return isNewer(current, latest) ? latest : null;
|
|
799
947
|
}
|
|
800
948
|
var versionCommand = new Command10("version").description("Show CLI version information").action(async () => {
|
|
801
|
-
const current = "0.2.
|
|
949
|
+
const current = "0.2.4";
|
|
802
950
|
console.log(`plaud ${current}`);
|
|
803
|
-
if ("
|
|
804
|
-
if ("2026-
|
|
951
|
+
if ("4b84277") console.log(`commit ${"4b84277"}`);
|
|
952
|
+
if ("2026-05-07T11:34:37.562Z") console.log(`built ${"2026-05-07T11:34:37.562Z"}`);
|
|
805
953
|
if (current === "unknown") return;
|
|
806
954
|
const newer = await checkForUpdate(current);
|
|
807
955
|
if (newer) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plaud-ai/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"plaud": "dist/index.js"
|
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
"files": [
|
|
9
9
|
"dist"
|
|
10
10
|
],
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsup",
|
|
13
|
+
"dev": "tsup --watch",
|
|
14
|
+
"clean": "rm -rf dist"
|
|
14
15
|
},
|
|
15
16
|
"dependencies": {
|
|
16
17
|
"@inquirer/prompts": "^7.2.0",
|
|
@@ -22,13 +23,8 @@
|
|
|
22
23
|
"yaml": "^2.8.3"
|
|
23
24
|
},
|
|
24
25
|
"devDependencies": {
|
|
26
|
+
"@plaud-ai/shared": "workspace:*",
|
|
25
27
|
"@types/node": "^25.5.0",
|
|
26
|
-
"typescript": "^5.7.0"
|
|
27
|
-
"@plaud-ai/shared": "0.1.0"
|
|
28
|
-
},
|
|
29
|
-
"scripts": {
|
|
30
|
-
"build": "tsup",
|
|
31
|
-
"dev": "tsup --watch",
|
|
32
|
-
"clean": "rm -rf dist"
|
|
28
|
+
"typescript": "^5.7.0"
|
|
33
29
|
}
|
|
34
|
-
}
|
|
30
|
+
}
|