@hauptsache.net/clickup-mcp 1.0.1 → 1.0.3
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/README.md +51 -126
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +120 -45
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,148 +1,73 @@
|
|
|
1
|
-
# ClickUp MCP
|
|
1
|
+
# ClickUp MCP for AI Assistants
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A Model Context Protocol (MCP) server that enables AI assistants like Claude, Windsurf, and Cursor to interact with your ClickUp tasks.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
This especially includes images in the description and in comments of tickets,
|
|
7
|
-
which are not that easy to understand by just passing the api responses.
|
|
5
|
+
## Why This MCP Is Great
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
This MCP excels at providing AI assistants with rich access to your ClickUp tasks:
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
9
|
+
- **Complete Task Information**: View detailed task data including status, creation date, and assignees
|
|
10
|
+
- **Full Comment History**: Access the entire conversation thread for context
|
|
11
|
+
- **Inline Images Support**: View images embedded in task descriptions and comments
|
|
12
|
+
- **Task Search**: Find relevant tasks using keywords
|
|
13
|
+
- **Todo Management**: List all your open tasks
|
|
15
14
|
|
|
16
|
-
The
|
|
15
|
+
The standout feature is the ability to retrieve individual tickets with their complete comment history and inline images, giving your AI assistant the full context of your work.
|
|
17
16
|
|
|
18
|
-
##
|
|
17
|
+
## Limitations
|
|
19
18
|
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
19
|
+
- **Todo Management**: The todo management feature is currently limited to 50 tasks since it would otherwise flood the context.
|
|
20
|
+
- **No List/Space Support**: This MCP does not currently support browsing ClickUp Lists or Spaces due to performance issues with large instances.
|
|
21
|
+
- **Image Limit**: The MCP processes only the 4 most recent images per task to prevent exceeding context limits. You can adjust this number by setting the `MAX_IMAGES` environment variable, though most AI tools have constraints that prevent using more than 4 images.
|
|
23
22
|
|
|
24
|
-
##
|
|
23
|
+
## Setup for Claude Desktop, Windsurf, or Cursor
|
|
25
24
|
|
|
26
|
-
1.
|
|
27
|
-
|
|
25
|
+
1. **Prerequisites**:
|
|
26
|
+
- A ClickUp account with API access (Profile Icon > Settings > Apps > API Token ~ usually starts with pk_)
|
|
27
|
+
- Your ClickUp API key and Team ID (The ~7 digit number in the url when you are in the settings)
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
```
|
|
29
|
+
2. **Configuration**:
|
|
30
|
+
Add the following to your MCP configuration file:
|
|
32
31
|
|
|
33
|
-
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"mcpServers": {
|
|
35
|
+
"clickup": {
|
|
36
|
+
"command": "npx",
|
|
37
|
+
"args": [
|
|
38
|
+
"-y",
|
|
39
|
+
"@hauptsache.net/clickup-mcp"
|
|
40
|
+
],
|
|
41
|
+
"env": {
|
|
42
|
+
"CLICKUP_API_KEY": "your_api_key",
|
|
43
|
+
"CLICKUP_TEAM_ID": "your_team_id"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
34
49
|
|
|
35
|
-
|
|
36
|
-
npm run build
|
|
37
|
-
```
|
|
50
|
+
Replace `your_api_key` and `your_team_id` with your actual ClickUp credentials.
|
|
38
51
|
|
|
39
|
-
|
|
52
|
+
3. **Connect Your AI Assistant**:
|
|
53
|
+
- **Claude Desktop**: Add this configuration in Settings > MCPs
|
|
54
|
+
- **Windsurf**: Add to your MCP configuration file
|
|
55
|
+
- **Cursor**: Configure through the MCP settings panel
|
|
40
56
|
|
|
41
|
-
|
|
57
|
+
## Using with Your AI Assistant
|
|
42
58
|
|
|
43
|
-
|
|
44
|
-
- `CLICKUP_TEAM_ID`: Your ClickUp team ID
|
|
59
|
+
Once connected, your AI assistant can:
|
|
45
60
|
|
|
46
|
-
|
|
61
|
+
1. **View Task Details**:
|
|
62
|
+
Ask: "Show me details for task CU-123456"
|
|
47
63
|
|
|
48
|
-
|
|
64
|
+
2. **Search Tasks**:
|
|
65
|
+
Ask: "Find tasks related to login functionality"
|
|
49
66
|
|
|
50
|
-
|
|
67
|
+
3. **Check Your Todo List**:
|
|
68
|
+
Ask: "What tasks are assigned to me?"
|
|
51
69
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
npm start
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
This will start the server using the standard input/output for communication, following the Model Context Protocol.
|
|
59
|
-
|
|
60
|
-
### Using with Claude Desktop or Similar LLM Apps
|
|
61
|
-
|
|
62
|
-
You can use this MCP server with Claude Desktop or similar applications by adding it to your MCP configuration:
|
|
63
|
-
|
|
64
|
-
```json
|
|
65
|
-
{
|
|
66
|
-
"mcpServers": {
|
|
67
|
-
"clickup": {
|
|
68
|
-
"command": "npx",
|
|
69
|
-
"args": [
|
|
70
|
-
"-y",
|
|
71
|
-
"@hauptsache.net/clickup-mcp"
|
|
72
|
-
],
|
|
73
|
-
"env": {
|
|
74
|
-
"CLICKUP_API_KEY": "your_api_key",
|
|
75
|
-
"CLICKUP_TEAM_ID": "your_team_id"
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
Replace `your_api_key` and `your_team_id` with your actual ClickUp credentials. This configuration allows Claude or other LLMs to directly access your ClickUp tasks.
|
|
83
|
-
|
|
84
|
-
### CLI Usage
|
|
85
|
-
|
|
86
|
-
For testing or manual usage, you can use the CLI interface:
|
|
87
|
-
|
|
88
|
-
```bash
|
|
89
|
-
# List all available tools
|
|
90
|
-
npm run cli
|
|
91
|
-
|
|
92
|
-
# Get a task by ID
|
|
93
|
-
npm run cli getTaskById id=abc1234
|
|
94
|
-
|
|
95
|
-
# Search for tasks
|
|
96
|
-
npm run cli searchTask terms="feature|bug|enhancement"
|
|
97
|
-
|
|
98
|
-
# List open tasks for the current user
|
|
99
|
-
npm run cli listTodo
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
## Available Tools
|
|
103
|
-
|
|
104
|
-
### getTaskById
|
|
105
|
-
|
|
106
|
-
Retrieves a complete ClickUp task with its description, comments, and embedded images.
|
|
107
|
-
|
|
108
|
-
Parameters:
|
|
109
|
-
- `id`: The 7-9 character ClickUp task ID (without "#" or "CU-" prefix)
|
|
110
|
-
|
|
111
|
-
### searchTask
|
|
112
|
-
|
|
113
|
-
Searches for tasks by name with support for multiple search terms (using OR logic).
|
|
114
|
-
|
|
115
|
-
Parameters:
|
|
116
|
-
- `terms`: Search terms separated by '|' (e.g., 'term1|term2|term3')
|
|
117
|
-
|
|
118
|
-
### listTodo
|
|
119
|
-
|
|
120
|
-
Lists all open tasks assigned to the current user.
|
|
121
|
-
|
|
122
|
-
Parameters: None
|
|
123
|
-
|
|
124
|
-
## Development
|
|
125
|
-
|
|
126
|
-
For development with hot reloading:
|
|
127
|
-
|
|
128
|
-
```bash
|
|
129
|
-
npm run dev
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
Format code with Prettier:
|
|
133
|
-
|
|
134
|
-
```bash
|
|
135
|
-
npm run prettier
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
## How It Works
|
|
139
|
-
|
|
140
|
-
This MCP implementation:
|
|
141
|
-
|
|
142
|
-
1. Connects to the ClickUp API using your API key
|
|
143
|
-
2. Provides tools for LLMs to query and process ClickUp data
|
|
144
|
-
3. Handles image processing in Markdown descriptions
|
|
145
|
-
4. Returns structured data in a format optimized for LLM consumption
|
|
70
|
+
The AI will retrieve the information directly from ClickUp, including all text content, comments, and images, providing you with comprehensive assistance on your tasks.
|
|
146
71
|
|
|
147
72
|
## License
|
|
148
73
|
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAiBpE,eAAO,MAAM,MAAM,WAGjB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ const clickup_text_1 = require("./clickup-text");
|
|
|
9
9
|
const CONFIG = {
|
|
10
10
|
apiKey: process.env.CLICKUP_API_KEY,
|
|
11
11
|
teamId: process.env.CLICKUP_TEAM_ID,
|
|
12
|
+
maxImages: process.env.MAX_IMAGES ? parseInt(process.env.MAX_IMAGES) : 4,
|
|
12
13
|
};
|
|
13
14
|
if (!CONFIG.apiKey || !CONFIG.teamId) {
|
|
14
15
|
throw new Error("Missing Clickup API key or team ID");
|
|
@@ -18,45 +19,75 @@ exports.server = new mcp_js_1.McpServer({
|
|
|
18
19
|
name: "Clickup MCP",
|
|
19
20
|
version: "1.0.0",
|
|
20
21
|
});
|
|
22
|
+
/**
|
|
23
|
+
* Checks if a string looks like a valid ClickUp task ID
|
|
24
|
+
* Valid task IDs are 7-9 characters long and contain only alphanumeric characters
|
|
25
|
+
*/
|
|
26
|
+
function isTaskId(str) {
|
|
27
|
+
// Task IDs are 7-9 characters long and contain only alphanumeric characters
|
|
28
|
+
return /^[a-z0-9]{7,9}$/i.test(str);
|
|
29
|
+
}
|
|
21
30
|
exports.server.tool("getTaskById", "Get a Clickup task with images and comments by ID", {
|
|
22
31
|
id: zod_1.z
|
|
23
32
|
.string()
|
|
24
33
|
.min(7)
|
|
25
34
|
.max(9)
|
|
26
|
-
.
|
|
35
|
+
.refine(val => isTaskId(val), {
|
|
36
|
+
message: "Task ID must be 7-9 alphanumeric characters only"
|
|
37
|
+
})
|
|
38
|
+
.describe(`The 7-9 character ID of the task to get without a prefix like "#", "CU-" or "https://app.clickup.com/t/"`),
|
|
27
39
|
}, async ({ id }) => {
|
|
28
40
|
const [content, comments] = await Promise.all([
|
|
29
41
|
loadTaskContent(id),
|
|
30
42
|
loadTaskComments(id),
|
|
31
43
|
]);
|
|
44
|
+
// Combine all content and limit the number of images
|
|
45
|
+
const allContent = [...content, ...comments];
|
|
46
|
+
const limitedContent = limitImages(allContent, CONFIG.maxImages);
|
|
32
47
|
return {
|
|
33
|
-
content:
|
|
48
|
+
content: limitedContent,
|
|
34
49
|
};
|
|
35
50
|
});
|
|
51
|
+
/**
|
|
52
|
+
* Helper function to generate consistent task metadata
|
|
53
|
+
*/
|
|
54
|
+
function generateTaskMetadata(task) {
|
|
55
|
+
const metadataLines = [
|
|
56
|
+
`task_id: ${task.id}`,
|
|
57
|
+
`name: ${task.name}`,
|
|
58
|
+
`status: ${task.status.status}`,
|
|
59
|
+
`date_created: ${new Date(+task.date_created)}`,
|
|
60
|
+
`date_updated: ${new Date(+task.date_updated)}`,
|
|
61
|
+
`creator: ${task.creator.username}`,
|
|
62
|
+
`list: ${task.list.name} (${task.list.id})`,
|
|
63
|
+
];
|
|
64
|
+
// Add parent task information if it exists
|
|
65
|
+
if (typeof task.parent === "string") {
|
|
66
|
+
metadataLines.push(`parent_task_id: ${task.parent}`);
|
|
67
|
+
}
|
|
68
|
+
// Add child task information if it exists
|
|
69
|
+
if (task.subtasks && task.subtasks.length > 0) {
|
|
70
|
+
metadataLines.push(`child_task_ids: ${task.subtasks.map((st) => st.id).join(', ')}`);
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
type: "text",
|
|
74
|
+
text: metadataLines.join("\n"),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
36
77
|
async function loadTaskContent(id) {
|
|
37
|
-
const response = await fetch(`https://api.clickup.com/api/v2/task/${id}?include_markdown_description=true`, { headers: { Authorization: CONFIG.apiKey } });
|
|
78
|
+
const response = await fetch(`https://api.clickup.com/api/v2/task/${id}?include_markdown_description=true&include_subtasks=true`, { headers: { Authorization: CONFIG.apiKey } });
|
|
38
79
|
const task = await response.json();
|
|
39
80
|
const content = await (0, clickup_text_1.processClickUpMarkdown)(task.markdown_description || "", task.attachments);
|
|
40
|
-
// Create the task metadata block
|
|
41
|
-
const taskMetadata =
|
|
42
|
-
type: "text",
|
|
43
|
-
text: [
|
|
44
|
-
`task_id: ${task.id}`,
|
|
45
|
-
`name: ${task.name}`,
|
|
46
|
-
`status: ${task.status.status}`,
|
|
47
|
-
`date_created: ${new Date(+task.date_created)}`,
|
|
48
|
-
`date_updated: ${new Date(+task.date_updated)}`,
|
|
49
|
-
`creator: ${task.creator.username}`,
|
|
50
|
-
`list: ${task.list.name} (${task.list.id})`,
|
|
51
|
-
].join("\n"),
|
|
52
|
-
};
|
|
81
|
+
// Create the task metadata block using the helper function
|
|
82
|
+
const taskMetadata = generateTaskMetadata(task);
|
|
53
83
|
return [taskMetadata, ...content];
|
|
54
84
|
}
|
|
55
85
|
async function loadTaskComments(id) {
|
|
56
86
|
const response = await fetch(`https://api.clickup.com/api/v2/task/${id}/comment`, { headers: { Authorization: CONFIG.apiKey } });
|
|
57
87
|
const comments = await response.json();
|
|
58
88
|
return Promise.all(comments.comments
|
|
59
|
-
|
|
89
|
+
// Sort comments by date, newest first to prioritize recent images
|
|
90
|
+
.sort((a, b) => +b.date - +a.date)
|
|
60
91
|
.map(async (comment) => {
|
|
61
92
|
// Create a header for the comment
|
|
62
93
|
const commentHeader = {
|
|
@@ -80,6 +111,41 @@ async function loadTaskComments(id) {
|
|
|
80
111
|
}
|
|
81
112
|
}));
|
|
82
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Limits the number of images in the content array, replacing excess images with text placeholders
|
|
116
|
+
* Prioritizes keeping the most recent images (assumes content is ordered with newest items last)
|
|
117
|
+
*
|
|
118
|
+
* @param content Array of content blocks that may contain images
|
|
119
|
+
* @param maxImages Maximum number of images to keep
|
|
120
|
+
* @returns Modified content array with limited images
|
|
121
|
+
*/
|
|
122
|
+
function limitImages(content, maxImages) {
|
|
123
|
+
// Count how many images we have
|
|
124
|
+
const imageIndices = [];
|
|
125
|
+
// Find all image blocks
|
|
126
|
+
content.forEach((block, index) => {
|
|
127
|
+
if (block.type === "image") {
|
|
128
|
+
imageIndices.push(index);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
// If we have fewer images than the limit, return the original content
|
|
132
|
+
if (imageIndices.length <= maxImages) {
|
|
133
|
+
return content;
|
|
134
|
+
}
|
|
135
|
+
// Determine which images to keep (the most recent ones)
|
|
136
|
+
// We want to keep the last 'maxImages' images
|
|
137
|
+
const imagesToRemove = imageIndices.slice(0, imageIndices.length - maxImages);
|
|
138
|
+
// Create a new content array with excess images replaced by text
|
|
139
|
+
return content.map((block, index) => {
|
|
140
|
+
if (block.type === "image" && imagesToRemove.includes(index)) {
|
|
141
|
+
return {
|
|
142
|
+
type: "text",
|
|
143
|
+
text: "[Image removed due to size limitations. Only the most recent images are shown.]",
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
return block;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
83
149
|
let cachedTasks = [];
|
|
84
150
|
let lastTaskCacheUpdate = 0;
|
|
85
151
|
exports.server.tool("searchTask", [
|
|
@@ -103,10 +169,41 @@ exports.server.tool("searchTask", [
|
|
|
103
169
|
const searchTerms = terms
|
|
104
170
|
.split("|")
|
|
105
171
|
.map((term) => term.trim().toLowerCase());
|
|
106
|
-
|
|
172
|
+
// Check if any search term looks like a task ID
|
|
173
|
+
const potentialTaskIds = searchTerms.filter(isTaskId);
|
|
174
|
+
// Fetch tasks from cache that match search terms
|
|
175
|
+
const tasksFromCache = cachedTasks.filter((task) => {
|
|
107
176
|
const taskNameLower = task.name.toLowerCase();
|
|
108
|
-
|
|
177
|
+
const taskId = task.id.toLowerCase();
|
|
178
|
+
return searchTerms.some((term) => taskNameLower.includes(term) || taskId.includes(term));
|
|
179
|
+
});
|
|
180
|
+
// Fetch tasks by ID directly if they look like task IDs and they're not already in the cache
|
|
181
|
+
const tasksToFetch = potentialTaskIds.filter(id => {
|
|
182
|
+
// Check if this ID is already in the tasksFromCache
|
|
183
|
+
return !tasksFromCache.some(task => task.id.toLowerCase() === id.toLowerCase());
|
|
184
|
+
});
|
|
185
|
+
const taskPromises = tasksToFetch.map(async (id) => {
|
|
186
|
+
try {
|
|
187
|
+
// Fetch task directly from API
|
|
188
|
+
const response = await fetch(`https://api.clickup.com/api/v2/task/${id}`, { headers: { Authorization: CONFIG.apiKey } });
|
|
189
|
+
if (!response.ok)
|
|
190
|
+
return null;
|
|
191
|
+
const task = await response.json();
|
|
192
|
+
return task;
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
console.error(`Error fetching task ${id}:`, error);
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
109
198
|
});
|
|
199
|
+
// Wait for all task fetches to complete
|
|
200
|
+
const directlyFetchedTasks = await Promise.all(taskPromises);
|
|
201
|
+
// Combine tasks from cache and directly fetched tasks, removing nulls
|
|
202
|
+
const allTasks = [
|
|
203
|
+
...tasksFromCache,
|
|
204
|
+
...directlyFetchedTasks.filter(Boolean)
|
|
205
|
+
];
|
|
206
|
+
const tasks = allTasks;
|
|
110
207
|
if (tasks.length === 0) {
|
|
111
208
|
return {
|
|
112
209
|
content: [
|
|
@@ -118,18 +215,7 @@ exports.server.tool("searchTask", [
|
|
|
118
215
|
};
|
|
119
216
|
}
|
|
120
217
|
return {
|
|
121
|
-
content: tasks.map((task) => (
|
|
122
|
-
type: "text",
|
|
123
|
-
text: [
|
|
124
|
-
`task_id: ${task.id}`,
|
|
125
|
-
`name: ${task.name}`,
|
|
126
|
-
`status: ${task.status.status}`,
|
|
127
|
-
`date_created: ${new Date(+task.date_created)}`,
|
|
128
|
-
`date_updated: ${new Date(+task.date_updated)}`,
|
|
129
|
-
`creator: ${task.creator.username}`,
|
|
130
|
-
`list: ${task.list.name} (${task.list.id})`,
|
|
131
|
-
].join("\n"),
|
|
132
|
-
})),
|
|
218
|
+
content: tasks.map((task) => generateTaskMetadata(task)),
|
|
133
219
|
};
|
|
134
220
|
});
|
|
135
221
|
exports.server.tool("listTodo", "Lists all open tasks for the current user.", {}, async () => {
|
|
@@ -143,8 +229,8 @@ exports.server.tool("listTodo", "Lists all open tasks for the current user.", {}
|
|
|
143
229
|
const tasks = taskLists.flatMap((tl) => tl.tasks);
|
|
144
230
|
// filter out closed tasks
|
|
145
231
|
const openTasks = tasks
|
|
146
|
-
.filter((task) => task.status.type !== "done")
|
|
147
|
-
.slice(0,
|
|
232
|
+
.filter((task) => task.status.type !== "done") // done is not closed but also not a todo
|
|
233
|
+
.slice(0, 50);
|
|
148
234
|
if (openTasks.length === 0) {
|
|
149
235
|
return {
|
|
150
236
|
content: [
|
|
@@ -153,18 +239,7 @@ exports.server.tool("listTodo", "Lists all open tasks for the current user.", {}
|
|
|
153
239
|
};
|
|
154
240
|
}
|
|
155
241
|
return {
|
|
156
|
-
content: openTasks.map((task) => (
|
|
157
|
-
type: "text",
|
|
158
|
-
text: [
|
|
159
|
-
`task_id: ${task.id}`,
|
|
160
|
-
`name: ${task.name}`,
|
|
161
|
-
`status: ${task.status.status}`,
|
|
162
|
-
`date_created: ${new Date(+task.date_created)}`,
|
|
163
|
-
`date_updated: ${new Date(+task.date_updated)}`,
|
|
164
|
-
`creator: ${task.creator.username}`,
|
|
165
|
-
`list: ${task.list.name} (${task.list.id})`,
|
|
166
|
-
].join("\n"),
|
|
167
|
-
})),
|
|
242
|
+
content: openTasks.map((task) => generateTaskMetadata(task)),
|
|
168
243
|
};
|
|
169
244
|
});
|
|
170
245
|
// Only connect to the transport if this file is being run directly (not imported)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hauptsache.net/clickup-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "A minimal implementation of a Model Context Protocol (MCP) server for ClickUp integration",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"dev": "tsc -w & nodemon dist/index.js",
|
|
16
16
|
"cli": "npx ts-node src/cli.ts",
|
|
17
17
|
"prettier": "prettier --write src/**/*.ts",
|
|
18
|
-
"prepublishOnly": "npm run build"
|
|
18
|
+
"prepublishOnly": "rm -r dist && npm run build",
|
|
19
|
+
"release": "npm run build && npm publish --access public && git add . && git commit -m \"Release v$(node -p 'require(\"./package.json\").version')\" && git tag -a v$(node -p 'require(\"./package.json\").version') -m \"Release v$(node -p 'require(\"./package.json\").version')\" && git push && git push --tags"
|
|
19
20
|
},
|
|
20
21
|
"keywords": [
|
|
21
22
|
"clickup",
|