apple-tools-mcp 1.1.3 → 1.2.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.
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Indexer daemon vs MCP stdio runtime helpers.
3
+ * Kept separate from index.js so daemon paths can be tested without
4
+ * starting MCP stdio or loading the embedding model.
5
+ */
6
+
7
+ /**
8
+ * @param {boolean} indexerMode
9
+ * @returns {boolean}
10
+ */
11
+ export function shouldConnectMcpStdio(indexerMode) {
12
+ return !indexerMode;
13
+ }
14
+
15
+ /**
16
+ * @param {boolean} indexerMode
17
+ * @returns {boolean}
18
+ */
19
+ export function shouldExitOnStdinClose(indexerMode) {
20
+ return !indexerMode;
21
+ }
22
+
23
+ /**
24
+ * Bind stdin `close` → callback only for MCP stdio clients.
25
+ * Indexer daemons must ignore stdin close (LaunchAgent often uses /dev/null).
26
+ *
27
+ * @param {{ on: (event: string, handler: () => void) => void }} stdin
28
+ * @param {boolean} indexerMode
29
+ * @param {() => void} onClose
30
+ * @returns {{ bound: boolean }}
31
+ */
32
+ export function bindStdinCloseExit(stdin, indexerMode, onClose) {
33
+ if (!shouldExitOnStdinClose(indexerMode)) {
34
+ return { bound: false };
35
+ }
36
+ stdin.on("close", onClose);
37
+ return { bound: true };
38
+ }
39
+
40
+ /**
41
+ * Start one index cycle, or skip if a cycle is already running.
42
+ *
43
+ * @param {boolean} indexingInProgress
44
+ * @param {(msg: string) => void} [log]
45
+ * @returns {{ started: boolean, indexingInProgress: boolean }}
46
+ */
47
+ export function beginIndexCycle(indexingInProgress, log = (msg) => console.error(msg)) {
48
+ if (indexingInProgress) {
49
+ log("Indexing already in progress, skipping cycle");
50
+ return { started: false, indexingInProgress: true };
51
+ }
52
+ return { started: true, indexingInProgress: true };
53
+ }
54
+
55
+ /**
56
+ * After a cycle: daemon keeps the lock; MCP local-fallback releases it.
57
+ *
58
+ * @param {{
59
+ * success: boolean,
60
+ * indexerMode: boolean,
61
+ * cycleEndFlags: (success: boolean) => {
62
+ * indexingInProgress: boolean,
63
+ * sessionIndexComplete: boolean,
64
+ * ownsIndexLock: boolean,
65
+ * isFirstEverRun?: boolean
66
+ * },
67
+ * releaseLock: () => void
68
+ * }} args
69
+ */
70
+ export function applyIndexerCycleEnd({ success, indexerMode, cycleEndFlags, releaseLock }) {
71
+ const flags = cycleEndFlags(success);
72
+ if (indexerMode) {
73
+ return {
74
+ indexingInProgress: flags.indexingInProgress,
75
+ sessionIndexComplete: flags.sessionIndexComplete,
76
+ ownsIndexLock: true,
77
+ isFirstEverRun: flags.isFirstEverRun,
78
+ released: false
79
+ };
80
+ }
81
+ releaseLock();
82
+ return {
83
+ indexingInProgress: flags.indexingInProgress,
84
+ sessionIndexComplete: flags.sessionIndexComplete,
85
+ ownsIndexLock: flags.ownsIndexLock,
86
+ isFirstEverRun: flags.isFirstEverRun,
87
+ released: true
88
+ };
89
+ }
90
+
91
+ /**
92
+ * MCP stdio startup: index locally only when the lock is free.
93
+ *
94
+ * @param {() => boolean} acquireLock
95
+ * @returns {{ startBackground: boolean, ownsIndexLock: boolean, startHeartbeat: boolean, reason: "local-fallback" | "lock-held" }}
96
+ */
97
+ export function mcpIndexingStartup(acquireLock) {
98
+ if (!acquireLock()) {
99
+ return { startBackground: false, ownsIndexLock: false, startHeartbeat: false, reason: "lock-held" };
100
+ }
101
+ return { startBackground: true, ownsIndexLock: true, startHeartbeat: true, reason: "local-fallback" };
102
+ }
103
+
104
+ /**
105
+ * Shared path once this process owns indexer.lock: heartbeat + background cycles.
106
+ * MCP local-fallback must use this too, not only the daemon.
107
+ *
108
+ * @param {{ startHeartbeat: () => void, startBackground: () => void }} steps
109
+ */
110
+ export function beginOwnedIndexing(steps) {
111
+ steps.startHeartbeat();
112
+ steps.startBackground();
113
+ }
114
+
115
+ /**
116
+ * Retry until the daemon owns indexer.lock, then run onAcquired.
117
+ *
118
+ * @param {() => boolean} acquireLock
119
+ * @param {{
120
+ * retryMs: number,
121
+ * onAcquired: () => void,
122
+ * log?: (msg: string) => void,
123
+ * setTimeoutFn?: typeof setTimeout
124
+ * }} options
125
+ */
126
+ export function waitForIndexerLock(acquireLock, options) {
127
+ const retryMs = options.retryMs;
128
+ const onAcquired = options.onAcquired;
129
+ const log = options.log || ((msg) => console.error(msg));
130
+ const setTimeoutFn = options.setTimeoutFn || setTimeout;
131
+
132
+ const tryAcquire = () => {
133
+ if (acquireLock()) {
134
+ log("Indexer daemon acquired indexer.lock");
135
+ onAcquired();
136
+ return;
137
+ }
138
+ log("Indexer daemon waiting for indexer.lock...");
139
+ setTimeoutFn(tryAcquire, retryMs);
140
+ };
141
+ tryAcquire();
142
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Process mode detection for apple-tools-mcp.
3
+ *
4
+ * Canonical indexer entrypoint: `node index.js --mode=indexer`
5
+ * Convenience bin: `apple-tools-indexer` (same file; detected via argv[1]).
6
+ * MCP stdio remains the default when neither is present.
7
+ */
8
+
9
+ import path from "path";
10
+
11
+ /**
12
+ * @param {string[]} [argv=process.argv]
13
+ * @returns {boolean}
14
+ */
15
+ export function isIndexerMode(argv = process.argv) {
16
+ if (!Array.isArray(argv) || argv.length === 0) {
17
+ return false;
18
+ }
19
+ if (argv.includes("--mode=indexer")) {
20
+ return true;
21
+ }
22
+ const modeIdx = argv.indexOf("--mode");
23
+ if (modeIdx !== -1 && argv[modeIdx + 1] === "indexer") {
24
+ return true;
25
+ }
26
+ const entry = argv[1] ? path.basename(argv[1]) : "";
27
+ return entry === "apple-tools-indexer";
28
+ }
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "apple-tools-mcp",
3
- "version": "1.1.3",
3
+ "version": "1.2.0",
4
4
  "description": "MCP server for semantic search across Apple Mail, Messages, and Calendar",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "bin": {
8
- "apple-tools-mcp": "./index.js"
8
+ "apple-tools-mcp": "./index.js",
9
+ "apple-tools-indexer": "./index.js"
9
10
  },
10
11
  "author": "Peter Coates",
11
12
  "license": "MIT",
@@ -27,8 +28,6 @@
27
28
  "messages",
28
29
  "imessage",
29
30
  "semantic-search",
30
- "claude",
31
- "anthropic",
32
31
  "macos"
33
32
  ],
34
33
  "engines": {
@@ -52,6 +51,7 @@
52
51
  ],
53
52
  "scripts": {
54
53
  "start": "node index.js",
54
+ "indexer": "node index.js --mode=indexer",
55
55
  "build-index": "node -e \"import('./indexer.js').then(i=>i.rebuildIndex()).catch(e=>{console.error(e.message);process.exit(1)})\"",
56
56
  "audit": "node scripts/audit-index.js",
57
57
  "test": "vitest run",