@alta-foundation/plaud-extractor 1.0.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/.env.example +9 -0
- package/.github/workflows/ci.yml +33 -0
- package/.github/workflows/publish.yml +46 -0
- package/CLAUDE.md +53 -0
- package/README.md +318 -0
- package/dist/PlaudExtractor.d.ts +61 -0
- package/dist/PlaudExtractor.d.ts.map +1 -0
- package/dist/PlaudExtractor.js +236 -0
- package/dist/PlaudExtractor.js.map +1 -0
- package/dist/auth/browser-auth.d.ts +10 -0
- package/dist/auth/browser-auth.d.ts.map +1 -0
- package/dist/auth/browser-auth.js +220 -0
- package/dist/auth/browser-auth.js.map +1 -0
- package/dist/auth/token-store.d.ts +9 -0
- package/dist/auth/token-store.d.ts.map +1 -0
- package/dist/auth/token-store.js +74 -0
- package/dist/auth/token-store.js.map +1 -0
- package/dist/auth/types.d.ts +266 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +32 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +30 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/commands/auth.d.ts +3 -0
- package/dist/cli/commands/auth.d.ts.map +1 -0
- package/dist/cli/commands/auth.js +22 -0
- package/dist/cli/commands/auth.js.map +1 -0
- package/dist/cli/commands/backfill.d.ts +3 -0
- package/dist/cli/commands/backfill.d.ts.map +1 -0
- package/dist/cli/commands/backfill.js +59 -0
- package/dist/cli/commands/backfill.js.map +1 -0
- package/dist/cli/commands/sync.d.ts +3 -0
- package/dist/cli/commands/sync.d.ts.map +1 -0
- package/dist/cli/commands/sync.js +55 -0
- package/dist/cli/commands/sync.js.map +1 -0
- package/dist/cli/commands/verify.d.ts +3 -0
- package/dist/cli/commands/verify.d.ts.map +1 -0
- package/dist/cli/commands/verify.js +28 -0
- package/dist/cli/commands/verify.js.map +1 -0
- package/dist/cli/exit-codes.d.ts +8 -0
- package/dist/cli/exit-codes.d.ts.map +1 -0
- package/dist/cli/exit-codes.js +16 -0
- package/dist/cli/exit-codes.js.map +1 -0
- package/dist/cli/options.d.ts +31 -0
- package/dist/cli/options.d.ts.map +1 -0
- package/dist/cli/options.js +11 -0
- package/dist/cli/options.js.map +1 -0
- package/dist/client/endpoints.d.ts +26 -0
- package/dist/client/endpoints.d.ts.map +1 -0
- package/dist/client/endpoints.js +54 -0
- package/dist/client/endpoints.js.map +1 -0
- package/dist/client/http.d.ts +17 -0
- package/dist/client/http.d.ts.map +1 -0
- package/dist/client/http.js +92 -0
- package/dist/client/http.js.map +1 -0
- package/dist/client/plaud-client.d.ts +14 -0
- package/dist/client/plaud-client.d.ts.map +1 -0
- package/dist/client/plaud-client.js +216 -0
- package/dist/client/plaud-client.js.map +1 -0
- package/dist/client/types.d.ts +154 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +41 -0
- package/dist/client/types.js.map +1 -0
- package/dist/errors.d.ts +24 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +51 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +9 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +37 -0
- package/dist/logger.js.map +1 -0
- package/dist/mcp/job-tools.d.ts +3 -0
- package/dist/mcp/job-tools.d.ts.map +1 -0
- package/dist/mcp/job-tools.js +108 -0
- package/dist/mcp/job-tools.js.map +1 -0
- package/dist/mcp/read-tools.d.ts +3 -0
- package/dist/mcp/read-tools.d.ts.map +1 -0
- package/dist/mcp/read-tools.js +173 -0
- package/dist/mcp/read-tools.js.map +1 -0
- package/dist/mcp/server.d.ts +3 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +32 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/storage/atomic.d.ts +5 -0
- package/dist/storage/atomic.d.ts.map +1 -0
- package/dist/storage/atomic.js +51 -0
- package/dist/storage/atomic.js.map +1 -0
- package/dist/storage/checksums.d.ts +15 -0
- package/dist/storage/checksums.d.ts.map +1 -0
- package/dist/storage/checksums.js +56 -0
- package/dist/storage/checksums.js.map +1 -0
- package/dist/storage/dataset-writer.d.ts +21 -0
- package/dist/storage/dataset-writer.d.ts.map +1 -0
- package/dist/storage/dataset-writer.js +52 -0
- package/dist/storage/dataset-writer.js.map +1 -0
- package/dist/storage/paths.d.ts +9 -0
- package/dist/storage/paths.d.ts.map +1 -0
- package/dist/storage/paths.js +38 -0
- package/dist/storage/paths.js.map +1 -0
- package/dist/storage/recording-store.d.ts +24 -0
- package/dist/storage/recording-store.d.ts.map +1 -0
- package/dist/storage/recording-store.js +161 -0
- package/dist/storage/recording-store.js.map +1 -0
- package/dist/sync/download-queue.d.ts +21 -0
- package/dist/sync/download-queue.d.ts.map +1 -0
- package/dist/sync/download-queue.js +82 -0
- package/dist/sync/download-queue.js.map +1 -0
- package/dist/sync/incremental.d.ts +21 -0
- package/dist/sync/incremental.d.ts.map +1 -0
- package/dist/sync/incremental.js +96 -0
- package/dist/sync/incremental.js.map +1 -0
- package/dist/sync/sync-engine.d.ts +6 -0
- package/dist/sync/sync-engine.d.ts.map +1 -0
- package/dist/sync/sync-engine.js +135 -0
- package/dist/sync/sync-engine.js.map +1 -0
- package/dist/sync/types.d.ts +130 -0
- package/dist/sync/types.d.ts.map +1 -0
- package/dist/sync/types.js +17 -0
- package/dist/sync/types.js.map +1 -0
- package/dist/transcript/formatter.d.ts +4 -0
- package/dist/transcript/formatter.d.ts.map +1 -0
- package/dist/transcript/formatter.js +88 -0
- package/dist/transcript/formatter.js.map +1 -0
- package/package.json +41 -0
- package/src/PlaudExtractor.ts +275 -0
- package/src/auth/browser-auth.ts +248 -0
- package/src/auth/token-store.ts +79 -0
- package/src/auth/types.ts +41 -0
- package/src/cli/bin.ts +30 -0
- package/src/cli/commands/auth.ts +27 -0
- package/src/cli/commands/backfill.ts +77 -0
- package/src/cli/commands/sync.ts +71 -0
- package/src/cli/commands/verify.ts +31 -0
- package/src/cli/exit-codes.ts +14 -0
- package/src/cli/options.ts +10 -0
- package/src/client/endpoints.ts +62 -0
- package/src/client/http.ts +110 -0
- package/src/client/plaud-client.ts +268 -0
- package/src/client/types.ts +62 -0
- package/src/errors.ts +57 -0
- package/src/index.ts +17 -0
- package/src/logger.ts +49 -0
- package/src/mcp/job-tools.ts +156 -0
- package/src/mcp/read-tools.ts +204 -0
- package/src/mcp/server.ts +39 -0
- package/src/storage/atomic.ts +51 -0
- package/src/storage/checksums.ts +76 -0
- package/src/storage/dataset-writer.ts +74 -0
- package/src/storage/paths.ts +44 -0
- package/src/storage/recording-store.ts +182 -0
- package/src/sync/download-queue.ts +102 -0
- package/src/sync/incremental.ts +111 -0
- package/src/sync/sync-engine.ts +183 -0
- package/src/sync/types.ts +64 -0
- package/src/transcript/formatter.ts +91 -0
- package/tsconfig.build.json +8 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { createLogger, setLogger } from './logger.js';
|
|
5
|
+
import { loadCredentials, saveCredentials, isExpired } from './auth/token-store.js';
|
|
6
|
+
import { runBrowserAuth } from './auth/browser-auth.js';
|
|
7
|
+
import { PlaudApiClient } from './client/plaud-client.js';
|
|
8
|
+
import { SyncEngine } from './sync/sync-engine.js';
|
|
9
|
+
import { IncrementalTracker } from './sync/incremental.js';
|
|
10
|
+
import { verifyChecksums } from './storage/checksums.js';
|
|
11
|
+
import { recordingDir, defaultOutDir } from './storage/paths.js';
|
|
12
|
+
import { AuthError } from './errors.js';
|
|
13
|
+
export class PlaudExtractor {
|
|
14
|
+
outDir;
|
|
15
|
+
engine;
|
|
16
|
+
constructor(config = {}) {
|
|
17
|
+
this.outDir = config.outDir
|
|
18
|
+
? path.resolve(config.outDir.replace(/^~/, os.homedir()))
|
|
19
|
+
: defaultOutDir();
|
|
20
|
+
if (config.logger) {
|
|
21
|
+
setLogger(config.logger);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
createLogger(this.outDir, { verbose: config.verbose, redact: config.redact });
|
|
25
|
+
}
|
|
26
|
+
this.engine = new SyncEngine();
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Launch browser for authentication.
|
|
30
|
+
* Saves credentials to ~/.alta/plaud-auth.json.
|
|
31
|
+
*/
|
|
32
|
+
async authenticate(opts = {}) {
|
|
33
|
+
const session = await runBrowserAuth(opts);
|
|
34
|
+
await saveCredentials(session);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Check if credentials exist and are not expired.
|
|
38
|
+
*/
|
|
39
|
+
async isAuthenticated() {
|
|
40
|
+
const creds = await loadCredentials();
|
|
41
|
+
if (!creds)
|
|
42
|
+
return false;
|
|
43
|
+
if (isExpired(creds))
|
|
44
|
+
return false;
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Incremental sync: only download new or changed recordings since last run.
|
|
49
|
+
* If the token expires mid-sync, re-authenticates automatically and retries once.
|
|
50
|
+
*/
|
|
51
|
+
async sync(opts = {}) {
|
|
52
|
+
return this.runWithReauth(opts, 'sync');
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Full backfill: re-evaluate all recordings regardless of sync state.
|
|
56
|
+
* If the token expires mid-backfill, re-authenticates automatically and retries once.
|
|
57
|
+
*/
|
|
58
|
+
async backfill(opts = {}) {
|
|
59
|
+
return this.runWithReauth(opts, 'backfill');
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Run sync/backfill, and if a token-expired AuthError occurs mid-run,
|
|
63
|
+
* automatically re-authenticate and retry once.
|
|
64
|
+
*/
|
|
65
|
+
async runWithReauth(opts, mode) {
|
|
66
|
+
try {
|
|
67
|
+
const client = await this.buildClient();
|
|
68
|
+
return await this.engine.run(client, this.buildSyncOptions(opts), mode);
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
if (!(err instanceof AuthError))
|
|
72
|
+
throw err;
|
|
73
|
+
// Token expired or rejected mid-run — re-authenticate and try once more
|
|
74
|
+
console.error('\nSession expired during sync. Re-authenticating...');
|
|
75
|
+
await this.authenticate();
|
|
76
|
+
console.log('Re-authenticated. Resuming sync...\n');
|
|
77
|
+
const client = await this.buildClient();
|
|
78
|
+
return this.engine.run(client, this.buildSyncOptions(opts), mode);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Walk all recording folders and verify checksums.
|
|
83
|
+
* With repair=true, re-download any file with a mismatch.
|
|
84
|
+
*/
|
|
85
|
+
async verify(opts = {}) {
|
|
86
|
+
const client = opts.repair ? await this.buildClient() : null;
|
|
87
|
+
const tracker = new IncrementalTracker();
|
|
88
|
+
await tracker.load(this.outDir);
|
|
89
|
+
const result = { scanned: 0, ok: 0, failed: 0, repaired: 0, issues: [] };
|
|
90
|
+
const recordingIds = tracker.getAllRecordingIds();
|
|
91
|
+
for (const id of recordingIds) {
|
|
92
|
+
const state = tracker.getRecordingState(id);
|
|
93
|
+
if (!state)
|
|
94
|
+
continue;
|
|
95
|
+
const dir = recordingDir(this.outDir, state.recordedAt, id);
|
|
96
|
+
result.scanned++;
|
|
97
|
+
try {
|
|
98
|
+
const mismatches = await verifyChecksums(dir);
|
|
99
|
+
if (mismatches.length === 0) {
|
|
100
|
+
result.ok++;
|
|
101
|
+
tracker.markVerified(id);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
result.failed++;
|
|
105
|
+
for (const m of mismatches) {
|
|
106
|
+
result.issues.push({
|
|
107
|
+
recordingId: id,
|
|
108
|
+
file: path.basename(m.filePath),
|
|
109
|
+
issue: `checksum mismatch (expected: ${m.expected.slice(0, 8)}..., got: ${m.actual === 'MISSING' ? 'MISSING' : m.actual.slice(0, 8) + '...'})`,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
// TODO: repair support requires re-fetching the recording object
|
|
113
|
+
// For now, log the mismatch
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
result.failed++;
|
|
118
|
+
result.issues.push({ recordingId: id, file: '', issue: String(err) });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
await tracker.persist(this.outDir);
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Export all local recordings to a JSONL dataset file.
|
|
126
|
+
* Returns the path to the generated file.
|
|
127
|
+
*/
|
|
128
|
+
async exportDataset(opts = {}) {
|
|
129
|
+
const { DatasetWriter } = await import('./storage/dataset-writer.js');
|
|
130
|
+
const { default: fsSync } = await import('node:fs');
|
|
131
|
+
// Walk recordings dir and collect existing transcript data
|
|
132
|
+
const datasetWriter = new DatasetWriter(this.outDir);
|
|
133
|
+
await datasetWriter.open();
|
|
134
|
+
// Re-generate from existing transcript.json files on disk
|
|
135
|
+
const recordingsBase = path.join(this.outDir, 'recordings');
|
|
136
|
+
try {
|
|
137
|
+
await this.walkAndExport(recordingsBase, datasetWriter);
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
await datasetWriter.close();
|
|
141
|
+
}
|
|
142
|
+
return datasetWriter.path;
|
|
143
|
+
}
|
|
144
|
+
async walkAndExport(recordingsBase, dataset) {
|
|
145
|
+
const { PlaudRecordingSchema } = await import('./client/types.js');
|
|
146
|
+
const { PlaudTranscriptSchema } = await import('./client/types.js');
|
|
147
|
+
// Walk year/month/dir structure
|
|
148
|
+
let yearDirs;
|
|
149
|
+
try {
|
|
150
|
+
yearDirs = await fs.readdir(recordingsBase);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
for (const year of yearDirs) {
|
|
156
|
+
const yearPath = path.join(recordingsBase, year);
|
|
157
|
+
let monthDirs;
|
|
158
|
+
try {
|
|
159
|
+
monthDirs = await fs.readdir(yearPath);
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
for (const month of monthDirs) {
|
|
165
|
+
const monthPath = path.join(yearPath, month);
|
|
166
|
+
let recDirs;
|
|
167
|
+
try {
|
|
168
|
+
recDirs = await fs.readdir(monthPath);
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
for (const recDir of recDirs) {
|
|
174
|
+
const recPath = path.join(monthPath, recDir);
|
|
175
|
+
try {
|
|
176
|
+
const metaRaw = await fs.readFile(path.join(recPath, 'meta.json'), 'utf8');
|
|
177
|
+
const transcriptRaw = await fs.readFile(path.join(recPath, 'transcript.json'), 'utf8');
|
|
178
|
+
const meta = JSON.parse(metaRaw);
|
|
179
|
+
const transcriptData = JSON.parse(transcriptRaw);
|
|
180
|
+
// Reconstruct minimal PlaudRecording from meta.json
|
|
181
|
+
const recording = PlaudRecordingSchema.parse({
|
|
182
|
+
id: meta['source_recording_id'],
|
|
183
|
+
title: meta['title'],
|
|
184
|
+
duration: meta['duration_seconds'],
|
|
185
|
+
recordedAt: meta['recorded_at'],
|
|
186
|
+
createdAt: meta['recorded_at'],
|
|
187
|
+
updatedAt: meta['recorded_at'],
|
|
188
|
+
hasTranscript: true,
|
|
189
|
+
_raw: meta,
|
|
190
|
+
});
|
|
191
|
+
const fullText = (transcriptData['segments'] ?? [])
|
|
192
|
+
.map(s => s.text ?? '')
|
|
193
|
+
.filter(Boolean)
|
|
194
|
+
.join('\n\n');
|
|
195
|
+
const transcript = PlaudTranscriptSchema.parse({
|
|
196
|
+
recordingId: String(meta['source_recording_id'] ?? ''),
|
|
197
|
+
duration: Number(meta['duration_seconds'] ?? 0),
|
|
198
|
+
segments: transcriptData['segments'] ?? [],
|
|
199
|
+
fullText,
|
|
200
|
+
_raw: transcriptData,
|
|
201
|
+
});
|
|
202
|
+
await dataset.append(this.outDir, recording, transcript);
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
// Skip recordings with missing/invalid files
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async buildClient() {
|
|
212
|
+
const creds = await loadCredentials();
|
|
213
|
+
if (!creds) {
|
|
214
|
+
throw new AuthError("No credentials found — run 'alta-plaud auth' to authenticate");
|
|
215
|
+
}
|
|
216
|
+
if (isExpired(creds)) {
|
|
217
|
+
throw new AuthError("Credentials expired — run 'alta-plaud auth' to re-authenticate");
|
|
218
|
+
}
|
|
219
|
+
return new PlaudApiClient(creds);
|
|
220
|
+
}
|
|
221
|
+
buildSyncOptions(partial) {
|
|
222
|
+
return {
|
|
223
|
+
outDir: this.outDir,
|
|
224
|
+
since: partial.since,
|
|
225
|
+
limit: partial.limit,
|
|
226
|
+
concurrency: partial.concurrency ?? 3,
|
|
227
|
+
formats: partial.formats ?? ['json', 'txt', 'md'],
|
|
228
|
+
includeDataset: partial.includeDataset ?? true,
|
|
229
|
+
dryRun: partial.dryRun ?? false,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
get dataDir() {
|
|
233
|
+
return this.outDir;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=PlaudExtractor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PlaudExtractor.js","sourceRoot":"","sources":["../src/PlaudExtractor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AuthSession } from './types.js';
|
|
2
|
+
export interface BrowserAuthOptions {
|
|
3
|
+
headless?: boolean;
|
|
4
|
+
email?: string;
|
|
5
|
+
password?: string;
|
|
6
|
+
/** How long to wait for the user to log in (ms). Default: 5 minutes. */
|
|
7
|
+
loginTimeoutMs?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function runBrowserAuth(opts?: BrowserAuthOptions): Promise<AuthSession>;
|
|
10
|
+
//# sourceMappingURL=browser-auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-auth.d.ts","sourceRoot":"","sources":["../../src/auth/browser-auth.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAe,MAAM,YAAY,CAAA;AAI1D,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,wEAAwE;IACxE,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,wBAAsB,cAAc,CAAC,IAAI,GAAE,kBAAuB,GAAG,OAAO,CAAC,WAAW,CAAC,CA4FxF"}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { chromium } from 'playwright';
|
|
3
|
+
import { AuthError } from '../errors.js';
|
|
4
|
+
import { getLogger } from '../logger.js';
|
|
5
|
+
import { loadCredentials } from './token-store.js';
|
|
6
|
+
import { extractRegionalBaseUrl } from '../client/endpoints.js';
|
|
7
|
+
const PLAUD_APP_URL = 'https://web.plaud.ai';
|
|
8
|
+
export async function runBrowserAuth(opts = {}) {
|
|
9
|
+
const log = getLogger();
|
|
10
|
+
const launchOpts = {
|
|
11
|
+
channel: 'chrome',
|
|
12
|
+
headless: opts.headless ?? false,
|
|
13
|
+
args: ['--disable-blink-features=AutomationControlled'],
|
|
14
|
+
};
|
|
15
|
+
const browser = await chromium.launch(launchOpts).catch(async (err) => {
|
|
16
|
+
const msg = String(err);
|
|
17
|
+
if (msg.includes("Executable doesn't exist") || msg.includes('not found')) {
|
|
18
|
+
log.warn('System Chrome not found, falling back to Playwright Chromium (Google OAuth may be blocked)');
|
|
19
|
+
return chromium.launch({ headless: opts.headless ?? false }).catch(err2 => {
|
|
20
|
+
if (String(err2).includes("Executable doesn't exist")) {
|
|
21
|
+
log.info('Installing Playwright Chromium (one-time setup)...');
|
|
22
|
+
execSync('npx playwright install chromium', { stdio: 'inherit' });
|
|
23
|
+
return chromium.launch({ headless: opts.headless ?? false });
|
|
24
|
+
}
|
|
25
|
+
throw err2;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
throw err;
|
|
29
|
+
});
|
|
30
|
+
const context = await browser.newContext({ userAgent: undefined });
|
|
31
|
+
const page = await context.newPage();
|
|
32
|
+
// Remove webdriver property that Google checks for automation detection
|
|
33
|
+
await page.addInitScript('Object.defineProperty(navigator, "webdriver", { get: () => undefined })');
|
|
34
|
+
// Inject existing plaud.ai cookies so we don't need a fresh login if session is still valid
|
|
35
|
+
await injectExistingCookies(context);
|
|
36
|
+
try {
|
|
37
|
+
log.info('Opening Plaud...');
|
|
38
|
+
// Set up Bearer token capture BEFORE navigation — the SPA fires API calls on load
|
|
39
|
+
const loginTimeoutMs = opts.loginTimeoutMs ?? 5 * 60_000;
|
|
40
|
+
const bearerTokenCapture = captureBearerToken(page, loginTimeoutMs, log);
|
|
41
|
+
await page.goto(PLAUD_APP_URL, { waitUntil: 'domcontentloaded' });
|
|
42
|
+
// Give SPA time to initialize and run its auth check (may redirect to /login)
|
|
43
|
+
await page.waitForLoadState('networkidle', { timeout: 10_000 }).catch(() => { });
|
|
44
|
+
if (opts.email && opts.password) {
|
|
45
|
+
await automatedLogin(page, opts.email, opts.password);
|
|
46
|
+
}
|
|
47
|
+
else if (isLoginUrl(page.url())) {
|
|
48
|
+
// Not logged in — prompt user and wait
|
|
49
|
+
console.log('\n──────────────────────────────────────────────────────────');
|
|
50
|
+
console.log(' Log in to Plaud in the browser window.');
|
|
51
|
+
console.log(' The browser will close automatically once connected.');
|
|
52
|
+
console.log(` (Waiting up to ${Math.round(loginTimeoutMs / 60_000)} minutes)`);
|
|
53
|
+
console.log('──────────────────────────────────────────────────────────\n');
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
log.info('Already connected — capturing token...');
|
|
57
|
+
}
|
|
58
|
+
// Wait for Bearer token from any API request (fires on page load if session is active,
|
|
59
|
+
// or after login if the user needed to authenticate)
|
|
60
|
+
const authToken = await bearerTokenCapture;
|
|
61
|
+
log.info('Bearer token captured — closing browser');
|
|
62
|
+
const cookies = await context.cookies();
|
|
63
|
+
// Close browser without blocking — Chrome can take a long time to flush its profile
|
|
64
|
+
void browser.close().catch(() => { });
|
|
65
|
+
// Discover the correct regional API base URL (e.g. api-euc1.plaud.ai for EU users)
|
|
66
|
+
const apiBaseUrl = await discoverApiRegion(authToken);
|
|
67
|
+
log.info({ apiBaseUrl }, 'Regional API base URL discovered');
|
|
68
|
+
return {
|
|
69
|
+
cookies: cookies.map(c => ({
|
|
70
|
+
name: c.name,
|
|
71
|
+
value: c.value,
|
|
72
|
+
domain: c.domain,
|
|
73
|
+
path: c.path,
|
|
74
|
+
httpOnly: c.httpOnly,
|
|
75
|
+
secure: c.secure,
|
|
76
|
+
sameSite: c.sameSite,
|
|
77
|
+
expires: c.expires && c.expires > 0 ? c.expires : undefined,
|
|
78
|
+
})),
|
|
79
|
+
authToken,
|
|
80
|
+
apiBaseUrl,
|
|
81
|
+
capturedAt: new Date().toISOString(),
|
|
82
|
+
endpointMap: buildEndpointMap(apiBaseUrl),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
await browser.close().catch(() => { });
|
|
87
|
+
throw err;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
91
|
+
/**
|
|
92
|
+
* Inject plaud.ai cookies from the previous auth session so the browser picks up
|
|
93
|
+
* an existing session without requiring the user to log in again.
|
|
94
|
+
*/
|
|
95
|
+
async function injectExistingCookies(context) {
|
|
96
|
+
const log = getLogger();
|
|
97
|
+
const existing = await loadCredentials().catch(() => null);
|
|
98
|
+
if (!existing?.cookies?.length)
|
|
99
|
+
return;
|
|
100
|
+
const plaudCookies = existing.cookies.filter(c => c.domain === 'web.plaud.ai' || c.domain.endsWith('.plaud.ai') || c.domain === 'plaud.ai');
|
|
101
|
+
if (plaudCookies.length === 0)
|
|
102
|
+
return;
|
|
103
|
+
try {
|
|
104
|
+
await context.addCookies(plaudCookies.map(c => ({
|
|
105
|
+
name: c.name,
|
|
106
|
+
value: c.value,
|
|
107
|
+
domain: c.domain,
|
|
108
|
+
path: c.path,
|
|
109
|
+
httpOnly: c.httpOnly,
|
|
110
|
+
secure: c.secure,
|
|
111
|
+
sameSite: (c.sameSite ?? 'Lax'),
|
|
112
|
+
expires: c.expires ?? -1,
|
|
113
|
+
})));
|
|
114
|
+
log.debug({ count: plaudCookies.length }, 'Injected existing session cookies');
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
log.debug({ err }, 'Could not inject existing cookies — fresh login required');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Wait for the first API request that carries a Bearer token.
|
|
122
|
+
* This fires automatically when:
|
|
123
|
+
* - The page loads with an existing authenticated session (cookies restored)
|
|
124
|
+
* - The user completes login via Google OAuth or email
|
|
125
|
+
*
|
|
126
|
+
* Resolves with the raw token string (without "bearer " prefix).
|
|
127
|
+
*/
|
|
128
|
+
function captureBearerToken(page, timeoutMs, log) {
|
|
129
|
+
return new Promise((resolve, reject) => {
|
|
130
|
+
const timer = setTimeout(() => {
|
|
131
|
+
page.off('request', handler);
|
|
132
|
+
reject(new AuthError(`Login timeout after ${Math.round(timeoutMs / 60_000)} minutes — no token captured`));
|
|
133
|
+
}, timeoutMs);
|
|
134
|
+
const handler = (req) => {
|
|
135
|
+
const auth = req.headers()['authorization'] ?? req.headers()['Authorization'];
|
|
136
|
+
if (!auth)
|
|
137
|
+
return;
|
|
138
|
+
const token = auth.replace(/^bearer\s+/i, '').trim();
|
|
139
|
+
// Basic sanity check: JWT has 3 parts separated by dots
|
|
140
|
+
if (token.split('.').length === 3) {
|
|
141
|
+
clearTimeout(timer);
|
|
142
|
+
page.off('request', handler);
|
|
143
|
+
log.debug({ url: req.url() }, 'Bearer token found in request');
|
|
144
|
+
resolve(token);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
page.on('request', handler);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/** Discover the correct regional API base URL (e.g. https://api-euc1.plaud.ai). */
|
|
151
|
+
async function discoverApiRegion(token) {
|
|
152
|
+
const log = getLogger();
|
|
153
|
+
try {
|
|
154
|
+
// The global endpoint returns a region-redirect response pointing to the right server
|
|
155
|
+
const res = await fetch('https://api.plaud.ai/user/me', {
|
|
156
|
+
headers: {
|
|
157
|
+
'Authorization': `bearer ${token}`,
|
|
158
|
+
'app-platform': 'web',
|
|
159
|
+
'Origin': 'https://web.plaud.ai',
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
const body = await res.json();
|
|
163
|
+
const regional = extractRegionalBaseUrl(body);
|
|
164
|
+
if (regional)
|
|
165
|
+
return regional;
|
|
166
|
+
// If the global endpoint returns user data directly (no redirect), it IS the right base
|
|
167
|
+
if (body?.data_user)
|
|
168
|
+
return 'https://api.plaud.ai';
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
log.debug({ err }, 'Region discovery failed — using global API');
|
|
172
|
+
}
|
|
173
|
+
return 'https://api.plaud.ai';
|
|
174
|
+
}
|
|
175
|
+
/** Build the complete endpoint map from the known regional API base URL. */
|
|
176
|
+
function buildEndpointMap(apiBaseUrl) {
|
|
177
|
+
return {
|
|
178
|
+
listRecordings: `${apiBaseUrl}/file/simple/web`,
|
|
179
|
+
batchDetail: `${apiBaseUrl}/file/list`,
|
|
180
|
+
getAudioUrl: `${apiBaseUrl}/file/temp-url`,
|
|
181
|
+
userProfile: `${apiBaseUrl}/user/me`,
|
|
182
|
+
apiBaseUrl,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function isLoginUrl(url) {
|
|
186
|
+
try {
|
|
187
|
+
const p = new URL(url).pathname;
|
|
188
|
+
return p.startsWith('/login') || p.startsWith('/signin') || p.startsWith('/auth');
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async function automatedLogin(page, email, password) {
|
|
195
|
+
const log = getLogger();
|
|
196
|
+
log.info('Attempting automated login...');
|
|
197
|
+
const emailSelectors = [
|
|
198
|
+
'input[type="email"]', 'input[name="email"]',
|
|
199
|
+
'input[name="username"]', '[data-testid="email"]', '#email',
|
|
200
|
+
];
|
|
201
|
+
const passwordSelectors = [
|
|
202
|
+
'input[type="password"]', 'input[name="password"]',
|
|
203
|
+
'[data-testid="password"]', '#password',
|
|
204
|
+
];
|
|
205
|
+
for (const sel of emailSelectors) {
|
|
206
|
+
if (await page.locator(sel).count() > 0) {
|
|
207
|
+
await page.fill(sel, email);
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
for (const sel of passwordSelectors) {
|
|
212
|
+
if (await page.locator(sel).count() > 0) {
|
|
213
|
+
await page.fill(sel, password);
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
await page.click('button[type="submit"], [type="submit"], button:has-text("Login"), button:has-text("Sign in")');
|
|
218
|
+
await page.waitForLoadState('networkidle', { timeout: 15_000 }).catch(() => undefined);
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=browser-auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-auth.js","sourceRoot":"","sources":["../../src/auth/browser-auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { authTokenPath } from '../storage/paths.js';
|
|
2
|
+
import { type AuthSession, type StoredCredentials } from './types.js';
|
|
3
|
+
export declare function loadCredentials(): Promise<StoredCredentials | null>;
|
|
4
|
+
export declare function saveCredentials(session: AuthSession): Promise<void>;
|
|
5
|
+
/** Returns true if the stored credentials are expired. */
|
|
6
|
+
export declare function isExpired(creds: StoredCredentials): boolean;
|
|
7
|
+
export declare function cookieHeader(creds: StoredCredentials): string;
|
|
8
|
+
export { authTokenPath };
|
|
9
|
+
//# sourceMappingURL=token-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-store.d.ts","sourceRoot":"","sources":["../../src/auth/token-store.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAA2B,KAAK,WAAW,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAI9F,wBAAsB,eAAe,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAgBzE;AAED,wBAAsB,eAAe,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAKzE;AAED,0DAA0D;AAC1D,wBAAgB,SAAS,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CA2B3D;AAeD,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,CAE7D;AAED,OAAO,EAAE,aAAa,EAAE,CAAA"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import { authTokenPath } from '../storage/paths.js';
|
|
3
|
+
import { StoredCredentialsSchema } from './types.js';
|
|
4
|
+
import { writeFileAtomic } from '../storage/atomic.js';
|
|
5
|
+
import { getLogger } from '../logger.js';
|
|
6
|
+
export async function loadCredentials() {
|
|
7
|
+
const tokenPath = authTokenPath();
|
|
8
|
+
try {
|
|
9
|
+
const raw = await fs.readFile(tokenPath, 'utf8');
|
|
10
|
+
const json = JSON.parse(raw);
|
|
11
|
+
const result = StoredCredentialsSchema.safeParse(json);
|
|
12
|
+
if (!result.success) {
|
|
13
|
+
getLogger().warn({ issues: result.error.issues }, 'Stored credentials failed schema validation — re-authenticate');
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
return result.data;
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
if (err.code === 'ENOENT')
|
|
20
|
+
return null;
|
|
21
|
+
getLogger().warn({ err }, 'Failed to read credentials file');
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export async function saveCredentials(session) {
|
|
26
|
+
const tokenPath = authTokenPath();
|
|
27
|
+
const stored = { ...session, schemaVersion: 1 };
|
|
28
|
+
await writeFileAtomic(tokenPath, JSON.stringify(stored, null, 2));
|
|
29
|
+
getLogger().info({ path: tokenPath }, 'Auth credentials saved');
|
|
30
|
+
}
|
|
31
|
+
/** Returns true if the stored credentials are expired. */
|
|
32
|
+
export function isExpired(creds) {
|
|
33
|
+
const now = Date.now();
|
|
34
|
+
// Explicit expiresAt takes precedence
|
|
35
|
+
if (creds.expiresAt) {
|
|
36
|
+
return now > new Date(creds.expiresAt).getTime();
|
|
37
|
+
}
|
|
38
|
+
// If we have a JWT bearer token, decode the exp claim (most reliable)
|
|
39
|
+
if (creds.authToken) {
|
|
40
|
+
const jwtExp = decodeJwtExp(creds.authToken);
|
|
41
|
+
if (jwtExp !== null)
|
|
42
|
+
return now > jwtExp * 1000;
|
|
43
|
+
}
|
|
44
|
+
// Fallback: check only plaud.ai session cookies (ignore analytics/CDN cookies
|
|
45
|
+
// which have short TTLs and would cause false "expired" readings)
|
|
46
|
+
const plaudCookies = creds.cookies.filter(c => c.expires && c.expires > 0 && (c.domain.endsWith('.plaud.ai') || c.domain === 'plaud.ai'));
|
|
47
|
+
if (plaudCookies.length > 0) {
|
|
48
|
+
const minExpiry = Math.min(...plaudCookies.map(c => (c.expires ?? 0) * 1000));
|
|
49
|
+
if (minExpiry > 0 && now > minExpiry)
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
// Last resort: treat as expired after 30 days
|
|
53
|
+
const capturedAt = new Date(creds.capturedAt).getTime();
|
|
54
|
+
return now - capturedAt > 30 * 24 * 60 * 60 * 1000;
|
|
55
|
+
}
|
|
56
|
+
/** Decode the `exp` claim from a JWT (no signature verification — just decode). */
|
|
57
|
+
function decodeJwtExp(token) {
|
|
58
|
+
try {
|
|
59
|
+
const parts = token.split('.');
|
|
60
|
+
if (parts.length !== 3)
|
|
61
|
+
return null;
|
|
62
|
+
const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));
|
|
63
|
+
const exp = payload['exp'];
|
|
64
|
+
return typeof exp === 'number' ? exp : null;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export function cookieHeader(creds) {
|
|
71
|
+
return creds.cookies.map(c => `${c.name}=${c.value}`).join('; ');
|
|
72
|
+
}
|
|
73
|
+
export { authTokenPath };
|
|
74
|
+
//# sourceMappingURL=token-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-store.js","sourceRoot":"","sources":["../../src/auth/token-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAA;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAAE,uBAAuB,EAA4C,MAAM,YAAY,CAAA;AAC9F,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAExC,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,MAAM,SAAS,GAAG,aAAa,EAAE,CAAA;IACjC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;QAChD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAC5B,MAAM,MAAM,GAAG,uBAAuB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;QACtD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,SAAS,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,+DAA+D,CAAC,CAAA;YAClH,OAAO,IAAI,CAAA;QACb,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAA;IACpB,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAA;QACjE,SAAS,EAAE,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,EAAE,iCAAiC,CAAC,CAAA;QAC5D,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAAoB;IACxD,MAAM,SAAS,GAAG,aAAa,EAAE,CAAA;IACjC,MAAM,MAAM,GAAsB,EAAE,GAAG,OAAO,EAAE,aAAa,EAAE,CAAC,EAAE,CAAA;IAClE,MAAM,eAAe,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAA;IACjE,SAAS,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,wBAAwB,CAAC,CAAA;AACjE,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,SAAS,CAAC,KAAwB;IAChD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IAEtB,sCAAsC;IACtC,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;QACpB,OAAO,GAAG,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAA;IAClD,CAAC;IAED,sEAAsE;IACtE,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;QACpB,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;QAC5C,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,GAAG,GAAG,MAAM,GAAG,IAAI,CAAA;IACjD,CAAC;IAED,8EAA8E;IAC9E,kEAAkE;IAClE,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CACvC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,UAAU,CAAC,CAC/F,CAAA;IACD,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAA;QAC7E,IAAI,SAAS,GAAG,CAAC,IAAI,GAAG,GAAG,SAAS;YAAE,OAAO,IAAI,CAAA;IACnD,CAAC;IAED,8CAA8C;IAC9C,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAA;IACvD,OAAO,GAAG,GAAG,UAAU,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;AACpD,CAAC;AAED,mFAAmF;AACnF,SAAS,YAAY,CAAC,KAAa;IACjC,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAC9B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAA;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAE,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAA4B,CAAA;QAC3G,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAA;QAC1B,OAAO,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAA;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAwB;IACnD,OAAO,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAClE,CAAC;AAED,OAAO,EAAE,aAAa,EAAE,CAAA"}
|