@krodak/clickup-cli 1.45.0 → 1.45.2
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.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clickup-cli",
|
|
3
3
|
"description": "ClickUp CLI skills for managing tasks, sprints, comments, checklists, custom fields, tags, and time tracking via the cup command",
|
|
4
|
-
"version": "1.45.
|
|
4
|
+
"version": "1.45.2",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Krzysztof Rodak"
|
|
7
7
|
},
|
|
@@ -269,6 +269,30 @@ async function writeBundleData(root, bundle, opts) {
|
|
|
269
269
|
]);
|
|
270
270
|
return { dir, contentHash, attachmentsDownloaded: downloaded, attachmentsFailed: failed };
|
|
271
271
|
}
|
|
272
|
+
async function backfillAttachments(root, taskId, download = defaultDownload) {
|
|
273
|
+
const dir = taskDir(root, taskId);
|
|
274
|
+
const file = join(dir, "attachments.json");
|
|
275
|
+
const entries = JSON.parse(await readFile(file, "utf8"));
|
|
276
|
+
const pending = entries.filter((a) => !a.local);
|
|
277
|
+
if (pending.length === 0) return { downloaded: 0, failed: [] };
|
|
278
|
+
const attDir = join(dir, "attachments");
|
|
279
|
+
await mkdir(attDir, { recursive: true });
|
|
280
|
+
const failed = [];
|
|
281
|
+
let downloaded = 0;
|
|
282
|
+
for (const att of pending) {
|
|
283
|
+
const name = safeAttachmentFilename(att.id, att.title);
|
|
284
|
+
const target = join(attDir, name);
|
|
285
|
+
try {
|
|
286
|
+
if (!existsSync(target)) await writeFile(target, await download(att.url));
|
|
287
|
+
att.local = `attachments/${name}`;
|
|
288
|
+
downloaded++;
|
|
289
|
+
} catch (err) {
|
|
290
|
+
failed.push({ id: att.id, title: att.title, error: err.message });
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
await writeFile(file, JSON.stringify(entries, null, 2) + "\n");
|
|
294
|
+
return { downloaded, failed };
|
|
295
|
+
}
|
|
272
296
|
async function readBundleData(root, taskId) {
|
|
273
297
|
const dir = taskDir(root, taskId);
|
|
274
298
|
const [task, comments, attachments, meta] = await Promise.all([
|
|
@@ -318,6 +342,7 @@ export {
|
|
|
318
342
|
safeAttachmentFilename,
|
|
319
343
|
taskDir,
|
|
320
344
|
writeBundleData,
|
|
345
|
+
backfillAttachments,
|
|
321
346
|
readBundleData,
|
|
322
347
|
renderBundleMarkdown,
|
|
323
348
|
writeTaskBundle
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
backfillAttachments,
|
|
3
4
|
formatDate,
|
|
4
5
|
formatDateISO,
|
|
5
6
|
formatDuration,
|
|
@@ -8,7 +9,7 @@ import {
|
|
|
8
9
|
readBundleData,
|
|
9
10
|
renderBundleMarkdown,
|
|
10
11
|
writeBundleData
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-OPPKVJ6P.js";
|
|
12
13
|
|
|
13
14
|
// src/index.ts
|
|
14
15
|
import { realpathSync as realpathSync2 } from "fs";
|
|
@@ -7463,6 +7464,11 @@ function saveManifest(root, manifest) {
|
|
|
7463
7464
|
renameSync(tmp, path);
|
|
7464
7465
|
}
|
|
7465
7466
|
|
|
7467
|
+
// src/util/plural.ts
|
|
7468
|
+
function plural(n, singular, pluralForm = `${singular}s`) {
|
|
7469
|
+
return `${n} ${n === 1 ? singular : pluralForm}`;
|
|
7470
|
+
}
|
|
7471
|
+
|
|
7466
7472
|
// src/export/engine.ts
|
|
7467
7473
|
function addSlice(manifest, taskId, slice) {
|
|
7468
7474
|
const entry = manifest.tasks[taskId];
|
|
@@ -7502,6 +7508,11 @@ async function runExport(client, plan, opts) {
|
|
|
7502
7508
|
try {
|
|
7503
7509
|
const cached = await readBundleData(opts.root, id);
|
|
7504
7510
|
for (const s of cached.subtaskIds) enqueue(s);
|
|
7511
|
+
if (opts.downloadAttachments && cached.attachments.length > 0) {
|
|
7512
|
+
const bf = await backfillAttachments(opts.root, id, opts.download);
|
|
7513
|
+
summary.attachmentsDownloaded += bf.downloaded;
|
|
7514
|
+
summary.attachmentsFailed += bf.failed.length;
|
|
7515
|
+
}
|
|
7505
7516
|
} catch {
|
|
7506
7517
|
delete manifest.tasks[id];
|
|
7507
7518
|
toFetch.push(id);
|
|
@@ -7540,7 +7551,7 @@ async function runExport(client, plan, opts) {
|
|
|
7540
7551
|
const total = touched.size + queue.length;
|
|
7541
7552
|
const done = summary.fetched + summary.skipped + summary.failed.length;
|
|
7542
7553
|
opts.log(
|
|
7543
|
-
`[${sliceName}] ${done}/${total}
|
|
7554
|
+
`[${sliceName}] ${done}/${plural(total, "task")} (${summary.fetched} fetched, ${summary.skipped} cached, ${summary.failed.length} failed)`
|
|
7544
7555
|
);
|
|
7545
7556
|
if (sinceCheckpoint >= checkpointEvery) {
|
|
7546
7557
|
saveManifest(opts.root, manifest);
|
|
@@ -7628,7 +7639,7 @@ function renderDocReadme(doc, written, location) {
|
|
|
7628
7639
|
const lines = [
|
|
7629
7640
|
`# ${docTitle(doc)}`,
|
|
7630
7641
|
"",
|
|
7631
|
-
`Doc id: ${doc.id} \xB7 Location: ${location} \xB7 ${written.length}
|
|
7642
|
+
`Doc id: ${doc.id} \xB7 Location: ${location} \xB7 ${plural(written.length, "page")}`,
|
|
7632
7643
|
...doc.date_updated ? [`Last updated: ${new Date(Number(doc.date_updated)).toISOString()}`] : [],
|
|
7633
7644
|
"",
|
|
7634
7645
|
"## Pages",
|
|
@@ -7646,7 +7657,7 @@ function renderDocsIndex(entries) {
|
|
|
7646
7657
|
const lines = [
|
|
7647
7658
|
"# Docs",
|
|
7648
7659
|
"",
|
|
7649
|
-
|
|
7660
|
+
plural(entries.length, "doc"),
|
|
7650
7661
|
"",
|
|
7651
7662
|
"| Doc | Location | Pages |",
|
|
7652
7663
|
"| --- | --- | --- |"
|
|
@@ -7680,7 +7691,7 @@ async function exportDocs(client, teamId, opts) {
|
|
|
7680
7691
|
};
|
|
7681
7692
|
const summary = { docs: 0, pages: 0, skipped: 0, failed: [] };
|
|
7682
7693
|
const entries = [];
|
|
7683
|
-
opts.log(`Plan [docs]: ${docs.length}
|
|
7694
|
+
opts.log(`Plan [docs]: ${plural(docs.length, "doc")} in workspace "${teamName}"`);
|
|
7684
7695
|
for (const doc of docs) {
|
|
7685
7696
|
const dir = docDirName(doc);
|
|
7686
7697
|
const location = locationOf(doc);
|
|
@@ -7707,7 +7718,7 @@ async function exportDocs(client, teamId, opts) {
|
|
|
7707
7718
|
summary.pages += pageCount;
|
|
7708
7719
|
entries.push({ doc, dir, pages: pageCount, location });
|
|
7709
7720
|
opts.log(
|
|
7710
|
-
`[docs] ${summary.docs + summary.skipped + summary.failed.length}/${docs.length} ${docTitle(doc)} (${pageCount
|
|
7721
|
+
`[docs] ${summary.docs + summary.skipped + summary.failed.length}/${docs.length} ${docTitle(doc)} (${plural(pageCount, "page")})`
|
|
7711
7722
|
);
|
|
7712
7723
|
} catch (err) {
|
|
7713
7724
|
summary.failed.push({ id: doc.id, error: err.message });
|
|
@@ -7732,17 +7743,16 @@ function link(task) {
|
|
|
7732
7743
|
function assignees(task) {
|
|
7733
7744
|
return task.assignees.map((a) => a.username).join(", ");
|
|
7734
7745
|
}
|
|
7735
|
-
function typeOf(task,
|
|
7746
|
+
function typeOf(task, opts) {
|
|
7736
7747
|
const id = task.custom_item_id ?? 0;
|
|
7737
7748
|
if (id === 0) return "task";
|
|
7738
|
-
|
|
7749
|
+
if (opts.initiativeItemId !== void 0 && id === opts.initiativeItemId) return "initiative";
|
|
7750
|
+
return opts.typeNames?.[id] ?? `type ${id}`;
|
|
7739
7751
|
}
|
|
7740
|
-
function taskTable(tasks,
|
|
7752
|
+
function taskTable(tasks, opts) {
|
|
7741
7753
|
const lines = ["| Task | Status | Assignees | Type |", "| --- | --- | --- | --- |"];
|
|
7742
7754
|
for (const t of tasks) {
|
|
7743
|
-
lines.push(
|
|
7744
|
-
`| ${link(t)} | ${t.status.status} | ${assignees(t)} | ${typeOf(t, initiativeItemId)} |`
|
|
7745
|
-
);
|
|
7755
|
+
lines.push(`| ${link(t)} | ${t.status.status} | ${assignees(t)} | ${typeOf(t, opts)} |`);
|
|
7746
7756
|
}
|
|
7747
7757
|
return lines;
|
|
7748
7758
|
}
|
|
@@ -7751,22 +7761,21 @@ function renderTeamIndex(hierarchy, tasks, opts) {
|
|
|
7751
7761
|
const byList = /* @__PURE__ */ new Map();
|
|
7752
7762
|
for (const t of topLevel) byList.set(t.list.id, [...byList.get(t.list.id) ?? [], t]);
|
|
7753
7763
|
const listCount = hierarchy.lists.length + hierarchy.folders.reduce((n, f) => n + f.lists.length, 0);
|
|
7754
|
-
const folderWord = hierarchy.folders.length === 1 ? "folder" : "folders";
|
|
7755
7764
|
const lines = [
|
|
7756
7765
|
`# ${hierarchy.space.name} (space)`,
|
|
7757
7766
|
"",
|
|
7758
|
-
`Exported ${opts.exportedAt.slice(0, 10)} \xB7 ${listCount}
|
|
7767
|
+
`Exported ${opts.exportedAt.slice(0, 10)} \xB7 ${plural(listCount, "list")} \xB7 ${plural(hierarchy.folders.length, "folder")} \xB7 ${plural(tasks.length, "task")}`,
|
|
7759
7768
|
""
|
|
7760
7769
|
];
|
|
7761
7770
|
const renderList = (list, level) => {
|
|
7762
7771
|
const group = byList.get(list.id) ?? [];
|
|
7763
7772
|
const h = "#".repeat(level);
|
|
7764
|
-
lines.push(`${h} ${list.name} \u2014 ${group.length}
|
|
7773
|
+
lines.push(`${h} ${list.name} \u2014 ${plural(group.length, "task")}`, "");
|
|
7765
7774
|
const related = opts.relatedSlices?.filter((s) => s.listId === list.id) ?? [];
|
|
7766
7775
|
for (const r of related) {
|
|
7767
7776
|
lines.push(`\u2192 see also [${r.name}](../${r.name}/README.md)`, "");
|
|
7768
7777
|
}
|
|
7769
|
-
if (group.length > 0) lines.push(...taskTable(group, opts
|
|
7778
|
+
if (group.length > 0) lines.push(...taskTable(group, opts), "");
|
|
7770
7779
|
};
|
|
7771
7780
|
for (const list of hierarchy.lists) renderList(list, 2);
|
|
7772
7781
|
for (const folder of hierarchy.folders) {
|
|
@@ -7789,7 +7798,7 @@ function renderRoadmapIndex(list, tasks, opts) {
|
|
|
7789
7798
|
const lines = [
|
|
7790
7799
|
`# ${list.name}`,
|
|
7791
7800
|
"",
|
|
7792
|
-
`Exported ${opts.exportedAt.slice(0, 10)} \xB7 ${initiatives.length}
|
|
7801
|
+
`Exported ${opts.exportedAt.slice(0, 10)} \xB7 ${plural(initiatives.length, "initiative")} \xB7 ${plural(tasks.length, "task")}`,
|
|
7793
7802
|
""
|
|
7794
7803
|
];
|
|
7795
7804
|
const renderTree = (parentId, depth) => {
|
|
@@ -7844,7 +7853,7 @@ function renderUserIndex(user, tasks, opts) {
|
|
|
7844
7853
|
const lines = [
|
|
7845
7854
|
`# ${user.username} \u2014 tasks`,
|
|
7846
7855
|
"",
|
|
7847
|
-
`Exported ${opts.exportedAt.slice(0, 10)} \xB7 ${tasks.length}
|
|
7856
|
+
`Exported ${opts.exportedAt.slice(0, 10)} \xB7 ${plural(tasks.length, "task")} assigned \xB7 user id ${user.id}`,
|
|
7848
7857
|
""
|
|
7849
7858
|
];
|
|
7850
7859
|
const open = tasks.filter((t) => !isDoneStatus(t.status.status));
|
|
@@ -7910,7 +7919,7 @@ function renderRootReadme(manifest) {
|
|
|
7910
7919
|
const lines = [
|
|
7911
7920
|
`# ClickUp export${ws ? `: ${ws.name}` : ""}`,
|
|
7912
7921
|
"",
|
|
7913
|
-
`${taskCount}
|
|
7922
|
+
`${plural(taskCount, "task")} \xB7 ${plural(docCount, "doc")} \xB7 generated by \`cup export\``,
|
|
7914
7923
|
"",
|
|
7915
7924
|
"## Slices",
|
|
7916
7925
|
"",
|
|
@@ -8009,9 +8018,17 @@ function emptySummary(plan, opts) {
|
|
|
8009
8018
|
attachmentsFailed: 0
|
|
8010
8019
|
};
|
|
8011
8020
|
}
|
|
8021
|
+
async function fetchTypeNames(client, teamId) {
|
|
8022
|
+
try {
|
|
8023
|
+
const types = await client.getCustomTaskTypes(teamId);
|
|
8024
|
+
return Object.fromEntries(types.map((t) => [t.id, t.name]));
|
|
8025
|
+
} catch {
|
|
8026
|
+
return {};
|
|
8027
|
+
}
|
|
8028
|
+
}
|
|
8012
8029
|
function describePlan(plan) {
|
|
8013
8030
|
const initiatives = plan.tasks.filter((t) => t.initiative).length;
|
|
8014
|
-
return `Plan [${plan.slice.name}]: ${plan.tasks.length}
|
|
8031
|
+
return `Plan [${plan.slice.name}]: ${plural(plan.tasks.length, "task")} (${plural(initiatives, "initiative")}) in workspace "${plan.workspace.name}"`;
|
|
8015
8032
|
}
|
|
8016
8033
|
async function execute(client, teamId, plan, opts, writeIndex) {
|
|
8017
8034
|
opts.log(describePlan(plan));
|
|
@@ -8052,7 +8069,7 @@ async function exportUser(config, userRef, opts) {
|
|
|
8052
8069
|
});
|
|
8053
8070
|
}
|
|
8054
8071
|
function formatExportSummary(s) {
|
|
8055
|
-
if (s.dryRun) return `Dry run: ${s.planned}
|
|
8072
|
+
if (s.dryRun) return `Dry run: ${plural(s.planned, "task")} would be exported to ${s.out}`;
|
|
8056
8073
|
const lines = [
|
|
8057
8074
|
`Exported slice "${s.slice}" to ${s.out}`,
|
|
8058
8075
|
` tasks: ${s.fetched} fetched, ${s.skipped} already present, ${s.failed.length} failed`,
|
|
@@ -8076,13 +8093,15 @@ async function exportTeam(config, spaceRef, opts) {
|
|
|
8076
8093
|
const manifest = loadManifest(root);
|
|
8077
8094
|
const relatedSlices = Object.entries(manifest.slices).filter(([, s]) => s.kind === "roadmap" || s.kind === "initiatives").map(([name, s]) => ({ name, listId: s.scope }));
|
|
8078
8095
|
const tasks = Object.values(plan.tasksById ?? {});
|
|
8096
|
+
const typeNames = await fetchTypeNames(client, teamId);
|
|
8079
8097
|
writeSliceFiles(
|
|
8080
8098
|
root,
|
|
8081
8099
|
plan.slice.name,
|
|
8082
8100
|
renderTeamIndex(plan.hierarchy, tasks, {
|
|
8083
8101
|
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8084
8102
|
relatedSlices,
|
|
8085
|
-
initiativeItemId: opts.initiativeItemId
|
|
8103
|
+
initiativeItemId: opts.initiativeItemId,
|
|
8104
|
+
typeNames
|
|
8086
8105
|
}),
|
|
8087
8106
|
{ hierarchy: plan.hierarchy, taskIds: plan.tasks.map((t) => t.id) }
|
|
8088
8107
|
);
|
|
@@ -8122,7 +8141,7 @@ async function exportInitiatives(config, listId, opts) {
|
|
|
8122
8141
|
initiativeItemId: opts.initiativeItemId
|
|
8123
8142
|
});
|
|
8124
8143
|
return execute(client, teamId, plan, opts, async (root) => {
|
|
8125
|
-
const { readBundleData: readBundleData2 } = await import("./writer-
|
|
8144
|
+
const { readBundleData: readBundleData2 } = await import("./writer-UZHLSQMW.js");
|
|
8126
8145
|
const manifest = loadManifest(root);
|
|
8127
8146
|
const wanted = /* @__PURE__ */ new Set();
|
|
8128
8147
|
const queue = plan.tasks.map((t) => t.id);
|
|
@@ -8157,7 +8176,7 @@ async function exportDocs2(config, opts) {
|
|
|
8157
8176
|
const root = resolve(opts.out);
|
|
8158
8177
|
if (opts.dryRun) {
|
|
8159
8178
|
const docs = await client.getAllDocs(teamId);
|
|
8160
|
-
opts.log(`Plan [docs]: ${docs.length}
|
|
8179
|
+
opts.log(`Plan [docs]: ${plural(docs.length, "doc")}`);
|
|
8161
8180
|
return {
|
|
8162
8181
|
slice: "docs",
|
|
8163
8182
|
out: root,
|
|
@@ -8177,7 +8196,7 @@ async function exportDocs2(config, opts) {
|
|
|
8177
8196
|
return { slice: "docs", out: root, dryRun: false, ...summary };
|
|
8178
8197
|
}
|
|
8179
8198
|
function formatDocsSummary(s) {
|
|
8180
|
-
if (s.dryRun) return `Dry run: ${s.docs}
|
|
8199
|
+
if (s.dryRun) return `Dry run: ${plural(s.docs, "doc")} would be exported to ${s.out}`;
|
|
8181
8200
|
const lines = [
|
|
8182
8201
|
`Exported docs to ${s.out}/docs`,
|
|
8183
8202
|
` docs: ${s.docs} fetched (${s.pages} pages), ${s.skipped} already present, ${s.failed.length} failed`
|
|
@@ -8210,9 +8229,9 @@ async function exportAll(config, opts) {
|
|
|
8210
8229
|
const estMinutes = Math.ceil(estRequests / opts.rpm);
|
|
8211
8230
|
opts.log(`Workspace export plan for "${workspaceName}":`);
|
|
8212
8231
|
opts.log(
|
|
8213
|
-
` ${spaces.length}
|
|
8232
|
+
` ${plural(spaces.length, "space")}, ${plural(listCount, "list")}, ${plural(allTaskIds.size, "task")}, ${plural(docs.length, "doc")}`
|
|
8214
8233
|
);
|
|
8215
|
-
opts.log(` Already exported: ${alreadyExported}
|
|
8234
|
+
opts.log(` Already exported: ${plural(alreadyExported, "task")} (will be skipped)`);
|
|
8216
8235
|
opts.log(` Estimated requests: ~${estRequests}`);
|
|
8217
8236
|
opts.log(
|
|
8218
8237
|
` Estimated time at ${opts.rpm} req/min: ~${estMinutes < 60 ? `${estMinutes}m` : `${Math.floor(estMinutes / 60)}h ${estMinutes % 60}m`}`
|
|
@@ -8246,6 +8265,7 @@ async function exportAll(config, opts) {
|
|
|
8246
8265
|
if (!ok) throw new Error("Cancelled");
|
|
8247
8266
|
}
|
|
8248
8267
|
const spaceNames = Object.fromEntries(spaces.map((s) => [s.id, s.name]));
|
|
8268
|
+
const typeNames = await fetchTypeNames(client, teamId);
|
|
8249
8269
|
const summary = { ...empty };
|
|
8250
8270
|
for (const plan of plans) {
|
|
8251
8271
|
const run2 = await runExport(client, plan, {
|
|
@@ -8267,7 +8287,8 @@ async function exportAll(config, opts) {
|
|
|
8267
8287
|
plan.slice.name,
|
|
8268
8288
|
renderTeamIndex(plan.hierarchy, tasks, {
|
|
8269
8289
|
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8270
|
-
initiativeItemId: opts.initiativeItemId
|
|
8290
|
+
initiativeItemId: opts.initiativeItemId,
|
|
8291
|
+
typeNames
|
|
8271
8292
|
}),
|
|
8272
8293
|
{ hierarchy: plan.hierarchy, taskIds: plan.tasks.map((t) => t.id) }
|
|
8273
8294
|
);
|
|
@@ -8278,7 +8299,7 @@ async function exportAll(config, opts) {
|
|
|
8278
8299
|
}
|
|
8279
8300
|
function formatAllSummary(s) {
|
|
8280
8301
|
if (s.dryRun)
|
|
8281
|
-
return `Dry run: ${s.planned}
|
|
8302
|
+
return `Dry run: ${plural(s.planned, "task")} across ${plural(s.spaces.length, "space")} would be exported to ${s.out}`;
|
|
8282
8303
|
const lines = [
|
|
8283
8304
|
`Exported workspace to ${s.out}`,
|
|
8284
8305
|
` spaces: ${s.spaces.length} (${s.spaces.join(", ")})`,
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
backfillAttachments,
|
|
3
4
|
readBundleData,
|
|
4
5
|
renderBundleMarkdown,
|
|
5
6
|
safeAttachmentFilename,
|
|
6
7
|
taskDir,
|
|
7
8
|
writeBundleData,
|
|
8
9
|
writeTaskBundle
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-OPPKVJ6P.js";
|
|
10
11
|
export {
|
|
12
|
+
backfillAttachments,
|
|
11
13
|
readBundleData,
|
|
12
14
|
renderBundleMarkdown,
|
|
13
15
|
safeAttachmentFilename,
|
package/package.json
CHANGED
|
@@ -3,11 +3,11 @@ name: clickup
|
|
|
3
3
|
description: 'Use when managing ClickUp tasks, sprints, or comments via the `cup` CLI tool. Triggers: task queries, status updates, sprint tracking, creating subtasks, posting comments, threaded replies, standup summaries, searching tasks, checking overdue items, assigning tasks, listing spaces and lists, opening tasks in browser, checking auth or config, setting custom fields, deleting tasks, managing tags, managing checklists, editing comments, task links, time tracking, attachments, file uploads, listing members, listing fields, duplicating tasks, bulk operations, goals, key results, saved filters, favorites.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# ClickUp CLI (`cup`) - skill version 1.45.
|
|
6
|
+
# ClickUp CLI (`cup`) - skill version 1.45.2
|
|
7
7
|
|
|
8
8
|
Reference for AI agents using the `cup` CLI tool. Covers task management, sprint tracking, comments, time tracking, custom fields, goals, docs, and project workflows.
|
|
9
9
|
|
|
10
|
-
> **Version check:** Run `cup --version`. If your installed version is older than 1.45.
|
|
10
|
+
> **Version check:** Run `cup --version`. If your installed version is older than 1.45.2, update with `npm install -g @krodak/clickup-cli` and refresh this skill with `cup skill`.
|
|
11
11
|
|
|
12
12
|
## Install & Configure
|
|
13
13
|
|
|
@@ -434,7 +434,7 @@ Every task stored once (`tasks/<id>/task.json` + `task.md` + comments + attachme
|
|
|
434
434
|
|
|
435
435
|
```bash
|
|
436
436
|
cup export user me --out ./archive # my tasks, incl. closed + archived
|
|
437
|
-
cup export team "
|
|
437
|
+
cup export team "Atlas" --out ./archive # every list in a space
|
|
438
438
|
cup export roadmap <listId> --item-id 1004 --out ./archive # initiatives grouped with subtask trees
|
|
439
439
|
cup export docs --out ./archive # every doc as markdown page trees
|
|
440
440
|
cup export all --dry-run # plan: counts, estimated time
|