Skip to content

feat: add clickup doc command group with Docs and Pages support#11

Merged
isaacrowntree merged 12 commits intotriptechtravel:mainfrom
ulises-jeremias:feature/clickup-docs-support
Apr 6, 2026
Merged

feat: add clickup doc command group with Docs and Pages support#11
isaacrowntree merged 12 commits intotriptechtravel:mainfrom
ulises-jeremias:feature/clickup-docs-support

Conversation

@ulises-jeremias
Copy link
Copy Markdown
Contributor

Summary

Add first-class ClickUp Docs support via the v3 API, following existing command architecture and UX conventions.

New commands

Command Description
clickup doc list List workspace Docs (filter by creator, parent, archived, deleted; paginate with cursor)
clickup doc view <doc-id> View Doc metadata
clickup doc create Create a Doc (scoped to space/folder/list, configurable visibility)
clickup doc page list <doc-id> List pages as a tree (--max-depth)
clickup doc page view <doc-id> <page-id> View a page (`--content-format text/md
clickup doc page create <doc-id> Create a page (nested pages, subtitle, content)
clickup doc page edit <doc-id> <page-id> Edit a page (`replace

All commands support --json, --jq, --template and require auth via PersistentPreRunE: cmdutil.NeedsAuth.

Implementation notes

  • Raw HTTP calls via api.Client.DoRequest() — same pattern used for unsupported go-clickup endpoints
  • API base: https://api.clickup.com/api/v3/workspaces/{workspace_id}/...
  • Singular command name doc matches 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 files
  • pkg/cmd/root/root.go — wired doc.NewCmdDoc(f)
  • cmd/gen-docs/main.go — added doc to category map (order 1, label "Docs")
  • docs/src/content/docs/ — 9 new reference pages + regenerated commands.md
  • examples/clickup-docs-create.yml — GitHub Actions workflow to create a Doc + first page
  • examples/clickup-docs-page-edit.yml — GitHub Actions workflow to append release notes on publish
  • skills/clickup-cli/SKILL.md — Docs section with JSON-first agent usage examples
  • README.md — Docs feature bullet + command table row

QA

  • go test ./...
  • go vet ./...
  • go build -o /dev/null ./cmd/clickup
  • make docs ✅ (69 reference pages generated)

ulises-jeremias and others added 3 commits March 17, 2026 13:38
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>
Copy link
Copy Markdown
Contributor

@isaacrowntree isaacrowntree left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-written pkg/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 escapedfmt.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 a bytes.Buffer
  • printDocView — same pattern
  • doRequest → if you switch to apiv3 wrappers, this becomes unnecessary. If keeping api.go, test with httptest.NewServer.

Minor

  • validParentTypes map iteration order is non-deterministic in error messages — use sort.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.

ulises-jeremias and others added 5 commits April 4, 2026 01:29
- 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
@ulises-jeremias
Copy link
Copy Markdown
Contributor Author

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-written pkg/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 escapedfmt.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 a `bytes.Buffer`

* **`printDocView`** — same pattern

* **`doRequest`** → if you switch to `apiv3` wrappers, this becomes unnecessary. If keeping `api.go`, test with `httptest.NewServer`.

Minor

* `validParentTypes` map iteration order is non-deterministic in error messages — use `sort.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.

@isaacrowntree Thanks for the detailed feedback! Here's a summary of the changes made to address each point:

Switched to internal/apiv3 typed wrappers
Added SearchDocs, GetDoc, CreateDoc, GetDocPages, CreatePage, EditPage, and GetPage directly to internal/apiv3.go (same pattern as internal/apiv2).
All commands now route through these wrappers — api.go is stripped down to validation helpers only, no hand-rolled HTTP remains.
Also added BaseURLV3() to api.Client so the v3 base URL is derived correctly for both production and test servers.

Correctness

  • Query params now use net/url.Values + q.Encode() throughout
  • All path segments (workspace ID, doc ID, page ID) go through url.PathEscape
  • --parent-type without --parent-id now returns a clear validation error in both list and create
  • API errors now go through api.HandleErrorResponse, which parses err/message fields and returns human-readable messages for 401/403/404/429

Consistency

  • doc list uses tableprinter.New(ios) with aligned columns and TSV support
  • All date_created / date_updated fields now pass through text.FormatUnixMillis (added to internal/text) for human-readable relative times
  • validParentTypes error message uses sort.Strings for deterministic output

Tests

Added HandleV3 / HandleFuncV3 helpers to testutil.TestFactory, then covered everything with:

  • 12 unit tests in internal/apiv3/apiv3_test.go (all operations, query params, path escaping, error handling)
  • 13 integration tests in pkg/cmd/doc/run_test.go using TestFactory, including pure-function tests for printPageTree and printDocView

isaacrowntree
isaacrowntree previously approved these changes Apr 5, 2026
isaacrowntree

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@isaacrowntree isaacrowntree left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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() on api.Client
  • HandleV3/HandleFuncV3 on TestFactory
  • text.FormatUnixMillis
  • api.go (validation helpers: parseParentType, containsString, resolveWorkspaceID)
  • All command implementations and tests — just update type references
  • BaseURLV3 test 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 ./...

@isaacrowntree isaacrowntree dismissed their stale review April 5, 2026 13:29

Superseded — requesting use of auto-gen types instead of hand-written

@isaacrowntree
Copy link
Copy Markdown
Contributor

isaacrowntree commented Apr 5, 2026

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.

ulises-jeremias and others added 4 commits April 5, 2026 20:29
…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
@ulises-jeremias
Copy link
Copy Markdown
Contributor Author

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.

@isaacrowntree hey! no problem at all! I just updated the implementation in the last commit. This PR is ready for you review now! 🫂

Copy link
Copy Markdown
Contributor

@isaacrowntree isaacrowntree left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All feedback addressed. Clean adoption of auto-gen types and wrappers.

  • apiv3.go is now just the do() helper — all types from clickupv3, all wrappers from operations.gen.go
  • BaseURLV3() and HandleErrorResponse integration is correct
  • FormatUnixMillisFloat is a good addition for the auto-gen float32 timestamps
  • Conditional Nullable sentinel in gen-api template is a useful fix
  • 26 tests (12 unit + 14 integration) with good coverage
  • oapi-codegen/runtime dependency 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.

@isaacrowntree isaacrowntree merged commit 263829c into triptechtravel:main Apr 6, 2026
isaacrowntree added a commit that referenced this pull request Apr 6, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants