@krodak/clickup-cli 1.18.0 → 1.19.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-plugin/plugin.json +1 -1
- package/README.md +50 -19
- package/dist/chunk-XAXTPUJP.js +2024 -0
- package/dist/index.js +382 -1994
- package/dist/sprint-7S5UOUQX.js +19 -0
- package/package.json +1 -1
- package/skills/clickup-cli/SKILL.md +106 -89
package/dist/index.js
CHANGED
|
@@ -1,1723 +1,60 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ClickUpClient,
|
|
4
|
+
SPRINT_KEYWORDS,
|
|
5
|
+
TASK_COLUMNS,
|
|
6
|
+
addProfile,
|
|
7
|
+
buildTypeMap,
|
|
8
|
+
deleteFavorite,
|
|
9
|
+
deleteFilter,
|
|
10
|
+
fetchMyTasks,
|
|
11
|
+
findRelatedSpaces,
|
|
12
|
+
formatAssignConfirmation,
|
|
13
|
+
formatCommentConfirmation,
|
|
14
|
+
formatCommentsMarkdown,
|
|
15
|
+
formatCreateConfirmation,
|
|
16
|
+
formatDate,
|
|
17
|
+
formatDateISO,
|
|
18
|
+
formatDuration,
|
|
19
|
+
formatGroupedTasksMarkdown,
|
|
20
|
+
formatListsMarkdown,
|
|
21
|
+
formatMarkdownTable,
|
|
22
|
+
formatSpacesMarkdown,
|
|
23
|
+
formatTable,
|
|
24
|
+
formatTaskDetail,
|
|
25
|
+
formatTaskDetailMarkdown,
|
|
26
|
+
formatTimestamp,
|
|
27
|
+
formatUpdateConfirmation,
|
|
28
|
+
getConfigPath,
|
|
29
|
+
getFavorites,
|
|
30
|
+
getFilters,
|
|
31
|
+
groupedTaskPicker,
|
|
32
|
+
isCustomTaskId,
|
|
33
|
+
isDoneStatus,
|
|
34
|
+
isTTY,
|
|
35
|
+
listProfiles,
|
|
36
|
+
loadConfig,
|
|
37
|
+
loadRawConfig,
|
|
38
|
+
openUrl,
|
|
39
|
+
parseSprintDates,
|
|
40
|
+
printTasks,
|
|
41
|
+
removeProfile,
|
|
42
|
+
runSprintCommand,
|
|
43
|
+
saveFavorite,
|
|
44
|
+
saveFilter,
|
|
45
|
+
setDefaultProfile,
|
|
46
|
+
shouldOutputJson,
|
|
47
|
+
showDetailsAndOpen,
|
|
48
|
+
summarize,
|
|
49
|
+
writeConfig
|
|
50
|
+
} from "./chunk-XAXTPUJP.js";
|
|
2
51
|
|
|
3
52
|
// src/index.ts
|
|
4
|
-
import { realpathSync } from "fs";
|
|
53
|
+
import { realpathSync as realpathSync2 } from "fs";
|
|
5
54
|
import { basename, resolve } from "path";
|
|
6
55
|
import { Command } from "commander";
|
|
7
56
|
import { createRequire } from "module";
|
|
8
|
-
import { fileURLToPath
|
|
9
|
-
|
|
10
|
-
// src/api.ts
|
|
11
|
-
var BASE_URL = "https://api.clickup.com/api/v2";
|
|
12
|
-
var BASE_URL_V3 = "https://api.clickup.com/api/v3";
|
|
13
|
-
var MAX_PAGES = 100;
|
|
14
|
-
function isRecord(value) {
|
|
15
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16
|
-
}
|
|
17
|
-
function expectRecord(value, context) {
|
|
18
|
-
if (!isRecord(value)) {
|
|
19
|
-
throw new Error(`Unexpected API response: expected ${context} object`);
|
|
20
|
-
}
|
|
21
|
-
return value;
|
|
22
|
-
}
|
|
23
|
-
function expectRecordField(data, key, context) {
|
|
24
|
-
return expectRecord(data[key], context);
|
|
25
|
-
}
|
|
26
|
-
function expectNumericField(data, key, context) {
|
|
27
|
-
const value = Number(data[key]);
|
|
28
|
-
if (!Number.isInteger(value)) {
|
|
29
|
-
throw new Error(`Unexpected API response: expected ${context}.${key} to be numeric`);
|
|
30
|
-
}
|
|
31
|
-
return value;
|
|
32
|
-
}
|
|
33
|
-
function expectStringField(data, key, context) {
|
|
34
|
-
const value = data[key];
|
|
35
|
-
if (typeof value !== "string") {
|
|
36
|
-
throw new Error(`Unexpected API response: expected ${context}.${key} to be a string`);
|
|
37
|
-
}
|
|
38
|
-
return value;
|
|
39
|
-
}
|
|
40
|
-
function expectArrayField(data, key, context) {
|
|
41
|
-
const value = data[key];
|
|
42
|
-
if (!Array.isArray(value)) {
|
|
43
|
-
throw new Error(`Unexpected API response: expected ${context}.${key} to be an array`);
|
|
44
|
-
}
|
|
45
|
-
return value;
|
|
46
|
-
}
|
|
47
|
-
function readCollectionField(data, key, context) {
|
|
48
|
-
if (data[key] === void 0) return [];
|
|
49
|
-
return expectArrayField(data, key, context);
|
|
50
|
-
}
|
|
51
|
-
function expectBooleanField(data, key, context) {
|
|
52
|
-
const value = data[key];
|
|
53
|
-
if (typeof value !== "boolean") {
|
|
54
|
-
throw new Error(`Unexpected API response: expected ${context}.${key} to be a boolean`);
|
|
55
|
-
}
|
|
56
|
-
return value;
|
|
57
|
-
}
|
|
58
|
-
function expectPaginatedCollectionField(data, key, context) {
|
|
59
|
-
const items = data[key];
|
|
60
|
-
if (!Array.isArray(items)) {
|
|
61
|
-
throw new Error(`Unexpected API response: expected ${key} array`);
|
|
62
|
-
}
|
|
63
|
-
return {
|
|
64
|
-
items,
|
|
65
|
-
lastPage: expectBooleanField(data, "last_page", context)
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
function isCustomTaskId(id) {
|
|
69
|
-
return /^[A-Z]+-\d+$/i.test(id);
|
|
70
|
-
}
|
|
71
|
-
var ClickUpClient = class {
|
|
72
|
-
apiToken;
|
|
73
|
-
teamId;
|
|
74
|
-
meCache = null;
|
|
75
|
-
constructor(config) {
|
|
76
|
-
this.apiToken = config.apiToken;
|
|
77
|
-
this.teamId = config.teamId;
|
|
78
|
-
}
|
|
79
|
-
taskPath(taskId, suffix = "") {
|
|
80
|
-
const base = `/task/${taskId}${suffix}`;
|
|
81
|
-
if (isCustomTaskId(taskId) && this.teamId) {
|
|
82
|
-
const sep = base.includes("?") ? "&" : "?";
|
|
83
|
-
return `${base}${sep}custom_task_ids=true&team_id=${this.teamId}`;
|
|
84
|
-
}
|
|
85
|
-
return base;
|
|
86
|
-
}
|
|
87
|
-
customIdQueryParams(taskId) {
|
|
88
|
-
if (isCustomTaskId(taskId) && this.teamId) {
|
|
89
|
-
return `?custom_task_ids=true&team_id=${this.teamId}`;
|
|
90
|
-
}
|
|
91
|
-
return "";
|
|
92
|
-
}
|
|
93
|
-
async _fetch(baseUrl, path, options = {}) {
|
|
94
|
-
const res = await fetch(`${baseUrl}${path}`, {
|
|
95
|
-
...options,
|
|
96
|
-
signal: AbortSignal.timeout(3e4),
|
|
97
|
-
headers: {
|
|
98
|
-
Authorization: this.apiToken,
|
|
99
|
-
...options.body ? { "Content-Type": "application/json" } : {},
|
|
100
|
-
...options.headers
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
if (res.status === 204 || res.headers.get("content-length") === "0") {
|
|
104
|
-
if (!res.ok) {
|
|
105
|
-
throw new Error(`ClickUp API error ${res.status}: ${res.statusText}`);
|
|
106
|
-
}
|
|
107
|
-
return {};
|
|
108
|
-
}
|
|
109
|
-
let parsed;
|
|
110
|
-
try {
|
|
111
|
-
parsed = await res.json();
|
|
112
|
-
} catch {
|
|
113
|
-
throw new Error(`ClickUp API error ${res.status}: response was not valid JSON`);
|
|
114
|
-
}
|
|
115
|
-
const data = expectRecord(parsed, "JSON");
|
|
116
|
-
if (!res.ok) {
|
|
117
|
-
const raw = data.err ?? data.error ?? data.ECODE ?? res.statusText;
|
|
118
|
-
const errMsg = typeof raw === "string" ? raw : JSON.stringify(raw);
|
|
119
|
-
throw new Error(`ClickUp API error ${res.status}: ${errMsg}`);
|
|
120
|
-
}
|
|
121
|
-
return data;
|
|
122
|
-
}
|
|
123
|
-
async request(path, options = {}) {
|
|
124
|
-
return this._fetch(BASE_URL, path, options);
|
|
125
|
-
}
|
|
126
|
-
async requestV3(path, options = {}) {
|
|
127
|
-
return this._fetch(BASE_URL_V3, path, options);
|
|
128
|
-
}
|
|
129
|
-
async getMe() {
|
|
130
|
-
if (this.meCache) return this.meCache;
|
|
131
|
-
const data = await this.request(
|
|
132
|
-
"/user"
|
|
133
|
-
);
|
|
134
|
-
const user = expectRecordField(data, "user", "user");
|
|
135
|
-
const timezone = typeof user.timezone === "string" && user.timezone ? user.timezone : void 0;
|
|
136
|
-
this.meCache = {
|
|
137
|
-
id: expectNumericField(user, "id", "user"),
|
|
138
|
-
username: expectStringField(user, "username", "user"),
|
|
139
|
-
...timezone ? { timezone } : {}
|
|
140
|
-
};
|
|
141
|
-
return this.meCache;
|
|
142
|
-
}
|
|
143
|
-
async getUserTimezone() {
|
|
144
|
-
const me = await this.getMe();
|
|
145
|
-
return me.timezone;
|
|
146
|
-
}
|
|
147
|
-
async paginate(buildPath) {
|
|
148
|
-
const allTasks = [];
|
|
149
|
-
let page = 0;
|
|
150
|
-
let lastPage = false;
|
|
151
|
-
while (!lastPage && page < MAX_PAGES) {
|
|
152
|
-
const data = await this.request(buildPath(page));
|
|
153
|
-
const taskPage = expectPaginatedCollectionField(
|
|
154
|
-
data,
|
|
155
|
-
"tasks",
|
|
156
|
-
"task page"
|
|
157
|
-
);
|
|
158
|
-
allTasks.push(...taskPage.items);
|
|
159
|
-
lastPage = taskPage.lastPage;
|
|
160
|
-
page++;
|
|
161
|
-
}
|
|
162
|
-
if (page >= MAX_PAGES && !lastPage) {
|
|
163
|
-
process.stderr.write(
|
|
164
|
-
`Warning: reached maximum page limit (${MAX_PAGES}), results may be incomplete
|
|
165
|
-
`
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
return allTasks;
|
|
169
|
-
}
|
|
170
|
-
async getMyTasks(teamId, filters = {}) {
|
|
171
|
-
const baseParams = new URLSearchParams({
|
|
172
|
-
subtasks: String(filters.subtasks ?? true)
|
|
173
|
-
});
|
|
174
|
-
if (filters.includeClosed) baseParams.set("include_closed", "true");
|
|
175
|
-
if (!filters.all) {
|
|
176
|
-
const me = await this.getMe();
|
|
177
|
-
baseParams.append("assignees[]", String(me.id));
|
|
178
|
-
}
|
|
179
|
-
if (filters.assignees) {
|
|
180
|
-
for (const id of filters.assignees) baseParams.append("assignees[]", String(id));
|
|
181
|
-
}
|
|
182
|
-
for (const s of filters.statuses ?? []) baseParams.append("statuses[]", s);
|
|
183
|
-
for (const id of filters.listIds ?? []) baseParams.append("list_ids[]", id);
|
|
184
|
-
for (const id of filters.spaceIds ?? []) baseParams.append("space_ids[]", id);
|
|
185
|
-
for (const tag of filters.tags ?? []) baseParams.append("tags[]", tag);
|
|
186
|
-
if (filters.dueDateGt) baseParams.set("due_date_gt", String(filters.dueDateGt));
|
|
187
|
-
if (filters.dueDateLt) baseParams.set("due_date_lt", String(filters.dueDateLt));
|
|
188
|
-
if (filters.dateCreatedGt) baseParams.set("date_created_gt", String(filters.dateCreatedGt));
|
|
189
|
-
if (filters.dateCreatedLt) baseParams.set("date_created_lt", String(filters.dateCreatedLt));
|
|
190
|
-
if (filters.dateUpdatedGt) baseParams.set("date_updated_gt", String(filters.dateUpdatedGt));
|
|
191
|
-
if (filters.dateUpdatedLt) baseParams.set("date_updated_lt", String(filters.dateUpdatedLt));
|
|
192
|
-
if (filters.customFields?.length) {
|
|
193
|
-
baseParams.set("custom_fields", JSON.stringify(filters.customFields));
|
|
194
|
-
}
|
|
195
|
-
return this.paginate((page) => {
|
|
196
|
-
const params = new URLSearchParams(baseParams);
|
|
197
|
-
params.set("page", String(page));
|
|
198
|
-
return `/team/${teamId}/task?${params.toString()}`;
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
async updateTask(taskId, options) {
|
|
202
|
-
return this.request(this.taskPath(taskId), {
|
|
203
|
-
method: "PUT",
|
|
204
|
-
body: JSON.stringify(options)
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
async postComment(taskId, commentText, notifyAll) {
|
|
208
|
-
const body = { comment_text: commentText };
|
|
209
|
-
if (notifyAll) body.notify_all = true;
|
|
210
|
-
return this.request(this.taskPath(taskId, "/comment"), {
|
|
211
|
-
method: "POST",
|
|
212
|
-
body: JSON.stringify(body)
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
async getTaskComments(taskId) {
|
|
216
|
-
const data = await this.request(this.taskPath(taskId, "/comment"));
|
|
217
|
-
return readCollectionField(
|
|
218
|
-
data,
|
|
219
|
-
"comments",
|
|
220
|
-
"task comments"
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
async getTasksFromList(listId, params = {}, options = {}) {
|
|
224
|
-
return this.paginate((page) => {
|
|
225
|
-
const base = { subtasks: "true", page: String(page), ...params };
|
|
226
|
-
if (options.includeClosed) base["include_closed"] = "true";
|
|
227
|
-
const qs = new URLSearchParams(base).toString();
|
|
228
|
-
return `/list/${listId}/task?${qs}`;
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
async getTask(taskId) {
|
|
232
|
-
return this.request(this.taskPath(taskId, "?include_markdown_description=true"));
|
|
233
|
-
}
|
|
234
|
-
async createTask(listId, options) {
|
|
235
|
-
return this.request(`/list/${listId}/task`, {
|
|
236
|
-
method: "POST",
|
|
237
|
-
body: JSON.stringify(options)
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
async getTeams() {
|
|
241
|
-
const data = await this.request("/team");
|
|
242
|
-
return readCollectionField(data, "teams", "teams");
|
|
243
|
-
}
|
|
244
|
-
async getSpaceWithStatuses(spaceId) {
|
|
245
|
-
return this.request(`/space/${spaceId}`);
|
|
246
|
-
}
|
|
247
|
-
async getListWithStatuses(listId) {
|
|
248
|
-
return this.request(`/list/${listId}`);
|
|
249
|
-
}
|
|
250
|
-
async createSpace(teamId, name) {
|
|
251
|
-
return this.request(`/team/${teamId}/space`, {
|
|
252
|
-
method: "POST",
|
|
253
|
-
body: JSON.stringify({ name, multiple_assignees: true })
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
async getSpaces(teamId) {
|
|
257
|
-
const data = await this.request(`/team/${teamId}/space?archived=false`);
|
|
258
|
-
return readCollectionField(data, "spaces", "spaces");
|
|
259
|
-
}
|
|
260
|
-
async getCustomTaskTypes(teamId) {
|
|
261
|
-
const data = await this.request(
|
|
262
|
-
`/team/${teamId}/custom_item`
|
|
263
|
-
);
|
|
264
|
-
return readCollectionField(
|
|
265
|
-
data,
|
|
266
|
-
"custom_items",
|
|
267
|
-
"custom task types"
|
|
268
|
-
);
|
|
269
|
-
}
|
|
270
|
-
async createList(spaceId, name) {
|
|
271
|
-
return this.request(`/space/${spaceId}/list`, {
|
|
272
|
-
method: "POST",
|
|
273
|
-
body: JSON.stringify({ name })
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
async createFolderList(folderId, name) {
|
|
277
|
-
return this.request(`/folder/${folderId}/list`, {
|
|
278
|
-
method: "POST",
|
|
279
|
-
body: JSON.stringify({ name })
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
async updateList(listId, payload) {
|
|
283
|
-
return this.request(`/list/${listId}`, {
|
|
284
|
-
method: "PUT",
|
|
285
|
-
body: JSON.stringify(payload)
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
async createFolder(spaceId, name) {
|
|
289
|
-
return this.request(`/space/${spaceId}/folder`, {
|
|
290
|
-
method: "POST",
|
|
291
|
-
body: JSON.stringify({ name })
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
async getLists(spaceId) {
|
|
295
|
-
const data = await this.request(`/space/${spaceId}/list?archived=false`);
|
|
296
|
-
return readCollectionField(data, "lists", "space lists");
|
|
297
|
-
}
|
|
298
|
-
async getFolders(spaceId) {
|
|
299
|
-
const data = await this.request(
|
|
300
|
-
`/space/${spaceId}/folder?archived=false`
|
|
301
|
-
);
|
|
302
|
-
return readCollectionField(data, "folders", "space folders");
|
|
303
|
-
}
|
|
304
|
-
async getFolderLists(folderId) {
|
|
305
|
-
const data = await this.request(`/folder/${folderId}/list?archived=false`);
|
|
306
|
-
return readCollectionField(data, "lists", "folder lists");
|
|
307
|
-
}
|
|
308
|
-
async getListViews(listId) {
|
|
309
|
-
return this.request(`/list/${listId}/view`);
|
|
310
|
-
}
|
|
311
|
-
async getSpaceViews(spaceId) {
|
|
312
|
-
const data = await this.request(`/space/${spaceId}/view`);
|
|
313
|
-
return readCollectionField(data, "views", "views");
|
|
314
|
-
}
|
|
315
|
-
async getFolderViews(folderId) {
|
|
316
|
-
const data = await this.request(`/folder/${folderId}/view`);
|
|
317
|
-
return readCollectionField(data, "views", "views");
|
|
318
|
-
}
|
|
319
|
-
async getWorkspaceViews(teamId) {
|
|
320
|
-
const data = await this.request(`/team/${teamId}/view`);
|
|
321
|
-
return readCollectionField(data, "views", "views");
|
|
322
|
-
}
|
|
323
|
-
async getViewTasks(viewId) {
|
|
324
|
-
return this.paginate((page) => `/view/${viewId}/task?page=${page}`);
|
|
325
|
-
}
|
|
326
|
-
async getView(viewId) {
|
|
327
|
-
const data = await this.request(`/view/${viewId}`);
|
|
328
|
-
return expectRecordField(data, "view", "view");
|
|
329
|
-
}
|
|
330
|
-
async createListView(listId, payload) {
|
|
331
|
-
const data = await this.request(`/list/${listId}/view`, {
|
|
332
|
-
method: "POST",
|
|
333
|
-
body: JSON.stringify(payload)
|
|
334
|
-
});
|
|
335
|
-
return expectRecordField(data, "view", "view");
|
|
336
|
-
}
|
|
337
|
-
async updateView(viewId, payload) {
|
|
338
|
-
const data = await this.request(`/view/${viewId}`, {
|
|
339
|
-
method: "PUT",
|
|
340
|
-
body: JSON.stringify(payload)
|
|
341
|
-
});
|
|
342
|
-
return expectRecordField(data, "view", "view");
|
|
343
|
-
}
|
|
344
|
-
async deleteView(viewId) {
|
|
345
|
-
await this.request(`/view/${viewId}`, { method: "DELETE" });
|
|
346
|
-
}
|
|
347
|
-
async getListTemplates(teamId) {
|
|
348
|
-
const data = await this.request(`/team/${teamId}/list_template`);
|
|
349
|
-
return readCollectionField(
|
|
350
|
-
data,
|
|
351
|
-
"templates",
|
|
352
|
-
"list templates"
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
|
-
async getFolderTemplates(teamId) {
|
|
356
|
-
const data = await this.request(
|
|
357
|
-
`/team/${teamId}/folder_template`
|
|
358
|
-
);
|
|
359
|
-
return readCollectionField(
|
|
360
|
-
data,
|
|
361
|
-
"templates",
|
|
362
|
-
"folder templates"
|
|
363
|
-
);
|
|
364
|
-
}
|
|
365
|
-
async createListFromTemplate(containerId, templateId, name, containerType) {
|
|
366
|
-
return this.request(
|
|
367
|
-
`/${containerType}/${containerId}/list_template/${templateId}`,
|
|
368
|
-
{ method: "POST", body: JSON.stringify({ name }) }
|
|
369
|
-
);
|
|
370
|
-
}
|
|
371
|
-
async addTaskToList(taskId, listId) {
|
|
372
|
-
await this.request(`/list/${listId}/task/${taskId}`, { method: "POST" });
|
|
373
|
-
}
|
|
374
|
-
async removeTaskFromList(taskId, listId) {
|
|
375
|
-
await this.request(`/list/${listId}/task/${taskId}`, { method: "DELETE" });
|
|
376
|
-
}
|
|
377
|
-
async setCustomFieldValue(taskId, fieldId, value) {
|
|
378
|
-
await this.request(this.taskPath(taskId, `/field/${fieldId}`), {
|
|
379
|
-
method: "POST",
|
|
380
|
-
body: JSON.stringify({ value })
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
async removeCustomFieldValue(taskId, fieldId) {
|
|
384
|
-
await this.request(this.taskPath(taskId, `/field/${fieldId}`), { method: "DELETE" });
|
|
385
|
-
}
|
|
386
|
-
async deleteTask(taskId) {
|
|
387
|
-
await this.request(this.taskPath(taskId), { method: "DELETE" });
|
|
388
|
-
}
|
|
389
|
-
async addTagToTask(taskId, tagName) {
|
|
390
|
-
await this.request(this.taskPath(taskId, `/tag/${encodeURIComponent(tagName)}`), {
|
|
391
|
-
method: "POST"
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
async removeTagFromTask(taskId, tagName) {
|
|
395
|
-
await this.request(this.taskPath(taskId, `/tag/${encodeURIComponent(tagName)}`), {
|
|
396
|
-
method: "DELETE"
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
async addDependency(taskId, opts) {
|
|
400
|
-
const body = {};
|
|
401
|
-
if (opts.dependsOn) body.depends_on = opts.dependsOn;
|
|
402
|
-
if (opts.dependencyOf) body.dependency_of = opts.dependencyOf;
|
|
403
|
-
await this.request(this.taskPath(taskId, "/dependency"), {
|
|
404
|
-
method: "POST",
|
|
405
|
-
body: JSON.stringify(body)
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
async deleteDependency(taskId, opts) {
|
|
409
|
-
const params = new URLSearchParams();
|
|
410
|
-
if (opts.dependsOn) params.set("depends_on", opts.dependsOn);
|
|
411
|
-
if (opts.dependencyOf) params.set("dependency_of", opts.dependencyOf);
|
|
412
|
-
await this.request(this.taskPath(taskId, `/dependency?${params.toString()}`), {
|
|
413
|
-
method: "DELETE"
|
|
414
|
-
});
|
|
415
|
-
}
|
|
416
|
-
async updateComment(commentId, text, resolved) {
|
|
417
|
-
const body = { comment_text: text };
|
|
418
|
-
if (resolved !== void 0) body.resolved = resolved;
|
|
419
|
-
await this.request(`/comment/${commentId}`, {
|
|
420
|
-
method: "PUT",
|
|
421
|
-
body: JSON.stringify(body)
|
|
422
|
-
});
|
|
423
|
-
}
|
|
424
|
-
async deleteComment(commentId) {
|
|
425
|
-
await this.request(`/comment/${commentId}`, { method: "DELETE" });
|
|
426
|
-
}
|
|
427
|
-
async getThreadedComments(commentId) {
|
|
428
|
-
const data = await this.request(`/comment/${commentId}/reply`);
|
|
429
|
-
return readCollectionField(
|
|
430
|
-
data,
|
|
431
|
-
"comments",
|
|
432
|
-
"threaded comments"
|
|
433
|
-
);
|
|
434
|
-
}
|
|
435
|
-
async createThreadedComment(commentId, text, notifyAll) {
|
|
436
|
-
const body = { comment_text: text };
|
|
437
|
-
if (notifyAll) body.notify_all = true;
|
|
438
|
-
await this.request(`/comment/${commentId}/reply`, {
|
|
439
|
-
method: "POST",
|
|
440
|
-
body: JSON.stringify(body)
|
|
441
|
-
});
|
|
442
|
-
}
|
|
443
|
-
async addTaskLink(taskId, linksTo) {
|
|
444
|
-
await this.request(this.taskPath(taskId, `/link/${linksTo}`), {
|
|
445
|
-
method: "POST"
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
async deleteTaskLink(taskId, linksTo) {
|
|
449
|
-
await this.request(this.taskPath(taskId, `/link/${linksTo}`), {
|
|
450
|
-
method: "DELETE"
|
|
451
|
-
});
|
|
452
|
-
}
|
|
453
|
-
async getListCustomFields(listId) {
|
|
454
|
-
const data = await this.request(`/list/${listId}/field`);
|
|
455
|
-
return readCollectionField(
|
|
456
|
-
data,
|
|
457
|
-
"fields",
|
|
458
|
-
"list custom fields"
|
|
459
|
-
);
|
|
460
|
-
}
|
|
461
|
-
async createChecklist(taskId, name) {
|
|
462
|
-
const data = await this.request(this.taskPath(taskId, "/checklist"), {
|
|
463
|
-
method: "POST",
|
|
464
|
-
body: JSON.stringify({ name })
|
|
465
|
-
});
|
|
466
|
-
return expectRecordField(
|
|
467
|
-
data,
|
|
468
|
-
"checklist",
|
|
469
|
-
"checklist"
|
|
470
|
-
);
|
|
471
|
-
}
|
|
472
|
-
async deleteChecklist(checklistId) {
|
|
473
|
-
await this.request(`/checklist/${checklistId}`, { method: "DELETE" });
|
|
474
|
-
}
|
|
475
|
-
async createChecklistItem(checklistId, name) {
|
|
476
|
-
const data = await this.request(
|
|
477
|
-
`/checklist/${checklistId}/checklist_item`,
|
|
478
|
-
{ method: "POST", body: JSON.stringify({ name }) }
|
|
479
|
-
);
|
|
480
|
-
return expectRecordField(
|
|
481
|
-
data,
|
|
482
|
-
"checklist",
|
|
483
|
-
"checklist"
|
|
484
|
-
);
|
|
485
|
-
}
|
|
486
|
-
async editChecklistItem(checklistId, checklistItemId, updates) {
|
|
487
|
-
const data = await this.request(
|
|
488
|
-
`/checklist/${checklistId}/checklist_item/${checklistItemId}`,
|
|
489
|
-
{ method: "PUT", body: JSON.stringify(updates) }
|
|
490
|
-
);
|
|
491
|
-
return expectRecordField(
|
|
492
|
-
data,
|
|
493
|
-
"checklist",
|
|
494
|
-
"checklist"
|
|
495
|
-
);
|
|
496
|
-
}
|
|
497
|
-
async deleteChecklistItem(checklistId, checklistItemId) {
|
|
498
|
-
await this.request(
|
|
499
|
-
`/checklist/${checklistId}/checklist_item/${checklistItemId}`,
|
|
500
|
-
{ method: "DELETE" }
|
|
501
|
-
);
|
|
502
|
-
}
|
|
503
|
-
async startTimeEntry(teamId, taskId, description) {
|
|
504
|
-
const body = {
|
|
505
|
-
tid: taskId,
|
|
506
|
-
start: Date.now(),
|
|
507
|
-
duration: -1
|
|
508
|
-
};
|
|
509
|
-
if (description) body.description = description;
|
|
510
|
-
const data = await this.request(
|
|
511
|
-
`/team/${teamId}/time_entries/start${this.customIdQueryParams(taskId)}`,
|
|
512
|
-
{
|
|
513
|
-
method: "POST",
|
|
514
|
-
body: JSON.stringify(body)
|
|
515
|
-
}
|
|
516
|
-
);
|
|
517
|
-
return data.data;
|
|
518
|
-
}
|
|
519
|
-
async stopTimeEntry(teamId) {
|
|
520
|
-
const data = await this.request(`/team/${teamId}/time_entries/stop`, {
|
|
521
|
-
method: "POST"
|
|
522
|
-
});
|
|
523
|
-
return data.data;
|
|
524
|
-
}
|
|
525
|
-
async getRunningTimeEntry(teamId) {
|
|
526
|
-
const data = await this.request(
|
|
527
|
-
`/team/${teamId}/time_entries/current`
|
|
528
|
-
);
|
|
529
|
-
return data.data ?? null;
|
|
530
|
-
}
|
|
531
|
-
async createTimeEntry(teamId, taskId, duration, opts) {
|
|
532
|
-
const start = opts?.start ?? Date.now() - duration;
|
|
533
|
-
const body = {
|
|
534
|
-
tid: taskId,
|
|
535
|
-
start,
|
|
536
|
-
duration
|
|
537
|
-
};
|
|
538
|
-
if (opts?.description) body.description = opts.description;
|
|
539
|
-
const data = await this.request(
|
|
540
|
-
`/team/${teamId}/time_entries${this.customIdQueryParams(taskId)}`,
|
|
541
|
-
{
|
|
542
|
-
method: "POST",
|
|
543
|
-
body: JSON.stringify(body)
|
|
544
|
-
}
|
|
545
|
-
);
|
|
546
|
-
return data.data;
|
|
547
|
-
}
|
|
548
|
-
async getTimeEntries(teamId, opts) {
|
|
549
|
-
const params = new URLSearchParams();
|
|
550
|
-
if (opts?.startDate != null) params.set("start_date", String(opts.startDate));
|
|
551
|
-
if (opts?.endDate != null) params.set("end_date", String(opts.endDate));
|
|
552
|
-
if (opts?.spaceId) params.set("space_id", opts.spaceId);
|
|
553
|
-
if (opts?.listId) params.set("list_id", opts.listId);
|
|
554
|
-
if (opts?.assigneeId) params.set("assignee", opts.assigneeId);
|
|
555
|
-
const query = params.toString();
|
|
556
|
-
const url = `/team/${teamId}/time_entries${query ? `?${query}` : ""}`;
|
|
557
|
-
const data = await this.request(url);
|
|
558
|
-
const entries = readCollectionField(
|
|
559
|
-
data,
|
|
560
|
-
"data",
|
|
561
|
-
"time entries"
|
|
562
|
-
);
|
|
563
|
-
if (opts?.taskId) {
|
|
564
|
-
return entries.filter((e) => e.task?.id === opts.taskId);
|
|
565
|
-
}
|
|
566
|
-
return entries;
|
|
567
|
-
}
|
|
568
|
-
async updateTimeEntry(teamId, timeEntryId, updates) {
|
|
569
|
-
const data = await this.request(
|
|
570
|
-
`/team/${teamId}/time_entries/${timeEntryId}`,
|
|
571
|
-
{ method: "PUT", body: JSON.stringify(updates) }
|
|
572
|
-
);
|
|
573
|
-
return data.data;
|
|
574
|
-
}
|
|
575
|
-
async getSpaceTags(spaceId) {
|
|
576
|
-
const data = await this.request(`/space/${spaceId}/tag`);
|
|
577
|
-
return readCollectionField(data, "tags", "space tags");
|
|
578
|
-
}
|
|
579
|
-
async createSpaceTag(spaceId, name, fg, bg) {
|
|
580
|
-
await this.request(`/space/${spaceId}/tag`, {
|
|
581
|
-
method: "POST",
|
|
582
|
-
body: JSON.stringify({
|
|
583
|
-
tag: { name, tag_fg: fg ?? "#000000", tag_bg: bg ?? "#04A9F4" }
|
|
584
|
-
})
|
|
585
|
-
});
|
|
586
|
-
}
|
|
587
|
-
async deleteSpaceTag(spaceId, tagName) {
|
|
588
|
-
await this.request(
|
|
589
|
-
`/space/${spaceId}/tag/${encodeURIComponent(tagName)}`,
|
|
590
|
-
{ method: "DELETE" }
|
|
591
|
-
);
|
|
592
|
-
}
|
|
593
|
-
async getWorkspaceMembers(teamId) {
|
|
594
|
-
const data = await this.request("/team");
|
|
595
|
-
const team = readCollectionField(
|
|
596
|
-
data,
|
|
597
|
-
"teams",
|
|
598
|
-
"workspace members"
|
|
599
|
-
).find((t) => t.id === teamId);
|
|
600
|
-
return team?.members?.map((m) => m.user) ?? [];
|
|
601
|
-
}
|
|
602
|
-
async deleteTimeEntry(teamId, timeEntryId) {
|
|
603
|
-
await this.request(`/team/${teamId}/time_entries/${timeEntryId}`, {
|
|
604
|
-
method: "DELETE"
|
|
605
|
-
});
|
|
606
|
-
}
|
|
607
|
-
async createTaskAttachment(taskId, filePath) {
|
|
608
|
-
const { readFile } = await import("fs/promises");
|
|
609
|
-
const { basename: basename2 } = await import("path");
|
|
610
|
-
const fileBuffer = await readFile(filePath);
|
|
611
|
-
const fileName = basename2(filePath);
|
|
612
|
-
const formData = new FormData();
|
|
613
|
-
formData.append("attachment", new Blob([fileBuffer]), fileName);
|
|
614
|
-
const res = await fetch(`${BASE_URL}${this.taskPath(taskId, "/attachment")}`, {
|
|
615
|
-
method: "POST",
|
|
616
|
-
headers: { Authorization: this.apiToken },
|
|
617
|
-
body: formData,
|
|
618
|
-
signal: AbortSignal.timeout(6e4)
|
|
619
|
-
});
|
|
620
|
-
if (!res.ok) {
|
|
621
|
-
let msg;
|
|
622
|
-
try {
|
|
623
|
-
const data2 = await res.json();
|
|
624
|
-
msg = data2.err ?? `HTTP ${res.status}`;
|
|
625
|
-
} catch {
|
|
626
|
-
msg = `HTTP ${res.status}`;
|
|
627
|
-
}
|
|
628
|
-
throw new Error(`ClickUp API error ${res.status}: ${msg}`);
|
|
629
|
-
}
|
|
630
|
-
let data;
|
|
631
|
-
try {
|
|
632
|
-
data = await res.json();
|
|
633
|
-
} catch {
|
|
634
|
-
throw new Error(`ClickUp API error ${res.status}: response was not valid JSON`);
|
|
635
|
-
}
|
|
636
|
-
return data;
|
|
637
|
-
}
|
|
638
|
-
async getDocs(workspaceId) {
|
|
639
|
-
const data = await this.requestV3(`/workspaces/${workspaceId}/docs`);
|
|
640
|
-
return readCollectionField(data, "docs", "docs");
|
|
641
|
-
}
|
|
642
|
-
async getDocPage(workspaceId, docId, pageId) {
|
|
643
|
-
return this.requestV3(
|
|
644
|
-
`/workspaces/${workspaceId}/docs/${docId}/pages/${pageId}?content_format=text/md`
|
|
645
|
-
);
|
|
646
|
-
}
|
|
647
|
-
async createDoc(workspaceId, title, content, parentId) {
|
|
648
|
-
const body = { title };
|
|
649
|
-
if (content) body.content = content;
|
|
650
|
-
if (parentId) {
|
|
651
|
-
body.parent_id = parentId;
|
|
652
|
-
body.parent_type = "doc";
|
|
653
|
-
}
|
|
654
|
-
return this.requestV3(`/workspaces/${workspaceId}/docs`, {
|
|
655
|
-
method: "POST",
|
|
656
|
-
body: JSON.stringify(body)
|
|
657
|
-
});
|
|
658
|
-
}
|
|
659
|
-
async createDocPage(workspaceId, docId, name, content, parentPageId) {
|
|
660
|
-
const body = { name, content_format: "text/md" };
|
|
661
|
-
if (content) body.content = content;
|
|
662
|
-
if (parentPageId) body.parent_page_id = parentPageId;
|
|
663
|
-
return this.requestV3(`/workspaces/${workspaceId}/docs/${docId}/pages`, {
|
|
664
|
-
method: "POST",
|
|
665
|
-
body: JSON.stringify(body)
|
|
666
|
-
});
|
|
667
|
-
}
|
|
668
|
-
async editDocPage(workspaceId, docId, pageId, updates) {
|
|
669
|
-
return this.requestV3(`/workspaces/${workspaceId}/docs/${docId}/pages/${pageId}`, {
|
|
670
|
-
method: "PUT",
|
|
671
|
-
body: JSON.stringify(updates)
|
|
672
|
-
});
|
|
673
|
-
}
|
|
674
|
-
async getDoc(workspaceId, docId) {
|
|
675
|
-
return this.requestV3(`/workspaces/${workspaceId}/docs/${docId}`);
|
|
676
|
-
}
|
|
677
|
-
async getDocPageListing(workspaceId, docId) {
|
|
678
|
-
const data = await this.requestV3(
|
|
679
|
-
`/workspaces/${workspaceId}/docs/${docId}/pagelisting`
|
|
680
|
-
);
|
|
681
|
-
return readCollectionField(
|
|
682
|
-
data,
|
|
683
|
-
"pages",
|
|
684
|
-
"doc page listing"
|
|
685
|
-
);
|
|
686
|
-
}
|
|
687
|
-
async getDocPages(workspaceId, docId) {
|
|
688
|
-
const data = await this.requestV3(
|
|
689
|
-
`/workspaces/${workspaceId}/docs/${docId}/pages?content_format=text/md`
|
|
690
|
-
);
|
|
691
|
-
return readCollectionField(data, "pages", "doc pages");
|
|
692
|
-
}
|
|
693
|
-
async getGoals(teamId) {
|
|
694
|
-
const data = await this.request(`/team/${teamId}/goal`);
|
|
695
|
-
return readCollectionField(data, "goals", "goals");
|
|
696
|
-
}
|
|
697
|
-
async createGoal(teamId, name, opts) {
|
|
698
|
-
const body = { name, multiple_owners: true };
|
|
699
|
-
if (opts?.description) body.description = opts.description;
|
|
700
|
-
if (opts?.dueDate != null) body.due_date = opts.dueDate;
|
|
701
|
-
if (opts?.color) body.color = opts.color;
|
|
702
|
-
const data = await this.request(`/team/${teamId}/goal`, {
|
|
703
|
-
method: "POST",
|
|
704
|
-
body: JSON.stringify(body)
|
|
705
|
-
});
|
|
706
|
-
return data.goal;
|
|
707
|
-
}
|
|
708
|
-
async updateGoal(goalId, updates) {
|
|
709
|
-
const data = await this.request(`/goal/${goalId}`, {
|
|
710
|
-
method: "PUT",
|
|
711
|
-
body: JSON.stringify(updates)
|
|
712
|
-
});
|
|
713
|
-
return data.goal;
|
|
714
|
-
}
|
|
715
|
-
async getKeyResults(goalId) {
|
|
716
|
-
const data = await this.request(`/goal/${goalId}`);
|
|
717
|
-
return data.goal?.key_results ?? [];
|
|
718
|
-
}
|
|
719
|
-
async createKeyResult(goalId, name, type, stepsEnd) {
|
|
720
|
-
const data = await this.request(`/goal/${goalId}/key_result`, {
|
|
721
|
-
method: "POST",
|
|
722
|
-
body: JSON.stringify({
|
|
723
|
-
name,
|
|
724
|
-
type,
|
|
725
|
-
steps_start: 0,
|
|
726
|
-
steps_end: stepsEnd,
|
|
727
|
-
unit: type === "number" ? "items" : "%"
|
|
728
|
-
})
|
|
729
|
-
});
|
|
730
|
-
return data.key_result;
|
|
731
|
-
}
|
|
732
|
-
async updateKeyResult(keyResultId, updates) {
|
|
733
|
-
const data = await this.request(`/key_result/${keyResultId}`, {
|
|
734
|
-
method: "PUT",
|
|
735
|
-
body: JSON.stringify(updates)
|
|
736
|
-
});
|
|
737
|
-
return data.key_result;
|
|
738
|
-
}
|
|
739
|
-
async deleteGoal(goalId) {
|
|
740
|
-
await this.request(`/goal/${goalId}`, { method: "DELETE" });
|
|
741
|
-
}
|
|
742
|
-
async deleteKeyResult(keyResultId) {
|
|
743
|
-
await this.request(`/key_result/${keyResultId}`, { method: "DELETE" });
|
|
744
|
-
}
|
|
745
|
-
async deleteDoc(workspaceId, docId) {
|
|
746
|
-
await this.requestV3(`/workspaces/${workspaceId}/docs/${docId}`, {
|
|
747
|
-
method: "DELETE"
|
|
748
|
-
});
|
|
749
|
-
}
|
|
750
|
-
async deleteDocPage(workspaceId, docId, pageId) {
|
|
751
|
-
await this.requestV3(
|
|
752
|
-
`/workspaces/${workspaceId}/docs/${docId}/pages/${pageId}`,
|
|
753
|
-
{ method: "DELETE" }
|
|
754
|
-
);
|
|
755
|
-
}
|
|
756
|
-
async updateSpaceTag(spaceId, tagName, updates) {
|
|
757
|
-
await this.request(
|
|
758
|
-
`/space/${spaceId}/tag/${encodeURIComponent(tagName)}`,
|
|
759
|
-
{
|
|
760
|
-
method: "PUT",
|
|
761
|
-
body: JSON.stringify({
|
|
762
|
-
tag: {
|
|
763
|
-
name: updates.name,
|
|
764
|
-
tag_fg: updates.tag_fg ?? "#000000",
|
|
765
|
-
tag_bg: updates.tag_bg ?? "#04A9F4"
|
|
766
|
-
}
|
|
767
|
-
})
|
|
768
|
-
}
|
|
769
|
-
);
|
|
770
|
-
}
|
|
771
|
-
async getTaskTemplates(teamId) {
|
|
772
|
-
const data = await this.request(
|
|
773
|
-
`/team/${teamId}/taskTemplate?page=0`
|
|
774
|
-
);
|
|
775
|
-
return readCollectionField(
|
|
776
|
-
data,
|
|
777
|
-
"templates",
|
|
778
|
-
"task templates"
|
|
779
|
-
);
|
|
780
|
-
}
|
|
781
|
-
async createTaskFromTemplate(listId, templateId, name) {
|
|
782
|
-
return this.request(`/list/${listId}/taskTemplate/${templateId}`, {
|
|
783
|
-
method: "POST",
|
|
784
|
-
body: JSON.stringify({ name })
|
|
785
|
-
});
|
|
786
|
-
}
|
|
787
|
-
async createCustomField(teamId, name, type, opts) {
|
|
788
|
-
const typeConfig = {};
|
|
789
|
-
if (opts?.options?.length) {
|
|
790
|
-
typeConfig.options = opts.options.map((optName, i) => ({
|
|
791
|
-
name: optName,
|
|
792
|
-
orderindex: i
|
|
793
|
-
}));
|
|
794
|
-
}
|
|
795
|
-
const body = {
|
|
796
|
-
name,
|
|
797
|
-
type,
|
|
798
|
-
type_config: typeConfig,
|
|
799
|
-
description: opts?.description ?? "",
|
|
800
|
-
required: opts?.required ?? false,
|
|
801
|
-
pinned: false,
|
|
802
|
-
hide_from_guests: false,
|
|
803
|
-
required_on_subtasks: false,
|
|
804
|
-
private: false,
|
|
805
|
-
permission_level: null,
|
|
806
|
-
members: [],
|
|
807
|
-
groups: []
|
|
808
|
-
};
|
|
809
|
-
const data = await this.request(
|
|
810
|
-
`/field?workspace_id=${teamId}`,
|
|
811
|
-
{ method: "POST", body: JSON.stringify(body) }
|
|
812
|
-
);
|
|
813
|
-
return data.data;
|
|
814
|
-
}
|
|
815
|
-
};
|
|
816
|
-
|
|
817
|
-
// src/config.ts
|
|
818
|
-
import fs from "fs";
|
|
819
|
-
import { homedir } from "os";
|
|
820
|
-
import { join } from "path";
|
|
821
|
-
function isRecord2(value) {
|
|
822
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
823
|
-
}
|
|
824
|
-
function readConfigString(parsed, key, path, strict) {
|
|
825
|
-
const value = parsed[key];
|
|
826
|
-
if (value === void 0) return void 0;
|
|
827
|
-
if (typeof value !== "string") {
|
|
828
|
-
if (strict) {
|
|
829
|
-
throw new Error(`Config field ${key} must be a string in ${path}.`);
|
|
830
|
-
}
|
|
831
|
-
return void 0;
|
|
832
|
-
}
|
|
833
|
-
const trimmed = value.trim();
|
|
834
|
-
return trimmed || void 0;
|
|
835
|
-
}
|
|
836
|
-
function parseConfigFile(raw, path, strictFields, strictRoot = strictFields) {
|
|
837
|
-
let parsed;
|
|
838
|
-
try {
|
|
839
|
-
parsed = JSON.parse(raw);
|
|
840
|
-
} catch {
|
|
841
|
-
if (strictRoot) {
|
|
842
|
-
throw new Error(`Config file at ${path} contains invalid JSON. Please check the file syntax.`);
|
|
843
|
-
}
|
|
844
|
-
return {};
|
|
845
|
-
}
|
|
846
|
-
if (!isRecord2(parsed)) {
|
|
847
|
-
if (strictRoot) {
|
|
848
|
-
throw new Error(`Config file at ${path} must contain a JSON object.`);
|
|
849
|
-
}
|
|
850
|
-
return {};
|
|
851
|
-
}
|
|
852
|
-
const apiToken = readConfigString(parsed, "apiToken", path, strictFields);
|
|
853
|
-
const teamId = readConfigString(parsed, "teamId", path, strictFields);
|
|
854
|
-
const sprintFolderId = readConfigString(parsed, "sprintFolderId", path, strictFields);
|
|
855
|
-
return {
|
|
856
|
-
...apiToken ? { apiToken } : {},
|
|
857
|
-
...teamId ? { teamId } : {},
|
|
858
|
-
...sprintFolderId ? { sprintFolderId } : {}
|
|
859
|
-
};
|
|
860
|
-
}
|
|
861
|
-
function trimConfigValue(value) {
|
|
862
|
-
const trimmed = value?.trim();
|
|
863
|
-
return trimmed || void 0;
|
|
864
|
-
}
|
|
865
|
-
function configDir() {
|
|
866
|
-
const xdg = process.env.XDG_CONFIG_HOME;
|
|
867
|
-
if (xdg) return join(xdg, "cup");
|
|
868
|
-
return join(homedir(), ".config", "cup");
|
|
869
|
-
}
|
|
870
|
-
function legacyConfigDir() {
|
|
871
|
-
const xdg = process.env.XDG_CONFIG_HOME;
|
|
872
|
-
if (xdg) return join(xdg, "cu");
|
|
873
|
-
return join(homedir(), ".config", "cu");
|
|
874
|
-
}
|
|
875
|
-
var migrationChecked = false;
|
|
876
|
-
function migrateFromLegacy() {
|
|
877
|
-
if (migrationChecked) return;
|
|
878
|
-
migrationChecked = true;
|
|
879
|
-
const legacy = legacyConfigDir();
|
|
880
|
-
const current = configDir();
|
|
881
|
-
if (fs.existsSync(join(legacy, "config.json")) && !fs.existsSync(join(current, "config.json"))) {
|
|
882
|
-
fs.mkdirSync(current, { recursive: true, mode: 448 });
|
|
883
|
-
fs.copyFileSync(join(legacy, "config.json"), join(current, "config.json"));
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
function configPath() {
|
|
887
|
-
return join(configDir(), "config.json");
|
|
888
|
-
}
|
|
889
|
-
function migrateToMultiProfile(parsed, filePath) {
|
|
890
|
-
if (typeof parsed.apiToken === "string" && !parsed.profiles) {
|
|
891
|
-
const profile = {};
|
|
892
|
-
const token = trimConfigValue(parsed.apiToken);
|
|
893
|
-
if (token) profile.apiToken = token;
|
|
894
|
-
const team = typeof parsed.teamId === "string" ? trimConfigValue(parsed.teamId) : void 0;
|
|
895
|
-
if (team) profile.teamId = team;
|
|
896
|
-
const sprint = typeof parsed.sprintFolderId === "string" ? trimConfigValue(parsed.sprintFolderId) : void 0;
|
|
897
|
-
if (sprint) profile.sprintFolderId = sprint;
|
|
898
|
-
const migrated = {
|
|
899
|
-
defaultProfile: "default",
|
|
900
|
-
profiles: { default: profile }
|
|
901
|
-
};
|
|
902
|
-
const dir = configDir();
|
|
903
|
-
if (!fs.existsSync(dir)) {
|
|
904
|
-
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
905
|
-
}
|
|
906
|
-
fs.writeFileSync(join(dir, "config.json"), JSON.stringify(migrated, null, 2) + "\n", {
|
|
907
|
-
encoding: "utf-8",
|
|
908
|
-
mode: 384
|
|
909
|
-
});
|
|
910
|
-
return migrated;
|
|
911
|
-
}
|
|
912
|
-
if (isRecord2(parsed.profiles)) {
|
|
913
|
-
const profiles = {};
|
|
914
|
-
for (const [name, value] of Object.entries(parsed.profiles)) {
|
|
915
|
-
if (isRecord2(value)) {
|
|
916
|
-
const p = {};
|
|
917
|
-
if (typeof value.apiToken === "string" && value.apiToken.trim())
|
|
918
|
-
p.apiToken = value.apiToken.trim();
|
|
919
|
-
if (typeof value.teamId === "string" && value.teamId.trim()) p.teamId = value.teamId.trim();
|
|
920
|
-
if (typeof value.sprintFolderId === "string" && value.sprintFolderId.trim())
|
|
921
|
-
p.sprintFolderId = value.sprintFolderId.trim();
|
|
922
|
-
if (isRecord2(value.filters)) p.filters = value.filters;
|
|
923
|
-
profiles[name] = p;
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
return {
|
|
927
|
-
defaultProfile: typeof parsed.defaultProfile === "string" ? parsed.defaultProfile : "",
|
|
928
|
-
profiles
|
|
929
|
-
};
|
|
930
|
-
}
|
|
931
|
-
throw new Error(`Config file at ${filePath} has unrecognized format.`);
|
|
932
|
-
}
|
|
933
|
-
function parseRawConfig(filePath) {
|
|
934
|
-
const raw = fs.readFileSync(filePath, "utf-8");
|
|
935
|
-
let parsed;
|
|
936
|
-
try {
|
|
937
|
-
parsed = JSON.parse(raw);
|
|
938
|
-
} catch {
|
|
939
|
-
throw new Error(
|
|
940
|
-
`Config file at ${filePath} contains invalid JSON. Please check the file syntax.`
|
|
941
|
-
);
|
|
942
|
-
}
|
|
943
|
-
if (!isRecord2(parsed)) {
|
|
944
|
-
throw new Error(`Config file at ${filePath} must contain a JSON object.`);
|
|
945
|
-
}
|
|
946
|
-
return { parsed, raw };
|
|
947
|
-
}
|
|
948
|
-
function isOldFormat(parsed) {
|
|
949
|
-
return typeof parsed.apiToken === "string" && !parsed.profiles;
|
|
950
|
-
}
|
|
951
|
-
function loadConfig(profileName) {
|
|
952
|
-
migrateFromLegacy();
|
|
953
|
-
const envToken = process.env.CU_API_TOKEN?.trim();
|
|
954
|
-
const envTeamId = process.env.CU_TEAM_ID?.trim();
|
|
955
|
-
if (envToken && envTeamId) {
|
|
956
|
-
if (!envToken.startsWith("pk_")) {
|
|
957
|
-
throw new Error("CU_API_TOKEN must start with pk_.");
|
|
958
|
-
}
|
|
959
|
-
return { apiToken: envToken, teamId: envTeamId };
|
|
960
|
-
}
|
|
961
|
-
const path = configPath();
|
|
962
|
-
if (!fs.existsSync(path)) {
|
|
963
|
-
if (envToken || envTeamId) {
|
|
964
|
-
throw new Error("Both CU_API_TOKEN and CU_TEAM_ID must be set, or run: cup init");
|
|
965
|
-
}
|
|
966
|
-
throw new Error("Config missing required field: apiToken.\nSet CU_API_TOKEN or run: cup init");
|
|
967
|
-
}
|
|
968
|
-
const { parsed } = parseRawConfig(path);
|
|
969
|
-
if (isOldFormat(parsed)) {
|
|
970
|
-
const fileConfig = parseConfigFile(JSON.stringify(parsed), path, true);
|
|
971
|
-
const apiToken2 = envToken ?? fileConfig.apiToken;
|
|
972
|
-
if (!apiToken2) {
|
|
973
|
-
throw new Error("Config missing required field: apiToken.\nSet CU_API_TOKEN or run: cup init");
|
|
974
|
-
}
|
|
975
|
-
if (!apiToken2.startsWith("pk_")) {
|
|
976
|
-
throw new Error("Config apiToken must start with pk_. The configured token does not.");
|
|
977
|
-
}
|
|
978
|
-
const teamId2 = envTeamId ?? fileConfig.teamId;
|
|
979
|
-
if (!teamId2) {
|
|
980
|
-
throw new Error("Config missing required field: teamId.\nSet CU_TEAM_ID or run: cup init");
|
|
981
|
-
}
|
|
982
|
-
migrateToMultiProfile(parsed, path);
|
|
983
|
-
return {
|
|
984
|
-
apiToken: apiToken2,
|
|
985
|
-
teamId: teamId2,
|
|
986
|
-
...fileConfig.sprintFolderId ? { sprintFolderId: fileConfig.sprintFolderId } : {}
|
|
987
|
-
};
|
|
988
|
-
}
|
|
989
|
-
const multi = loadMultiProfileConfig();
|
|
990
|
-
const resolvedProfile = profileName ?? process.env.CU_PROFILE?.trim() ?? multi.defaultProfile;
|
|
991
|
-
if (!resolvedProfile) {
|
|
992
|
-
throw new Error("No default profile set. Run: cup profile use <name>");
|
|
993
|
-
}
|
|
994
|
-
const profile = multi.profiles[resolvedProfile];
|
|
995
|
-
if (!profile) {
|
|
996
|
-
const available = Object.keys(multi.profiles).join(", ");
|
|
997
|
-
throw new Error(`Profile "${resolvedProfile}" not found. Available: ${available}`);
|
|
998
|
-
}
|
|
999
|
-
const apiToken = envToken ?? profile.apiToken?.trim();
|
|
1000
|
-
if (!apiToken) {
|
|
1001
|
-
throw new Error(
|
|
1002
|
-
`Profile "${resolvedProfile}" missing apiToken. Run: cup profile add ${resolvedProfile}`
|
|
1003
|
-
);
|
|
1004
|
-
}
|
|
1005
|
-
if (!apiToken.startsWith("pk_")) {
|
|
1006
|
-
throw new Error("Config apiToken must start with pk_. The configured token does not.");
|
|
1007
|
-
}
|
|
1008
|
-
const teamId = envTeamId ?? profile.teamId?.trim();
|
|
1009
|
-
if (!teamId) {
|
|
1010
|
-
throw new Error(`Profile "${resolvedProfile}" missing teamId.`);
|
|
1011
|
-
}
|
|
1012
|
-
return {
|
|
1013
|
-
apiToken,
|
|
1014
|
-
teamId,
|
|
1015
|
-
...profile.sprintFolderId ? { sprintFolderId: profile.sprintFolderId } : {}
|
|
1016
|
-
};
|
|
1017
|
-
}
|
|
1018
|
-
function loadMultiProfileConfig() {
|
|
1019
|
-
migrateFromLegacy();
|
|
1020
|
-
const path = configPath();
|
|
1021
|
-
if (!fs.existsSync(path)) {
|
|
1022
|
-
return { defaultProfile: "", profiles: {} };
|
|
1023
|
-
}
|
|
1024
|
-
let parsed;
|
|
1025
|
-
try {
|
|
1026
|
-
const raw = fs.readFileSync(path, "utf-8");
|
|
1027
|
-
parsed = JSON.parse(raw);
|
|
1028
|
-
} catch {
|
|
1029
|
-
return { defaultProfile: "", profiles: {} };
|
|
1030
|
-
}
|
|
1031
|
-
if (!isRecord2(parsed)) return { defaultProfile: "", profiles: {} };
|
|
1032
|
-
if (isOldFormat(parsed)) {
|
|
1033
|
-
return migrateToMultiProfile(parsed, path);
|
|
1034
|
-
}
|
|
1035
|
-
return migrateToMultiProfile(parsed, path);
|
|
1036
|
-
}
|
|
1037
|
-
function saveMultiProfileConfig(config) {
|
|
1038
|
-
const dir = configDir();
|
|
1039
|
-
if (!fs.existsSync(dir)) {
|
|
1040
|
-
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
1041
|
-
}
|
|
1042
|
-
fs.writeFileSync(join(dir, "config.json"), JSON.stringify(config, null, 2) + "\n", {
|
|
1043
|
-
encoding: "utf-8",
|
|
1044
|
-
mode: 384
|
|
1045
|
-
});
|
|
1046
|
-
}
|
|
1047
|
-
function addProfile(name, profile) {
|
|
1048
|
-
const multi = loadMultiProfileConfig();
|
|
1049
|
-
multi.profiles[name] = profile;
|
|
1050
|
-
if (!multi.defaultProfile) multi.defaultProfile = name;
|
|
1051
|
-
saveMultiProfileConfig(multi);
|
|
1052
|
-
}
|
|
1053
|
-
function removeProfile(name) {
|
|
1054
|
-
const multi = loadMultiProfileConfig();
|
|
1055
|
-
if (!multi.profiles[name]) {
|
|
1056
|
-
throw new Error(`Profile "${name}" not found.`);
|
|
1057
|
-
}
|
|
1058
|
-
const keys = Object.keys(multi.profiles);
|
|
1059
|
-
if (keys.length <= 1) {
|
|
1060
|
-
throw new Error("Cannot remove the last profile.");
|
|
1061
|
-
}
|
|
1062
|
-
delete multi.profiles[name];
|
|
1063
|
-
if (multi.defaultProfile === name) {
|
|
1064
|
-
multi.defaultProfile = Object.keys(multi.profiles)[0] ?? "";
|
|
1065
|
-
}
|
|
1066
|
-
saveMultiProfileConfig(multi);
|
|
1067
|
-
}
|
|
1068
|
-
function setDefaultProfile(name) {
|
|
1069
|
-
const multi = loadMultiProfileConfig();
|
|
1070
|
-
if (!multi.profiles[name]) {
|
|
1071
|
-
const available = Object.keys(multi.profiles).join(", ");
|
|
1072
|
-
throw new Error(`Profile "${name}" not found. Available: ${available}`);
|
|
1073
|
-
}
|
|
1074
|
-
multi.defaultProfile = name;
|
|
1075
|
-
saveMultiProfileConfig(multi);
|
|
1076
|
-
}
|
|
1077
|
-
function listProfiles() {
|
|
1078
|
-
const multi = loadMultiProfileConfig();
|
|
1079
|
-
return Object.entries(multi.profiles).map(([name, profile]) => ({
|
|
1080
|
-
name,
|
|
1081
|
-
isDefault: name === multi.defaultProfile,
|
|
1082
|
-
teamId: profile.teamId
|
|
1083
|
-
}));
|
|
1084
|
-
}
|
|
1085
|
-
function loadRawConfig(profileName) {
|
|
1086
|
-
migrateFromLegacy();
|
|
1087
|
-
const path = configPath();
|
|
1088
|
-
if (!fs.existsSync(path)) return {};
|
|
1089
|
-
let parsed;
|
|
1090
|
-
try {
|
|
1091
|
-
const raw = fs.readFileSync(path, "utf-8");
|
|
1092
|
-
parsed = JSON.parse(raw);
|
|
1093
|
-
} catch {
|
|
1094
|
-
return {};
|
|
1095
|
-
}
|
|
1096
|
-
if (!isRecord2(parsed)) {
|
|
1097
|
-
throw new Error(`Config file at ${path} must contain a JSON object.`);
|
|
1098
|
-
}
|
|
1099
|
-
if (isOldFormat(parsed)) {
|
|
1100
|
-
return parseConfigFile(JSON.stringify(parsed), path, false, true);
|
|
1101
|
-
}
|
|
1102
|
-
const multi = migrateToMultiProfile(parsed, path);
|
|
1103
|
-
const name = profileName || multi.defaultProfile || "default";
|
|
1104
|
-
return multi.profiles[name] ?? {};
|
|
1105
|
-
}
|
|
1106
|
-
function getConfigPath() {
|
|
1107
|
-
migrateFromLegacy();
|
|
1108
|
-
return configPath();
|
|
1109
|
-
}
|
|
1110
|
-
function getFilters(profileName) {
|
|
1111
|
-
const multi = loadMultiProfileConfig();
|
|
1112
|
-
const name = profileName ?? process.env.CU_PROFILE?.trim() ?? multi.defaultProfile;
|
|
1113
|
-
const profile = name ? multi.profiles[name] ?? {} : {};
|
|
1114
|
-
return profile.filters ?? {};
|
|
1115
|
-
}
|
|
1116
|
-
function saveFilter(name, entry, profileName) {
|
|
1117
|
-
const multi = loadMultiProfileConfig();
|
|
1118
|
-
const pName = profileName ?? process.env.CU_PROFILE?.trim() ?? multi.defaultProfile ?? "default";
|
|
1119
|
-
const profile = multi.profiles[pName] ?? {};
|
|
1120
|
-
const filters = { ...profile.filters ?? {}, [name]: entry };
|
|
1121
|
-
multi.profiles[pName] = { ...profile, filters };
|
|
1122
|
-
if (!multi.defaultProfile) multi.defaultProfile = pName;
|
|
1123
|
-
saveMultiProfileConfig(multi);
|
|
1124
|
-
}
|
|
1125
|
-
function deleteFilter(name, profileName) {
|
|
1126
|
-
const multi = loadMultiProfileConfig();
|
|
1127
|
-
const pName = profileName ?? process.env.CU_PROFILE?.trim() ?? multi.defaultProfile ?? "default";
|
|
1128
|
-
const profile = multi.profiles[pName] ?? {};
|
|
1129
|
-
const filters = { ...profile.filters ?? {} };
|
|
1130
|
-
if (!(name in filters)) {
|
|
1131
|
-
throw new Error(`Filter "${name}" not found.`);
|
|
1132
|
-
}
|
|
1133
|
-
delete filters[name];
|
|
1134
|
-
multi.profiles[pName] = { ...profile, filters };
|
|
1135
|
-
saveMultiProfileConfig(multi);
|
|
1136
|
-
}
|
|
1137
|
-
function writeConfig(config, profileName) {
|
|
1138
|
-
const multi = loadMultiProfileConfig();
|
|
1139
|
-
const name = profileName || multi.defaultProfile || "default";
|
|
1140
|
-
const apiToken = trimConfigValue(config.apiToken) ?? void 0;
|
|
1141
|
-
const teamId = trimConfigValue(config.teamId) ?? void 0;
|
|
1142
|
-
const sprintFolderId = trimConfigValue(config.sprintFolderId);
|
|
1143
|
-
const normalizedConfig = {
|
|
1144
|
-
...apiToken ? { apiToken } : {},
|
|
1145
|
-
...teamId ? { teamId } : {},
|
|
1146
|
-
...sprintFolderId ? { sprintFolderId } : {}
|
|
1147
|
-
};
|
|
1148
|
-
multi.profiles[name] = {
|
|
1149
|
-
...multi.profiles[name],
|
|
1150
|
-
...normalizedConfig
|
|
1151
|
-
};
|
|
1152
|
-
if (!multi.defaultProfile) multi.defaultProfile = name;
|
|
1153
|
-
saveMultiProfileConfig(multi);
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
// src/date.ts
|
|
1157
|
-
function formatDate(ms) {
|
|
1158
|
-
return new Date(Number(ms)).toLocaleDateString("en-US", {
|
|
1159
|
-
month: "short",
|
|
1160
|
-
day: "numeric",
|
|
1161
|
-
year: "numeric"
|
|
1162
|
-
});
|
|
1163
|
-
}
|
|
1164
|
-
function formatTimestamp(ms) {
|
|
1165
|
-
return new Date(Number(ms)).toLocaleString("en-US", {
|
|
1166
|
-
month: "short",
|
|
1167
|
-
day: "numeric",
|
|
1168
|
-
hour: "numeric",
|
|
1169
|
-
minute: "2-digit"
|
|
1170
|
-
});
|
|
1171
|
-
}
|
|
1172
|
-
function formatDuration(ms) {
|
|
1173
|
-
const totalMinutes = Math.round(Math.abs(ms) / 6e4);
|
|
1174
|
-
const hours = Math.floor(totalMinutes / 60);
|
|
1175
|
-
const minutes = totalMinutes % 60;
|
|
1176
|
-
if (hours > 0 && minutes > 0) return `${hours}h ${minutes}m`;
|
|
1177
|
-
if (hours > 0) return `${hours}h`;
|
|
1178
|
-
return `${minutes}m`;
|
|
1179
|
-
}
|
|
1180
|
-
function formatDateISO(ms) {
|
|
1181
|
-
const d = new Date(Number(ms));
|
|
1182
|
-
const year = d.getUTCFullYear();
|
|
1183
|
-
const month = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
1184
|
-
const day = String(d.getUTCDate()).padStart(2, "0");
|
|
1185
|
-
return `${year}-${month}-${day}`;
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
// src/output.ts
|
|
1189
|
-
import chalk from "chalk";
|
|
1190
|
-
function isTTY() {
|
|
1191
|
-
return Boolean(process.stdout.isTTY);
|
|
1192
|
-
}
|
|
1193
|
-
function shouldOutputJson(forceJson) {
|
|
1194
|
-
if (forceJson) return true;
|
|
1195
|
-
if (process.env["CU_OUTPUT"] === "json") return true;
|
|
1196
|
-
return false;
|
|
1197
|
-
}
|
|
1198
|
-
function cell(value, width) {
|
|
1199
|
-
if (value.length > width) return value.slice(0, width - 1) + "\u2026";
|
|
1200
|
-
return value.padEnd(width);
|
|
1201
|
-
}
|
|
1202
|
-
function computeWidths(rows, columns) {
|
|
1203
|
-
return columns.map((col) => {
|
|
1204
|
-
const headerLen = col.label.length;
|
|
1205
|
-
const maxDataLen = rows.reduce((max, row) => {
|
|
1206
|
-
const val = String(row[col.key] ?? "");
|
|
1207
|
-
return Math.max(max, val.length);
|
|
1208
|
-
}, 0);
|
|
1209
|
-
const natural = Math.max(headerLen, maxDataLen);
|
|
1210
|
-
return col.maxWidth ? Math.min(natural, col.maxWidth) : natural;
|
|
1211
|
-
});
|
|
1212
|
-
}
|
|
1213
|
-
function formatTable(rows, columns) {
|
|
1214
|
-
const widths = computeWidths(rows, columns);
|
|
1215
|
-
const header = columns.map((c, i) => cell(c.label, widths[i])).join(" ");
|
|
1216
|
-
const divider = chalk.dim("-".repeat(header.replace(/\x1b\[[0-9;]*m/g, "").length));
|
|
1217
|
-
const lines = [chalk.bold(header), divider];
|
|
1218
|
-
for (const row of rows) {
|
|
1219
|
-
lines.push(
|
|
1220
|
-
columns.map((c, i) => {
|
|
1221
|
-
const raw = String(row[c.key] ?? "");
|
|
1222
|
-
const width = widths[i];
|
|
1223
|
-
const truncated = raw.length > width ? raw.slice(0, width - 1) + "\u2026" : raw;
|
|
1224
|
-
const padding = " ".repeat(Math.max(0, width - truncated.length));
|
|
1225
|
-
return c.format ? c.format(truncated, row) + padding : truncated + padding;
|
|
1226
|
-
}).join(" ")
|
|
1227
|
-
);
|
|
1228
|
-
}
|
|
1229
|
-
return lines.join("\n");
|
|
1230
|
-
}
|
|
1231
|
-
function colorStatus(status) {
|
|
1232
|
-
const lower = status.toLowerCase();
|
|
1233
|
-
if (lower.includes("done") || lower.includes("complete") || lower.includes("closed"))
|
|
1234
|
-
return chalk.green(status);
|
|
1235
|
-
if (lower.includes("progress") || lower.includes("review") || lower.includes("active"))
|
|
1236
|
-
return chalk.yellow(status);
|
|
1237
|
-
if (lower.includes("block") || lower.includes("stuck")) return chalk.red(status);
|
|
1238
|
-
return chalk.dim(status);
|
|
1239
|
-
}
|
|
1240
|
-
function colorPriority(priority) {
|
|
1241
|
-
const lower = priority.toLowerCase();
|
|
1242
|
-
if (lower === "urgent") return chalk.red(priority);
|
|
1243
|
-
if (lower === "high") return chalk.yellow(priority);
|
|
1244
|
-
if (lower === "normal") return priority;
|
|
1245
|
-
if (lower === "low") return chalk.dim(priority);
|
|
1246
|
-
return priority;
|
|
1247
|
-
}
|
|
1248
|
-
function colorDueDate(dateStr, rawTimestamp) {
|
|
1249
|
-
if (!dateStr) return dateStr;
|
|
1250
|
-
if (rawTimestamp) {
|
|
1251
|
-
const ts = Number(rawTimestamp);
|
|
1252
|
-
if (Number.isFinite(ts) && ts < Date.now()) return chalk.red(dateStr);
|
|
1253
|
-
}
|
|
1254
|
-
return dateStr;
|
|
1255
|
-
}
|
|
1256
|
-
var TASK_COLUMNS = [
|
|
1257
|
-
{ key: "id", label: "ID" },
|
|
1258
|
-
{ key: "name", label: "NAME", maxWidth: 60 },
|
|
1259
|
-
{ key: "status", label: "STATUS", maxWidth: 20, format: (v) => colorStatus(v) },
|
|
1260
|
-
{ key: "priority", label: "PRIORITY", maxWidth: 10, format: (v) => colorPriority(v) },
|
|
1261
|
-
{
|
|
1262
|
-
key: "due_date",
|
|
1263
|
-
label: "DUE",
|
|
1264
|
-
maxWidth: 15,
|
|
1265
|
-
format: (v, row) => v ? colorDueDate(v, row.dueRaw) : ""
|
|
1266
|
-
},
|
|
1267
|
-
{ key: "list", label: "LIST" }
|
|
1268
|
-
];
|
|
1269
|
-
|
|
1270
|
-
// src/markdown.ts
|
|
1271
|
-
function escapeCell(value) {
|
|
1272
|
-
return value.replace(/\|/g, "\\|");
|
|
1273
|
-
}
|
|
1274
|
-
function formatMarkdownTable(rows, columns) {
|
|
1275
|
-
const header = "| " + columns.map((c) => c.label).join(" | ") + " |";
|
|
1276
|
-
const divider = "| " + columns.map(() => "---").join(" | ") + " |";
|
|
1277
|
-
const lines = [header, divider];
|
|
1278
|
-
for (const row of rows) {
|
|
1279
|
-
const cells = columns.map((c) => escapeCell(String(row[c.key] ?? "")));
|
|
1280
|
-
lines.push("| " + cells.join(" | ") + " |");
|
|
1281
|
-
}
|
|
1282
|
-
return lines.join("\n");
|
|
1283
|
-
}
|
|
1284
|
-
var TASK_MD_COLUMNS = [
|
|
1285
|
-
{ key: "id", label: "ID" },
|
|
1286
|
-
{ key: "name", label: "Name" },
|
|
1287
|
-
{ key: "status", label: "Status" },
|
|
1288
|
-
{ key: "priority", label: "Priority" },
|
|
1289
|
-
{ key: "due_date", label: "Due" },
|
|
1290
|
-
{ key: "list", label: "List" }
|
|
1291
|
-
];
|
|
1292
|
-
function formatTasksMarkdown(tasks) {
|
|
1293
|
-
if (tasks.length === 0) return "No tasks found.";
|
|
1294
|
-
return formatMarkdownTable(tasks, TASK_MD_COLUMNS);
|
|
1295
|
-
}
|
|
1296
|
-
function formatCommentsMarkdown(comments) {
|
|
1297
|
-
if (comments.length === 0) return "No comments found.";
|
|
1298
|
-
return comments.map((c) => `**${c.user}** (${formatDateISO(c.date)})
|
|
1299
|
-
|
|
1300
|
-
${c.text}`).join("\n\n---\n\n");
|
|
1301
|
-
}
|
|
1302
|
-
var LIST_MD_COLUMNS = [
|
|
1303
|
-
{ key: "id", label: "ID" },
|
|
1304
|
-
{ key: "name", label: "Name" },
|
|
1305
|
-
{ key: "folder", label: "Folder" }
|
|
1306
|
-
];
|
|
1307
|
-
function formatListsMarkdown(lists) {
|
|
1308
|
-
if (lists.length === 0) return "No lists found.";
|
|
1309
|
-
return formatMarkdownTable(lists, LIST_MD_COLUMNS);
|
|
1310
|
-
}
|
|
1311
|
-
var SPACE_MD_COLUMNS = [
|
|
1312
|
-
{ key: "id", label: "ID" },
|
|
1313
|
-
{ key: "name", label: "Name" }
|
|
1314
|
-
];
|
|
1315
|
-
function formatSpacesMarkdown(spaces) {
|
|
1316
|
-
if (spaces.length === 0) return "No spaces found.";
|
|
1317
|
-
return formatMarkdownTable(spaces, SPACE_MD_COLUMNS);
|
|
1318
|
-
}
|
|
1319
|
-
function formatGroupedTasksMarkdown(groups) {
|
|
1320
|
-
const sections = groups.filter((g) => g.tasks.length > 0).map((g) => `## ${g.label}
|
|
1321
|
-
|
|
1322
|
-
${formatMarkdownTable(g.tasks, TASK_MD_COLUMNS)}`);
|
|
1323
|
-
if (sections.length === 0) return "No tasks found.";
|
|
1324
|
-
return sections.join("\n\n");
|
|
1325
|
-
}
|
|
1326
|
-
function formatTaskDetailMarkdown(task) {
|
|
1327
|
-
const lines = [`# ${task.name}`, ""];
|
|
1328
|
-
const isInitiative = (task.custom_item_id ?? 0) !== 0;
|
|
1329
|
-
const fields = [
|
|
1330
|
-
["ID", task.id],
|
|
1331
|
-
["Status", task.status.status],
|
|
1332
|
-
["Type", isInitiative ? "initiative" : "task"],
|
|
1333
|
-
["List", task.list.name],
|
|
1334
|
-
["URL", task.url],
|
|
1335
|
-
[
|
|
1336
|
-
"Assignees",
|
|
1337
|
-
task.assignees.length > 0 ? task.assignees.map((a) => a.username).join(", ") : void 0
|
|
1338
|
-
],
|
|
1339
|
-
["Priority", task.priority?.priority],
|
|
1340
|
-
["Parent", task.parent ?? void 0],
|
|
1341
|
-
["Start Date", task.start_date ? formatDateISO(task.start_date) : void 0],
|
|
1342
|
-
["Due Date", task.due_date ? formatDateISO(task.due_date) : void 0],
|
|
1343
|
-
[
|
|
1344
|
-
"Time Estimate",
|
|
1345
|
-
task.time_estimate != null && task.time_estimate > 0 ? formatDuration(task.time_estimate) : void 0
|
|
1346
|
-
],
|
|
1347
|
-
[
|
|
1348
|
-
"Time Spent",
|
|
1349
|
-
task.time_spent != null && task.time_spent > 0 ? formatDuration(task.time_spent) : void 0
|
|
1350
|
-
],
|
|
1351
|
-
["Tags", task.tags && task.tags.length > 0 ? task.tags.map((t) => t.name).join(", ") : void 0],
|
|
1352
|
-
[
|
|
1353
|
-
"Lists",
|
|
1354
|
-
task.locations && task.locations.length > 0 ? task.locations.map((l) => l.name).join(", ") : void 0
|
|
1355
|
-
],
|
|
1356
|
-
["Created", task.date_created ? formatDateISO(task.date_created) : void 0],
|
|
1357
|
-
["Updated", task.date_updated ? formatDateISO(task.date_updated) : void 0]
|
|
1358
|
-
];
|
|
1359
|
-
for (const [label, value] of fields) {
|
|
1360
|
-
if (value != null && value !== "") {
|
|
1361
|
-
lines.push(`**${label}:** ${value}`);
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
const descriptionContent = task.markdown_content ?? task.description;
|
|
1365
|
-
if (descriptionContent) {
|
|
1366
|
-
lines.push("", "## Description", "", descriptionContent);
|
|
1367
|
-
}
|
|
1368
|
-
if (task.checklists?.length) {
|
|
1369
|
-
lines.push("", "## Checklists", "");
|
|
1370
|
-
for (const cl of task.checklists) {
|
|
1371
|
-
const resolved = cl.items.filter((i) => i.resolved).length;
|
|
1372
|
-
lines.push(`### ${cl.name} (${resolved}/${cl.items.length})`, "");
|
|
1373
|
-
for (const item of cl.items) {
|
|
1374
|
-
lines.push(`- [${item.resolved ? "x" : " "}] ${item.name}`);
|
|
1375
|
-
}
|
|
1376
|
-
lines.push("");
|
|
1377
|
-
}
|
|
1378
|
-
}
|
|
1379
|
-
if (task.attachments?.length) {
|
|
1380
|
-
lines.push("", "## Attachments", "");
|
|
1381
|
-
for (const att of task.attachments) {
|
|
1382
|
-
lines.push(`- [${att.title}](${att.url})`);
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
if (task.dependencies?.length) {
|
|
1386
|
-
lines.push("", "## Dependencies", "");
|
|
1387
|
-
for (const dep of task.dependencies) {
|
|
1388
|
-
const direction = dep.depends_on === task.id ? "blocks" : "depends on";
|
|
1389
|
-
const otherId = dep.depends_on === task.id ? dep.task_id : dep.depends_on;
|
|
1390
|
-
lines.push(`- ${direction} ${otherId}`);
|
|
1391
|
-
}
|
|
1392
|
-
}
|
|
1393
|
-
if (task.linked_tasks?.length) {
|
|
1394
|
-
lines.push("", "## Linked Tasks", "");
|
|
1395
|
-
for (const lt of task.linked_tasks) {
|
|
1396
|
-
lines.push(`- ${lt.task_id}`);
|
|
1397
|
-
}
|
|
1398
|
-
}
|
|
1399
|
-
return lines.join("\n");
|
|
1400
|
-
}
|
|
1401
|
-
function formatUpdateConfirmation(id, name) {
|
|
1402
|
-
return `Updated task ${id}: "${name}"`;
|
|
1403
|
-
}
|
|
1404
|
-
function formatCreateConfirmation(id, name, url) {
|
|
1405
|
-
return `Created task ${id}: "${name}" - ${url}`;
|
|
1406
|
-
}
|
|
1407
|
-
function formatCommentConfirmation(id) {
|
|
1408
|
-
return `Comment posted (id: ${id})`;
|
|
1409
|
-
}
|
|
1410
|
-
function formatAssignConfirmation(taskId, opts) {
|
|
1411
|
-
const parts = [];
|
|
1412
|
-
if (opts.to) parts.push(`Assigned ${opts.to} to ${taskId}`);
|
|
1413
|
-
if (opts.remove) parts.push(`Removed ${opts.remove} from ${taskId}`);
|
|
1414
|
-
return parts.join("; ");
|
|
1415
|
-
}
|
|
1416
|
-
|
|
1417
|
-
// src/interactive.ts
|
|
1418
|
-
import { execFileSync } from "child_process";
|
|
1419
|
-
import { checkbox, confirm, Separator } from "@inquirer/prompts";
|
|
1420
|
-
import chalk2 from "chalk";
|
|
1421
|
-
function openUrl(url) {
|
|
1422
|
-
switch (process.platform) {
|
|
1423
|
-
case "darwin":
|
|
1424
|
-
execFileSync("open", [url]);
|
|
1425
|
-
break;
|
|
1426
|
-
case "linux":
|
|
1427
|
-
execFileSync("xdg-open", [url]);
|
|
1428
|
-
break;
|
|
1429
|
-
case "win32":
|
|
1430
|
-
execFileSync("cmd", ["/c", "start", "", url]);
|
|
1431
|
-
break;
|
|
1432
|
-
default:
|
|
1433
|
-
process.stderr.write(`Cannot open browser on ${process.platform}. Visit: ${url}
|
|
1434
|
-
`);
|
|
1435
|
-
}
|
|
1436
|
-
}
|
|
1437
|
-
function descriptionPreview(text, maxLines = 3) {
|
|
1438
|
-
const lines = text.split("\n").filter((l) => l.trim().length > 0);
|
|
1439
|
-
const preview = lines.slice(0, maxLines);
|
|
1440
|
-
const result = preview.map((l) => ` ${chalk2.dim(l.length > 100 ? l.slice(0, 99) + "\u2026" : l)}`).join("\n");
|
|
1441
|
-
if (lines.length > maxLines)
|
|
1442
|
-
return result + `
|
|
1443
|
-
${chalk2.dim(`... (${lines.length - maxLines} more lines)`)}`;
|
|
1444
|
-
return result;
|
|
1445
|
-
}
|
|
1446
|
-
function stringifyFieldValue(value) {
|
|
1447
|
-
if (typeof value === "string") return value;
|
|
1448
|
-
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
1449
|
-
return JSON.stringify(value);
|
|
1450
|
-
}
|
|
1451
|
-
function formatCustomFieldValue(field) {
|
|
1452
|
-
if (field.value === null || field.value === void 0) return null;
|
|
1453
|
-
const options = field.type_config?.options;
|
|
1454
|
-
switch (field.type) {
|
|
1455
|
-
case "drop_down": {
|
|
1456
|
-
if (!options) return stringifyFieldValue(field.value);
|
|
1457
|
-
const match = options.find((o) => o.id === Number(field.value));
|
|
1458
|
-
return match?.name ?? stringifyFieldValue(field.value);
|
|
1459
|
-
}
|
|
1460
|
-
case "labels": {
|
|
1461
|
-
if (!Array.isArray(field.value) || !options) return stringifyFieldValue(field.value);
|
|
1462
|
-
const names = field.value.map((id) => options.find((o) => o.id === id)?.name).filter((n) => n !== void 0);
|
|
1463
|
-
return names.length > 0 ? names.join(", ") : null;
|
|
1464
|
-
}
|
|
1465
|
-
case "date": {
|
|
1466
|
-
const ts = Number(field.value);
|
|
1467
|
-
if (!Number.isFinite(ts)) return stringifyFieldValue(field.value);
|
|
1468
|
-
return formatDate(String(ts));
|
|
1469
|
-
}
|
|
1470
|
-
case "checkbox":
|
|
1471
|
-
return field.value === true || field.value === "true" ? "Yes" : "No";
|
|
1472
|
-
default:
|
|
1473
|
-
return stringifyFieldValue(field.value);
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
function formatTaskDetail(task) {
|
|
1477
|
-
const lines = [];
|
|
1478
|
-
const isInitiative = (task.custom_item_id ?? 0) !== 0;
|
|
1479
|
-
const typeLabel = isInitiative ? "initiative" : "task";
|
|
1480
|
-
lines.push(chalk2.bold.underline(task.name));
|
|
1481
|
-
lines.push("");
|
|
1482
|
-
const fields = [
|
|
1483
|
-
["ID", task.id],
|
|
1484
|
-
["Status", task.status?.status ? colorStatus(task.status.status) : void 0],
|
|
1485
|
-
["Type", typeLabel],
|
|
1486
|
-
["List", task.list?.name],
|
|
1487
|
-
[
|
|
1488
|
-
"Assignees",
|
|
1489
|
-
task.assignees?.length ? task.assignees.map((a) => a.username).join(", ") : void 0
|
|
1490
|
-
],
|
|
1491
|
-
["Priority", task.priority?.priority ? colorPriority(task.priority.priority) : void 0],
|
|
1492
|
-
["Start", task.start_date ? formatDate(task.start_date) : void 0],
|
|
1493
|
-
["Due", task.due_date ? colorDueDate(formatDate(task.due_date), task.due_date) : void 0],
|
|
1494
|
-
["Estimate", task.time_estimate ? formatDuration(task.time_estimate) : void 0],
|
|
1495
|
-
["Tracked", task.time_spent ? formatDuration(task.time_spent) : void 0],
|
|
1496
|
-
["Tags", task.tags?.length ? task.tags.map((t) => t.name).join(", ") : void 0],
|
|
1497
|
-
["Lists", task.locations?.length ? task.locations.map((l) => l.name).join(", ") : void 0],
|
|
1498
|
-
["Parent", task.parent || void 0],
|
|
1499
|
-
["URL", task.url]
|
|
1500
|
-
];
|
|
1501
|
-
const maxLabel = Math.max(...fields.filter(([, v]) => v).map(([k]) => k.length));
|
|
1502
|
-
for (const [label, value] of fields) {
|
|
1503
|
-
if (!value) continue;
|
|
1504
|
-
lines.push(` ${chalk2.bold(label.padEnd(maxLabel + 1))} ${value}`);
|
|
1505
|
-
}
|
|
1506
|
-
if (task.custom_fields?.length) {
|
|
1507
|
-
const formatted = task.custom_fields.map((f) => [f.name, formatCustomFieldValue(f)]).filter((pair) => pair[1] !== null);
|
|
1508
|
-
if (formatted.length > 0) {
|
|
1509
|
-
lines.push("");
|
|
1510
|
-
lines.push(chalk2.bold("Custom Fields"));
|
|
1511
|
-
for (const [name, value] of formatted) {
|
|
1512
|
-
lines.push(` ${chalk2.bold(name)} ${value}`);
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
1515
|
-
}
|
|
1516
|
-
if (task.checklists?.length) {
|
|
1517
|
-
lines.push("");
|
|
1518
|
-
lines.push(chalk2.bold("Checklists"));
|
|
1519
|
-
for (const cl of task.checklists) {
|
|
1520
|
-
const resolved = cl.items.filter((i) => i.resolved).length;
|
|
1521
|
-
lines.push(` ${chalk2.bold(cl.name)} (${resolved}/${cl.items.length})`);
|
|
1522
|
-
for (const item of cl.items) {
|
|
1523
|
-
const check = item.resolved ? chalk2.green("[x]") : chalk2.dim("[ ]");
|
|
1524
|
-
lines.push(` ${check} ${item.name}`);
|
|
1525
|
-
}
|
|
1526
|
-
}
|
|
1527
|
-
}
|
|
1528
|
-
if (task.attachments?.length) {
|
|
1529
|
-
lines.push("");
|
|
1530
|
-
lines.push(chalk2.bold("Attachments"));
|
|
1531
|
-
for (const att of task.attachments) {
|
|
1532
|
-
lines.push(` ${att.title} ${chalk2.dim(att.url)}`);
|
|
1533
|
-
}
|
|
1534
|
-
}
|
|
1535
|
-
if (task.dependencies?.length) {
|
|
1536
|
-
lines.push("");
|
|
1537
|
-
lines.push(chalk2.bold("Dependencies"));
|
|
1538
|
-
for (const dep of task.dependencies) {
|
|
1539
|
-
const direction = dep.depends_on === task.id ? "blocks" : "depends on";
|
|
1540
|
-
const otherId = dep.depends_on === task.id ? dep.task_id : dep.depends_on;
|
|
1541
|
-
lines.push(` ${direction} ${chalk2.dim(otherId)}`);
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
if (task.linked_tasks?.length) {
|
|
1545
|
-
lines.push("");
|
|
1546
|
-
lines.push(chalk2.bold("Linked Tasks"));
|
|
1547
|
-
for (const lt of task.linked_tasks) {
|
|
1548
|
-
lines.push(` ${chalk2.dim(lt.task_id)}`);
|
|
1549
|
-
}
|
|
1550
|
-
}
|
|
1551
|
-
if (task.text_content?.trim()) {
|
|
1552
|
-
lines.push("");
|
|
1553
|
-
lines.push(descriptionPreview(task.text_content));
|
|
1554
|
-
}
|
|
1555
|
-
return lines.join("\n");
|
|
1556
|
-
}
|
|
1557
|
-
function formatChoiceName(task) {
|
|
1558
|
-
const id = task.id.padEnd(12);
|
|
1559
|
-
const name = task.name.length > 50 ? task.name.slice(0, 49) + "\u2026" : task.name.padEnd(50);
|
|
1560
|
-
const status = colorStatus(task.status);
|
|
1561
|
-
const priority = task.priority !== "none" ? colorPriority(task.priority) : "";
|
|
1562
|
-
return `${id} ${name} ${status}${priority ? " " + priority : ""}`;
|
|
1563
|
-
}
|
|
1564
|
-
async function interactiveTaskPicker(tasks) {
|
|
1565
|
-
if (tasks.length === 0) return [];
|
|
1566
|
-
const selected = await checkbox({
|
|
1567
|
-
message: `${tasks.length} task(s) found. Select to view details / open in browser:`,
|
|
1568
|
-
choices: tasks.map((t) => ({
|
|
1569
|
-
name: formatChoiceName(t),
|
|
1570
|
-
value: t.id
|
|
1571
|
-
})),
|
|
1572
|
-
pageSize: 20
|
|
1573
|
-
});
|
|
1574
|
-
return tasks.filter((t) => selected.includes(t.id));
|
|
1575
|
-
}
|
|
1576
|
-
async function groupedTaskPicker(groups) {
|
|
1577
|
-
const allTasks = groups.flatMap((g) => g.tasks);
|
|
1578
|
-
const totalCount = allTasks.length;
|
|
1579
|
-
if (totalCount === 0) return [];
|
|
1580
|
-
const choices = [];
|
|
1581
|
-
for (const group of groups) {
|
|
1582
|
-
if (group.tasks.length === 0) continue;
|
|
1583
|
-
choices.push(new Separator(chalk2.bold(`${group.label} (${group.tasks.length})`)));
|
|
1584
|
-
for (const task of group.tasks) {
|
|
1585
|
-
choices.push({ name: formatChoiceName(task), value: task.id });
|
|
1586
|
-
}
|
|
1587
|
-
}
|
|
1588
|
-
const selected = await checkbox({
|
|
1589
|
-
message: `${totalCount} task(s) found. Select to view details / open in browser:`,
|
|
1590
|
-
choices,
|
|
1591
|
-
pageSize: 20
|
|
1592
|
-
});
|
|
1593
|
-
return allTasks.filter((t) => selected.includes(t.id));
|
|
1594
|
-
}
|
|
1595
|
-
async function showDetailsAndOpen(tasks, fetchTask) {
|
|
1596
|
-
if (tasks.length === 0) return;
|
|
1597
|
-
const separator = chalk2.dim("\u2500".repeat(60));
|
|
1598
|
-
for (let i = 0; i < tasks.length; i++) {
|
|
1599
|
-
const task = tasks[i];
|
|
1600
|
-
if (i > 0) {
|
|
1601
|
-
console.log("");
|
|
1602
|
-
console.log(separator);
|
|
1603
|
-
}
|
|
1604
|
-
console.log("");
|
|
1605
|
-
if (fetchTask) {
|
|
1606
|
-
const full = await fetchTask(task.id);
|
|
1607
|
-
console.log(formatTaskDetail(full));
|
|
1608
|
-
} else {
|
|
1609
|
-
const fallback = {
|
|
1610
|
-
id: task.id,
|
|
1611
|
-
name: task.name,
|
|
1612
|
-
status: { status: task.status, color: "" },
|
|
1613
|
-
custom_item_id: task.task_type === "initiative" ? 1 : 0,
|
|
1614
|
-
assignees: [],
|
|
1615
|
-
url: task.url,
|
|
1616
|
-
list: { id: "", name: task.list },
|
|
1617
|
-
parent: task.parent
|
|
1618
|
-
};
|
|
1619
|
-
console.log(formatTaskDetail(fallback));
|
|
1620
|
-
}
|
|
1621
|
-
}
|
|
1622
|
-
const urls = tasks.map((t) => t.url);
|
|
1623
|
-
console.log("");
|
|
1624
|
-
const shouldOpen = await confirm({
|
|
1625
|
-
message: `Open ${urls.length} task(s) in browser?`,
|
|
1626
|
-
default: true
|
|
1627
|
-
});
|
|
1628
|
-
if (shouldOpen) {
|
|
1629
|
-
for (const url of urls) {
|
|
1630
|
-
openUrl(url);
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
}
|
|
1634
|
-
|
|
1635
|
-
// src/commands/tasks.ts
|
|
1636
|
-
var DONE_PATTERNS = ["done", "complete", "closed"];
|
|
1637
|
-
function isDoneStatus(status) {
|
|
1638
|
-
const lower = status.toLowerCase();
|
|
1639
|
-
return DONE_PATTERNS.some((p) => lower.includes(p));
|
|
1640
|
-
}
|
|
1641
|
-
function formatDueDate(ms) {
|
|
1642
|
-
if (!ms) return "";
|
|
1643
|
-
return formatDate(ms);
|
|
1644
|
-
}
|
|
1645
|
-
function resolveTaskType(task, typeMap) {
|
|
1646
|
-
const id = task.custom_item_id ?? 0;
|
|
1647
|
-
if (id === 0) return "task";
|
|
1648
|
-
return typeMap.get(id) ?? `type_${id}`;
|
|
1649
|
-
}
|
|
1650
|
-
function summarize(task, typeMap) {
|
|
1651
|
-
return {
|
|
1652
|
-
id: task.id,
|
|
1653
|
-
name: task.name,
|
|
1654
|
-
status: task.status.status,
|
|
1655
|
-
task_type: resolveTaskType(task, typeMap ?? /* @__PURE__ */ new Map()),
|
|
1656
|
-
priority: task.priority?.priority ?? "none",
|
|
1657
|
-
due_date: formatDueDate(task.due_date),
|
|
1658
|
-
...task.due_date ? { dueRaw: task.due_date } : {},
|
|
1659
|
-
list: task.list.name,
|
|
1660
|
-
url: task.url,
|
|
1661
|
-
...task.parent ? { parent: task.parent } : {}
|
|
1662
|
-
};
|
|
1663
|
-
}
|
|
1664
|
-
function buildTypeMap(types) {
|
|
1665
|
-
const map = /* @__PURE__ */ new Map();
|
|
1666
|
-
for (const t of types) {
|
|
1667
|
-
map.set(t.id, t.name);
|
|
1668
|
-
}
|
|
1669
|
-
return map;
|
|
1670
|
-
}
|
|
1671
|
-
function resolveTypeFilter(typeFilter, typeMap) {
|
|
1672
|
-
if (typeFilter === "task") return 0;
|
|
1673
|
-
const asNum = Number(typeFilter);
|
|
1674
|
-
if (Number.isFinite(asNum)) return asNum;
|
|
1675
|
-
const lower = typeFilter.toLowerCase();
|
|
1676
|
-
for (const [id, name] of typeMap) {
|
|
1677
|
-
if (name.toLowerCase() === lower) return id;
|
|
1678
|
-
}
|
|
1679
|
-
const available = ["task", ...Array.from(typeMap.values())].join(", ");
|
|
1680
|
-
throw new Error(`Unknown task type "${typeFilter}". Available types: ${available}`);
|
|
1681
|
-
}
|
|
1682
|
-
async function fetchMyTasks(config, opts = {}) {
|
|
1683
|
-
const client = new ClickUpClient(config);
|
|
1684
|
-
const { typeFilter, name, ...apiFilters } = opts;
|
|
1685
|
-
const [allTasks, customTypes] = await Promise.all([
|
|
1686
|
-
client.getMyTasks(config.teamId, apiFilters),
|
|
1687
|
-
client.getCustomTaskTypes(config.teamId)
|
|
1688
|
-
]);
|
|
1689
|
-
const typeMap = buildTypeMap(customTypes);
|
|
1690
|
-
let filtered = allTasks;
|
|
1691
|
-
if (typeFilter) {
|
|
1692
|
-
const targetId = resolveTypeFilter(typeFilter, typeMap);
|
|
1693
|
-
filtered = allTasks.filter((t) => (t.custom_item_id ?? 0) === targetId);
|
|
1694
|
-
}
|
|
1695
|
-
if (name) {
|
|
1696
|
-
const query = name.toLowerCase();
|
|
1697
|
-
filtered = filtered.filter((t) => t.name.toLowerCase().includes(query));
|
|
1698
|
-
}
|
|
1699
|
-
return filtered.map((t) => summarize(t, typeMap));
|
|
1700
|
-
}
|
|
1701
|
-
async function printTasks(tasks, forceJson, config) {
|
|
1702
|
-
if (shouldOutputJson(forceJson)) {
|
|
1703
|
-
console.log(JSON.stringify(tasks, null, 2));
|
|
1704
|
-
return;
|
|
1705
|
-
}
|
|
1706
|
-
if (!isTTY()) {
|
|
1707
|
-
console.log(formatTasksMarkdown(tasks));
|
|
1708
|
-
return;
|
|
1709
|
-
}
|
|
1710
|
-
if (tasks.length === 0) {
|
|
1711
|
-
console.log("No tasks found.");
|
|
1712
|
-
return;
|
|
1713
|
-
}
|
|
1714
|
-
const fetchTask = config ? (() => {
|
|
1715
|
-
const client = new ClickUpClient(config);
|
|
1716
|
-
return (id) => client.getTask(id);
|
|
1717
|
-
})() : void 0;
|
|
1718
|
-
const selected = await interactiveTaskPicker(tasks);
|
|
1719
|
-
await showDetailsAndOpen(selected, fetchTask);
|
|
1720
|
-
}
|
|
57
|
+
import { fileURLToPath } from "url";
|
|
1721
58
|
|
|
1722
59
|
// src/status.ts
|
|
1723
60
|
function matchStatus(input, statuses) {
|
|
@@ -1947,13 +284,13 @@ async function getTask(config, taskId) {
|
|
|
1947
284
|
}
|
|
1948
285
|
|
|
1949
286
|
// src/commands/init.ts
|
|
1950
|
-
import { password, select, confirm
|
|
1951
|
-
import
|
|
287
|
+
import { password, select, confirm } from "@inquirer/prompts";
|
|
288
|
+
import fs from "fs";
|
|
1952
289
|
async function runInitCommand() {
|
|
1953
|
-
const
|
|
1954
|
-
if (
|
|
1955
|
-
const overwrite = await
|
|
1956
|
-
message: `Config already exists at ${
|
|
290
|
+
const configPath2 = getConfigPath();
|
|
291
|
+
if (fs.existsSync(configPath2)) {
|
|
292
|
+
const overwrite = await confirm({
|
|
293
|
+
message: `Config already exists at ${configPath2}. Overwrite?`,
|
|
1957
294
|
default: false
|
|
1958
295
|
});
|
|
1959
296
|
if (!overwrite) {
|
|
@@ -1991,190 +328,19 @@ async function runInitCommand() {
|
|
|
1991
328
|
});
|
|
1992
329
|
}
|
|
1993
330
|
writeConfig({ apiToken, teamId });
|
|
1994
|
-
process.stdout.write(`Config written to ${
|
|
1995
|
-
`);
|
|
1996
|
-
}
|
|
1997
|
-
|
|
1998
|
-
// src/commands/sprint.ts
|
|
1999
|
-
import { select as select2 } from "@inquirer/prompts";
|
|
2000
|
-
var SPRINT_KEYWORDS = ["sprint", "iteration", "cycle", "scrum"];
|
|
2001
|
-
function parseUSDateRange(name) {
|
|
2002
|
-
const m = name.match(/\((\d{1,2})\/(\d{1,2})\s*[-–]\s*(\d{1,2})\/(\d{1,2})\)/);
|
|
2003
|
-
if (!m) return null;
|
|
2004
|
-
const year = (/* @__PURE__ */ new Date()).getFullYear();
|
|
2005
|
-
const start = new Date(year, Number(m[1]) - 1, Number(m[2]));
|
|
2006
|
-
const end = new Date(year, Number(m[3]) - 1, Number(m[4]), 23, 59, 59);
|
|
2007
|
-
if (end < start) end.setFullYear(end.getFullYear() + 1);
|
|
2008
|
-
return { start, end };
|
|
2009
|
-
}
|
|
2010
|
-
function parseISODateRange(name) {
|
|
2011
|
-
const m = name.match(/\((\d{4}-\d{2}-\d{2})\s*[-–]\s*(\d{4}-\d{2}-\d{2})\)/);
|
|
2012
|
-
if (!m) return null;
|
|
2013
|
-
const [sy, sm, sd] = m[1].split("-").map(Number);
|
|
2014
|
-
const [ey, em, ed] = m[2].split("-").map(Number);
|
|
2015
|
-
const start = new Date(sy, sm - 1, sd);
|
|
2016
|
-
const end = new Date(ey, em - 1, ed, 23, 59, 59);
|
|
2017
|
-
return { start, end };
|
|
2018
|
-
}
|
|
2019
|
-
function parseMonthDayRange(name) {
|
|
2020
|
-
const months = {
|
|
2021
|
-
jan: 0,
|
|
2022
|
-
feb: 1,
|
|
2023
|
-
mar: 2,
|
|
2024
|
-
apr: 3,
|
|
2025
|
-
may: 4,
|
|
2026
|
-
jun: 5,
|
|
2027
|
-
jul: 6,
|
|
2028
|
-
aug: 7,
|
|
2029
|
-
sep: 8,
|
|
2030
|
-
oct: 9,
|
|
2031
|
-
nov: 10,
|
|
2032
|
-
dec: 11
|
|
2033
|
-
};
|
|
2034
|
-
const m = name.match(/\(([A-Za-z]{3})\s+(\d{1,2})\s*[-–]\s*([A-Za-z]{3})\s+(\d{1,2})\)/);
|
|
2035
|
-
if (!m) return null;
|
|
2036
|
-
const sm = months[m[1].toLowerCase()];
|
|
2037
|
-
const em = months[m[3].toLowerCase()];
|
|
2038
|
-
if (sm === void 0 || em === void 0) return null;
|
|
2039
|
-
const year = (/* @__PURE__ */ new Date()).getFullYear();
|
|
2040
|
-
const start = new Date(year, sm, Number(m[2]));
|
|
2041
|
-
const end = new Date(year, em, Number(m[4]), 23, 59, 59);
|
|
2042
|
-
if (end < start) end.setFullYear(end.getFullYear() + 1);
|
|
2043
|
-
return { start, end };
|
|
2044
|
-
}
|
|
2045
|
-
function parseEuropeanDateRange(name) {
|
|
2046
|
-
const m = name.match(/\((\d{1,2})\.(\d{1,2})\s*[-–]\s*(\d{1,2})\.(\d{1,2})\)/);
|
|
2047
|
-
if (!m) return null;
|
|
2048
|
-
const year = (/* @__PURE__ */ new Date()).getFullYear();
|
|
2049
|
-
const start = new Date(year, Number(m[2]) - 1, Number(m[1]));
|
|
2050
|
-
const end = new Date(year, Number(m[4]) - 1, Number(m[3]), 23, 59, 59);
|
|
2051
|
-
if (end < start) end.setFullYear(end.getFullYear() + 1);
|
|
2052
|
-
return { start, end };
|
|
2053
|
-
}
|
|
2054
|
-
function parseSprintDates(name) {
|
|
2055
|
-
return parseUSDateRange(name) ?? parseISODateRange(name) ?? parseMonthDayRange(name) ?? parseEuropeanDateRange(name);
|
|
2056
|
-
}
|
|
2057
|
-
function findActiveSprintList(lists, today = /* @__PURE__ */ new Date()) {
|
|
2058
|
-
if (lists.length === 0) return null;
|
|
2059
|
-
for (const list of lists) {
|
|
2060
|
-
const dates = parseSprintDates(list.name);
|
|
2061
|
-
if (dates && today >= dates.start && today <= dates.end) return list;
|
|
2062
|
-
}
|
|
2063
|
-
for (const list of lists) {
|
|
2064
|
-
if (list.start_date && list.due_date) {
|
|
2065
|
-
const start = new Date(Number(list.start_date));
|
|
2066
|
-
const end = new Date(Number(list.due_date));
|
|
2067
|
-
if (today >= start && today <= end) return list;
|
|
2068
|
-
}
|
|
2069
|
-
}
|
|
2070
|
-
return lists[lists.length - 1] ?? null;
|
|
2071
|
-
}
|
|
2072
|
-
var NOISE_WORDS = /* @__PURE__ */ new Set(["product", "team", "the", "and", "for", "test"]);
|
|
2073
|
-
function extractSpaceKeywords(spaceName) {
|
|
2074
|
-
return spaceName.replace(/[^a-zA-Z0-9\s]/g, "").split(/\s+/).map((w) => w.toLowerCase()).filter((w) => w.length >= 3 && !NOISE_WORDS.has(w));
|
|
2075
|
-
}
|
|
2076
|
-
function findRelatedSpaces(mySpaceIds, allSpaces) {
|
|
2077
|
-
const mySpaces = allSpaces.filter((s) => mySpaceIds.has(s.id));
|
|
2078
|
-
const keywords = mySpaces.flatMap((s) => extractSpaceKeywords(s.name));
|
|
2079
|
-
if (keywords.length === 0) return allSpaces;
|
|
2080
|
-
return allSpaces.filter(
|
|
2081
|
-
(s) => mySpaceIds.has(s.id) || keywords.some((kw) => s.name.toLowerCase().includes(kw))
|
|
2082
|
-
);
|
|
2083
|
-
}
|
|
2084
|
-
async function runSprintCommand(config, opts) {
|
|
2085
|
-
const client = new ClickUpClient(config);
|
|
2086
|
-
process.stderr.write("Detecting active sprint...\n");
|
|
2087
|
-
const folderId = opts.folder ?? config.sprintFolderId;
|
|
2088
|
-
const [myTasks, allSpaces, customTypes] = await Promise.all([
|
|
2089
|
-
client.getMyTasks(config.teamId),
|
|
2090
|
-
folderId ? Promise.resolve([]) : client.getSpaces(config.teamId),
|
|
2091
|
-
client.getCustomTaskTypes(config.teamId)
|
|
2092
|
-
]);
|
|
2093
|
-
const typeMap = buildTypeMap(customTypes);
|
|
2094
|
-
let sprintLists;
|
|
2095
|
-
if (folderId) {
|
|
2096
|
-
sprintLists = await client.getFolderLists(folderId);
|
|
2097
|
-
} else {
|
|
2098
|
-
let spaces;
|
|
2099
|
-
if (opts.space) {
|
|
2100
|
-
spaces = allSpaces.filter(
|
|
2101
|
-
(s) => s.name.toLowerCase().includes(opts.space.toLowerCase()) || s.id === opts.space
|
|
2102
|
-
);
|
|
2103
|
-
if (spaces.length === 0) {
|
|
2104
|
-
throw new Error(
|
|
2105
|
-
`No space matching "${opts.space}" found. Use \`cup spaces\` to list available spaces.`
|
|
2106
|
-
);
|
|
2107
|
-
}
|
|
2108
|
-
} else {
|
|
2109
|
-
const mySpaceIds = new Set(
|
|
2110
|
-
myTasks.map((t) => t.space?.id).filter((id) => Boolean(id))
|
|
2111
|
-
);
|
|
2112
|
-
spaces = findRelatedSpaces(mySpaceIds, allSpaces);
|
|
2113
|
-
}
|
|
2114
|
-
const foldersBySpace = await Promise.all(spaces.map((space) => client.getFolders(space.id)));
|
|
2115
|
-
const sprintFolders = foldersBySpace.flat().filter((f) => {
|
|
2116
|
-
const lower = f.name.toLowerCase();
|
|
2117
|
-
return SPRINT_KEYWORDS.some((kw) => lower.includes(kw));
|
|
2118
|
-
});
|
|
2119
|
-
const listsByFolder = await Promise.all(
|
|
2120
|
-
sprintFolders.map((folder) => client.getFolderLists(folder.id))
|
|
2121
|
-
);
|
|
2122
|
-
sprintLists = listsByFolder.flat();
|
|
2123
|
-
}
|
|
2124
|
-
let activeList = findActiveSprintList(sprintLists);
|
|
2125
|
-
if (!activeList && sprintLists.length > 1 && isTTY()) {
|
|
2126
|
-
const choice = await select2({
|
|
2127
|
-
message: "Multiple sprint lists found. Which one?",
|
|
2128
|
-
choices: sprintLists.map((l) => ({
|
|
2129
|
-
name: `${l.name} (${l.id})`,
|
|
2130
|
-
value: l
|
|
2131
|
-
}))
|
|
2132
|
-
});
|
|
2133
|
-
activeList = choice;
|
|
2134
|
-
}
|
|
2135
|
-
if (!activeList && sprintLists.length > 1) {
|
|
2136
|
-
process.stderr.write(
|
|
2137
|
-
`Multiple sprint lists found:
|
|
2138
|
-
${sprintLists.map((l) => ` - ${l.name} (${l.id})`).join("\n")}
|
|
2139
|
-
Using: ${sprintLists[sprintLists.length - 1].name}
|
|
2140
|
-
`
|
|
2141
|
-
);
|
|
2142
|
-
activeList = sprintLists[sprintLists.length - 1] ?? null;
|
|
2143
|
-
}
|
|
2144
|
-
if (!activeList) {
|
|
2145
|
-
throw new Error(
|
|
2146
|
-
'No sprint list found. Ensure sprint folders contain "sprint", "iteration", "cycle", or "scrum" in their name.'
|
|
2147
|
-
);
|
|
2148
|
-
}
|
|
2149
|
-
process.stderr.write(`Active sprint: ${activeList.name}
|
|
331
|
+
process.stdout.write(`Config written to ${configPath2}
|
|
2150
332
|
`);
|
|
2151
|
-
const me = await client.getMe();
|
|
2152
|
-
const viewData = await client.getListViews(activeList.id);
|
|
2153
|
-
const listView = viewData.required_views?.list;
|
|
2154
|
-
let allTasks;
|
|
2155
|
-
if (listView) {
|
|
2156
|
-
allTasks = await client.getViewTasks(listView.id);
|
|
2157
|
-
} else {
|
|
2158
|
-
allTasks = await client.getTasksFromList(activeList.id);
|
|
2159
|
-
}
|
|
2160
|
-
let sprintTasks = allTasks.filter((t) => t.assignees.some((a) => Number(a.id) === me.id));
|
|
2161
|
-
if (!opts.includeClosed) {
|
|
2162
|
-
sprintTasks = sprintTasks.filter((t) => !isDoneStatus(t.status.status));
|
|
2163
|
-
}
|
|
2164
|
-
const filtered = opts.status ? sprintTasks.filter((t) => t.status.status.toLowerCase() === opts.status.toLowerCase()) : sprintTasks;
|
|
2165
|
-
const summaries = filtered.map((t) => summarize(t, typeMap));
|
|
2166
|
-
await printTasks(summaries, opts.json ?? false, config);
|
|
2167
333
|
}
|
|
2168
334
|
|
|
2169
335
|
// src/commands/sprints.ts
|
|
2170
|
-
import
|
|
336
|
+
import chalk from "chalk";
|
|
2171
337
|
var SPRINT_COLUMNS = [
|
|
2172
338
|
{ key: "id", label: "ID" },
|
|
2173
339
|
{
|
|
2174
340
|
key: "sprint",
|
|
2175
341
|
label: "SPRINT",
|
|
2176
342
|
maxWidth: 60,
|
|
2177
|
-
format: (v, row) => row.active ?
|
|
343
|
+
format: (v, row) => row.active ? chalk.green(v) : v
|
|
2178
344
|
},
|
|
2179
345
|
{ key: "dates", label: "DATES" }
|
|
2180
346
|
];
|
|
@@ -2217,11 +383,15 @@ async function listSprints(config, opts = {}) {
|
|
|
2217
383
|
);
|
|
2218
384
|
spaces = findRelatedSpaces(mySpaceIds, allSpaces);
|
|
2219
385
|
}
|
|
386
|
+
const favorites = getFavorites();
|
|
387
|
+
const favoriteFolderIds = Object.values(favorites).filter((f) => f.type === "sprint-folder").map((f) => f.id);
|
|
2220
388
|
const foldersBySpace = await Promise.all(spaces.map((space) => client.getFolders(space.id)));
|
|
2221
389
|
const sprintFolders = foldersBySpace.flat().filter((f) => {
|
|
2222
390
|
const lower = f.name.toLowerCase();
|
|
2223
391
|
return SPRINT_KEYWORDS.some((kw) => lower.includes(kw));
|
|
2224
392
|
});
|
|
393
|
+
const detectedFolderIds = new Set(sprintFolders.map((f) => f.id));
|
|
394
|
+
const extraFavoriteIds = favoriteFolderIds.filter((id) => !detectedFolderIds.has(id));
|
|
2225
395
|
const today = /* @__PURE__ */ new Date();
|
|
2226
396
|
const allSprints = [];
|
|
2227
397
|
const listsByFolder = await Promise.all(
|
|
@@ -2232,6 +402,16 @@ async function listSprints(config, opts = {}) {
|
|
|
2232
402
|
const lists = listsByFolder[i];
|
|
2233
403
|
allSprints.push(...buildSprintInfos(lists, folder.name, today));
|
|
2234
404
|
}
|
|
405
|
+
const extraListsByFolder = await Promise.all(
|
|
406
|
+
extraFavoriteIds.map((id) => client.getFolderLists(id))
|
|
407
|
+
);
|
|
408
|
+
for (let i = 0; i < extraFavoriteIds.length; i++) {
|
|
409
|
+
const lists = extraListsByFolder[i];
|
|
410
|
+
const alias = Object.entries(favorites).find(
|
|
411
|
+
([_, f]) => f.type === "sprint-folder" && f.id === extraFavoriteIds[i]
|
|
412
|
+
)?.[1]?.name ?? `Folder ${extraFavoriteIds[i]}`;
|
|
413
|
+
allSprints.push(...buildSprintInfos(lists, alias, today));
|
|
414
|
+
}
|
|
2235
415
|
if (shouldOutputJson(opts.json ?? false)) {
|
|
2236
416
|
console.log(JSON.stringify(allSprints, null, 2));
|
|
2237
417
|
return;
|
|
@@ -2286,7 +466,7 @@ async function postComment(config, taskId, text, notifyAll) {
|
|
|
2286
466
|
}
|
|
2287
467
|
|
|
2288
468
|
// src/commands/comments.ts
|
|
2289
|
-
import
|
|
469
|
+
import chalk2 from "chalk";
|
|
2290
470
|
async function fetchComments(config, taskId) {
|
|
2291
471
|
const client = new ClickUpClient(config);
|
|
2292
472
|
const comments = await client.getTaskComments(taskId);
|
|
@@ -2310,11 +490,11 @@ function printComments(comments, forceJson) {
|
|
|
2310
490
|
console.log("No comments found.");
|
|
2311
491
|
return;
|
|
2312
492
|
}
|
|
2313
|
-
const separator =
|
|
493
|
+
const separator = chalk2.dim("-".repeat(60));
|
|
2314
494
|
for (let i = 0; i < comments.length; i++) {
|
|
2315
495
|
const c = comments[i];
|
|
2316
496
|
if (i > 0) console.log(separator);
|
|
2317
|
-
console.log(`${
|
|
497
|
+
console.log(`${chalk2.bold(c.user)} ${chalk2.dim(formatTimestamp(c.date))}`);
|
|
2318
498
|
console.log(c.text);
|
|
2319
499
|
if (i < comments.length - 1) console.log("");
|
|
2320
500
|
}
|
|
@@ -2624,7 +804,7 @@ async function openTask(config, query, opts = {}) {
|
|
|
2624
804
|
}
|
|
2625
805
|
|
|
2626
806
|
// src/commands/summary.ts
|
|
2627
|
-
import
|
|
807
|
+
import chalk3 from "chalk";
|
|
2628
808
|
var IN_PROGRESS_PATTERNS = ["in progress", "in review", "code review", "doing"];
|
|
2629
809
|
function isCompletedRecently(task, cutoff) {
|
|
2630
810
|
if (!isDoneStatus(task.status.status)) return false;
|
|
@@ -2662,9 +842,9 @@ function categorizeTasks(tasks, hoursBack, typeMap) {
|
|
|
2662
842
|
}
|
|
2663
843
|
function colorSectionLabel(label) {
|
|
2664
844
|
const lower = label.toLowerCase();
|
|
2665
|
-
if (lower.includes("completed")) return
|
|
2666
|
-
if (lower.includes("progress")) return
|
|
2667
|
-
if (lower.includes("overdue")) return
|
|
845
|
+
if (lower.includes("completed")) return chalk3.green(label);
|
|
846
|
+
if (lower.includes("progress")) return chalk3.yellow(label);
|
|
847
|
+
if (lower.includes("overdue")) return chalk3.red(label);
|
|
2668
848
|
return label;
|
|
2669
849
|
}
|
|
2670
850
|
function printSection(label, tasks) {
|
|
@@ -2766,7 +946,7 @@ function setConfigValue(key, value, profileName) {
|
|
|
2766
946
|
}
|
|
2767
947
|
writeConfig(merged, profileName);
|
|
2768
948
|
}
|
|
2769
|
-
function
|
|
949
|
+
function configPath() {
|
|
2770
950
|
return getConfigPath();
|
|
2771
951
|
}
|
|
2772
952
|
|
|
@@ -2793,7 +973,7 @@ async function assignTask(config, taskId, opts) {
|
|
|
2793
973
|
}
|
|
2794
974
|
|
|
2795
975
|
// src/commands/activity.ts
|
|
2796
|
-
import
|
|
976
|
+
import chalk4 from "chalk";
|
|
2797
977
|
async function fetchActivity(config, taskId) {
|
|
2798
978
|
const client = new ClickUpClient(config);
|
|
2799
979
|
const [task, rawComments] = await Promise.all([
|
|
@@ -2825,8 +1005,8 @@ ${commentsMd}`);
|
|
|
2825
1005
|
}
|
|
2826
1006
|
console.log(formatTaskDetail(result.task));
|
|
2827
1007
|
console.log("");
|
|
2828
|
-
console.log(
|
|
2829
|
-
console.log(
|
|
1008
|
+
console.log(chalk4.bold("Comments"));
|
|
1009
|
+
console.log(chalk4.dim("-".repeat(60)));
|
|
2830
1010
|
if (result.comments.length === 0) {
|
|
2831
1011
|
console.log("No comments.");
|
|
2832
1012
|
return;
|
|
@@ -2835,9 +1015,9 @@ ${commentsMd}`);
|
|
|
2835
1015
|
const c = result.comments[i];
|
|
2836
1016
|
if (i > 0) {
|
|
2837
1017
|
console.log("");
|
|
2838
|
-
console.log(
|
|
1018
|
+
console.log(chalk4.dim("-".repeat(60)));
|
|
2839
1019
|
}
|
|
2840
|
-
console.log(`${
|
|
1020
|
+
console.log(`${chalk4.bold(c.user)} ${chalk4.dim(formatTimestamp(c.date))}`);
|
|
2841
1021
|
console.log(c.text);
|
|
2842
1022
|
}
|
|
2843
1023
|
}
|
|
@@ -2985,7 +1165,7 @@ var commandMetadata = [
|
|
|
2985
1165
|
{
|
|
2986
1166
|
name: "comment-delete",
|
|
2987
1167
|
description: "Delete a comment",
|
|
2988
|
-
flags: ["--json"],
|
|
1168
|
+
flags: ["--mine", "--match", "--json"],
|
|
2989
1169
|
quickReference: [
|
|
2990
1170
|
{
|
|
2991
1171
|
section: "write",
|
|
@@ -3648,6 +1828,23 @@ var commandMetadata = [
|
|
|
3648
1828
|
{ section: "read", usage: "filter run <name>", description: "Run a saved shortcut" }
|
|
3649
1829
|
]
|
|
3650
1830
|
},
|
|
1831
|
+
{
|
|
1832
|
+
name: "favorite",
|
|
1833
|
+
description: "Manage local favorites (sprint folders, spaces, lists, etc.)",
|
|
1834
|
+
quickReference: [
|
|
1835
|
+
{
|
|
1836
|
+
section: "configuration",
|
|
1837
|
+
usage: "favorite add <type> <id> [alias]",
|
|
1838
|
+
description: "Add a favorite"
|
|
1839
|
+
},
|
|
1840
|
+
{
|
|
1841
|
+
section: "configuration",
|
|
1842
|
+
usage: "favorite remove <alias>",
|
|
1843
|
+
description: "Remove a favorite"
|
|
1844
|
+
},
|
|
1845
|
+
{ section: "read", usage: "favorite list", description: "List saved favorites" }
|
|
1846
|
+
]
|
|
1847
|
+
},
|
|
3651
1848
|
{
|
|
3652
1849
|
name: "profile",
|
|
3653
1850
|
description: "Manage profiles",
|
|
@@ -3716,6 +1913,7 @@ var bashSpecialCaseCommands = /* @__PURE__ */ new Set([
|
|
|
3716
1913
|
"time",
|
|
3717
1914
|
"bulk",
|
|
3718
1915
|
"filter",
|
|
1916
|
+
"favorite",
|
|
3719
1917
|
"config",
|
|
3720
1918
|
"profile",
|
|
3721
1919
|
"completion"
|
|
@@ -3812,6 +2010,18 @@ ${renderBashCommandCases()}
|
|
|
3812
2010
|
COMPREPLY=($(compgen -W "status assign due-date tag" -- "$cur"))
|
|
3813
2011
|
fi
|
|
3814
2012
|
;;
|
|
2013
|
+
favorite)
|
|
2014
|
+
if [[ $cword -eq 2 ]]; then
|
|
2015
|
+
COMPREPLY=($(compgen -W "add remove list" -- "$cur"))
|
|
2016
|
+
elif [[ $cword -eq 3 ]]; then
|
|
2017
|
+
local subcmd="\${words[2]}"
|
|
2018
|
+
case "$subcmd" in
|
|
2019
|
+
add)
|
|
2020
|
+
COMPREPLY=($(compgen -W "sprint-folder space list folder view task" -- "$cur"))
|
|
2021
|
+
;;
|
|
2022
|
+
esac
|
|
2023
|
+
fi
|
|
2024
|
+
;;
|
|
3815
2025
|
profile)
|
|
3816
2026
|
if [[ $cword -eq 2 ]]; then
|
|
3817
2027
|
COMPREPLY=($(compgen -W "list add remove use" -- "$cur"))
|
|
@@ -4181,6 +2391,8 @@ ${renderZshTopLevelCommands(name)}
|
|
|
4181
2391
|
comment-delete)
|
|
4182
2392
|
_arguments \\
|
|
4183
2393
|
'1:comment_id:' \\
|
|
2394
|
+
'--mine[Delete one of my comments from the specified task]' \\
|
|
2395
|
+
'--match[Only match comments containing this text]:text:' \\
|
|
4184
2396
|
'--json[Force JSON output]'
|
|
4185
2397
|
;;
|
|
4186
2398
|
replies)
|
|
@@ -4469,6 +2681,40 @@ ${renderZshTopLevelCommands(name)}
|
|
|
4469
2681
|
'(-c --content)'{-c,--content}'[New page content]:text:' \\
|
|
4470
2682
|
'--json[Force JSON output]'
|
|
4471
2683
|
;;
|
|
2684
|
+
favorite)
|
|
2685
|
+
local -a favorite_cmds
|
|
2686
|
+
favorite_cmds=(
|
|
2687
|
+
'add:Add a favorite (types: sprint-folder, space, list, folder, view, task)'
|
|
2688
|
+
'remove:Remove a favorite by alias'
|
|
2689
|
+
'list:List saved favorites'
|
|
2690
|
+
)
|
|
2691
|
+
_arguments -C \\
|
|
2692
|
+
'1:favorite command:->favorite_cmd' \\
|
|
2693
|
+
'*::favorite_arg:->favorite_args'
|
|
2694
|
+
case $state in
|
|
2695
|
+
favorite_cmd)
|
|
2696
|
+
_describe 'favorite command' favorite_cmds
|
|
2697
|
+
;;
|
|
2698
|
+
favorite_args)
|
|
2699
|
+
case $words[1] in
|
|
2700
|
+
add)
|
|
2701
|
+
_arguments \\
|
|
2702
|
+
'1:type:(sprint-folder space list folder view task)' \\
|
|
2703
|
+
'2:id:' \\
|
|
2704
|
+
'3:alias:' \\
|
|
2705
|
+
'(-n --name)'{-n,--name}'[Display name]:name:' \\
|
|
2706
|
+
'--json[Force JSON output]'
|
|
2707
|
+
;;
|
|
2708
|
+
remove)
|
|
2709
|
+
_arguments '1:alias:' '--json[Force JSON output]'
|
|
2710
|
+
;;
|
|
2711
|
+
list)
|
|
2712
|
+
_arguments '--type[Filter by entity type]:type:(sprint-folder space list folder view task)' '--json[Force JSON output]'
|
|
2713
|
+
;;
|
|
2714
|
+
esac
|
|
2715
|
+
;;
|
|
2716
|
+
esac
|
|
2717
|
+
;;
|
|
4472
2718
|
filter)
|
|
4473
2719
|
local -a filter_cmds
|
|
4474
2720
|
filter_cmds=(
|
|
@@ -4651,6 +2897,14 @@ complete -c ${name} -n '__fish_seen_subcommand_from filter; and not __fish_seen_
|
|
|
4651
2897
|
complete -c ${name} -n '__fish_seen_subcommand_from filter; and not __fish_seen_subcommand_from save run list delete show' -a show -d 'Show details of a saved shortcut'
|
|
4652
2898
|
complete -c ${name} -n '__fish_seen_subcommand_from save run list delete show; and __fish_seen_subcommand_from filter' -l json -d 'Force JSON output'
|
|
4653
2899
|
complete -c ${name} -n '__fish_seen_subcommand_from save; and __fish_seen_subcommand_from filter' -s d -l description -d 'Filter description'
|
|
2900
|
+
|
|
2901
|
+
complete -c ${name} -n '__fish_seen_subcommand_from favorite; and not __fish_seen_subcommand_from add remove list' -a add -d 'Add a favorite'
|
|
2902
|
+
complete -c ${name} -n '__fish_seen_subcommand_from favorite; and not __fish_seen_subcommand_from add remove list' -a remove -d 'Remove a favorite'
|
|
2903
|
+
complete -c ${name} -n '__fish_seen_subcommand_from favorite; and not __fish_seen_subcommand_from add remove list' -a list -d 'List saved favorites'
|
|
2904
|
+
complete -c ${name} -n '__fish_seen_subcommand_from add; and __fish_seen_subcommand_from favorite' -a 'sprint-folder space list folder view task' -d 'Entity type'
|
|
2905
|
+
complete -c ${name} -n '__fish_seen_subcommand_from add remove list; and __fish_seen_subcommand_from favorite' -l json -d 'Force JSON output'
|
|
2906
|
+
complete -c ${name} -n '__fish_seen_subcommand_from add; and __fish_seen_subcommand_from favorite' -s n -l name -d 'Display name'
|
|
2907
|
+
complete -c ${name} -n '__fish_seen_subcommand_from list; and __fish_seen_subcommand_from favorite' -l type -d 'Filter by entity type'
|
|
4654
2908
|
`;
|
|
4655
2909
|
}
|
|
4656
2910
|
function generateCompletion(shell, name = "cup") {
|
|
@@ -4667,24 +2921,31 @@ function generateCompletion(shell, name = "cup") {
|
|
|
4667
2921
|
}
|
|
4668
2922
|
|
|
4669
2923
|
// src/commands/skill.ts
|
|
4670
|
-
import { readFileSync, mkdirSync, copyFileSync, existsSync } from "fs";
|
|
4671
|
-
import { join
|
|
4672
|
-
import {
|
|
4673
|
-
import
|
|
4674
|
-
import chalk7 from "chalk";
|
|
2924
|
+
import { readFileSync, realpathSync, mkdirSync, copyFileSync, existsSync } from "fs";
|
|
2925
|
+
import { join, dirname } from "path";
|
|
2926
|
+
import { homedir } from "os";
|
|
2927
|
+
import chalk5 from "chalk";
|
|
4675
2928
|
function skillPath() {
|
|
4676
|
-
|
|
4677
|
-
|
|
2929
|
+
if (!process.argv[1]) {
|
|
2930
|
+
throw new Error("Cannot determine install path. Run with: cup skill");
|
|
2931
|
+
}
|
|
2932
|
+
const entryPoint = realpathSync(process.argv[1]);
|
|
2933
|
+
const packageRoot = dirname(dirname(entryPoint));
|
|
2934
|
+
const candidate = join(packageRoot, "skills", "clickup-cli", "SKILL.md");
|
|
2935
|
+
if (existsSync(candidate)) return candidate;
|
|
2936
|
+
const altCandidate = join(dirname(entryPoint), "..", "skills", "clickup-cli", "SKILL.md");
|
|
2937
|
+
if (existsSync(altCandidate)) return altCandidate;
|
|
2938
|
+
throw new Error("SKILL.md not found. Reinstall with: npm install -g @krodak/clickup-cli");
|
|
4678
2939
|
}
|
|
4679
2940
|
function printSkill() {
|
|
4680
2941
|
return readFileSync(skillPath(), "utf-8");
|
|
4681
2942
|
}
|
|
4682
2943
|
function getAgentTargets() {
|
|
4683
|
-
const home =
|
|
2944
|
+
const home = homedir();
|
|
4684
2945
|
const targets = [
|
|
4685
|
-
{ name: "Claude Code", dir:
|
|
4686
|
-
{ name: "Codex", dir:
|
|
4687
|
-
{ name: "OpenCode", dir:
|
|
2946
|
+
{ name: "Claude Code", dir: join(home, ".claude", "skills", "clickup") },
|
|
2947
|
+
{ name: "Codex", dir: join(home, ".agents", "skills", "clickup") },
|
|
2948
|
+
{ name: "OpenCode", dir: join(home, ".config", "opencode", "skills", "clickup") }
|
|
4688
2949
|
];
|
|
4689
2950
|
return targets.map((t) => ({
|
|
4690
2951
|
...t,
|
|
@@ -4696,12 +2957,12 @@ async function installSkillInteractive() {
|
|
|
4696
2957
|
const source = skillPath();
|
|
4697
2958
|
const installed = [];
|
|
4698
2959
|
if (isTTY()) {
|
|
4699
|
-
const { checkbox
|
|
2960
|
+
const { checkbox } = await import("@inquirer/prompts");
|
|
4700
2961
|
const preselected = targets.filter((t) => t.detected).map((t) => t.name);
|
|
4701
|
-
const selected = await
|
|
2962
|
+
const selected = await checkbox({
|
|
4702
2963
|
message: "Install skill for which agents?",
|
|
4703
2964
|
choices: targets.map((t) => ({
|
|
4704
|
-
name: `${t.name}${t.detected ?
|
|
2965
|
+
name: `${t.name}${t.detected ? chalk5.dim(" (detected)") : ""}`,
|
|
4705
2966
|
value: t.name,
|
|
4706
2967
|
checked: t.detected
|
|
4707
2968
|
}))
|
|
@@ -4711,25 +2972,25 @@ async function installSkillInteractive() {
|
|
|
4711
2972
|
}
|
|
4712
2973
|
for (const name of selected) {
|
|
4713
2974
|
const target = targets.find((t) => t.name === name);
|
|
2975
|
+
if (!target) continue;
|
|
4714
2976
|
if (!existsSync(target.dir)) mkdirSync(target.dir, { recursive: true });
|
|
4715
|
-
const dest =
|
|
2977
|
+
const dest = join(target.dir, "SKILL.md");
|
|
4716
2978
|
copyFileSync(source, dest);
|
|
4717
2979
|
installed.push(`${target.name}: ${dest}`);
|
|
4718
2980
|
}
|
|
4719
2981
|
} else {
|
|
4720
|
-
|
|
2982
|
+
const detected = targets.filter((t) => t.detected);
|
|
2983
|
+
if (detected.length === 0) {
|
|
2984
|
+
throw new Error(
|
|
2985
|
+
"No agents detected. Use --path to specify install location:\n cup skill --path ~/.claude/skills/clickup/SKILL.md"
|
|
2986
|
+
);
|
|
2987
|
+
}
|
|
2988
|
+
for (const target of detected) {
|
|
4721
2989
|
if (!existsSync(target.dir)) mkdirSync(target.dir, { recursive: true });
|
|
4722
|
-
const dest =
|
|
2990
|
+
const dest = join(target.dir, "SKILL.md");
|
|
4723
2991
|
copyFileSync(source, dest);
|
|
4724
2992
|
installed.push(`${target.name}: ${dest}`);
|
|
4725
2993
|
}
|
|
4726
|
-
if (installed.length === 0) {
|
|
4727
|
-
const fallback = targets[0];
|
|
4728
|
-
mkdirSync(fallback.dir, { recursive: true });
|
|
4729
|
-
const dest = join2(fallback.dir, "SKILL.md");
|
|
4730
|
-
copyFileSync(source, dest);
|
|
4731
|
-
installed.push(`${fallback.name}: ${dest}`);
|
|
4732
|
-
}
|
|
4733
2994
|
}
|
|
4734
2995
|
return installed;
|
|
4735
2996
|
}
|
|
@@ -4946,8 +3207,8 @@ async function deleteTaskCommand(config, taskId, opts) {
|
|
|
4946
3207
|
throw new Error("Destructive operation requires --confirm flag in non-interactive mode");
|
|
4947
3208
|
}
|
|
4948
3209
|
const task = await client.getTask(taskId);
|
|
4949
|
-
const { confirm:
|
|
4950
|
-
const confirmed = await
|
|
3210
|
+
const { confirm: confirm2 } = await import("@inquirer/prompts");
|
|
3211
|
+
const confirmed = await confirm2({
|
|
4951
3212
|
message: `Delete task "${task.name}" (${task.id})? This cannot be undone.`,
|
|
4952
3213
|
default: false
|
|
4953
3214
|
});
|
|
@@ -4967,8 +3228,8 @@ async function archiveTaskCommand(config, taskId, opts) {
|
|
|
4967
3228
|
throw new Error(`Destructive operation requires --confirm flag in non-interactive mode`);
|
|
4968
3229
|
}
|
|
4969
3230
|
const task = await client.getTask(taskId);
|
|
4970
|
-
const { confirm:
|
|
4971
|
-
const confirmed = await
|
|
3231
|
+
const { confirm: confirm2 } = await import("@inquirer/prompts");
|
|
3232
|
+
const confirmed = await confirm2({
|
|
4972
3233
|
message: `${opts.unarchive ? "Unarchive" : "Archive"} task "${task.name}" (${task.id})?`,
|
|
4973
3234
|
default: false
|
|
4974
3235
|
});
|
|
@@ -5019,7 +3280,7 @@ async function manageTags(config, taskId, opts) {
|
|
|
5019
3280
|
}
|
|
5020
3281
|
|
|
5021
3282
|
// src/commands/checklist.ts
|
|
5022
|
-
import
|
|
3283
|
+
import chalk6 from "chalk";
|
|
5023
3284
|
async function viewChecklists(config, taskId) {
|
|
5024
3285
|
const client = new ClickUpClient(config);
|
|
5025
3286
|
const task = await client.getTask(taskId);
|
|
@@ -5052,14 +3313,14 @@ function formatChecklists(checklists) {
|
|
|
5052
3313
|
const lines = [];
|
|
5053
3314
|
for (const cl of checklists) {
|
|
5054
3315
|
const resolved = cl.items.filter((i) => i.resolved).length;
|
|
5055
|
-
lines.push(
|
|
5056
|
-
lines.push(
|
|
3316
|
+
lines.push(chalk6.bold(`${cl.name} (${resolved}/${cl.items.length})`));
|
|
3317
|
+
lines.push(chalk6.dim(` ID: ${cl.id}`));
|
|
5057
3318
|
for (const item of cl.items) {
|
|
5058
|
-
const check = item.resolved ?
|
|
5059
|
-
const name = item.resolved ?
|
|
5060
|
-
const assignee = item.assignee ?
|
|
3319
|
+
const check = item.resolved ? chalk6.green("[x]") : chalk6.dim("[ ]");
|
|
3320
|
+
const name = item.resolved ? chalk6.dim(item.name) : item.name;
|
|
3321
|
+
const assignee = item.assignee ? chalk6.dim(` @${item.assignee.username}`) : "";
|
|
5061
3322
|
lines.push(` ${check} ${name}${assignee}`);
|
|
5062
|
-
lines.push(
|
|
3323
|
+
lines.push(chalk6.dim(` item-id: ${item.id}`));
|
|
5063
3324
|
}
|
|
5064
3325
|
}
|
|
5065
3326
|
return lines.join("\n");
|
|
@@ -5091,9 +3352,34 @@ async function deleteComment(config, commentId) {
|
|
|
5091
3352
|
const client = new ClickUpClient(config);
|
|
5092
3353
|
await client.deleteComment(commentId);
|
|
5093
3354
|
}
|
|
3355
|
+
function matchesCommentText(commentText, match) {
|
|
3356
|
+
if (!match) return true;
|
|
3357
|
+
return commentText.toLowerCase().includes(match.toLowerCase());
|
|
3358
|
+
}
|
|
3359
|
+
async function deleteCommentByTaskSelection(config, taskId, options) {
|
|
3360
|
+
if (!options.mine) {
|
|
3361
|
+
throw new Error("Task-scoped comment deletion requires --mine");
|
|
3362
|
+
}
|
|
3363
|
+
const client = new ClickUpClient(config);
|
|
3364
|
+
const me = await client.getMe();
|
|
3365
|
+
const comments = await client.getTaskComments(taskId);
|
|
3366
|
+
const matches = comments.filter((comment2) => {
|
|
3367
|
+
const authorId = "id" in comment2.user ? comment2.user.id : void 0;
|
|
3368
|
+
return authorId === me.id && matchesCommentText(comment2.comment_text, options.match);
|
|
3369
|
+
});
|
|
3370
|
+
if (matches.length === 0) {
|
|
3371
|
+
throw new Error("No matching comments found for the current user");
|
|
3372
|
+
}
|
|
3373
|
+
if (matches.length > 1) {
|
|
3374
|
+
throw new Error("Multiple matching comments found - refine with --match or use a comment ID");
|
|
3375
|
+
}
|
|
3376
|
+
const comment = matches[0];
|
|
3377
|
+
await client.deleteComment(comment.id);
|
|
3378
|
+
return { commentId: comment.id, taskId };
|
|
3379
|
+
}
|
|
5094
3380
|
|
|
5095
3381
|
// src/commands/replies.ts
|
|
5096
|
-
import
|
|
3382
|
+
import chalk7 from "chalk";
|
|
5097
3383
|
async function getReplies(config, commentId) {
|
|
5098
3384
|
const client = new ClickUpClient(config);
|
|
5099
3385
|
return client.getThreadedComments(commentId);
|
|
@@ -5108,7 +3394,7 @@ function formatReplies(replies) {
|
|
|
5108
3394
|
return replies.map((r) => {
|
|
5109
3395
|
const user = r.user?.username ?? "Unknown";
|
|
5110
3396
|
const date = formatTimestamp(Number(r.date));
|
|
5111
|
-
return `${
|
|
3397
|
+
return `${chalk7.bold(user)} ${chalk7.dim(date)}
|
|
5112
3398
|
${r.comment_text}`;
|
|
5113
3399
|
}).join("\n\n");
|
|
5114
3400
|
}
|
|
@@ -5171,7 +3457,7 @@ function formatDocsMarkdown(docs) {
|
|
|
5171
3457
|
}
|
|
5172
3458
|
|
|
5173
3459
|
// src/commands/doc.ts
|
|
5174
|
-
import
|
|
3460
|
+
import chalk8 from "chalk";
|
|
5175
3461
|
async function getDocInfo(config, docId) {
|
|
5176
3462
|
const client = new ClickUpClient(config);
|
|
5177
3463
|
const [doc, pages] = await Promise.all([
|
|
@@ -5183,14 +3469,14 @@ async function getDocInfo(config, docId) {
|
|
|
5183
3469
|
function formatDocInfo(doc, pages, indent = 0) {
|
|
5184
3470
|
const lines = [];
|
|
5185
3471
|
if (indent === 0) {
|
|
5186
|
-
lines.push(`${
|
|
3472
|
+
lines.push(`${chalk8.bold(doc.name)} ${chalk8.dim(doc.id)}`);
|
|
5187
3473
|
if (pages.length === 0) {
|
|
5188
3474
|
lines.push(" (no pages)");
|
|
5189
3475
|
}
|
|
5190
3476
|
}
|
|
5191
3477
|
for (const page of pages) {
|
|
5192
3478
|
const prefix = " ".repeat(indent + 1);
|
|
5193
|
-
lines.push(`${prefix}${page.name} ${
|
|
3479
|
+
lines.push(`${prefix}${page.name} ${chalk8.dim(page.id)}`);
|
|
5194
3480
|
if (page.pages && page.pages.length > 0) {
|
|
5195
3481
|
lines.push(formatDocInfo(doc, page.pages, indent + 1));
|
|
5196
3482
|
}
|
|
@@ -5269,7 +3555,7 @@ async function deleteDocPage(config, docId, pageId) {
|
|
|
5269
3555
|
}
|
|
5270
3556
|
|
|
5271
3557
|
// src/commands/folders.ts
|
|
5272
|
-
import
|
|
3558
|
+
import chalk9 from "chalk";
|
|
5273
3559
|
async function listFolders(config, spaceId, nameFilter) {
|
|
5274
3560
|
const client = new ClickUpClient(config);
|
|
5275
3561
|
const folders = await client.getFolders(spaceId);
|
|
@@ -5288,9 +3574,9 @@ async function listFolders(config, spaceId, nameFilter) {
|
|
|
5288
3574
|
function formatFolders(folders) {
|
|
5289
3575
|
if (folders.length === 0) return "No folders found";
|
|
5290
3576
|
return folders.map((f) => {
|
|
5291
|
-
const header = `${
|
|
3577
|
+
const header = `${chalk9.bold(f.name)} ${chalk9.dim(f.id)}`;
|
|
5292
3578
|
if (f.lists.length === 0) return header;
|
|
5293
|
-
const listLines = f.lists.map((l) => ` ${
|
|
3579
|
+
const listLines = f.lists.map((l) => ` ${chalk9.dim(">")} ${l.name} ${chalk9.dim(l.id)}`);
|
|
5294
3580
|
return [header, ...listLines].join("\n");
|
|
5295
3581
|
}).join("\n\n");
|
|
5296
3582
|
}
|
|
@@ -5305,13 +3591,13 @@ function formatFoldersMarkdown(folders) {
|
|
|
5305
3591
|
}
|
|
5306
3592
|
|
|
5307
3593
|
// src/commands/time.ts
|
|
5308
|
-
import
|
|
3594
|
+
import chalk10 from "chalk";
|
|
5309
3595
|
var TIME_COLUMNS = [
|
|
5310
3596
|
{ key: "task", label: "Task", maxWidth: 35 },
|
|
5311
3597
|
{ key: "duration", label: "Duration", maxWidth: 10 },
|
|
5312
3598
|
{ key: "date", label: "Date", maxWidth: 20 },
|
|
5313
3599
|
{ key: "description", label: "Description", maxWidth: 30 },
|
|
5314
|
-
{ key: "status", label: "", maxWidth: 10, format: (v) => v === "RUNNING" ?
|
|
3600
|
+
{ key: "status", label: "", maxWidth: 10, format: (v) => v === "RUNNING" ? chalk10.green(v) : "" }
|
|
5315
3601
|
];
|
|
5316
3602
|
async function startTimer(config, taskId, description) {
|
|
5317
3603
|
const client = new ClickUpClient(config);
|
|
@@ -5407,7 +3693,7 @@ function formatTimeEntriesMarkdown(entries) {
|
|
|
5407
3693
|
}
|
|
5408
3694
|
|
|
5409
3695
|
// src/commands/tags.ts
|
|
5410
|
-
import
|
|
3696
|
+
import chalk11 from "chalk";
|
|
5411
3697
|
var TAG_COLUMNS = [
|
|
5412
3698
|
{ key: "name", label: "Name", maxWidth: 40 },
|
|
5413
3699
|
{ key: "fg", label: "FG", maxWidth: 10 },
|
|
@@ -5440,13 +3726,13 @@ function formatTags(tags) {
|
|
|
5440
3726
|
if (tags.length === 0) return "No tags found";
|
|
5441
3727
|
if (isTTY()) {
|
|
5442
3728
|
const rows = tags.map((t) => ({
|
|
5443
|
-
name: t.tag_bg ?
|
|
3729
|
+
name: t.tag_bg ? chalk11.bgHex(t.tag_bg).hex(t.tag_fg || "#ffffff")(` ${t.name} `) : chalk11.bold(t.name),
|
|
5444
3730
|
fg: t.tag_fg || "",
|
|
5445
3731
|
bg: t.tag_bg || ""
|
|
5446
3732
|
}));
|
|
5447
3733
|
return formatTable(rows, TAG_COLUMNS);
|
|
5448
3734
|
}
|
|
5449
|
-
return tags.map((t) =>
|
|
3735
|
+
return tags.map((t) => chalk11.bold(t.name)).join(", ");
|
|
5450
3736
|
}
|
|
5451
3737
|
function formatTagsMarkdown(tags) {
|
|
5452
3738
|
if (tags.length === 0) return "No tags found";
|
|
@@ -5481,7 +3767,7 @@ function formatMembersMarkdown(members) {
|
|
|
5481
3767
|
}
|
|
5482
3768
|
|
|
5483
3769
|
// src/commands/fields.ts
|
|
5484
|
-
import
|
|
3770
|
+
import chalk12 from "chalk";
|
|
5485
3771
|
var FIELD_COLUMNS = [
|
|
5486
3772
|
{ key: "id", label: "ID", maxWidth: 20 },
|
|
5487
3773
|
{ key: "name", label: "Name", maxWidth: 30 },
|
|
@@ -5490,7 +3776,7 @@ var FIELD_COLUMNS = [
|
|
|
5490
3776
|
key: "required",
|
|
5491
3777
|
label: "Required",
|
|
5492
3778
|
maxWidth: 10,
|
|
5493
|
-
format: (v) => v === "yes" ?
|
|
3779
|
+
format: (v) => v === "yes" ? chalk12.yellow(v) : chalk12.dim(v)
|
|
5494
3780
|
},
|
|
5495
3781
|
{ key: "options", label: "Options", maxWidth: 40 }
|
|
5496
3782
|
];
|
|
@@ -5597,13 +3883,13 @@ async function bulkTag(config, tagName, taskIds, action) {
|
|
|
5597
3883
|
}
|
|
5598
3884
|
|
|
5599
3885
|
// src/commands/goals.ts
|
|
5600
|
-
import
|
|
3886
|
+
import chalk13 from "chalk";
|
|
5601
3887
|
function colorProgress(value) {
|
|
5602
3888
|
const num = parseInt(value, 10);
|
|
5603
3889
|
if (isNaN(num)) return value;
|
|
5604
|
-
if (num >= 75) return
|
|
5605
|
-
if (num >= 25) return
|
|
5606
|
-
return
|
|
3890
|
+
if (num >= 75) return chalk13.green(value);
|
|
3891
|
+
if (num >= 25) return chalk13.yellow(value);
|
|
3892
|
+
return chalk13.red(value);
|
|
5607
3893
|
}
|
|
5608
3894
|
var GOAL_COLUMNS = [
|
|
5609
3895
|
{ key: "id", label: "ID", maxWidth: 15 },
|
|
@@ -5697,14 +3983,14 @@ function formatKeyResultsMarkdown(keyResults) {
|
|
|
5697
3983
|
}
|
|
5698
3984
|
|
|
5699
3985
|
// src/commands/task-types.ts
|
|
5700
|
-
import
|
|
3986
|
+
import chalk14 from "chalk";
|
|
5701
3987
|
async function listTaskTypes(config) {
|
|
5702
3988
|
const client = new ClickUpClient(config);
|
|
5703
3989
|
return client.getCustomTaskTypes(config.teamId);
|
|
5704
3990
|
}
|
|
5705
3991
|
function formatTaskTypes(types) {
|
|
5706
3992
|
if (types.length === 0) return "No custom task types";
|
|
5707
|
-
return types.map((t) => `${
|
|
3993
|
+
return types.map((t) => `${chalk14.bold(t.name)} ${chalk14.dim(`(${t.id})`)}`).join("\n");
|
|
5708
3994
|
}
|
|
5709
3995
|
function formatTaskTypesMarkdown(types) {
|
|
5710
3996
|
if (types.length === 0) return "No custom task types";
|
|
@@ -5712,14 +3998,14 @@ function formatTaskTypesMarkdown(types) {
|
|
|
5712
3998
|
}
|
|
5713
3999
|
|
|
5714
4000
|
// src/commands/templates.ts
|
|
5715
|
-
import
|
|
4001
|
+
import chalk15 from "chalk";
|
|
5716
4002
|
async function listTemplates(config) {
|
|
5717
4003
|
const client = new ClickUpClient(config);
|
|
5718
4004
|
return client.getTaskTemplates(config.teamId);
|
|
5719
4005
|
}
|
|
5720
4006
|
function formatTemplates(templates) {
|
|
5721
4007
|
if (templates.length === 0) return "No task templates";
|
|
5722
|
-
return templates.map((t) => `${
|
|
4008
|
+
return templates.map((t) => `${chalk15.bold(t.name)} ${chalk15.dim(`(${t.id})`)}`).join("\n");
|
|
5723
4009
|
}
|
|
5724
4010
|
function formatTemplatesMarkdown(templates) {
|
|
5725
4011
|
if (templates.length === 0) return "No task templates";
|
|
@@ -5727,14 +4013,14 @@ function formatTemplatesMarkdown(templates) {
|
|
|
5727
4013
|
}
|
|
5728
4014
|
|
|
5729
4015
|
// src/commands/list-templates.ts
|
|
5730
|
-
import
|
|
4016
|
+
import chalk16 from "chalk";
|
|
5731
4017
|
async function listListTemplates(config) {
|
|
5732
4018
|
const client = new ClickUpClient(config);
|
|
5733
4019
|
return client.getListTemplates(config.teamId);
|
|
5734
4020
|
}
|
|
5735
4021
|
function formatListTemplates(templates) {
|
|
5736
4022
|
if (templates.length === 0) return "No list templates";
|
|
5737
|
-
return templates.map((t) => `${
|
|
4023
|
+
return templates.map((t) => `${chalk16.bold(t.name)} ${chalk16.dim(`(${t.id})`)}`).join("\n");
|
|
5738
4024
|
}
|
|
5739
4025
|
function formatListTemplatesMarkdown(templates) {
|
|
5740
4026
|
if (templates.length === 0) return "No list templates";
|
|
@@ -5742,14 +4028,14 @@ function formatListTemplatesMarkdown(templates) {
|
|
|
5742
4028
|
}
|
|
5743
4029
|
|
|
5744
4030
|
// src/commands/folder-templates.ts
|
|
5745
|
-
import
|
|
4031
|
+
import chalk17 from "chalk";
|
|
5746
4032
|
async function listFolderTemplates(config) {
|
|
5747
4033
|
const client = new ClickUpClient(config);
|
|
5748
4034
|
return client.getFolderTemplates(config.teamId);
|
|
5749
4035
|
}
|
|
5750
4036
|
function formatFolderTemplates(templates) {
|
|
5751
4037
|
if (templates.length === 0) return "No folder templates";
|
|
5752
|
-
return templates.map((t) => `${
|
|
4038
|
+
return templates.map((t) => `${chalk17.bold(t.name)} ${chalk17.dim(`(${t.id})`)}`).join("\n");
|
|
5753
4039
|
}
|
|
5754
4040
|
function formatFolderTemplatesMarkdown(templates) {
|
|
5755
4041
|
if (templates.length === 0) return "No folder templates";
|
|
@@ -5772,7 +4058,7 @@ async function createListFromTemplate(config, name, opts) {
|
|
|
5772
4058
|
}
|
|
5773
4059
|
|
|
5774
4060
|
// src/commands/views.ts
|
|
5775
|
-
import
|
|
4061
|
+
import chalk18 from "chalk";
|
|
5776
4062
|
async function listViews(config, id, container = "list") {
|
|
5777
4063
|
const client = new ClickUpClient(config);
|
|
5778
4064
|
if (container === "space") return client.getSpaceViews(id);
|
|
@@ -5783,7 +4069,7 @@ async function listViews(config, id, container = "list") {
|
|
|
5783
4069
|
}
|
|
5784
4070
|
function formatViews(views) {
|
|
5785
4071
|
if (views.length === 0) return "No views";
|
|
5786
|
-
return views.map((v) => `${
|
|
4072
|
+
return views.map((v) => `${chalk18.bold(v.name)} ${chalk18.dim(`(${v.id})`)} ${chalk18.dim(v.type)}`).join("\n");
|
|
5787
4073
|
}
|
|
5788
4074
|
function formatViewsMarkdown(views) {
|
|
5789
4075
|
if (views.length === 0) return "No views";
|
|
@@ -5791,20 +4077,20 @@ function formatViewsMarkdown(views) {
|
|
|
5791
4077
|
}
|
|
5792
4078
|
|
|
5793
4079
|
// src/commands/view.ts
|
|
5794
|
-
import
|
|
4080
|
+
import chalk19 from "chalk";
|
|
5795
4081
|
async function getView(config, viewId) {
|
|
5796
4082
|
const client = new ClickUpClient(config);
|
|
5797
4083
|
return client.getView(viewId);
|
|
5798
4084
|
}
|
|
5799
4085
|
function formatView(view) {
|
|
5800
4086
|
const lines = [];
|
|
5801
|
-
lines.push(
|
|
4087
|
+
lines.push(chalk19.bold.underline(view.name));
|
|
5802
4088
|
lines.push("");
|
|
5803
|
-
lines.push(` ${
|
|
5804
|
-
lines.push(` ${
|
|
5805
|
-
if (view.visibility) lines.push(` ${
|
|
5806
|
-
if (view.date_created) lines.push(` ${
|
|
5807
|
-
if (view.protected !== void 0) lines.push(` ${
|
|
4089
|
+
lines.push(` ${chalk19.bold("ID")} ${view.id}`);
|
|
4090
|
+
lines.push(` ${chalk19.bold("Type")} ${view.type}`);
|
|
4091
|
+
if (view.visibility) lines.push(` ${chalk19.bold("Visibility")} ${view.visibility}`);
|
|
4092
|
+
if (view.date_created) lines.push(` ${chalk19.bold("Created")} ${formatDate(view.date_created)}`);
|
|
4093
|
+
if (view.protected !== void 0) lines.push(` ${chalk19.bold("Protected")} ${view.protected}`);
|
|
5808
4094
|
return lines.join("\n");
|
|
5809
4095
|
}
|
|
5810
4096
|
function formatViewMarkdown(view) {
|
|
@@ -5888,8 +4174,8 @@ async function deleteViewCommand(config, viewId, opts) {
|
|
|
5888
4174
|
throw new Error("Destructive operation requires --confirm flag in non-interactive mode");
|
|
5889
4175
|
}
|
|
5890
4176
|
const view = await client.getView(viewId);
|
|
5891
|
-
const { confirm:
|
|
5892
|
-
const confirmed = await
|
|
4177
|
+
const { confirm: confirm2 } = await import("@inquirer/prompts");
|
|
4178
|
+
const confirmed = await confirm2({
|
|
5893
4179
|
message: `Delete view "${view.name}" (${viewId})? This cannot be undone.`,
|
|
5894
4180
|
default: false
|
|
5895
4181
|
});
|
|
@@ -6028,6 +4314,50 @@ async function copyStatusesFrom(client, sourceId) {
|
|
|
6028
4314
|
}
|
|
6029
4315
|
}
|
|
6030
4316
|
|
|
4317
|
+
// src/commands/favorite.ts
|
|
4318
|
+
var VALID_TYPES = /* @__PURE__ */ new Set([
|
|
4319
|
+
"sprint-folder",
|
|
4320
|
+
"space",
|
|
4321
|
+
"list",
|
|
4322
|
+
"folder",
|
|
4323
|
+
"view",
|
|
4324
|
+
"task"
|
|
4325
|
+
]);
|
|
4326
|
+
function validateFavoriteType(type) {
|
|
4327
|
+
if (!VALID_TYPES.has(type)) {
|
|
4328
|
+
throw new Error(`Invalid favorite type: "${type}". Valid types: ${[...VALID_TYPES].join(", ")}`);
|
|
4329
|
+
}
|
|
4330
|
+
}
|
|
4331
|
+
function slugify(value) {
|
|
4332
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
4333
|
+
}
|
|
4334
|
+
var FAVORITE_COLUMNS = [
|
|
4335
|
+
{ key: "alias", label: "ALIAS", maxWidth: 30 },
|
|
4336
|
+
{ key: "type", label: "TYPE", maxWidth: 20 },
|
|
4337
|
+
{ key: "id", label: "ID", maxWidth: 20 },
|
|
4338
|
+
{ key: "name", label: "NAME", maxWidth: 40 }
|
|
4339
|
+
];
|
|
4340
|
+
function formatFavoritesTable(favorites) {
|
|
4341
|
+
const entries = Object.entries(favorites);
|
|
4342
|
+
if (entries.length === 0) return "No favorites saved";
|
|
4343
|
+
const rows = entries.map(([alias, entry]) => ({
|
|
4344
|
+
alias,
|
|
4345
|
+
type: entry.type,
|
|
4346
|
+
id: entry.id,
|
|
4347
|
+
name: entry.name ?? ""
|
|
4348
|
+
}));
|
|
4349
|
+
return formatTable(rows, FAVORITE_COLUMNS);
|
|
4350
|
+
}
|
|
4351
|
+
function formatFavoritesMarkdown(favorites) {
|
|
4352
|
+
const entries = Object.entries(favorites);
|
|
4353
|
+
if (entries.length === 0) return "No favorites saved";
|
|
4354
|
+
const lines = ["| Alias | Type | ID | Name |", "| --- | --- | --- | --- |"];
|
|
4355
|
+
for (const [alias, entry] of entries) {
|
|
4356
|
+
lines.push(`| ${alias} | ${entry.type} | ${entry.id} | ${entry.name ?? ""} |`);
|
|
4357
|
+
}
|
|
4358
|
+
return lines.join("\n");
|
|
4359
|
+
}
|
|
4360
|
+
|
|
6031
4361
|
// src/index.ts
|
|
6032
4362
|
var require2 = createRequire(import.meta.url);
|
|
6033
4363
|
var { version } = require2("../package.json");
|
|
@@ -6197,6 +4527,10 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
|
|
|
6197
4527
|
program.command("create").description("Create a new task").option("-l, --list <listId>", "Target list ID (auto-detected from --parent if omitted)").requiredOption("-n, --name <name>", "Task name").option("-d, --description <text>", "Task description (markdown supported)").option("-p, --parent <taskId>", "Parent task ID (list auto-detected from parent)").option("-s, --status <status>", "Initial status").option("--priority <level>", "Priority: urgent, high, normal, low (or 1-4)").option("--due-date <date>", "Due date (YYYY-MM-DD)").option("--start-date <date>", "Start date (YYYY-MM-DD)").option("--assignee <userId>", 'Assignee user ID or "me"').option("--tags <tags>", "Comma-separated tag names").option("--custom-item-id <id>", "Custom task type ID (use to create initiatives)").option("--time-estimate <duration>", 'Time estimate (e.g. "2h", "30m", "1h30m")').option("--template <id>", "Create from a task template").option("--json", "Force JSON output even in terminal").action(
|
|
6198
4528
|
wrapAction(async (opts) => {
|
|
6199
4529
|
const config = loadConfig(getProfileName());
|
|
4530
|
+
if (opts.list === "sprint:current") {
|
|
4531
|
+
const { resolveActiveSprintListId } = await import("./sprint-7S5UOUQX.js");
|
|
4532
|
+
opts.list = await resolveActiveSprintListId(config);
|
|
4533
|
+
}
|
|
6200
4534
|
if (opts.assignee === "me") {
|
|
6201
4535
|
const client = new ClickUpClient(config);
|
|
6202
4536
|
opts.assignee = String(await resolveAssigneeId(client, "me"));
|
|
@@ -6276,16 +4610,23 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
|
|
|
6276
4610
|
}
|
|
6277
4611
|
)
|
|
6278
4612
|
);
|
|
6279
|
-
program.command("comment-delete <commentId>").description("Delete a comment").option("--json", "Force JSON output even in terminal").action(
|
|
6280
|
-
wrapAction(
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
4613
|
+
program.command("comment-delete <commentId>").description("Delete a comment").option("--mine", "Delete one of my comments from the specified task instead of by comment ID").option("--match <text>", "Only match my task comments containing this text").option("--json", "Force JSON output even in terminal").action(
|
|
4614
|
+
wrapAction(
|
|
4615
|
+
async (commentId, opts) => {
|
|
4616
|
+
const config = loadConfig(getProfileName());
|
|
4617
|
+
const result = opts.mine || opts.match ? await deleteCommentByTaskSelection(config, commentId, {
|
|
4618
|
+
mine: opts.mine,
|
|
4619
|
+
match: opts.match
|
|
4620
|
+
}) : (await deleteComment(config, commentId), { commentId });
|
|
4621
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
4622
|
+
console.log(JSON.stringify({ success: true, ...result }, null, 2));
|
|
4623
|
+
} else {
|
|
4624
|
+
console.log(
|
|
4625
|
+
result.taskId ? `Deleted comment ${result.commentId} from task ${result.taskId}` : `Deleted comment ${result.commentId}`
|
|
4626
|
+
);
|
|
4627
|
+
}
|
|
6287
4628
|
}
|
|
6288
|
-
|
|
4629
|
+
)
|
|
6289
4630
|
);
|
|
6290
4631
|
program.command("replies <commentId>").description("List threaded replies on a comment").option("--json", "Force JSON output even in terminal").action(
|
|
6291
4632
|
wrapAction(async (commentId, opts) => {
|
|
@@ -6500,6 +4841,10 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
|
|
|
6500
4841
|
program.command("move <taskId>").description("Add or remove a task from a list").option("--to <listId>", "Add task to this list").option("--remove <listId>", "Remove task from this list").option("--json", "Force JSON output even in terminal").action(
|
|
6501
4842
|
wrapAction(async (taskId, opts) => {
|
|
6502
4843
|
const config = loadConfig(getProfileName());
|
|
4844
|
+
if (opts.to === "sprint:current") {
|
|
4845
|
+
const { resolveActiveSprintListId } = await import("./sprint-7S5UOUQX.js");
|
|
4846
|
+
opts.to = await resolveActiveSprintListId(config);
|
|
4847
|
+
}
|
|
6503
4848
|
const message = await moveTask(config, taskId, opts);
|
|
6504
4849
|
if (shouldOutputJson(opts.json ?? false)) {
|
|
6505
4850
|
console.log(
|
|
@@ -7478,6 +5823,49 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
|
|
|
7478
5823
|
}
|
|
7479
5824
|
})
|
|
7480
5825
|
);
|
|
5826
|
+
const favoriteCmd = program.command("favorite").description("Manage local favorites (sprint folders, spaces, lists, etc.)");
|
|
5827
|
+
favoriteCmd.command("add <type> <id> [alias]").description("Add a favorite (types: sprint-folder, space, list, folder, view, task)").option("-n, --name <name>", "Display name for the favorite").option("--json", "Force JSON output even in terminal").action(
|
|
5828
|
+
wrapAction(
|
|
5829
|
+
async (type, id, alias, opts) => {
|
|
5830
|
+
validateFavoriteType(type);
|
|
5831
|
+
const resolvedAlias = alias ?? slugify(opts.name ?? id);
|
|
5832
|
+
const entry = { type, id, ...opts.name ? { name: opts.name } : {} };
|
|
5833
|
+
saveFavorite(resolvedAlias, entry, getProfileName());
|
|
5834
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
5835
|
+
console.log(JSON.stringify({ alias: resolvedAlias, ...entry }, null, 2));
|
|
5836
|
+
} else {
|
|
5837
|
+
console.log(`Added favorite "${resolvedAlias}" (${type} ${id})`);
|
|
5838
|
+
}
|
|
5839
|
+
}
|
|
5840
|
+
)
|
|
5841
|
+
);
|
|
5842
|
+
favoriteCmd.command("remove <alias>").description("Remove a favorite by alias").option("--json", "Force JSON output even in terminal").action(
|
|
5843
|
+
wrapAction(async (alias, opts) => {
|
|
5844
|
+
deleteFavorite(alias, getProfileName());
|
|
5845
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
5846
|
+
console.log(JSON.stringify({ success: true, alias }, null, 2));
|
|
5847
|
+
} else {
|
|
5848
|
+
console.log(`Removed favorite "${alias}"`);
|
|
5849
|
+
}
|
|
5850
|
+
})
|
|
5851
|
+
);
|
|
5852
|
+
favoriteCmd.command("list").description("List saved favorites").option("--type <type>", "Filter by entity type").option("--json", "Force JSON output even in terminal").action(
|
|
5853
|
+
wrapAction(async (opts) => {
|
|
5854
|
+
let favorites = getFavorites(getProfileName());
|
|
5855
|
+
if (opts.type) {
|
|
5856
|
+
favorites = Object.fromEntries(
|
|
5857
|
+
Object.entries(favorites).filter(([, entry]) => entry.type === opts.type)
|
|
5858
|
+
);
|
|
5859
|
+
}
|
|
5860
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
5861
|
+
console.log(JSON.stringify(favorites, null, 2));
|
|
5862
|
+
} else if (isTTY()) {
|
|
5863
|
+
console.log(formatFavoritesTable(favorites));
|
|
5864
|
+
} else {
|
|
5865
|
+
console.log(formatFavoritesMarkdown(favorites));
|
|
5866
|
+
}
|
|
5867
|
+
})
|
|
5868
|
+
);
|
|
7481
5869
|
const profileCmd = program.command("profile").description("Manage profiles");
|
|
7482
5870
|
profileCmd.command("list").description("List all profiles").option("--json", "Force JSON output even in terminal").action(
|
|
7483
5871
|
wrapAction(async (opts) => {
|
|
@@ -7496,7 +5884,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
|
|
|
7496
5884
|
);
|
|
7497
5885
|
profileCmd.command("add <name>").description("Add a new profile").action(
|
|
7498
5886
|
wrapAction(async (name) => {
|
|
7499
|
-
const { password: password2, select:
|
|
5887
|
+
const { password: password2, select: select2 } = await import("@inquirer/prompts");
|
|
7500
5888
|
const apiToken = (await password2({ message: "ClickUp API token (pk_...):" })).trim();
|
|
7501
5889
|
if (!apiToken.startsWith("pk_")) throw new Error("Token must start with pk_");
|
|
7502
5890
|
const client = new ClickUpClient({ apiToken });
|
|
@@ -7511,7 +5899,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
|
|
|
7511
5899
|
process.stdout.write(`Workspace: ${teams[0].name}
|
|
7512
5900
|
`);
|
|
7513
5901
|
} else {
|
|
7514
|
-
teamId = await
|
|
5902
|
+
teamId = await select2({
|
|
7515
5903
|
message: "Select workspace:",
|
|
7516
5904
|
choices: teams.map((t) => ({ name: t.name, value: t.id }))
|
|
7517
5905
|
});
|
|
@@ -7549,7 +5937,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
|
|
|
7549
5937
|
);
|
|
7550
5938
|
configCmd.command("path").description("Print config file path").action(
|
|
7551
5939
|
wrapAction(async () => {
|
|
7552
|
-
console.log(
|
|
5940
|
+
console.log(configPath());
|
|
7553
5941
|
})
|
|
7554
5942
|
);
|
|
7555
5943
|
program.command("completion <shell>").description("Output shell completion script (bash, zsh, fish)").action(
|
|
@@ -7589,7 +5977,7 @@ process.on("SIGINT", () => {
|
|
|
7589
5977
|
});
|
|
7590
5978
|
function checkDirectExecution() {
|
|
7591
5979
|
try {
|
|
7592
|
-
return process.argv[1] !== void 0 &&
|
|
5980
|
+
return process.argv[1] !== void 0 && fileURLToPath(import.meta.url) === realpathSync2(resolve(process.argv[1]));
|
|
7593
5981
|
} catch {
|
|
7594
5982
|
return false;
|
|
7595
5983
|
}
|