@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.
- package/CHANGELOG.md +575 -0
- package/LICENSE +21 -0
- package/README.md +304 -0
- package/README.zh-CN.md +270 -0
- package/bin/event +0 -0
- package/bin/event-disclaim +0 -0
- package/dist/bin/apple-calendar.d.ts +6 -0
- package/dist/bin/apple-calendar.js +8 -0
- package/dist/bin/apple-calendar.js.map +1 -0
- package/dist/bin/apple-reminders.d.ts +7 -0
- package/dist/bin/apple-reminders.js +9 -0
- package/dist/bin/apple-reminders.js.map +1 -0
- package/dist/bootstrap.d.ts +11 -0
- package/dist/bootstrap.js +44 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/server/handlers.d.ts +12 -0
- package/dist/server/handlers.js +58 -0
- package/dist/server/handlers.js.map +1 -0
- package/dist/server/mode.d.ts +19 -0
- package/dist/server/mode.js +47 -0
- package/dist/server/mode.js.map +1 -0
- package/dist/server/promptAbstractions.d.ts +61 -0
- package/dist/server/promptAbstractions.js +140 -0
- package/dist/server/promptAbstractions.js.map +1 -0
- package/dist/server/prompts.d.ts +13 -0
- package/dist/server/prompts.js +461 -0
- package/dist/server/prompts.js.map +1 -0
- package/dist/server/server.d.ts +61 -0
- package/dist/server/server.js +112 -0
- package/dist/server/server.js.map +1 -0
- package/dist/tools/definitions.d.ts +9 -0
- package/dist/tools/definitions.js +272 -0
- package/dist/tools/definitions.js.map +1 -0
- package/dist/tools/handlers/calendarHandlers.d.ts +11 -0
- package/dist/tools/handlers/calendarHandlers.js +151 -0
- package/dist/tools/handlers/calendarHandlers.js.map +1 -0
- package/dist/tools/handlers/formatters.d.ts +24 -0
- package/dist/tools/handlers/formatters.js +105 -0
- package/dist/tools/handlers/formatters.js.map +1 -0
- package/dist/tools/handlers/index.d.ts +8 -0
- package/dist/tools/handlers/index.js +9 -0
- package/dist/tools/handlers/index.js.map +1 -0
- package/dist/tools/handlers/listHandlers.d.ts +10 -0
- package/dist/tools/handlers/listHandlers.js +47 -0
- package/dist/tools/handlers/listHandlers.js.map +1 -0
- package/dist/tools/handlers/reminderHandlers.d.ts +10 -0
- package/dist/tools/handlers/reminderHandlers.js +219 -0
- package/dist/tools/handlers/reminderHandlers.js.map +1 -0
- package/dist/tools/handlers/shared.d.ts +28 -0
- package/dist/tools/handlers/shared.js +57 -0
- package/dist/tools/handlers/shared.js.map +1 -0
- package/dist/tools/handlers/subtaskHandlers.d.ts +12 -0
- package/dist/tools/handlers/subtaskHandlers.js +113 -0
- package/dist/tools/handlers/subtaskHandlers.js.map +1 -0
- package/dist/tools/index.d.ts +10 -0
- package/dist/tools/index.js +74 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types/index.d.ts +263 -0
- package/dist/types/index.js +42 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/prompts.d.ts +88 -0
- package/dist/types/prompts.js +6 -0
- package/dist/types/prompts.js.map +1 -0
- package/dist/types/repository.d.ts +197 -0
- package/dist/types/repository.js +14 -0
- package/dist/types/repository.js.map +1 -0
- package/dist/utils/binaryValidator.d.ts +52 -0
- package/dist/utils/binaryValidator.js +185 -0
- package/dist/utils/binaryValidator.js.map +1 -0
- package/dist/utils/calendarRepository.d.ts +49 -0
- package/dist/utils/calendarRepository.js +226 -0
- package/dist/utils/calendarRepository.js.map +1 -0
- package/dist/utils/constants.d.ts +79 -0
- package/dist/utils/constants.js +80 -0
- package/dist/utils/constants.js.map +1 -0
- package/dist/utils/dateFiltering.d.ts +41 -0
- package/dist/utils/dateFiltering.js +129 -0
- package/dist/utils/dateFiltering.js.map +1 -0
- package/dist/utils/dateUtils.d.ts +27 -0
- package/dist/utils/dateUtils.js +71 -0
- package/dist/utils/dateUtils.js.map +1 -0
- package/dist/utils/errorHandling.d.ts +27 -0
- package/dist/utils/errorHandling.js +88 -0
- package/dist/utils/errorHandling.js.map +1 -0
- package/dist/utils/eventCli.d.ts +40 -0
- package/dist/utils/eventCli.js +294 -0
- package/dist/utils/eventCli.js.map +1 -0
- package/dist/utils/helpers.d.ts +35 -0
- package/dist/utils/helpers.js +61 -0
- package/dist/utils/helpers.js.map +1 -0
- package/dist/utils/looseDateParser.d.ts +19 -0
- package/dist/utils/looseDateParser.js +149 -0
- package/dist/utils/looseDateParser.js.map +1 -0
- package/dist/utils/projectUtils.d.ts +13 -0
- package/dist/utils/projectUtils.js +92 -0
- package/dist/utils/projectUtils.js.map +1 -0
- package/dist/utils/reminderDateParser.d.ts +8 -0
- package/dist/utils/reminderDateParser.js +89 -0
- package/dist/utils/reminderDateParser.js.map +1 -0
- package/dist/utils/reminderRepository.d.ts +33 -0
- package/dist/utils/reminderRepository.js +262 -0
- package/dist/utils/reminderRepository.js.map +1 -0
- package/dist/utils/subtaskUtils.d.ts +103 -0
- package/dist/utils/subtaskUtils.js +253 -0
- package/dist/utils/subtaskUtils.js.map +1 -0
- package/dist/utils/tagUtils.d.ts +58 -0
- package/dist/utils/tagUtils.js +177 -0
- package/dist/utils/tagUtils.js.map +1 -0
- package/dist/utils/timeHelpers.d.ts +40 -0
- package/dist/utils/timeHelpers.js +136 -0
- package/dist/utils/timeHelpers.js.map +1 -0
- package/dist/validation/schemas.d.ts +385 -0
- package/dist/validation/schemas.js +579 -0
- package/dist/validation/schemas.js.map +1 -0
- package/package.json +96 -0
- package/scripts/build-event.mjs +513 -0
- package/scripts/disclaim.c +69 -0
- package/scripts/event-Info.plist +29 -0
- package/scripts/event.entitlements +10 -0
- 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, '&')
|
|
189
|
+
.replace(/</g, '<')
|
|
190
|
+
.replace(/>/g, '>');
|
|
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
|
+
}
|