@discountry/clickup-cli
Advanced tools
+1
-1
| { | ||
| "name": "@discountry/clickup-cli", | ||
| "version": "1.0.1", | ||
| "version": "1.0.2", | ||
| "description": "A globally installable ClickUp CLI for tasks, comments, and docs.", | ||
@@ -5,0 +5,0 @@ "type": "module", |
+14
-5
@@ -14,3 +14,3 @@ # @discountry/clickup-cli | ||
| 安装 Codex skill: | ||
| 从当前仓库安装 Codex skill: | ||
@@ -75,11 +75,20 @@ ```bash | ||
| clickup docs "API" | ||
| clickup docs "Sprint1" --parent-type FOLDER --limit 100 | ||
| clickup doc abc123 | ||
| clickup create-doc "Project Notes" --content "# Notes" | ||
| clickup page abc123 page456 | ||
| clickup create-page abc123 "New Section" --content "Hello" | ||
| clickup edit-page abc123 page456 --name "Renamed" --content "# Updated" | ||
| clickup page abc123 page456 --content-format text/plain | ||
| clickup create-page abc123 "New Section" --content "Hello" --sub-title "Summary" | ||
| clickup edit-page abc123 page456 --name "Renamed" --sub-title "Updated" --content "# Updated" --content-edit-mode append | ||
| ``` | ||
| 需要原始 API 输出时加 `--json`。 | ||
| 需要结构化命令输出时加 `--json`。 | ||
| 文档和页面命令: | ||
| - `docs` 支持 `--id`、`--creator`、`--deleted`、`--archived`、`--parent-id`、`--parent-type`、`--limit`。 | ||
| - `doc` 支持 `--max-page-depth`。 | ||
| - `page` 支持 `--content-format text/md|text/plain`。 | ||
| - `create-page` 支持 `--sub-title`、`--parent-page-id`、`--content-format`。 | ||
| - `edit-page` 支持 `--name`、`--sub-title`、`--content`、`--content-edit-mode`、`--content-format`。 | ||
| ## 开发 | ||
@@ -86,0 +95,0 @@ |
+14
-5
@@ -14,3 +14,3 @@ # @discountry/clickup-cli | ||
| Install the Codex skill: | ||
| Install the Codex skill from this repo: | ||
@@ -75,11 +75,20 @@ ```bash | ||
| clickup docs "API" | ||
| clickup docs "Sprint1" --parent-type FOLDER --limit 100 | ||
| clickup doc abc123 | ||
| clickup create-doc "Project Notes" --content "# Notes" | ||
| clickup page abc123 page456 | ||
| clickup create-page abc123 "New Section" --content "Hello" | ||
| clickup edit-page abc123 page456 --name "Renamed" --content "# Updated" | ||
| clickup page abc123 page456 --content-format text/plain | ||
| clickup create-page abc123 "New Section" --content "Hello" --sub-title "Summary" | ||
| clickup edit-page abc123 page456 --name "Renamed" --sub-title "Updated" --content "# Updated" --content-edit-mode append | ||
| ``` | ||
| Add `--json` for raw API output. | ||
| Add `--json` for structured command output. | ||
| Docs and pages: | ||
| - `docs` supports `--id`, `--creator`, `--deleted`, `--archived`, `--parent-id`, `--parent-type`, `--limit`. | ||
| - `doc` supports `--max-page-depth`. | ||
| - `page` supports `--content-format text/md|text/plain`. | ||
| - `create-page` supports `--sub-title`, `--parent-page-id`, `--content-format`. | ||
| - `edit-page` supports `--name`, `--sub-title`, `--content`, `--content-edit-mode`, `--content-format`. | ||
| ## Development | ||
@@ -86,0 +95,0 @@ |
+13
-1
@@ -11,2 +11,14 @@ export const OPTION_DEFINITIONS = [ | ||
| { key: 'name', names: ['--name', '-n'], type: 'string', description: 'New page name for `edit-page`' }, | ||
| { key: 'subTitle', names: ['--sub-title'], type: 'string', description: 'Page subtitle for `create-page` or `edit-page`' }, | ||
| { key: 'parentPageId', names: ['--parent-page-id'], type: 'string', description: 'Parent page id for `create-page`' }, | ||
| { key: 'contentFormat', names: ['--content-format'], type: 'string', description: 'Page content format: `text/md` or `text/plain`' }, | ||
| { key: 'contentEditMode', names: ['--content-edit-mode'], type: 'string', description: 'Page content update mode: `replace`, `append`, `prepend`' }, | ||
| { key: 'id', names: ['--id'], type: 'string', description: 'Doc id filter for `docs`' }, | ||
| { key: 'creator', names: ['--creator'], type: 'string', description: 'Creator id filter for `docs`' }, | ||
| { key: 'deleted', names: ['--deleted'], type: 'boolean', description: 'Include deleted docs in `docs`' }, | ||
| { key: 'archived', names: ['--archived'], type: 'boolean', description: 'Include archived docs in `docs`' }, | ||
| { key: 'parentId', names: ['--parent-id'], type: 'string', description: 'Parent doc id filter for `docs`' }, | ||
| { key: 'parentType', names: ['--parent-type'], type: 'string', description: 'Parent doc type filter for `docs`' }, | ||
| { key: 'limit', names: ['--limit'], type: 'string', description: 'Page size for paginated docs queries' }, | ||
| { key: 'maxPageDepth', names: ['--max-page-depth'], type: 'string', description: 'Maximum page depth for `doc` page listings' }, | ||
| ]; | ||
@@ -40,3 +52,3 @@ | ||
| { name: 'create-page', usage: 'create-page <doc_id> "title"', description: 'Create a page in a doc', section: 'Document Commands' }, | ||
| { name: 'edit-page', usage: 'edit-page <doc_id> <page_id>', description: 'Edit a page with --content and/or --name', section: 'Document Commands' }, | ||
| { name: 'edit-page', usage: 'edit-page <doc_id> <page_id>', description: 'Edit a page with --name, --sub-title, and/or --content', section: 'Document Commands' }, | ||
| ]; | ||
@@ -43,0 +55,0 @@ |
+28
-11
@@ -82,7 +82,16 @@ import { CliUsageError } from '../errors.mjs'; | ||
| async docs({ args }) { | ||
| async docs({ args, options }) { | ||
| ensureArgRange(args, { max: 1 }, 'docs ["query"]'); | ||
| const [query] = args; | ||
| const app = getApp(); | ||
| const docs = await app.docService.searchDocs(query ? { query } : {}); | ||
| const docs = await app.docService.searchDocs({ | ||
| ...(query ? { query } : {}), | ||
| ...(options.id !== null ? { id: options.id } : {}), | ||
| ...(options.creator !== null ? { creator: options.creator } : {}), | ||
| ...(options.deleted ? { deleted: true } : {}), | ||
| ...(options.archived ? { archived: true } : {}), | ||
| ...(options.parentId !== null ? { parentId: options.parentId } : {}), | ||
| ...(options.parentType !== null ? { parentType: options.parentType } : {}), | ||
| ...(options.limit !== null ? { limit: options.limit } : {}), | ||
| }); | ||
| const text = | ||
@@ -330,3 +339,3 @@ docs.length === 0 | ||
| async doc({ args }) { | ||
| async doc({ args, options }) { | ||
| ensureArgRange(args, { min: 1, max: 1 }, 'doc <doc_id>'); | ||
@@ -338,3 +347,5 @@ const docId = parseDocId(args[0]); | ||
| app.docService.getDoc(docId), | ||
| app.docService.getDocPageListing(docId), | ||
| app.docService.getDocPageListing(docId, { | ||
| ...(options.maxPageDepth !== null ? { maxPageDepth: options.maxPageDepth } : {}), | ||
| }), | ||
| ]); | ||
@@ -347,3 +358,3 @@ return formatResult( | ||
| async page({ args }) { | ||
| async page({ args, options }) { | ||
| ensureArgRange(args, { min: 2, max: 2 }, 'page <doc_id> <page_id>'); | ||
@@ -355,3 +366,3 @@ const docId = parseDocId(args[0]); | ||
| const app = getApp(); | ||
| const page = await app.docService.getPage(docId, pageId); | ||
| const page = await app.docService.getPage(docId, pageId, options.contentFormat ?? 'text/md'); | ||
| return formatResult(page, formatPage(page)); | ||
@@ -369,2 +380,5 @@ }, | ||
| content: options.content ?? undefined, | ||
| contentFormat: options.contentFormat ?? undefined, | ||
| parentPageId: options.parentPageId ?? undefined, | ||
| subTitle: options.subTitle ?? undefined, | ||
| }); | ||
@@ -380,6 +394,6 @@ return formatResult(page, `Page created: ${page.name}\nID: ${page.id}`); | ||
| requireArg(pageId, 'Page ID required.', 'edit-page <doc_id> <page_id>'); | ||
| if (!options.content && !options.name) { | ||
| if (options.content === null && options.name === null && options.subTitle === null) { | ||
| throw new CliUsageError( | ||
| 'At least --content or --name is required.', | ||
| 'edit-page <doc_id> <page_id> [--content "content"] [--name "name"]' | ||
| 'At least --content, --name, or --sub-title is required.', | ||
| 'edit-page <doc_id> <page_id> [--content "content"] [--name "name"] [--sub-title "subtitle"]' | ||
| ); | ||
@@ -390,4 +404,7 @@ } | ||
| const page = await app.docService.editPage(docId, pageId, { | ||
| ...(options.content ? { content: options.content } : {}), | ||
| ...(options.name ? { name: options.name } : {}), | ||
| ...(options.content !== null ? { content: options.content } : {}), | ||
| ...(options.name !== null ? { name: options.name } : {}), | ||
| ...(options.subTitle !== null ? { subTitle: options.subTitle } : {}), | ||
| ...(options.contentEditMode !== null ? { contentEditMode: options.contentEditMode } : {}), | ||
| ...(options.contentFormat !== null ? { contentFormat: options.contentFormat } : {}), | ||
| }); | ||
@@ -394,0 +411,0 @@ |
@@ -22,2 +22,14 @@ import { CliUsageError } from '../errors.mjs'; | ||
| name: null, | ||
| subTitle: null, | ||
| parentPageId: null, | ||
| contentFormat: null, | ||
| contentEditMode: null, | ||
| id: null, | ||
| creator: null, | ||
| deleted: false, | ||
| archived: false, | ||
| parentId: null, | ||
| parentType: null, | ||
| limit: null, | ||
| maxPageDepth: null, | ||
| }; | ||
@@ -24,0 +36,0 @@ } |
+2
-1
@@ -30,2 +30,3 @@ import { createApplication } from '../app.mjs'; | ||
| fetchImpl = globalThis.fetch, | ||
| createApplicationImpl = createApplication, | ||
| stdout = process.stdout, | ||
@@ -52,3 +53,3 @@ stderr = process.stderr, | ||
| if (!application) { | ||
| application = createApplication({ env, fetchImpl, now }); | ||
| application = createApplicationImpl({ env, fetchImpl, now }); | ||
| } | ||
@@ -55,0 +56,0 @@ return application; |
| export function createDocService({ client, userService }) { | ||
| function buildQueryString(paramsBuilder) { | ||
| const params = new URLSearchParams(); | ||
| paramsBuilder(params); | ||
| const query = params.toString(); | ||
| return query ? `?${query}` : ''; | ||
| } | ||
| async function listDocsPage(workspaceId, options = {}) { | ||
| const query = buildQueryString((params) => { | ||
| params.set('limit', String(options.limit ?? 100)); | ||
| if (options.id) { | ||
| params.set('id', options.id); | ||
| } | ||
| if (options.creator) { | ||
| params.set('creator', String(options.creator)); | ||
| } | ||
| if (options.deleted) { | ||
| params.set('deleted', 'true'); | ||
| } | ||
| if (options.archived) { | ||
| params.set('archived', 'true'); | ||
| } | ||
| if (options.parentId) { | ||
| params.set('parent_id', options.parentId); | ||
| } | ||
| if (options.parentType) { | ||
| params.set('parent_type', String(options.parentType)); | ||
| } | ||
| if (options.cursor) { | ||
| // ClickUp documents the `cursor` parameter, but the docs endpoint currently | ||
| // paginates only when the deprecated `next_cursor` parameter is provided. | ||
| params.set('next_cursor', options.cursor); | ||
| } | ||
| }); | ||
| return client.requestV3(`/workspaces/${workspaceId}/docs${query}`); | ||
| } | ||
| async function searchDocs(options = {}) { | ||
| const workspaceId = await userService.getWorkspaceId(); | ||
| const params = new URLSearchParams(); | ||
| if (options.query) { | ||
| params.set('query', options.query); | ||
| const docs = []; | ||
| const seenCursors = new Set(); | ||
| let cursor = null; | ||
| while (true) { | ||
| const response = await listDocsPage(workspaceId, { ...options, cursor }); | ||
| docs.push(...(response.docs ?? [])); | ||
| cursor = response.next_cursor ?? null; | ||
| if (!cursor || seenCursors.has(cursor)) { | ||
| break; | ||
| } | ||
| seenCursors.add(cursor); | ||
| } | ||
| const query = params.toString(); | ||
| const response = await client.requestV3( | ||
| `/workspaces/${workspaceId}/docs${query ? `?${query}` : ''}` | ||
| ); | ||
| return response.docs ?? []; | ||
| if (!options.query) { | ||
| return docs; | ||
| } | ||
| const normalizedQuery = options.query.trim().toLowerCase(); | ||
| if (!normalizedQuery) { | ||
| return docs; | ||
| } | ||
| return docs.filter((doc) => doc.name?.toLowerCase().includes(normalizedQuery)); | ||
| } | ||
@@ -20,7 +77,17 @@ | ||
| async function getDocPageListing(docId) { | ||
| async function getDocPageListing(docId, options = {}) { | ||
| const workspaceId = await userService.getWorkspaceId(); | ||
| const query = buildQueryString((params) => { | ||
| if (options.maxPageDepth !== undefined) { | ||
| params.set('max_page_depth', String(options.maxPageDepth)); | ||
| } | ||
| }); | ||
| const response = await client.requestV3( | ||
| `/workspaces/${workspaceId}/docs/${docId}/pageListing` | ||
| `/workspaces/${workspaceId}/docs/${docId}/page_listing${query}` | ||
| ); | ||
| if (Array.isArray(response)) { | ||
| return response; | ||
| } | ||
| return response.pages ?? []; | ||
@@ -31,9 +98,9 @@ } | ||
| const workspaceId = await userService.getWorkspaceId(); | ||
| const query = buildQueryString((params) => { | ||
| if (contentFormat) { | ||
| params.set('content_format', contentFormat); | ||
| } | ||
| }); | ||
| const page = await client.requestV3( | ||
| `/workspaces/${workspaceId}/docs/${docId}/pages/${pageId}`, | ||
| { | ||
| headers: { | ||
| Accept: contentFormat, | ||
| }, | ||
| } | ||
| `/workspaces/${workspaceId}/docs/${docId}/pages/${pageId}${query}` | ||
| ); | ||
@@ -84,5 +151,6 @@ | ||
| name, | ||
| ...(options.content ? { content: options.content } : {}), | ||
| ...(options.content !== undefined ? { content: options.content } : {}), | ||
| ...(options.contentFormat ? { content_format: options.contentFormat } : {}), | ||
| ...(options.parentPageId ? { parent_page_id: options.parentPageId } : {}), | ||
| ...(options.subTitle ? { sub_title: options.subTitle } : {}), | ||
| ...(options.subTitle !== undefined ? { sub_title: options.subTitle } : {}), | ||
| }, | ||
@@ -104,2 +172,8 @@ }); | ||
| } | ||
| if (updates.contentEditMode !== undefined) { | ||
| body.content_edit_mode = updates.contentEditMode; | ||
| } | ||
| if (updates.contentFormat !== undefined) { | ||
| body.content_format = updates.contentFormat; | ||
| } | ||
@@ -106,0 +180,0 @@ return client.requestV3(`/workspaces/${workspaceId}/docs/${docId}/pages/${pageId}`, { |
@@ -68,2 +68,7 @@ function isBareIdentifier(value) { | ||
| const shareMatch = input.match(/\/d\/h\/([a-zA-Z0-9_-]+)(?:\/|$)/); | ||
| if (shareMatch) { | ||
| return shareMatch[1]; | ||
| } | ||
| return input; | ||
@@ -86,3 +91,13 @@ } | ||
| const docCenterMatch = input.match(/\/dc\/[a-zA-Z0-9_-]+\/([a-zA-Z0-9_-]+)(?:[/?#]|$)/); | ||
| if (docCenterMatch) { | ||
| return docCenterMatch[1]; | ||
| } | ||
| const shareMatch = input.match(/\/d\/h\/[a-zA-Z0-9_-]+\/[^/]+\/([a-zA-Z0-9_-]+)(?:[/?#]|$)/); | ||
| if (shareMatch) { | ||
| return shareMatch[1]; | ||
| } | ||
| return input; | ||
| } |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
73668
10.09%1866
6.57%108
9.09%