clickup-cli 1.2.0__py3-none-any.whl
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.
- clickup_cli/__init__.py +3 -0
- clickup_cli/__main__.py +5 -0
- clickup_cli/cli.py +176 -0
- clickup_cli/client.py +115 -0
- clickup_cli/commands/__init__.py +71 -0
- clickup_cli/commands/comments.py +278 -0
- clickup_cli/commands/docs.py +441 -0
- clickup_cli/commands/folders.py +202 -0
- clickup_cli/commands/init.py +153 -0
- clickup_cli/commands/lists.py +258 -0
- clickup_cli/commands/spaces.py +137 -0
- clickup_cli/commands/tags.py +132 -0
- clickup_cli/commands/tasks.py +733 -0
- clickup_cli/commands/team.py +114 -0
- clickup_cli/config.py +200 -0
- clickup_cli/helpers.py +163 -0
- clickup_cli-1.2.0.dist-info/METADATA +204 -0
- clickup_cli-1.2.0.dist-info/RECORD +21 -0
- clickup_cli-1.2.0.dist-info/WHEEL +4 -0
- clickup_cli-1.2.0.dist-info/entry_points.txt +2 -0
- clickup_cli-1.2.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
"""Doc command handlers — list, get, create, pages, get-page, edit-page, create-page."""
|
|
2
|
+
|
|
3
|
+
from ..config import WORKSPACE_ID, SPACES
|
|
4
|
+
from ..helpers import read_content, error, add_id_argument
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def register_parser(subparsers, F):
|
|
8
|
+
"""Register all docs subcommands on the given subparsers object."""
|
|
9
|
+
docs_parser = subparsers.add_parser(
|
|
10
|
+
"docs",
|
|
11
|
+
formatter_class=F,
|
|
12
|
+
help="Create docs, list, read, and edit docs and pages",
|
|
13
|
+
description="""\
|
|
14
|
+
Manage ClickUp docs — create docs, list docs, inspect pages, edit and create pages.
|
|
15
|
+
|
|
16
|
+
Subcommands:
|
|
17
|
+
list — list docs in the workspace or a specific space
|
|
18
|
+
get — fetch one doc by ID
|
|
19
|
+
create — create a new doc in a space (mutating, v3)
|
|
20
|
+
pages — list pages in a doc (use this to discover page IDs)
|
|
21
|
+
get-page — fetch one page by doc ID and page ID
|
|
22
|
+
edit-page — edit an existing page (mutating)
|
|
23
|
+
create-page — create a new page in a doc (mutating)
|
|
24
|
+
|
|
25
|
+
Important: doc ID is not the same as page ID. After finding a doc,
|
|
26
|
+
use 'docs pages' to discover page IDs before using get-page or edit-page.
|
|
27
|
+
|
|
28
|
+
Does not cover: renaming docs, or deleting docs/pages
|
|
29
|
+
(these are not supported by the ClickUp API).""",
|
|
30
|
+
epilog="""\
|
|
31
|
+
typical workflow:
|
|
32
|
+
1. clickup docs list — find the doc ID
|
|
33
|
+
2. clickup docs pages <doc_id> — find the page ID
|
|
34
|
+
3. clickup docs get-page <doc_id> <page_id> — read the page
|
|
35
|
+
4. clickup docs edit-page <doc_id> <page_id> --content-file new.md
|
|
36
|
+
|
|
37
|
+
examples:
|
|
38
|
+
clickup docs list --space <name>
|
|
39
|
+
clickup docs pages doc_abc123
|
|
40
|
+
clickup --dry-run docs edit-page doc_abc page_xyz --content "Updated" """,
|
|
41
|
+
)
|
|
42
|
+
docs_sub = docs_parser.add_subparsers(dest="command", required=True)
|
|
43
|
+
|
|
44
|
+
# docs list
|
|
45
|
+
dl = docs_sub.add_parser(
|
|
46
|
+
"list",
|
|
47
|
+
formatter_class=F,
|
|
48
|
+
help="List docs in the workspace or a space",
|
|
49
|
+
description="""\
|
|
50
|
+
List docs in the workspace. Optionally filter by space.
|
|
51
|
+
Results are paginated internally and returned as a single JSON object.""",
|
|
52
|
+
epilog="""\
|
|
53
|
+
returns:
|
|
54
|
+
{"docs": [...], "count": N}
|
|
55
|
+
|
|
56
|
+
examples:
|
|
57
|
+
clickup docs list
|
|
58
|
+
clickup docs list --space <name>
|
|
59
|
+
clickup --pretty docs list""",
|
|
60
|
+
)
|
|
61
|
+
dl.add_argument(
|
|
62
|
+
"--space", type=str, help="Filter docs to a specific space"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# docs get
|
|
66
|
+
dg = docs_sub.add_parser(
|
|
67
|
+
"get",
|
|
68
|
+
formatter_class=F,
|
|
69
|
+
help="Fetch one doc by ID",
|
|
70
|
+
description="""\
|
|
71
|
+
Fetch a single doc by its ClickUp doc ID.
|
|
72
|
+
|
|
73
|
+
Use this when you need metadata about a doc. To read page content,
|
|
74
|
+
use 'docs pages' followed by 'docs get-page'.""",
|
|
75
|
+
epilog="""\
|
|
76
|
+
returns:
|
|
77
|
+
One doc JSON object.
|
|
78
|
+
|
|
79
|
+
examples:
|
|
80
|
+
clickup docs get doc_abc123
|
|
81
|
+
clickup --pretty docs get doc_abc123""",
|
|
82
|
+
)
|
|
83
|
+
add_id_argument(dg, "doc_id", "ClickUp doc ID")
|
|
84
|
+
|
|
85
|
+
# docs create
|
|
86
|
+
dc = docs_sub.add_parser(
|
|
87
|
+
"create",
|
|
88
|
+
formatter_class=F,
|
|
89
|
+
help="Create a new doc in a space",
|
|
90
|
+
description="""\
|
|
91
|
+
Create a new doc in a space. This is a mutating command (v3 API).
|
|
92
|
+
|
|
93
|
+
Optionally provide initial content via --content or --content-file.
|
|
94
|
+
If content is provided, it is written to the auto-created default page
|
|
95
|
+
automatically — no need for a separate edit-page call.
|
|
96
|
+
|
|
97
|
+
Use --dry-run to preview the request body without creating the doc.
|
|
98
|
+
Global flags may appear before or after the command group:
|
|
99
|
+
clickup --dry-run docs create --space <name> --name "My doc" """,
|
|
100
|
+
epilog="""\
|
|
101
|
+
returns:
|
|
102
|
+
The created doc object from the API. If content was written, includes
|
|
103
|
+
_initial_content_written: true and _page_id fields.
|
|
104
|
+
|
|
105
|
+
examples:
|
|
106
|
+
clickup docs create --space <name> --name "Sprint notes"
|
|
107
|
+
clickup docs create --space <name> --name "API spec" --content-file spec.md
|
|
108
|
+
clickup docs create --space <name> --name "Journal" --content "# Entry"
|
|
109
|
+
clickup --dry-run docs create --space <name> --name "Test doc"
|
|
110
|
+
|
|
111
|
+
notes:
|
|
112
|
+
--content and --content-file are mutually exclusive. Using both is an error.
|
|
113
|
+
Does not support: renaming or deleting docs (ClickUp API limitation).""",
|
|
114
|
+
)
|
|
115
|
+
dc.add_argument(
|
|
116
|
+
"--space",
|
|
117
|
+
required=True,
|
|
118
|
+
type=str,
|
|
119
|
+
help="Space to create the doc in (required)",
|
|
120
|
+
)
|
|
121
|
+
dc.add_argument("--name", required=True, help="Document name (required)")
|
|
122
|
+
dc.add_argument(
|
|
123
|
+
"--content",
|
|
124
|
+
type=str,
|
|
125
|
+
help="Inline initial content as markdown (mutually exclusive with --content-file)",
|
|
126
|
+
)
|
|
127
|
+
dc.add_argument(
|
|
128
|
+
"--content-file", type=str, help="Path to a file containing initial content"
|
|
129
|
+
)
|
|
130
|
+
dc.add_argument(
|
|
131
|
+
"--visibility", type=str, help="Doc visibility (e.g. 'PRIVATE', 'PUBLIC')"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# docs pages
|
|
135
|
+
dp = docs_sub.add_parser(
|
|
136
|
+
"pages",
|
|
137
|
+
formatter_class=F,
|
|
138
|
+
help="List pages in a doc (use this to discover page IDs)",
|
|
139
|
+
description="""\
|
|
140
|
+
List all pages in a doc. Returns page metadata including page IDs.
|
|
141
|
+
|
|
142
|
+
Use this to discover valid page IDs before calling get-page or edit-page.
|
|
143
|
+
Doc ID is not the same as page ID — this command bridges the gap.""",
|
|
144
|
+
epilog="""\
|
|
145
|
+
returns:
|
|
146
|
+
A JSON array of page objects with id, name, and content fields.
|
|
147
|
+
|
|
148
|
+
examples:
|
|
149
|
+
clickup docs pages doc_abc123
|
|
150
|
+
clickup --pretty docs pages doc_abc123
|
|
151
|
+
|
|
152
|
+
notes:
|
|
153
|
+
Always run this before get-page or edit-page if you do not already
|
|
154
|
+
have the page ID. Using the doc ID as a page ID will fail.""",
|
|
155
|
+
)
|
|
156
|
+
add_id_argument(dp, "doc_id", "ClickUp doc ID")
|
|
157
|
+
|
|
158
|
+
# docs get-page
|
|
159
|
+
dgp = docs_sub.add_parser(
|
|
160
|
+
"get-page",
|
|
161
|
+
formatter_class=F,
|
|
162
|
+
help="Fetch one page by doc ID and page ID",
|
|
163
|
+
description="""\
|
|
164
|
+
Fetch a single page from a doc by its doc ID and page ID.
|
|
165
|
+
|
|
166
|
+
Use docs pages first to discover valid page IDs. The doc ID is not
|
|
167
|
+
a valid page ID — using it as one will fail.
|
|
168
|
+
|
|
169
|
+
Returns one page object with content in the requested format.""",
|
|
170
|
+
epilog="""\
|
|
171
|
+
returns:
|
|
172
|
+
One page JSON object with id, name, and content.
|
|
173
|
+
|
|
174
|
+
examples:
|
|
175
|
+
clickup docs get-page doc_abc page_xyz
|
|
176
|
+
clickup docs get-page doc_abc page_xyz --format plain
|
|
177
|
+
clickup --pretty docs get-page doc_abc page_xyz
|
|
178
|
+
|
|
179
|
+
notes:
|
|
180
|
+
Default format is markdown (md). Use --format plain for plain text.
|
|
181
|
+
Page ID must come from 'docs pages', not from the doc ID itself.""",
|
|
182
|
+
)
|
|
183
|
+
add_id_argument(dgp, "doc_id", "ClickUp doc ID")
|
|
184
|
+
add_id_argument(dgp, "page_id", "Page ID (from 'docs pages')")
|
|
185
|
+
dgp.add_argument(
|
|
186
|
+
"--format",
|
|
187
|
+
choices=["md", "plain"],
|
|
188
|
+
default="md",
|
|
189
|
+
help="Content format: md (default) or plain",
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# docs edit-page
|
|
193
|
+
dep = docs_sub.add_parser(
|
|
194
|
+
"edit-page",
|
|
195
|
+
formatter_class=F,
|
|
196
|
+
help="Edit an existing page in a doc",
|
|
197
|
+
description="""\
|
|
198
|
+
Edit an existing page in a doc. This is a mutating command.
|
|
199
|
+
|
|
200
|
+
At least one editable field is required: --content, --content-file, or --name.
|
|
201
|
+
If none are provided, the command exits with an error.
|
|
202
|
+
|
|
203
|
+
Use --content for inline text or --content-file for file-based content.
|
|
204
|
+
Do not use both at the same time. Content is sent as markdown.
|
|
205
|
+
|
|
206
|
+
Use --append to keep the existing page content and add the new content at
|
|
207
|
+
the end. In append mode, the CLI reads the current page first, combines
|
|
208
|
+
the content locally, then sends one update request.
|
|
209
|
+
|
|
210
|
+
Use --dry-run to preview the request body without applying changes.
|
|
211
|
+
Global flags may appear before or after the command group:
|
|
212
|
+
clickup --dry-run docs edit-page doc_abc page_xyz --content "New text"
|
|
213
|
+
clickup docs edit-page doc_abc page_xyz --content "New text" --dry-run""",
|
|
214
|
+
epilog="""\
|
|
215
|
+
returns:
|
|
216
|
+
The updated page object from the API.
|
|
217
|
+
|
|
218
|
+
examples:
|
|
219
|
+
clickup docs edit-page doc_abc page_xyz --content "# Updated heading"
|
|
220
|
+
clickup docs edit-page doc_abc page_xyz --content-file revised.md
|
|
221
|
+
clickup docs edit-page doc_abc page_xyz --content-file session.md --append
|
|
222
|
+
clickup docs edit-page doc_abc page_xyz --name "Renamed page"
|
|
223
|
+
clickup --dry-run docs edit-page doc_abc page_xyz --content "Test"
|
|
224
|
+
|
|
225
|
+
notes:
|
|
226
|
+
--content and --content-file are mutually exclusive. Using both is an error.
|
|
227
|
+
--append requires --content or --content-file.
|
|
228
|
+
Does not support: deleting pages (not available in the ClickUp API).
|
|
229
|
+
Use 'docs pages' to find the correct page ID before editing.""",
|
|
230
|
+
)
|
|
231
|
+
add_id_argument(dep, "doc_id", "ClickUp doc ID")
|
|
232
|
+
add_id_argument(dep, "page_id", "Page ID (from 'docs pages')")
|
|
233
|
+
dep.add_argument(
|
|
234
|
+
"--content",
|
|
235
|
+
type=str,
|
|
236
|
+
help="Inline page content as markdown (mutually exclusive with --content-file)",
|
|
237
|
+
)
|
|
238
|
+
dep.add_argument(
|
|
239
|
+
"--content-file", type=str, help="Path to a file containing page content"
|
|
240
|
+
)
|
|
241
|
+
dep.add_argument("--name", type=str, help="New page name (rename)")
|
|
242
|
+
dep.add_argument(
|
|
243
|
+
"--append",
|
|
244
|
+
action="store_true",
|
|
245
|
+
help="Append new content to the existing page instead of replacing it",
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
# docs create-page
|
|
249
|
+
dcp = docs_sub.add_parser(
|
|
250
|
+
"create-page",
|
|
251
|
+
formatter_class=F,
|
|
252
|
+
help="Create a new page in a doc",
|
|
253
|
+
description="""\
|
|
254
|
+
Create a new page inside an existing doc. This is a mutating command.
|
|
255
|
+
|
|
256
|
+
Use --content for inline text or --content-file for file-based content.
|
|
257
|
+
Do not use both at the same time.
|
|
258
|
+
|
|
259
|
+
Note: when a doc is first created, it already has a blank default page.
|
|
260
|
+
If you want to write to that existing page, use 'docs edit-page' instead
|
|
261
|
+
of creating an additional page.
|
|
262
|
+
|
|
263
|
+
Use --dry-run to preview the request body without creating the page.
|
|
264
|
+
Global flags may appear before or after the command group:
|
|
265
|
+
clickup --dry-run docs create-page doc_abc --name "New page" """,
|
|
266
|
+
epilog="""\
|
|
267
|
+
returns:
|
|
268
|
+
The created page object from the API.
|
|
269
|
+
|
|
270
|
+
examples:
|
|
271
|
+
clickup docs create-page doc_abc --name "Meeting notes"
|
|
272
|
+
clickup docs create-page doc_abc --name "Spec" --content-file spec.md
|
|
273
|
+
clickup --dry-run docs create-page doc_abc --name "Draft"
|
|
274
|
+
|
|
275
|
+
notes:
|
|
276
|
+
--content and --content-file are mutually exclusive. Using both is an error.
|
|
277
|
+
A newly created doc already has a default blank page. Use edit-page to
|
|
278
|
+
write to that page instead of creating a duplicate.
|
|
279
|
+
Does not support: deleting pages (not available in the ClickUp API).""",
|
|
280
|
+
)
|
|
281
|
+
add_id_argument(dcp, "doc_id", "ClickUp doc ID")
|
|
282
|
+
dcp.add_argument("--name", required=True, help="Page name (required)")
|
|
283
|
+
dcp.add_argument(
|
|
284
|
+
"--content",
|
|
285
|
+
type=str,
|
|
286
|
+
help="Inline page content as markdown (mutually exclusive with --content-file)",
|
|
287
|
+
)
|
|
288
|
+
dcp.add_argument(
|
|
289
|
+
"--content-file", type=str, help="Path to a file containing page content"
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _append_markdown(existing, new_content):
|
|
294
|
+
"""Append markdown content with a readable separator when both sides exist."""
|
|
295
|
+
existing = existing or ""
|
|
296
|
+
new_content = new_content or ""
|
|
297
|
+
|
|
298
|
+
if not existing:
|
|
299
|
+
return new_content
|
|
300
|
+
if not new_content:
|
|
301
|
+
return existing
|
|
302
|
+
|
|
303
|
+
return f"{existing.rstrip()}\n\n{new_content.lstrip()}"
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def cmd_docs_list(client, args):
|
|
307
|
+
if client.dry_run:
|
|
308
|
+
return {"dry_run": True, "action": "list_docs", "space": getattr(args, "space", None)}
|
|
309
|
+
|
|
310
|
+
params = {"limit": "100"}
|
|
311
|
+
if args.space:
|
|
312
|
+
space = SPACES.get(args.space)
|
|
313
|
+
if space:
|
|
314
|
+
params["parent_id"] = space["space_id"]
|
|
315
|
+
params["parent_type"] = "SPACE"
|
|
316
|
+
|
|
317
|
+
all_docs = []
|
|
318
|
+
cursor = None
|
|
319
|
+
while True:
|
|
320
|
+
if cursor:
|
|
321
|
+
params["cursor"] = cursor
|
|
322
|
+
resp = client.get_v3(f"/workspaces/{WORKSPACE_ID}/docs", params=params)
|
|
323
|
+
docs = resp.get("docs", [])
|
|
324
|
+
all_docs.extend(docs)
|
|
325
|
+
cursor = resp.get("next_cursor")
|
|
326
|
+
if not cursor:
|
|
327
|
+
break
|
|
328
|
+
|
|
329
|
+
return {"docs": all_docs, "count": len(all_docs)}
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def cmd_docs_get(client, args):
|
|
333
|
+
return client.get_v3(f"/workspaces/{WORKSPACE_ID}/docs/{args.doc_id}")
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def cmd_docs_create(client, args):
|
|
337
|
+
"""Create a new doc in a space."""
|
|
338
|
+
content = read_content(args.content, args.content_file, "--content")
|
|
339
|
+
space = SPACES.get(args.space)
|
|
340
|
+
if not space:
|
|
341
|
+
error(f"Unknown space: {args.space}. Check your config file.")
|
|
342
|
+
|
|
343
|
+
body = {"name": args.name}
|
|
344
|
+
body["parent"] = {"id": space["space_id"], "type": 4}
|
|
345
|
+
if args.visibility:
|
|
346
|
+
body["visibility"] = args.visibility
|
|
347
|
+
|
|
348
|
+
if client.dry_run:
|
|
349
|
+
return {"dry_run": True, "action": "create_doc", "body": body}
|
|
350
|
+
|
|
351
|
+
doc = client.post_v3(f"/workspaces/{WORKSPACE_ID}/docs", data=body)
|
|
352
|
+
|
|
353
|
+
# If content provided, write it to the auto-created default page
|
|
354
|
+
if content and not client.dry_run:
|
|
355
|
+
doc_id = doc.get("id")
|
|
356
|
+
if doc_id:
|
|
357
|
+
pages = client.get_v3(
|
|
358
|
+
f"/workspaces/{WORKSPACE_ID}/docs/{doc_id}/pages",
|
|
359
|
+
params={"content_format": "text/md"},
|
|
360
|
+
)
|
|
361
|
+
page_list = pages if isinstance(pages, list) else pages.get("pages", [])
|
|
362
|
+
if page_list:
|
|
363
|
+
page_id = page_list[0].get("id")
|
|
364
|
+
if page_id:
|
|
365
|
+
client.put_v3(
|
|
366
|
+
f"/workspaces/{WORKSPACE_ID}/docs/{doc_id}/pages/{page_id}",
|
|
367
|
+
data={"content": content, "content_format": "text/md"},
|
|
368
|
+
)
|
|
369
|
+
doc["_initial_content_written"] = True
|
|
370
|
+
doc["_page_id"] = page_id
|
|
371
|
+
|
|
372
|
+
return doc
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def cmd_docs_pages(client, args):
|
|
376
|
+
params = {"content_format": "text/md"}
|
|
377
|
+
return client.get_v3(
|
|
378
|
+
f"/workspaces/{WORKSPACE_ID}/docs/{args.doc_id}/pages", params=params
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def cmd_docs_get_page(client, args):
|
|
383
|
+
fmt = "text/md" if args.format == "md" else "text/plain"
|
|
384
|
+
params = {"content_format": fmt}
|
|
385
|
+
return client.get_v3(
|
|
386
|
+
f"/workspaces/{WORKSPACE_ID}/docs/{args.doc_id}/pages/{args.page_id}",
|
|
387
|
+
params=params,
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def cmd_docs_edit_page(client, args):
|
|
392
|
+
content = read_content(args.content, args.content_file, "--content")
|
|
393
|
+
body = {}
|
|
394
|
+
|
|
395
|
+
if getattr(args, "append", False):
|
|
396
|
+
if not content:
|
|
397
|
+
error("--append requires --content or --content-file")
|
|
398
|
+
page = client.get_v3(
|
|
399
|
+
f"/workspaces/{WORKSPACE_ID}/docs/{args.doc_id}/pages/{args.page_id}",
|
|
400
|
+
params={"content_format": "text/md"},
|
|
401
|
+
allow_dry_run=True,
|
|
402
|
+
)
|
|
403
|
+
content = _append_markdown(page.get("content", ""), content)
|
|
404
|
+
|
|
405
|
+
if content:
|
|
406
|
+
body["content"] = content
|
|
407
|
+
body["content_format"] = "text/md"
|
|
408
|
+
if args.name:
|
|
409
|
+
body["name"] = args.name
|
|
410
|
+
|
|
411
|
+
if not body:
|
|
412
|
+
error(
|
|
413
|
+
"Nothing to update — provide at least one of: --content, --content-file, --name"
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
if client.dry_run:
|
|
417
|
+
return {
|
|
418
|
+
"dry_run": True,
|
|
419
|
+
"action": "edit_page",
|
|
420
|
+
"doc_id": args.doc_id,
|
|
421
|
+
"page_id": args.page_id,
|
|
422
|
+
"body": body,
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return client.put_v3(
|
|
426
|
+
f"/workspaces/{WORKSPACE_ID}/docs/{args.doc_id}/pages/{args.page_id}",
|
|
427
|
+
data=body,
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def cmd_docs_create_page(client, args):
|
|
432
|
+
content = read_content(args.content, args.content_file, "--content")
|
|
433
|
+
body = {"name": args.name}
|
|
434
|
+
if content:
|
|
435
|
+
body["content"] = content
|
|
436
|
+
body["content_format"] = "text/md"
|
|
437
|
+
|
|
438
|
+
return client.post_v3(
|
|
439
|
+
f"/workspaces/{WORKSPACE_ID}/docs/{args.doc_id}/pages",
|
|
440
|
+
data=body,
|
|
441
|
+
)
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""Folder command handlers — list, get, create, update, delete."""
|
|
2
|
+
|
|
3
|
+
from ..helpers import error, resolve_space_id, add_id_argument
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def register_parser(subparsers, F):
|
|
7
|
+
"""Register all folders subcommands on the given subparsers object."""
|
|
8
|
+
folders_parser = subparsers.add_parser(
|
|
9
|
+
"folders",
|
|
10
|
+
formatter_class=F,
|
|
11
|
+
help="Full folder CRUD: list, get, create, update, delete",
|
|
12
|
+
description="""\
|
|
13
|
+
Manage ClickUp folders — organize lists within spaces.
|
|
14
|
+
|
|
15
|
+
Folders are containers that sit between spaces and lists. Use them to
|
|
16
|
+
group related lists together (e.g. a "Sprint 1" folder under a space).
|
|
17
|
+
|
|
18
|
+
Subcommands:
|
|
19
|
+
list — list all folders in a space
|
|
20
|
+
get — fetch full details of a folder by ID
|
|
21
|
+
create — create a new folder in a space (mutating)
|
|
22
|
+
update — update a folder's name (mutating)
|
|
23
|
+
delete — delete a folder (destructive)
|
|
24
|
+
|
|
25
|
+
Does not cover: reordering folders or setting folder-level statuses
|
|
26
|
+
(use the ClickUp UI for these).""",
|
|
27
|
+
epilog="""\
|
|
28
|
+
examples:
|
|
29
|
+
clickup folders list --space <name>
|
|
30
|
+
clickup folders get 12345
|
|
31
|
+
clickup --dry-run folders create --space <name> --name "My folder"
|
|
32
|
+
clickup folders update 12345 --name "Renamed folder"
|
|
33
|
+
clickup --dry-run folders delete 12345""",
|
|
34
|
+
)
|
|
35
|
+
folders_sub = folders_parser.add_subparsers(dest="command", required=True)
|
|
36
|
+
|
|
37
|
+
# folders list
|
|
38
|
+
fl = folders_sub.add_parser(
|
|
39
|
+
"list",
|
|
40
|
+
formatter_class=F,
|
|
41
|
+
help="List all folders in a space",
|
|
42
|
+
description="""\
|
|
43
|
+
List all folders in a space. Returns folder names, IDs, and metadata.
|
|
44
|
+
|
|
45
|
+
Use this to discover folder IDs before creating lists inside them
|
|
46
|
+
or to see the organizational structure of a space.""",
|
|
47
|
+
epilog="""\
|
|
48
|
+
returns:
|
|
49
|
+
{"folders": [...], "count": N}
|
|
50
|
+
|
|
51
|
+
examples:
|
|
52
|
+
clickup folders list --space <name>
|
|
53
|
+
clickup folders list --space 901810200000
|
|
54
|
+
clickup --pretty folders list --space <name>""",
|
|
55
|
+
)
|
|
56
|
+
fl.add_argument(
|
|
57
|
+
"--space",
|
|
58
|
+
required=True,
|
|
59
|
+
type=str,
|
|
60
|
+
help="Space name (from config) or raw space ID",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# folders get
|
|
64
|
+
fg = folders_sub.add_parser(
|
|
65
|
+
"get",
|
|
66
|
+
formatter_class=F,
|
|
67
|
+
help="Fetch full details of a folder by ID",
|
|
68
|
+
description="""\
|
|
69
|
+
Fetch full details of a folder including its lists, statuses, and metadata.
|
|
70
|
+
|
|
71
|
+
Use this when you need to inspect a specific folder or discover the
|
|
72
|
+
lists inside it.""",
|
|
73
|
+
epilog="""\
|
|
74
|
+
returns:
|
|
75
|
+
One folder JSON object with all fields (id, name, lists, statuses, etc.)
|
|
76
|
+
|
|
77
|
+
examples:
|
|
78
|
+
clickup folders get 12345
|
|
79
|
+
clickup --pretty folders get 12345""",
|
|
80
|
+
)
|
|
81
|
+
add_id_argument(fg, "folder_id", "ClickUp folder ID")
|
|
82
|
+
|
|
83
|
+
# folders create
|
|
84
|
+
fc = folders_sub.add_parser(
|
|
85
|
+
"create",
|
|
86
|
+
formatter_class=F,
|
|
87
|
+
help="Create a new folder in a space",
|
|
88
|
+
description="""\
|
|
89
|
+
Create a new folder in a space. This is a mutating command.
|
|
90
|
+
|
|
91
|
+
Use --dry-run to preview the request body without creating the folder.
|
|
92
|
+
Global flags may appear before or after the command group:
|
|
93
|
+
clickup --dry-run folders create --space <name> --name "My folder" """,
|
|
94
|
+
epilog="""\
|
|
95
|
+
returns:
|
|
96
|
+
The created folder object from the API.
|
|
97
|
+
|
|
98
|
+
examples:
|
|
99
|
+
clickup folders create --space <name> --name "My folder"
|
|
100
|
+
clickup --dry-run folders create --space <name> --name "Test folder" """,
|
|
101
|
+
)
|
|
102
|
+
fc.add_argument(
|
|
103
|
+
"--space",
|
|
104
|
+
required=True,
|
|
105
|
+
type=str,
|
|
106
|
+
help="Space name (from config) or raw space ID",
|
|
107
|
+
)
|
|
108
|
+
fc.add_argument("--name", required=True, help="Folder name (required)")
|
|
109
|
+
|
|
110
|
+
# folders update
|
|
111
|
+
fu = folders_sub.add_parser(
|
|
112
|
+
"update",
|
|
113
|
+
formatter_class=F,
|
|
114
|
+
help="Update a folder (name)",
|
|
115
|
+
description="""\
|
|
116
|
+
Update a folder's name. This is a mutating command.
|
|
117
|
+
|
|
118
|
+
Use --dry-run to preview without applying changes.
|
|
119
|
+
Global flags may appear before or after the command group:
|
|
120
|
+
clickup --dry-run folders update 12345 --name "New name" """,
|
|
121
|
+
epilog="""\
|
|
122
|
+
returns:
|
|
123
|
+
The updated folder object from the API.
|
|
124
|
+
|
|
125
|
+
examples:
|
|
126
|
+
clickup folders update 12345 --name "Renamed folder"
|
|
127
|
+
clickup --dry-run folders update 12345 --name "Test rename" """,
|
|
128
|
+
)
|
|
129
|
+
add_id_argument(fu, "folder_id", "ClickUp folder ID to update")
|
|
130
|
+
fu.add_argument("--name", type=str, help="New folder name")
|
|
131
|
+
|
|
132
|
+
# folders delete
|
|
133
|
+
fd = folders_sub.add_parser(
|
|
134
|
+
"delete",
|
|
135
|
+
formatter_class=F,
|
|
136
|
+
help="Delete a folder (destructive)",
|
|
137
|
+
description="""\
|
|
138
|
+
Delete a folder permanently. This is a destructive, irreversible command.
|
|
139
|
+
|
|
140
|
+
Deleting a folder also deletes all lists and tasks inside it.
|
|
141
|
+
Use with extreme caution.
|
|
142
|
+
|
|
143
|
+
Use --dry-run to preview the operation without deleting anything.
|
|
144
|
+
Global flags may appear before or after the command group:
|
|
145
|
+
clickup --dry-run folders delete 12345""",
|
|
146
|
+
epilog="""\
|
|
147
|
+
returns:
|
|
148
|
+
{"status": "ok", "action": "deleted", "folder_id": "..."}
|
|
149
|
+
|
|
150
|
+
examples:
|
|
151
|
+
clickup --dry-run folders delete 12345
|
|
152
|
+
clickup folders delete 12345""",
|
|
153
|
+
)
|
|
154
|
+
add_id_argument(fd, "folder_id", "ClickUp folder ID to delete")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def cmd_folders_list(client, args):
|
|
158
|
+
"""List all folders in a space."""
|
|
159
|
+
space_id = resolve_space_id(args.space)
|
|
160
|
+
resp = client.get_v2(f"/space/{space_id}/folder")
|
|
161
|
+
folders = resp.get("folders", [])
|
|
162
|
+
return {"folders": folders, "count": len(folders)}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def cmd_folders_get(client, args):
|
|
166
|
+
"""Get full details of a folder by ID."""
|
|
167
|
+
return client.get_v2(f"/folder/{args.folder_id}")
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def cmd_folders_create(client, args):
|
|
171
|
+
"""Create a folder in a space."""
|
|
172
|
+
space_id = resolve_space_id(args.space)
|
|
173
|
+
body = {"name": args.name}
|
|
174
|
+
|
|
175
|
+
if client.dry_run:
|
|
176
|
+
return {"dry_run": True, "action": "create_folder", "space_id": space_id, "body": body}
|
|
177
|
+
|
|
178
|
+
return client.post_v2(f"/space/{space_id}/folder", data=body)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def cmd_folders_update(client, args):
|
|
182
|
+
"""Update a folder (name)."""
|
|
183
|
+
body = {}
|
|
184
|
+
if args.name:
|
|
185
|
+
body["name"] = args.name
|
|
186
|
+
|
|
187
|
+
if not body:
|
|
188
|
+
error("Nothing to update — provide at least --name")
|
|
189
|
+
|
|
190
|
+
if client.dry_run:
|
|
191
|
+
return {"dry_run": True, "action": "update_folder", "folder_id": args.folder_id, "body": body}
|
|
192
|
+
|
|
193
|
+
return client.put_v2(f"/folder/{args.folder_id}", data=body)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def cmd_folders_delete(client, args):
|
|
197
|
+
"""Delete a folder by ID."""
|
|
198
|
+
if client.dry_run:
|
|
199
|
+
return {"dry_run": True, "action": "delete_folder", "folder_id": args.folder_id}
|
|
200
|
+
|
|
201
|
+
client.delete_v2(f"/folder/{args.folder_id}")
|
|
202
|
+
return {"status": "ok", "action": "deleted", "folder_id": args.folder_id}
|