@krodak/clickup-cli 0.21.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +22 -18
- package/dist/index.js +482 -117
- package/package.json +1 -2
- package/skills/clickup-cli/SKILL.md +97 -72
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { createRequire } from "module";
|
|
|
7
7
|
|
|
8
8
|
// src/api.ts
|
|
9
9
|
var BASE_URL = "https://api.clickup.com/api/v2";
|
|
10
|
+
var BASE_URL_V3 = "https://api.clickup.com/api/v3";
|
|
10
11
|
var MAX_PAGES = 100;
|
|
11
12
|
function isCustomTaskId(id) {
|
|
12
13
|
return /^[A-Z]+-\d+$/i.test(id);
|
|
@@ -56,6 +57,29 @@ var ClickUpClient = class {
|
|
|
56
57
|
}
|
|
57
58
|
return data;
|
|
58
59
|
}
|
|
60
|
+
async requestV3(path, options = {}) {
|
|
61
|
+
const res = await fetch(`${BASE_URL_V3}${path}`, {
|
|
62
|
+
...options,
|
|
63
|
+
signal: AbortSignal.timeout(3e4),
|
|
64
|
+
headers: {
|
|
65
|
+
Authorization: this.apiToken,
|
|
66
|
+
...options.body ? { "Content-Type": "application/json" } : {},
|
|
67
|
+
...options.headers
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
let data;
|
|
71
|
+
try {
|
|
72
|
+
data = await res.json();
|
|
73
|
+
} catch {
|
|
74
|
+
throw new Error(`ClickUp API error ${res.status}: response was not valid JSON`);
|
|
75
|
+
}
|
|
76
|
+
if (!res.ok) {
|
|
77
|
+
const raw = data.err ?? data.error ?? data.ECODE ?? res.statusText;
|
|
78
|
+
const msg = typeof raw === "string" ? raw : JSON.stringify(raw);
|
|
79
|
+
throw new Error(`ClickUp API error ${res.status}: ${msg}`);
|
|
80
|
+
}
|
|
81
|
+
return data;
|
|
82
|
+
}
|
|
59
83
|
async getMe() {
|
|
60
84
|
if (this.meCache) return this.meCache;
|
|
61
85
|
const data = await this.request("/user");
|
|
@@ -383,6 +407,42 @@ var ClickUpClient = class {
|
|
|
383
407
|
}
|
|
384
408
|
return data;
|
|
385
409
|
}
|
|
410
|
+
async getDocs(workspaceId) {
|
|
411
|
+
const data = await this.requestV3(`/workspaces/${workspaceId}/docs`);
|
|
412
|
+
return data.docs ?? [];
|
|
413
|
+
}
|
|
414
|
+
async getDocPage(workspaceId, docId, pageId) {
|
|
415
|
+
return this.requestV3(
|
|
416
|
+
`/workspaces/${workspaceId}/docs/${docId}/pages/${pageId}?content_format=text/md`
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
async createDoc(workspaceId, title, content, parentId) {
|
|
420
|
+
const body = { title };
|
|
421
|
+
if (content) body.content = content;
|
|
422
|
+
if (parentId) {
|
|
423
|
+
body.parent_id = parentId;
|
|
424
|
+
body.parent_type = "doc";
|
|
425
|
+
}
|
|
426
|
+
return this.requestV3(`/workspaces/${workspaceId}/docs`, {
|
|
427
|
+
method: "POST",
|
|
428
|
+
body: JSON.stringify(body)
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
async createDocPage(workspaceId, docId, name, content, parentPageId) {
|
|
432
|
+
const body = { name, content_format: "text/md" };
|
|
433
|
+
if (content) body.content = content;
|
|
434
|
+
if (parentPageId) body.parent_page_id = parentPageId;
|
|
435
|
+
return this.requestV3(`/workspaces/${workspaceId}/docs/${docId}/pages`, {
|
|
436
|
+
method: "POST",
|
|
437
|
+
body: JSON.stringify(body)
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
async editDocPage(workspaceId, docId, pageId, updates) {
|
|
441
|
+
return this.requestV3(`/workspaces/${workspaceId}/docs/${docId}/pages/${pageId}`, {
|
|
442
|
+
method: "PUT",
|
|
443
|
+
body: JSON.stringify(updates)
|
|
444
|
+
});
|
|
445
|
+
}
|
|
386
446
|
};
|
|
387
447
|
|
|
388
448
|
// src/config.ts
|
|
@@ -390,18 +450,36 @@ import fs from "fs";
|
|
|
390
450
|
import { homedir } from "os";
|
|
391
451
|
import { join } from "path";
|
|
392
452
|
function configDir() {
|
|
453
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
454
|
+
if (xdg) return join(xdg, "cup");
|
|
455
|
+
return join(homedir(), ".config", "cup");
|
|
456
|
+
}
|
|
457
|
+
function legacyConfigDir() {
|
|
393
458
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
394
459
|
if (xdg) return join(xdg, "cu");
|
|
395
460
|
return join(homedir(), ".config", "cu");
|
|
396
461
|
}
|
|
462
|
+
var migrationChecked = false;
|
|
463
|
+
function migrateFromLegacy() {
|
|
464
|
+
if (migrationChecked) return;
|
|
465
|
+
migrationChecked = true;
|
|
466
|
+
const legacy = legacyConfigDir();
|
|
467
|
+
const current = configDir();
|
|
468
|
+
if (fs.existsSync(join(legacy, "config.json")) && !fs.existsSync(join(current, "config.json"))) {
|
|
469
|
+
fs.mkdirSync(current, { recursive: true, mode: 448 });
|
|
470
|
+
fs.copyFileSync(join(legacy, "config.json"), join(current, "config.json"));
|
|
471
|
+
}
|
|
472
|
+
}
|
|
397
473
|
function configPath() {
|
|
398
474
|
return join(configDir(), "config.json");
|
|
399
475
|
}
|
|
400
476
|
function loadConfig() {
|
|
477
|
+
migrateFromLegacy();
|
|
401
478
|
const envToken = process.env.CU_API_TOKEN?.trim();
|
|
402
479
|
const envTeamId = process.env.CU_TEAM_ID?.trim();
|
|
403
480
|
let fileToken;
|
|
404
481
|
let fileTeamId;
|
|
482
|
+
let fileSprintFolderId;
|
|
405
483
|
const path = configPath();
|
|
406
484
|
if (fs.existsSync(path)) {
|
|
407
485
|
const raw = fs.readFileSync(path, "utf-8");
|
|
@@ -413,6 +491,7 @@ function loadConfig() {
|
|
|
413
491
|
}
|
|
414
492
|
fileToken = parsed.apiToken?.trim();
|
|
415
493
|
fileTeamId = parsed.teamId?.trim();
|
|
494
|
+
fileSprintFolderId = parsed.sprintFolderId?.trim() || void 0;
|
|
416
495
|
}
|
|
417
496
|
const apiToken = envToken || fileToken;
|
|
418
497
|
if (!apiToken) {
|
|
@@ -425,9 +504,10 @@ function loadConfig() {
|
|
|
425
504
|
if (!teamId) {
|
|
426
505
|
throw new Error("Config missing required field: teamId.\nSet CU_TEAM_ID or run: cup init");
|
|
427
506
|
}
|
|
428
|
-
return { apiToken, teamId };
|
|
507
|
+
return { apiToken, teamId, ...fileSprintFolderId ? { sprintFolderId: fileSprintFolderId } : {} };
|
|
429
508
|
}
|
|
430
509
|
function loadRawConfig() {
|
|
510
|
+
migrateFromLegacy();
|
|
431
511
|
const path = configPath();
|
|
432
512
|
if (!fs.existsSync(path)) return {};
|
|
433
513
|
try {
|
|
@@ -437,6 +517,7 @@ function loadRawConfig() {
|
|
|
437
517
|
}
|
|
438
518
|
}
|
|
439
519
|
function getConfigPath() {
|
|
520
|
+
migrateFromLegacy();
|
|
440
521
|
return configPath();
|
|
441
522
|
}
|
|
442
523
|
function writeConfig(config) {
|
|
@@ -453,10 +534,33 @@ function writeConfig(config) {
|
|
|
453
534
|
|
|
454
535
|
// src/date.ts
|
|
455
536
|
function formatDate(ms) {
|
|
537
|
+
return new Date(Number(ms)).toLocaleDateString("en-US", {
|
|
538
|
+
month: "short",
|
|
539
|
+
day: "numeric",
|
|
540
|
+
year: "numeric"
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
function formatTimestamp(ms) {
|
|
544
|
+
return new Date(Number(ms)).toLocaleString("en-US", {
|
|
545
|
+
month: "short",
|
|
546
|
+
day: "numeric",
|
|
547
|
+
hour: "numeric",
|
|
548
|
+
minute: "2-digit"
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
function formatDuration(ms) {
|
|
552
|
+
const totalMinutes = Math.round(Math.abs(ms) / 6e4);
|
|
553
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
554
|
+
const minutes = totalMinutes % 60;
|
|
555
|
+
if (hours > 0 && minutes > 0) return `${hours}h ${minutes}m`;
|
|
556
|
+
if (hours > 0) return `${hours}h`;
|
|
557
|
+
return `${minutes}m`;
|
|
558
|
+
}
|
|
559
|
+
function formatDateISO(ms) {
|
|
456
560
|
const d = new Date(Number(ms));
|
|
457
|
-
const year = d.
|
|
458
|
-
const month = String(d.
|
|
459
|
-
const day = String(d.
|
|
561
|
+
const year = d.getFullYear();
|
|
562
|
+
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
563
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
460
564
|
return `${year}-${month}-${day}`;
|
|
461
565
|
}
|
|
462
566
|
|
|
@@ -560,12 +664,6 @@ ${formatMarkdownTable(g.tasks, TASK_MD_COLUMNS)}`);
|
|
|
560
664
|
if (sections.length === 0) return "No tasks found.";
|
|
561
665
|
return sections.join("\n\n");
|
|
562
666
|
}
|
|
563
|
-
function formatDuration(ms) {
|
|
564
|
-
const totalMinutes = Math.floor(ms / 6e4);
|
|
565
|
-
const hours = Math.floor(totalMinutes / 60);
|
|
566
|
-
const minutes = totalMinutes % 60;
|
|
567
|
-
return `${hours}h ${minutes}m`;
|
|
568
|
-
}
|
|
569
667
|
function formatTaskDetailMarkdown(task) {
|
|
570
668
|
const lines = [`# ${task.name}`, ""];
|
|
571
669
|
const isInitiative = (task.custom_item_id ?? 0) !== 0;
|
|
@@ -581,8 +679,8 @@ function formatTaskDetailMarkdown(task) {
|
|
|
581
679
|
],
|
|
582
680
|
["Priority", task.priority?.priority],
|
|
583
681
|
["Parent", task.parent ?? void 0],
|
|
584
|
-
["Start Date", task.start_date ?
|
|
585
|
-
["Due Date", task.due_date ?
|
|
682
|
+
["Start Date", task.start_date ? formatDateISO(task.start_date) : void 0],
|
|
683
|
+
["Due Date", task.due_date ? formatDateISO(task.due_date) : void 0],
|
|
586
684
|
[
|
|
587
685
|
"Time Estimate",
|
|
588
686
|
task.time_estimate != null && task.time_estimate > 0 ? formatDuration(task.time_estimate) : void 0
|
|
@@ -592,8 +690,8 @@ function formatTaskDetailMarkdown(task) {
|
|
|
592
690
|
task.time_spent != null && task.time_spent > 0 ? formatDuration(task.time_spent) : void 0
|
|
593
691
|
],
|
|
594
692
|
["Tags", task.tags && task.tags.length > 0 ? task.tags.map((t) => t.name).join(", ") : void 0],
|
|
595
|
-
["Created", task.date_created ?
|
|
596
|
-
["Updated", task.date_updated ?
|
|
693
|
+
["Created", task.date_created ? formatDateISO(task.date_created) : void 0],
|
|
694
|
+
["Updated", task.date_updated ? formatDateISO(task.date_updated) : void 0]
|
|
597
695
|
];
|
|
598
696
|
for (const [label, value] of fields) {
|
|
599
697
|
if (value != null && value !== "") {
|
|
@@ -673,20 +771,6 @@ function openUrl(url) {
|
|
|
673
771
|
`);
|
|
674
772
|
}
|
|
675
773
|
}
|
|
676
|
-
function formatMs(ms) {
|
|
677
|
-
const hours = Math.floor(ms / 36e5);
|
|
678
|
-
const mins = Math.floor(ms % 36e5 / 6e4);
|
|
679
|
-
if (hours > 0 && mins > 0) return `${hours}h ${mins}m`;
|
|
680
|
-
if (hours > 0) return `${hours}h`;
|
|
681
|
-
return `${mins}m`;
|
|
682
|
-
}
|
|
683
|
-
function formatTimestamp(ts) {
|
|
684
|
-
return new Date(Number(ts)).toLocaleDateString("en-US", {
|
|
685
|
-
month: "short",
|
|
686
|
-
day: "numeric",
|
|
687
|
-
year: "numeric"
|
|
688
|
-
});
|
|
689
|
-
}
|
|
690
774
|
function descriptionPreview(text, maxLines = 3) {
|
|
691
775
|
const lines = text.split("\n").filter((l) => l.trim().length > 0);
|
|
692
776
|
const preview = lines.slice(0, maxLines);
|
|
@@ -746,10 +830,10 @@ function formatTaskDetail(task) {
|
|
|
746
830
|
task.assignees?.length ? task.assignees.map((a) => a.username).join(", ") : void 0
|
|
747
831
|
],
|
|
748
832
|
["Priority", task.priority?.priority],
|
|
749
|
-
["Start", task.start_date ?
|
|
750
|
-
["Due", task.due_date ?
|
|
751
|
-
["Estimate", task.time_estimate ?
|
|
752
|
-
["Tracked", task.time_spent ?
|
|
833
|
+
["Start", task.start_date ? formatDate(task.start_date) : void 0],
|
|
834
|
+
["Due", task.due_date ? formatDate(task.due_date) : void 0],
|
|
835
|
+
["Estimate", task.time_estimate ? formatDuration(task.time_estimate) : void 0],
|
|
836
|
+
["Tracked", task.time_spent ? formatDuration(task.time_spent) : void 0],
|
|
753
837
|
["Tags", task.tags?.length ? task.tags.map((t) => t.name).join(", ") : void 0],
|
|
754
838
|
["Parent", task.parent || void 0],
|
|
755
839
|
["URL", task.url]
|
|
@@ -1004,7 +1088,8 @@ function parseDueDate(value) {
|
|
|
1004
1088
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
1005
1089
|
throw new Error("Date must be in YYYY-MM-DD format");
|
|
1006
1090
|
}
|
|
1007
|
-
const
|
|
1091
|
+
const parts = value.split("-");
|
|
1092
|
+
const date = new Date(Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]));
|
|
1008
1093
|
if (isNaN(date.getTime())) throw new Error(`Invalid date: ${value}`);
|
|
1009
1094
|
return date.getTime();
|
|
1010
1095
|
}
|
|
@@ -1180,23 +1265,75 @@ async function runInitCommand() {
|
|
|
1180
1265
|
}
|
|
1181
1266
|
|
|
1182
1267
|
// src/commands/sprint.ts
|
|
1183
|
-
|
|
1268
|
+
import { select as select2 } from "@inquirer/prompts";
|
|
1269
|
+
var SPRINT_KEYWORDS = ["sprint", "iteration", "cycle", "scrum"];
|
|
1270
|
+
function parseUSDateRange(name) {
|
|
1184
1271
|
const m = name.match(/\((\d{1,2})\/(\d{1,2})\s*[-–]\s*(\d{1,2})\/(\d{1,2})\)/);
|
|
1185
1272
|
if (!m) return null;
|
|
1186
1273
|
const year = (/* @__PURE__ */ new Date()).getFullYear();
|
|
1187
1274
|
const start = new Date(year, Number(m[1]) - 1, Number(m[2]));
|
|
1188
1275
|
const end = new Date(year, Number(m[3]) - 1, Number(m[4]), 23, 59, 59);
|
|
1189
|
-
if (end < start)
|
|
1190
|
-
|
|
1191
|
-
|
|
1276
|
+
if (end < start) end.setFullYear(end.getFullYear() + 1);
|
|
1277
|
+
return { start, end };
|
|
1278
|
+
}
|
|
1279
|
+
function parseISODateRange(name) {
|
|
1280
|
+
const m = name.match(/\((\d{4}-\d{2}-\d{2})\s*[-–]\s*(\d{4}-\d{2}-\d{2})\)/);
|
|
1281
|
+
if (!m) return null;
|
|
1282
|
+
const [sy, sm, sd] = m[1].split("-").map(Number);
|
|
1283
|
+
const [ey, em, ed] = m[2].split("-").map(Number);
|
|
1284
|
+
const start = new Date(sy, sm - 1, sd);
|
|
1285
|
+
const end = new Date(ey, em - 1, ed, 23, 59, 59);
|
|
1192
1286
|
return { start, end };
|
|
1193
1287
|
}
|
|
1288
|
+
function parseMonthDayRange(name) {
|
|
1289
|
+
const months = {
|
|
1290
|
+
jan: 0,
|
|
1291
|
+
feb: 1,
|
|
1292
|
+
mar: 2,
|
|
1293
|
+
apr: 3,
|
|
1294
|
+
may: 4,
|
|
1295
|
+
jun: 5,
|
|
1296
|
+
jul: 6,
|
|
1297
|
+
aug: 7,
|
|
1298
|
+
sep: 8,
|
|
1299
|
+
oct: 9,
|
|
1300
|
+
nov: 10,
|
|
1301
|
+
dec: 11
|
|
1302
|
+
};
|
|
1303
|
+
const m = name.match(/\(([A-Za-z]{3})\s+(\d{1,2})\s*[-–]\s*([A-Za-z]{3})\s+(\d{1,2})\)/);
|
|
1304
|
+
if (!m) return null;
|
|
1305
|
+
const sm = months[m[1].toLowerCase()];
|
|
1306
|
+
const em = months[m[3].toLowerCase()];
|
|
1307
|
+
if (sm === void 0 || em === void 0) return null;
|
|
1308
|
+
const year = (/* @__PURE__ */ new Date()).getFullYear();
|
|
1309
|
+
const start = new Date(year, sm, Number(m[2]));
|
|
1310
|
+
const end = new Date(year, em, Number(m[4]), 23, 59, 59);
|
|
1311
|
+
if (end < start) end.setFullYear(end.getFullYear() + 1);
|
|
1312
|
+
return { start, end };
|
|
1313
|
+
}
|
|
1314
|
+
function parseEuropeanDateRange(name) {
|
|
1315
|
+
const m = name.match(/\((\d{1,2})\.(\d{1,2})\s*[-–]\s*(\d{1,2})\.(\d{1,2})\)/);
|
|
1316
|
+
if (!m) return null;
|
|
1317
|
+
const year = (/* @__PURE__ */ new Date()).getFullYear();
|
|
1318
|
+
const start = new Date(year, Number(m[2]) - 1, Number(m[1]));
|
|
1319
|
+
const end = new Date(year, Number(m[4]) - 1, Number(m[3]), 23, 59, 59);
|
|
1320
|
+
if (end < start) end.setFullYear(end.getFullYear() + 1);
|
|
1321
|
+
return { start, end };
|
|
1322
|
+
}
|
|
1323
|
+
function parseSprintDates(name) {
|
|
1324
|
+
return parseUSDateRange(name) ?? parseISODateRange(name) ?? parseMonthDayRange(name) ?? parseEuropeanDateRange(name);
|
|
1325
|
+
}
|
|
1194
1326
|
function findActiveSprintList(lists, today = /* @__PURE__ */ new Date()) {
|
|
1195
1327
|
if (lists.length === 0) return null;
|
|
1196
1328
|
for (const list of lists) {
|
|
1197
1329
|
const dates = parseSprintDates(list.name);
|
|
1198
|
-
if (dates && today >= dates.start && today <= dates.end)
|
|
1199
|
-
|
|
1330
|
+
if (dates && today >= dates.start && today <= dates.end) return list;
|
|
1331
|
+
}
|
|
1332
|
+
for (const list of lists) {
|
|
1333
|
+
if (list.start_date && list.due_date) {
|
|
1334
|
+
const start = new Date(Number(list.start_date));
|
|
1335
|
+
const end = new Date(Number(list.due_date));
|
|
1336
|
+
if (today >= start && today <= end) return list;
|
|
1200
1337
|
}
|
|
1201
1338
|
}
|
|
1202
1339
|
return lists[lists.length - 1] ?? null;
|
|
@@ -1216,37 +1353,67 @@ function findRelatedSpaces(mySpaceIds, allSpaces) {
|
|
|
1216
1353
|
async function runSprintCommand(config, opts) {
|
|
1217
1354
|
const client = new ClickUpClient(config);
|
|
1218
1355
|
process.stderr.write("Detecting active sprint...\n");
|
|
1356
|
+
const folderId = opts.folder ?? config.sprintFolderId;
|
|
1219
1357
|
const [myTasks, allSpaces, customTypes] = await Promise.all([
|
|
1220
1358
|
client.getMyTasks(config.teamId),
|
|
1221
|
-
client.getSpaces(config.teamId),
|
|
1359
|
+
folderId ? Promise.resolve([]) : client.getSpaces(config.teamId),
|
|
1222
1360
|
client.getCustomTaskTypes(config.teamId)
|
|
1223
1361
|
]);
|
|
1224
1362
|
const typeMap = buildTypeMap(customTypes);
|
|
1225
|
-
let
|
|
1226
|
-
if (
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
if (
|
|
1231
|
-
|
|
1232
|
-
|
|
1363
|
+
let sprintLists;
|
|
1364
|
+
if (folderId) {
|
|
1365
|
+
sprintLists = await client.getFolderLists(folderId);
|
|
1366
|
+
} else {
|
|
1367
|
+
let spaces;
|
|
1368
|
+
if (opts.space) {
|
|
1369
|
+
spaces = allSpaces.filter(
|
|
1370
|
+
(s) => s.name.toLowerCase().includes(opts.space.toLowerCase()) || s.id === opts.space
|
|
1371
|
+
);
|
|
1372
|
+
if (spaces.length === 0) {
|
|
1373
|
+
throw new Error(
|
|
1374
|
+
`No space matching "${opts.space}" found. Use \`cup spaces\` to list available spaces.`
|
|
1375
|
+
);
|
|
1376
|
+
}
|
|
1377
|
+
} else {
|
|
1378
|
+
const mySpaceIds = new Set(
|
|
1379
|
+
myTasks.map((t) => t.space?.id).filter((id) => Boolean(id))
|
|
1233
1380
|
);
|
|
1381
|
+
spaces = findRelatedSpaces(mySpaceIds, allSpaces);
|
|
1234
1382
|
}
|
|
1235
|
-
|
|
1236
|
-
const
|
|
1237
|
-
|
|
1383
|
+
const foldersBySpace = await Promise.all(spaces.map((space) => client.getFolders(space.id)));
|
|
1384
|
+
const sprintFolders = foldersBySpace.flat().filter((f) => {
|
|
1385
|
+
const lower = f.name.toLowerCase();
|
|
1386
|
+
return SPRINT_KEYWORDS.some((kw) => lower.includes(kw));
|
|
1387
|
+
});
|
|
1388
|
+
const listsByFolder = await Promise.all(
|
|
1389
|
+
sprintFolders.map((folder) => client.getFolderLists(folder.id))
|
|
1238
1390
|
);
|
|
1239
|
-
|
|
1391
|
+
sprintLists = listsByFolder.flat();
|
|
1392
|
+
}
|
|
1393
|
+
let activeList = findActiveSprintList(sprintLists);
|
|
1394
|
+
if (!activeList && sprintLists.length > 1 && isTTY()) {
|
|
1395
|
+
const choice = await select2({
|
|
1396
|
+
message: "Multiple sprint lists found. Which one?",
|
|
1397
|
+
choices: sprintLists.map((l) => ({
|
|
1398
|
+
name: `${l.name} (${l.id})`,
|
|
1399
|
+
value: l
|
|
1400
|
+
}))
|
|
1401
|
+
});
|
|
1402
|
+
activeList = choice;
|
|
1403
|
+
}
|
|
1404
|
+
if (!activeList && sprintLists.length > 1) {
|
|
1405
|
+
process.stderr.write(
|
|
1406
|
+
`Multiple sprint lists found:
|
|
1407
|
+
${sprintLists.map((l) => ` - ${l.name} (${l.id})`).join("\n")}
|
|
1408
|
+
Using: ${sprintLists[sprintLists.length - 1].name}
|
|
1409
|
+
`
|
|
1410
|
+
);
|
|
1411
|
+
activeList = sprintLists[sprintLists.length - 1] ?? null;
|
|
1240
1412
|
}
|
|
1241
|
-
const foldersBySpace = await Promise.all(spaces.map((space) => client.getFolders(space.id)));
|
|
1242
|
-
const sprintFolders = foldersBySpace.flat().filter((f) => f.name.toLowerCase().includes("sprint"));
|
|
1243
|
-
const listsByFolder = await Promise.all(
|
|
1244
|
-
sprintFolders.map((folder) => client.getFolderLists(folder.id))
|
|
1245
|
-
);
|
|
1246
|
-
const sprintLists = listsByFolder.flat();
|
|
1247
|
-
const activeList = findActiveSprintList(sprintLists);
|
|
1248
1413
|
if (!activeList) {
|
|
1249
|
-
throw new Error(
|
|
1414
|
+
throw new Error(
|
|
1415
|
+
'No sprint list found. Ensure sprint folders contain "sprint", "iteration", "cycle", or "scrum" in their name.'
|
|
1416
|
+
);
|
|
1250
1417
|
}
|
|
1251
1418
|
process.stderr.write(`Active sprint: ${activeList.name}
|
|
1252
1419
|
`);
|
|
@@ -1274,7 +1441,7 @@ var SPRINT_COLUMNS = [
|
|
|
1274
1441
|
{ key: "sprint", label: "SPRINT", maxWidth: 60 },
|
|
1275
1442
|
{ key: "dates", label: "DATES" }
|
|
1276
1443
|
];
|
|
1277
|
-
function
|
|
1444
|
+
function formatSprintDate(d) {
|
|
1278
1445
|
return `${d.getMonth() + 1}/${d.getDate()}`;
|
|
1279
1446
|
}
|
|
1280
1447
|
function buildSprintInfos(lists, folderName, today) {
|
|
@@ -1304,7 +1471,7 @@ async function listSprints(config, opts = {}) {
|
|
|
1304
1471
|
);
|
|
1305
1472
|
if (spaces.length === 0) {
|
|
1306
1473
|
throw new Error(
|
|
1307
|
-
`No space matching "${opts.space}" found. Use \`
|
|
1474
|
+
`No space matching "${opts.space}" found. Use \`cup spaces\` to list available spaces.`
|
|
1308
1475
|
);
|
|
1309
1476
|
}
|
|
1310
1477
|
} else {
|
|
@@ -1314,7 +1481,10 @@ async function listSprints(config, opts = {}) {
|
|
|
1314
1481
|
spaces = findRelatedSpaces(mySpaceIds, allSpaces);
|
|
1315
1482
|
}
|
|
1316
1483
|
const foldersBySpace = await Promise.all(spaces.map((space) => client.getFolders(space.id)));
|
|
1317
|
-
const sprintFolders = foldersBySpace.flat().filter((f) =>
|
|
1484
|
+
const sprintFolders = foldersBySpace.flat().filter((f) => {
|
|
1485
|
+
const lower = f.name.toLowerCase();
|
|
1486
|
+
return SPRINT_KEYWORDS.some((kw) => lower.includes(kw));
|
|
1487
|
+
});
|
|
1318
1488
|
const today = /* @__PURE__ */ new Date();
|
|
1319
1489
|
const allSprints = [];
|
|
1320
1490
|
const listsByFolder = await Promise.all(
|
|
@@ -1335,7 +1505,7 @@ async function listSprints(config, opts = {}) {
|
|
|
1335
1505
|
}
|
|
1336
1506
|
const rows = allSprints.map((s) => {
|
|
1337
1507
|
const dates = parseSprintDates(s.name);
|
|
1338
|
-
const dateStr = dates ? `${
|
|
1508
|
+
const dateStr = dates ? `${formatSprintDate(dates.start)} - ${formatSprintDate(dates.end)}` : "";
|
|
1339
1509
|
return {
|
|
1340
1510
|
id: s.id,
|
|
1341
1511
|
sprint: s.active ? `* ${s.name}` : s.name,
|
|
@@ -1379,15 +1549,6 @@ async function postComment(config, taskId, text, notifyAll) {
|
|
|
1379
1549
|
|
|
1380
1550
|
// src/commands/comments.ts
|
|
1381
1551
|
import chalk3 from "chalk";
|
|
1382
|
-
function formatDate3(timestamp) {
|
|
1383
|
-
return new Date(Number(timestamp)).toLocaleString("en-US", {
|
|
1384
|
-
month: "short",
|
|
1385
|
-
day: "numeric",
|
|
1386
|
-
year: "numeric",
|
|
1387
|
-
hour: "numeric",
|
|
1388
|
-
minute: "2-digit"
|
|
1389
|
-
});
|
|
1390
|
-
}
|
|
1391
1552
|
async function fetchComments(config, taskId) {
|
|
1392
1553
|
const client = new ClickUpClient(config);
|
|
1393
1554
|
const comments = await client.getTaskComments(taskId);
|
|
@@ -1415,7 +1576,7 @@ function printComments(comments, forceJson) {
|
|
|
1415
1576
|
for (let i = 0; i < comments.length; i++) {
|
|
1416
1577
|
const c = comments[i];
|
|
1417
1578
|
if (i > 0) console.log(separator);
|
|
1418
|
-
console.log(`${chalk3.bold(c.user)} ${chalk3.dim(
|
|
1579
|
+
console.log(`${chalk3.bold(c.user)} ${chalk3.dim(formatTimestamp(c.date))}`);
|
|
1419
1580
|
console.log(c.text);
|
|
1420
1581
|
if (i < comments.length - 1) console.log("");
|
|
1421
1582
|
}
|
|
@@ -1812,7 +1973,7 @@ async function fetchOverdueTasks(config, opts = {}) {
|
|
|
1812
1973
|
}
|
|
1813
1974
|
|
|
1814
1975
|
// src/commands/config.ts
|
|
1815
|
-
var VALID_KEYS = /* @__PURE__ */ new Set(["apiToken", "teamId"]);
|
|
1976
|
+
var VALID_KEYS = /* @__PURE__ */ new Set(["apiToken", "teamId", "sprintFolderId"]);
|
|
1816
1977
|
function assertValidKey(key) {
|
|
1817
1978
|
if (!VALID_KEYS.has(key)) {
|
|
1818
1979
|
throw new Error(`Unknown config key: ${key}. Valid keys: ${[...VALID_KEYS].join(", ")}`);
|
|
@@ -1868,15 +2029,6 @@ async function assignTask(config, taskId, opts) {
|
|
|
1868
2029
|
|
|
1869
2030
|
// src/commands/activity.ts
|
|
1870
2031
|
import chalk4 from "chalk";
|
|
1871
|
-
function formatDate4(timestamp) {
|
|
1872
|
-
return new Date(Number(timestamp)).toLocaleString("en-US", {
|
|
1873
|
-
month: "short",
|
|
1874
|
-
day: "numeric",
|
|
1875
|
-
year: "numeric",
|
|
1876
|
-
hour: "numeric",
|
|
1877
|
-
minute: "2-digit"
|
|
1878
|
-
});
|
|
1879
|
-
}
|
|
1880
2032
|
async function fetchActivity(config, taskId) {
|
|
1881
2033
|
const client = new ClickUpClient(config);
|
|
1882
2034
|
const [task, rawComments] = await Promise.all([
|
|
@@ -1920,7 +2072,7 @@ ${commentsMd}`);
|
|
|
1920
2072
|
console.log("");
|
|
1921
2073
|
console.log(chalk4.dim("-".repeat(60)));
|
|
1922
2074
|
}
|
|
1923
|
-
console.log(`${chalk4.bold(c.user)} ${chalk4.dim(
|
|
2075
|
+
console.log(`${chalk4.bold(c.user)} ${chalk4.dim(formatTimestamp(c.date))}`);
|
|
1924
2076
|
console.log(c.text);
|
|
1925
2077
|
}
|
|
1926
2078
|
}
|
|
@@ -1939,7 +2091,7 @@ function bashCompletion(name) {
|
|
|
1939
2091
|
cword=$COMP_CWORD
|
|
1940
2092
|
fi
|
|
1941
2093
|
|
|
1942
|
-
local commands="init auth tasks task update create sprint sprints subtasks comment comment-edit comment-delete comments replies reply activity lists spaces inbox assigned open search summary overdue assign depend link attach move field delete tag checklist time config completion"
|
|
2094
|
+
local commands="init auth tasks task update create sprint sprints subtasks comment comment-edit comment-delete comments replies reply activity lists spaces inbox assigned open search summary overdue assign depend link attach move field delete tag checklist time docs doc doc-create doc-page-create doc-page-edit config completion"
|
|
1943
2095
|
|
|
1944
2096
|
if [[ $cword -eq 1 ]]; then
|
|
1945
2097
|
COMPREPLY=($(compgen -W "$commands --help --version" -- "$cur"))
|
|
@@ -1973,7 +2125,7 @@ function bashCompletion(name) {
|
|
|
1973
2125
|
COMPREPLY=($(compgen -W "-l --list -n --name -d --description -p --parent -s --status --priority --due-date --assignee --tags --custom-item-id --time-estimate --json" -- "$cur"))
|
|
1974
2126
|
;;
|
|
1975
2127
|
sprint)
|
|
1976
|
-
COMPREPLY=($(compgen -W "--status --space --include-closed --json" -- "$cur"))
|
|
2128
|
+
COMPREPLY=($(compgen -W "--status --space --folder --include-closed --json" -- "$cur"))
|
|
1977
2129
|
;;
|
|
1978
2130
|
sprints)
|
|
1979
2131
|
COMPREPLY=($(compgen -W "--space --json" -- "$cur"))
|
|
@@ -2063,6 +2215,21 @@ function bashCompletion(name) {
|
|
|
2063
2215
|
attach)
|
|
2064
2216
|
COMPREPLY=($(compgen -f -- "$cur"))
|
|
2065
2217
|
;;
|
|
2218
|
+
docs)
|
|
2219
|
+
COMPREPLY=($(compgen -W "--json" -- "$cur"))
|
|
2220
|
+
;;
|
|
2221
|
+
doc)
|
|
2222
|
+
COMPREPLY=($(compgen -W "--json" -- "$cur"))
|
|
2223
|
+
;;
|
|
2224
|
+
doc-create)
|
|
2225
|
+
COMPREPLY=($(compgen -W "-c --content --json" -- "$cur"))
|
|
2226
|
+
;;
|
|
2227
|
+
doc-page-create)
|
|
2228
|
+
COMPREPLY=($(compgen -W "-c --content --parent-page --json" -- "$cur"))
|
|
2229
|
+
;;
|
|
2230
|
+
doc-page-edit)
|
|
2231
|
+
COMPREPLY=($(compgen -W "--name -c --content --json" -- "$cur"))
|
|
2232
|
+
;;
|
|
2066
2233
|
config)
|
|
2067
2234
|
if [[ $cword -eq 2 ]]; then
|
|
2068
2235
|
COMPREPLY=($(compgen -W "get set path" -- "$cur"))
|
|
@@ -2070,7 +2237,7 @@ function bashCompletion(name) {
|
|
|
2070
2237
|
local subcmd="\${words[2]}"
|
|
2071
2238
|
case "$subcmd" in
|
|
2072
2239
|
get|set)
|
|
2073
|
-
COMPREPLY=($(compgen -W "apiToken teamId" -- "$cur"))
|
|
2240
|
+
COMPREPLY=($(compgen -W "apiToken teamId sprintFolderId" -- "$cur"))
|
|
2074
2241
|
;;
|
|
2075
2242
|
esac
|
|
2076
2243
|
fi
|
|
@@ -2123,6 +2290,11 @@ _${name}() {
|
|
|
2123
2290
|
'reply:Reply to a comment'
|
|
2124
2291
|
'link:Add or remove a link between two tasks'
|
|
2125
2292
|
'attach:Upload a file attachment to a task'
|
|
2293
|
+
'docs:List workspace docs'
|
|
2294
|
+
'doc:View a doc page'
|
|
2295
|
+
'doc-create:Create a new doc'
|
|
2296
|
+
'doc-page-create:Create a page in a doc'
|
|
2297
|
+
'doc-page-edit:Edit a doc page'
|
|
2126
2298
|
'config:Manage CLI configuration'
|
|
2127
2299
|
'completion:Output shell completion script'
|
|
2128
2300
|
)
|
|
@@ -2186,6 +2358,7 @@ _${name}() {
|
|
|
2186
2358
|
_arguments \\
|
|
2187
2359
|
'--status[Filter by status]:status:(open "in progress" "in review" done closed)' \\
|
|
2188
2360
|
'--space[Narrow sprint search to a space]:space:' \\
|
|
2361
|
+
'--folder[Sprint folder ID]:folder_id:' \\
|
|
2189
2362
|
'--include-closed[Include done/closed tasks]' \\
|
|
2190
2363
|
'--json[Force JSON output]'
|
|
2191
2364
|
;;
|
|
@@ -2444,6 +2617,39 @@ _${name}() {
|
|
|
2444
2617
|
'2:file_path:_files' \\
|
|
2445
2618
|
'--json[Force JSON output]'
|
|
2446
2619
|
;;
|
|
2620
|
+
docs)
|
|
2621
|
+
_arguments \\
|
|
2622
|
+
'1:query:' \\
|
|
2623
|
+
'--json[Force JSON output]'
|
|
2624
|
+
;;
|
|
2625
|
+
doc)
|
|
2626
|
+
_arguments \\
|
|
2627
|
+
'1:doc_id:' \\
|
|
2628
|
+
'2:page_id:' \\
|
|
2629
|
+
'--json[Force JSON output]'
|
|
2630
|
+
;;
|
|
2631
|
+
doc-create)
|
|
2632
|
+
_arguments \\
|
|
2633
|
+
'1:title:' \\
|
|
2634
|
+
'(-c --content)'{-c,--content}'[Initial content]:text:' \\
|
|
2635
|
+
'--json[Force JSON output]'
|
|
2636
|
+
;;
|
|
2637
|
+
doc-page-create)
|
|
2638
|
+
_arguments \\
|
|
2639
|
+
'1:doc_id:' \\
|
|
2640
|
+
'2:name:' \\
|
|
2641
|
+
'(-c --content)'{-c,--content}'[Page content]:text:' \\
|
|
2642
|
+
'--parent-page[Parent page ID]:page_id:' \\
|
|
2643
|
+
'--json[Force JSON output]'
|
|
2644
|
+
;;
|
|
2645
|
+
doc-page-edit)
|
|
2646
|
+
_arguments \\
|
|
2647
|
+
'1:doc_id:' \\
|
|
2648
|
+
'2:page_id:' \\
|
|
2649
|
+
'--name[New page name]:text:' \\
|
|
2650
|
+
'(-c --content)'{-c,--content}'[New page content]:text:' \\
|
|
2651
|
+
'--json[Force JSON output]'
|
|
2652
|
+
;;
|
|
2447
2653
|
config)
|
|
2448
2654
|
local -a config_cmds
|
|
2449
2655
|
config_cmds=(
|
|
@@ -2461,7 +2667,7 @@ _${name}() {
|
|
|
2461
2667
|
config_args)
|
|
2462
2668
|
case $words[1] in
|
|
2463
2669
|
get|set)
|
|
2464
|
-
_arguments '1:key:(apiToken teamId)'
|
|
2670
|
+
_arguments '1:key:(apiToken teamId sprintFolderId)'
|
|
2465
2671
|
;;
|
|
2466
2672
|
esac
|
|
2467
2673
|
;;
|
|
@@ -2518,6 +2724,11 @@ complete -c ${name} -n __fish_use_subcommand -a replies -d 'List threaded replie
|
|
|
2518
2724
|
complete -c ${name} -n __fish_use_subcommand -a reply -d 'Reply to a comment'
|
|
2519
2725
|
complete -c ${name} -n __fish_use_subcommand -a link -d 'Add or remove a link between two tasks'
|
|
2520
2726
|
complete -c ${name} -n __fish_use_subcommand -a attach -d 'Upload a file attachment to a task'
|
|
2727
|
+
complete -c ${name} -n __fish_use_subcommand -a docs -d 'List workspace docs'
|
|
2728
|
+
complete -c ${name} -n __fish_use_subcommand -a doc -d 'View a doc page'
|
|
2729
|
+
complete -c ${name} -n __fish_use_subcommand -a doc-create -d 'Create a new doc'
|
|
2730
|
+
complete -c ${name} -n __fish_use_subcommand -a doc-page-create -d 'Create a page in a doc'
|
|
2731
|
+
complete -c ${name} -n __fish_use_subcommand -a doc-page-edit -d 'Edit a doc page'
|
|
2521
2732
|
complete -c ${name} -n __fish_use_subcommand -a config -d 'Manage CLI configuration'
|
|
2522
2733
|
complete -c ${name} -n __fish_use_subcommand -a completion -d 'Output shell completion script'
|
|
2523
2734
|
|
|
@@ -2558,6 +2769,7 @@ complete -c ${name} -n '__fish_seen_subcommand_from create' -l json -d 'Force JS
|
|
|
2558
2769
|
|
|
2559
2770
|
complete -c ${name} -n '__fish_seen_subcommand_from sprint' -l status -d 'Filter by status'
|
|
2560
2771
|
complete -c ${name} -n '__fish_seen_subcommand_from sprint' -l space -d 'Narrow sprint search to a space'
|
|
2772
|
+
complete -c ${name} -n '__fish_seen_subcommand_from sprint' -l folder -d 'Sprint folder ID'
|
|
2561
2773
|
complete -c ${name} -n '__fish_seen_subcommand_from sprint' -l include-closed -d 'Include done/closed tasks'
|
|
2562
2774
|
complete -c ${name} -n '__fish_seen_subcommand_from sprint' -l json -d 'Force JSON output'
|
|
2563
2775
|
|
|
@@ -2670,10 +2882,25 @@ complete -c ${name} -n '__fish_seen_subcommand_from comment-edit' -l resolved -d
|
|
|
2670
2882
|
complete -c ${name} -n '__fish_seen_subcommand_from comment-edit' -l unresolved -d 'Mark comment as unresolved'
|
|
2671
2883
|
complete -c ${name} -n '__fish_seen_subcommand_from comment-edit' -l json -d 'Force JSON output'
|
|
2672
2884
|
|
|
2885
|
+
complete -c ${name} -n '__fish_seen_subcommand_from docs' -l json -d 'Force JSON output'
|
|
2886
|
+
|
|
2887
|
+
complete -c ${name} -n '__fish_seen_subcommand_from doc' -l json -d 'Force JSON output'
|
|
2888
|
+
|
|
2889
|
+
complete -c ${name} -n '__fish_seen_subcommand_from doc-create' -s c -l content -d 'Initial content'
|
|
2890
|
+
complete -c ${name} -n '__fish_seen_subcommand_from doc-create' -l json -d 'Force JSON output'
|
|
2891
|
+
|
|
2892
|
+
complete -c ${name} -n '__fish_seen_subcommand_from doc-page-create' -s c -l content -d 'Page content'
|
|
2893
|
+
complete -c ${name} -n '__fish_seen_subcommand_from doc-page-create' -l parent-page -d 'Parent page ID'
|
|
2894
|
+
complete -c ${name} -n '__fish_seen_subcommand_from doc-page-create' -l json -d 'Force JSON output'
|
|
2895
|
+
|
|
2896
|
+
complete -c ${name} -n '__fish_seen_subcommand_from doc-page-edit' -l name -d 'New page name'
|
|
2897
|
+
complete -c ${name} -n '__fish_seen_subcommand_from doc-page-edit' -s c -l content -d 'New page content'
|
|
2898
|
+
complete -c ${name} -n '__fish_seen_subcommand_from doc-page-edit' -l json -d 'Force JSON output'
|
|
2899
|
+
|
|
2673
2900
|
complete -c ${name} -n '__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from get set path' -a get -d 'Print a config value'
|
|
2674
2901
|
complete -c ${name} -n '__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from get set path' -a set -d 'Set a config value'
|
|
2675
2902
|
complete -c ${name} -n '__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from get set path' -a path -d 'Print config file path'
|
|
2676
|
-
complete -c ${name} -n '__fish_seen_subcommand_from get set' -a 'apiToken teamId' -d 'Config key'
|
|
2903
|
+
complete -c ${name} -n '__fish_seen_subcommand_from get set' -a 'apiToken teamId sprintFolderId' -d 'Config key'
|
|
2677
2904
|
|
|
2678
2905
|
complete -c ${name} -n '__fish_seen_subcommand_from completion' -a 'bash zsh fish' -d 'Shell type'
|
|
2679
2906
|
`;
|
|
@@ -2969,6 +3196,17 @@ function formatChecklists(checklists) {
|
|
|
2969
3196
|
}
|
|
2970
3197
|
return lines.join("\n");
|
|
2971
3198
|
}
|
|
3199
|
+
function formatChecklistsMarkdown(checklists) {
|
|
3200
|
+
if (checklists.length === 0) return "No checklists";
|
|
3201
|
+
return checklists.map((cl) => {
|
|
3202
|
+
const resolved = cl.items.filter((i) => i.resolved).length;
|
|
3203
|
+
const header = `### ${cl.name} (${resolved}/${cl.items.length})`;
|
|
3204
|
+
const items = cl.items.map(
|
|
3205
|
+
(item) => `- [${item.resolved ? "x" : " "}] ${item.name}`
|
|
3206
|
+
);
|
|
3207
|
+
return [header, "", ...items].join("\n");
|
|
3208
|
+
}).join("\n\n");
|
|
3209
|
+
}
|
|
2972
3210
|
|
|
2973
3211
|
// src/commands/comment-edit.ts
|
|
2974
3212
|
async function editComment(config, commentId, text, resolved) {
|
|
@@ -2998,11 +3236,21 @@ function formatReplies(replies) {
|
|
|
2998
3236
|
if (replies.length === 0) return "No replies";
|
|
2999
3237
|
return replies.map((r) => {
|
|
3000
3238
|
const user = r.user?.username ?? "Unknown";
|
|
3001
|
-
const date =
|
|
3239
|
+
const date = formatTimestamp(Number(r.date));
|
|
3002
3240
|
return `${chalk6.bold(user)} ${chalk6.dim(date)}
|
|
3003
3241
|
${r.comment_text}`;
|
|
3004
3242
|
}).join("\n\n");
|
|
3005
3243
|
}
|
|
3244
|
+
function formatRepliesMarkdown(replies) {
|
|
3245
|
+
if (replies.length === 0) return "No replies";
|
|
3246
|
+
return replies.map((r) => {
|
|
3247
|
+
const user = r.user?.username ?? "Unknown";
|
|
3248
|
+
const date = formatTimestamp(Number(r.date));
|
|
3249
|
+
return `**${user}** (${date})
|
|
3250
|
+
|
|
3251
|
+
${r.comment_text}`;
|
|
3252
|
+
}).join("\n\n---\n\n");
|
|
3253
|
+
}
|
|
3006
3254
|
|
|
3007
3255
|
// src/commands/link.ts
|
|
3008
3256
|
async function manageTaskLink(config, taskId, linksTo, remove) {
|
|
@@ -3027,24 +3275,52 @@ async function attachFile(config, taskId, filePath) {
|
|
|
3027
3275
|
return client.createTaskAttachment(taskId, filePath);
|
|
3028
3276
|
}
|
|
3029
3277
|
|
|
3030
|
-
// src/commands/
|
|
3278
|
+
// src/commands/docs.ts
|
|
3031
3279
|
import chalk7 from "chalk";
|
|
3032
|
-
function
|
|
3033
|
-
const
|
|
3034
|
-
const
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3280
|
+
async function listDocs(config, query) {
|
|
3281
|
+
const client = new ClickUpClient(config);
|
|
3282
|
+
const docs = await client.getDocs(config.teamId);
|
|
3283
|
+
if (query) {
|
|
3284
|
+
const lower = query.toLowerCase();
|
|
3285
|
+
return docs.filter((d) => d.name.toLowerCase().includes(lower));
|
|
3286
|
+
}
|
|
3287
|
+
return docs;
|
|
3039
3288
|
}
|
|
3040
|
-
function
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
});
|
|
3289
|
+
function formatDocs(docs) {
|
|
3290
|
+
if (docs.length === 0) return "No docs found";
|
|
3291
|
+
return docs.map((d) => `${chalk7.bold(d.name)} ${chalk7.dim(d.id)}`).join("\n");
|
|
3292
|
+
}
|
|
3293
|
+
function formatDocsMarkdown(docs) {
|
|
3294
|
+
if (docs.length === 0) return "No docs found";
|
|
3295
|
+
return docs.map((d) => `- **${d.name}** (${d.id})`).join("\n");
|
|
3296
|
+
}
|
|
3297
|
+
|
|
3298
|
+
// src/commands/doc.ts
|
|
3299
|
+
async function getDocPage(config, docId, pageId) {
|
|
3300
|
+
const client = new ClickUpClient(config);
|
|
3301
|
+
return client.getDocPage(config.teamId, docId, pageId);
|
|
3302
|
+
}
|
|
3303
|
+
async function createDoc(config, title, content) {
|
|
3304
|
+
if (!title.trim()) throw new Error("Doc title cannot be empty");
|
|
3305
|
+
const client = new ClickUpClient(config);
|
|
3306
|
+
const doc = await client.createDoc(config.teamId, title, content);
|
|
3307
|
+
return { id: doc.id, title: doc.name ?? title };
|
|
3047
3308
|
}
|
|
3309
|
+
async function createDocPage(config, docId, name, content, parentPageId) {
|
|
3310
|
+
if (!name.trim()) throw new Error("Page name cannot be empty");
|
|
3311
|
+
const client = new ClickUpClient(config);
|
|
3312
|
+
return client.createDocPage(config.teamId, docId, name, content, parentPageId);
|
|
3313
|
+
}
|
|
3314
|
+
async function editDocPage(config, docId, pageId, updates) {
|
|
3315
|
+
if (!updates.name && !updates.content) {
|
|
3316
|
+
throw new Error("Provide --name or --content to update");
|
|
3317
|
+
}
|
|
3318
|
+
const client = new ClickUpClient(config);
|
|
3319
|
+
return client.editDocPage(config.teamId, docId, pageId, updates);
|
|
3320
|
+
}
|
|
3321
|
+
|
|
3322
|
+
// src/commands/time.ts
|
|
3323
|
+
import chalk8 from "chalk";
|
|
3048
3324
|
async function startTimer(config, taskId, description) {
|
|
3049
3325
|
const client = new ClickUpClient(config);
|
|
3050
3326
|
return client.startTimeEntry(config.teamId, taskId, description);
|
|
@@ -3079,11 +3355,11 @@ function formatTimeEntry(entry) {
|
|
|
3079
3355
|
const taskId = entry.task?.id ?? "";
|
|
3080
3356
|
const isRunning = entry.duration < 0;
|
|
3081
3357
|
const elapsed = isRunning ? Date.now() - Number(entry.start) : entry.duration;
|
|
3082
|
-
const durationStr =
|
|
3083
|
-
const status = isRunning ?
|
|
3084
|
-
lines.push(`${
|
|
3358
|
+
const durationStr = formatDuration(elapsed);
|
|
3359
|
+
const status = isRunning ? chalk8.green("RUNNING") : "";
|
|
3360
|
+
lines.push(`${chalk8.bold(taskName)} ${chalk8.dim(taskId)} ${status}`);
|
|
3085
3361
|
lines.push(
|
|
3086
|
-
` ${durationStr} - ${
|
|
3362
|
+
` ${durationStr} - ${formatTimestamp(entry.start)}${entry.description ? ` - ${entry.description}` : ""}`
|
|
3087
3363
|
);
|
|
3088
3364
|
return lines.join("\n");
|
|
3089
3365
|
}
|
|
@@ -3091,6 +3367,19 @@ function formatTimeEntries(entries) {
|
|
|
3091
3367
|
if (entries.length === 0) return "No time entries";
|
|
3092
3368
|
return entries.map(formatTimeEntry).join("\n");
|
|
3093
3369
|
}
|
|
3370
|
+
function formatTimeEntryMarkdown(entry) {
|
|
3371
|
+
const taskName = entry.task?.name ?? "No task";
|
|
3372
|
+
const taskId = entry.task?.id ?? "";
|
|
3373
|
+
const isRunning = entry.duration < 0;
|
|
3374
|
+
const elapsed = isRunning ? Date.now() - Number(entry.start) : entry.duration;
|
|
3375
|
+
const durationStr = formatDuration(elapsed);
|
|
3376
|
+
const status = isRunning ? " (RUNNING)" : "";
|
|
3377
|
+
return `**${taskName}** ${taskId}${status} - ${durationStr}${entry.description ? ` - ${entry.description}` : ""}`;
|
|
3378
|
+
}
|
|
3379
|
+
function formatTimeEntriesMarkdown(entries) {
|
|
3380
|
+
if (entries.length === 0) return "No time entries";
|
|
3381
|
+
return entries.map(formatTimeEntryMarkdown).join("\n");
|
|
3382
|
+
}
|
|
3094
3383
|
|
|
3095
3384
|
// src/index.ts
|
|
3096
3385
|
var require2 = createRequire(import.meta.url);
|
|
@@ -3185,7 +3474,7 @@ program.command("create").description("Create a new task").option("-l, --list <l
|
|
|
3185
3474
|
}
|
|
3186
3475
|
})
|
|
3187
3476
|
);
|
|
3188
|
-
program.command("sprint").description("List my tasks in the current active sprint (auto-detected)").option("--status <status>", "Filter by status").option("--space <nameOrId>", "Narrow sprint search to a specific space (partial name or ID)").option("--include-closed", "Include done/closed tasks").option("--json", "Force JSON output even in terminal").action(
|
|
3477
|
+
program.command("sprint").description("List my tasks in the current active sprint (auto-detected)").option("--status <status>", "Filter by status").option("--space <nameOrId>", "Narrow sprint search to a specific space (partial name or ID)").option("--folder <folderId>", "Sprint folder ID (overrides config and auto-detection)").option("--include-closed", "Include done/closed tasks").option("--json", "Force JSON output even in terminal").action(
|
|
3189
3478
|
wrapAction(
|
|
3190
3479
|
async (opts) => {
|
|
3191
3480
|
const config = loadConfig();
|
|
@@ -3269,8 +3558,10 @@ program.command("replies <commentId>").description("List threaded replies on a c
|
|
|
3269
3558
|
const replies = await getReplies(config, commentId);
|
|
3270
3559
|
if (shouldOutputJson(opts.json ?? false)) {
|
|
3271
3560
|
console.log(JSON.stringify(replies, null, 2));
|
|
3272
|
-
} else {
|
|
3561
|
+
} else if (isTTY()) {
|
|
3273
3562
|
console.log(formatReplies(replies));
|
|
3563
|
+
} else {
|
|
3564
|
+
console.log(formatRepliesMarkdown(replies));
|
|
3274
3565
|
}
|
|
3275
3566
|
})
|
|
3276
3567
|
);
|
|
@@ -3490,8 +3781,10 @@ checklistCmd.command("view <taskId>").description("View checklists on a task").o
|
|
|
3490
3781
|
const checklists = await viewChecklists(config, taskId);
|
|
3491
3782
|
if (shouldOutputJson(opts.json ?? false)) {
|
|
3492
3783
|
console.log(JSON.stringify(checklists, null, 2));
|
|
3493
|
-
} else {
|
|
3784
|
+
} else if (isTTY()) {
|
|
3494
3785
|
console.log(formatChecklists(checklists));
|
|
3786
|
+
} else {
|
|
3787
|
+
console.log(formatChecklistsMarkdown(checklists));
|
|
3495
3788
|
}
|
|
3496
3789
|
})
|
|
3497
3790
|
);
|
|
@@ -3578,8 +3871,10 @@ timeCmd.command("stop").description("Stop the running timer").option("--json", "
|
|
|
3578
3871
|
const result = await stopTimer(config);
|
|
3579
3872
|
if (shouldOutputJson(opts.json ?? false)) {
|
|
3580
3873
|
console.log(JSON.stringify(result, null, 2));
|
|
3581
|
-
} else {
|
|
3874
|
+
} else if (isTTY()) {
|
|
3582
3875
|
console.log(formatTimeEntry(result));
|
|
3876
|
+
} else {
|
|
3877
|
+
console.log(formatTimeEntryMarkdown(result));
|
|
3583
3878
|
}
|
|
3584
3879
|
})
|
|
3585
3880
|
);
|
|
@@ -3589,10 +3884,12 @@ timeCmd.command("status").description("Show the currently running timer").option
|
|
|
3589
3884
|
const result = await timerStatus(config);
|
|
3590
3885
|
if (shouldOutputJson(opts.json ?? false)) {
|
|
3591
3886
|
console.log(JSON.stringify(result, null, 2));
|
|
3592
|
-
} else if (result) {
|
|
3887
|
+
} else if (!result) {
|
|
3888
|
+
console.log("No timer running");
|
|
3889
|
+
} else if (isTTY()) {
|
|
3593
3890
|
console.log(formatTimeEntry(result));
|
|
3594
3891
|
} else {
|
|
3595
|
-
console.log(
|
|
3892
|
+
console.log(formatTimeEntryMarkdown(result));
|
|
3596
3893
|
}
|
|
3597
3894
|
})
|
|
3598
3895
|
);
|
|
@@ -3619,11 +3916,79 @@ timeCmd.command("list").description("List recent time entries (default: last 7 d
|
|
|
3619
3916
|
const entries = await listTimeEntries(config, { days, taskId: opts.task });
|
|
3620
3917
|
if (shouldOutputJson(opts.json ?? false)) {
|
|
3621
3918
|
console.log(JSON.stringify(entries, null, 2));
|
|
3622
|
-
} else {
|
|
3919
|
+
} else if (isTTY()) {
|
|
3623
3920
|
console.log(formatTimeEntries(entries));
|
|
3921
|
+
} else {
|
|
3922
|
+
console.log(formatTimeEntriesMarkdown(entries));
|
|
3923
|
+
}
|
|
3924
|
+
})
|
|
3925
|
+
);
|
|
3926
|
+
program.command("docs [query]").description("List workspace docs (optionally filter by name)").option("--json", "Force JSON output even in terminal").action(
|
|
3927
|
+
wrapAction(async (query, opts) => {
|
|
3928
|
+
const config = loadConfig();
|
|
3929
|
+
const docs = await listDocs(config, query);
|
|
3930
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
3931
|
+
console.log(JSON.stringify(docs, null, 2));
|
|
3932
|
+
} else if (isTTY()) {
|
|
3933
|
+
console.log(formatDocs(docs));
|
|
3934
|
+
} else {
|
|
3935
|
+
console.log(formatDocsMarkdown(docs));
|
|
3624
3936
|
}
|
|
3625
3937
|
})
|
|
3626
3938
|
);
|
|
3939
|
+
program.command("doc <docId> <pageId>").description("View a doc page").option("--json", "Force JSON output even in terminal").action(
|
|
3940
|
+
wrapAction(async (docId, pageId, opts) => {
|
|
3941
|
+
const config = loadConfig();
|
|
3942
|
+
const page = await getDocPage(config, docId, pageId);
|
|
3943
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
3944
|
+
console.log(JSON.stringify(page, null, 2));
|
|
3945
|
+
} else {
|
|
3946
|
+
if (page.name) console.log(`# ${page.name}
|
|
3947
|
+
`);
|
|
3948
|
+
console.log(page.content ?? "");
|
|
3949
|
+
}
|
|
3950
|
+
})
|
|
3951
|
+
);
|
|
3952
|
+
program.command("doc-create <title>").description("Create a new doc").option("-c, --content <text>", "Initial content (markdown)").option("--json", "Force JSON output even in terminal").action(
|
|
3953
|
+
wrapAction(async (title, opts) => {
|
|
3954
|
+
const config = loadConfig();
|
|
3955
|
+
const result = await createDoc(config, title, opts.content);
|
|
3956
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
3957
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3958
|
+
} else {
|
|
3959
|
+
console.log(`Created doc "${result.title}" (${result.id})`);
|
|
3960
|
+
}
|
|
3961
|
+
})
|
|
3962
|
+
);
|
|
3963
|
+
program.command("doc-page-create <docId> <name>").description("Create a page in a doc").option("-c, --content <text>", "Page content (markdown)").option("--parent-page <pageId>", "Parent page ID for nesting").option("--json", "Force JSON output even in terminal").action(
|
|
3964
|
+
wrapAction(
|
|
3965
|
+
async (docId, name, opts) => {
|
|
3966
|
+
const config = loadConfig();
|
|
3967
|
+
const page = await createDocPage(config, docId, name, opts.content, opts.parentPage);
|
|
3968
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
3969
|
+
console.log(JSON.stringify(page, null, 2));
|
|
3970
|
+
} else {
|
|
3971
|
+
console.log(`Created page "${page.name}" (${page.id}) in doc ${docId}`);
|
|
3972
|
+
}
|
|
3973
|
+
}
|
|
3974
|
+
)
|
|
3975
|
+
);
|
|
3976
|
+
program.command("doc-page-edit <docId> <pageId>").description("Edit a doc page").option("--name <text>", "New page name").option("-c, --content <text>", "New page content (markdown)").option("--json", "Force JSON output even in terminal").action(
|
|
3977
|
+
wrapAction(
|
|
3978
|
+
async (docId, pageId, opts) => {
|
|
3979
|
+
const config = loadConfig();
|
|
3980
|
+
const page = await editDocPage(config, docId, pageId, {
|
|
3981
|
+
name: opts.name,
|
|
3982
|
+
content: opts.content
|
|
3983
|
+
});
|
|
3984
|
+
if (shouldOutputJson(opts.json ?? false)) {
|
|
3985
|
+
console.log(JSON.stringify(page, null, 2));
|
|
3986
|
+
} else {
|
|
3987
|
+
console.log(`Updated page "${page.name}" (${page.id})`);
|
|
3988
|
+
}
|
|
3989
|
+
}
|
|
3990
|
+
)
|
|
3991
|
+
);
|
|
3627
3992
|
var configCmd = program.command("config").description("Manage CLI configuration");
|
|
3628
3993
|
configCmd.command("get <key>").description("Print a config value").action(
|
|
3629
3994
|
wrapAction(async (key) => {
|