feat: add clickup doc command group with Docs and Pages support#11
Conversation
Add first-class ClickUp Docs support via the v3 API: Commands: - clickup doc list -- list workspace Docs (filter by creator, parent, archived, deleted) - clickup doc view -- view a Doc's metadata - clickup doc create -- create a new Doc (with optional parent scoping and visibility) - clickup doc page list -- list pages in a Doc as a tree (--max-depth) - clickup doc page view -- view a page with optional content format (text/md|text/plain) - clickup doc page create -- create a page with content, subtitle, parent nesting - clickup doc page edit -- edit a page (replace|append|prepend content) All commands support --json, --jq, --template for machine-readable output. Raw HTTP calls via api.Client.DoRequest() following existing repo patterns. Also: - Wire doc into pkg/cmd/root/root.go - Add 'doc' category to cmd/gen-docs/main.go catMap (order 1, 'Docs') - Regenerate docs/src/content/docs/reference/* and commands.md - Add Docs section to skills/clickup-cli/SKILL.md - Add Docs bullet + table row to README.md - Add examples/clickup-docs-create.yml and examples/clickup-docs-page-edit.yml API limitation noted: delete/archive/restore Docs/Pages not available via the public ClickUp v3 API at this time. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Review: feat: add clickup doc command group with Docs and Pages support
Note (v0.21.0): Main now has auto-generated V3 API wrappers from the ClickUp OpenAPI spec.
internal/apiv3/provides typed functions for all V3 operations including docs —SearchDocsPublic,GetDocPublic,CreateDocPublic,GetDocPagesPublic,CreatePagePublic,EditPagePublic,GetPagePublic,GetDocPageListingPublic. Consider using these instead of the hand-writtenpkg/cmd/doc/api.go— it would give you typed request/response types, consistent auth + rate limiting, and zero custom HTTP code.
Nice work — the command structure, flag consistency, and documentation are solid. The v3 API surface is well covered. Requesting changes on a few correctness and consistency items before merge.
Use auto-generated V3 wrappers (new on main)
pkg/cmd/doc/api.go hand-rolls all HTTP requests with doRequest(). Main now has internal/apiv3/ with auto-generated typed wrappers from the official ClickUp V3 OpenAPI spec. Strongly recommend replacing api.go with calls to:
import "github.com/triptechtravel/clickup-cli/internal/apiv3"
// Instead of: doRequest(ctx, client, "GET", ".../docs", nil, &result)
// Use: apiv3.SearchDocsPublic(ctx, client, workspaceID)
// apiv3.GetDocPublic(ctx, client, workspaceID, docID)
// apiv3.CreateDocPublic(ctx, client, workspaceID, req)
// apiv3.GetDocPagesPublic(ctx, client, workspaceID, docID)
// apiv3.CreatePagePublic(ctx, client, workspaceID, docID, req)
// apiv3.EditPagePublic(ctx, client, workspaceID, docID, pageID, req)This eliminates the entire api.go file and gives you spec-validated request types.
Correctness
Query params built via string concatenation (list.go) — parentID and cursor are user-provided strings inserted directly into the URL without escaping. Use net/url.Values to build query strings.
Path parameters not escaped — fmt.Sprintf("%s/workspaces/%s/docs/%s/pages/%s", ...) trusts user-provided doc/page IDs. Use url.PathEscape for safety. Update: Main now uses client.URL() for all API URL construction — the V3 wrappers handle this automatically if you use them.
--parent-type without --parent-id is silently ignored in both list.go and create.go. A validation error or warning would improve UX.
Consistency with existing codebase
doc list doesn't use tableprinter — it prints each doc as a single line. The rest of the CLI uses tableprinter.New(ios) for list output, which gives aligned columns, TSV for piping, and truncation.
Raw timestamps displayed — ClickUp's v3 API returns timestamps as Unix millisecond strings. Other commands format these as human-readable dates.
API error messages dump raw JSON — Other commands parse the error body for human-readable fields.
Testability
Update: Test infrastructure has landed on main (internal/testutil/). TestFactory provides an httptest server with Factory overrides — no real auth needed. The existing flag-only tests are good but consider adding:
printPageTree— pure function, easily testable with abytes.BufferprintDocView— same patterndoRequest→ if you switch toapiv3wrappers, this becomes unnecessary. If keepingapi.go, test withhttptest.NewServer.
Minor
validParentTypesmap iteration order is non-deterministic in error messages — usesort.Strings(keys)- The GH Actions example
--content "${{ github.event.release.body }}"may contain shell-unsafe characters
Overall this is a well-structured addition. The biggest improvement would be switching to internal/apiv3/ wrappers — it would eliminate api.go entirely and give you typed, spec-validated request/response types for free.
- Add BaseURLV3() to api.Client to derive the v3 base URL from v2 - Add typed doc functions to internal/apiv3: SearchDocs, GetDoc, CreateDoc, GetDocPages, CreatePage, EditPage, GetPage — with proper request/response types and url.PathEscape for path parameters - Add HandleV3/HandleFuncV3 helpers to testutil.TestFactory for v3 path registration in tests - Add FormatUnixMillis to internal/text for human-readable timestamps - Add comprehensive tests: apiv3_test.go (12 cases), text_test FormatUnixMillis, api client BaseURLV3 test - Add oapi-codegen/runtime runtime dependency (needed by generated clickupv3 types) Co-authored-by: Ulises Jeremias <ulisescf.24@gmail.com>
- Remove doRequest/apiBase from api.go; replace with apiv3.SearchDocs, GetDoc, CreateDoc, GetDocPages, CreatePage, EditPage, GetPage - Use net/url.Values for query string building (fixes unsafe string concat) - Use url.PathEscape for all path parameters (doc ID, page ID, workspace ID) - Add --parent-type without --parent-id validation error in list and create - Switch doc list output to tableprinter with aligned columns and TSV support - Format Unix millisecond date fields using text.FormatUnixMillis - Fix validParentTypes error message using sort.Strings for deterministic order - Add integration tests (run_test.go) using testutil.TestFactory + HandleV3: list output, query params, JSON output, create body, page tree, page create/edit/view bodies and query params, parent-type validation Co-authored-by: Ulises Jeremias <ulisescf.24@gmail.com>
- Replace raw fmt.Errorf("API error (HTTP %d): %s") with api.HandleErrorResponse
which parses JSON error fields (err, message) and applies human-readable
messages for 401/403/404/429 status codes
- Add TestPrintDocView_Pure integration test for pure printDocView function
(verifies timestamps are formatted, not raw numbers)
Co-authored-by: Ulises Jeremias <ulisescf.24@gmail.com>
refactor(doc): adopt internal/apiv3 typed wrappers, fix correctness and consistency issues
@isaacrowntree Thanks for the detailed feedback! Here's a summary of the changes made to address each point: Switched to Correctness
Consistency
TestsAdded
|
There was a problem hiding this comment.
Requesting changes: use auto-gen types and wrappers from main
Main now has complete auto-generated V3 support — typed responses, typed request bodies, and typed query params. The hand-written types and wrappers in this PR should be replaced.
What to replace
Types — use api/clickupv3 auto-gen types:
| Your hand-written type | Auto-gen replacement |
|---|---|
DocCoreResult |
clickupv3.PublicDocsDocDto |
DocsSearchResult |
clickupv3.PublicDocsDocsSearchResultDto |
DocUser, DocParent |
Nested in PublicDocsDocDto |
PageDetail |
clickupv3.PublicDocsPageV3Dto |
PageRef / PagesListResult |
clickupv3.PublicDocsGetDocPagesPublic200Response |
CreateDocRequest |
clickupv3.PublicDocsCreateDocOptionsDto |
CreatePageRequest |
clickupv3.PublicDocsPublicCreatePageOptionsDto |
EditPageRequest |
clickupv3.PublicDocsPublicEditPageOptionsDto |
SearchDocsParams |
apiv3.SearchDocsPublicParams (auto-gen with typed fields) |
GetPageParams |
apiv3.GetPagePublicParams |
Wrappers — use internal/apiv3/operations.gen.go:
| Your function | Auto-gen replacement |
|---|---|
SearchDocs(ctx, client, ws, params) |
apiv3.SearchDocsPublic(ctx, client, ws, apiv3.SearchDocsPublicParams{...}) |
GetDoc(ctx, client, ws, id) |
apiv3.GetDocPublic(ctx, client, ws, id) |
CreateDoc(ctx, client, ws, req) |
apiv3.CreateDocPublic(ctx, client, ws, req) |
GetDocPages(ctx, client, ws, id, depth) |
apiv3.GetDocPagesPublic(ctx, client, ws, id) |
GetPage(ctx, client, ws, doc, page, params) |
apiv3.GetPagePublic(ctx, client, ws, doc, page, apiv3.GetPagePublicParams{...}) |
CreatePage(ctx, client, ws, doc, req) |
apiv3.CreatePagePublic(ctx, client, ws, doc, req) |
EditPage(ctx, client, ws, doc, page, req) |
apiv3.EditPagePublic(ctx, client, ws, doc, page, req) |
After this, internal/apiv3/apiv3.go should contain only the do() helper. All custom types and wrapper functions should be removed.
Query params are now variadic — callers can pass apiv3.SearchDocsPublicParams{Limit: 10, Cursor: "tok"} or omit the arg entirely.
What stays (no changes needed)
BaseURLV3()onapi.ClientHandleV3/HandleFuncV3on TestFactorytext.FormatUnixMillisapi.go(validation helpers:parseParentType,containsString,resolveWorkspaceID)- All command implementations and tests — just update type references
BaseURLV3test in spec_compliance_test.go
How to rebase
git fetch origin
git rebase origin/main
# Resolve conflicts in internal/apiv3/apiv3.go (keep only the do() helper)
# Update imports in pkg/cmd/doc/*.go from custom types to clickupv3.*
make api-gen # regenerate everything
go test ./...Superseded — requesting use of auto-gen types instead of hand-written
|
Sorry about the back and forth here. I realised the auto-gen was a bit buggy and that I had only completed the v2 properly, not the v3. |
…lisFloat - gen-api template now checks HasNullable flag — only emits 'var _ TypesPkg.Nullable[string]' when the types package actually defines Nullable (V2 has it, V3 does not) - writeFile now receives existingTypes to determine HasNullable without re-reading the types file - Add text.FormatUnixMillisFloat to format float32 Unix ms timestamps as returned by auto-generated clickupv3 types Co-authored-by: Ulises Jeremias <ulisescf.24@gmail.com>
Replace all hand-written types and wrapper functions with the auto-generated
equivalents from internal/apiv3/operations.gen.go and api/clickupv3/:
Types replaced:
- DocCoreResult → clickupv3.PublicDocsDocCoreDto (search results)
- DocsSearchResult → clickupv3.PublicDocsDocsSearchResultDto
- DocUser, DocParent → inline in PublicDocsDocDto / PublicDocsParentDto
- PageDetail → clickupv3.PublicDocsPageV3Dto
- PageRef / PagesListResult → clickupv3.PublicDocsGetDocPagesPublic200Response
- CreateDocRequest → clickupv3.PublicDocsCreateDocOptionsDto
- CreatePageRequest → clickupv3.PublicDocsPublicCreatePageOptionsDto
- EditPageRequest → clickupv3.PublicDocsPublicEditPageOptionsDto
- SearchDocsParams → apiv3.SearchDocsPublicParams (variadic, auto-gen)
- GetPageParams → apiv3.GetPagePublicParams
Wrappers replaced (internal/apiv3):
- SearchDocs → apiv3.SearchDocsPublic (variadic params)
- GetDoc → apiv3.GetDocPublic
- CreateDoc → apiv3.CreateDocPublic
- GetDocPages → apiv3.GetDocPagesPublic (variadic params with MaxPageDepth)
- GetPage → apiv3.GetPagePublic (variadic params with ContentFormat)
- CreatePage → apiv3.CreatePagePublic
- EditPage → apiv3.EditPagePublic (returns error only — no body in spec)
internal/apiv3/apiv3.go now contains only the do() helper.
Command-level changes:
- list.go: ParentType passed as string name ("SPACE") — API accepts both
string names and numeric values; no longer converting to int first
- page_list.go: maxDepth flag changed to float64 to match GetDocPagesPublicParams
- page_edit.go: --json with no response body outputs the request sent
- view.go: Public bool shown as "public"/"private" (no Visibility field in spec)
- Timestamps use text.FormatUnixMillisFloat (float32 → relative time)
Test updates:
- apiv3_test.go: rewritten to use auto-gen functions and clickupv3 types
- run_test.go: JSON fixtures updated to match clickupv3 field shapes;
type references updated from apiv3.* to clickupv3.*
Co-authored-by: Ulises Jeremias <ulisescf.24@gmail.com>
refactor(doc): adopt auto-generated clickupv3 types and apiv3 wrappers
@isaacrowntree hey! no problem at all! I just updated the implementation in the last commit. This PR is ready for you review now! 🫂 |
isaacrowntree
left a comment
There was a problem hiding this comment.
All feedback addressed. Clean adoption of auto-gen types and wrappers.
apiv3.gois now just thedo()helper — all types fromclickupv3, all wrappers fromoperations.gen.goBaseURLV3()andHandleErrorResponseintegration is correctFormatUnixMillisFloatis a good addition for the auto-gen float32 timestamps- Conditional
Nullablesentinel in gen-api template is a useful fix - 26 tests (12 unit + 14 integration) with good coverage
oapi-codegen/runtimedependency is expected for generated code
One minor note: go.mod bumps golang.org/x/text from v0.9.0 to v0.21.0 and mattn/go-colorable from v0.1.2 to v0.1.13 — these are fine, just noting they're transitive dependency updates from oapi-codegen/runtime.
Ship it.
The merged PR #11 was built against stable oapi-codegen which produces different naming conventions. Updated to match experimental output: - Field casing: .Id → .ID, .DocId → .DocID, .ParentPageId → .ParentPageID - Parent type: PublicDocsParentDto → PublicDocsCreateDocOptionsDtoParent - Visibility: union type via OneOf0 field instead of From* methods - ContentFormat/EditMode: *string instead of typed enums - Pages: []T instead of *[]T Also regenerated reference docs and cleaned stale build artifacts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
Add first-class ClickUp Docs support via the v3 API, following existing command architecture and UX conventions.
New commands
clickup doc listclickup doc view <doc-id>clickup doc createclickup doc page list <doc-id>--max-depth)clickup doc page view <doc-id> <page-id>clickup doc page create <doc-id>clickup doc page edit <doc-id> <page-id>All commands support
--json,--jq,--templateand require auth viaPersistentPreRunE: cmdutil.NeedsAuth.Implementation notes
api.Client.DoRequest()— same pattern used for unsupported go-clickup endpointshttps://api.clickup.com/api/v3/workspaces/{workspace_id}/...docmatches existing conventions (task,comment,tag, etc.)API limitation
Delete, archive, and restore operations for Docs/Pages are not available via the public ClickUp v3 API. This is documented in
SKILL.md.Files changed
pkg/cmd/doc/— 10 implementation files + 9 test filespkg/cmd/root/root.go— wireddoc.NewCmdDoc(f)cmd/gen-docs/main.go— addeddocto category map (order 1, label "Docs")docs/src/content/docs/— 9 new reference pages + regeneratedcommands.mdexamples/clickup-docs-create.yml— GitHub Actions workflow to create a Doc + first pageexamples/clickup-docs-page-edit.yml— GitHub Actions workflow to append release notes on publishskills/clickup-cli/SKILL.md— Docs section with JSON-first agent usage examplesREADME.md— Docs feature bullet + command table rowQA
go test ./...✅go vet ./...✅go build -o /dev/null ./cmd/clickup✅make docs✅ (69 reference pages generated)