@plaud-ai/mcp 0.3.2 → 0.3.4

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.
@@ -0,0 +1,832 @@
1
+ // ../shared/dist/errors.js
2
+ function errorTypeFromStatus(status) {
3
+ if (status === 401)
4
+ return "auth";
5
+ if (status === 403)
6
+ return "permission";
7
+ if (status === 404)
8
+ return "not_found";
9
+ if (status >= 500)
10
+ return "server_error";
11
+ if (status >= 400)
12
+ return "client_error";
13
+ return "unknown";
14
+ }
15
+ function classifyError(err) {
16
+ const e = err instanceof Error ? err : void 0;
17
+ const msg = e ? e.message : String(err ?? "");
18
+ const name = e ? e.name : "";
19
+ if (name === "AbortError" || /\btimeout\b|ETIMEDOUT/i.test(msg))
20
+ return "timeout";
21
+ if (err instanceof TypeError || /fetch failed|ECONNREFUSED|ENOTFOUND|ECONNRESET|EAI_AGAIN|network/i.test(msg))
22
+ return "network";
23
+ if (/\b401\b|not authenticated|unauthorized/i.test(msg))
24
+ return "auth";
25
+ if (/\b403\b|forbidden/i.test(msg))
26
+ return "permission";
27
+ if (/\b404\b|not found/i.test(msg))
28
+ return "not_found";
29
+ if (/\b5\d\d\b|internal server error|bad gateway|service unavailable|gateway timeout/i.test(msg))
30
+ return "server_error";
31
+ if (/\b4\d\d\b/.test(msg))
32
+ return "client_error";
33
+ return "unknown";
34
+ }
35
+ function oauthCallbackErrorType(status) {
36
+ switch (status) {
37
+ case "denied":
38
+ return "user_cancel";
39
+ case "timeout":
40
+ return "timeout";
41
+ case "exchange-failed":
42
+ return "server_error";
43
+ case "listen-failed":
44
+ return "internal";
45
+ default:
46
+ return "unknown";
47
+ }
48
+ }
49
+
50
+ // ../shared/dist/oauth.js
51
+ import { randomBytes, createHash } from "crypto";
52
+
53
+ // ../shared/dist/token-store.js
54
+ import { readFile, writeFile, mkdir, rm } from "fs/promises";
55
+ import { join } from "path";
56
+ import { homedir } from "os";
57
+ var TokenStore = class {
58
+ configDir;
59
+ tokenPath;
60
+ constructor(filename = "tokens.json") {
61
+ this.configDir = join(homedir(), ".plaud");
62
+ this.tokenPath = join(this.configDir, filename);
63
+ }
64
+ async save(tokenSet) {
65
+ await mkdir(this.configDir, { recursive: true });
66
+ await writeFile(this.tokenPath, JSON.stringify(tokenSet, null, 2), "utf-8");
67
+ }
68
+ async load() {
69
+ try {
70
+ const data = await readFile(this.tokenPath, "utf-8");
71
+ return JSON.parse(data);
72
+ } catch {
73
+ return null;
74
+ }
75
+ }
76
+ async clear() {
77
+ try {
78
+ await rm(this.tokenPath);
79
+ } catch {
80
+ }
81
+ }
82
+ };
83
+
84
+ // ../shared/dist/oauth.js
85
+ var DEFAULT_AUTHORIZATION_URL = "https://web.plaud.ai/platform/oauth";
86
+ var DEFAULT_TOKEN_URL = "https://platform.plaud.ai/developer/api/oauth/third-party/access-token";
87
+ var DEFAULT_REFRESH_URL = "https://platform.plaud.ai/developer/api/oauth/third-party/access-token/refresh";
88
+ function generateCodeVerifier() {
89
+ return randomBytes(32).toString("base64url");
90
+ }
91
+ function generateCodeChallenge(verifier) {
92
+ return createHash("sha256").update(verifier).digest("base64url");
93
+ }
94
+ function generateState() {
95
+ return randomBytes(16).toString("base64url");
96
+ }
97
+ var OAuth = class {
98
+ config;
99
+ tokenStore;
100
+ authorizationUrl;
101
+ tokenUrl;
102
+ refreshUrl;
103
+ constructor(config) {
104
+ this.config = config;
105
+ this.tokenStore = new TokenStore(config.tokenFile);
106
+ this.authorizationUrl = config.authorizationUrl ?? DEFAULT_AUTHORIZATION_URL;
107
+ this.tokenUrl = config.tokenUrl ?? DEFAULT_TOKEN_URL;
108
+ this.refreshUrl = config.refreshUrl ?? DEFAULT_REFRESH_URL;
109
+ }
110
+ createAuthorizationRequest() {
111
+ const codeVerifier = generateCodeVerifier();
112
+ const codeChallenge = generateCodeChallenge(codeVerifier);
113
+ const state2 = generateState();
114
+ const params = new URLSearchParams({
115
+ client_id: this.config.clientId,
116
+ redirect_uri: this.config.redirectUri,
117
+ response_type: "code",
118
+ code_challenge: codeChallenge,
119
+ code_challenge_method: "S256",
120
+ state: state2
121
+ });
122
+ return {
123
+ url: `${this.authorizationUrl}?${params.toString()}`,
124
+ codeVerifier,
125
+ state: state2
126
+ };
127
+ }
128
+ /**
129
+ * @deprecated Use createAuthorizationRequest() for PKCE flow
130
+ */
131
+ getAuthorizationUrl() {
132
+ return this.createAuthorizationRequest().url;
133
+ }
134
+ async exchangeCode(code, codeVerifier, state2) {
135
+ const basicAuth = Buffer.from(`${this.config.clientId}:${this.config.clientSecret}`).toString("base64");
136
+ const body = {
137
+ code,
138
+ redirect_uri: this.config.redirectUri
139
+ };
140
+ if (codeVerifier) {
141
+ body.code_verifier = codeVerifier;
142
+ }
143
+ if (state2) {
144
+ body.state = state2;
145
+ }
146
+ const res = await fetch(this.tokenUrl, {
147
+ method: "POST",
148
+ headers: {
149
+ "Content-Type": "application/x-www-form-urlencoded",
150
+ Accept: "application/json",
151
+ Authorization: `Basic ${basicAuth}`,
152
+ ...this.config.extraHeaders
153
+ },
154
+ body: new URLSearchParams(body)
155
+ });
156
+ if (!res.ok) {
157
+ throw new Error(`Token exchange failed: ${res.status} ${await res.text()}`);
158
+ }
159
+ const data = await res.json();
160
+ const tokenSet = {
161
+ access_token: data.access_token,
162
+ refresh_token: data.refresh_token,
163
+ token_type: data.token_type ?? "Bearer",
164
+ expires_at: data.expires_in ? Date.now() + data.expires_in * 1e3 : void 0
165
+ };
166
+ await this.tokenStore.save(tokenSet);
167
+ return tokenSet;
168
+ }
169
+ async getAccessToken() {
170
+ const tokenSet = await this.tokenStore.load();
171
+ if (!tokenSet)
172
+ return null;
173
+ if (tokenSet.expires_at && Date.now() > tokenSet.expires_at - 6e4) {
174
+ if (tokenSet.refresh_token) {
175
+ try {
176
+ const refreshed = await this.refresh(tokenSet.refresh_token);
177
+ return refreshed.access_token;
178
+ } catch {
179
+ return null;
180
+ }
181
+ }
182
+ return null;
183
+ }
184
+ return tokenSet.access_token;
185
+ }
186
+ async refresh(refreshToken) {
187
+ let res;
188
+ try {
189
+ res = await fetch(this.refreshUrl, {
190
+ method: "POST",
191
+ headers: {
192
+ "Content-Type": "application/x-www-form-urlencoded",
193
+ Accept: "application/json",
194
+ ...this.config.extraHeaders
195
+ },
196
+ body: new URLSearchParams({
197
+ refresh_token: refreshToken
198
+ })
199
+ });
200
+ } catch (err) {
201
+ this.config.onTokenRefresh?.("error", "network");
202
+ throw err;
203
+ }
204
+ if (!res.ok) {
205
+ const body = await res.text();
206
+ this.config.onTokenRefresh?.("error", errorTypeFromStatus(res.status));
207
+ throw new Error(`Token refresh failed: ${res.status} ${body}`);
208
+ }
209
+ const data = await res.json();
210
+ const tokenSet = {
211
+ access_token: data.access_token,
212
+ refresh_token: data.refresh_token ?? refreshToken,
213
+ token_type: data.token_type ?? "Bearer",
214
+ expires_at: data.expires_in ? Date.now() + data.expires_in * 1e3 : void 0
215
+ };
216
+ await this.tokenStore.save(tokenSet);
217
+ this.config.onTokenRefresh?.("success");
218
+ return tokenSet;
219
+ }
220
+ async logout() {
221
+ await this.tokenStore.clear();
222
+ }
223
+ };
224
+
225
+ // ../shared/dist/client.js
226
+ var DEFAULT_API_BASE = "https://platform.plaud.ai/developer/api";
227
+ var PlaudClient = class {
228
+ oauth;
229
+ apiBase;
230
+ extraHeaders;
231
+ staticToken;
232
+ onRequest;
233
+ constructor(config) {
234
+ this.oauth = new OAuth(config);
235
+ this.apiBase = config.apiBase ?? DEFAULT_API_BASE;
236
+ this.extraHeaders = config.extraHeaders ?? {};
237
+ this.staticToken = config.staticToken;
238
+ this.onRequest = config.onRequest;
239
+ }
240
+ get auth() {
241
+ return this.oauth;
242
+ }
243
+ async request(path, init) {
244
+ const token = this.staticToken ?? await this.oauth.getAccessToken();
245
+ if (!token) {
246
+ throw new Error("Not authenticated. Please login first.");
247
+ }
248
+ const url = `${this.apiBase}${path}`;
249
+ const method = init?.method ?? "GET";
250
+ const headers = {
251
+ Authorization: `Bearer ${token}`,
252
+ Accept: "application/json",
253
+ ...this.extraHeaders,
254
+ ...init?.headers
255
+ };
256
+ const start = Date.now();
257
+ const res = await fetch(url, { ...init, headers });
258
+ if (this.onRequest) {
259
+ try {
260
+ const parsed = new URL(url);
261
+ this.onRequest({
262
+ host: parsed.host,
263
+ path: parsed.pathname,
264
+ method,
265
+ status: res.status,
266
+ durationMs: Date.now() - start
267
+ });
268
+ } catch {
269
+ }
270
+ }
271
+ if (!res.ok) {
272
+ const body = await res.text();
273
+ if (res.status === 422) {
274
+ try {
275
+ const parsed = JSON.parse(body);
276
+ const messages = parsed.detail.map((d) => `${d.loc.at(-1)}: ${d.msg}`).join("; ");
277
+ throw new Error(messages);
278
+ } catch (e) {
279
+ if (e instanceof SyntaxError)
280
+ throw new Error(`API error: ${res.status} ${res.statusText}`);
281
+ throw e;
282
+ }
283
+ }
284
+ throw new Error(`API error: ${res.status} ${res.statusText}`);
285
+ }
286
+ const json = await res.json();
287
+ return json;
288
+ }
289
+ async getCurrentUser() {
290
+ return this.request("/open/third-party/users/current");
291
+ }
292
+ async revokeCurrentUser() {
293
+ await this.request("/open/third-party/users/current/revoke", {
294
+ method: "POST"
295
+ });
296
+ }
297
+ async listFiles(page = 1, pageSize = 20) {
298
+ return this.request(`/open/third-party/files/?page=${page}&page_size=${pageSize}`);
299
+ }
300
+ async getFile(fileId) {
301
+ return this.request(`/open/third-party/files/${fileId}`);
302
+ }
303
+ };
304
+
305
+ // ../shared/dist/oauth-callback-server.js
306
+ import { createServer } from "http";
307
+ var SUCCESS_HTML = '<!doctype html><html><head><meta charset="utf-8"><title>Plaud</title></head><body style="font-family:system-ui;padding:2rem;text-align:center;"><h1>Authorization successful!</h1><p>You can close this tab.</p></body></html>';
308
+ var NEUTRAL_HTML = '<!doctype html><html><head><meta charset="utf-8"><title>Plaud</title></head><body style="font-family:system-ui;padding:2rem;text-align:center;"><h1>Continue authorization in the original window.</h1><p>This page can be closed.</p></body></html>';
309
+ function errorHtml(message) {
310
+ const escaped = message.replace(/[&<>]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;" })[c]);
311
+ return '<!doctype html><html><head><meta charset="utf-8"><title>Plaud</title></head><body style="font-family:system-ui;padding:2rem;text-align:center;"><h1>Authorization failed</h1><pre style="white-space:pre-wrap;">' + escaped + "</pre></body></html>";
312
+ }
313
+ var CORS_HEADERS = {
314
+ "Access-Control-Allow-Origin": "*",
315
+ "Access-Control-Allow-Methods": "GET, OPTIONS",
316
+ "Access-Control-Allow-Headers": "*"
317
+ };
318
+ function runOAuthCallback(opts) {
319
+ const { port, expectedState, exchangeCode, timeoutMs = 12e4, onListening, postSuccessDelayMs = 1500 } = opts;
320
+ return new Promise((resolve) => {
321
+ let settled = false;
322
+ let exchangeStarted = false;
323
+ let exchangeSucceeded = false;
324
+ let timeoutId = null;
325
+ let closeTimeoutId = null;
326
+ const server = createServer((req, res) => {
327
+ if (req.method === "OPTIONS") {
328
+ res.writeHead(204, CORS_HEADERS);
329
+ res.end();
330
+ return;
331
+ }
332
+ const reqUrl = new URL(req.url ?? "/", `http://localhost:${port}`);
333
+ if (reqUrl.pathname !== "/auth/callback") {
334
+ res.writeHead(404, CORS_HEADERS);
335
+ res.end();
336
+ return;
337
+ }
338
+ const params = reqUrl.searchParams;
339
+ const error = params.get("error");
340
+ const state2 = params.get("state");
341
+ const code = params.get("code");
342
+ if (error) {
343
+ const desc = params.get("error_description") ?? error;
344
+ res.writeHead(400, { "Content-Type": "text/html", ...CORS_HEADERS });
345
+ res.end(errorHtml(`Authorization denied: ${desc}`));
346
+ finalize({ status: "denied", error: new Error(desc) });
347
+ return;
348
+ }
349
+ if (!state2 || state2 !== expectedState) {
350
+ respondNeutral(res);
351
+ return;
352
+ }
353
+ if (exchangeSucceeded) {
354
+ respondSuccess(res);
355
+ return;
356
+ }
357
+ if (!code) {
358
+ respondNeutral(res);
359
+ return;
360
+ }
361
+ if (exchangeStarted) {
362
+ respondNeutral(res);
363
+ return;
364
+ }
365
+ exchangeStarted = true;
366
+ exchangeCode(code).then(() => {
367
+ exchangeSucceeded = true;
368
+ respondSuccess(res);
369
+ finalize({ status: "success" });
370
+ }, (err) => {
371
+ const e = err instanceof Error ? err : new Error(String(err));
372
+ res.writeHead(500, { "Content-Type": "text/html", ...CORS_HEADERS });
373
+ res.end(errorHtml(e.message));
374
+ finalize({ status: "exchange-failed", error: e });
375
+ });
376
+ });
377
+ server.on("error", (err) => {
378
+ if (settled)
379
+ return;
380
+ const message = err.code === "EADDRINUSE" ? `port ${port} is in use \u2014 another \`plaud login\` may still be running. Wait a few seconds and retry.` : `callback server error: ${err.message}`;
381
+ finalize({ status: "listen-failed", error: new Error(message) }, true);
382
+ });
383
+ timeoutId = setTimeout(() => {
384
+ finalize({ status: "timeout" }, true);
385
+ }, timeoutMs);
386
+ server.listen(port, () => {
387
+ onListening?.();
388
+ });
389
+ function respondSuccess(res) {
390
+ res.writeHead(200, { "Content-Type": "text/html", ...CORS_HEADERS });
391
+ res.end(SUCCESS_HTML);
392
+ }
393
+ function respondNeutral(res) {
394
+ res.writeHead(200, { "Content-Type": "text/html", ...CORS_HEADERS });
395
+ res.end(NEUTRAL_HTML);
396
+ }
397
+ function finalize(result, immediate = false) {
398
+ if (settled)
399
+ return;
400
+ settled = true;
401
+ if (timeoutId) {
402
+ clearTimeout(timeoutId);
403
+ timeoutId = null;
404
+ }
405
+ const close = () => {
406
+ try {
407
+ server.closeAllConnections?.();
408
+ } catch {
409
+ }
410
+ server.close(() => resolve(result));
411
+ };
412
+ if (immediate || result.status !== "success") {
413
+ close();
414
+ } else {
415
+ closeTimeoutId = setTimeout(close, postSuccessDelayMs);
416
+ closeTimeoutId.unref?.();
417
+ }
418
+ }
419
+ });
420
+ }
421
+
422
+ // ../telemetry/dist/client.js
423
+ import { PostHog } from "posthog-node";
424
+
425
+ // ../telemetry/dist/config.js
426
+ var DEFAULT_HOST = "https://us.i.posthog.com";
427
+ function isTruthyEnv(value) {
428
+ if (!value)
429
+ return false;
430
+ return ["1", "true", "yes", "on"].includes(value.toLowerCase());
431
+ }
432
+ function loadConfig() {
433
+ if (isTruthyEnv(process.env.DO_NOT_TRACK)) {
434
+ return {
435
+ apiKey: null,
436
+ host: DEFAULT_HOST,
437
+ optedOut: true,
438
+ optOutReason: "DO_NOT_TRACK is set"
439
+ };
440
+ }
441
+ if (isTruthyEnv(process.env.PLAUD_TELEMETRY_DISABLED)) {
442
+ return {
443
+ apiKey: null,
444
+ host: DEFAULT_HOST,
445
+ optedOut: true,
446
+ optOutReason: "PLAUD_TELEMETRY_DISABLED is set"
447
+ };
448
+ }
449
+ const apiKey = "phc_Be6wE0Vfi6lsbbfKfl4tgpzqKkB1UG29ddOlSA6B8NW";
450
+ const host = process.env.PLAUD_POSTHOG_HOST ?? DEFAULT_HOST;
451
+ return { apiKey, host, optedOut: false, optOutReason: null };
452
+ }
453
+
454
+ // ../telemetry/dist/client.js
455
+ var FLUSH_AT = 5;
456
+ var FLUSH_INTERVAL_MS = 1e3;
457
+ var SHUTDOWN_TIMEOUT_MS = 3e3;
458
+ var cachedClient = null;
459
+ var cachedConfig = null;
460
+ var initialised = false;
461
+ function ensureInit() {
462
+ if (initialised)
463
+ return;
464
+ initialised = true;
465
+ cachedConfig = loadConfig();
466
+ if (cachedConfig.optedOut)
467
+ return;
468
+ if (!cachedConfig.apiKey)
469
+ return;
470
+ cachedClient = new PostHog(cachedConfig.apiKey, {
471
+ host: cachedConfig.host,
472
+ flushAt: FLUSH_AT,
473
+ flushInterval: FLUSH_INTERVAL_MS,
474
+ disableGeoip: true
475
+ });
476
+ }
477
+ function getClient() {
478
+ ensureInit();
479
+ return cachedClient;
480
+ }
481
+ async function shutdown() {
482
+ if (!cachedClient)
483
+ return;
484
+ const client = cachedClient;
485
+ cachedClient = null;
486
+ await Promise.race([
487
+ client.shutdown(),
488
+ new Promise((resolve) => setTimeout(resolve, SHUTDOWN_TIMEOUT_MS))
489
+ ]);
490
+ }
491
+
492
+ // ../telemetry/dist/api.js
493
+ import { release as osRelease } from "os";
494
+
495
+ // ../telemetry/dist/filter.js
496
+ var EMAIL_REGEX = /[\w.+-]+@[\w-]+\.[\w.-]+/;
497
+ var ALLOWED_PROPERTIES = /* @__PURE__ */ new Set([
498
+ // Global common properties (Plaud Common Event Properties + Spec §2)
499
+ "user_id",
500
+ "member_id",
501
+ // fill-if-present; OAuth /users/current doesn't return it yet (Q16)
502
+ "workspace_id",
503
+ // fill-if-present (Q18)
504
+ "role",
505
+ // workspace role, fill-if-present (Q44, 06-05 DA-added common prop)
506
+ "device_id",
507
+ "platform",
508
+ "os",
509
+ "os_version",
510
+ "app_version",
511
+ // Application surface
512
+ "transport",
513
+ // stdio | http
514
+ "mcp_host",
515
+ // claude_desktop | cursor | chatgpt | other
516
+ "dcr_client_id",
517
+ // OAuth Dynamic Client Registration id (HTTP MCP only)
518
+ // Per-call business properties
519
+ "duration_ms",
520
+ "status",
521
+ // success | failed
522
+ "error_type",
523
+ // Event Tracking Hub: auth|permission|not_found|timeout|network|server_error|client_error|user_cancel|internal|unknown
524
+ "failure_kind",
525
+ // legacy alias of error_type (kept allow-listed; error_type is canonical)
526
+ "file_id",
527
+ "passive",
528
+ // true | false
529
+ "request_id",
530
+ // per-invocation id; ties click ↔ success/error of one call (06-08)
531
+ // Event-discriminator props (generic event + name, per 06-04 decision):
532
+ // mcp:tool:* + tool_name, cli:command:* + command_name
533
+ "tool",
534
+ "command",
535
+ "tool_name",
536
+ "command_name",
537
+ // Pagination / result metadata
538
+ "page",
539
+ "page_size",
540
+ "total",
541
+ "count",
542
+ "scanned",
543
+ "matched",
544
+ "truncated",
545
+ "has_filter"
546
+ ]);
547
+ var BANNED_PROPERTIES = /* @__PURE__ */ new Set([
548
+ // Direct PII (OAuth /users/current returns email + nickname inline — see Q16.1)
549
+ "email",
550
+ "email_hash",
551
+ "nickname",
552
+ "name",
553
+ "full_name",
554
+ "avatar",
555
+ "phone",
556
+ "phone_number",
557
+ "address",
558
+ "ip",
559
+ "ip_address",
560
+ // Credentials
561
+ "password",
562
+ "token",
563
+ "access_token",
564
+ "refresh_token",
565
+ "api_key",
566
+ "secret",
567
+ "credit_card",
568
+ "card_number",
569
+ "cvv",
570
+ "ssn",
571
+ // User-generated content (Plaud's core business deals with these)
572
+ "file_name",
573
+ "filename",
574
+ "title",
575
+ "file_title",
576
+ "transcript",
577
+ "transcript_text",
578
+ "summary",
579
+ "summary_text",
580
+ "note",
581
+ "note_content",
582
+ "content",
583
+ "message",
584
+ "text"
585
+ ]);
586
+ function filterProperties(props) {
587
+ const filtered = {};
588
+ const dropped = [];
589
+ if (!props)
590
+ return { filtered, dropped };
591
+ for (const [key, value] of Object.entries(props)) {
592
+ if (key.startsWith("$")) {
593
+ dropped.push(key);
594
+ continue;
595
+ }
596
+ if (BANNED_PROPERTIES.has(key)) {
597
+ dropped.push(key);
598
+ continue;
599
+ }
600
+ if (!ALLOWED_PROPERTIES.has(key)) {
601
+ dropped.push(key);
602
+ continue;
603
+ }
604
+ if (typeof value === "string" && EMAIL_REGEX.test(value)) {
605
+ dropped.push(key);
606
+ continue;
607
+ }
608
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
609
+ dropped.push(key);
610
+ continue;
611
+ }
612
+ filtered[key] = value;
613
+ }
614
+ return { filtered, dropped };
615
+ }
616
+
617
+ // ../telemetry/dist/identity.js
618
+ import { randomUUID } from "crypto";
619
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, rm as rm2 } from "fs/promises";
620
+ import { homedir as homedir2 } from "os";
621
+ import { join as join2 } from "path";
622
+ var CONFIG_DIR = join2(homedir2(), ".plaud");
623
+ var ANONYMOUS_ID_PATH = join2(CONFIG_DIR, "anonymous_id");
624
+ var DEVICE_ID_PATH = join2(CONFIG_DIR, "device_id");
625
+ function userIdentityPath(surface) {
626
+ return join2(CONFIG_DIR, `telemetry-user-${surface}.json`);
627
+ }
628
+ var UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
629
+ var cachedAnonymousId = null;
630
+ var cachedDeviceId = null;
631
+ async function readOrCreateId(path) {
632
+ try {
633
+ const data = (await readFile2(path, "utf8")).trim();
634
+ if (UUID_V4_REGEX.test(data))
635
+ return data;
636
+ } catch {
637
+ }
638
+ const id = randomUUID();
639
+ await mkdir2(CONFIG_DIR, { recursive: true });
640
+ await writeFile2(path, id, "utf8");
641
+ return id;
642
+ }
643
+ async function getDeviceId() {
644
+ if (cachedDeviceId)
645
+ return cachedDeviceId;
646
+ cachedDeviceId = await readOrCreateId(DEVICE_ID_PATH);
647
+ return cachedDeviceId;
648
+ }
649
+ async function getAnonymousId() {
650
+ if (cachedAnonymousId)
651
+ return cachedAnonymousId;
652
+ cachedAnonymousId = await readOrCreateId(ANONYMOUS_ID_PATH);
653
+ return cachedAnonymousId;
654
+ }
655
+ async function clearAnonymousId() {
656
+ cachedAnonymousId = null;
657
+ try {
658
+ await rm2(ANONYMOUS_ID_PATH);
659
+ } catch {
660
+ }
661
+ }
662
+ async function saveUserIdentity(surface, userId, identity) {
663
+ const payload = { userId, identity };
664
+ await mkdir2(CONFIG_DIR, { recursive: true });
665
+ await writeFile2(userIdentityPath(surface), JSON.stringify(payload), "utf8");
666
+ }
667
+ async function loadUserIdentity(surface) {
668
+ try {
669
+ const parsed = JSON.parse(await readFile2(userIdentityPath(surface), "utf8"));
670
+ if (parsed && typeof parsed.userId === "string" && parsed.userId.length > 0) {
671
+ return {
672
+ userId: parsed.userId,
673
+ identity: typeof parsed.identity === "object" && parsed.identity !== null ? parsed.identity : void 0
674
+ };
675
+ }
676
+ } catch {
677
+ }
678
+ return null;
679
+ }
680
+ async function clearUserIdentity(surface) {
681
+ try {
682
+ await rm2(userIdentityPath(surface));
683
+ } catch {
684
+ }
685
+ }
686
+
687
+ // ../telemetry/dist/api.js
688
+ var state = {
689
+ initialised: false,
690
+ surface: null,
691
+ appVersion: null,
692
+ transport: null,
693
+ deviceId: null,
694
+ currentUserId: null,
695
+ currentDistinctId: null,
696
+ memberId: null,
697
+ workspaceId: null,
698
+ role: null,
699
+ mcpHost: null
700
+ };
701
+ async function initTelemetry(options) {
702
+ state.surface = options.surface;
703
+ state.appVersion = options.appVersion;
704
+ state.transport = options.transport ?? null;
705
+ state.deviceId = await getDeviceId();
706
+ let userId = options.userId ?? null;
707
+ let identity = options.identity;
708
+ if (!userId) {
709
+ const persisted = await loadUserIdentity(options.surface);
710
+ if (persisted) {
711
+ userId = persisted.userId;
712
+ identity = identity ?? persisted.identity;
713
+ }
714
+ }
715
+ state.currentUserId = userId;
716
+ state.memberId = identity?.memberId ?? null;
717
+ state.workspaceId = identity?.workspaceId ?? null;
718
+ state.role = identity?.role ?? null;
719
+ state.currentDistinctId = identity?.idHash || userId || await getAnonymousId();
720
+ state.initialised = true;
721
+ }
722
+ async function setUser(userId, identity) {
723
+ if (!state.initialised)
724
+ return;
725
+ state.currentUserId = userId;
726
+ state.currentDistinctId = identity?.idHash || userId;
727
+ state.memberId = identity?.memberId ?? null;
728
+ state.workspaceId = identity?.workspaceId ?? null;
729
+ state.role = identity?.role ?? null;
730
+ if (state.surface) {
731
+ try {
732
+ await saveUserIdentity(state.surface, userId, identity);
733
+ } catch {
734
+ }
735
+ }
736
+ const client = getClient();
737
+ if (!client)
738
+ return;
739
+ const anonymousId = await getAnonymousId();
740
+ if (anonymousId === state.currentDistinctId)
741
+ return;
742
+ try {
743
+ client.alias({
744
+ distinctId: state.currentDistinctId,
745
+ alias: anonymousId
746
+ });
747
+ } catch {
748
+ }
749
+ }
750
+ async function clearUser() {
751
+ state.currentUserId = null;
752
+ state.memberId = null;
753
+ state.workspaceId = null;
754
+ state.role = null;
755
+ if (state.surface) {
756
+ try {
757
+ await clearUserIdentity(state.surface);
758
+ } catch {
759
+ }
760
+ }
761
+ await clearAnonymousId();
762
+ state.currentDistinctId = state.initialised ? await getAnonymousId() : null;
763
+ }
764
+ function setMcpHost(host) {
765
+ state.mcpHost = host && host.length > 0 ? host : null;
766
+ }
767
+ function capture(event, props) {
768
+ if (!state.initialised)
769
+ return;
770
+ if (!state.currentDistinctId)
771
+ return;
772
+ const client = getClient();
773
+ if (!client)
774
+ return;
775
+ const merged = { ...buildCommonProperties(), ...props ?? {} };
776
+ const { filtered } = filterProperties(merged);
777
+ const safe = { ...filtered, $ip: "0.0.0.0" };
778
+ try {
779
+ client.capture({
780
+ distinctId: state.currentDistinctId,
781
+ event,
782
+ properties: safe
783
+ });
784
+ } catch {
785
+ }
786
+ }
787
+ function buildCommonProperties() {
788
+ const out = {
789
+ platform: state.surface,
790
+ app_version: state.appVersion,
791
+ os: process.platform,
792
+ os_version: osRelease()
793
+ };
794
+ if (state.deviceId)
795
+ out.device_id = state.deviceId;
796
+ if (state.transport)
797
+ out.transport = state.transport;
798
+ if (state.currentUserId)
799
+ out.user_id = state.currentUserId;
800
+ if (state.memberId)
801
+ out.member_id = state.memberId;
802
+ if (state.workspaceId)
803
+ out.workspace_id = state.workspaceId;
804
+ if (state.role)
805
+ out.role = state.role;
806
+ if (state.mcpHost)
807
+ out.mcp_host = state.mcpHost;
808
+ return out;
809
+ }
810
+ function extractIdentity(user) {
811
+ const str = (v) => typeof v === "string" && v.length > 0 ? v : void 0;
812
+ return {
813
+ idHash: str(user.id_hash),
814
+ memberId: str(user.member_id),
815
+ workspaceId: str(user.workspace_id),
816
+ role: str(user.role)
817
+ };
818
+ }
819
+
820
+ export {
821
+ classifyError,
822
+ oauthCallbackErrorType,
823
+ PlaudClient,
824
+ runOAuthCallback,
825
+ shutdown,
826
+ initTelemetry,
827
+ setUser,
828
+ clearUser,
829
+ setMcpHost,
830
+ capture,
831
+ extractIdentity
832
+ };