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/scrub.js ADDED
@@ -0,0 +1,124 @@
1
+ import { NOTION_TEXT_CHUNK, NOTION_VERSION } from "./constants.js";
2
+ import { redactSecrets } from "./redact.js";
3
+ import { findCardDigits, redactCards } from "./card.js";
4
+ import { findSsnDigits, redactDigits } from "./ssn.js";
5
+ /** Consecutive plain-text runs with identical formatting and link; other runs stand alone. */
6
+ function sameFormatGroups(rich) {
7
+ const key = (r) => (r.type === "text" ? JSON.stringify([r.annotations, r.text?.link ?? null]) : undefined);
8
+ const groups = [];
9
+ for (const r of rich) {
10
+ const last = groups[groups.length - 1];
11
+ if (last && key(r) !== undefined && key(r) === key(last[0])) {
12
+ last.push(r);
13
+ }
14
+ else {
15
+ groups.push([r]);
16
+ }
17
+ }
18
+ return groups;
19
+ }
20
+ export async function scrubSensitive(options) {
21
+ const f = options.fetchImpl || fetch;
22
+ const api = async (method, path, body) => {
23
+ const res = await f(`https://api.notion.com/v1${path}`, {
24
+ method,
25
+ headers: { Authorization: `Bearer ${options.token}`, "Notion-Version": NOTION_VERSION, "Content-Type": "application/json" },
26
+ body: body === undefined ? undefined : JSON.stringify(body)
27
+ });
28
+ const json = (await res.json().catch(() => ({})));
29
+ if (!res.ok) {
30
+ throw new Error(redactSecrets(`Notion HTTP ${res.status}: ${String(json.message || "")}`));
31
+ }
32
+ return json;
33
+ };
34
+ const pages = [];
35
+ let cursor;
36
+ do {
37
+ const j = await api("POST", `/data_sources/${options.dataSourceId}/query`, { page_size: 100, start_cursor: cursor });
38
+ pages.push(...(j.results || []));
39
+ cursor = j.has_more ? String(j.next_cursor) : undefined;
40
+ } while (cursor);
41
+ const blocksOf = async (id) => {
42
+ const out = [];
43
+ let c;
44
+ do {
45
+ const j = await api("GET", `/blocks/${id}/children?page_size=100${c ? `&start_cursor=${c}` : ""}`);
46
+ for (const b of (j.results || [])) {
47
+ const type = String(b.type);
48
+ const rich = (b[type]?.rich_text || []);
49
+ if (rich.length) {
50
+ out.push({ id: String(b.id), type, rich });
51
+ }
52
+ if (b.has_children) {
53
+ out.push(...(await blocksOf(String(b.id))));
54
+ }
55
+ }
56
+ c = j.has_more ? String(j.next_cursor) : undefined;
57
+ } while (c);
58
+ return out;
59
+ };
60
+ const groups = new Map();
61
+ for (const p of pages) {
62
+ const rec = p.properties?.Recorded?.date;
63
+ const key = rec?.start ? rec.start.slice(0, 16) : String(p.id);
64
+ groups.set(key, [...(groups.get(key) || []), p]);
65
+ }
66
+ const result = { rowsScanned: pages.length, rowsAffected: [], blocksUpdated: 0 };
67
+ for (const rows of groups.values()) {
68
+ const loaded = await Promise.all(rows.map(async (p) => ({ page: p, blocks: await blocksOf(String(p.id)) })));
69
+ const texts = loaded.map((l) => l.blocks.map((b) => b.rich.map((r) => r.plain_text).join("")).join("\n"));
70
+ const cardFound = findCardDigits(texts);
71
+ // SSN values are looked for after card details are gone, exactly like ingest.
72
+ const ssnFound = findSsnDigits(texts.map((t) => redactCards(t, cardFound).text));
73
+ for (const { page, blocks } of loaded) {
74
+ let ssn = 0;
75
+ let card = 0;
76
+ for (const block of blocks) {
77
+ let changed = false;
78
+ // Adjacent runs with the same formatting are redacted as one string, so a
79
+ // trigger and its number split across runs are still caught. The Zap wrote
80
+ // whole transcripts as one run of up to ~11k chars; the API only accepts
81
+ // ≤2000 per run on write, so rewritten runs are re-split (same formatting).
82
+ const rich = [];
83
+ for (const group of sameFormatGroups(block.rich)) {
84
+ if (group[0].type !== "text") {
85
+ rich.push(...group);
86
+ continue;
87
+ }
88
+ const plain = group.map((r) => r.plain_text).join("");
89
+ const c = redactCards(plain, cardFound);
90
+ const n = redactDigits(c.text, ssnFound);
91
+ if (!c.count && !n.count) {
92
+ rich.push(...group);
93
+ continue;
94
+ }
95
+ card += c.count;
96
+ ssn += n.count;
97
+ changed = true;
98
+ for (let i = 0; i < n.text.length; i += NOTION_TEXT_CHUNK) {
99
+ rich.push({
100
+ type: "text",
101
+ text: { content: n.text.slice(i, i + NOTION_TEXT_CHUNK), link: group[0].text?.link ?? null },
102
+ annotations: group[0].annotations
103
+ });
104
+ }
105
+ }
106
+ if (changed) {
107
+ result.blocksUpdated++;
108
+ if (options.apply) {
109
+ await api("PATCH", `/blocks/${block.id}`, { [block.type]: { rich_text: rich } });
110
+ }
111
+ }
112
+ }
113
+ if (ssn || card) {
114
+ const props = page.properties;
115
+ const title = (props.Meeting?.title || []).map((t) => t.plain_text).join("");
116
+ const type = (props.Type?.multi_select || []).map((t) => t.name).join(",");
117
+ result.rowsAffected.push({ title, type, ssn, card });
118
+ options.log(`${options.apply ? "scrubbed" : "DRY RUN would scrub"} ${ssn} SSN + ${card} card mention(s) in ${type} "${title}"`);
119
+ }
120
+ }
121
+ }
122
+ return result;
123
+ }
124
+ //# sourceMappingURL=scrub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scrub.js","sourceRoot":"","sources":["../src/scrub.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ export type SecurityRunImpl = (bin: string, args: readonly string[], stdin?: string) => Promise<{
2
+ stdout: string;
3
+ stderr: string;
4
+ }>;
5
+ export declare const KEYCHAIN_WRITE_FAILED = "Keychain write failed. Run plaud-notes-to-notion from a logged-in GUI session on the Mini (SSH / non-GUI shells cannot write Keychain).";
6
+ /** Quote for `security -i` so the secret is on stdin, not `ps` argv. */
7
+ export declare function quoteSecurityArg(value: string): string;
8
+ export declare function defaultSecurityRun(bin: string, args: readonly string[], stdin?: string): Promise<{
9
+ stdout: string;
10
+ stderr: string;
11
+ }>;
12
+ /**
13
+ * Mini: `security -i` with the password on stdin (not argv / `ps`).
14
+ * `-U` updates an existing item. GUI Terminal.app required — non-GUI write fails.
15
+ */
16
+ export declare function writeKeychainPassword(options: {
17
+ service: string;
18
+ account: string;
19
+ value: string;
20
+ securityBin?: string;
21
+ securityRunImpl?: SecurityRunImpl;
22
+ }): Promise<void>;
23
+ export interface SecretStore {
24
+ describe(): string;
25
+ get(account: string): Promise<string | null>;
26
+ set(account: string, value: string): Promise<void>;
27
+ delete(account: string): Promise<void>;
28
+ }
29
+ export declare class MemorySecretStore implements SecretStore {
30
+ private readonly values;
31
+ describe(): string;
32
+ get(account: string): Promise<string | null>;
33
+ set(account: string, value: string): Promise<void>;
34
+ delete(account: string): Promise<void>;
35
+ }
36
+ /** File-backed store for tests (`PLAUD_NOTES_HOME`) and Linux fallback. Mini uses Keychain. */
37
+ export declare class FileSecretStore implements SecretStore {
38
+ private readonly dir;
39
+ constructor(dir: string);
40
+ describe(): string;
41
+ private fileFor;
42
+ get(account: string): Promise<string | null>;
43
+ set(account: string, value: string): Promise<void>;
44
+ delete(account: string): Promise<void>;
45
+ }
46
+ export declare class KeychainSecretStore implements SecretStore {
47
+ private readonly service;
48
+ private readonly securityBin;
49
+ private readonly securityRunImpl;
50
+ constructor(service?: string, securityBin?: string, securityRunImpl?: SecurityRunImpl);
51
+ describe(): string;
52
+ get(account: string): Promise<string | null>;
53
+ set(account: string, value: string): Promise<void>;
54
+ delete(account: string): Promise<void>;
55
+ }
56
+ export declare function createSecretStore(options?: {
57
+ env?: NodeJS.ProcessEnv;
58
+ platform?: NodeJS.Platform;
59
+ }): SecretStore;
@@ -0,0 +1,220 @@
1
+ import { spawn } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { KEYCHAIN_SERVICE } from "./constants.js";
5
+ import { getStateDir } from "./paths.js";
6
+ export const KEYCHAIN_WRITE_FAILED = "Keychain write failed. Run plaud-notes-to-notion from a logged-in GUI session on the Mini (SSH / non-GUI shells cannot write Keychain).";
7
+ function stripSecurityPassword(stdout) {
8
+ return stdout.replace(/\n$/, "");
9
+ }
10
+ /** Quote for `security -i` so the secret is on stdin, not `ps` argv. */
11
+ export function quoteSecurityArg(value) {
12
+ return `'${value.replace(/'/g, `'\\''`)}'`;
13
+ }
14
+ export async function defaultSecurityRun(bin, args, stdin) {
15
+ return new Promise((resolve, reject) => {
16
+ const child = spawn(bin, [...args], { stdio: ["pipe", "pipe", "pipe"] });
17
+ let stdout = "";
18
+ let stderr = "";
19
+ child.stdout.on("data", (chunk) => {
20
+ stdout += String(chunk);
21
+ });
22
+ child.stderr.on("data", (chunk) => {
23
+ stderr += String(chunk);
24
+ });
25
+ child.on("error", reject);
26
+ child.on("close", (code) => {
27
+ if (code === 0) {
28
+ resolve({ stdout, stderr });
29
+ return;
30
+ }
31
+ reject(new Error(stderr.trim() || `security exited ${code}`));
32
+ });
33
+ if (!child.stdin) {
34
+ reject(new Error(KEYCHAIN_WRITE_FAILED));
35
+ return;
36
+ }
37
+ child.stdin.on("error", () => {
38
+ /* EPIPE: close/error settles the promise */
39
+ });
40
+ if (stdin !== undefined) {
41
+ child.stdin.write(stdin);
42
+ }
43
+ child.stdin.end();
44
+ });
45
+ }
46
+ /**
47
+ * Mini: `security -i` with the password on stdin (not argv / `ps`).
48
+ * `-U` updates an existing item. GUI Terminal.app required — non-GUI write fails.
49
+ */
50
+ export async function writeKeychainPassword(options) {
51
+ if (options.value.includes("\n") || options.value.includes("\r")) {
52
+ throw new Error(KEYCHAIN_WRITE_FAILED);
53
+ }
54
+ const securityBin = options.securityBin || "/usr/bin/security";
55
+ const run = options.securityRunImpl || defaultSecurityRun;
56
+ const addLine = (update) => `add-generic-password${update ? " -U" : ""} -s ${quoteSecurityArg(options.service)} -a ${quoteSecurityArg(options.account)} -w ${quoteSecurityArg(options.value)}\n`;
57
+ try {
58
+ await run(securityBin, ["-i"], addLine(true));
59
+ }
60
+ catch {
61
+ try {
62
+ await run(securityBin, ["delete-generic-password", "-s", options.service, "-a", options.account]);
63
+ }
64
+ catch {
65
+ /* nothing to replace */
66
+ }
67
+ try {
68
+ await run(securityBin, ["-i"], addLine(false));
69
+ }
70
+ catch {
71
+ throw new Error(KEYCHAIN_WRITE_FAILED);
72
+ }
73
+ }
74
+ let readBack;
75
+ try {
76
+ const { stdout } = await run(securityBin, [
77
+ "find-generic-password",
78
+ "-s",
79
+ options.service,
80
+ "-a",
81
+ options.account,
82
+ "-w"
83
+ ]);
84
+ readBack = stripSecurityPassword(stdout);
85
+ }
86
+ catch {
87
+ throw new Error(KEYCHAIN_WRITE_FAILED);
88
+ }
89
+ if (readBack !== options.value) {
90
+ throw new Error(KEYCHAIN_WRITE_FAILED);
91
+ }
92
+ }
93
+ export class MemorySecretStore {
94
+ values = new Map();
95
+ describe() {
96
+ return "in-memory store";
97
+ }
98
+ async get(account) {
99
+ return this.values.get(account) ?? null;
100
+ }
101
+ async set(account, value) {
102
+ this.values.set(account, value);
103
+ }
104
+ async delete(account) {
105
+ this.values.delete(account);
106
+ }
107
+ }
108
+ /** File-backed store for tests (`PLAUD_NOTES_HOME`) and Linux fallback. Mini uses Keychain. */
109
+ export class FileSecretStore {
110
+ dir;
111
+ constructor(dir) {
112
+ this.dir = dir;
113
+ }
114
+ describe() {
115
+ return `file store under ${path.join(this.dir, "secrets")}`;
116
+ }
117
+ fileFor(account) {
118
+ const safe = account.replace(/[^A-Za-z0-9._-]/g, "_");
119
+ return path.join(this.dir, "secrets", safe);
120
+ }
121
+ async get(account) {
122
+ try {
123
+ const raw = await fs.promises.readFile(this.fileFor(account), "utf8");
124
+ const trimmed = raw.trim();
125
+ return trimmed ? trimmed : null;
126
+ }
127
+ catch (err) {
128
+ if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") {
129
+ return null;
130
+ }
131
+ throw err;
132
+ }
133
+ }
134
+ async set(account, value) {
135
+ const file = this.fileFor(account);
136
+ await fs.promises.mkdir(path.dirname(file), { recursive: true, mode: 0o700 });
137
+ await fs.promises.writeFile(file, value, { encoding: "utf8", mode: 0o600 });
138
+ await fs.promises.chmod(file, 0o600);
139
+ }
140
+ async delete(account) {
141
+ try {
142
+ await fs.promises.rm(this.fileFor(account));
143
+ }
144
+ catch (err) {
145
+ if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") {
146
+ return;
147
+ }
148
+ throw err;
149
+ }
150
+ }
151
+ }
152
+ export class KeychainSecretStore {
153
+ service;
154
+ securityBin;
155
+ securityRunImpl;
156
+ constructor(service = KEYCHAIN_SERVICE, securityBin = "/usr/bin/security", securityRunImpl = defaultSecurityRun) {
157
+ this.service = service;
158
+ this.securityBin = securityBin;
159
+ this.securityRunImpl = securityRunImpl;
160
+ }
161
+ describe() {
162
+ return `macOS Keychain (service ${this.service})`;
163
+ }
164
+ async get(account) {
165
+ try {
166
+ const { stdout } = await this.securityRunImpl(this.securityBin, [
167
+ "find-generic-password",
168
+ "-s",
169
+ this.service,
170
+ "-a",
171
+ account,
172
+ "-w"
173
+ ]);
174
+ const value = stripSecurityPassword(stdout);
175
+ return value ? value : null;
176
+ }
177
+ catch {
178
+ return null;
179
+ }
180
+ }
181
+ async set(account, value) {
182
+ await writeKeychainPassword({
183
+ service: this.service,
184
+ account,
185
+ value,
186
+ securityBin: this.securityBin,
187
+ securityRunImpl: this.securityRunImpl
188
+ });
189
+ }
190
+ async delete(account) {
191
+ try {
192
+ await this.securityRunImpl(this.securityBin, [
193
+ "delete-generic-password",
194
+ "-s",
195
+ this.service,
196
+ "-a",
197
+ account
198
+ ]);
199
+ }
200
+ catch {
201
+ /* already gone */
202
+ }
203
+ }
204
+ }
205
+ export function createSecretStore(options = {}) {
206
+ const env = options.env || process.env;
207
+ if (env.PLAUD_NOTES_SECRET_STORE === "memory") {
208
+ return new MemorySecretStore();
209
+ }
210
+ const platform = options.platform || process.platform;
211
+ const home = env.PLAUD_NOTES_HOME && env.PLAUD_NOTES_HOME.trim();
212
+ if (home) {
213
+ return new FileSecretStore(path.resolve(home));
214
+ }
215
+ if (platform === "darwin") {
216
+ return new KeychainSecretStore();
217
+ }
218
+ return new FileSecretStore(getStateDir(env));
219
+ }
220
+ //# sourceMappingURL=secrets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.js","sourceRoot":"","sources":["../src/secrets.ts"],"names":[],"mappings":"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"}
package/dist/ssn.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Social Security number redaction for Plaud summaries and transcripts.
3
+ *
4
+ * Runs on raw Plaud text before anything leaves the Mini (OpenAI or Notion).
5
+ * Handles written digits ("4417", "523-81-4417") and spoken digits
6
+ * ("zero zero two three", "double oh two three"), including answers that land
7
+ * in the next speaker's utterance after "last four of your social?".
8
+ */
9
+ export declare const SSN_REPLACEMENT = "[SSN redacted]";
10
+ export declare const DIGIT_WORDS: Record<string, string>;
11
+ export interface SsnRedaction {
12
+ text: string;
13
+ count: number;
14
+ }
15
+ export declare function spokenDigits(run: string): string;
16
+ export declare function protectedRanges(text: string): Array<[number, number]>;
17
+ export declare function looksLikeYear(digits: string): boolean;
18
+ export declare function isYearInDate(text: string, run: {
19
+ start: number;
20
+ digits: string;
21
+ }): boolean;
22
+ /** Digits of every SSN-shaped value the triggers point at, across all given texts. */
23
+ export declare function findSsnDigits(texts: string[]): Set<string>;
24
+ /** Replace every number run whose digits match a found SSN value (or contain a full one). */
25
+ export declare function redactDigits(text: string, ssnDigits: Set<string>): SsnRedaction;
26
+ /**
27
+ * Redact SSNs across every text of one recording. Values found in one text
28
+ * (e.g. the transcript) are also removed from the others (e.g. the summary).
29
+ */
30
+ export declare function redactSsnAcross<T extends Record<string, string>>(texts: T): {
31
+ texts: T;
32
+ count: number;
33
+ };
package/dist/ssn.js ADDED
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Social Security number redaction for Plaud summaries and transcripts.
3
+ *
4
+ * Runs on raw Plaud text before anything leaves the Mini (OpenAI or Notion).
5
+ * Handles written digits ("4417", "523-81-4417") and spoken digits
6
+ * ("zero zero two three", "double oh two three"), including answers that land
7
+ * in the next speaker's utterance after "last four of your social?".
8
+ */
9
+ export const SSN_REPLACEMENT = "[SSN redacted]";
10
+ export const DIGIT_WORDS = {
11
+ zero: "0",
12
+ oh: "0",
13
+ one: "1",
14
+ two: "2",
15
+ three: "3",
16
+ four: "4",
17
+ five: "5",
18
+ six: "6",
19
+ seven: "7",
20
+ eight: "8",
21
+ nine: "9"
22
+ };
23
+ const WORD = "(?:zero|oh|one|two|three|four|five|six|seven|eight|nine)";
24
+ const TOKEN = `(?:\\d+|(?:double|triple)[ -]${WORD}|${WORD})`;
25
+ const SEP = "[ \\t,.\\-\\u2013\\u2014/]*";
26
+ /** A run of written or spoken digits on one line. */
27
+ const NUMBER_RUN = new RegExp(`(?<![\\w])${TOKEN}(?:${SEP}${TOKEN})*(?![\\w])`, "gi");
28
+ /**
29
+ * Strong triggers name the SSN outright; weak triggers ("social", "last four")
30
+ * need a following 4- or 9-digit number and skip year-shaped values.
31
+ */
32
+ const STRONG_TRIGGER = /\b(?:social[ -]security(?:[ -](?:number|no\.?|#))?|s\.?\s?s\.?\s?n\.?|last\s+(?:four|4)(?:\s+digits)?\s+of\s+(?:(?:my|your|his|her|the)\s+)?(?:social|ss|ssn))\b/gi;
33
+ const WEAK_TRIGGER = /\b(?:social(?!\s+(?:media|work|worker|workers|club|event|events|network|networks|skills|life|anxiety|studies|circle|hour|security))|last\s+(?:four|4)(?![^.?!\n]{0,40}\bcard\b)(?:\s+digits)?)\b/gi;
34
+ /** Lookahead window after a trigger, long enough to cross into the next utterance. */
35
+ const WINDOW_CHARS = 320;
36
+ /** Plaud transcript header lines (`Speaker 1 00:01:05`) and bare timestamps are never SSNs. */
37
+ const PROTECTED = /^[^\n]*\b\d{1,2}:\d{2}:\d{2}[ \t]*$|\b\d{1,2}:\d{2}(?::\d{2})?\b/gm;
38
+ export function spokenDigits(run) {
39
+ let out = "";
40
+ const re = new RegExp(`\\d+|(double|triple)[ -](${WORD})|${WORD}`, "gi");
41
+ let m;
42
+ while ((m = re.exec(run))) {
43
+ if (/^\d+$/.test(m[0])) {
44
+ out += m[0];
45
+ }
46
+ else if (m[1]) {
47
+ const d = DIGIT_WORDS[m[2].toLowerCase()];
48
+ out += m[1].toLowerCase() === "double" ? d.repeat(2) : d.repeat(3);
49
+ }
50
+ else {
51
+ out += DIGIT_WORDS[m[0].toLowerCase()];
52
+ }
53
+ }
54
+ return out;
55
+ }
56
+ export function protectedRanges(text) {
57
+ const ranges = [];
58
+ PROTECTED.lastIndex = 0;
59
+ let m;
60
+ while ((m = PROTECTED.exec(text))) {
61
+ ranges.push([m.index, m.index + m[0].length]);
62
+ if (m[0].length === 0) {
63
+ PROTECTED.lastIndex++;
64
+ }
65
+ }
66
+ return ranges;
67
+ }
68
+ function numberRuns(text) {
69
+ const guarded = protectedRanges(text);
70
+ const runs = [];
71
+ NUMBER_RUN.lastIndex = 0;
72
+ let m;
73
+ while ((m = NUMBER_RUN.exec(text))) {
74
+ const start = m.index;
75
+ const end = start + m[0].replace(/[ \t,.\-–—/]+$/, "").length;
76
+ if (guarded.some(([a, b]) => start < b && end > a)) {
77
+ continue;
78
+ }
79
+ const digits = spokenDigits(m[0]);
80
+ if (digits) {
81
+ runs.push({ start, end, digits });
82
+ }
83
+ }
84
+ return runs;
85
+ }
86
+ export function looksLikeYear(digits) {
87
+ return digits.length === 4 && /^(?:19|20)\d\d$/.test(digits);
88
+ }
89
+ const MONTH = "(?:jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|june?|july?|aug(?:ust)?|sep(?:t(?:ember)?)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?)";
90
+ /** "January 1st, 1990", "August 10, 1984", "born in 1984", "10/08/1984" — a year inside a date, not an SSN. */
91
+ const DATE_BEFORE = new RegExp(`(?:\\b${MONTH}\\.?\\s+\\w{1,9}(?:st|nd|rd|th)?,?\\s*|\\b(?:born|birth|birthday|year|dob)\\b[^.?!\\n]{0,30}|\\d{1,2}[/-]\\d{1,2}[/-])$`, "i");
92
+ export function isYearInDate(text, run) {
93
+ return looksLikeYear(run.digits) && DATE_BEFORE.test(text.slice(Math.max(0, run.start - 40), run.start));
94
+ }
95
+ function triggerEnds(text, re) {
96
+ const ends = [];
97
+ re.lastIndex = 0;
98
+ let m;
99
+ while ((m = re.exec(text))) {
100
+ ends.push(m.index + m[0].length);
101
+ }
102
+ return ends;
103
+ }
104
+ /** Digits of every SSN-shaped value the triggers point at, across all given texts. */
105
+ export function findSsnDigits(texts) {
106
+ const found = new Set();
107
+ for (const text of texts) {
108
+ const runs = numberRuns(text);
109
+ for (const m of text.matchAll(/\b\d{3}-\d{2}-\d{4}\b/g)) {
110
+ found.add(m[0].replace(/-/g, ""));
111
+ }
112
+ const scan = (ends, strong) => {
113
+ for (const end of ends) {
114
+ const hit = runs.find((r) => r.start >= end &&
115
+ r.start <= end + WINDOW_CHARS &&
116
+ (r.digits.length === 4 || r.digits.length === 9) &&
117
+ !isYearInDate(text, r) &&
118
+ (strong || !looksLikeYear(r.digits)));
119
+ if (hit) {
120
+ found.add(hit.digits);
121
+ }
122
+ }
123
+ };
124
+ scan(triggerEnds(text, STRONG_TRIGGER), true);
125
+ scan(triggerEnds(text, WEAK_TRIGGER), false);
126
+ }
127
+ // A full SSN also exposes its last four.
128
+ for (const d of [...found]) {
129
+ if (d.length === 9) {
130
+ found.add(d.slice(5));
131
+ }
132
+ }
133
+ return found;
134
+ }
135
+ /** Replace every number run whose digits match a found SSN value (or contain a full one). */
136
+ export function redactDigits(text, ssnDigits) {
137
+ if (ssnDigits.size === 0) {
138
+ return { text, count: 0 };
139
+ }
140
+ const runs = numberRuns(text).filter((r) => {
141
+ if (ssnDigits.has(r.digits)) {
142
+ return true;
143
+ }
144
+ return [...ssnDigits].some((d) => d.length === 9 && r.digits.includes(d));
145
+ });
146
+ let out = "";
147
+ let cursor = 0;
148
+ for (const r of runs) {
149
+ out += text.slice(cursor, r.start) + SSN_REPLACEMENT;
150
+ cursor = r.end;
151
+ }
152
+ out += text.slice(cursor);
153
+ return { text: out, count: runs.length };
154
+ }
155
+ /**
156
+ * Redact SSNs across every text of one recording. Values found in one text
157
+ * (e.g. the transcript) are also removed from the others (e.g. the summary).
158
+ */
159
+ export function redactSsnAcross(texts) {
160
+ const found = findSsnDigits(Object.values(texts));
161
+ let count = 0;
162
+ const out = {};
163
+ for (const [key, value] of Object.entries(texts)) {
164
+ const r = redactDigits(value, found);
165
+ out[key] = r.text;
166
+ count += r.count;
167
+ }
168
+ return { texts: out, count };
169
+ }
170
+ //# sourceMappingURL=ssn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssn.js","sourceRoot":"","sources":["../src/ssn.ts"],"names":[],"mappings":"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"}