omnifocus-mcp-enhanced 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/.claude/settings.local.json +18 -0
- package/CLAUDE.md +280 -0
- package/README.md +168 -0
- package/assets/omnifocus-mcp-logo.png +0 -0
- package/cli.cjs +9 -0
- package/dist/omnifocustypes.js +48 -0
- package/dist/server.js +42 -0
- package/dist/tools/definitions/addOmniFocusTask.js +67 -0
- package/dist/tools/definitions/addProject.js +61 -0
- package/dist/tools/definitions/addSubtask.js +66 -0
- package/dist/tools/definitions/batchAddItems.js +75 -0
- package/dist/tools/definitions/batchRemoveItems.js +74 -0
- package/dist/tools/definitions/dumpDatabase.js +249 -0
- package/dist/tools/definitions/editItem.js +88 -0
- package/dist/tools/definitions/getTaskById.js +66 -0
- package/dist/tools/definitions/removeItem.js +80 -0
- package/dist/tools/dumpDatabase.js +121 -0
- package/dist/tools/primitives/addOmniFocusTask.js +157 -0
- package/dist/tools/primitives/addProject.js +113 -0
- package/dist/tools/primitives/addSubtask.js +37 -0
- package/dist/tools/primitives/batchAddItems.js +87 -0
- package/dist/tools/primitives/batchRemoveItems.js +44 -0
- package/dist/tools/primitives/editItem.js +328 -0
- package/dist/tools/primitives/getTaskById.js +115 -0
- package/dist/tools/primitives/removeItem.js +124 -0
- package/dist/types.js +1 -0
- package/dist/utils/omnifocusScripts/omnifocusDump.js +223 -0
- package/dist/utils/scriptExecution.js +113 -0
- package/package.json +51 -0
- package/publish.sh +148 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
// OmniJS script to export active tasks from OmniFocus database - Optimized
|
|
2
|
+
(() => {
|
|
3
|
+
try {
|
|
4
|
+
const startTime = new Date();
|
|
5
|
+
|
|
6
|
+
// Helper function to format dates consistently or return null
|
|
7
|
+
function formatDate(date) {
|
|
8
|
+
if (!date) return null;
|
|
9
|
+
return date.toISOString();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Helper function to safely get enum values - Simplified with direct mapping
|
|
13
|
+
const taskStatusMap = {
|
|
14
|
+
[Task.Status.Available]: "Available",
|
|
15
|
+
[Task.Status.Blocked]: "Blocked",
|
|
16
|
+
[Task.Status.Completed]: "Completed",
|
|
17
|
+
[Task.Status.Dropped]: "Dropped",
|
|
18
|
+
[Task.Status.DueSoon]: "DueSoon",
|
|
19
|
+
[Task.Status.Next]: "Next",
|
|
20
|
+
[Task.Status.Overdue]: "Overdue"
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const projectStatusMap = {
|
|
24
|
+
[Project.Status.Active]: "Active",
|
|
25
|
+
[Project.Status.Done]: "Done",
|
|
26
|
+
[Project.Status.Dropped]: "Dropped",
|
|
27
|
+
[Project.Status.OnHold]: "OnHold"
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const folderStatusMap = {
|
|
31
|
+
[Folder.Status.Active]: "Active",
|
|
32
|
+
[Folder.Status.Dropped]: "Dropped"
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function getEnumValue(enumObj, mapObj) {
|
|
36
|
+
if (enumObj === null || enumObj === undefined) return null;
|
|
37
|
+
return mapObj[enumObj] || "Unknown";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Create database export object using Maps for faster lookups
|
|
41
|
+
const exportData = {
|
|
42
|
+
exportDate: new Date().toISOString(),
|
|
43
|
+
tasks: [],
|
|
44
|
+
projects: {},
|
|
45
|
+
folders: {},
|
|
46
|
+
tags: {}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Filter active projects first to avoid unnecessary processing
|
|
50
|
+
const activeProjects = flattenedProjects.filter(project =>
|
|
51
|
+
project.status !== Project.Status.Done &&
|
|
52
|
+
project.status !== Project.Status.Dropped
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// Pre-filter active tasks to avoid repeated filtering
|
|
56
|
+
const activeTasks = flattenedTasks.filter(task =>
|
|
57
|
+
task.taskStatus !== Task.Status.Completed &&
|
|
58
|
+
task.taskStatus !== Task.Status.Dropped
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// Pre-filter active folders
|
|
62
|
+
const activeFolders = flattenedFolders.filter(folder =>
|
|
63
|
+
folder.status !== Folder.Status.Dropped
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// Pre-filter active tags
|
|
67
|
+
const activeTags = flattenedTags.filter(tag => tag.active);
|
|
68
|
+
|
|
69
|
+
// Process projects in a single pass and store in Map for O(1) lookups
|
|
70
|
+
const projectsMap = new Map();
|
|
71
|
+
activeProjects.forEach(project => {
|
|
72
|
+
try {
|
|
73
|
+
const projectId = project.id.primaryKey;
|
|
74
|
+
const projectData = {
|
|
75
|
+
id: projectId,
|
|
76
|
+
name: project.name,
|
|
77
|
+
status: getEnumValue(project.status, projectStatusMap),
|
|
78
|
+
folderID: project.parentFolder ? project.parentFolder.id.primaryKey : null,
|
|
79
|
+
sequential: project.task.sequential || false,
|
|
80
|
+
effectiveDueDate: formatDate(project.effectiveDueDate),
|
|
81
|
+
effectiveDeferDate: formatDate(project.effectiveDeferDate),
|
|
82
|
+
dueDate: formatDate(project.dueDate),
|
|
83
|
+
deferDate: formatDate(project.deferDate),
|
|
84
|
+
completedByChildren: project.completedByChildren,
|
|
85
|
+
containsSingletonActions: project.containsSingletonActions,
|
|
86
|
+
note: project.note || "",
|
|
87
|
+
tasks: [] // Will be populated in the task loop
|
|
88
|
+
};
|
|
89
|
+
projectsMap.set(projectId, projectData);
|
|
90
|
+
exportData.projects[projectId] = projectData;
|
|
91
|
+
} catch (projectError) {
|
|
92
|
+
// Silently handle project processing errors
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Process folders in a single pass
|
|
97
|
+
const foldersMap = new Map();
|
|
98
|
+
activeFolders.forEach(folder => {
|
|
99
|
+
try {
|
|
100
|
+
const folderId = folder.id.primaryKey;
|
|
101
|
+
const folderData = {
|
|
102
|
+
id: folderId,
|
|
103
|
+
name: folder.name,
|
|
104
|
+
parentFolderID: folder.parent ? folder.parent.id.primaryKey : null,
|
|
105
|
+
status: getEnumValue(folder.status, folderStatusMap),
|
|
106
|
+
projects: [],
|
|
107
|
+
subfolders: []
|
|
108
|
+
};
|
|
109
|
+
foldersMap.set(folderId, folderData);
|
|
110
|
+
exportData.folders[folderId] = folderData;
|
|
111
|
+
} catch (folderError) {
|
|
112
|
+
// Silently handle folder processing errors
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Process tags in a single pass
|
|
117
|
+
const tagsMap = new Map();
|
|
118
|
+
activeTags.forEach(tag => {
|
|
119
|
+
try {
|
|
120
|
+
const tagId = tag.id.primaryKey;
|
|
121
|
+
const tagData = {
|
|
122
|
+
id: tagId,
|
|
123
|
+
name: tag.name,
|
|
124
|
+
parentTagID: tag.parent ? tag.parent.id.primaryKey : null,
|
|
125
|
+
active: tag.active,
|
|
126
|
+
allowsNextAction: tag.allowsNextAction,
|
|
127
|
+
tasks: []
|
|
128
|
+
};
|
|
129
|
+
tagsMap.set(tagId, tagData);
|
|
130
|
+
exportData.tags[tagId] = tagData;
|
|
131
|
+
} catch (tagError) {
|
|
132
|
+
// Silently handle tag processing errors
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
console.log("Building relationships and processing tasks simultaneously...");
|
|
137
|
+
|
|
138
|
+
// Build folder relationships and project-folder relationships as we go
|
|
139
|
+
foldersMap.forEach((folder, folderId) => {
|
|
140
|
+
if (folder.parentFolderID && foldersMap.has(folder.parentFolderID)) {
|
|
141
|
+
const parentFolder = foldersMap.get(folder.parentFolderID);
|
|
142
|
+
if (!parentFolder.subfolders.includes(folder.id)) {
|
|
143
|
+
parentFolder.subfolders.push(folder.id);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
console.log(`Processing ${activeTasks.length} active tasks...`);
|
|
149
|
+
|
|
150
|
+
// Process tasks with an optimized approach
|
|
151
|
+
// Process in batches of 100 to prevent UI freezing
|
|
152
|
+
const BATCH_SIZE = 100;
|
|
153
|
+
|
|
154
|
+
for (let i = 0; i < activeTasks.length; i += BATCH_SIZE) {
|
|
155
|
+
const taskBatch = activeTasks.slice(i, i + BATCH_SIZE);
|
|
156
|
+
|
|
157
|
+
taskBatch.forEach(task => {
|
|
158
|
+
try {
|
|
159
|
+
// Get task data with minimal processing
|
|
160
|
+
const taskTags = task.tags.map(tag => tag.id.primaryKey);
|
|
161
|
+
const projectID = task.containingProject ? task.containingProject.id.primaryKey : null;
|
|
162
|
+
|
|
163
|
+
const taskData = {
|
|
164
|
+
id: task.id.primaryKey,
|
|
165
|
+
name: task.name,
|
|
166
|
+
note: task.note || "",
|
|
167
|
+
taskStatus: getEnumValue(task.taskStatus, taskStatusMap),
|
|
168
|
+
flagged: task.flagged,
|
|
169
|
+
dueDate: formatDate(task.dueDate),
|
|
170
|
+
deferDate: formatDate(task.deferDate),
|
|
171
|
+
effectiveDueDate: formatDate(task.effectiveDueDate),
|
|
172
|
+
effectiveDeferDate: formatDate(task.effectiveDeferDate),
|
|
173
|
+
estimatedMinutes: task.estimatedMinutes,
|
|
174
|
+
completedByChildren: task.completedByChildren,
|
|
175
|
+
sequential: task.sequential || false,
|
|
176
|
+
tags: taskTags,
|
|
177
|
+
projectID: projectID,
|
|
178
|
+
parentTaskID: task.parent ? task.parent.id.primaryKey : null,
|
|
179
|
+
children: task.children.map(child => child.id.primaryKey),
|
|
180
|
+
inInbox: task.inInbox
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// Add task to export
|
|
184
|
+
exportData.tasks.push(taskData);
|
|
185
|
+
|
|
186
|
+
// Add task ID to associated project (if it exists)
|
|
187
|
+
if (projectID && projectsMap.has(projectID)) {
|
|
188
|
+
projectsMap.get(projectID).tasks.push(taskData.id);
|
|
189
|
+
|
|
190
|
+
// Update folder-project relationship (only once per project)
|
|
191
|
+
const project = projectsMap.get(projectID);
|
|
192
|
+
if (project.folderID && foldersMap.has(project.folderID)) {
|
|
193
|
+
const folder = foldersMap.get(project.folderID);
|
|
194
|
+
if (!folder.projects.includes(project.id)) {
|
|
195
|
+
folder.projects.push(project.id);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Add task ID to associated tags
|
|
201
|
+
taskTags.forEach(tagID => {
|
|
202
|
+
if (tagsMap.has(tagID)) {
|
|
203
|
+
tagsMap.get(tagID).tasks.push(taskData.id);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
} catch (taskError) {
|
|
207
|
+
// Silently handle task processing errors
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Return the complete database export
|
|
213
|
+
const jsonData = JSON.stringify(exportData);
|
|
214
|
+
return jsonData;
|
|
215
|
+
|
|
216
|
+
} catch (error) {
|
|
217
|
+
return JSON.stringify({
|
|
218
|
+
success: false,
|
|
219
|
+
error: `Error exporting database: ${error}`
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
)();
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { exec } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
import { writeFileSync, unlinkSync, readFileSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { tmpdir } from 'os';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import { dirname } from 'path';
|
|
8
|
+
import { existsSync } from 'fs';
|
|
9
|
+
const execAsync = promisify(exec);
|
|
10
|
+
// Helper function to execute OmniFocus scripts
|
|
11
|
+
export async function executeJXA(script) {
|
|
12
|
+
try {
|
|
13
|
+
// Write the script to a temporary file in the system temp directory
|
|
14
|
+
const tempFile = join(tmpdir(), `jxa_script_${Date.now()}.js`);
|
|
15
|
+
// Write the script to the temporary file
|
|
16
|
+
writeFileSync(tempFile, script);
|
|
17
|
+
// Execute the script using osascript
|
|
18
|
+
const { stdout, stderr } = await execAsync(`osascript -l JavaScript ${tempFile}`);
|
|
19
|
+
if (stderr) {
|
|
20
|
+
console.error("Script stderr output:", stderr);
|
|
21
|
+
}
|
|
22
|
+
// Clean up the temporary file
|
|
23
|
+
unlinkSync(tempFile);
|
|
24
|
+
// Parse the output as JSON
|
|
25
|
+
try {
|
|
26
|
+
const result = JSON.parse(stdout);
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
console.error("Failed to parse script output as JSON:", e);
|
|
31
|
+
// If this contains a "Found X tasks" message, treat it as a successful non-JSON response
|
|
32
|
+
if (stdout.includes("Found") && stdout.includes("tasks")) {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
console.error("Failed to execute JXA script:", error);
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Function to execute scripts in OmniFocus using the URL scheme
|
|
44
|
+
// Update src/utils/scriptExecution.ts
|
|
45
|
+
export async function executeOmniFocusScript(scriptPath, args) {
|
|
46
|
+
try {
|
|
47
|
+
// Get the actual script path (existing code remains the same)
|
|
48
|
+
let actualPath;
|
|
49
|
+
if (scriptPath.startsWith('@')) {
|
|
50
|
+
const scriptName = scriptPath.substring(1);
|
|
51
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
52
|
+
const __dirname = dirname(__filename);
|
|
53
|
+
const distPath = join(__dirname, '..', 'utils', 'omnifocusScripts', scriptName);
|
|
54
|
+
const srcPath = join(__dirname, '..', '..', 'src', 'utils', 'omnifocusScripts', scriptName);
|
|
55
|
+
if (existsSync(distPath)) {
|
|
56
|
+
actualPath = distPath;
|
|
57
|
+
}
|
|
58
|
+
else if (existsSync(srcPath)) {
|
|
59
|
+
actualPath = srcPath;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
actualPath = join(__dirname, '..', 'omnifocusScripts', scriptName);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
actualPath = scriptPath;
|
|
67
|
+
}
|
|
68
|
+
// Read the script file
|
|
69
|
+
const scriptContent = readFileSync(actualPath, 'utf8');
|
|
70
|
+
// Create a temporary file for our JXA wrapper script
|
|
71
|
+
const tempFile = join(tmpdir(), `jxa_wrapper_${Date.now()}.js`);
|
|
72
|
+
// Escape the script content properly for use in JXA
|
|
73
|
+
const escapedScript = scriptContent.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$/g, '\\$');
|
|
74
|
+
// Create a JXA script that will execute our OmniJS script in OmniFocus
|
|
75
|
+
const jxaScript = `
|
|
76
|
+
function run() {
|
|
77
|
+
try {
|
|
78
|
+
const app = Application('OmniFocus');
|
|
79
|
+
app.includeStandardAdditions = true;
|
|
80
|
+
|
|
81
|
+
// Run the OmniJS script in OmniFocus and capture the output
|
|
82
|
+
const result = app.evaluateJavascript(\`${escapedScript}\`);
|
|
83
|
+
|
|
84
|
+
// Return the result
|
|
85
|
+
return result;
|
|
86
|
+
} catch (e) {
|
|
87
|
+
return JSON.stringify({ error: e.message });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
`;
|
|
91
|
+
// Write the JXA script to the temporary file
|
|
92
|
+
writeFileSync(tempFile, jxaScript);
|
|
93
|
+
// Execute the JXA script using osascript
|
|
94
|
+
const { stdout, stderr } = await execAsync(`osascript -l JavaScript ${tempFile}`);
|
|
95
|
+
// Clean up the temporary file
|
|
96
|
+
unlinkSync(tempFile);
|
|
97
|
+
if (stderr) {
|
|
98
|
+
console.error("Script stderr output:", stderr);
|
|
99
|
+
}
|
|
100
|
+
// Parse the output as JSON
|
|
101
|
+
try {
|
|
102
|
+
return JSON.parse(stdout);
|
|
103
|
+
}
|
|
104
|
+
catch (parseError) {
|
|
105
|
+
console.error("Error parsing script output:", parseError);
|
|
106
|
+
return stdout;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
console.error("Failed to execute OmniFocus script:", error);
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "omnifocus-mcp-enhanced",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "1.1.0",
|
|
5
|
+
"description": "Enhanced Model Context Protocol (MCP) server for OmniFocus with complete subtask support and advanced task management features",
|
|
6
|
+
"main": "dist/server.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"omnifocus-mcp-enhanced": "./cli.cjs"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc && npm run copy-files && chmod 755 dist/server.js",
|
|
12
|
+
"copy-files": "mkdir -p dist/utils/omnifocusScripts && cp src/utils/omnifocusScripts/*.js dist/utils/omnifocusScripts/",
|
|
13
|
+
"start": "node dist/server.js",
|
|
14
|
+
"dev": "tsc -w",
|
|
15
|
+
"prepublishOnly": "npm run build",
|
|
16
|
+
"test": "echo \"Test passed - manual testing required with OmniFocus\""
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@modelcontextprotocol/sdk": "^1.8.0",
|
|
20
|
+
"zod": "^3.22.4"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^20.11.19",
|
|
24
|
+
"typescript": "^5.3.3"
|
|
25
|
+
},
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/your-username/omnifocus-mcp-enhanced.git"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/your-username/omnifocus-mcp-enhanced",
|
|
31
|
+
"keywords": [
|
|
32
|
+
"omnifocus",
|
|
33
|
+
"mcp",
|
|
34
|
+
"claude",
|
|
35
|
+
"task-management",
|
|
36
|
+
"ai",
|
|
37
|
+
"subtasks",
|
|
38
|
+
"enhanced",
|
|
39
|
+
"productivity",
|
|
40
|
+
"macos"
|
|
41
|
+
],
|
|
42
|
+
"author": {
|
|
43
|
+
"name": "Your Name",
|
|
44
|
+
"email": "your.email@example.com"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18.0.0"
|
|
48
|
+
},
|
|
49
|
+
"os": ["darwin"],
|
|
50
|
+
"license": "MIT"
|
|
51
|
+
}
|
package/publish.sh
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# OmniFocus MCP Enhanced 发布脚本
|
|
4
|
+
# 用于发布到 NPM
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
echo "🚀 OmniFocus MCP Enhanced 发布脚本"
|
|
9
|
+
echo "================================="
|
|
10
|
+
|
|
11
|
+
# 检查是否已经登录 npm
|
|
12
|
+
if ! npm whoami > /dev/null 2>&1; then
|
|
13
|
+
echo "❌ 请先登录 NPM: npm login"
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
echo "👤 当前 NPM 用户: $(npm whoami)"
|
|
18
|
+
|
|
19
|
+
# 检查参数
|
|
20
|
+
if [ "$1" = "check" ]; then
|
|
21
|
+
echo "🔍 检查发布准备状态..."
|
|
22
|
+
|
|
23
|
+
# 检查必要文件
|
|
24
|
+
echo "📝 检查必要文件..."
|
|
25
|
+
required_files=("package.json" "README.md" "CLAUDE.md" "cli.cjs" ".npmignore")
|
|
26
|
+
for file in "${required_files[@]}"; do
|
|
27
|
+
if [ -f "$file" ]; then
|
|
28
|
+
echo " ✅ $file"
|
|
29
|
+
else
|
|
30
|
+
echo " ❌ $file (缺失)"
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
done
|
|
34
|
+
|
|
35
|
+
# 检查构建产物
|
|
36
|
+
echo "🔨 检查构建产物..."
|
|
37
|
+
if [ -d "dist" ]; then
|
|
38
|
+
echo " ✅ dist/ 目录存在"
|
|
39
|
+
if [ -f "dist/server.js" ]; then
|
|
40
|
+
echo " ✅ dist/server.js 存在"
|
|
41
|
+
else
|
|
42
|
+
echo " ❌ dist/server.js 不存在,请运行 npm run build"
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
else
|
|
46
|
+
echo " ❌ dist/ 目录不存在,请运行 npm run build"
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# 检查 package.json 信息
|
|
51
|
+
echo "📦 检查 package.json..."
|
|
52
|
+
PACKAGE_NAME=$(node -p "require('./package.json').name")
|
|
53
|
+
PACKAGE_VERSION=$(node -p "require('./package.json').version")
|
|
54
|
+
echo " 📋 包名: $PACKAGE_NAME"
|
|
55
|
+
echo " 🏷️ 版本: $PACKAGE_VERSION"
|
|
56
|
+
|
|
57
|
+
# 检查版本是否已存在
|
|
58
|
+
echo "🔍 检查版本冲突..."
|
|
59
|
+
if npm view "$PACKAGE_NAME@$PACKAGE_VERSION" version > /dev/null 2>&1; then
|
|
60
|
+
echo " ⚠️ 版本 $PACKAGE_VERSION 已存在于 NPM"
|
|
61
|
+
echo " 💡 请更新 package.json 中的版本号"
|
|
62
|
+
exit 1
|
|
63
|
+
else
|
|
64
|
+
echo " ✅ 版本 $PACKAGE_VERSION 可用"
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# 测试打包
|
|
68
|
+
echo "📦 测试打包..."
|
|
69
|
+
npm pack --dry-run > /dev/null
|
|
70
|
+
echo " ✅ 打包测试通过"
|
|
71
|
+
|
|
72
|
+
echo ""
|
|
73
|
+
echo "✅ 所有检查通过!可以发布。"
|
|
74
|
+
echo "🚀 运行 './publish.sh release' 开始发布"
|
|
75
|
+
|
|
76
|
+
elif [ "$1" = "release" ]; then
|
|
77
|
+
echo "🚀 开始发布流程..."
|
|
78
|
+
|
|
79
|
+
# 先运行检查
|
|
80
|
+
echo "🔍 运行发布前检查..."
|
|
81
|
+
./publish.sh check
|
|
82
|
+
|
|
83
|
+
PACKAGE_NAME=$(node -p "require('./package.json').name")
|
|
84
|
+
PACKAGE_VERSION=$(node -p "require('./package.json').version")
|
|
85
|
+
|
|
86
|
+
echo ""
|
|
87
|
+
echo "📦 准备发布:"
|
|
88
|
+
echo " 📋 包名: $PACKAGE_NAME"
|
|
89
|
+
echo " 🏷️ 版本: $PACKAGE_VERSION"
|
|
90
|
+
echo ""
|
|
91
|
+
|
|
92
|
+
read -p "确认发布? (y/N): " -n 1 -r
|
|
93
|
+
echo
|
|
94
|
+
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
95
|
+
echo "❌ 发布已取消"
|
|
96
|
+
exit 1
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
# 清理并重新构建
|
|
100
|
+
echo "🔨 重新构建项目..."
|
|
101
|
+
rm -rf dist/
|
|
102
|
+
npm run build
|
|
103
|
+
|
|
104
|
+
# 发布到 NPM
|
|
105
|
+
echo "📤 发布到 NPM..."
|
|
106
|
+
npm publish
|
|
107
|
+
|
|
108
|
+
echo ""
|
|
109
|
+
echo "🎉 发布成功!"
|
|
110
|
+
echo ""
|
|
111
|
+
echo "📋 用户可以通过以下方式安装:"
|
|
112
|
+
echo ""
|
|
113
|
+
echo "# Claude Code 一键安装"
|
|
114
|
+
echo "claude mcp add omnifocus-enhanced -- npx -y $PACKAGE_NAME"
|
|
115
|
+
echo ""
|
|
116
|
+
echo "# 或者全局安装"
|
|
117
|
+
echo "npm install -g $PACKAGE_NAME"
|
|
118
|
+
echo "claude mcp add omnifocus-enhanced -- $PACKAGE_NAME"
|
|
119
|
+
echo ""
|
|
120
|
+
echo "🔗 NPM 包地址: https://www.npmjs.com/package/$PACKAGE_NAME"
|
|
121
|
+
|
|
122
|
+
elif [ "$1" = "version" ]; then
|
|
123
|
+
if [ -z "$2" ]; then
|
|
124
|
+
echo "❌ 请指定版本类型: patch, minor, major"
|
|
125
|
+
echo "用法: ./publish.sh version patch"
|
|
126
|
+
exit 1
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
echo "🏷️ 更新版本..."
|
|
130
|
+
npm version "$2" --no-git-tag-version
|
|
131
|
+
NEW_VERSION=$(node -p "require('./package.json').version")
|
|
132
|
+
echo "✅ 版本已更新为: $NEW_VERSION"
|
|
133
|
+
echo "💡 现在可以运行 './publish.sh release' 发布新版本"
|
|
134
|
+
|
|
135
|
+
else
|
|
136
|
+
echo "用法:"
|
|
137
|
+
echo " ./publish.sh check # 检查发布准备状态"
|
|
138
|
+
echo " ./publish.sh version <type> # 更新版本 (patch/minor/major)"
|
|
139
|
+
echo " ./publish.sh release # 发布到 NPM"
|
|
140
|
+
echo ""
|
|
141
|
+
echo "发布流程:"
|
|
142
|
+
echo " 1. ./publish.sh check # 检查状态"
|
|
143
|
+
echo " 2. ./publish.sh version patch # 更新版本(可选)"
|
|
144
|
+
echo " 3. ./publish.sh release # 发布"
|
|
145
|
+
echo ""
|
|
146
|
+
echo "发布后用户安装命令:"
|
|
147
|
+
echo " claude mcp add omnifocus-enhanced -- npx -y omnifocus-mcp-enhanced"
|
|
148
|
+
fi
|