@plaud-ai/cli 0.1.4 → 0.2.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/dist/index.js +466 -31
- package/package.json +6 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
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";
|
|
@@ -52,7 +52,7 @@ var TokenStore = class {
|
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
// ../shared/dist/oauth.js
|
|
55
|
-
var DEFAULT_AUTHORIZATION_URL = "https://
|
|
55
|
+
var DEFAULT_AUTHORIZATION_URL = "https://web.plaud.ai/platform/oauth";
|
|
56
56
|
var DEFAULT_TOKEN_URL = "https://platform.plaud.ai/developer/api/oauth/third-party/access-token";
|
|
57
57
|
var DEFAULT_REFRESH_URL = "https://platform.plaud.ai/developer/api/oauth/third-party/access-token/refresh";
|
|
58
58
|
function generateCodeVerifier() {
|
|
@@ -61,6 +61,9 @@ function generateCodeVerifier() {
|
|
|
61
61
|
function generateCodeChallenge(verifier) {
|
|
62
62
|
return createHash("sha256").update(verifier).digest("base64url");
|
|
63
63
|
}
|
|
64
|
+
function generateState() {
|
|
65
|
+
return randomBytes(16).toString("base64url");
|
|
66
|
+
}
|
|
64
67
|
var OAuth = class {
|
|
65
68
|
config;
|
|
66
69
|
tokenStore;
|
|
@@ -77,17 +80,19 @@ var OAuth = class {
|
|
|
77
80
|
createAuthorizationRequest() {
|
|
78
81
|
const codeVerifier = generateCodeVerifier();
|
|
79
82
|
const codeChallenge = generateCodeChallenge(codeVerifier);
|
|
83
|
+
const state = generateState();
|
|
80
84
|
const params = new URLSearchParams({
|
|
81
85
|
client_id: this.config.clientId,
|
|
82
86
|
redirect_uri: this.config.redirectUri,
|
|
83
87
|
response_type: "code",
|
|
84
88
|
code_challenge: codeChallenge,
|
|
85
|
-
code_challenge_method: "S256"
|
|
89
|
+
code_challenge_method: "S256",
|
|
90
|
+
state
|
|
86
91
|
});
|
|
87
92
|
return {
|
|
88
93
|
url: `${this.authorizationUrl}?${params.toString()}`,
|
|
89
94
|
codeVerifier,
|
|
90
|
-
state
|
|
95
|
+
state
|
|
91
96
|
};
|
|
92
97
|
}
|
|
93
98
|
/**
|
|
@@ -96,7 +101,7 @@ var OAuth = class {
|
|
|
96
101
|
getAuthorizationUrl() {
|
|
97
102
|
return this.createAuthorizationRequest().url;
|
|
98
103
|
}
|
|
99
|
-
async exchangeCode(code, codeVerifier) {
|
|
104
|
+
async exchangeCode(code, codeVerifier, state) {
|
|
100
105
|
const basicAuth = Buffer.from(`${this.config.clientId}:${this.config.clientSecret}`).toString("base64");
|
|
101
106
|
const body = {
|
|
102
107
|
code,
|
|
@@ -105,6 +110,9 @@ var OAuth = class {
|
|
|
105
110
|
if (codeVerifier) {
|
|
106
111
|
body.code_verifier = codeVerifier;
|
|
107
112
|
}
|
|
113
|
+
if (state) {
|
|
114
|
+
body.state = state;
|
|
115
|
+
}
|
|
108
116
|
const res = await fetch(this.tokenUrl, {
|
|
109
117
|
method: "POST",
|
|
110
118
|
headers: {
|
|
@@ -182,16 +190,18 @@ var PlaudClient = class {
|
|
|
182
190
|
oauth;
|
|
183
191
|
apiBase;
|
|
184
192
|
extraHeaders;
|
|
193
|
+
staticToken;
|
|
185
194
|
constructor(config) {
|
|
186
195
|
this.oauth = new OAuth(config);
|
|
187
196
|
this.apiBase = config.apiBase ?? DEFAULT_API_BASE;
|
|
188
197
|
this.extraHeaders = config.extraHeaders ?? {};
|
|
198
|
+
this.staticToken = config.staticToken;
|
|
189
199
|
}
|
|
190
200
|
get auth() {
|
|
191
201
|
return this.oauth;
|
|
192
202
|
}
|
|
193
203
|
async request(path, init) {
|
|
194
|
-
const token = await this.oauth.getAccessToken();
|
|
204
|
+
const token = this.staticToken ?? await this.oauth.getAccessToken();
|
|
195
205
|
if (!token) {
|
|
196
206
|
throw new Error("Not authenticated. Please login first.");
|
|
197
207
|
}
|
|
@@ -322,7 +332,7 @@ var loginCommand = new Command("login").description("Authenticate with Plaud via
|
|
|
322
332
|
} catch {
|
|
323
333
|
await client2.auth.logout();
|
|
324
334
|
}
|
|
325
|
-
const { url, codeVerifier } = client2.auth.createAuthorizationRequest();
|
|
335
|
+
const { url, codeVerifier, state } = client2.auth.createAuthorizationRequest();
|
|
326
336
|
const spinner = ora("Waiting for browser authentication...").start();
|
|
327
337
|
const server = createServer(async (req, res) => {
|
|
328
338
|
if (req.method === "OPTIONS") {
|
|
@@ -346,7 +356,7 @@ var loginCommand = new Command("login").description("Authenticate with Plaud via
|
|
|
346
356
|
return;
|
|
347
357
|
}
|
|
348
358
|
try {
|
|
349
|
-
await client2.auth.exchangeCode(code, codeVerifier);
|
|
359
|
+
await client2.auth.exchangeCode(code, codeVerifier, state);
|
|
350
360
|
res.writeHead(200, { "Content-Type": "text/html", ...CORS_HEADERS });
|
|
351
361
|
res.end("<h1>Authentication successful!</h1><p>You can close this tab.</p>");
|
|
352
362
|
spinner.succeed("Logged in successfully!");
|
|
@@ -424,6 +434,42 @@ var meCommand = new Command3("me").description("Show current authenticated user
|
|
|
424
434
|
import { Command as Command4 } from "commander";
|
|
425
435
|
import chalk5 from "chalk";
|
|
426
436
|
import ora3 from "ora";
|
|
437
|
+
|
|
438
|
+
// src/format.ts
|
|
439
|
+
function formatDuration(ms) {
|
|
440
|
+
if (!ms || ms < 0) return "-";
|
|
441
|
+
const totalSeconds = Math.floor(ms / 1e3);
|
|
442
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
443
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
444
|
+
const seconds = totalSeconds % 60;
|
|
445
|
+
if (hours > 0) return `${hours}h${String(minutes).padStart(2, "0")}m`;
|
|
446
|
+
if (minutes > 0) return `${minutes}m${String(seconds).padStart(2, "0")}s`;
|
|
447
|
+
return `${seconds}s`;
|
|
448
|
+
}
|
|
449
|
+
function formatDate(iso) {
|
|
450
|
+
if (!iso) return "-";
|
|
451
|
+
const d = new Date(iso);
|
|
452
|
+
if (Number.isNaN(d.getTime())) return iso;
|
|
453
|
+
const y = d.getFullYear();
|
|
454
|
+
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
455
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
456
|
+
return `${y}-${m}-${day}`;
|
|
457
|
+
}
|
|
458
|
+
function formatTime(ms) {
|
|
459
|
+
const totalSeconds = Math.floor(ms / 1e3);
|
|
460
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
461
|
+
const seconds = totalSeconds % 60;
|
|
462
|
+
return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// src/commands/list-files.ts
|
|
466
|
+
var ID_WIDTH = 34;
|
|
467
|
+
var NAME_WIDTH = 36;
|
|
468
|
+
var DATE_WIDTH = 12;
|
|
469
|
+
function truncate(s, width) {
|
|
470
|
+
if (s.length <= width) return s.padEnd(width);
|
|
471
|
+
return s.slice(0, width - 1) + "\u2026";
|
|
472
|
+
}
|
|
427
473
|
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
474
|
const page = parseInt(opts.page);
|
|
429
475
|
const pageSize = parseInt(opts.pageSize);
|
|
@@ -443,10 +489,15 @@ var listFilesCommand = new Command4("files").description("List your Plaud record
|
|
|
443
489
|
console.log(chalk5.bold(`
|
|
444
490
|
Files on this page: ${result.data.length}
|
|
445
491
|
`));
|
|
492
|
+
const header = ` ${chalk5.bold("ID".padEnd(ID_WIDTH))} ${chalk5.bold("NAME".padEnd(NAME_WIDTH))} ${chalk5.bold("DATE".padEnd(DATE_WIDTH))} ${chalk5.bold("DURATION")}`;
|
|
493
|
+
console.log(header);
|
|
494
|
+
console.log(chalk5.gray(" " + "\u2500".repeat(ID_WIDTH + NAME_WIDTH + DATE_WIDTH + 16)));
|
|
446
495
|
for (const file of result.data) {
|
|
447
|
-
const
|
|
448
|
-
const
|
|
449
|
-
|
|
496
|
+
const id = chalk5.cyan(file.id.padEnd(ID_WIDTH));
|
|
497
|
+
const name = truncate(file.name ?? "", NAME_WIDTH);
|
|
498
|
+
const date = chalk5.gray(formatDate(file.created_at).padEnd(DATE_WIDTH));
|
|
499
|
+
const duration = chalk5.gray(formatDuration(file.duration));
|
|
500
|
+
console.log(` ${id} ${name} ${date} ${duration}`);
|
|
450
501
|
}
|
|
451
502
|
console.log(chalk5.gray(`
|
|
452
503
|
Page ${result.page}`));
|
|
@@ -473,12 +524,6 @@ Page ${result.page}`));
|
|
|
473
524
|
import { Command as Command5 } from "commander";
|
|
474
525
|
import chalk6 from "chalk";
|
|
475
526
|
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
527
|
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
528
|
const client2 = getClient();
|
|
484
529
|
const spinner = ora4("Fetching file...").start();
|
|
@@ -494,7 +539,7 @@ var getFileCommand = new Command5("file").description("Get details of a specific
|
|
|
494
539
|
console.log(` ${chalk6.cyan("name")}: ${file.name}`);
|
|
495
540
|
console.log(` ${chalk6.cyan("created_at")}: ${file.created_at}`);
|
|
496
541
|
console.log(` ${chalk6.cyan("start_at")}: ${file.start_at ?? "-"}`);
|
|
497
|
-
console.log(` ${chalk6.cyan("duration")}: ${
|
|
542
|
+
console.log(` ${chalk6.cyan("duration")}: ${formatDuration(file.duration)}`);
|
|
498
543
|
console.log(` ${chalk6.cyan("serial_number")}: ${file.serial_number ?? "-"}`);
|
|
499
544
|
console.log(` ${chalk6.cyan("audio")}: ${file.presigned_url ? chalk6.green("available") : chalk6.gray("unavailable")}`);
|
|
500
545
|
console.log(` ${chalk6.cyan("transcript")}: ${hasTranscript ? chalk6.green("available") : chalk6.gray("unavailable")}`);
|
|
@@ -565,12 +610,6 @@ import { Command as Command7 } from "commander";
|
|
|
565
610
|
import chalk8 from "chalk";
|
|
566
611
|
import ora6 from "ora";
|
|
567
612
|
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
613
|
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
614
|
const client2 = getClient();
|
|
576
615
|
const spinner = ora6("Fetching transcript...").start();
|
|
@@ -667,16 +706,408 @@ Summary: ${file.name}
|
|
|
667
706
|
});
|
|
668
707
|
|
|
669
708
|
// src/commands/version.ts
|
|
709
|
+
import { Command as Command10 } from "commander";
|
|
710
|
+
import chalk11 from "chalk";
|
|
711
|
+
import { readFile as readFile2, writeFile as writeFile4, mkdir as mkdir2 } from "fs/promises";
|
|
712
|
+
import { homedir as homedir3 } from "os";
|
|
713
|
+
import { join as join3, dirname } from "path";
|
|
714
|
+
|
|
715
|
+
// src/commands/update.ts
|
|
670
716
|
import { Command as Command9 } from "commander";
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
717
|
+
import chalk10 from "chalk";
|
|
718
|
+
import ora8 from "ora";
|
|
719
|
+
var PKG_NAME = "@plaud-ai/cli";
|
|
720
|
+
var REGISTRY = "https://registry.npmjs.org";
|
|
721
|
+
async function fetchLatestVersion(timeoutMs = 5e3) {
|
|
722
|
+
const ctrl = new AbortController();
|
|
723
|
+
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
724
|
+
try {
|
|
725
|
+
const res = await fetch(`${REGISTRY}/${encodeURIComponent(PKG_NAME)}/latest`, { signal: ctrl.signal });
|
|
726
|
+
if (!res.ok) return null;
|
|
727
|
+
const json = await res.json();
|
|
728
|
+
return json.version ?? null;
|
|
729
|
+
} catch {
|
|
730
|
+
return null;
|
|
731
|
+
} finally {
|
|
732
|
+
clearTimeout(t);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
function isNewer(current, latest) {
|
|
736
|
+
const c = current.split(".").map(Number);
|
|
737
|
+
const l = latest.split(".").map(Number);
|
|
738
|
+
for (let i = 0; i < Math.max(c.length, l.length); i++) {
|
|
739
|
+
const cv = c[i] ?? 0;
|
|
740
|
+
const lv = l[i] ?? 0;
|
|
741
|
+
if (lv > cv) return true;
|
|
742
|
+
if (lv < cv) return false;
|
|
743
|
+
}
|
|
744
|
+
return false;
|
|
745
|
+
}
|
|
746
|
+
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.0";
|
|
748
|
+
const spinner = ora8("Checking npm for latest version...").start();
|
|
749
|
+
const latest = await fetchLatestVersion();
|
|
750
|
+
spinner.stop();
|
|
751
|
+
if (!latest) {
|
|
752
|
+
printError("UNREACHABLE", "Could not reach npm registry to check for updates.");
|
|
753
|
+
process.exit(ExitCode.UNREACHABLE);
|
|
754
|
+
}
|
|
755
|
+
if (!isNewer(current, latest)) {
|
|
756
|
+
console.log(chalk10.green(`You're on the latest version (${current}).`));
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
console.log(chalk10.yellow(`A newer version is available: ${current} \u2192 ${latest}`));
|
|
760
|
+
console.log();
|
|
761
|
+
console.log("Run this command to upgrade:");
|
|
762
|
+
console.log();
|
|
763
|
+
console.log(chalk10.bold(` npm install -g ${PKG_NAME}@latest`));
|
|
764
|
+
console.log();
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
// src/commands/version.ts
|
|
768
|
+
var CACHE_PATH = join3(homedir3(), ".plaud", "version-check.json");
|
|
769
|
+
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
770
|
+
async function readCache() {
|
|
771
|
+
try {
|
|
772
|
+
const raw = await readFile2(CACHE_PATH, "utf-8");
|
|
773
|
+
const parsed = JSON.parse(raw);
|
|
774
|
+
if (typeof parsed.checked_at !== "number" || typeof parsed.latest !== "string") return null;
|
|
775
|
+
return parsed;
|
|
776
|
+
} catch {
|
|
777
|
+
return null;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
async function writeCache(entry) {
|
|
781
|
+
try {
|
|
782
|
+
await mkdir2(dirname(CACHE_PATH), { recursive: true });
|
|
783
|
+
await writeFile4(CACHE_PATH, JSON.stringify(entry), "utf-8");
|
|
784
|
+
} catch {
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
async function checkForUpdate(current) {
|
|
788
|
+
const cached = await readCache();
|
|
789
|
+
const now = Date.now();
|
|
790
|
+
let latest = null;
|
|
791
|
+
if (cached && now - cached.checked_at < CACHE_TTL_MS) {
|
|
792
|
+
latest = cached.latest;
|
|
793
|
+
} else {
|
|
794
|
+
latest = await fetchLatestVersion(2e3);
|
|
795
|
+
if (latest) await writeCache({ checked_at: now, latest });
|
|
796
|
+
}
|
|
797
|
+
if (!latest) return null;
|
|
798
|
+
return isNewer(current, latest) ? latest : null;
|
|
799
|
+
}
|
|
800
|
+
var versionCommand = new Command10("version").description("Show CLI version information").action(async () => {
|
|
801
|
+
const current = "0.2.0";
|
|
802
|
+
console.log(`plaud ${current}`);
|
|
803
|
+
if ("9ac224a") console.log(`commit ${"9ac224a"}`);
|
|
804
|
+
if ("2026-04-24T08:28:52.747Z") console.log(`built ${"2026-04-24T08:28:52.747Z"}`);
|
|
805
|
+
if (current === "unknown") return;
|
|
806
|
+
const newer = await checkForUpdate(current);
|
|
807
|
+
if (newer) {
|
|
808
|
+
console.log();
|
|
809
|
+
console.log(chalk11.yellow(`A newer version is available: ${current} \u2192 ${newer}`));
|
|
810
|
+
console.log(chalk11.gray(`Run \`plaud update\` for upgrade instructions.`));
|
|
811
|
+
}
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
// src/commands/search.ts
|
|
815
|
+
import { Command as Command11 } from "commander";
|
|
816
|
+
import chalk12 from "chalk";
|
|
817
|
+
import ora9 from "ora";
|
|
818
|
+
var MAX_PAGES = 5;
|
|
819
|
+
var PAGE_SIZE = 100;
|
|
820
|
+
function parseDate(s) {
|
|
821
|
+
if (!s) return null;
|
|
822
|
+
const d = new Date(s);
|
|
823
|
+
return Number.isNaN(d.getTime()) ? null : d.getTime();
|
|
824
|
+
}
|
|
825
|
+
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) => {
|
|
826
|
+
const max = parseInt(opts.max);
|
|
827
|
+
if (isNaN(max) || max < 1 || max > 500) {
|
|
828
|
+
printError("INVALID_ARGS", "--max must be a number between 1 and 500");
|
|
829
|
+
process.exit(ExitCode.ERROR);
|
|
830
|
+
}
|
|
831
|
+
const from = parseDate(opts.from);
|
|
832
|
+
const toRaw = parseDate(opts.to);
|
|
833
|
+
const to = toRaw !== null ? toRaw + 24 * 60 * 60 * 1e3 - 1 : null;
|
|
834
|
+
if (opts.from && from === null) {
|
|
835
|
+
printError("INVALID_ARGS", `Invalid --from date: ${opts.from}`);
|
|
836
|
+
process.exit(ExitCode.ERROR);
|
|
837
|
+
}
|
|
838
|
+
if (opts.to && toRaw === null) {
|
|
839
|
+
printError("INVALID_ARGS", `Invalid --to date: ${opts.to}`);
|
|
840
|
+
process.exit(ExitCode.ERROR);
|
|
841
|
+
}
|
|
842
|
+
const client2 = getClient();
|
|
843
|
+
const spinner = ora9(`Searching for "${keyword}"...`).start();
|
|
844
|
+
const q = keyword.toLowerCase();
|
|
845
|
+
try {
|
|
846
|
+
const matches = [];
|
|
847
|
+
let scanned = 0;
|
|
848
|
+
let truncated = false;
|
|
849
|
+
for (let page = 1; page <= MAX_PAGES; page++) {
|
|
850
|
+
spinner.text = `Searching for "${keyword}"... (page ${page})`;
|
|
851
|
+
const result = await client2.listFiles(page, PAGE_SIZE);
|
|
852
|
+
scanned += result.data.length;
|
|
853
|
+
for (const file of result.data) {
|
|
854
|
+
if (!(file.name ?? "").toLowerCase().includes(q)) continue;
|
|
855
|
+
if (from !== null || to !== null) {
|
|
856
|
+
const created = parseDate(file.created_at);
|
|
857
|
+
if (created === null) continue;
|
|
858
|
+
if (from !== null && created < from) continue;
|
|
859
|
+
if (to !== null && created > to) continue;
|
|
860
|
+
}
|
|
861
|
+
matches.push(file);
|
|
862
|
+
if (matches.length >= max) break;
|
|
863
|
+
}
|
|
864
|
+
if (matches.length >= max) break;
|
|
865
|
+
if (result.data.length < PAGE_SIZE) break;
|
|
866
|
+
if (page === MAX_PAGES) truncated = true;
|
|
867
|
+
}
|
|
868
|
+
spinner.stop();
|
|
869
|
+
if (matches.length === 0) {
|
|
870
|
+
console.log(chalk12.yellow(`No recordings matched "${keyword}" in ${scanned} scanned.`));
|
|
871
|
+
if (truncated) {
|
|
872
|
+
console.log(chalk12.gray(`(Scanned first ${MAX_PAGES * PAGE_SIZE}; narrow the window with --from/--to if your target is older.)`));
|
|
873
|
+
}
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
console.log(chalk12.bold(`
|
|
877
|
+
Matched ${matches.length}${truncated ? `+` : ""} of ${scanned} scanned
|
|
878
|
+
`));
|
|
879
|
+
for (const file of matches) {
|
|
880
|
+
const id = chalk12.cyan(file.id);
|
|
881
|
+
const date = chalk12.gray(formatDate(file.created_at));
|
|
882
|
+
const duration = chalk12.gray(formatDuration(file.duration));
|
|
883
|
+
console.log(` ${id} ${file.name ?? ""} ${date} ${duration}`);
|
|
884
|
+
}
|
|
885
|
+
if (truncated) {
|
|
886
|
+
console.log(chalk12.gray(`
|
|
887
|
+
(Truncated at ${MAX_PAGES} pages; results beyond the most recent ${MAX_PAGES * PAGE_SIZE} not scanned.)`));
|
|
888
|
+
}
|
|
889
|
+
console.log();
|
|
890
|
+
} catch (err) {
|
|
891
|
+
spinner.stop();
|
|
892
|
+
if (isAuthError(err)) {
|
|
893
|
+
printError("AUTH_FAILED", "Token invalid or expired. Run `plaud login`.");
|
|
894
|
+
process.exit(ExitCode.AUTH_FAILED);
|
|
895
|
+
}
|
|
896
|
+
if (isNetworkError(err)) {
|
|
897
|
+
printError("UNREACHABLE", "Cannot reach Plaud servers. Check your network.", err);
|
|
898
|
+
process.exit(ExitCode.UNREACHABLE);
|
|
899
|
+
}
|
|
900
|
+
if (isTimeoutError(err)) {
|
|
901
|
+
printError("TIMEOUT", "Request timed out.");
|
|
902
|
+
process.exit(ExitCode.TIMEOUT);
|
|
903
|
+
}
|
|
904
|
+
printError("FETCH_FAILED", "Failed to search files.", err);
|
|
905
|
+
process.exit(ExitCode.ERROR);
|
|
906
|
+
}
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
// src/commands/recent.ts
|
|
910
|
+
import { Command as Command12 } from "commander";
|
|
911
|
+
import chalk13 from "chalk";
|
|
912
|
+
import ora10 from "ora";
|
|
913
|
+
var MAX_PAGES2 = 3;
|
|
914
|
+
var PAGE_SIZE2 = 100;
|
|
915
|
+
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) => {
|
|
916
|
+
const days = parseInt(opts.days);
|
|
917
|
+
if (isNaN(days) || days < 1 || days > 365) {
|
|
918
|
+
printError("INVALID_ARGS", "--days must be a number between 1 and 365");
|
|
919
|
+
process.exit(ExitCode.ERROR);
|
|
920
|
+
}
|
|
921
|
+
const from = Date.now() - days * 24 * 60 * 60 * 1e3;
|
|
922
|
+
const client2 = getClient();
|
|
923
|
+
const spinner = ora10(`Fetching recordings from the last ${days} days...`).start();
|
|
924
|
+
try {
|
|
925
|
+
const matches = [];
|
|
926
|
+
for (let page = 1; page <= MAX_PAGES2; page++) {
|
|
927
|
+
const result = await client2.listFiles(page, PAGE_SIZE2);
|
|
928
|
+
let allOlder = true;
|
|
929
|
+
let hasValidTimestamps = false;
|
|
930
|
+
for (const file of result.data) {
|
|
931
|
+
const created = new Date(file.created_at).getTime();
|
|
932
|
+
if (Number.isNaN(created)) continue;
|
|
933
|
+
hasValidTimestamps = true;
|
|
934
|
+
if (created >= from) {
|
|
935
|
+
matches.push(file);
|
|
936
|
+
allOlder = false;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
if (hasValidTimestamps && allOlder && result.data.length > 0) break;
|
|
940
|
+
if (result.data.length < PAGE_SIZE2) break;
|
|
941
|
+
}
|
|
942
|
+
spinner.stop();
|
|
943
|
+
if (matches.length === 0) {
|
|
944
|
+
console.log(chalk13.yellow(`No recordings in the last ${days} days.`));
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
console.log(chalk13.bold(`
|
|
948
|
+
Recordings in the last ${days} days: ${matches.length}
|
|
949
|
+
`));
|
|
950
|
+
for (const file of matches) {
|
|
951
|
+
const id = chalk13.cyan(file.id);
|
|
952
|
+
const date = chalk13.gray(formatDate(file.created_at));
|
|
953
|
+
const duration = chalk13.gray(formatDuration(file.duration));
|
|
954
|
+
console.log(` ${id} ${file.name ?? ""} ${date} ${duration}`);
|
|
955
|
+
}
|
|
956
|
+
console.log();
|
|
957
|
+
} catch (err) {
|
|
958
|
+
spinner.stop();
|
|
959
|
+
if (isAuthError(err)) {
|
|
960
|
+
printError("AUTH_FAILED", "Token invalid or expired. Run `plaud login`.");
|
|
961
|
+
process.exit(ExitCode.AUTH_FAILED);
|
|
962
|
+
}
|
|
963
|
+
if (isNetworkError(err)) {
|
|
964
|
+
printError("UNREACHABLE", "Cannot reach Plaud servers. Check your network.", err);
|
|
965
|
+
process.exit(ExitCode.UNREACHABLE);
|
|
966
|
+
}
|
|
967
|
+
if (isTimeoutError(err)) {
|
|
968
|
+
printError("TIMEOUT", "Request timed out.");
|
|
969
|
+
process.exit(ExitCode.TIMEOUT);
|
|
970
|
+
}
|
|
971
|
+
printError("FETCH_FAILED", "Failed to fetch recent files.", err);
|
|
972
|
+
process.exit(ExitCode.ERROR);
|
|
973
|
+
}
|
|
974
|
+
});
|
|
975
|
+
var todayCommand = new Command12("today").description("List recordings created today").action(async () => {
|
|
976
|
+
const start = /* @__PURE__ */ new Date();
|
|
977
|
+
start.setHours(0, 0, 0, 0);
|
|
978
|
+
const startMs = start.getTime();
|
|
979
|
+
const client2 = getClient();
|
|
980
|
+
const spinner = ora10("Fetching today's recordings...").start();
|
|
981
|
+
try {
|
|
982
|
+
const result = await client2.listFiles(1, 50);
|
|
983
|
+
spinner.stop();
|
|
984
|
+
const matches = result.data.filter((f) => {
|
|
985
|
+
const t = new Date(f.created_at).getTime();
|
|
986
|
+
return !Number.isNaN(t) && t >= startMs;
|
|
987
|
+
});
|
|
988
|
+
if (matches.length === 0) {
|
|
989
|
+
console.log(chalk13.yellow("No recordings created today."));
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
console.log(chalk13.bold(`
|
|
993
|
+
Today's recordings: ${matches.length}
|
|
994
|
+
`));
|
|
995
|
+
for (const file of matches) {
|
|
996
|
+
const id = chalk13.cyan(file.id);
|
|
997
|
+
const date = chalk13.gray(formatDate(file.created_at));
|
|
998
|
+
const duration = chalk13.gray(formatDuration(file.duration));
|
|
999
|
+
console.log(` ${id} ${file.name ?? ""} ${date} ${duration}`);
|
|
1000
|
+
}
|
|
1001
|
+
console.log();
|
|
1002
|
+
} catch (err) {
|
|
1003
|
+
spinner.stop();
|
|
1004
|
+
if (isAuthError(err)) {
|
|
1005
|
+
printError("AUTH_FAILED", "Token invalid or expired. Run `plaud login`.");
|
|
1006
|
+
process.exit(ExitCode.AUTH_FAILED);
|
|
1007
|
+
}
|
|
1008
|
+
if (isNetworkError(err)) {
|
|
1009
|
+
printError("UNREACHABLE", "Cannot reach Plaud servers. Check your network.", err);
|
|
1010
|
+
process.exit(ExitCode.UNREACHABLE);
|
|
1011
|
+
}
|
|
1012
|
+
if (isTimeoutError(err)) {
|
|
1013
|
+
printError("TIMEOUT", "Request timed out.");
|
|
1014
|
+
process.exit(ExitCode.TIMEOUT);
|
|
1015
|
+
}
|
|
1016
|
+
printError("FETCH_FAILED", "Failed to fetch today's files.", err);
|
|
1017
|
+
process.exit(ExitCode.ERROR);
|
|
1018
|
+
}
|
|
675
1019
|
});
|
|
676
1020
|
|
|
1021
|
+
// src/commands/wizard.ts
|
|
1022
|
+
import chalk14 from "chalk";
|
|
1023
|
+
import { select, input, confirm } from "@inquirer/prompts";
|
|
1024
|
+
import { spawnSync } from "child_process";
|
|
1025
|
+
function runSelf(args) {
|
|
1026
|
+
const result = spawnSync(process.execPath, [process.argv[1] ?? "", ...args], {
|
|
1027
|
+
stdio: "inherit"
|
|
1028
|
+
});
|
|
1029
|
+
return result.status ?? 0;
|
|
1030
|
+
}
|
|
1031
|
+
async function ensureLoggedIn() {
|
|
1032
|
+
const client2 = getClient();
|
|
1033
|
+
try {
|
|
1034
|
+
const token = await client2.auth.getAccessToken();
|
|
1035
|
+
if (token) return true;
|
|
1036
|
+
} catch {
|
|
1037
|
+
}
|
|
1038
|
+
const go = await confirm({ message: "You are not logged in. Open the browser to log in now?", default: true });
|
|
1039
|
+
if (!go) return false;
|
|
1040
|
+
const code = runSelf(["login"]);
|
|
1041
|
+
return code === 0;
|
|
1042
|
+
}
|
|
1043
|
+
async function runWizard() {
|
|
1044
|
+
console.log(chalk14.bold("\nWelcome to Plaud\n"));
|
|
1045
|
+
try {
|
|
1046
|
+
const ok = await ensureLoggedIn();
|
|
1047
|
+
if (!ok) {
|
|
1048
|
+
console.log(chalk14.gray("Exiting. Run `plaud login` when ready."));
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
const action = await select({
|
|
1052
|
+
message: "What do you want to do?",
|
|
1053
|
+
choices: [
|
|
1054
|
+
{ name: "Browse my recordings", value: "browse" },
|
|
1055
|
+
{ name: "Search by name", value: "search" },
|
|
1056
|
+
{ name: "Recent recordings (7 days)", value: "recent" },
|
|
1057
|
+
{ name: "Today's recordings", value: "today" },
|
|
1058
|
+
{ name: "Read a transcript", value: "transcript" },
|
|
1059
|
+
{ name: "Read an AI summary", value: "summary" },
|
|
1060
|
+
{ name: "Who am I logged in as", value: "me" },
|
|
1061
|
+
{ name: "Log out", value: "logout" },
|
|
1062
|
+
{ name: "Quit", value: "quit" }
|
|
1063
|
+
]
|
|
1064
|
+
});
|
|
1065
|
+
switch (action) {
|
|
1066
|
+
case "browse":
|
|
1067
|
+
runSelf(["files"]);
|
|
1068
|
+
break;
|
|
1069
|
+
case "search": {
|
|
1070
|
+
const keyword = await input({ message: "Keyword to search for:", validate: (v) => v.trim().length > 0 || "Enter a keyword" });
|
|
1071
|
+
runSelf(["search", keyword.trim()]);
|
|
1072
|
+
break;
|
|
1073
|
+
}
|
|
1074
|
+
case "recent":
|
|
1075
|
+
runSelf(["recent"]);
|
|
1076
|
+
break;
|
|
1077
|
+
case "today":
|
|
1078
|
+
runSelf(["today"]);
|
|
1079
|
+
break;
|
|
1080
|
+
case "transcript": {
|
|
1081
|
+
const id = await input({ message: "File ID:", validate: (v) => v.trim().length > 0 || "Enter a file ID" });
|
|
1082
|
+
runSelf(["transcript", id.trim()]);
|
|
1083
|
+
break;
|
|
1084
|
+
}
|
|
1085
|
+
case "summary": {
|
|
1086
|
+
const id = await input({ message: "File ID:", validate: (v) => v.trim().length > 0 || "Enter a file ID" });
|
|
1087
|
+
runSelf(["summary", id.trim()]);
|
|
1088
|
+
break;
|
|
1089
|
+
}
|
|
1090
|
+
case "me":
|
|
1091
|
+
runSelf(["me"]);
|
|
1092
|
+
break;
|
|
1093
|
+
case "logout":
|
|
1094
|
+
runSelf(["logout"]);
|
|
1095
|
+
break;
|
|
1096
|
+
case "quit":
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
} catch (err) {
|
|
1100
|
+
const name = err?.name;
|
|
1101
|
+
if (name === "ExitPromptError") return;
|
|
1102
|
+
throw err;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
|
|
677
1106
|
// src/index.ts
|
|
678
|
-
var program = new
|
|
679
|
-
program.name("plaud").description("Plaud CLI - manage your Plaud recordings")
|
|
1107
|
+
var program = new Command13();
|
|
1108
|
+
program.name("plaud").description("Plaud CLI - manage your Plaud recordings").action(async () => {
|
|
1109
|
+
await runWizard();
|
|
1110
|
+
});
|
|
680
1111
|
program.addCommand(loginCommand);
|
|
681
1112
|
program.addCommand(logoutCommand);
|
|
682
1113
|
program.addCommand(meCommand);
|
|
@@ -685,5 +1116,9 @@ program.addCommand(getFileCommand);
|
|
|
685
1116
|
program.addCommand(audioCommand);
|
|
686
1117
|
program.addCommand(transcriptCommand);
|
|
687
1118
|
program.addCommand(summaryCommand);
|
|
1119
|
+
program.addCommand(searchCommand);
|
|
1120
|
+
program.addCommand(recentCommand);
|
|
1121
|
+
program.addCommand(todayCommand);
|
|
1122
|
+
program.addCommand(updateCommand);
|
|
688
1123
|
program.addCommand(versionCommand);
|
|
689
|
-
program.
|
|
1124
|
+
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.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"plaud": "dist/index.js"
|
|
@@ -8,7 +8,12 @@
|
|
|
8
8
|
"files": [
|
|
9
9
|
"dist"
|
|
10
10
|
],
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"registry": "https://registry.npmjs.org/",
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
11
15
|
"dependencies": {
|
|
16
|
+
"@inquirer/prompts": "^7.2.0",
|
|
12
17
|
"chalk": "^5.4.0",
|
|
13
18
|
"commander": "^13.0.0",
|
|
14
19
|
"dotenv": "^17.3.1",
|