plaud-notes-to-notion 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 (91) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/LICENSE +21 -0
  3. package/README.md +102 -0
  4. package/bin/plaud-notes-to-notion.js +16 -0
  5. package/dist/card.d.ts +30 -0
  6. package/dist/card.js +287 -0
  7. package/dist/card.js.map +1 -0
  8. package/dist/cli.d.ts +1 -0
  9. package/dist/cli.js +266 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/config.d.ts +41 -0
  12. package/dist/config.js +85 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/constants.d.ts +52 -0
  15. package/dist/constants.js +53 -0
  16. package/dist/constants.js.map +1 -0
  17. package/dist/daemon.d.ts +13 -0
  18. package/dist/daemon.js +182 -0
  19. package/dist/daemon.js.map +1 -0
  20. package/dist/format.d.ts +36 -0
  21. package/dist/format.js +108 -0
  22. package/dist/format.js.map +1 -0
  23. package/dist/ingest.d.ts +49 -0
  24. package/dist/ingest.js +205 -0
  25. package/dist/ingest.js.map +1 -0
  26. package/dist/llm.d.ts +65 -0
  27. package/dist/llm.js +177 -0
  28. package/dist/llm.js.map +1 -0
  29. package/dist/lock.d.ts +26 -0
  30. package/dist/lock.js +134 -0
  31. package/dist/lock.js.map +1 -0
  32. package/dist/log.d.ts +14 -0
  33. package/dist/log.js +41 -0
  34. package/dist/log.js.map +1 -0
  35. package/dist/notion.d.ts +86 -0
  36. package/dist/notion.js +254 -0
  37. package/dist/notion.js.map +1 -0
  38. package/dist/paths.d.ts +11 -0
  39. package/dist/paths.js +51 -0
  40. package/dist/paths.js.map +1 -0
  41. package/dist/plaud/callback.d.ts +19 -0
  42. package/dist/plaud/callback.js +162 -0
  43. package/dist/plaud/callback.js.map +1 -0
  44. package/dist/plaud/client.d.ts +55 -0
  45. package/dist/plaud/client.js +246 -0
  46. package/dist/plaud/client.js.map +1 -0
  47. package/dist/plaud/constants.d.ts +18 -0
  48. package/dist/plaud/constants.js +19 -0
  49. package/dist/plaud/constants.js.map +1 -0
  50. package/dist/plaud/errors.d.ts +10 -0
  51. package/dist/plaud/errors.js +32 -0
  52. package/dist/plaud/errors.js.map +1 -0
  53. package/dist/plaud/login.d.ts +17 -0
  54. package/dist/plaud/login.js +89 -0
  55. package/dist/plaud/login.js.map +1 -0
  56. package/dist/plaud/oauth.d.ts +21 -0
  57. package/dist/plaud/oauth.js +197 -0
  58. package/dist/plaud/oauth.js.map +1 -0
  59. package/dist/plaud/pkce.d.ts +3 -0
  60. package/dist/plaud/pkce.js +11 -0
  61. package/dist/plaud/pkce.js.map +1 -0
  62. package/dist/plaud/session.d.ts +23 -0
  63. package/dist/plaud/session.js +77 -0
  64. package/dist/plaud/session.js.map +1 -0
  65. package/dist/plaud/types.d.ts +21 -0
  66. package/dist/plaud/types.js +2 -0
  67. package/dist/plaud/types.js.map +1 -0
  68. package/dist/redact.d.ts +3 -0
  69. package/dist/redact.js +37 -0
  70. package/dist/redact.js.map +1 -0
  71. package/dist/runtime.d.ts +44 -0
  72. package/dist/runtime.js +94 -0
  73. package/dist/runtime.js.map +1 -0
  74. package/dist/scrub.d.ts +17 -0
  75. package/dist/scrub.js +124 -0
  76. package/dist/scrub.js.map +1 -0
  77. package/dist/secrets.d.ts +59 -0
  78. package/dist/secrets.js +220 -0
  79. package/dist/secrets.js.map +1 -0
  80. package/dist/ssn.d.ts +33 -0
  81. package/dist/ssn.js +170 -0
  82. package/dist/ssn.js.map +1 -0
  83. package/dist/state.d.ts +46 -0
  84. package/dist/state.js +23 -0
  85. package/dist/state.js.map +1 -0
  86. package/dist/sync.d.ts +60 -0
  87. package/dist/sync.js +190 -0
  88. package/dist/sync.js.map +1 -0
  89. package/examples/com.plaud-notes-to-notion.plist +34 -0
  90. package/examples/install-launchagent.sh +16 -0
  91. package/package.json +37 -0
package/dist/daemon.js ADDED
@@ -0,0 +1,182 @@
1
+ import fs from "node:fs";
2
+ import { loadConfig } from "./config.js";
3
+ import { relogin, RELLOGIN_NOTION } from "./constants.js";
4
+ import { runIngest } from "./ingest.js";
5
+ import { createJobLock } from "./lock.js";
6
+ import { NotionError } from "./notion.js";
7
+ import { getConfigPath, getLockFilePath } from "./paths.js";
8
+ import { isAuthExpiredError, isTransportError } from "./plaud/errors.js";
9
+ import { safeErrorMessage } from "./redact.js";
10
+ import { buildProfileRuntime, notifyMac, readStatus, SetupError, updateStatus } from "./runtime.js";
11
+ import { loadState, pruneState, saveState } from "./state.js";
12
+ import { runDeletionSync, runSync } from "./sync.js";
13
+ const DEEP_SCAN_EVERY_MS = 10 * 60_000;
14
+ const ALERT_EVERY_MS = 6 * 3_600_000;
15
+ const RUNTIME_RETRY_MS = 5 * 60_000;
16
+ function describe(err, credentials) {
17
+ if (isAuthExpiredError(err)) {
18
+ return { message: relogin(credentials), auth: true };
19
+ }
20
+ if (err instanceof NotionError && err.unauthorized) {
21
+ return { message: RELLOGIN_NOTION(credentials), auth: true };
22
+ }
23
+ if (err instanceof SetupError) {
24
+ return { message: err.message, auth: true };
25
+ }
26
+ if (isTransportError(err)) {
27
+ const c = err.cause;
28
+ const detail = c ? ` — ${[c.name, c.code, c.message].filter(Boolean).join(" ")}` : "";
29
+ return { message: `Network problem (${safeErrorMessage(err)}${safeErrorMessage(detail)}); retrying next cycle.`, auth: false };
30
+ }
31
+ return { message: safeErrorMessage(err), auth: false };
32
+ }
33
+ /**
34
+ * Long-running LaunchAgent loop. Per enabled profile: ingest every pollSeconds
35
+ * (deep scan every 10 min), Summary→Transcript sync every syncSeconds.
36
+ * Re-reads config.json when it changes, so turning Tim on needs no restart.
37
+ */
38
+ export async function runDaemon(options) {
39
+ const env = options.env || process.env;
40
+ const log = options.log;
41
+ const lock = createJobLock({ lockFile: getLockFilePath(env), log: (m) => log.warn(m) });
42
+ if (!lock.acquire()) {
43
+ log.warn("Another plaud-notes-to-notion daemon holds the lock; exiting.");
44
+ return 0;
45
+ }
46
+ const stop = () => lock.release();
47
+ process.once("SIGTERM", () => {
48
+ stop();
49
+ process.exit(0);
50
+ });
51
+ process.once("SIGINT", () => {
52
+ stop();
53
+ process.exit(0);
54
+ });
55
+ let config = loadConfig(env);
56
+ let configMtime = fs.statSync(getConfigPath(env)).mtimeMs;
57
+ const timers = new Map();
58
+ log.info(`Daemon started (poll ${config.pollSeconds}s, sync ${config.syncSeconds}s, model ${config.llmModel}/${config.llmEffort}).`);
59
+ const reportError = (name, credentials, err) => {
60
+ const { message, auth } = describe(err, credentials);
61
+ const now = new Date();
62
+ const prev = readStatus(env)[name];
63
+ const patch = { lastError: message, lastErrorAt: now.toISOString() };
64
+ if (auth && (!prev?.alertedAt || now.getTime() - Date.parse(prev.alertedAt) > ALERT_EVERY_MS)) {
65
+ notifyMac("Plaud Notes to Notion", message);
66
+ patch.alertedAt = now.toISOString();
67
+ }
68
+ updateStatus(name, patch, env);
69
+ log.error(`[${name}] ${message}`);
70
+ return auth;
71
+ };
72
+ while (!options.signal?.aborted) {
73
+ try {
74
+ const mtime = fs.statSync(getConfigPath(env)).mtimeMs;
75
+ if (mtime !== configMtime) {
76
+ config = loadConfig(env);
77
+ configMtime = mtime;
78
+ for (const t of timers.values()) {
79
+ t.runtime = undefined;
80
+ }
81
+ log.info("Config changed; reloaded.");
82
+ }
83
+ }
84
+ catch (err) {
85
+ log.error(`Config reload failed; keeping previous config: ${safeErrorMessage(err)}`);
86
+ }
87
+ for (const [name, profile] of Object.entries(config.profiles)) {
88
+ if (!profile.enabled || (!profile.ingest && !profile.syncTranscripts)) {
89
+ continue;
90
+ }
91
+ const now = Date.now();
92
+ const t = timers.get(name) || { nextPoll: now, nextSync: now, nextDeep: now, runtimeRetryAt: 0 };
93
+ timers.set(name, t);
94
+ if (!t.runtime) {
95
+ if (now < t.runtimeRetryAt) {
96
+ continue;
97
+ }
98
+ try {
99
+ t.runtime = await buildProfileRuntime({
100
+ name,
101
+ profile,
102
+ config,
103
+ store: options.store,
104
+ env,
105
+ needPlaud: profile.ingest,
106
+ needLabeler: profile.ingest
107
+ });
108
+ }
109
+ catch (err) {
110
+ reportError(name, profile.credentials, err);
111
+ t.runtimeRetryAt = now + RUNTIME_RETRY_MS;
112
+ continue;
113
+ }
114
+ }
115
+ const rt = t.runtime;
116
+ const state = loadState(name, env);
117
+ const save = () => saveState(name, state, env);
118
+ const plog = (m) => log.info(m);
119
+ if (profile.ingest && now >= t.nextPoll) {
120
+ const deep = now >= t.nextDeep;
121
+ t.nextPoll = now + config.pollSeconds * 1000;
122
+ if (deep) {
123
+ t.nextDeep = now + DEEP_SCAN_EVERY_MS;
124
+ }
125
+ updateStatus(name, { lastPollAt: new Date().toISOString() }, env);
126
+ try {
127
+ const r = await runIngest({
128
+ profile: name,
129
+ owner: profile.owner,
130
+ startAfter: new Date(profile.startAfter),
131
+ plaud: rt.plaud,
132
+ notion: rt.notion,
133
+ labeler: rt.labeler,
134
+ state,
135
+ save,
136
+ log: plog
137
+ }, { deep });
138
+ pruneState(state, new Date());
139
+ save();
140
+ const patch = { lastPollOkAt: new Date().toISOString(), pending: r.pending };
141
+ if (r.written.length) {
142
+ patch.lastWritten = { title: r.written.at(-1)?.title, at: new Date().toISOString() };
143
+ }
144
+ if (!r.errors.length) {
145
+ patch.lastError = undefined;
146
+ }
147
+ else {
148
+ for (const e of r.errors) {
149
+ log.error(`[${name}] ${e}`);
150
+ }
151
+ }
152
+ updateStatus(name, patch, env);
153
+ }
154
+ catch (err) {
155
+ if (reportError(name, profile.credentials, err)) {
156
+ t.runtime = undefined;
157
+ t.runtimeRetryAt = Date.now() + RUNTIME_RETRY_MS;
158
+ continue;
159
+ }
160
+ }
161
+ }
162
+ if (profile.syncTranscripts && now >= t.nextSync) {
163
+ t.nextSync = now + config.syncSeconds * 1000;
164
+ try {
165
+ await runSync({ profile: name, notion: rt.notion, state, save, log: plog });
166
+ await runDeletionSync({ profile: name, notion: rt.notion, state, save, log: plog });
167
+ updateStatus(name, { lastSyncOkAt: new Date().toISOString() }, env);
168
+ }
169
+ catch (err) {
170
+ if (reportError(name, profile.credentials, err)) {
171
+ t.runtime = undefined;
172
+ t.runtimeRetryAt = Date.now() + RUNTIME_RETRY_MS;
173
+ }
174
+ }
175
+ }
176
+ }
177
+ await new Promise((r) => setTimeout(r, 1000));
178
+ }
179
+ stop();
180
+ return 0;
181
+ }
182
+ //# sourceMappingURL=daemon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon.js","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import type { PlaudSegment } from "./plaud/client.js";
2
+ /**
3
+ * Summary cleanup ported from the Zap (steps 3–5), in the same order:
4
+ * 3. drop the literal "PLAUD NOTE" banner
5
+ * 4. straight double quotes → typographic quotes (the Zap turned every `"`
6
+ * into a closing `”`; this pairs them as “…”)
7
+ * 5. drop Plaud image embeds (`![..](permanent/..)`), `---` dividers and blank
8
+ * lines, and split `> **Label:** text` into a bold label + quote.
9
+ */
10
+ export declare function cleanSummaryMarkdown(markdown: string): string;
11
+ /**
12
+ * Consecutive `>` lines are one quote in the Zap's output (one Notion quote
13
+ * block with line breaks). Notion's markdown API makes a block per line, so
14
+ * join them with `<br>`; list lines inside a quote become the quote's
15
+ * children (the Zap nested them, but under the previous to-do by mistake).
16
+ */
17
+ export declare function mergeQuoteRuns(markdown: string): string;
18
+ /** Opening quote after start/space/bracket/dash, closing quote everywhere else. */
19
+ export declare function smartQuotes(text: string): string;
20
+ export declare function formatOffset(ms: number): string;
21
+ /**
22
+ * Transcript text exactly as the Zap wrote it (Plaud's "Markdown" transcript):
23
+ * `Speaker 1 00:00:01` (start rounded to the second) then the utterance, one
24
+ * line each, no merging.
25
+ */
26
+ export declare function formatTranscript(segments: PlaudSegment[]): string;
27
+ /** Notion `Recorded`: recording start, truncated to the minute, as a UTC instant. */
28
+ export declare function recordedMinute(startAt: Date): string;
29
+ /** Meeting title: Plaud's own title minus its leading `MM-DD ` date (Peter 2026-09-26). */
30
+ export declare function meetingTitle(plaudName: string): string;
31
+ /**
32
+ * Participants as Notion multi-select options: owner and generic diarization
33
+ * labels removed, commas stripped (Notion rejects them), duplicates dropped.
34
+ */
35
+ export declare function cleanParticipants(names: string[], owner: string): string[];
36
+ export declare function cleanTitle(title: string): string;
package/dist/format.js ADDED
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Summary cleanup ported from the Zap (steps 3–5), in the same order:
3
+ * 3. drop the literal "PLAUD NOTE" banner
4
+ * 4. straight double quotes → typographic quotes (the Zap turned every `"`
5
+ * into a closing `”`; this pairs them as “…”)
6
+ * 5. drop Plaud image embeds (`![..](permanent/..)`), `---` dividers and blank
7
+ * lines, and split `> **Label:** text` into a bold label + quote.
8
+ */
9
+ export function cleanSummaryMarkdown(markdown) {
10
+ let t = markdown.replace(/\r\n?/g, "\n");
11
+ t = t.split("PLAUD NOTE").join("");
12
+ t = smartQuotes(t);
13
+ t = t.replace(/!\[[^\]]*\]\([ \t]*\n?[ \t]*permanent\/[^)]*\)\n*/g, "");
14
+ t = t.replace(/\n-{3,}\n/g, "\n");
15
+ t = t.replace(/\n[ \t]*\n+/g, "\n");
16
+ t = t.replace(/^> \*\*(.+?):\*\*[ \t]*(.*)$/gm, (_m, label, rest) => rest && rest.trim().length > 0 ? `**${label}:**\n> ${rest}` : `**${label}:**`);
17
+ return mergeQuoteRuns(t.trim());
18
+ }
19
+ /**
20
+ * Consecutive `>` lines are one quote in the Zap's output (one Notion quote
21
+ * block with line breaks). Notion's markdown API makes a block per line, so
22
+ * join them with `<br>`; list lines inside a quote become the quote's
23
+ * children (the Zap nested them, but under the previous to-do by mistake).
24
+ */
25
+ export function mergeQuoteRuns(markdown) {
26
+ const lines = markdown.split("\n");
27
+ const out = [];
28
+ let i = 0;
29
+ while (i < lines.length) {
30
+ if (!/^>/.test(lines[i])) {
31
+ out.push(lines[i]);
32
+ i++;
33
+ continue;
34
+ }
35
+ const text = [];
36
+ const children = [];
37
+ while (i < lines.length && /^>/.test(lines[i])) {
38
+ const body = lines[i].replace(/^>[ \t]?/, "");
39
+ if (children.length || (text.length && /^(?:\d+\.|[-*+])\s+/.test(body))) {
40
+ children.push(`\t${body}`);
41
+ }
42
+ else {
43
+ text.push(body);
44
+ }
45
+ i++;
46
+ }
47
+ out.push(`> ${text.join("<br>")}`, ...children);
48
+ }
49
+ return out.join("\n");
50
+ }
51
+ /** Opening quote after start/space/bracket/dash, closing quote everywhere else. */
52
+ export function smartQuotes(text) {
53
+ return text.replace(/(^|[\s(\[{—–-])"/gm, "$1“").replace(/"/g, "”");
54
+ }
55
+ export function formatOffset(ms) {
56
+ const total = Math.max(0, Math.round(ms / 1000));
57
+ const h = Math.floor(total / 3600);
58
+ const m = Math.floor((total % 3600) / 60);
59
+ const s = total % 60;
60
+ return [h, m, s].map((n) => String(n).padStart(2, "0")).join(":");
61
+ }
62
+ /**
63
+ * Transcript text exactly as the Zap wrote it (Plaud's "Markdown" transcript):
64
+ * `Speaker 1 00:00:01` (start rounded to the second) then the utterance, one
65
+ * line each, no merging.
66
+ */
67
+ export function formatTranscript(segments) {
68
+ return segments.map((s) => `${s.speaker} ${formatOffset(s.startMs)}\n${s.text}`).join("\n");
69
+ }
70
+ /** Notion `Recorded`: recording start, truncated to the minute, as a UTC instant. */
71
+ export function recordedMinute(startAt) {
72
+ const d = new Date(startAt.getTime());
73
+ d.setUTCSeconds(0, 0);
74
+ return d.toISOString();
75
+ }
76
+ /** Meeting title: Plaud's own title minus its leading `MM-DD ` date (Peter 2026-09-26). */
77
+ export function meetingTitle(plaudName) {
78
+ const t = plaudName.replace(/^\d{2}-\d{2}\s+/, "").trim();
79
+ return t || "Untitled recording";
80
+ }
81
+ /**
82
+ * Participants as Notion multi-select options: owner and generic diarization
83
+ * labels removed, commas stripped (Notion rejects them), duplicates dropped.
84
+ */
85
+ export function cleanParticipants(names, owner) {
86
+ const ownerLc = owner.trim().toLowerCase();
87
+ const seen = new Set();
88
+ const out = [];
89
+ for (const raw of names) {
90
+ const name = raw.replace(/,/g, "").replace(/\s+/g, " ").trim().slice(0, 100);
91
+ const lc = name.toLowerCase();
92
+ if (!name || /^speaker\s*\d*$/i.test(name) || /^unknown/i.test(name)) {
93
+ continue;
94
+ }
95
+ if (lc === ownerLc) {
96
+ continue;
97
+ }
98
+ if (!seen.has(lc)) {
99
+ seen.add(lc);
100
+ out.push(name);
101
+ }
102
+ }
103
+ return out;
104
+ }
105
+ export function cleanTitle(title) {
106
+ return title.replace(/\s+/g, " ").trim().slice(0, 300);
107
+ }
108
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import type { Labeler } from "./llm.js";
2
+ import type { NotionStore } from "./notion.js";
3
+ import type { PlaudClient, PlaudRecording } from "./plaud/client.js";
4
+ import type { ProfileState } from "./state.js";
5
+ export interface IngestDeps {
6
+ profile: string;
7
+ owner: string;
8
+ startAfter: Date;
9
+ plaud: PlaudClient;
10
+ notion: NotionStore;
11
+ labeler: Labeler;
12
+ state: ProfileState;
13
+ save: () => void;
14
+ log: (msg: string) => void;
15
+ now?: () => Date;
16
+ dryRun?: boolean;
17
+ }
18
+ export interface IngestResult {
19
+ checked: number;
20
+ written: Array<{
21
+ id: string;
22
+ title: string;
23
+ }>;
24
+ pending: number;
25
+ errors: string[];
26
+ }
27
+ /**
28
+ * One-time snapshot at cutover: every recording from the last WATCH_DAYS that was
29
+ * uploaded before startAfter. Ones Plaud has already finished belonged to the Zap
30
+ * ("baseline", never written). Ones still without a summary/transcript go on the
31
+ * slow watch list, so a summary generated later is still written — as the Zap did.
32
+ */
33
+ export declare function runBaseline(deps: IngestDeps): Promise<{
34
+ baseline: number;
35
+ watching: number;
36
+ }>;
37
+ export interface PreparedRecording {
38
+ recorded: string;
39
+ summaryMarkdown: string;
40
+ transcriptText: string;
41
+ plaudTitle: string;
42
+ ssnRedactions: number;
43
+ cardRedactions: number;
44
+ }
45
+ /** Everything that happens to Plaud text before it leaves this Mac. */
46
+ export declare function prepareRecording(rec: PlaudRecording): PreparedRecording;
47
+ export declare function runIngest(deps: IngestDeps, options?: {
48
+ deep?: boolean;
49
+ }): Promise<IngestResult>;
package/dist/ingest.js ADDED
@@ -0,0 +1,205 @@
1
+ import { LLM_MAX_ATTEMPTS, PENDING_FAST_HOURS, TYPE_SUMMARY, TYPE_TRANSCRIPT, WATCH_DAYS } from "./constants.js";
2
+ import { cleanParticipants, cleanSummaryMarkdown, cleanTitle, formatTranscript, meetingTitle, recordedMinute } from "./format.js";
3
+ import { redactCardsAcross } from "./card.js";
4
+ import { redactSsnAcross } from "./ssn.js";
5
+ import { safeErrorMessage } from "./redact.js";
6
+ const DEEP_SCAN_PAGES = 15;
7
+ /**
8
+ * Files to look at this cycle, oldest upload first:
9
+ * - new files (uploaded at/after startAfter) not seen yet;
10
+ * - files still waiting on Plaud (no summary/transcript yet) — every cycle while
11
+ * fresh, only on deep scans once they are "slow" (older than PENDING_FAST_HOURS).
12
+ * Plaud lists by recording start, so a deep scan (every 10 min) walks back
13
+ * WATCH_DAYS to catch late uploads and summaries generated late on old recordings.
14
+ */
15
+ async function candidates(deps, deep) {
16
+ const now = (deps.now || (() => new Date()))();
17
+ const floor = new Date(now.getTime() - WATCH_DAYS * 86_400_000);
18
+ const out = [];
19
+ const pages = deep ? DEEP_SCAN_PAGES : 1;
20
+ for (let page = 1; page <= pages; page++) {
21
+ const list = await deps.plaud.listFiles(page, 20);
22
+ for (const f of list) {
23
+ const uploaded = f.createdAt || f.startAt;
24
+ const st = deps.state.files[f.id];
25
+ if (st) {
26
+ if (st.status === "pending" && (deep || !st.slow)) {
27
+ out.push(f);
28
+ }
29
+ }
30
+ else if (uploaded && uploaded >= deps.startAfter) {
31
+ out.push(f);
32
+ }
33
+ }
34
+ const oldest = list.at(-1)?.startAt;
35
+ if (list.length < 20 || !oldest || oldest < floor) {
36
+ break;
37
+ }
38
+ }
39
+ return out.sort((a, b) => (a.createdAt?.getTime() ?? 0) - (b.createdAt?.getTime() ?? 0));
40
+ }
41
+ /**
42
+ * One-time snapshot at cutover: every recording from the last WATCH_DAYS that was
43
+ * uploaded before startAfter. Ones Plaud has already finished belonged to the Zap
44
+ * ("baseline", never written). Ones still without a summary/transcript go on the
45
+ * slow watch list, so a summary generated later is still written — as the Zap did.
46
+ */
47
+ export async function runBaseline(deps) {
48
+ const now = (deps.now || (() => new Date()))();
49
+ const floor = new Date(now.getTime() - WATCH_DAYS * 86_400_000);
50
+ let baseline = 0;
51
+ let watching = 0;
52
+ for (let page = 1; page <= DEEP_SCAN_PAGES; page++) {
53
+ const list = await deps.plaud.listFiles(page, 20);
54
+ for (const f of list) {
55
+ const uploaded = f.createdAt || f.startAt;
56
+ if (deps.state.files[f.id] || !uploaded || uploaded >= deps.startAfter) {
57
+ continue;
58
+ }
59
+ const rec = await deps.plaud.getRecording(f.id);
60
+ const ready = Boolean(rec.summaryMarkdown && rec.segments.length > 0 && rec.startAt);
61
+ deps.state.files[f.id] = ready
62
+ ? { status: "baseline", firstSeen: now.toISOString(), name: rec.name, llmAttempts: 0 }
63
+ : { status: "pending", slow: true, firstSeen: now.toISOString(), name: rec.name, llmAttempts: 0 };
64
+ ready ? baseline++ : watching++;
65
+ }
66
+ const oldest = list.at(-1)?.startAt;
67
+ if (list.length < 20 || !oldest || oldest < floor) {
68
+ break;
69
+ }
70
+ }
71
+ if (!deps.dryRun) {
72
+ deps.state.baselineAt = now.toISOString();
73
+ deps.save();
74
+ }
75
+ deps.log(`[${deps.profile}] baseline: ${baseline} recording(s) already handled before cutover; watching ${watching} still without a summary`);
76
+ return { baseline, watching };
77
+ }
78
+ /** A row we (or a crashed earlier attempt) already wrote: same minute, same Type, same title. */
79
+ function alreadyWritten(rows, type, title) {
80
+ return rows.find((r) => r.types.includes(type) && r.title.trim() === title.trim());
81
+ }
82
+ /** Everything that happens to Plaud text before it leaves this Mac. */
83
+ export function prepareRecording(rec) {
84
+ // Cards first: "last four of the card" belongs to the card, not the SSN.
85
+ const cards = redactCardsAcross({
86
+ summary: rec.summaryMarkdown || "",
87
+ transcript: formatTranscript(rec.segments),
88
+ title: rec.name
89
+ });
90
+ const redacted = redactSsnAcross(cards.texts);
91
+ return {
92
+ recorded: recordedMinute(rec.startAt),
93
+ summaryMarkdown: cleanSummaryMarkdown(redacted.texts.summary),
94
+ transcriptText: redacted.texts.transcript,
95
+ plaudTitle: redacted.texts.title,
96
+ ssnRedactions: redacted.count,
97
+ cardRedactions: cards.count
98
+ };
99
+ }
100
+ async function processOne(deps, file, st, result) {
101
+ const now = (deps.now || (() => new Date()))();
102
+ const rec = await deps.plaud.getRecording(file.id);
103
+ st.name = rec.name;
104
+ if (!rec.summaryMarkdown || rec.segments.length === 0 || !rec.startAt) {
105
+ const waitedH = (now.getTime() - Date.parse(st.firstSeen)) / 3_600_000;
106
+ if (!st.slow && waitedH > PENDING_FAST_HOURS) {
107
+ st.slow = true;
108
+ deps.log(`[${deps.profile}] "${rec.name}" still has no ${!rec.summaryMarkdown ? "summary" : "transcript"} after ${PENDING_FAST_HOURS}h; checking it every 10 min instead`);
109
+ }
110
+ result.pending++;
111
+ return;
112
+ }
113
+ const prepared = prepareRecording(rec);
114
+ st.recorded = prepared.recorded;
115
+ st.ssnRedactions = prepared.ssnRedactions;
116
+ st.cardRedactions = prepared.cardRedactions;
117
+ if (prepared.ssnRedactions > 0) {
118
+ deps.log(`[${deps.profile}] redacted ${prepared.ssnRedactions} SSN mention(s) in "${prepared.plaudTitle}"`);
119
+ }
120
+ if (prepared.cardRedactions > 0) {
121
+ deps.log(`[${deps.profile}] redacted ${prepared.cardRedactions} card detail(s) in "${prepared.plaudTitle}"`);
122
+ }
123
+ if (!st.title) {
124
+ const notionSchema = await deps.notion.schema();
125
+ try {
126
+ const labels = await deps.labeler.label({
127
+ owner: deps.owner,
128
+ plaudTitle: prepared.plaudTitle,
129
+ summaryMarkdown: prepared.summaryMarkdown,
130
+ transcriptText: prepared.transcriptText,
131
+ knownParticipants: notionSchema.participantOptions
132
+ });
133
+ st.participants = cleanParticipants(labels.participants, deps.owner);
134
+ st.labelSource = "llm";
135
+ }
136
+ catch (err) {
137
+ st.llmAttempts++;
138
+ st.lastError = safeErrorMessage(err);
139
+ if (st.llmAttempts < LLM_MAX_ATTEMPTS) {
140
+ deps.log(`[${deps.profile}] participant labeling failed for "${prepared.plaudTitle}" (attempt ${st.llmAttempts}/${LLM_MAX_ATTEMPTS}): ${st.lastError}`);
141
+ result.pending++;
142
+ return;
143
+ }
144
+ st.participants = [];
145
+ st.labelSource = "fallback";
146
+ deps.log(`[${deps.profile}] participant labeling failed ${LLM_MAX_ATTEMPTS}x; writing "${prepared.plaudTitle}" with no participants`);
147
+ }
148
+ st.title = cleanTitle(meetingTitle(prepared.plaudTitle));
149
+ deps.save();
150
+ }
151
+ const title = st.title;
152
+ const participants = st.participants || [];
153
+ if (deps.dryRun) {
154
+ deps.log(`[${deps.profile}] DRY RUN would write "${title}" Recorded ${prepared.recorded} Participants ${JSON.stringify(participants)} ` +
155
+ `(summary ${prepared.summaryMarkdown.length} chars, transcript ${prepared.transcriptText.length} chars, SSN redactions ${prepared.ssnRedactions}, card redactions ${prepared.cardRedactions})`);
156
+ result.written.push({ id: file.id, title });
157
+ return;
158
+ }
159
+ const existing = await deps.notion.rowsAtMinute(prepared.recorded);
160
+ if (!st.summaryPageId) {
161
+ const found = alreadyWritten(existing, TYPE_SUMMARY, title);
162
+ st.summaryPageId = found
163
+ ? found.id
164
+ : (await deps.notion.createSummary({ title, recorded: prepared.recorded, type: TYPE_SUMMARY, participants }, prepared.summaryMarkdown)).id;
165
+ deps.save();
166
+ }
167
+ if (!st.transcriptPageId) {
168
+ const found = alreadyWritten(existing, TYPE_TRANSCRIPT, title);
169
+ st.transcriptPageId = found
170
+ ? found.id
171
+ : (await deps.notion.createTranscript({ title, recorded: prepared.recorded, type: TYPE_TRANSCRIPT, participants }, prepared.transcriptText)).id;
172
+ }
173
+ st.status = "done";
174
+ st.doneAt = now.toISOString();
175
+ delete st.lastError;
176
+ deps.save();
177
+ result.written.push({ id: file.id, title });
178
+ deps.log(`[${deps.profile}] wrote "${title}" (Recorded ${prepared.recorded}, participants ${JSON.stringify(participants)})`);
179
+ }
180
+ export async function runIngest(deps, options = {}) {
181
+ const now = (deps.now || (() => new Date()))();
182
+ const result = { checked: 0, written: [], pending: 0, errors: [] };
183
+ if (!deps.state.baselineAt) {
184
+ await runBaseline(deps);
185
+ }
186
+ for (const file of await candidates(deps, options.deep === true)) {
187
+ result.checked++;
188
+ const st = (deps.state.files[file.id] ||= { status: "pending", firstSeen: now.toISOString(), llmAttempts: 0 });
189
+ try {
190
+ await processOne(deps, file, st, result);
191
+ }
192
+ catch (err) {
193
+ st.lastError = safeErrorMessage(err);
194
+ result.errors.push(`${file.name}: ${st.lastError}`);
195
+ deps.save();
196
+ // Auth problems affect every file; stop the cycle and let the daemon alert.
197
+ const e = err;
198
+ if (e.unauthorized || e.code === "AUTH_EXPIRED") {
199
+ throw err;
200
+ }
201
+ }
202
+ }
203
+ return result;
204
+ }
205
+ //# sourceMappingURL=ingest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest.js","sourceRoot":"","sources":["../src/ingest.ts"],"names":[],"mappings":"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"}