apple-mail-mcp 1.0.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/LICENSE +21 -0
- package/README.md +522 -0
- package/build/index.d.ts +23 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +401 -0
- package/build/services/appleMailManager.d.ts +232 -0
- package/build/services/appleMailManager.d.ts.map +1 -0
- package/build/services/appleMailManager.js +1208 -0
- package/build/types.d.ts +306 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +13 -0
- package/build/utils/applescript.d.ts +45 -0
- package/build/utils/applescript.d.ts.map +1 -0
- package/build/utils/applescript.js +372 -0
- package/package.json +86 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AppleScript Execution Utilities
|
|
3
|
+
*
|
|
4
|
+
* This module provides a safe interface for executing AppleScript commands
|
|
5
|
+
* on macOS. It handles script execution, error capture, and result parsing.
|
|
6
|
+
*
|
|
7
|
+
* @module utils/applescript
|
|
8
|
+
*/
|
|
9
|
+
import { execSync, spawnSync } from "child_process";
|
|
10
|
+
/**
|
|
11
|
+
* Default execution timeout for AppleScript commands in milliseconds.
|
|
12
|
+
* 30 seconds is sufficient for most operations, including complex
|
|
13
|
+
* searches on large mailboxes. Can be overridden per-call.
|
|
14
|
+
*/
|
|
15
|
+
const DEFAULT_TIMEOUT_MS = 30000;
|
|
16
|
+
/**
|
|
17
|
+
* Default retry configuration.
|
|
18
|
+
* - 1 attempt means no retries (default behavior)
|
|
19
|
+
* - Use maxRetries: 3 for exponential backoff with 1s/2s delays
|
|
20
|
+
*/
|
|
21
|
+
const DEFAULT_MAX_RETRIES = 1;
|
|
22
|
+
const DEFAULT_RETRY_DELAY_MS = 1000;
|
|
23
|
+
/**
|
|
24
|
+
* Check if debug/verbose logging is enabled.
|
|
25
|
+
* Set DEBUG=1 or DEBUG=true or VERBOSE=1 to enable.
|
|
26
|
+
*/
|
|
27
|
+
const isDebugEnabled = () => {
|
|
28
|
+
const debug = process.env.DEBUG;
|
|
29
|
+
const verbose = process.env.VERBOSE;
|
|
30
|
+
return debug === "1" || debug === "true" || verbose === "1" || verbose === "true";
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Log a debug message if debug mode is enabled.
|
|
34
|
+
*
|
|
35
|
+
* @param message - The message to log
|
|
36
|
+
* @param data - Optional additional data to log
|
|
37
|
+
*/
|
|
38
|
+
function debugLog(message, data) {
|
|
39
|
+
if (!isDebugEnabled())
|
|
40
|
+
return;
|
|
41
|
+
const timestamp = new Date().toISOString();
|
|
42
|
+
if (data !== undefined) {
|
|
43
|
+
console.error(`[DEBUG ${timestamp}] ${message}`, data);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
console.error(`[DEBUG ${timestamp}] ${message}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Escapes a string for safe inclusion in a shell command.
|
|
51
|
+
*
|
|
52
|
+
* When passing AppleScript to osascript via shell, we need to handle
|
|
53
|
+
* the interaction between shell quoting and AppleScript string literals.
|
|
54
|
+
* This function escapes single quotes since we wrap the script in single quotes.
|
|
55
|
+
*
|
|
56
|
+
* @param script - The raw AppleScript code
|
|
57
|
+
* @returns Shell-safe version of the script
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* // Input: tell app "Notes" to get note "Rob's Note"
|
|
61
|
+
* // Output: tell app "Notes" to get note "Rob'\''s Note"
|
|
62
|
+
*/
|
|
63
|
+
function escapeForShell(script) {
|
|
64
|
+
// Replace single quotes with: end quote, escaped quote, start quote
|
|
65
|
+
// This is the standard shell escaping pattern for single-quoted strings
|
|
66
|
+
return script.replace(/'/g, "'\\''");
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Checks if an error is a timeout error from execSync.
|
|
70
|
+
*
|
|
71
|
+
* Node.js throws errors with specific properties when a child process
|
|
72
|
+
* is killed due to timeout.
|
|
73
|
+
*
|
|
74
|
+
* @param error - The caught error object
|
|
75
|
+
* @returns True if this was a timeout error
|
|
76
|
+
*/
|
|
77
|
+
function isTimeoutError(error) {
|
|
78
|
+
if (error instanceof Error) {
|
|
79
|
+
const execError = error;
|
|
80
|
+
// execSync kills the process with SIGTERM on timeout
|
|
81
|
+
return execError.killed === true || execError.signal === "SIGTERM";
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Error patterns that indicate transient failures worth retrying.
|
|
87
|
+
* These typically occur when Mail.app is busy or temporarily unresponsive.
|
|
88
|
+
*/
|
|
89
|
+
const RETRYABLE_ERROR_PATTERNS = [
|
|
90
|
+
/timed? out/i,
|
|
91
|
+
/not responding/i,
|
|
92
|
+
/connection.*invalid/i,
|
|
93
|
+
/lost connection/i,
|
|
94
|
+
/busy/i,
|
|
95
|
+
];
|
|
96
|
+
/**
|
|
97
|
+
* Checks if an error message indicates a transient failure that should be retried.
|
|
98
|
+
*
|
|
99
|
+
* @param errorMessage - The error message to check
|
|
100
|
+
* @returns True if this error is worth retrying
|
|
101
|
+
*/
|
|
102
|
+
function isRetryableError(errorMessage) {
|
|
103
|
+
return RETRYABLE_ERROR_PATTERNS.some((pattern) => pattern.test(errorMessage));
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Synchronous sleep using the system's sleep command.
|
|
107
|
+
* Used between retry attempts for exponential backoff.
|
|
108
|
+
*
|
|
109
|
+
* This is more efficient than a busy-wait loop as it doesn't
|
|
110
|
+
* consume CPU cycles during the delay.
|
|
111
|
+
*
|
|
112
|
+
* Uses spawnSync instead of execSync to avoid interference with
|
|
113
|
+
* execSync mocks in tests.
|
|
114
|
+
*
|
|
115
|
+
* @param ms - Milliseconds to sleep
|
|
116
|
+
*/
|
|
117
|
+
function sleep(ms) {
|
|
118
|
+
// Use system sleep command with fractional seconds support
|
|
119
|
+
// This avoids CPU-spinning busy wait while keeping the code synchronous
|
|
120
|
+
const seconds = ms / 1000;
|
|
121
|
+
const result = spawnSync("sleep", [seconds.toString()], { stdio: "ignore" });
|
|
122
|
+
if (result.error) {
|
|
123
|
+
// Fallback to busy-wait if sleep command fails (shouldn't happen on macOS)
|
|
124
|
+
const end = Date.now() + ms;
|
|
125
|
+
while (Date.now() < end) {
|
|
126
|
+
// Busy wait fallback
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* User-friendly error messages mapped from common AppleScript errors.
|
|
132
|
+
* Each entry maps a pattern (regex or string) to a user-friendly message.
|
|
133
|
+
*/
|
|
134
|
+
const ERROR_MAPPINGS = [
|
|
135
|
+
// Permission errors
|
|
136
|
+
{
|
|
137
|
+
pattern: /not authorized|not permitted|access.*denied/i,
|
|
138
|
+
message: "Permission denied. Grant automation access in System Preferences > Privacy & Security > Automation.",
|
|
139
|
+
},
|
|
140
|
+
// Application not running
|
|
141
|
+
{
|
|
142
|
+
pattern: /application isn't running|not running/i,
|
|
143
|
+
message: "Mail.app is not responding. Try opening Mail.app manually.",
|
|
144
|
+
},
|
|
145
|
+
// Connection errors
|
|
146
|
+
{
|
|
147
|
+
pattern: /connection is invalid|lost connection/i,
|
|
148
|
+
message: "Lost connection to Mail.app. The app may have crashed or been restarted.",
|
|
149
|
+
},
|
|
150
|
+
// Message not found
|
|
151
|
+
{
|
|
152
|
+
pattern: /can't get message/i,
|
|
153
|
+
message: "Message not found. The message may have been deleted or moved.",
|
|
154
|
+
},
|
|
155
|
+
// Mailbox not found
|
|
156
|
+
{
|
|
157
|
+
pattern: /can't get mailbox "([^"]+)"/i,
|
|
158
|
+
message: 'Mailbox "$1" not found. Use list-mailboxes to see available mailboxes.',
|
|
159
|
+
},
|
|
160
|
+
// Account not found
|
|
161
|
+
{
|
|
162
|
+
pattern: /can't get account "([^"]+)"/i,
|
|
163
|
+
message: 'Account "$1" not found. Use list-accounts to see available accounts.',
|
|
164
|
+
},
|
|
165
|
+
// Send failed
|
|
166
|
+
{
|
|
167
|
+
pattern: /couldn't send|send failed|cannot send/i,
|
|
168
|
+
message: "Failed to send email. Check your network connection and Mail.app settings.",
|
|
169
|
+
},
|
|
170
|
+
// Offline
|
|
171
|
+
{
|
|
172
|
+
pattern: /offline|no connection/i,
|
|
173
|
+
message: "Mail.app is offline. Check your network connection.",
|
|
174
|
+
},
|
|
175
|
+
// Cannot delete (various reasons)
|
|
176
|
+
{
|
|
177
|
+
pattern: /can't delete|cannot delete/i,
|
|
178
|
+
message: "Cannot delete. The message may be locked or in use.",
|
|
179
|
+
},
|
|
180
|
+
// Syntax/script errors (usually programming bugs)
|
|
181
|
+
{
|
|
182
|
+
pattern: /syntax error|expected/i,
|
|
183
|
+
message: "Internal error. Please report this issue.",
|
|
184
|
+
},
|
|
185
|
+
];
|
|
186
|
+
/**
|
|
187
|
+
* Parses error output from osascript to extract meaningful error messages.
|
|
188
|
+
*
|
|
189
|
+
* osascript errors typically include execution error numbers and descriptions.
|
|
190
|
+
* This function attempts to extract the human-readable portion and map it
|
|
191
|
+
* to a user-friendly message with helpful suggestions.
|
|
192
|
+
*
|
|
193
|
+
* @param errorOutput - Raw error string from execSync
|
|
194
|
+
* @returns User-friendly error message with suggested action
|
|
195
|
+
*/
|
|
196
|
+
function parseErrorMessage(errorOutput) {
|
|
197
|
+
// First, extract the core error message from AppleScript format
|
|
198
|
+
let coreError = errorOutput;
|
|
199
|
+
// Check for execution error format: "execution error: Message (-1234)"
|
|
200
|
+
const executionError = errorOutput.match(/execution error: (.+?)(?:\s*\(-?\d+\))?$/m);
|
|
201
|
+
if (executionError) {
|
|
202
|
+
coreError = executionError[1].trim();
|
|
203
|
+
}
|
|
204
|
+
// Try to match against known error patterns for user-friendly messages
|
|
205
|
+
for (const { pattern, message } of ERROR_MAPPINGS) {
|
|
206
|
+
const match = coreError.match(pattern);
|
|
207
|
+
if (match) {
|
|
208
|
+
// Replace $1, $2, etc. with captured groups
|
|
209
|
+
let result = message;
|
|
210
|
+
for (let i = 1; i < match.length; i++) {
|
|
211
|
+
result = result.replace(`$${i}`, match[i] || "");
|
|
212
|
+
}
|
|
213
|
+
return result;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Fall back to basic "Can't get X" parsing
|
|
217
|
+
const notFoundError = coreError.match(/Can't get (.+?)\./);
|
|
218
|
+
if (notFoundError) {
|
|
219
|
+
return `Not found: ${notFoundError[1]}`;
|
|
220
|
+
}
|
|
221
|
+
// Return cleaned version of original error
|
|
222
|
+
return coreError.trim() || "Unknown AppleScript error";
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Executes an AppleScript command and returns a structured result.
|
|
226
|
+
*
|
|
227
|
+
* This function serves as the bridge between TypeScript and macOS AppleScript.
|
|
228
|
+
* It handles the complexity of shell escaping, execution, and error handling
|
|
229
|
+
* so that calling code can work with clean TypeScript interfaces.
|
|
230
|
+
*
|
|
231
|
+
* The script is executed synchronously via the `osascript` command-line tool.
|
|
232
|
+
* Multi-line scripts are supported and preserved (important for AppleScript
|
|
233
|
+
* tell blocks and repeat loops).
|
|
234
|
+
*
|
|
235
|
+
* @param script - The AppleScript code to execute
|
|
236
|
+
* @param options - Optional execution settings (timeout, etc.)
|
|
237
|
+
* @returns A result object with success status and output or error message
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* ```typescript
|
|
241
|
+
* // Basic usage with default timeout (30 seconds)
|
|
242
|
+
* const result = executeAppleScript(`
|
|
243
|
+
* tell application "Notes"
|
|
244
|
+
* get name of every note
|
|
245
|
+
* end tell
|
|
246
|
+
* `);
|
|
247
|
+
*
|
|
248
|
+
* // With custom timeout for complex operations
|
|
249
|
+
* const result = executeAppleScript(complexScript, { timeoutMs: 60000 });
|
|
250
|
+
*
|
|
251
|
+
* if (result.success) {
|
|
252
|
+
* console.log("Notes:", result.output);
|
|
253
|
+
* } else {
|
|
254
|
+
* console.error("Failed:", result.error);
|
|
255
|
+
* }
|
|
256
|
+
* ```
|
|
257
|
+
*/
|
|
258
|
+
export function executeAppleScript(script, options = {}) {
|
|
259
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
260
|
+
const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
261
|
+
const retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
|
|
262
|
+
// Validate input - empty scripts are likely programmer errors
|
|
263
|
+
if (!script || !script.trim()) {
|
|
264
|
+
return {
|
|
265
|
+
success: false,
|
|
266
|
+
output: "",
|
|
267
|
+
error: "Cannot execute empty AppleScript",
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
// Prepare the script:
|
|
271
|
+
// 1. Trim leading/trailing whitespace (cosmetic)
|
|
272
|
+
// 2. Preserve internal newlines (required for AppleScript syntax)
|
|
273
|
+
// 3. Escape for shell execution
|
|
274
|
+
const preparedScript = escapeForShell(script.trim());
|
|
275
|
+
// Build the osascript command
|
|
276
|
+
// We use single quotes to wrap the script, which is why we escape
|
|
277
|
+
// single quotes within the script itself
|
|
278
|
+
const command = `osascript -e '${preparedScript}'`;
|
|
279
|
+
// Debug: Log the script being executed
|
|
280
|
+
debugLog("Executing AppleScript", {
|
|
281
|
+
scriptPreview: script.trim().substring(0, 200) + (script.length > 200 ? "..." : ""),
|
|
282
|
+
timeout: timeoutMs,
|
|
283
|
+
maxRetries,
|
|
284
|
+
});
|
|
285
|
+
let lastError = null;
|
|
286
|
+
const startTime = Date.now();
|
|
287
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
288
|
+
const attemptStart = Date.now();
|
|
289
|
+
try {
|
|
290
|
+
// Execute synchronously - MCP tools are inherently synchronous
|
|
291
|
+
// and Apple Notes operations are fast enough that async isn't needed
|
|
292
|
+
const output = execSync(command, {
|
|
293
|
+
encoding: "utf8",
|
|
294
|
+
timeout: timeoutMs,
|
|
295
|
+
// Capture stderr separately to get error details
|
|
296
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
297
|
+
});
|
|
298
|
+
const duration = Date.now() - attemptStart;
|
|
299
|
+
debugLog("AppleScript succeeded", {
|
|
300
|
+
attempt,
|
|
301
|
+
duration: `${duration}ms`,
|
|
302
|
+
outputLength: output.length,
|
|
303
|
+
outputPreview: output.substring(0, 100) + (output.length > 100 ? "..." : ""),
|
|
304
|
+
});
|
|
305
|
+
return {
|
|
306
|
+
success: true,
|
|
307
|
+
output: output.trim(),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
// execSync throws on non-zero exit codes
|
|
312
|
+
// The error object contains stderr output with AppleScript error details
|
|
313
|
+
const attemptDuration = Date.now() - attemptStart;
|
|
314
|
+
let errorMessage;
|
|
315
|
+
let isTimeout = false;
|
|
316
|
+
let rawError;
|
|
317
|
+
// Check for timeout first - provide specific message
|
|
318
|
+
if (isTimeoutError(error)) {
|
|
319
|
+
isTimeout = true;
|
|
320
|
+
const timeoutSecs = Math.round(timeoutMs / 1000);
|
|
321
|
+
errorMessage = `Operation timed out after ${timeoutSecs} seconds. Mail.app may be unresponsive or the operation involves too many messages.`;
|
|
322
|
+
}
|
|
323
|
+
else if (error instanceof Error) {
|
|
324
|
+
rawError = error.message;
|
|
325
|
+
// Node's ExecException includes stderr in the message
|
|
326
|
+
errorMessage = parseErrorMessage(error.message);
|
|
327
|
+
}
|
|
328
|
+
else if (typeof error === "string") {
|
|
329
|
+
rawError = error;
|
|
330
|
+
errorMessage = parseErrorMessage(error);
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
errorMessage = "AppleScript execution failed with unknown error";
|
|
334
|
+
}
|
|
335
|
+
// Debug: Log error details
|
|
336
|
+
debugLog("AppleScript failed", {
|
|
337
|
+
attempt,
|
|
338
|
+
duration: `${attemptDuration}ms`,
|
|
339
|
+
totalElapsed: `${Date.now() - startTime}ms`,
|
|
340
|
+
isTimeout,
|
|
341
|
+
errorMessage,
|
|
342
|
+
rawError: rawError?.substring(0, 500),
|
|
343
|
+
});
|
|
344
|
+
lastError = {
|
|
345
|
+
success: false,
|
|
346
|
+
output: "",
|
|
347
|
+
error: errorMessage,
|
|
348
|
+
};
|
|
349
|
+
// Check if we should retry
|
|
350
|
+
const canRetry = isTimeout || isRetryableError(errorMessage);
|
|
351
|
+
const hasAttemptsLeft = attempt < maxRetries;
|
|
352
|
+
if (canRetry && hasAttemptsLeft) {
|
|
353
|
+
const delayMs = retryDelayMs * Math.pow(2, attempt - 1);
|
|
354
|
+
console.error(`AppleScript retry: Attempt ${attempt}/${maxRetries} failed with "${errorMessage}". Retrying in ${delayMs}ms...`);
|
|
355
|
+
sleep(delayMs);
|
|
356
|
+
// Continue to next attempt
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
// Log final error and return
|
|
360
|
+
if (isTimeout) {
|
|
361
|
+
console.error(`AppleScript timeout: ${errorMessage}`);
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
console.error(`AppleScript error: ${errorMessage}`);
|
|
365
|
+
}
|
|
366
|
+
return lastError;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
// Return the last error (all retries exhausted - shouldn't reach here normally)
|
|
371
|
+
return lastError;
|
|
372
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "apple-mail-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for Apple Mail - read, search, send, and manage emails via Claude",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "build/index.js",
|
|
7
|
+
"types": "build/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"apple-mail-mcp": "build/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"build",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc && tsc-alias",
|
|
18
|
+
"start": "node build/index.js",
|
|
19
|
+
"dev": "tsc --watch",
|
|
20
|
+
"test": "vitest run",
|
|
21
|
+
"test:watch": "vitest",
|
|
22
|
+
"test:coverage": "vitest run --coverage",
|
|
23
|
+
"lint": "eslint src",
|
|
24
|
+
"lint:fix": "eslint src --fix",
|
|
25
|
+
"format": "prettier --write src",
|
|
26
|
+
"format:check": "prettier --check src",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"prepublishOnly": "npm run lint && npm run test && npm run build",
|
|
29
|
+
"prepare": "husky"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"mcp",
|
|
33
|
+
"apple-mail",
|
|
34
|
+
"claude",
|
|
35
|
+
"ai",
|
|
36
|
+
"applescript",
|
|
37
|
+
"macos",
|
|
38
|
+
"email",
|
|
39
|
+
"model-context-protocol"
|
|
40
|
+
],
|
|
41
|
+
"author": "Rob Sweet <rob@superiortech.io>",
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "git+https://github.com/sweetrb/apple-mail-mcp.git"
|
|
46
|
+
},
|
|
47
|
+
"homepage": "https://github.com/sweetrb/apple-mail-mcp#readme",
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/sweetrb/apple-mail-mcp/issues"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=20.0.0"
|
|
53
|
+
},
|
|
54
|
+
"os": [
|
|
55
|
+
"darwin"
|
|
56
|
+
],
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"@modelcontextprotocol/sdk": "1.4.1",
|
|
59
|
+
"zod": "^3.22.4"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@types/node": "^20.0.0",
|
|
63
|
+
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
64
|
+
"@typescript-eslint/parser": "^8.0.0",
|
|
65
|
+
"@vitest/coverage-v8": "^2.1.9",
|
|
66
|
+
"eslint": "^9.0.0",
|
|
67
|
+
"globals": "^17.0.0",
|
|
68
|
+
"husky": "^9.1.7",
|
|
69
|
+
"lint-staged": "^16.2.7",
|
|
70
|
+
"prettier": "^3.0.0",
|
|
71
|
+
"tsc-alias": "^1.8.10",
|
|
72
|
+
"tsconfig-paths": "^4.2.0",
|
|
73
|
+
"typescript": "^5.0.0",
|
|
74
|
+
"typescript-eslint": "^8.51.0",
|
|
75
|
+
"vitest": "^2.0.0"
|
|
76
|
+
},
|
|
77
|
+
"volta": {
|
|
78
|
+
"node": "22.13.1"
|
|
79
|
+
},
|
|
80
|
+
"lint-staged": {
|
|
81
|
+
"src/**/*.ts": [
|
|
82
|
+
"eslint --fix",
|
|
83
|
+
"prettier --write"
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
}
|