@laylzj777/my-apple-apple-eventkit 1.1.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 (123) hide show
  1. package/CHANGELOG.md +575 -0
  2. package/LICENSE +21 -0
  3. package/README.md +304 -0
  4. package/README.zh-CN.md +270 -0
  5. package/bin/event +0 -0
  6. package/bin/event-disclaim +0 -0
  7. package/dist/bin/apple-calendar.d.ts +6 -0
  8. package/dist/bin/apple-calendar.js +8 -0
  9. package/dist/bin/apple-calendar.js.map +1 -0
  10. package/dist/bin/apple-reminders.d.ts +7 -0
  11. package/dist/bin/apple-reminders.js +9 -0
  12. package/dist/bin/apple-reminders.js.map +1 -0
  13. package/dist/bootstrap.d.ts +11 -0
  14. package/dist/bootstrap.js +44 -0
  15. package/dist/bootstrap.js.map +1 -0
  16. package/dist/index.d.ts +10 -0
  17. package/dist/index.js +12 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/server/handlers.d.ts +12 -0
  20. package/dist/server/handlers.js +58 -0
  21. package/dist/server/handlers.js.map +1 -0
  22. package/dist/server/mode.d.ts +19 -0
  23. package/dist/server/mode.js +47 -0
  24. package/dist/server/mode.js.map +1 -0
  25. package/dist/server/promptAbstractions.d.ts +61 -0
  26. package/dist/server/promptAbstractions.js +140 -0
  27. package/dist/server/promptAbstractions.js.map +1 -0
  28. package/dist/server/prompts.d.ts +13 -0
  29. package/dist/server/prompts.js +461 -0
  30. package/dist/server/prompts.js.map +1 -0
  31. package/dist/server/server.d.ts +61 -0
  32. package/dist/server/server.js +112 -0
  33. package/dist/server/server.js.map +1 -0
  34. package/dist/tools/definitions.d.ts +9 -0
  35. package/dist/tools/definitions.js +272 -0
  36. package/dist/tools/definitions.js.map +1 -0
  37. package/dist/tools/handlers/calendarHandlers.d.ts +11 -0
  38. package/dist/tools/handlers/calendarHandlers.js +151 -0
  39. package/dist/tools/handlers/calendarHandlers.js.map +1 -0
  40. package/dist/tools/handlers/formatters.d.ts +24 -0
  41. package/dist/tools/handlers/formatters.js +105 -0
  42. package/dist/tools/handlers/formatters.js.map +1 -0
  43. package/dist/tools/handlers/index.d.ts +8 -0
  44. package/dist/tools/handlers/index.js +9 -0
  45. package/dist/tools/handlers/index.js.map +1 -0
  46. package/dist/tools/handlers/listHandlers.d.ts +10 -0
  47. package/dist/tools/handlers/listHandlers.js +47 -0
  48. package/dist/tools/handlers/listHandlers.js.map +1 -0
  49. package/dist/tools/handlers/reminderHandlers.d.ts +10 -0
  50. package/dist/tools/handlers/reminderHandlers.js +219 -0
  51. package/dist/tools/handlers/reminderHandlers.js.map +1 -0
  52. package/dist/tools/handlers/shared.d.ts +28 -0
  53. package/dist/tools/handlers/shared.js +57 -0
  54. package/dist/tools/handlers/shared.js.map +1 -0
  55. package/dist/tools/handlers/subtaskHandlers.d.ts +12 -0
  56. package/dist/tools/handlers/subtaskHandlers.js +113 -0
  57. package/dist/tools/handlers/subtaskHandlers.js.map +1 -0
  58. package/dist/tools/index.d.ts +10 -0
  59. package/dist/tools/index.js +74 -0
  60. package/dist/tools/index.js.map +1 -0
  61. package/dist/types/index.d.ts +263 -0
  62. package/dist/types/index.js +42 -0
  63. package/dist/types/index.js.map +1 -0
  64. package/dist/types/prompts.d.ts +88 -0
  65. package/dist/types/prompts.js +6 -0
  66. package/dist/types/prompts.js.map +1 -0
  67. package/dist/types/repository.d.ts +197 -0
  68. package/dist/types/repository.js +14 -0
  69. package/dist/types/repository.js.map +1 -0
  70. package/dist/utils/binaryValidator.d.ts +52 -0
  71. package/dist/utils/binaryValidator.js +185 -0
  72. package/dist/utils/binaryValidator.js.map +1 -0
  73. package/dist/utils/calendarRepository.d.ts +49 -0
  74. package/dist/utils/calendarRepository.js +226 -0
  75. package/dist/utils/calendarRepository.js.map +1 -0
  76. package/dist/utils/constants.d.ts +79 -0
  77. package/dist/utils/constants.js +80 -0
  78. package/dist/utils/constants.js.map +1 -0
  79. package/dist/utils/dateFiltering.d.ts +41 -0
  80. package/dist/utils/dateFiltering.js +129 -0
  81. package/dist/utils/dateFiltering.js.map +1 -0
  82. package/dist/utils/dateUtils.d.ts +27 -0
  83. package/dist/utils/dateUtils.js +71 -0
  84. package/dist/utils/dateUtils.js.map +1 -0
  85. package/dist/utils/errorHandling.d.ts +27 -0
  86. package/dist/utils/errorHandling.js +88 -0
  87. package/dist/utils/errorHandling.js.map +1 -0
  88. package/dist/utils/eventCli.d.ts +40 -0
  89. package/dist/utils/eventCli.js +294 -0
  90. package/dist/utils/eventCli.js.map +1 -0
  91. package/dist/utils/helpers.d.ts +35 -0
  92. package/dist/utils/helpers.js +61 -0
  93. package/dist/utils/helpers.js.map +1 -0
  94. package/dist/utils/looseDateParser.d.ts +19 -0
  95. package/dist/utils/looseDateParser.js +149 -0
  96. package/dist/utils/looseDateParser.js.map +1 -0
  97. package/dist/utils/projectUtils.d.ts +13 -0
  98. package/dist/utils/projectUtils.js +92 -0
  99. package/dist/utils/projectUtils.js.map +1 -0
  100. package/dist/utils/reminderDateParser.d.ts +8 -0
  101. package/dist/utils/reminderDateParser.js +89 -0
  102. package/dist/utils/reminderDateParser.js.map +1 -0
  103. package/dist/utils/reminderRepository.d.ts +33 -0
  104. package/dist/utils/reminderRepository.js +262 -0
  105. package/dist/utils/reminderRepository.js.map +1 -0
  106. package/dist/utils/subtaskUtils.d.ts +103 -0
  107. package/dist/utils/subtaskUtils.js +253 -0
  108. package/dist/utils/subtaskUtils.js.map +1 -0
  109. package/dist/utils/tagUtils.d.ts +58 -0
  110. package/dist/utils/tagUtils.js +177 -0
  111. package/dist/utils/tagUtils.js.map +1 -0
  112. package/dist/utils/timeHelpers.d.ts +40 -0
  113. package/dist/utils/timeHelpers.js +136 -0
  114. package/dist/utils/timeHelpers.js.map +1 -0
  115. package/dist/validation/schemas.d.ts +385 -0
  116. package/dist/validation/schemas.js +579 -0
  117. package/dist/validation/schemas.js.map +1 -0
  118. package/package.json +96 -0
  119. package/scripts/build-event.mjs +513 -0
  120. package/scripts/disclaim.c +69 -0
  121. package/scripts/event-Info.plist +29 -0
  122. package/scripts/event.entitlements +10 -0
  123. package/scripts/postinstall.mjs +91 -0
package/package.json ADDED
@@ -0,0 +1,96 @@
1
+ {
2
+ "name": "@laylzj777/my-apple-apple-eventkit",
3
+ "version": "1.1.0",
4
+ "description": "Apple Calendar & Reminders MCP server with a prebuilt EventKit CLI and lenient date-input normalization. Based on mcp-server-apple-events (FradSer, MIT).",
5
+ "author": "my-apple-apple-eventkit contributors",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/lay777/my-apple-apple-eventkit.git"
10
+ },
11
+ "keywords": [
12
+ "mcp",
13
+ "apple",
14
+ "reminders",
15
+ "calendar",
16
+ "events",
17
+ "automation"
18
+ ],
19
+ "publishConfig": {
20
+ "registry": "https://registry.npmjs.org",
21
+ "access": "public"
22
+ },
23
+ "type": "module",
24
+ "main": "dist/index.js",
25
+ "module": "dist/index.js",
26
+ "bin": {
27
+ "apple-calendar-mcp": "./dist/bin/apple-calendar.js",
28
+ "apple-reminders-mcp": "./dist/bin/apple-reminders.js"
29
+ },
30
+ "files": [
31
+ "bin/event",
32
+ "bin/event-disclaim",
33
+ "dist/",
34
+ "scripts/postinstall.mjs",
35
+ "scripts/build-event.mjs",
36
+ "scripts/event-Info.plist",
37
+ "scripts/event.entitlements",
38
+ "scripts/disclaim.c",
39
+ "CHANGELOG.md"
40
+ ],
41
+ "engines": {
42
+ "node": ">=20.0.0"
43
+ },
44
+ "scripts": {
45
+ "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
46
+ "build:ts": "tsc -p tsconfig.build.json",
47
+ "build:event": "node scripts/build-event.mjs",
48
+ "build": "pnpm run clean && pnpm run build:ts && pnpm run build:event",
49
+ "notarize": "node scripts/notarize.mjs",
50
+ "build:release": "pnpm run clean && pnpm run build:ts && pnpm run build:event && pnpm run notarize",
51
+ "postinstall": "node scripts/postinstall.mjs",
52
+ "test": "NODE_NO_WARNINGS=1 jest",
53
+ "test:ci": "NODE_NO_WARNINGS=1 jest --coverage",
54
+ "lint": "pnpm exec biome check --write --unsafe && pnpm exec tsc --noEmit --project tsconfig.json",
55
+ "check": "pnpm run lint && pnpm run test:ci"
56
+ },
57
+ "dependencies": {
58
+ "@modelcontextprotocol/sdk": "^1.29.0",
59
+ "exit-on-epipe": "^1.0.1",
60
+ "zod": "^4.4.3"
61
+ },
62
+ "devDependencies": {
63
+ "@biomejs/biome": "2.5.2",
64
+ "@swc/core": "^1.15.43",
65
+ "@swc/jest": "^0.2.39",
66
+ "@types/jest": "^30.0.0",
67
+ "@types/node": "^26.1.0",
68
+ "jest": "^30.4.2",
69
+ "typescript": "^6.0.3"
70
+ },
71
+ "peerDependencies": {
72
+ "typescript": ">=5.0.0"
73
+ },
74
+ "pnpm": {
75
+ "overrides": {
76
+ "test-exclude": "^8.0.0",
77
+ "glob": "^13.0.6"
78
+ },
79
+ "onlyBuiltDependencies": [
80
+ ",",
81
+ "b",
82
+ "d",
83
+ "e",
84
+ "i",
85
+ "k",
86
+ "l",
87
+ "o",
88
+ "r",
89
+ "s",
90
+ "u",
91
+ "unrs-resolver",
92
+ "w"
93
+ ]
94
+ },
95
+ "packageManager": "pnpm@10.28.2+sha512.41872f037ad22f7348e3b1debbaf7e867cfd448f2726d9cf74c08f19507c31d2c8e7a11525b983febc2df640b5438dee6023ebb1f84ed43cc2d654d2bc326264"
96
+ }
@@ -0,0 +1,513 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promises as fs } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { promisify } from 'node:util';
6
+
7
+ const execFileAsync = promisify(execFile);
8
+
9
+ // Use `execFile` with an argv array so paths containing shell metacharacters
10
+ // (spaces, `$`, backticks) reach the tool intact.
11
+ async function run(command, args, options = {}) {
12
+ try {
13
+ const { stdout, stderr } = await execFileAsync(command, args, options);
14
+ return { stdout: stdout ?? '', stderr: stderr ?? '' };
15
+ } catch (error) {
16
+ error.stdout = error.stdout ?? '';
17
+ error.stderr = error.stderr ?? '';
18
+ throw error;
19
+ }
20
+ }
21
+
22
+ // macOS 26+ SDKs ship a Foundation.swiftinterface that older swiftc cannot
23
+ // parse, surfacing as `could not build module 'Foundation'` /
24
+ // `SDK is not supported by the compiler` (see issue #85). The vendored
25
+ // `event` package requires Swift 6.2+ (swift-tools-version in its
26
+ // Package.swift) and is compiled by the host's swiftc, so we still need the
27
+ // same minimum-version guard.
28
+ const MIN_SWIFT_MAJOR_FOR_MACOS_26 = 6;
29
+ const MIN_SWIFT_MINOR_FOR_MACOS_26 = 3;
30
+
31
+ async function getSwiftVersion() {
32
+ try {
33
+ const { stdout } = await run('xcrun', ['swiftc', '--version']);
34
+ const match = stdout.match(/Apple Swift version (\d+)\.(\d+)(?:\.(\d+))?/);
35
+ if (!match) return null;
36
+ return {
37
+ major: Number.parseInt(match[1], 10),
38
+ minor: Number.parseInt(match[2], 10),
39
+ raw: match[0].replace(/^Apple Swift version /, ''),
40
+ };
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+
46
+ async function getSdkVersion() {
47
+ try {
48
+ const { stdout } = await run('xcrun', ['--show-sdk-version']);
49
+ const trimmed = stdout.trim();
50
+ const [maj, min] = trimmed.split('.');
51
+ return {
52
+ major: Number.parseInt(maj, 10),
53
+ minor: Number.parseInt(min ?? '0', 10),
54
+ raw: trimmed,
55
+ };
56
+ } catch {
57
+ return null;
58
+ }
59
+ }
60
+
61
+ function isSdkTooNewForSwift(swift, sdk) {
62
+ if (!swift || !sdk) return false;
63
+ if (sdk.major < 26) return false;
64
+ if (swift.major > MIN_SWIFT_MAJOR_FOR_MACOS_26) return false;
65
+ if (swift.major < MIN_SWIFT_MAJOR_FOR_MACOS_26) return true;
66
+ return swift.minor < MIN_SWIFT_MINOR_FOR_MACOS_26;
67
+ }
68
+
69
+ function printIncompatibilityRemediation(swift, sdk) {
70
+ const swiftStr = swift ? swift.raw : 'unknown';
71
+ const sdkStr = sdk ? sdk.raw : 'unknown';
72
+ console.error(
73
+ [
74
+ '',
75
+ `Error: Swift toolchain ${swiftStr} cannot build against the macOS ${sdkStr} SDK.`,
76
+ `The macOS 26+ SDK requires Swift ${MIN_SWIFT_MAJOR_FOR_MACOS_26}.${MIN_SWIFT_MINOR_FOR_MACOS_26} or newer; older swiftc fails with`,
77
+ `"could not build module 'Foundation'" because the SDK's Foundation.swiftinterface`,
78
+ 'uses availability markers older compilers do not understand.',
79
+ '',
80
+ 'See: https://github.com/FradSer/mcp-server-apple-events/issues/85',
81
+ '',
82
+ 'Fixes (pick one):',
83
+ ' 1. Install Xcode 26.x from the App Store (ships Swift 6.3+).',
84
+ ' 2. Update Command Line Tools to a version that ships Swift 6.3+:',
85
+ ' softwareupdate --list',
86
+ ' sudo softwareupdate -i "Command Line Tools for Xcode-<latest>"',
87
+ ' 3. If both Xcode and Command Line Tools are installed, point xcode-select',
88
+ ' at the full Xcode that has the matching toolchain:',
89
+ ' sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer',
90
+ '',
91
+ ].join('\n'),
92
+ );
93
+ }
94
+
95
+ function looksLikeFoundationModuleMismatch(stderr) {
96
+ if (!stderr) return false;
97
+ return (
98
+ /could not build module 'Foundation'/.test(stderr) ||
99
+ /SDK is not supported by the compiler/.test(stderr) ||
100
+ /module compiled with Swift [\d.]+ cannot be imported/.test(stderr)
101
+ );
102
+ }
103
+
104
+ /**
105
+ * Resolves the code-signing identity to use.
106
+ *
107
+ * Priority order:
108
+ * 1. APPLE_SIGNING_IDENTITY env var (explicit override)
109
+ * 2. First "Developer ID Application:" cert found in the login keychain
110
+ * 3. Ad-hoc ("-") with a warning
111
+ *
112
+ * A Developer ID signature makes `event` the TCC-responsible process
113
+ * regardless of which parent process spawned it, fixing Calendar permission
114
+ * dialogs on OCLP Sequoia and macOS 26+.
115
+ */
116
+ async function resolveSigningIdentity() {
117
+ if (process.env.APPLE_SIGNING_IDENTITY) {
118
+ return process.env.APPLE_SIGNING_IDENTITY;
119
+ }
120
+
121
+ try {
122
+ const { stdout } = await run('security', [
123
+ 'find-identity',
124
+ '-v',
125
+ '-p',
126
+ 'codesigning',
127
+ ]);
128
+ const lines = stdout.split('\n');
129
+
130
+ // Prefer Developer ID Application (trusted on all Macs) over Apple Development
131
+ // (trusted only on the developer's own machine, but sufficient for local use).
132
+ for (const prefix of ['Developer ID Application:', 'Apple Development:']) {
133
+ const matches = lines.filter((line) => line.includes(prefix));
134
+ if (matches.length === 1) {
135
+ const m = matches[0].match(/"([^"]+)"/);
136
+ if (m) return m[1];
137
+ }
138
+ if (matches.length > 1) {
139
+ console.warn(`Multiple "${prefix}" certificates found in keychain.`);
140
+ console.warn(
141
+ 'Set APPLE_SIGNING_IDENTITY to the exact certificate name to avoid ambiguity.',
142
+ );
143
+ break;
144
+ }
145
+ }
146
+ } catch {
147
+ // security binary unavailable — fall through to ad-hoc
148
+ }
149
+
150
+ console.warn(
151
+ 'No Apple code-signing certificate found. Using ad-hoc signing (-).',
152
+ );
153
+ console.warn(
154
+ 'Calendar TCC permission dialogs may be suppressed on OCLP Sequoia and macOS 26+.',
155
+ );
156
+ console.warn(
157
+ 'To fix this, set APPLE_SIGNING_IDENTITY="Developer ID Application: Your Name (TEAMID)".',
158
+ );
159
+ return '-';
160
+ }
161
+
162
+ // `event`'s Package.swift declares an SSH dependency
163
+ // (git@github.com:FradSer/apple-sync-kit.git). Most build hosts have no SSH
164
+ // key registered with GitHub, so a plain `swift build` fails resolving that
165
+ // dependency. `event`'s own CI works around this by rewriting SSH GitHub
166
+ // URLs to HTTPS for the duration of the git-fetch child process (see
167
+ // FradSer/event@bf34a4c, "Configure git to fetch SSH dependencies via
168
+ // HTTPS"). Do the same here via `GIT_CONFIG_*` env vars scoped only to the
169
+ // `swift build` child processes below — this never touches the invoking
170
+ // user's global ~/.gitconfig.
171
+ const SSH_TO_HTTPS_GIT_ENV = {
172
+ GIT_CONFIG_COUNT: '1',
173
+ GIT_CONFIG_KEY_0: 'url.https://github.com/.insteadOf',
174
+ GIT_CONFIG_VALUE_0: 'git@github.com:',
175
+ };
176
+
177
+ /**
178
+ * Resolve the Info.plist used for codesigning: when APP_EVENT_DISPLAY_NAME is
179
+ * unset, use the source file as-is; when set, replace CFBundleName /
180
+ * CFBundleDisplayName with that value and write a temporary plist under bin/
181
+ * (this generated file is not part of the npm files whitelist).
182
+ */
183
+ async function resolveInfoPlist(sourceFile, outDir) {
184
+ const displayName = process.env.APP_EVENT_DISPLAY_NAME;
185
+ if (!displayName) return sourceFile;
186
+
187
+ const escaped = displayName
188
+ .replace(/&/g, '&amp;')
189
+ .replace(/</g, '&lt;')
190
+ .replace(/>/g, '&gt;');
191
+ const source = await fs.readFile(sourceFile, 'utf-8');
192
+ const patched = source
193
+ .replace(
194
+ /(<key>CFBundleName<\/key>\s*<string>)[^<]*(<\/string>)/,
195
+ `$1${escaped}$2`,
196
+ )
197
+ .replace(
198
+ /(<key>CFBundleDisplayName<\/key>\s*<string>)[^<]*(<\/string>)/,
199
+ `$1${escaped}$2`,
200
+ );
201
+ await fs.mkdir(outDir, { recursive: true });
202
+ const generated = path.join(outDir, 'event-Info.generated.plist');
203
+ await fs.writeFile(generated, patched, 'utf-8');
204
+ console.log(`Display name overridden via APP_EVENT_DISPLAY_NAME: ${displayName}`);
205
+ return generated;
206
+ }
207
+
208
+ async function main() {
209
+ console.log(
210
+ 'Building vendored `event` CLI (FradSer/event) for Apple Reminders MCP Server...',
211
+ );
212
+
213
+ if (process.platform !== 'darwin') {
214
+ console.error('Error: This project requires macOS to compile `event`.');
215
+ process.exit(1);
216
+ }
217
+
218
+ try {
219
+ await run('xcrun', ['--find', 'lipo']);
220
+ } catch (_error) {
221
+ console.error(
222
+ 'Error: lipo not found. Required for universal binary creation.',
223
+ );
224
+ console.error(
225
+ 'Please install Xcode or Xcode Command Line Tools: xcode-select --install',
226
+ );
227
+ process.exit(1);
228
+ }
229
+
230
+ // Resolve paths relative to script location, not process.cwd()
231
+ const __filename = fileURLToPath(import.meta.url);
232
+ const __dirname = path.dirname(__filename);
233
+ const projectRoot = path.resolve(__dirname, '..');
234
+ const eventPackagePath = path.join(projectRoot, 'vendor', 'event');
235
+ const eventPackageManifest = path.join(eventPackagePath, 'Package.swift');
236
+ const binDir = path.join(projectRoot, 'bin');
237
+ const outputFile = path.join(binDir, 'event');
238
+ const scriptsDir = path.join(projectRoot, 'scripts');
239
+ // The TCC prompt display name is build-time configurable: APP_EVENT_DISPLAY_NAME
240
+ // overrides CFBundleName/CFBundleDisplayName in the plist (a temp plist is
241
+ // generated for codesigning; the source file stays untouched).
242
+ // It is not runtime-configurable — the name is baked into the binary via code
243
+ // signing, which is how the system prevents permission-prompt spoofing.
244
+ const infoPlistFile = await resolveInfoPlist(
245
+ path.join(scriptsDir, 'event-Info.plist'),
246
+ binDir,
247
+ );
248
+ const entitlementsFile = path.join(scriptsDir, 'event.entitlements');
249
+ const disclaimSourceFile = path.join(scriptsDir, 'disclaim.c');
250
+ const disclaimOutputFile = path.join(binDir, 'event-disclaim');
251
+
252
+ // Run the independent pre-flight checks (toolchain probe, manifest access,
253
+ // bin dir creation) concurrently so `pnpm install`'s postinstall hook
254
+ // doesn't serialise them.
255
+ const [swift, sdk, manifestExists] = await Promise.all([
256
+ getSwiftVersion(),
257
+ getSdkVersion(),
258
+ fs.access(eventPackageManifest).then(
259
+ () => true,
260
+ () => false,
261
+ ),
262
+ fs.mkdir(binDir, { recursive: true }),
263
+ ]);
264
+
265
+ if (!swift) {
266
+ console.error('Error: Swift compiler (swiftc) not found via xcrun.');
267
+ console.error(
268
+ 'Please install Xcode or Xcode Command Line Tools: xcode-select --install',
269
+ );
270
+ process.exit(1);
271
+ }
272
+ if (isSdkTooNewForSwift(swift, sdk)) {
273
+ printIncompatibilityRemediation(swift, sdk);
274
+ process.exit(1);
275
+ }
276
+ if (!manifestExists) {
277
+ console.error(
278
+ `Error: vendor/event/Package.swift not found at ${eventPackageManifest}`,
279
+ );
280
+ console.error(
281
+ 'The `event` source tree is vendored as a git submodule. Run:',
282
+ );
283
+ console.error(' git submodule update --init --recursive');
284
+ process.exit(1);
285
+ }
286
+
287
+ console.log(
288
+ `Compiling vendor/event in release mode (swift ${swift.raw}, sdk ${sdk?.raw ?? 'unknown'})...`,
289
+ );
290
+
291
+ // Build a universal (fat) binary containing both arm64 and x86_64 slices.
292
+ // Modern SwiftPM (as of this Swift toolchain) has no public `--arch` flag
293
+ // that emits a universal Mach-O from a single `swift build` invocation, so
294
+ // each slice is built separately — pinned to `event`'s own deployment
295
+ // target (`platforms: [.macOS(.v14)]` in vendor/event/Package.swift) via
296
+ // `--triple`, with a dedicated `--scratch-path` so the two builds don't
297
+ // clobber each other's `.build` cache — then merged with `lipo`. This
298
+ // mirrors what the old EventKitCLI build (`swiftc -target ...` per slice +
299
+ // `lipo -create`) did for the same reason.
300
+ const gitEnv = { ...process.env, ...SSH_TO_HTTPS_GIT_ENV };
301
+ const slices = [
302
+ {
303
+ triple: 'arm64-apple-macosx14.0',
304
+ scratchPath: path.join(eventPackagePath, '.build-arm64'),
305
+ label: 'arm64',
306
+ },
307
+ {
308
+ triple: 'x86_64-apple-macosx14.0',
309
+ scratchPath: path.join(eventPackagePath, '.build-x86_64'),
310
+ label: 'x86_64',
311
+ },
312
+ ];
313
+
314
+ let builtBinaries;
315
+ try {
316
+ // Ask SwiftPM for the actual release output directory per slice instead
317
+ // of assuming a `.build/<triple>/release` layout, since that convention
318
+ // is not a stable public contract.
319
+ builtBinaries = await Promise.all(
320
+ slices.map(async (s) => {
321
+ const { stdout: binPathOut } = await run(
322
+ 'swift',
323
+ [
324
+ 'build',
325
+ '--show-bin-path',
326
+ '-c',
327
+ 'release',
328
+ '--package-path',
329
+ eventPackagePath,
330
+ '--scratch-path',
331
+ s.scratchPath,
332
+ '--triple',
333
+ s.triple,
334
+ ],
335
+ { env: gitEnv },
336
+ );
337
+ return path.join(binPathOut.trim(), 'event');
338
+ }),
339
+ );
340
+
341
+ // SwiftPM's incremental build does not track the -sectcreate Info.plist
342
+ // as a link input, so editing scripts/event-Info.plist alone would leave
343
+ // a stale plist embedded in the cached executable. Deleting the slice
344
+ // outputs forces a relink (object files stay cached — this costs seconds,
345
+ // not a rebuild).
346
+ await Promise.all(
347
+ slices.map((_, i) => fs.rm(builtBinaries[i], { force: true })),
348
+ );
349
+
350
+ // Slices are independent — build in parallel to roughly halve build time.
351
+ const results = await Promise.all(
352
+ slices.map((s) =>
353
+ run(
354
+ 'swift',
355
+ [
356
+ 'build',
357
+ '-c',
358
+ 'release',
359
+ '--package-path',
360
+ eventPackagePath,
361
+ '--scratch-path',
362
+ s.scratchPath,
363
+ '--triple',
364
+ s.triple,
365
+ // Embed the Info.plist (bundle id + EventKit usage strings) into
366
+ // the executable so TCC can prompt for `event` itself when the
367
+ // binary is spawned disclaimed (see scripts/disclaim.c). A bare
368
+ // Mach-O has no bundle, so the plist rides in the __info_plist
369
+ // section of __TEXT — the documented location TCC reads for
370
+ // command-line tools.
371
+ '-Xlinker',
372
+ '-sectcreate',
373
+ '-Xlinker',
374
+ '__TEXT',
375
+ '-Xlinker',
376
+ '__info_plist',
377
+ '-Xlinker',
378
+ infoPlistFile,
379
+ ],
380
+ { env: gitEnv },
381
+ ),
382
+ ),
383
+ );
384
+ results.forEach(({ stdout, stderr }, i) => {
385
+ if (stderr) console.warn(`${slices[i].label} build warnings:\n${stderr}`);
386
+ if (stdout) console.log(stdout);
387
+ });
388
+
389
+ await run('xcrun', [
390
+ 'lipo',
391
+ '-create',
392
+ '-output',
393
+ outputFile,
394
+ ...builtBinaries,
395
+ ]);
396
+
397
+ console.log(
398
+ `Compilation successful! Universal binary saved to ${outputFile}`,
399
+ );
400
+ } catch (error) {
401
+ console.error('Compilation failed!');
402
+ const stderr = error?.stderr ?? '';
403
+ if (looksLikeFoundationModuleMismatch(stderr)) {
404
+ printIncompatibilityRemediation(swift, sdk);
405
+ }
406
+ console.error(error);
407
+ process.exit(1);
408
+ }
409
+
410
+ await fs.chmod(outputFile, '755');
411
+ console.log('Binary is now executable.');
412
+
413
+ // Compile the disclaim shim (scripts/disclaim.c). It posix_spawns `event`
414
+ // with TCC responsibility disclaimed, so the Reminders/Calendar permission
415
+ // prompt is attributed to `event` itself (which ships the usage strings
416
+ // above) instead of the desktop MCP client that launched the server —
417
+ // clients like Codex Desktop declare no EventKit usage strings, and TCC
418
+ // refuses the request outright when they are the responsible process
419
+ // (issue #93). clang emits a universal Mach-O directly from repeated
420
+ // -arch flags; the deployment target matches `event`'s macosx14.0 triples.
421
+ try {
422
+ await run('xcrun', [
423
+ 'clang',
424
+ '-O2',
425
+ '-mmacosx-version-min=14.0',
426
+ '-arch',
427
+ 'arm64',
428
+ '-arch',
429
+ 'x86_64',
430
+ '-o',
431
+ disclaimOutputFile,
432
+ disclaimSourceFile,
433
+ ]);
434
+ await fs.chmod(disclaimOutputFile, '755');
435
+ console.log(`Disclaim shim saved to ${disclaimOutputFile}`);
436
+ } catch (error) {
437
+ console.error('Disclaim shim compilation failed!');
438
+ console.error(error);
439
+ process.exit(1);
440
+ }
441
+
442
+ const signingIdentity = await resolveSigningIdentity();
443
+ const isAdHoc = signingIdentity === '-';
444
+
445
+ console.log(
446
+ isAdHoc
447
+ ? 'Signing with ad-hoc identity...'
448
+ : `Signing with Developer ID: ${signingIdentity}`,
449
+ );
450
+
451
+ // --options runtime: Hardened Runtime, required for notarization and for
452
+ // macOS 26+ TCC prompting policy.
453
+ // --timestamp: secure timestamp from Apple CA; included for Developer ID
454
+ // signatures to ensure long-term validity; omitted for ad-hoc (no CA).
455
+ // --entitlements (event only): hardened-runtime binaries must hold the
456
+ // com.apple.security.personal-information.{calendars,reminders}
457
+ // entitlements to be allowed to request EventKit access when they are
458
+ // the TCC-responsible process (which they are when spawned disclaimed).
459
+ // --identifier (event only): pinned to the CFBundleIdentifier in
460
+ // scripts/event-Info.plist so the TCC grant is recorded under a stable,
461
+ // deterministic identity across rebuilds.
462
+ const signBinary = async (file, extraArgs) => {
463
+ const codesignArgs = [
464
+ '--force',
465
+ '--sign',
466
+ signingIdentity,
467
+ '--options',
468
+ 'runtime',
469
+ ...(isAdHoc ? [] : ['--timestamp']),
470
+ ...extraArgs,
471
+ file,
472
+ ];
473
+ const { stdout: csOut, stderr: csErr } = await run(
474
+ 'codesign',
475
+ codesignArgs,
476
+ );
477
+ if (csErr) {
478
+ console.warn(`codesign warnings:\n${csErr}`);
479
+ }
480
+ if (csOut) {
481
+ console.log(csOut);
482
+ }
483
+ };
484
+
485
+ try {
486
+ await signBinary(outputFile, [
487
+ '--identifier',
488
+ 'io.github.lay777.calendar-event-cli',
489
+ '--entitlements',
490
+ entitlementsFile,
491
+ ]);
492
+ await signBinary(disclaimOutputFile, []);
493
+ console.log(
494
+ isAdHoc
495
+ ? 'Binaries signed (ad-hoc) with hardened runtime.'
496
+ : 'Binaries signed with Developer ID and hardened runtime.',
497
+ );
498
+ } catch (error) {
499
+ console.error('codesign failed!');
500
+ console.error(error);
501
+ process.exit(1);
502
+ }
503
+
504
+ console.log('event CLI build complete!');
505
+ }
506
+
507
+ main().catch((error) => {
508
+ console.error(
509
+ 'An unexpected error occurred during the build process:',
510
+ error,
511
+ );
512
+ process.exit(1);
513
+ });
@@ -0,0 +1,69 @@
1
+ /*
2
+ * disclaim.c — TCC responsibility disclaim shim (issue #93).
3
+ *
4
+ * Usage: event-disclaim <binary> [args...]
5
+ *
6
+ * Re-execs <binary> via posix_spawn with POSIX_SPAWN_SETEXEC and TCC
7
+ * responsibility disclaimed, making <binary> its own TCC-responsible
8
+ * process. macOS then reads the EventKit usage strings from <binary>'s
9
+ * embedded Info.plist and can show the Reminders/Calendar permission
10
+ * prompt even when the process tree was started by a GUI MCP client
11
+ * (Codex Desktop, Claude Desktop, ...) that declares no such strings —
12
+ * without the disclaim, TCC attributes the request to that client and
13
+ * refuses it before EventKit is reached.
14
+ *
15
+ * POSIX_SPAWN_SETEXEC replaces this process image in place (exec
16
+ * semantics), so stdio, exit codes, and signals need no proxying.
17
+ */
18
+ #include <spawn.h>
19
+ #include <stdio.h>
20
+ #include <string.h>
21
+
22
+ extern char **environ;
23
+
24
+ /*
25
+ * Private libSystem API (macOS 10.14+), the same call Chromium uses to make
26
+ * helper processes self-responsible for TCC. Declared weakly so the shim
27
+ * degrades to a plain exec — the pre-shim, host-attributed behavior — if a
28
+ * future macOS removes the symbol.
29
+ */
30
+ extern int responsibility_spawnattrs_setdisclaim(posix_spawnattr_t *attrs,
31
+ int disclaim)
32
+ __attribute__((weak_import));
33
+
34
+ int main(int argc, char *argv[]) {
35
+ if (argc < 2) {
36
+ fprintf(stderr, "usage: %s <binary> [args...]\n",
37
+ argc > 0 ? argv[0] : "event-disclaim");
38
+ return 64;
39
+ }
40
+
41
+ posix_spawnattr_t attr;
42
+ int rc = posix_spawnattr_init(&attr);
43
+ if (rc != 0) {
44
+ fprintf(stderr, "event-disclaim: posix_spawnattr_init: %s\n",
45
+ strerror(rc));
46
+ return 127;
47
+ }
48
+ rc = posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETEXEC);
49
+ if (rc != 0) {
50
+ /* Without SETEXEC, posix_spawn would fork instead of exec-replacing
51
+ this process — fail loudly rather than leave a confusing parent. */
52
+ posix_spawnattr_destroy(&attr);
53
+ fprintf(stderr, "event-disclaim: posix_spawnattr_setflags: %s\n",
54
+ strerror(rc));
55
+ return 127;
56
+ }
57
+ if (responsibility_spawnattrs_setdisclaim) {
58
+ responsibility_spawnattrs_setdisclaim(&attr, 1);
59
+ }
60
+
61
+ pid_t pid;
62
+ rc = posix_spawn(&pid, argv[1], NULL, &attr, &argv[1], environ);
63
+ /* Reached only when the spawn itself failed — with SETEXEC a successful
64
+ spawn never returns. */
65
+ posix_spawnattr_destroy(&attr);
66
+ fprintf(stderr, "event-disclaim: failed to exec %s: %s\n", argv[1],
67
+ strerror(rc));
68
+ return 127;
69
+ }