@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.
Files changed (163) hide show
  1. package/.env.example +9 -0
  2. package/.github/workflows/ci.yml +33 -0
  3. package/.github/workflows/publish.yml +46 -0
  4. package/CLAUDE.md +53 -0
  5. package/README.md +318 -0
  6. package/dist/PlaudExtractor.d.ts +61 -0
  7. package/dist/PlaudExtractor.d.ts.map +1 -0
  8. package/dist/PlaudExtractor.js +236 -0
  9. package/dist/PlaudExtractor.js.map +1 -0
  10. package/dist/auth/browser-auth.d.ts +10 -0
  11. package/dist/auth/browser-auth.d.ts.map +1 -0
  12. package/dist/auth/browser-auth.js +220 -0
  13. package/dist/auth/browser-auth.js.map +1 -0
  14. package/dist/auth/token-store.d.ts +9 -0
  15. package/dist/auth/token-store.d.ts.map +1 -0
  16. package/dist/auth/token-store.js +74 -0
  17. package/dist/auth/token-store.js.map +1 -0
  18. package/dist/auth/types.d.ts +266 -0
  19. package/dist/auth/types.d.ts.map +1 -0
  20. package/dist/auth/types.js +32 -0
  21. package/dist/auth/types.js.map +1 -0
  22. package/dist/cli/bin.d.ts +3 -0
  23. package/dist/cli/bin.d.ts.map +1 -0
  24. package/dist/cli/bin.js +30 -0
  25. package/dist/cli/bin.js.map +1 -0
  26. package/dist/cli/commands/auth.d.ts +3 -0
  27. package/dist/cli/commands/auth.d.ts.map +1 -0
  28. package/dist/cli/commands/auth.js +22 -0
  29. package/dist/cli/commands/auth.js.map +1 -0
  30. package/dist/cli/commands/backfill.d.ts +3 -0
  31. package/dist/cli/commands/backfill.d.ts.map +1 -0
  32. package/dist/cli/commands/backfill.js +59 -0
  33. package/dist/cli/commands/backfill.js.map +1 -0
  34. package/dist/cli/commands/sync.d.ts +3 -0
  35. package/dist/cli/commands/sync.d.ts.map +1 -0
  36. package/dist/cli/commands/sync.js +55 -0
  37. package/dist/cli/commands/sync.js.map +1 -0
  38. package/dist/cli/commands/verify.d.ts +3 -0
  39. package/dist/cli/commands/verify.d.ts.map +1 -0
  40. package/dist/cli/commands/verify.js +28 -0
  41. package/dist/cli/commands/verify.js.map +1 -0
  42. package/dist/cli/exit-codes.d.ts +8 -0
  43. package/dist/cli/exit-codes.d.ts.map +1 -0
  44. package/dist/cli/exit-codes.js +16 -0
  45. package/dist/cli/exit-codes.js.map +1 -0
  46. package/dist/cli/options.d.ts +31 -0
  47. package/dist/cli/options.d.ts.map +1 -0
  48. package/dist/cli/options.js +11 -0
  49. package/dist/cli/options.js.map +1 -0
  50. package/dist/client/endpoints.d.ts +26 -0
  51. package/dist/client/endpoints.d.ts.map +1 -0
  52. package/dist/client/endpoints.js +54 -0
  53. package/dist/client/endpoints.js.map +1 -0
  54. package/dist/client/http.d.ts +17 -0
  55. package/dist/client/http.d.ts.map +1 -0
  56. package/dist/client/http.js +92 -0
  57. package/dist/client/http.js.map +1 -0
  58. package/dist/client/plaud-client.d.ts +14 -0
  59. package/dist/client/plaud-client.d.ts.map +1 -0
  60. package/dist/client/plaud-client.js +216 -0
  61. package/dist/client/plaud-client.js.map +1 -0
  62. package/dist/client/types.d.ts +154 -0
  63. package/dist/client/types.d.ts.map +1 -0
  64. package/dist/client/types.js +41 -0
  65. package/dist/client/types.js.map +1 -0
  66. package/dist/errors.d.ts +24 -0
  67. package/dist/errors.d.ts.map +1 -0
  68. package/dist/errors.js +51 -0
  69. package/dist/errors.js.map +1 -0
  70. package/dist/index.d.ts +7 -0
  71. package/dist/index.d.ts.map +1 -0
  72. package/dist/index.js +5 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/logger.d.ts +9 -0
  75. package/dist/logger.d.ts.map +1 -0
  76. package/dist/logger.js +37 -0
  77. package/dist/logger.js.map +1 -0
  78. package/dist/mcp/job-tools.d.ts +3 -0
  79. package/dist/mcp/job-tools.d.ts.map +1 -0
  80. package/dist/mcp/job-tools.js +108 -0
  81. package/dist/mcp/job-tools.js.map +1 -0
  82. package/dist/mcp/read-tools.d.ts +3 -0
  83. package/dist/mcp/read-tools.d.ts.map +1 -0
  84. package/dist/mcp/read-tools.js +173 -0
  85. package/dist/mcp/read-tools.js.map +1 -0
  86. package/dist/mcp/server.d.ts +3 -0
  87. package/dist/mcp/server.d.ts.map +1 -0
  88. package/dist/mcp/server.js +32 -0
  89. package/dist/mcp/server.js.map +1 -0
  90. package/dist/storage/atomic.d.ts +5 -0
  91. package/dist/storage/atomic.d.ts.map +1 -0
  92. package/dist/storage/atomic.js +51 -0
  93. package/dist/storage/atomic.js.map +1 -0
  94. package/dist/storage/checksums.d.ts +15 -0
  95. package/dist/storage/checksums.d.ts.map +1 -0
  96. package/dist/storage/checksums.js +56 -0
  97. package/dist/storage/checksums.js.map +1 -0
  98. package/dist/storage/dataset-writer.d.ts +21 -0
  99. package/dist/storage/dataset-writer.d.ts.map +1 -0
  100. package/dist/storage/dataset-writer.js +52 -0
  101. package/dist/storage/dataset-writer.js.map +1 -0
  102. package/dist/storage/paths.d.ts +9 -0
  103. package/dist/storage/paths.d.ts.map +1 -0
  104. package/dist/storage/paths.js +38 -0
  105. package/dist/storage/paths.js.map +1 -0
  106. package/dist/storage/recording-store.d.ts +24 -0
  107. package/dist/storage/recording-store.d.ts.map +1 -0
  108. package/dist/storage/recording-store.js +161 -0
  109. package/dist/storage/recording-store.js.map +1 -0
  110. package/dist/sync/download-queue.d.ts +21 -0
  111. package/dist/sync/download-queue.d.ts.map +1 -0
  112. package/dist/sync/download-queue.js +82 -0
  113. package/dist/sync/download-queue.js.map +1 -0
  114. package/dist/sync/incremental.d.ts +21 -0
  115. package/dist/sync/incremental.d.ts.map +1 -0
  116. package/dist/sync/incremental.js +96 -0
  117. package/dist/sync/incremental.js.map +1 -0
  118. package/dist/sync/sync-engine.d.ts +6 -0
  119. package/dist/sync/sync-engine.d.ts.map +1 -0
  120. package/dist/sync/sync-engine.js +135 -0
  121. package/dist/sync/sync-engine.js.map +1 -0
  122. package/dist/sync/types.d.ts +130 -0
  123. package/dist/sync/types.d.ts.map +1 -0
  124. package/dist/sync/types.js +17 -0
  125. package/dist/sync/types.js.map +1 -0
  126. package/dist/transcript/formatter.d.ts +4 -0
  127. package/dist/transcript/formatter.d.ts.map +1 -0
  128. package/dist/transcript/formatter.js +88 -0
  129. package/dist/transcript/formatter.js.map +1 -0
  130. package/package.json +41 -0
  131. package/src/PlaudExtractor.ts +275 -0
  132. package/src/auth/browser-auth.ts +248 -0
  133. package/src/auth/token-store.ts +79 -0
  134. package/src/auth/types.ts +41 -0
  135. package/src/cli/bin.ts +30 -0
  136. package/src/cli/commands/auth.ts +27 -0
  137. package/src/cli/commands/backfill.ts +77 -0
  138. package/src/cli/commands/sync.ts +71 -0
  139. package/src/cli/commands/verify.ts +31 -0
  140. package/src/cli/exit-codes.ts +14 -0
  141. package/src/cli/options.ts +10 -0
  142. package/src/client/endpoints.ts +62 -0
  143. package/src/client/http.ts +110 -0
  144. package/src/client/plaud-client.ts +268 -0
  145. package/src/client/types.ts +62 -0
  146. package/src/errors.ts +57 -0
  147. package/src/index.ts +17 -0
  148. package/src/logger.ts +49 -0
  149. package/src/mcp/job-tools.ts +156 -0
  150. package/src/mcp/read-tools.ts +204 -0
  151. package/src/mcp/server.ts +39 -0
  152. package/src/storage/atomic.ts +51 -0
  153. package/src/storage/checksums.ts +76 -0
  154. package/src/storage/dataset-writer.ts +74 -0
  155. package/src/storage/paths.ts +44 -0
  156. package/src/storage/recording-store.ts +182 -0
  157. package/src/sync/download-queue.ts +102 -0
  158. package/src/sync/incremental.ts +111 -0
  159. package/src/sync/sync-engine.ts +183 -0
  160. package/src/sync/types.ts +64 -0
  161. package/src/transcript/formatter.ts +91 -0
  162. package/tsconfig.build.json +8 -0
  163. package/tsconfig.json +19 -0
@@ -0,0 +1,96 @@
1
+ import fs from 'node:fs/promises';
2
+ import crypto from 'node:crypto';
3
+ import { syncStatePath } from '../storage/paths.js';
4
+ import { writeFileAtomic } from '../storage/atomic.js';
5
+ import { SyncStateSchema } from './types.js';
6
+ import { getLogger } from '../logger.js';
7
+ export class IncrementalTracker {
8
+ state = {
9
+ schemaVersion: 1,
10
+ recordings: {},
11
+ };
12
+ async load(outDir) {
13
+ const filePath = syncStatePath(outDir);
14
+ try {
15
+ const raw = await fs.readFile(filePath, 'utf8');
16
+ const json = JSON.parse(raw);
17
+ const result = SyncStateSchema.safeParse(json);
18
+ if (result.success) {
19
+ this.state = result.data;
20
+ getLogger().debug({ recordingCount: Object.keys(this.state.recordings).length }, 'Loaded sync state');
21
+ }
22
+ else {
23
+ getLogger().warn({ issues: result.error.issues }, 'Sync state schema invalid — starting fresh');
24
+ }
25
+ }
26
+ catch (err) {
27
+ if (err.code !== 'ENOENT') {
28
+ getLogger().warn({ err }, 'Failed to read sync state — starting fresh');
29
+ }
30
+ }
31
+ }
32
+ async persist(outDir) {
33
+ this.state.lastAttemptAt = new Date().toISOString();
34
+ await writeFileAtomic(syncStatePath(outDir), JSON.stringify(this.state, null, 2));
35
+ }
36
+ markSuccessfulSync() {
37
+ this.state.lastSuccessfulSyncAt = new Date().toISOString();
38
+ }
39
+ getSince() {
40
+ if (!this.state.lastSuccessfulSyncAt)
41
+ return undefined;
42
+ return new Date(this.state.lastSuccessfulSyncAt);
43
+ }
44
+ needsDownload(recording) {
45
+ const existing = this.state.recordings[recording.id];
46
+ if (!existing)
47
+ return true;
48
+ const newHash = this.computeContentHash(recording);
49
+ if (existing.contentHash !== newHash)
50
+ return true;
51
+ // Re-download if key files are missing
52
+ if (!existing.hasTranscript && recording.hasTranscript)
53
+ return true;
54
+ if (!existing.downloadedAt)
55
+ return true;
56
+ return false;
57
+ }
58
+ computeContentHash(recording) {
59
+ const key = JSON.stringify({
60
+ id: recording.id,
61
+ updatedAt: recording.updatedAt,
62
+ hasTranscript: recording.hasTranscript,
63
+ transcriptStatus: recording.transcriptStatus,
64
+ duration: recording.duration,
65
+ title: recording.title,
66
+ });
67
+ return crypto.createHash('sha256').update(key).digest('hex').slice(0, 16);
68
+ }
69
+ markComplete(recordingId, recordedAt, opts) {
70
+ this.state.recordings[recordingId] = {
71
+ recordedAt,
72
+ contentHash: opts.contentHash,
73
+ downloadedAt: new Date().toISOString(),
74
+ hasAudio: opts.hasAudio,
75
+ hasTranscript: opts.hasTranscript,
76
+ verified: false,
77
+ };
78
+ }
79
+ markVerified(recordingId) {
80
+ const existing = this.state.recordings[recordingId];
81
+ if (existing) {
82
+ existing.verified = true;
83
+ existing.verifiedAt = new Date().toISOString();
84
+ }
85
+ }
86
+ getRecordingState(recordingId) {
87
+ return this.state.recordings[recordingId];
88
+ }
89
+ getAllRecordingIds() {
90
+ return Object.keys(this.state.recordings);
91
+ }
92
+ get lastSuccessfulSyncAt() {
93
+ return this.state.lastSuccessfulSyncAt;
94
+ }
95
+ }
96
+ //# sourceMappingURL=incremental.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"incremental.js","sourceRoot":"","sources":["../../src/sync/incremental.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { SyncOptions, SyncResult } from './types.js';
2
+ import type { PlaudClient } from '../client/types.js';
3
+ export declare class SyncEngine {
4
+ run(client: PlaudClient, opts: SyncOptions, mode?: 'sync' | 'backfill'): Promise<SyncResult>;
5
+ }
6
+ //# sourceMappingURL=sync-engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-engine.d.ts","sourceRoot":"","sources":["../../src/sync/sync-engine.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAmB,MAAM,YAAY,CAAA;AAC1E,OAAO,KAAK,EAAE,WAAW,EAAkB,MAAM,oBAAoB,CAAA;AAGrE,qBAAa,UAAU;IACf,GAAG,CACP,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,WAAW,EACjB,IAAI,GAAE,MAAM,GAAG,UAAmB,GACjC,OAAO,CAAC,UAAU,CAAC;CA4GvB"}
@@ -0,0 +1,135 @@
1
+ import { AuthError } from '../errors.js';
2
+ import { getLogger } from '../logger.js';
3
+ import { IncrementalTracker } from './incremental.js';
4
+ import { processQueue, retryWithBackoff } from './download-queue.js';
5
+ import { RecordingStore } from '../storage/recording-store.js';
6
+ import { DatasetWriter } from '../storage/dataset-writer.js';
7
+ export class SyncEngine {
8
+ async run(client, opts, mode = 'sync') {
9
+ const log = getLogger();
10
+ const startedAt = Date.now();
11
+ // 1. Verify auth
12
+ const authed = await client.isAuthenticated();
13
+ if (!authed) {
14
+ throw new AuthError("Not authenticated — run 'alta-plaud auth' first");
15
+ }
16
+ // 2. Load sync state
17
+ const tracker = new IncrementalTracker();
18
+ await tracker.load(opts.outDir);
19
+ // 3. Determine effective --since
20
+ const since = opts.since ?? (mode === 'sync' ? tracker.getSince() : undefined);
21
+ log.info({ mode, since: since?.toISOString(), outDir: opts.outDir }, 'Starting sync');
22
+ // 4. Collect recordings to process
23
+ const toProcess = [];
24
+ const skipped = [];
25
+ let listCount = 0;
26
+ for await (const recording of client.listRecordings({ since, limit: opts.limit })) {
27
+ listCount++;
28
+ if (mode === 'sync' && !tracker.needsDownload(recording)) {
29
+ skipped.push(recording);
30
+ }
31
+ else {
32
+ toProcess.push(recording);
33
+ }
34
+ if (opts.limit && toProcess.length >= opts.limit)
35
+ break;
36
+ }
37
+ log.info({ total: listCount, toProcess: toProcess.length, skipped: skipped.length }, 'Recordings collected');
38
+ // 5. Dry run — just print plan
39
+ if (opts.dryRun) {
40
+ for (const rec of toProcess) {
41
+ log.info({ id: rec.id, title: rec.title, recordedAt: rec.recordedAt }, '[dry-run] Would download');
42
+ }
43
+ return {
44
+ mode,
45
+ attempted: 0,
46
+ succeeded: 0,
47
+ failed: 0,
48
+ skipped: skipped.length,
49
+ durationMs: Date.now() - startedAt,
50
+ errors: [],
51
+ };
52
+ }
53
+ // 6. Initialize storage
54
+ const store = new RecordingStore(opts.outDir);
55
+ const dataset = opts.includeDataset ? new DatasetWriter(opts.outDir) : null;
56
+ if (dataset)
57
+ await dataset.open();
58
+ // 7. Run download queue
59
+ const errors = [];
60
+ const httpClient = getHttpClient(client);
61
+ const { succeeded, failed } = await processQueue(toProcess, async (recording) => {
62
+ await retryWithBackoff(() => downloadRecording(recording, client, store, dataset, tracker, httpClient, opts), { label: `recording:${recording.id}` });
63
+ }, opts.concurrency);
64
+ for (const { item, error } of failed) {
65
+ errors.push({ recordingId: item.id, error });
66
+ log.error({ recordingId: item.id, err: error }, 'Failed to download recording');
67
+ }
68
+ // 8. Mark successful sync only if zero failures
69
+ if (errors.length === 0) {
70
+ tracker.markSuccessfulSync();
71
+ }
72
+ // 9. Persist state
73
+ await tracker.persist(opts.outDir);
74
+ if (dataset)
75
+ await dataset.close();
76
+ const result = {
77
+ mode,
78
+ attempted: toProcess.length,
79
+ succeeded: succeeded.length,
80
+ failed: failed.length,
81
+ skipped: skipped.length,
82
+ durationMs: Date.now() - startedAt,
83
+ errors,
84
+ datasetPath: dataset?.path,
85
+ };
86
+ log.info(result, 'Sync complete');
87
+ return result;
88
+ }
89
+ }
90
+ async function downloadRecording(recording, client, store, dataset, tracker, httpClient, opts) {
91
+ const log = getLogger();
92
+ log.info({ recordingId: recording.id, title: recording.title }, 'Downloading recording');
93
+ // a. Write metadata
94
+ await store.writeMetadata(recording);
95
+ // b. Download transcript
96
+ let hasTranscript = false;
97
+ if (recording.hasTranscript) {
98
+ try {
99
+ const transcript = await client.getTranscript(recording.id);
100
+ await store.writeTranscript(recording, transcript, opts.formats);
101
+ if (dataset) {
102
+ await dataset.append(opts.outDir, recording, transcript);
103
+ }
104
+ hasTranscript = true;
105
+ }
106
+ catch (err) {
107
+ log.warn({ recordingId: recording.id, err }, 'Failed to get transcript');
108
+ }
109
+ }
110
+ // c. Download audio
111
+ let hasAudio = false;
112
+ if (httpClient) {
113
+ const audioUrl = await client.getAudioDownloadUrl(recording.id);
114
+ if (audioUrl) {
115
+ hasAudio = await store.writeAudioFromUrl(recording, audioUrl, httpClient);
116
+ }
117
+ }
118
+ // d. Write checksums
119
+ await store.writeChecksums(recording);
120
+ // e. Mark complete in state
121
+ tracker.markComplete(recording.id, recording.recordedAt, {
122
+ hasAudio,
123
+ hasTranscript,
124
+ contentHash: tracker.computeContentHash(recording),
125
+ });
126
+ }
127
+ /** Extract HttpClient from PlaudApiClient for audio downloads */
128
+ function getHttpClient(client) {
129
+ // PlaudApiClient exposes getHttpClient()
130
+ if ('getHttpClient' in client && typeof client.getHttpClient === 'function') {
131
+ return client.getHttpClient();
132
+ }
133
+ return null;
134
+ }
135
+ //# sourceMappingURL=sync-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-engine.js","sourceRoot":"","sources":["../../src/sync/sync-engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,130 @@
1
+ import { z } from 'zod';
2
+ import type { TranscriptFormat } from '../storage/recording-store.js';
3
+ export declare const RecordingStateSchema: z.ZodObject<{
4
+ recordedAt: z.ZodString;
5
+ contentHash: z.ZodOptional<z.ZodString>;
6
+ downloadedAt: z.ZodOptional<z.ZodString>;
7
+ hasAudio: z.ZodDefault<z.ZodBoolean>;
8
+ hasTranscript: z.ZodDefault<z.ZodBoolean>;
9
+ verified: z.ZodDefault<z.ZodBoolean>;
10
+ verifiedAt: z.ZodOptional<z.ZodString>;
11
+ }, "strip", z.ZodTypeAny, {
12
+ recordedAt: string;
13
+ hasTranscript: boolean;
14
+ hasAudio: boolean;
15
+ verified: boolean;
16
+ contentHash?: string | undefined;
17
+ downloadedAt?: string | undefined;
18
+ verifiedAt?: string | undefined;
19
+ }, {
20
+ recordedAt: string;
21
+ hasTranscript?: boolean | undefined;
22
+ contentHash?: string | undefined;
23
+ downloadedAt?: string | undefined;
24
+ hasAudio?: boolean | undefined;
25
+ verified?: boolean | undefined;
26
+ verifiedAt?: string | undefined;
27
+ }>;
28
+ export type RecordingState = z.infer<typeof RecordingStateSchema>;
29
+ export declare const SyncStateSchema: z.ZodObject<{
30
+ schemaVersion: z.ZodLiteral<1>;
31
+ lastSuccessfulSyncAt: z.ZodOptional<z.ZodString>;
32
+ lastAttemptAt: z.ZodOptional<z.ZodString>;
33
+ recordings: z.ZodRecord<z.ZodString, z.ZodObject<{
34
+ recordedAt: z.ZodString;
35
+ contentHash: z.ZodOptional<z.ZodString>;
36
+ downloadedAt: z.ZodOptional<z.ZodString>;
37
+ hasAudio: z.ZodDefault<z.ZodBoolean>;
38
+ hasTranscript: z.ZodDefault<z.ZodBoolean>;
39
+ verified: z.ZodDefault<z.ZodBoolean>;
40
+ verifiedAt: z.ZodOptional<z.ZodString>;
41
+ }, "strip", z.ZodTypeAny, {
42
+ recordedAt: string;
43
+ hasTranscript: boolean;
44
+ hasAudio: boolean;
45
+ verified: boolean;
46
+ contentHash?: string | undefined;
47
+ downloadedAt?: string | undefined;
48
+ verifiedAt?: string | undefined;
49
+ }, {
50
+ recordedAt: string;
51
+ hasTranscript?: boolean | undefined;
52
+ contentHash?: string | undefined;
53
+ downloadedAt?: string | undefined;
54
+ hasAudio?: boolean | undefined;
55
+ verified?: boolean | undefined;
56
+ verifiedAt?: string | undefined;
57
+ }>>;
58
+ }, "strip", z.ZodTypeAny, {
59
+ recordings: Record<string, {
60
+ recordedAt: string;
61
+ hasTranscript: boolean;
62
+ hasAudio: boolean;
63
+ verified: boolean;
64
+ contentHash?: string | undefined;
65
+ downloadedAt?: string | undefined;
66
+ verifiedAt?: string | undefined;
67
+ }>;
68
+ schemaVersion: 1;
69
+ lastSuccessfulSyncAt?: string | undefined;
70
+ lastAttemptAt?: string | undefined;
71
+ }, {
72
+ recordings: Record<string, {
73
+ recordedAt: string;
74
+ hasTranscript?: boolean | undefined;
75
+ contentHash?: string | undefined;
76
+ downloadedAt?: string | undefined;
77
+ hasAudio?: boolean | undefined;
78
+ verified?: boolean | undefined;
79
+ verifiedAt?: string | undefined;
80
+ }>;
81
+ schemaVersion: 1;
82
+ lastSuccessfulSyncAt?: string | undefined;
83
+ lastAttemptAt?: string | undefined;
84
+ }>;
85
+ export type SyncState = z.infer<typeof SyncStateSchema>;
86
+ export interface SyncOptions {
87
+ /** Output directory root */
88
+ outDir: string;
89
+ /** Only sync recordings after this date */
90
+ since?: Date;
91
+ /** Max number of recordings to process */
92
+ limit?: number;
93
+ /** Parallel downloads (default: 3) */
94
+ concurrency: number;
95
+ /** Transcript formats to write */
96
+ formats: TranscriptFormat[];
97
+ /** Append to JSONL dataset */
98
+ includeDataset: boolean;
99
+ /** Print plan without downloading */
100
+ dryRun: boolean;
101
+ }
102
+ export interface BackfillOptions extends Omit<SyncOptions, 'since'> {
103
+ /** Backfill from a specific date; defaults to all-time */
104
+ since?: Date;
105
+ }
106
+ export interface SyncResult {
107
+ mode: 'sync' | 'backfill';
108
+ attempted: number;
109
+ succeeded: number;
110
+ failed: number;
111
+ skipped: number;
112
+ durationMs: number;
113
+ errors: Array<{
114
+ recordingId: string;
115
+ error: Error;
116
+ }>;
117
+ datasetPath?: string;
118
+ }
119
+ export interface VerifyResult {
120
+ scanned: number;
121
+ ok: number;
122
+ failed: number;
123
+ repaired: number;
124
+ issues: Array<{
125
+ recordingId: string;
126
+ file: string;
127
+ issue: string;
128
+ }>;
129
+ }
130
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/sync/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AAErE,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;EAQ/B,CAAA;AAEF,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAA;AAEjE,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAK1B,CAAA;AAEF,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA;AAEvD,MAAM,WAAW,WAAW;IAC1B,4BAA4B;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,2CAA2C;IAC3C,KAAK,CAAC,EAAE,IAAI,CAAA;IACZ,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,sCAAsC;IACtC,WAAW,EAAE,MAAM,CAAA;IACnB,kCAAkC;IAClC,OAAO,EAAE,gBAAgB,EAAE,CAAA;IAC3B,8BAA8B;IAC9B,cAAc,EAAE,OAAO,CAAA;IACvB,qCAAqC;IACrC,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC;IACjE,0DAA0D;IAC1D,KAAK,CAAC,EAAE,IAAI,CAAA;CACb;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,GAAG,UAAU,CAAA;IACzB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,KAAK,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,CAAC,CAAA;IACpD,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAA;IACf,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,KAAK,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACpE"}
@@ -0,0 +1,17 @@
1
+ import { z } from 'zod';
2
+ export const RecordingStateSchema = z.object({
3
+ recordedAt: z.string().datetime(),
4
+ contentHash: z.string().optional(),
5
+ downloadedAt: z.string().datetime().optional(),
6
+ hasAudio: z.boolean().default(false),
7
+ hasTranscript: z.boolean().default(false),
8
+ verified: z.boolean().default(false),
9
+ verifiedAt: z.string().datetime().optional(),
10
+ });
11
+ export const SyncStateSchema = z.object({
12
+ schemaVersion: z.literal(1),
13
+ lastSuccessfulSyncAt: z.string().datetime().optional(),
14
+ lastAttemptAt: z.string().datetime().optional(),
15
+ recordings: z.record(z.string(), RecordingStateSchema),
16
+ });
17
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/sync/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAC9C,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IACpC,aAAa,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IACzC,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IACpC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;CAC7C,CAAC,CAAA;AAIF,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IAC3B,oBAAoB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IACtD,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAC/C,UAAU,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,oBAAoB,CAAC;CACvD,CAAC,CAAA"}
@@ -0,0 +1,4 @@
1
+ import type { PlaudTranscript, PlaudRecording } from '../client/types.js';
2
+ export declare function toPlainText(transcript: PlaudTranscript): string;
3
+ export declare function toMarkdown(transcript: PlaudTranscript, recording: PlaudRecording): string;
4
+ //# sourceMappingURL=formatter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatter.d.ts","sourceRoot":"","sources":["../../src/transcript/formatter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAEzE,wBAAgB,WAAW,CAAC,UAAU,EAAE,eAAe,GAAG,MAAM,CAO/D;AAED,wBAAgB,UAAU,CAAC,UAAU,EAAE,eAAe,EAAE,SAAS,EAAE,cAAc,GAAG,MAAM,CA2CzF"}
@@ -0,0 +1,88 @@
1
+ export function toPlainText(transcript) {
2
+ return transcript.segments
3
+ .map(seg => {
4
+ const speaker = seg.speaker ? `${seg.speaker}: ` : '';
5
+ return `${speaker}${seg.text}`;
6
+ })
7
+ .join('\n\n');
8
+ }
9
+ export function toMarkdown(transcript, recording) {
10
+ const lines = [];
11
+ // YAML frontmatter
12
+ lines.push('---');
13
+ lines.push('source: plaud');
14
+ lines.push(`id: "${recording.id}"`);
15
+ lines.push(`recorded_at: "${recording.recordedAt}"`);
16
+ if (recording.title)
17
+ lines.push(`title: "${recording.title.replace(/"/g, '\\"')}"`);
18
+ if (recording.language)
19
+ lines.push(`language: "${recording.language}"`);
20
+ lines.push(`duration_seconds: ${recording.duration}`);
21
+ if (recording.tags?.length)
22
+ lines.push(`tags: [${recording.tags.map(t => `"${t}"`).join(', ')}]`);
23
+ lines.push('---');
24
+ lines.push('');
25
+ // Title
26
+ lines.push(`# ${recording.title ?? 'Untitled Recording'}`);
27
+ lines.push('');
28
+ // Metadata block
29
+ lines.push(`**Recorded:** ${formatDate(recording.recordedAt)}`);
30
+ lines.push(`**Duration:** ${formatDuration(recording.duration)}`);
31
+ if (recording.language)
32
+ lines.push(`**Language:** ${recording.language}`);
33
+ lines.push('');
34
+ lines.push('## Transcript');
35
+ lines.push('');
36
+ // Segments
37
+ const hasTimestamps = transcript.segments.some(s => s.startMs > 0 || s.endMs > 0);
38
+ for (const seg of transcript.segments) {
39
+ if (hasTimestamps) {
40
+ const ts = `\`[${msToTimestamp(seg.startMs)}]\``;
41
+ const speaker = seg.speaker ? ` **${seg.speaker}**` : '';
42
+ lines.push(`${ts}${speaker}`);
43
+ }
44
+ else if (seg.speaker) {
45
+ lines.push(`**${seg.speaker}**`);
46
+ }
47
+ lines.push(seg.text);
48
+ lines.push('');
49
+ }
50
+ return lines.join('\n');
51
+ }
52
+ function msToTimestamp(ms) {
53
+ const totalSeconds = Math.floor(ms / 1000);
54
+ const h = Math.floor(totalSeconds / 3600);
55
+ const m = Math.floor((totalSeconds % 3600) / 60);
56
+ const s = totalSeconds % 60;
57
+ if (h > 0) {
58
+ return `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
59
+ }
60
+ return `${m}:${String(s).padStart(2, '0')}`;
61
+ }
62
+ function formatDuration(seconds) {
63
+ const h = Math.floor(seconds / 3600);
64
+ const m = Math.floor((seconds % 3600) / 60);
65
+ const s = Math.floor(seconds % 60);
66
+ if (h > 0)
67
+ return `${h}h ${m}m ${s}s`;
68
+ if (m > 0)
69
+ return `${m}m ${s}s`;
70
+ return `${s}s`;
71
+ }
72
+ function formatDate(iso) {
73
+ try {
74
+ return new Date(iso).toLocaleString('en-US', {
75
+ weekday: 'long',
76
+ year: 'numeric',
77
+ month: 'long',
78
+ day: 'numeric',
79
+ hour: '2-digit',
80
+ minute: '2-digit',
81
+ timeZoneName: 'short',
82
+ });
83
+ }
84
+ catch {
85
+ return iso;
86
+ }
87
+ }
88
+ //# sourceMappingURL=formatter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatter.js","sourceRoot":"","sources":["../../src/transcript/formatter.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@alta-foundation/plaud-extractor",
3
+ "version": "1.0.0",
4
+ "description": "Export recordings, transcripts, and metadata from Plaud — SDK + CLI",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "bin": {
15
+ "alta-plaud": "./dist/cli/bin.js",
16
+ "alta-plaud-mcp": "./dist/mcp/server.js"
17
+ },
18
+ "dependencies": {
19
+ "@modelcontextprotocol/sdk": "^1.27.1",
20
+ "commander": "^12.1.0",
21
+ "pino": "^9.5.0",
22
+ "pino-pretty": "^11.3.0",
23
+ "playwright": "^1.49.0",
24
+ "undici": "^6.21.0",
25
+ "zod": "^3.23.8"
26
+ },
27
+ "devDependencies": {
28
+ "@types/node": "^20.17.0",
29
+ "tsx": "^4.19.0",
30
+ "typescript": "^5.7.0"
31
+ },
32
+ "engines": {
33
+ "node": ">=20.0.0"
34
+ },
35
+ "scripts": {
36
+ "build": "tsc -p tsconfig.build.json",
37
+ "dev": "tsx src/cli/bin.ts",
38
+ "dev:mcp": "tsx src/mcp/server.ts",
39
+ "typecheck": "tsc --noEmit"
40
+ }
41
+ }