perplexity-user-mcp 0.8.36

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 (125) hide show
  1. package/README.md +192 -0
  2. package/dist/attachments.d.ts +20 -0
  3. package/dist/attachments.mjs +43 -0
  4. package/dist/checks/browser.d.ts +100 -0
  5. package/dist/checks/browser.mjs +89 -0
  6. package/dist/checks/config.d.ts +91 -0
  7. package/dist/checks/config.mjs +88 -0
  8. package/dist/checks/ide.d.ts +89 -0
  9. package/dist/checks/ide.mjs +80 -0
  10. package/dist/checks/mcp.d.ts +61 -0
  11. package/dist/checks/mcp.mjs +56 -0
  12. package/dist/checks/native-deps.d.ts +131 -0
  13. package/dist/checks/native-deps.mjs +115 -0
  14. package/dist/checks/network.d.ts +71 -0
  15. package/dist/checks/network.mjs +70 -0
  16. package/dist/checks/probe.d.ts +93 -0
  17. package/dist/checks/probe.mjs +82 -0
  18. package/dist/checks/profiles.d.ts +99 -0
  19. package/dist/checks/profiles.mjs +90 -0
  20. package/dist/checks/runtime.d.ts +89 -0
  21. package/dist/checks/runtime.mjs +90 -0
  22. package/dist/checks/vault.d.ts +101 -0
  23. package/dist/checks/vault.mjs +90 -0
  24. package/dist/chunk-3B276PGG.mjs +115 -0
  25. package/dist/chunk-4UEJOM6W.mjs +9 -0
  26. package/dist/chunk-6EP2BLTV.mjs +205 -0
  27. package/dist/chunk-6YMQVLFX.mjs +146 -0
  28. package/dist/chunk-7JL36EBH.mjs +118 -0
  29. package/dist/chunk-DPGMKSSA.mjs +57 -0
  30. package/dist/chunk-H4BUAPPO.mjs +1950 -0
  31. package/dist/chunk-HMKLWVXB.mjs +109 -0
  32. package/dist/chunk-HTUAQRKH.mjs +125 -0
  33. package/dist/chunk-HU5B4FXS.mjs +139 -0
  34. package/dist/chunk-KCXM2M4B.mjs +1006 -0
  35. package/dist/chunk-LKJMLGFP.mjs +237 -0
  36. package/dist/chunk-LZPLNZ5U.mjs +67 -0
  37. package/dist/chunk-MTDFKNXX.mjs +19 -0
  38. package/dist/chunk-OF4DMAPJ.mjs +511 -0
  39. package/dist/chunk-PE23RMXY.mjs +43 -0
  40. package/dist/chunk-Q2VY4R5F.mjs +175 -0
  41. package/dist/chunk-S5VD7WTU.mjs +2540 -0
  42. package/dist/chunk-SVPRB62V.mjs +106 -0
  43. package/dist/chunk-TQLCLE4L.mjs +345 -0
  44. package/dist/chunk-U3DGFLXZ.mjs +43 -0
  45. package/dist/chunk-X45O6YD3.mjs +688 -0
  46. package/dist/chunk-XKSWCEGI.mjs +168 -0
  47. package/dist/chunk-Z7DAACGZ.mjs +534 -0
  48. package/dist/chunk-ZQFUZPLO.mjs +257 -0
  49. package/dist/cli.d.ts +952 -0
  50. package/dist/cli.mjs +827 -0
  51. package/dist/client.d.ts +355 -0
  52. package/dist/client.mjs +27 -0
  53. package/dist/cloud-sync.d-Cqt6y18U.d.ts +42 -0
  54. package/dist/cloud-sync.d.ts +42 -0
  55. package/dist/cloud-sync.mjs +17 -0
  56. package/dist/config.d.ts +186 -0
  57. package/dist/config.mjs +54 -0
  58. package/dist/daemon/attach.d.ts +36 -0
  59. package/dist/daemon/attach.mjs +25 -0
  60. package/dist/daemon/audit.d.ts +23 -0
  61. package/dist/daemon/audit.mjs +12 -0
  62. package/dist/daemon/client-http.d.ts +42 -0
  63. package/dist/daemon/client-http.mjs +29 -0
  64. package/dist/daemon/index.d.ts +14 -0
  65. package/dist/daemon/index.mjs +110 -0
  66. package/dist/daemon/install-tunnel.d.ts +46 -0
  67. package/dist/daemon/install-tunnel.mjs +14 -0
  68. package/dist/daemon/launcher.d.ts +163 -0
  69. package/dist/daemon/launcher.mjs +50 -0
  70. package/dist/daemon/lockfile.d.ts +29 -0
  71. package/dist/daemon/lockfile.mjs +18 -0
  72. package/dist/daemon/server.d.ts +159 -0
  73. package/dist/daemon/server.mjs +20 -0
  74. package/dist/daemon/token.d.ts +17 -0
  75. package/dist/daemon/token.mjs +17 -0
  76. package/dist/daemon/tunnel-providers/index.d.ts +330 -0
  77. package/dist/daemon/tunnel-providers/index.mjs +57 -0
  78. package/dist/daemon/tunnel.d.ts +23 -0
  79. package/dist/daemon/tunnel.mjs +9 -0
  80. package/dist/doctor-report.d.ts +24 -0
  81. package/dist/doctor-report.mjs +14 -0
  82. package/dist/doctor.d-CXmUqOXX.d.ts +43 -0
  83. package/dist/doctor.d.ts +44 -0
  84. package/dist/doctor.mjs +16 -0
  85. package/dist/export.d.ts +19 -0
  86. package/dist/export.mjs +15 -0
  87. package/dist/health-check.d.ts +108 -0
  88. package/dist/health-check.mjs +92 -0
  89. package/dist/history-store.d-BzjBF2m3.d.ts +65 -0
  90. package/dist/history-store.d.ts +65 -0
  91. package/dist/history-store.mjs +48 -0
  92. package/dist/impit-login-runner.d.ts +469 -0
  93. package/dist/impit-login-runner.mjs +685 -0
  94. package/dist/index.d.ts +159 -0
  95. package/dist/index.mjs +236 -0
  96. package/dist/login-runner.d.ts +333 -0
  97. package/dist/login-runner.mjs +320 -0
  98. package/dist/logout.d.ts +28 -0
  99. package/dist/logout.mjs +45 -0
  100. package/dist/manual-login-runner.d.ts +150 -0
  101. package/dist/manual-login-runner.mjs +146 -0
  102. package/dist/native-deps-BNThFHxa.d.ts +175 -0
  103. package/dist/native-deps-YNKXITRY.mjs +139 -0
  104. package/dist/profiles.d-DqS1oZWr.d.ts +41 -0
  105. package/dist/profiles.d.ts +41 -0
  106. package/dist/profiles.mjs +33 -0
  107. package/dist/redact.d.ts +159 -0
  108. package/dist/redact.mjs +11 -0
  109. package/dist/refresh.d.ts +118 -0
  110. package/dist/refresh.mjs +21 -0
  111. package/dist/reinit-watcher.d.ts +15 -0
  112. package/dist/reinit-watcher.mjs +8 -0
  113. package/dist/session-metadata-B9aV_n5g.d.ts +148 -0
  114. package/dist/tty-prompt.d.ts +44 -0
  115. package/dist/tty-prompt.mjs +39 -0
  116. package/dist/vault.d-BtRSLZiM.d.ts +8 -0
  117. package/dist/vault.d.ts +37 -0
  118. package/dist/vault.mjs +21 -0
  119. package/dist/viewer-detect.d-HWGnyFAA.d.ts +4 -0
  120. package/dist/viewer-detect.d.ts +4 -0
  121. package/dist/viewer-detect.mjs +37 -0
  122. package/dist/viewers.d-BGCK6sw6.d.ts +10 -0
  123. package/dist/viewers.d.ts +18 -0
  124. package/dist/viewers.mjs +122 -0
  125. package/package.json +152 -0
@@ -0,0 +1,511 @@
1
+ import {
2
+ getActiveName,
3
+ getProfilePaths
4
+ } from "./chunk-XKSWCEGI.mjs";
5
+
6
+ // src/history-store.js
7
+ import { randomUUID } from "crypto";
8
+ import {
9
+ existsSync,
10
+ mkdirSync,
11
+ readFileSync,
12
+ readdirSync,
13
+ renameSync,
14
+ rmSync,
15
+ writeFileSync
16
+ } from "fs";
17
+ import { basename, dirname, join } from "path";
18
+ import matter from "gray-matter";
19
+ var HISTORY_LIMIT = 50;
20
+ var INDEX_VERSION = 2;
21
+ function getActiveProfileName() {
22
+ return process.env.PERPLEXITY_PROFILE || getActiveName() || "default";
23
+ }
24
+ function getActivePaths() {
25
+ return getProfilePaths(getActiveProfileName());
26
+ }
27
+ function getHistoryDir() {
28
+ return getActivePaths().history;
29
+ }
30
+ function getAttachmentsRoot() {
31
+ return getActivePaths().attachments;
32
+ }
33
+ function getIndexPath() {
34
+ return join(getHistoryDir(), "index.json");
35
+ }
36
+ function ensureStoreDirs() {
37
+ mkdirSync(getHistoryDir(), { recursive: true });
38
+ mkdirSync(getAttachmentsRoot(), { recursive: true });
39
+ }
40
+ function atomicWrite(path, contents) {
41
+ mkdirSync(dirname(path), { recursive: true });
42
+ const tempPath = `${path}.tmp`;
43
+ writeFileSync(tempPath, contents);
44
+ renameSync(tempPath, path);
45
+ }
46
+ function stripInternal(record) {
47
+ const { filename, body, mdPath, attachmentsDir, ...item } = record;
48
+ return item;
49
+ }
50
+ function collapseWhitespace(text) {
51
+ return String(text ?? "").replace(/\s+/g, " ").trim();
52
+ }
53
+ function buildPreview(body, error) {
54
+ if (error) return collapseWhitespace(error).slice(0, 220);
55
+ return collapseWhitespace(body).slice(0, 220);
56
+ }
57
+ function slugifyQuery(query) {
58
+ const normalized = String(query ?? "").normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-zA-Z0-9\s-]/g, " ").replace(/[-\s]+/g, " ").trim().toLowerCase();
59
+ const words = normalized.split(/\s+/).filter(Boolean).slice(0, 6);
60
+ const slug = words.join("-").slice(0, 60).replace(/^-+|-+$/g, "");
61
+ return slug || "entry";
62
+ }
63
+ function isoFileStamp(createdAt) {
64
+ return String(createdAt ?? (/* @__PURE__ */ new Date()).toISOString()).replace(/[:.]/g, "-");
65
+ }
66
+ function toArray(value) {
67
+ return Array.isArray(value) ? value : [];
68
+ }
69
+ function uniqueStrings(values) {
70
+ return [...new Set(toArray(values).map((value) => String(value).trim()).filter(Boolean))];
71
+ }
72
+ function cleanSources(sources) {
73
+ return toArray(sources).map((source, index) => ({
74
+ n: typeof source?.n === "number" ? source.n : index + 1,
75
+ title: String(source?.title ?? "").trim(),
76
+ url: String(source?.url ?? "").trim(),
77
+ ...source?.snippet ? { snippet: String(source.snippet) } : {}
78
+ })).filter((source) => source.title || source.url);
79
+ }
80
+ function cleanAttachments(attachments) {
81
+ return toArray(attachments).map((attachment) => ({
82
+ filename: String(attachment?.filename ?? "").trim(),
83
+ relPath: String(attachment?.relPath ?? "").replace(/\\/g, "/").trim(),
84
+ ...attachment?.mimeType ? { mimeType: String(attachment.mimeType) } : {},
85
+ ...typeof attachment?.sizeBytes === "number" ? { sizeBytes: attachment.sizeBytes } : {},
86
+ ...attachment?.kind ? { kind: attachment.kind } : {}
87
+ })).filter((attachment) => attachment.filename && attachment.relPath);
88
+ }
89
+ function normalizeStatus(value, error) {
90
+ if (value === "completed" || value === "pending" || value === "failed") {
91
+ return value;
92
+ }
93
+ return error ? "failed" : "completed";
94
+ }
95
+ function normalizeEntry(entry, body) {
96
+ const createdAt = typeof entry?.createdAt === "string" && entry.createdAt ? entry.createdAt : (/* @__PURE__ */ new Date()).toISOString();
97
+ const error = entry?.error ? String(entry.error) : void 0;
98
+ const sources = cleanSources(entry?.sources);
99
+ const attachments = cleanAttachments(entry?.attachments);
100
+ const status = normalizeStatus(entry?.status, error);
101
+ const answerPreview = entry?.answerPreview ? String(entry.answerPreview).slice(0, 220) : buildPreview(body, error);
102
+ const sourceCount = typeof entry?.sourceCount === "number" ? entry.sourceCount : sources.length;
103
+ const tier = entry?.tier ?? void 0;
104
+ const normalized = {
105
+ id: entry?.id ? String(entry.id) : randomUUID(),
106
+ tool: String(entry?.tool ?? "perplexity_search"),
107
+ query: String(entry?.query ?? ""),
108
+ model: entry?.model ?? null,
109
+ mode: entry?.mode ?? null,
110
+ language: entry?.language ?? null,
111
+ createdAt,
112
+ answerPreview,
113
+ sourceCount,
114
+ ...entry?.threadUrl ? { threadUrl: String(entry.threadUrl) } : {},
115
+ ...status ? { status } : {},
116
+ ...entry?.completedAt ? { completedAt: String(entry.completedAt) } : status === "completed" ? { completedAt: createdAt } : {},
117
+ ...tier ? { tier } : {},
118
+ ...entry?.threadSlug !== void 0 ? { threadSlug: entry.threadSlug ?? null } : {},
119
+ ...entry?.backendUuid !== void 0 ? { backendUuid: entry.backendUuid ?? null } : {},
120
+ ...entry?.readWriteToken !== void 0 ? { readWriteToken: entry.readWriteToken ?? null } : {},
121
+ ...sources.length > 0 ? { sources } : {},
122
+ ...attachments.length > 0 ? { attachments } : {},
123
+ ...uniqueStrings(entry?.tags).length > 0 ? { tags: uniqueStrings(entry.tags) } : {},
124
+ ...entry?.pinned !== void 0 ? { pinned: !!entry.pinned } : {},
125
+ ...entry?.source ? { source: String(entry.source) } : {},
126
+ ...entry?.cloudHydratedAt ? { cloudHydratedAt: String(entry.cloudHydratedAt) } : {},
127
+ ...error ? { error } : {}
128
+ };
129
+ return normalized;
130
+ }
131
+ function buildFilename(createdAt, query) {
132
+ return `${isoFileStamp(createdAt)}-${slugifyQuery(query)}.md`;
133
+ }
134
+ function ensureUniqueFilename(baseFilename) {
135
+ const historyDir = getHistoryDir();
136
+ let filename = baseFilename;
137
+ let attempt = 1;
138
+ while (existsSync(join(historyDir, filename))) {
139
+ const suffix = `-${attempt}`;
140
+ filename = baseFilename.endsWith(".md") ? `${baseFilename.slice(0, -3)}${suffix}.md` : `${baseFilename}${suffix}.md`;
141
+ attempt += 1;
142
+ }
143
+ return filename;
144
+ }
145
+ function toFrontmatter(entry) {
146
+ const frontmatter = {
147
+ id: entry.id,
148
+ tool: entry.tool,
149
+ query: entry.query,
150
+ model: entry.model,
151
+ mode: entry.mode,
152
+ language: entry.language,
153
+ createdAt: entry.createdAt,
154
+ answerPreview: entry.answerPreview,
155
+ sourceCount: entry.sourceCount,
156
+ threadUrl: entry.threadUrl,
157
+ status: entry.status,
158
+ completedAt: entry.completedAt,
159
+ tier: entry.tier,
160
+ threadSlug: entry.threadSlug,
161
+ backendUuid: entry.backendUuid,
162
+ readWriteToken: entry.readWriteToken,
163
+ sources: entry.sources,
164
+ attachments: entry.attachments,
165
+ tags: entry.tags,
166
+ pinned: entry.pinned,
167
+ source: entry.source,
168
+ cloudHydratedAt: entry.cloudHydratedAt,
169
+ error: entry.error
170
+ };
171
+ const cleaned = {};
172
+ for (const [key, value] of Object.entries(frontmatter)) {
173
+ if (value === void 0) continue;
174
+ cleaned[key] = value;
175
+ }
176
+ return cleaned;
177
+ }
178
+ function serializeRecord(entry, body) {
179
+ const content = body ? String(body).trimEnd() : "";
180
+ return `${matter.stringify(content, toFrontmatter(entry)).trimEnd()}
181
+ `;
182
+ }
183
+ function parseMarkdownFile(path, filenameOverride) {
184
+ const raw = readFileSync(path, "utf8");
185
+ const parsed = matter(raw);
186
+ const filename = filenameOverride ?? basename(path);
187
+ const stem = filename.endsWith(".md") ? filename.slice(0, -3) : filename;
188
+ const body = String(parsed.content ?? "").trim();
189
+ const normalized = normalizeEntry(parsed.data ?? {}, body);
190
+ if (!normalized.id || !normalized.query || !normalized.tool) {
191
+ return null;
192
+ }
193
+ return {
194
+ ...normalized,
195
+ filename,
196
+ body,
197
+ mdPath: path,
198
+ attachmentsDir: join(getAttachmentsRoot(), stem)
199
+ };
200
+ }
201
+ function sortEntries(entries) {
202
+ return [...entries].sort((left, right) => {
203
+ const rightTime = Date.parse(right.createdAt ?? "") || 0;
204
+ const leftTime = Date.parse(left.createdAt ?? "") || 0;
205
+ return rightTime - leftTime;
206
+ });
207
+ }
208
+ function writeIndex(entries) {
209
+ const payload = {
210
+ version: INDEX_VERSION,
211
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
212
+ items: sortEntries(entries).map((entry) => ({
213
+ filename: entry.filename,
214
+ ...stripInternal(entry)
215
+ }))
216
+ };
217
+ atomicWrite(getIndexPath(), `${JSON.stringify(payload, null, 2)}
218
+ `);
219
+ }
220
+ function readIndex() {
221
+ if (!existsSync(getIndexPath())) {
222
+ return null;
223
+ }
224
+ try {
225
+ const raw = JSON.parse(readFileSync(getIndexPath(), "utf8"));
226
+ if (raw?.version !== INDEX_VERSION || !Array.isArray(raw.items)) {
227
+ return null;
228
+ }
229
+ return raw.items.map((entry) => ({
230
+ ...entry,
231
+ filename: String(entry.filename ?? "")
232
+ }));
233
+ } catch {
234
+ return null;
235
+ }
236
+ }
237
+ function loadIndexedEntries() {
238
+ ensureStoreDirs();
239
+ const indexed = readIndex();
240
+ if (indexed) {
241
+ return sortEntries(indexed);
242
+ }
243
+ return rebuildIndex().items;
244
+ }
245
+ function readRecordByFilename(filename) {
246
+ const mdPath = join(getHistoryDir(), filename);
247
+ if (!existsSync(mdPath)) {
248
+ return null;
249
+ }
250
+ return parseMarkdownFile(mdPath, filename);
251
+ }
252
+ function findIndexedEntry(id) {
253
+ return loadIndexedEntries().find((entry) => entry.id === id) ?? null;
254
+ }
255
+ function resolveRecord(id) {
256
+ const indexed = findIndexedEntry(id);
257
+ if (!indexed) {
258
+ return null;
259
+ }
260
+ const record = readRecordByFilename(indexed.filename);
261
+ if (record) {
262
+ return record;
263
+ }
264
+ const rebuilt = rebuildIndex().items.find((entry) => entry.id === id) ?? null;
265
+ return rebuilt ? readRecordByFilename(rebuilt.filename) : null;
266
+ }
267
+ function append(entry) {
268
+ ensureStoreDirs();
269
+ if (entry?.id && resolveRecord(entry.id)) {
270
+ throw new Error(`History entry '${entry.id}' already exists.`);
271
+ }
272
+ const body = String(entry?.body ?? "").trim();
273
+ const normalized = normalizeEntry(entry ?? {}, body);
274
+ const filename = ensureUniqueFilename(buildFilename(normalized.createdAt, normalized.query));
275
+ const mdPath = join(getHistoryDir(), filename);
276
+ atomicWrite(mdPath, serializeRecord(normalized, body));
277
+ const record = {
278
+ ...normalized,
279
+ filename,
280
+ body,
281
+ mdPath,
282
+ attachmentsDir: join(getAttachmentsRoot(), filename.slice(0, -3))
283
+ };
284
+ const next = loadIndexedEntries().filter((item) => item.id !== normalized.id);
285
+ next.unshift({ ...record, ...stripInternal(record) });
286
+ writeIndex(next);
287
+ return stripInternal(record);
288
+ }
289
+ function update(id, patch = {}) {
290
+ const existing = resolveRecord(id);
291
+ if (!existing) {
292
+ return null;
293
+ }
294
+ const body = patch.body !== void 0 ? String(patch.body).trim() : existing.body;
295
+ const merged = normalizeEntry(
296
+ {
297
+ ...existing,
298
+ ...patch,
299
+ id,
300
+ createdAt: existing.createdAt,
301
+ answerPreview: patch.answerPreview ?? existing.answerPreview,
302
+ sourceCount: patch.sourceCount ?? existing.sourceCount
303
+ },
304
+ body
305
+ );
306
+ atomicWrite(existing.mdPath, serializeRecord(merged, body));
307
+ const record = {
308
+ ...merged,
309
+ filename: existing.filename,
310
+ body,
311
+ mdPath: existing.mdPath,
312
+ attachmentsDir: existing.attachmentsDir
313
+ };
314
+ const next = loadIndexedEntries().filter((item) => item.id !== id);
315
+ next.unshift({ ...record, ...stripInternal(record) });
316
+ writeIndex(next);
317
+ return stripInternal(record);
318
+ }
319
+ function list(options = {}) {
320
+ const {
321
+ limit = HISTORY_LIMIT,
322
+ status,
323
+ tool,
324
+ tools,
325
+ filter
326
+ } = options;
327
+ const toolSet = tools ? new Set(toArray(tools).map((value) => String(value))) : null;
328
+ const needle = String(filter ?? "").trim().toLowerCase();
329
+ let items = loadIndexedEntries().map((entry) => stripInternal(entry));
330
+ if (status) {
331
+ items = items.filter((entry) => entry.status === status);
332
+ }
333
+ if (tool) {
334
+ items = items.filter((entry) => entry.tool === tool);
335
+ }
336
+ if (toolSet) {
337
+ items = items.filter((entry) => toolSet.has(entry.tool));
338
+ }
339
+ if (needle) {
340
+ items = items.filter(
341
+ (entry) => entry.query.toLowerCase().includes(needle) || entry.tool.toLowerCase().includes(needle) || entry.answerPreview.toLowerCase().includes(needle) || toArray(entry.tags).some((tag2) => String(tag2).toLowerCase().includes(needle))
342
+ );
343
+ }
344
+ return limit === Infinity ? items : items.slice(0, Number(limit) || HISTORY_LIMIT);
345
+ }
346
+ function get(id) {
347
+ const record = resolveRecord(id);
348
+ if (!record) {
349
+ return null;
350
+ }
351
+ return {
352
+ ...stripInternal(record),
353
+ body: record.body,
354
+ mdPath: record.mdPath,
355
+ attachmentsDir: record.attachmentsDir
356
+ };
357
+ }
358
+ function deleteEntry(id) {
359
+ const record = resolveRecord(id);
360
+ if (!record) {
361
+ return false;
362
+ }
363
+ rmSync(record.mdPath, { force: true });
364
+ rmSync(record.attachmentsDir, { recursive: true, force: true });
365
+ const next = loadIndexedEntries().filter((entry) => entry.id !== id);
366
+ writeIndex(next);
367
+ return true;
368
+ }
369
+ function pin(id, pinned) {
370
+ return update(id, { pinned: !!pinned });
371
+ }
372
+ function tag(id, tags) {
373
+ return update(id, { tags: uniqueStrings(tags) });
374
+ }
375
+ function rebuildIndex() {
376
+ ensureStoreDirs();
377
+ const files = readdirSync(getHistoryDir()).filter((file) => file.endsWith(".md"));
378
+ const items = [];
379
+ let skipped = 0;
380
+ for (const file of files) {
381
+ try {
382
+ const record = parseMarkdownFile(join(getHistoryDir(), file), file);
383
+ if (!record) {
384
+ skipped += 1;
385
+ continue;
386
+ }
387
+ items.push(record);
388
+ } catch {
389
+ skipped += 1;
390
+ }
391
+ }
392
+ writeIndex(items);
393
+ return {
394
+ scanned: files.length,
395
+ recovered: items.length,
396
+ skipped,
397
+ items: sortEntries(items).map((entry) => ({
398
+ ...stripInternal(entry),
399
+ filename: entry.filename
400
+ }))
401
+ };
402
+ }
403
+ function getMdPath(id) {
404
+ return resolveRecord(id)?.mdPath ?? null;
405
+ }
406
+ function getAttachmentsDir(id) {
407
+ return resolveRecord(id)?.attachmentsDir ?? null;
408
+ }
409
+ function findPendingByThread(threadSlug) {
410
+ if (!threadSlug) {
411
+ return null;
412
+ }
413
+ return list({ limit: Infinity, status: "pending" }).find((entry) => entry.threadSlug === threadSlug) ?? null;
414
+ }
415
+ function countAll() {
416
+ ensureStoreDirs();
417
+ const indexed = readIndex();
418
+ if (indexed) {
419
+ return indexed.length;
420
+ }
421
+ return readdirSync(getHistoryDir()).filter((file) => file.endsWith(".md")).length;
422
+ }
423
+ function appendHistory(entry) {
424
+ return append(entry);
425
+ }
426
+ function readHistory(limit = HISTORY_LIMIT) {
427
+ return list({ limit });
428
+ }
429
+ function findByBackendUuid(backendUuid) {
430
+ if (!backendUuid) return null;
431
+ const items = loadIndexedEntries();
432
+ return items.find((entry) => entry.backendUuid === String(backendUuid)) ?? null;
433
+ }
434
+ var CLOUD_STUB_BODY = "_Click **Rich View** to fetch this thread from Perplexity._\n\nThis entry was synced from your Perplexity.ai library. The full answer and sources are fetched on demand to keep sync fast.";
435
+ function upsertFromCloud(meta) {
436
+ if (!meta?.backendUuid) throw new Error("upsertFromCloud requires backendUuid");
437
+ const existing = findByBackendUuid(meta.backendUuid);
438
+ if (existing && existing.source !== "cloud") {
439
+ return { action: "skipped-local", id: existing.id };
440
+ }
441
+ if (existing) {
442
+ const patched = update(existing.id, {
443
+ query: meta.query ?? existing.query,
444
+ answerPreview: meta.answerPreview ?? existing.answerPreview,
445
+ threadUrl: meta.threadUrl ?? existing.threadUrl,
446
+ threadSlug: meta.threadSlug ?? existing.threadSlug,
447
+ readWriteToken: meta.readWriteToken ?? existing.readWriteToken,
448
+ mode: meta.mode ?? existing.mode,
449
+ model: meta.model ?? existing.model,
450
+ sourceCount: meta.sourceCount ?? existing.sourceCount,
451
+ status: meta.status ?? existing.status
452
+ });
453
+ return { action: "updated", id: patched?.id ?? existing.id };
454
+ }
455
+ const inserted = append({
456
+ tool: meta.tool ?? "perplexity_search",
457
+ query: meta.query ?? meta.title ?? "(untitled)",
458
+ model: meta.model ?? null,
459
+ mode: meta.mode ?? null,
460
+ language: meta.language ?? null,
461
+ tier: meta.tier,
462
+ createdAt: meta.createdAt,
463
+ answerPreview: meta.answerPreview ?? "",
464
+ sourceCount: meta.sourceCount ?? 0,
465
+ threadUrl: meta.threadUrl,
466
+ threadSlug: meta.threadSlug,
467
+ backendUuid: meta.backendUuid,
468
+ readWriteToken: meta.readWriteToken,
469
+ status: meta.status ?? "completed",
470
+ source: "cloud",
471
+ body: CLOUD_STUB_BODY
472
+ });
473
+ return { action: "inserted", id: inserted.id };
474
+ }
475
+ function hydrateCloudEntry(id, payload) {
476
+ if (!id) throw new Error("hydrateCloudEntry: id required");
477
+ const existing = resolveRecord(id);
478
+ if (!existing) return null;
479
+ return update(id, {
480
+ body: payload.body ?? existing.body,
481
+ sources: payload.sources ?? existing.sources,
482
+ attachments: payload.attachments ?? existing.attachments,
483
+ answerPreview: payload.answerPreview ?? existing.answerPreview,
484
+ sourceCount: typeof payload.sourceCount === "number" ? payload.sourceCount : existing.sourceCount,
485
+ cloudHydratedAt: (/* @__PURE__ */ new Date()).toISOString()
486
+ });
487
+ }
488
+
489
+ export {
490
+ HISTORY_LIMIT,
491
+ getHistoryDir,
492
+ getAttachmentsRoot,
493
+ getIndexPath,
494
+ append,
495
+ update,
496
+ list,
497
+ get,
498
+ deleteEntry,
499
+ pin,
500
+ tag,
501
+ rebuildIndex,
502
+ getMdPath,
503
+ getAttachmentsDir,
504
+ findPendingByThread,
505
+ countAll,
506
+ appendHistory,
507
+ readHistory,
508
+ findByBackendUuid,
509
+ upsertFromCloud,
510
+ hydrateCloudEntry
511
+ };
@@ -0,0 +1,43 @@
1
+ import {
2
+ getConfigDir
3
+ } from "./chunk-XKSWCEGI.mjs";
4
+
5
+ // src/daemon/audit.ts
6
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync } from "fs";
7
+ import { dirname, join } from "path";
8
+ var DEFAULT_MAX_BYTES = 10 * 1024 * 1024;
9
+ function getAuditLogPath(configDir = getConfigDir()) {
10
+ return join(configDir, "audit.log");
11
+ }
12
+ function appendAuditEntry(entry, options = {}) {
13
+ const auditPath = options.auditPath ?? getAuditLogPath();
14
+ const line = JSON.stringify(entry) + "\n";
15
+ mkdirSync(dirname(auditPath), { recursive: true });
16
+ rotateIfNeeded(auditPath, Buffer.byteLength(line), options.maxBytes ?? DEFAULT_MAX_BYTES);
17
+ appendFileSync(auditPath, line, "utf8");
18
+ }
19
+ function readAuditTail(limit = 50, options = {}) {
20
+ const auditPath = options.auditPath ?? getAuditLogPath();
21
+ if (!existsSync(auditPath)) {
22
+ return [];
23
+ }
24
+ return readFileSync(auditPath, "utf8").split(/\r?\n/).filter(Boolean).slice(-limit).map((line) => JSON.parse(line));
25
+ }
26
+ function rotateIfNeeded(auditPath, nextWriteBytes, maxBytes) {
27
+ if (!existsSync(auditPath)) {
28
+ return;
29
+ }
30
+ const size = statSync(auditPath).size;
31
+ if (size + nextWriteBytes <= maxBytes) {
32
+ return;
33
+ }
34
+ const rotatedPath = `${auditPath}.1`;
35
+ rmSync(rotatedPath, { force: true });
36
+ renameSync(auditPath, rotatedPath);
37
+ }
38
+
39
+ export {
40
+ getAuditLogPath,
41
+ appendAuditEntry,
42
+ readAuditTail
43
+ };