@disco_trooper/apple-notes-mcp 1.1.0 → 1.3.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.
- package/README.md +104 -24
- package/package.json +11 -12
- package/src/config/claude.test.ts +47 -0
- package/src/config/claude.ts +106 -0
- package/src/config/constants.ts +11 -2
- package/src/config/paths.test.ts +40 -0
- package/src/config/paths.ts +86 -0
- package/src/db/arrow-fix.test.ts +101 -0
- package/src/db/lancedb.test.ts +254 -2
- package/src/db/lancedb.ts +385 -38
- package/src/embeddings/cache.test.ts +150 -0
- package/src/embeddings/cache.ts +204 -0
- package/src/embeddings/index.ts +22 -4
- package/src/embeddings/local.ts +57 -17
- package/src/embeddings/openrouter.ts +233 -11
- package/src/errors/index.test.ts +64 -0
- package/src/errors/index.ts +62 -0
- package/src/graph/export.test.ts +81 -0
- package/src/graph/export.ts +163 -0
- package/src/graph/extract.test.ts +90 -0
- package/src/graph/extract.ts +52 -0
- package/src/graph/queries.test.ts +156 -0
- package/src/graph/queries.ts +224 -0
- package/src/index.ts +309 -23
- package/src/notes/conversion.ts +62 -0
- package/src/notes/crud.test.ts +41 -8
- package/src/notes/crud.ts +75 -64
- package/src/notes/read.test.ts +58 -3
- package/src/notes/read.ts +142 -210
- package/src/notes/resolve.ts +174 -0
- package/src/notes/tables.ts +69 -40
- package/src/search/chunk-indexer.test.ts +353 -0
- package/src/search/chunk-indexer.ts +207 -0
- package/src/search/chunk-search.test.ts +327 -0
- package/src/search/chunk-search.ts +298 -0
- package/src/search/index.ts +4 -6
- package/src/search/indexer.ts +164 -109
- package/src/setup.ts +46 -67
- package/src/types/index.ts +4 -0
- package/src/utils/chunker.test.ts +182 -0
- package/src/utils/chunker.ts +170 -0
- package/src/utils/content-filter.test.ts +225 -0
- package/src/utils/content-filter.ts +275 -0
- package/src/utils/debug.ts +0 -2
- package/src/utils/runtime.test.ts +70 -0
- package/src/utils/runtime.ts +40 -0
- package/src/utils/text.test.ts +32 -0
- package/CLAUDE.md +0 -56
- package/src/server.ts +0 -427
package/src/search/indexer.ts
CHANGED
|
@@ -7,20 +7,20 @@
|
|
|
7
7
|
* - Single note reindexing
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { getEmbedding } from "../embeddings/index.js";
|
|
10
|
+
import { getEmbedding, getEmbeddingBatch } from "../embeddings/index.js";
|
|
11
11
|
import { getVectorStore, type NoteRecord } from "../db/lancedb.js";
|
|
12
|
-
import { getAllNotes, getNoteByFolderAndTitle, getNoteByTitle, type NoteInfo } from "../notes/read.js";
|
|
12
|
+
import { getAllNotes, getAllNotesWithContent, getNoteByFolderAndTitle, getNoteByTitle, type NoteInfo } from "../notes/read.js";
|
|
13
13
|
import { createDebugLogger } from "../utils/debug.js";
|
|
14
14
|
import { truncateForEmbedding } from "../utils/text.js";
|
|
15
|
-
import {
|
|
15
|
+
import { NoteNotFoundError } from "../errors/index.js";
|
|
16
|
+
import { extractMetadata } from "../graph/extract.js";
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Extract note title from folder/title key.
|
|
19
20
|
* Handles nested folders correctly by taking the last segment.
|
|
20
21
|
*/
|
|
21
22
|
export function extractTitleFromKey(key: string): string {
|
|
22
|
-
|
|
23
|
-
return parts[parts.length - 1];
|
|
23
|
+
return key.split("/").at(-1) ?? key;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
// Debug logging
|
|
@@ -50,89 +50,127 @@ export interface IndexResult {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
|
-
*
|
|
53
|
+
* Note data prepared for embedding.
|
|
54
54
|
*/
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
interface PreparedNote {
|
|
56
|
+
id: string;
|
|
57
|
+
title: string;
|
|
58
|
+
content: string;
|
|
59
|
+
truncatedContent: string;
|
|
60
|
+
folder: string;
|
|
61
|
+
created: string;
|
|
62
|
+
modified: string;
|
|
63
|
+
tags: string[];
|
|
64
|
+
outlinks: string[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Prepare a note for embedding by extracting metadata and truncating content.
|
|
69
|
+
* Returns null if the note content is empty.
|
|
70
|
+
*/
|
|
71
|
+
function prepareNoteForEmbedding(note: {
|
|
72
|
+
id: string;
|
|
73
|
+
title: string;
|
|
74
|
+
content: string;
|
|
75
|
+
folder: string;
|
|
76
|
+
created: string;
|
|
77
|
+
modified: string;
|
|
78
|
+
}): PreparedNote | null {
|
|
79
|
+
if (!note.content.trim()) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const metadata = extractMetadata(note.content);
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
id: note.id,
|
|
87
|
+
title: note.title,
|
|
88
|
+
content: note.content,
|
|
89
|
+
truncatedContent: truncateForEmbedding(note.content),
|
|
90
|
+
folder: note.folder,
|
|
91
|
+
created: note.created,
|
|
92
|
+
modified: note.modified,
|
|
93
|
+
tags: metadata.tags,
|
|
94
|
+
outlinks: metadata.outlinks,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Build a NoteRecord from a PreparedNote and its embedding vector.
|
|
100
|
+
*/
|
|
101
|
+
function buildNoteRecord(
|
|
102
|
+
note: PreparedNote,
|
|
103
|
+
vector: number[],
|
|
104
|
+
indexedAt: string
|
|
105
|
+
): NoteRecord {
|
|
106
|
+
return {
|
|
107
|
+
id: note.id,
|
|
108
|
+
title: note.title,
|
|
109
|
+
content: note.content,
|
|
110
|
+
vector,
|
|
111
|
+
folder: note.folder,
|
|
112
|
+
created: note.created,
|
|
113
|
+
modified: note.modified,
|
|
114
|
+
indexed_at: indexedAt,
|
|
115
|
+
tags: note.tags,
|
|
116
|
+
outlinks: note.outlinks,
|
|
117
|
+
};
|
|
57
118
|
}
|
|
58
119
|
|
|
59
120
|
/**
|
|
60
121
|
* Perform full reindexing of all notes.
|
|
61
122
|
* Drops existing index and rebuilds from scratch.
|
|
123
|
+
* Uses single JXA call + batch embedding for maximum speed.
|
|
62
124
|
*/
|
|
63
125
|
export async function fullIndex(): Promise<IndexResult> {
|
|
64
126
|
const startTime = Date.now();
|
|
65
127
|
debug("Starting full index...");
|
|
66
128
|
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const records: NoteRecord[] = [];
|
|
72
|
-
let errors = 0;
|
|
73
|
-
const failedNotes: string[] = [];
|
|
129
|
+
// Phase 1: Fetch all notes with content in single JXA call
|
|
130
|
+
debug("Phase 1: Fetching all notes with content (single JXA call)...");
|
|
131
|
+
const allNotes = await getAllNotesWithContent();
|
|
132
|
+
debug(`Fetched ${allNotes.length} notes from Apple Notes`);
|
|
74
133
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
134
|
+
// Filter empty notes and prepare for embedding
|
|
135
|
+
const preparedNotes = allNotes
|
|
136
|
+
.map(prepareNoteForEmbedding)
|
|
137
|
+
.filter((note): note is PreparedNote => note !== null);
|
|
78
138
|
|
|
79
|
-
|
|
80
|
-
// Get full note content using folder and title separately
|
|
81
|
-
// to handle notes with "/" in their titles
|
|
82
|
-
const noteDetails = await getNoteByFolderAndTitle(noteInfo.folder, noteInfo.title);
|
|
83
|
-
if (!noteDetails) {
|
|
84
|
-
debug(`Could not fetch note: ${noteInfo.title}`);
|
|
85
|
-
failedNotes.push(`${noteInfo.folder}/${noteInfo.title}`);
|
|
86
|
-
errors++;
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
139
|
+
debug(`Prepared ${preparedNotes.length} notes for embedding`);
|
|
89
140
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
141
|
+
// Phase 2: Generate embeddings in batch (with concurrent API calls)
|
|
142
|
+
debug("Phase 2: Generating embeddings in batch...");
|
|
143
|
+
const textsToEmbed = preparedNotes.map(n => n.truncatedContent);
|
|
95
144
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
content: noteDetails.content,
|
|
103
|
-
vector,
|
|
104
|
-
folder: noteDetails.folder,
|
|
105
|
-
created: noteDetails.created,
|
|
106
|
-
modified: noteDetails.modified,
|
|
107
|
-
indexed_at: new Date().toISOString(),
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
records.push(record);
|
|
111
|
-
|
|
112
|
-
// Delay to avoid rate limiting
|
|
113
|
-
if (i < notes.length - 1) {
|
|
114
|
-
await sleep(EMBEDDING_DELAY_MS);
|
|
115
|
-
}
|
|
116
|
-
} catch (error) {
|
|
117
|
-
debug(`Error processing ${noteInfo.title}:`, error);
|
|
118
|
-
failedNotes.push(`${noteInfo.folder}/${noteInfo.title}`);
|
|
119
|
-
errors++;
|
|
120
|
-
}
|
|
145
|
+
let vectors: number[][];
|
|
146
|
+
try {
|
|
147
|
+
vectors = await getEmbeddingBatch(textsToEmbed);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
debug("Batch embedding failed:", error);
|
|
150
|
+
throw error;
|
|
121
151
|
}
|
|
122
152
|
|
|
123
|
-
|
|
153
|
+
debug(`Generated ${vectors.length} embeddings`);
|
|
154
|
+
|
|
155
|
+
// Phase 3: Build records and store
|
|
156
|
+
debug("Phase 3: Storing in database...");
|
|
157
|
+
const indexedAt = new Date().toISOString();
|
|
158
|
+
const records = preparedNotes.map((note, i) =>
|
|
159
|
+
buildNoteRecord(note, vectors[i], indexedAt)
|
|
160
|
+
);
|
|
161
|
+
|
|
124
162
|
const store = getVectorStore();
|
|
125
163
|
await store.index(records);
|
|
126
164
|
|
|
127
165
|
const timeMs = Date.now() - startTime;
|
|
128
|
-
|
|
166
|
+
const skipped = allNotes.length - preparedNotes.length;
|
|
167
|
+
debug(`Full index complete: ${records.length} indexed, ${skipped} empty/skipped, ${timeMs}ms`);
|
|
129
168
|
|
|
130
169
|
return {
|
|
131
|
-
total:
|
|
170
|
+
total: allNotes.length,
|
|
132
171
|
indexed: records.length,
|
|
133
|
-
errors,
|
|
172
|
+
errors: 0,
|
|
134
173
|
timeMs,
|
|
135
|
-
failedNotes: failedNotes.length > 0 ? failedNotes : undefined,
|
|
136
174
|
};
|
|
137
175
|
}
|
|
138
176
|
|
|
@@ -211,47 +249,63 @@ export async function incrementalIndex(): Promise<IndexResult> {
|
|
|
211
249
|
let errors = 0;
|
|
212
250
|
const failedNotes: string[] = [];
|
|
213
251
|
|
|
214
|
-
// Process additions and updates
|
|
252
|
+
// Process additions and updates in batch
|
|
215
253
|
const toProcess = [...toAdd, ...toUpdate];
|
|
216
|
-
for (let i = 0; i < toProcess.length; i++) {
|
|
217
|
-
const noteInfo = toProcess[i];
|
|
218
|
-
debug(`Processing ${i + 1}/${toProcess.length}: ${noteInfo.title}`);
|
|
219
254
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
255
|
+
if (toProcess.length > 0) {
|
|
256
|
+
// Phase 1: Fetch all note content
|
|
257
|
+
debug(`Phase 1: Fetching ${toProcess.length} notes content...`);
|
|
258
|
+
const preparedNotes: PreparedNote[] = [];
|
|
259
|
+
|
|
260
|
+
for (const noteInfo of toProcess) {
|
|
261
|
+
try {
|
|
262
|
+
const noteDetails = await getNoteByFolderAndTitle(noteInfo.folder, noteInfo.title);
|
|
263
|
+
if (!noteDetails) {
|
|
264
|
+
failedNotes.push(`${noteInfo.folder}/${noteInfo.title}`);
|
|
265
|
+
errors++;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const prepared = prepareNoteForEmbedding(noteDetails);
|
|
270
|
+
if (prepared) {
|
|
271
|
+
preparedNotes.push(prepared);
|
|
272
|
+
}
|
|
273
|
+
} catch (error) {
|
|
274
|
+
debug(`Error fetching ${noteInfo.title}:`, error);
|
|
224
275
|
failedNotes.push(`${noteInfo.folder}/${noteInfo.title}`);
|
|
225
276
|
errors++;
|
|
226
|
-
continue;
|
|
227
277
|
}
|
|
278
|
+
}
|
|
228
279
|
|
|
229
|
-
|
|
230
|
-
|
|
280
|
+
if (preparedNotes.length > 0) {
|
|
281
|
+
// Phase 2: Generate embeddings in batch
|
|
282
|
+
debug(`Phase 2: Generating ${preparedNotes.length} embeddings in batch...`);
|
|
283
|
+
const textsToEmbed = preparedNotes.map(n => n.truncatedContent);
|
|
284
|
+
|
|
285
|
+
let vectors: number[][];
|
|
286
|
+
try {
|
|
287
|
+
vectors = await getEmbeddingBatch(textsToEmbed);
|
|
288
|
+
} catch (error) {
|
|
289
|
+
debug("Batch embedding failed:", error);
|
|
290
|
+
throw error;
|
|
231
291
|
}
|
|
232
292
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
if (i < toProcess.length - 1) {
|
|
249
|
-
await sleep(EMBEDDING_DELAY_MS);
|
|
293
|
+
// Phase 3: Update database
|
|
294
|
+
debug("Phase 3: Updating database...");
|
|
295
|
+
const indexedAt = new Date().toISOString();
|
|
296
|
+
|
|
297
|
+
for (let i = 0; i < preparedNotes.length; i++) {
|
|
298
|
+
const note = preparedNotes[i];
|
|
299
|
+
const record = buildNoteRecord(note, vectors[i], indexedAt);
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
await store.update(record);
|
|
303
|
+
} catch (error) {
|
|
304
|
+
debug(`Error updating ${note.title}:`, error);
|
|
305
|
+
failedNotes.push(`${note.folder}/${note.title}`);
|
|
306
|
+
errors++;
|
|
307
|
+
}
|
|
250
308
|
}
|
|
251
|
-
} catch (error) {
|
|
252
|
-
debug(`Error processing ${noteInfo.title}:`, error);
|
|
253
|
-
failedNotes.push(`${noteInfo.folder}/${noteInfo.title}`);
|
|
254
|
-
errors++;
|
|
255
309
|
}
|
|
256
310
|
}
|
|
257
311
|
|
|
@@ -270,6 +324,12 @@ export async function incrementalIndex(): Promise<IndexResult> {
|
|
|
270
324
|
}
|
|
271
325
|
}
|
|
272
326
|
|
|
327
|
+
// Rebuild FTS index if any changes were made
|
|
328
|
+
if (toAdd.length > 0 || toUpdate.length > 0 || toDelete.length > 0) {
|
|
329
|
+
debug("Rebuilding FTS index after incremental changes");
|
|
330
|
+
await store.rebuildFtsIndex();
|
|
331
|
+
}
|
|
332
|
+
|
|
273
333
|
const timeMs = Date.now() - startTime;
|
|
274
334
|
debug(`Incremental index complete: ${timeMs}ms`);
|
|
275
335
|
|
|
@@ -296,29 +356,24 @@ export async function reindexNote(title: string): Promise<void> {
|
|
|
296
356
|
|
|
297
357
|
const noteDetails = await getNoteByTitle(title);
|
|
298
358
|
if (!noteDetails) {
|
|
299
|
-
throw new
|
|
359
|
+
throw new NoteNotFoundError(title);
|
|
300
360
|
}
|
|
301
361
|
|
|
302
|
-
|
|
362
|
+
const prepared = prepareNoteForEmbedding(noteDetails);
|
|
363
|
+
if (!prepared) {
|
|
303
364
|
throw new Error(`Note is empty: "${title}"`);
|
|
304
365
|
}
|
|
305
366
|
|
|
306
|
-
const
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
const record: NoteRecord = {
|
|
310
|
-
title: noteDetails.title,
|
|
311
|
-
content: noteDetails.content,
|
|
312
|
-
vector,
|
|
313
|
-
folder: noteDetails.folder,
|
|
314
|
-
created: noteDetails.created,
|
|
315
|
-
modified: noteDetails.modified,
|
|
316
|
-
indexed_at: new Date().toISOString(),
|
|
317
|
-
};
|
|
367
|
+
const vector = await getEmbedding(prepared.truncatedContent);
|
|
368
|
+
const record = buildNoteRecord(prepared, vector, new Date().toISOString());
|
|
318
369
|
|
|
319
370
|
const store = getVectorStore();
|
|
320
371
|
await store.update(record);
|
|
321
372
|
|
|
373
|
+
// Rebuild FTS index after single note update
|
|
374
|
+
debug("Rebuilding FTS index after single note reindex");
|
|
375
|
+
await store.rebuildFtsIndex();
|
|
376
|
+
|
|
322
377
|
debug(`Reindexed: ${title}`);
|
|
323
378
|
}
|
|
324
379
|
|
package/src/setup.ts
CHANGED
|
@@ -12,14 +12,23 @@
|
|
|
12
12
|
|
|
13
13
|
import * as p from "@clack/prompts";
|
|
14
14
|
import * as fs from "node:fs";
|
|
15
|
-
import
|
|
15
|
+
import {
|
|
16
|
+
getEnvPath,
|
|
17
|
+
ensureConfigDir,
|
|
18
|
+
hasLegacyConfig,
|
|
19
|
+
getLegacyEnvPath,
|
|
20
|
+
hasConfig,
|
|
21
|
+
isNpmInstall,
|
|
22
|
+
} from "./config/paths.js";
|
|
23
|
+
import {
|
|
24
|
+
getClaudeConfigEntry,
|
|
25
|
+
writeClaudeConfig,
|
|
26
|
+
getExistingInstallMethod,
|
|
27
|
+
} from "./config/claude.js";
|
|
28
|
+
import { checkBunRuntime } from "./utils/runtime.js";
|
|
29
|
+
|
|
16
30
|
// Paths
|
|
17
|
-
const
|
|
18
|
-
const ENV_FILE = path.join(PROJECT_DIR, "..", ".env");
|
|
19
|
-
const CLAUDE_CONFIG_PATH = path.join(
|
|
20
|
-
process.env.HOME || "~",
|
|
21
|
-
".claude.json"
|
|
22
|
-
);
|
|
31
|
+
const ENV_FILE = getEnvPath();
|
|
23
32
|
|
|
24
33
|
interface Config {
|
|
25
34
|
provider: "local" | "openrouter";
|
|
@@ -69,6 +78,7 @@ function readExistingEnv(): Record<string, string> {
|
|
|
69
78
|
* Write configuration to .env file
|
|
70
79
|
*/
|
|
71
80
|
function writeEnvFile(config: Config): void {
|
|
81
|
+
ensureConfigDir();
|
|
72
82
|
const lines: string[] = [
|
|
73
83
|
"# apple-notes-mcp configuration",
|
|
74
84
|
"# Generated by setup wizard",
|
|
@@ -114,80 +124,29 @@ function writeEnvFile(config: Config): void {
|
|
|
114
124
|
fs.writeFileSync(ENV_FILE, lines.join("\n") + "\n");
|
|
115
125
|
}
|
|
116
126
|
|
|
117
|
-
/**
|
|
118
|
-
* Read Claude Code config if it exists
|
|
119
|
-
*/
|
|
120
|
-
function readClaudeConfig(): Record<string, unknown> | null {
|
|
121
|
-
if (!fs.existsSync(CLAUDE_CONFIG_PATH)) {
|
|
122
|
-
return null;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
try {
|
|
126
|
-
const content = fs.readFileSync(CLAUDE_CONFIG_PATH, "utf-8");
|
|
127
|
-
return JSON.parse(content);
|
|
128
|
-
} catch (error) {
|
|
129
|
-
// Config doesn't exist or is invalid JSON
|
|
130
|
-
if (process.env.DEBUG === "true") {
|
|
131
|
-
console.error("[SETUP] Could not read Claude config:", error);
|
|
132
|
-
}
|
|
133
|
-
return null;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
127
|
/**
|
|
138
128
|
* Add MCP server to Claude Code config
|
|
139
129
|
*/
|
|
140
130
|
function addToClaudeConfig(): boolean {
|
|
141
|
-
const
|
|
142
|
-
const serverEntry = {
|
|
143
|
-
command: "bun",
|
|
144
|
-
args: ["run", path.join(projectPath, "src", "index.ts")],
|
|
145
|
-
env: {},
|
|
146
|
-
};
|
|
131
|
+
const entry = getClaudeConfigEntry();
|
|
147
132
|
|
|
148
|
-
|
|
133
|
+
// Check for install method change
|
|
134
|
+
const existingMethod = getExistingInstallMethod();
|
|
135
|
+
const currentMethod = isNpmInstall() ? "npm" : "source";
|
|
149
136
|
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
config = {
|
|
153
|
-
mcpServers: {
|
|
154
|
-
"apple-notes": serverEntry,
|
|
155
|
-
},
|
|
156
|
-
};
|
|
157
|
-
} else {
|
|
158
|
-
// Add to existing config
|
|
159
|
-
const mcpServers = (config.mcpServers || {}) as Record<string, unknown>;
|
|
160
|
-
mcpServers["apple-notes"] = serverEntry;
|
|
161
|
-
config.mcpServers = mcpServers;
|
|
137
|
+
if (existingMethod && existingMethod !== currentMethod) {
|
|
138
|
+
p.log.info(`Updating Claude config from ${existingMethod} to ${currentMethod} installation`);
|
|
162
139
|
}
|
|
163
140
|
|
|
164
|
-
|
|
165
|
-
fs.writeFileSync(CLAUDE_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n");
|
|
166
|
-
return true;
|
|
167
|
-
} catch (error) {
|
|
168
|
-
if (process.env.DEBUG === "true") {
|
|
169
|
-
console.error("[SETUP] Failed to write Claude config:", error);
|
|
170
|
-
}
|
|
171
|
-
return false;
|
|
172
|
-
}
|
|
141
|
+
return writeClaudeConfig(entry);
|
|
173
142
|
}
|
|
174
143
|
|
|
175
144
|
/**
|
|
176
145
|
* Generate config snippet for manual setup
|
|
177
146
|
*/
|
|
178
147
|
function getConfigSnippet(): string {
|
|
179
|
-
const
|
|
180
|
-
return JSON.stringify(
|
|
181
|
-
{
|
|
182
|
-
"apple-notes": {
|
|
183
|
-
command: "bun",
|
|
184
|
-
args: ["run", path.join(projectPath, "src", "index.ts")],
|
|
185
|
-
env: {},
|
|
186
|
-
},
|
|
187
|
-
},
|
|
188
|
-
null,
|
|
189
|
-
2
|
|
190
|
-
);
|
|
148
|
+
const entry = getClaudeConfigEntry();
|
|
149
|
+
return JSON.stringify({ "apple-notes": entry }, null, 2);
|
|
191
150
|
}
|
|
192
151
|
|
|
193
152
|
/**
|
|
@@ -217,6 +176,7 @@ async function downloadLocalModel(): Promise<void> {
|
|
|
217
176
|
* Main setup wizard
|
|
218
177
|
*/
|
|
219
178
|
async function main(): Promise<void> {
|
|
179
|
+
checkBunRuntime();
|
|
220
180
|
console.clear();
|
|
221
181
|
|
|
222
182
|
p.intro("apple-notes-mcp Setup Wizard");
|
|
@@ -233,6 +193,25 @@ async function main(): Promise<void> {
|
|
|
233
193
|
);
|
|
234
194
|
}
|
|
235
195
|
|
|
196
|
+
// Check for legacy config migration
|
|
197
|
+
if (hasLegacyConfig() && !hasConfig()) {
|
|
198
|
+
const migrate = await p.confirm({
|
|
199
|
+
message: "Found config in project directory. Migrate to ~/.apple-notes-mcp/?",
|
|
200
|
+
initialValue: true,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
if (p.isCancel(migrate)) {
|
|
204
|
+
p.cancel("Setup cancelled.");
|
|
205
|
+
process.exit(0);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (migrate) {
|
|
209
|
+
ensureConfigDir();
|
|
210
|
+
fs.copyFileSync(getLegacyEnvPath(), getEnvPath());
|
|
211
|
+
p.log.success("Config migrated to ~/.apple-notes-mcp/.env");
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
236
215
|
// Provider selection
|
|
237
216
|
const provider = await p.select({
|
|
238
217
|
message: "Which embedding provider would you like to use?",
|
package/src/types/index.ts
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* Contains the full content of the note.
|
|
8
8
|
*/
|
|
9
9
|
export interface DBSearchResult {
|
|
10
|
+
/** Apple Notes unique identifier */
|
|
11
|
+
id?: string;
|
|
10
12
|
/** Note title */
|
|
11
13
|
title: string;
|
|
12
14
|
/** Folder containing the note */
|
|
@@ -24,6 +26,8 @@ export interface DBSearchResult {
|
|
|
24
26
|
* Contains a preview instead of full content by default.
|
|
25
27
|
*/
|
|
26
28
|
export interface SearchResult {
|
|
29
|
+
/** Apple Notes unique identifier */
|
|
30
|
+
id?: string;
|
|
27
31
|
/** Note title */
|
|
28
32
|
title: string;
|
|
29
33
|
/** Folder containing the note */
|