@andrzejchm/notion-cli 0.5.0 → 0.7.0
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.
- package/README.md +10 -0
- package/dist/cli.js +357 -61
- package/dist/cli.js.map +1 -1
- package/docs/FEATURE-PARITY.md +189 -0
- package/docs/README.agents.md +42 -0
- package/docs/skills/using-notion-cli/SKILL.md +52 -1
- package/docs/testing-setup.md +250 -0
- package/package.json +5 -3
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# Feature Parity: notion-cli vs Notion MCP
|
|
2
|
+
|
|
3
|
+
> Compared against the official Notion MCP server tools (2026-03).
|
|
4
|
+
> This document tracks gaps and serves as a prioritized roadmap for closing them.
|
|
5
|
+
|
|
6
|
+
**Legend:**
|
|
7
|
+
- **CLI** = `@andrzejchm/notion-cli` (this repo, v0.6.0)
|
|
8
|
+
- **MCP** = Official Notion MCP server
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Current Parity Summary
|
|
13
|
+
|
|
14
|
+
| Area | CLI | MCP | Parity |
|
|
15
|
+
|------|-----|-----|--------|
|
|
16
|
+
| Search | Basic keyword | Semantic + AI + connected sources + date/creator filters | Partial |
|
|
17
|
+
| Page read | Markdown via API | Markdown + discussions + transcripts | Partial |
|
|
18
|
+
| Page create | Under pages only | Under pages, databases, data sources; batch; templates; icon/cover | Partial |
|
|
19
|
+
| Page edit | Surgical replace via `--range` | Search-and-replace (multi-op), full replace, template apply, verification | Partial |
|
|
20
|
+
| Page properties | Read-only (db query) | Full read + write (update any property) | Gap |
|
|
21
|
+
| Move pages | - | Batch move to any parent | Gap |
|
|
22
|
+
| Duplicate pages | - | Duplicate with async content copy | Gap |
|
|
23
|
+
| Archive/delete | - | Trash pages | Gap |
|
|
24
|
+
| Database create | - | SQL DDL `CREATE TABLE` syntax | Gap |
|
|
25
|
+
| Database schema update | Read-only schema | `ADD/DROP/RENAME/ALTER COLUMN` via DDL | Gap |
|
|
26
|
+
| Database views | - | Create + update 10 view types with DSL | Gap |
|
|
27
|
+
| Comments | Page-level add + list | Page-level + inline (selection-anchored) + reply to thread + rich text | Partial |
|
|
28
|
+
| Users | List all | List + search + fetch by ID + fetch self | Partial |
|
|
29
|
+
| Teams | - | List teamspaces + search | Gap |
|
|
30
|
+
| Create pages in DB | - | Full property support, date/place/checkbox expanded formats | Gap |
|
|
31
|
+
| Batch operations | - | Create up to 100 pages, move up to 100 pages | Gap |
|
|
32
|
+
| Icon / Cover | - | Set emoji/image icon + cover on create and update | Gap |
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Prioritized Gaps (Roadmap)
|
|
37
|
+
|
|
38
|
+
Ordered by impact for AI-agent workflows first, then developer productivity.
|
|
39
|
+
|
|
40
|
+
### Tier 1 - High Impact (core agent workflows)
|
|
41
|
+
|
|
42
|
+
These gaps directly limit what an AI agent can accomplish through the CLI compared to using MCP directly.
|
|
43
|
+
|
|
44
|
+
#### 1. Update page properties
|
|
45
|
+
**MCP:** `update_properties` command — set title, status, dates, select, people, checkbox, numbers, etc. Supports `null` to clear.
|
|
46
|
+
**CLI:** No equivalent. Can only read properties via `db query`.
|
|
47
|
+
**Why first:** AI agents frequently need to update task status, dates, assignees. This is the single most impactful missing write operation.
|
|
48
|
+
**Suggested command:** `notion update <id> --prop "Status=Done" --prop "Priority=High"`
|
|
49
|
+
|
|
50
|
+
#### 2. Create pages in databases (with properties)
|
|
51
|
+
**MCP:** `create-pages` supports `data_source_id` parent with full property map including expanded date, place, checkbox formats.
|
|
52
|
+
**CLI:** `create-page` only supports `page_id` parent with title + markdown body. Cannot create database entries.
|
|
53
|
+
**Why second:** Agents need to create tasks, tickets, and records in databases — not just child pages.
|
|
54
|
+
**Suggested command:** `notion create-page --parent <db-id> --title "Task" --prop "Status=To Do" --prop "Due=2026-04-01"`
|
|
55
|
+
|
|
56
|
+
#### 3. Archive / trash pages
|
|
57
|
+
**MCP:** `update-page` with property changes or page operations; `update_data_source` with `in_trash: true`.
|
|
58
|
+
**CLI:** No equivalent.
|
|
59
|
+
**Why third:** Agents need to clean up after themselves — archive completed tasks, delete draft pages.
|
|
60
|
+
**Suggested command:** `notion archive <id>` / `notion delete <id>`
|
|
61
|
+
|
|
62
|
+
#### 4. Search filters (date range, creator)
|
|
63
|
+
**MCP:** `created_date_range` (start/end dates), `created_by_user_ids` filter, scoped search within page/database/teamspace.
|
|
64
|
+
**CLI:** Keyword-only search with `--type` filter.
|
|
65
|
+
**Why important:** Agents searching for "recent" items or "my tasks" need date and creator filters to get relevant results without scanning everything.
|
|
66
|
+
**Suggested flags:** `--created-after`, `--created-before`, `--created-by`
|
|
67
|
+
|
|
68
|
+
### Tier 2 - Medium Impact (power-user and advanced agent workflows)
|
|
69
|
+
|
|
70
|
+
#### 5. Inline / anchored comments
|
|
71
|
+
**MCP:** `selection_with_ellipsis` targets a specific block; `discussion_id` replies to existing threads. Rich text with mentions, dates, links.
|
|
72
|
+
**CLI:** Page-level comments only. Plain text only.
|
|
73
|
+
**Why:** Agents doing code/doc review need to comment on specific sections, not just drop a page-level note. Thread replies keep conversations organized.
|
|
74
|
+
**Suggested flags:** `notion comment <id> -m "text" --anchor "## Section...content"` / `--reply-to <discussion-id>`
|
|
75
|
+
|
|
76
|
+
#### 6. Create databases
|
|
77
|
+
**MCP:** `CREATE TABLE` DDL with full type system (select, relation, rollup, formula, unique_id, etc.).
|
|
78
|
+
**CLI:** No equivalent.
|
|
79
|
+
**Why:** Agents building project scaffolds or workflows need to create structured databases, not just pages.
|
|
80
|
+
**Suggested command:** `notion create-db --parent <id> --title "Tasks" --schema "Name TITLE, Status SELECT(Todo,Done)"`
|
|
81
|
+
|
|
82
|
+
#### 7. Move pages
|
|
83
|
+
**MCP:** Batch move up to 100 pages/databases to a new parent (page, database, data source, or workspace).
|
|
84
|
+
**CLI:** No equivalent.
|
|
85
|
+
**Why:** Reorganizing content (moving completed items to archive, restructuring projects) is a common agent task.
|
|
86
|
+
**Suggested command:** `notion move <id...> --to <parent-id>`
|
|
87
|
+
|
|
88
|
+
#### 8. Update database schema
|
|
89
|
+
**MCP:** `ADD COLUMN`, `DROP COLUMN`, `RENAME COLUMN`, `ALTER COLUMN SET` via DDL.
|
|
90
|
+
**CLI:** Read-only schema via `notion db schema`.
|
|
91
|
+
**Why:** Evolving database structure (adding a new status option, renaming a field) without leaving the terminal.
|
|
92
|
+
**Suggested command:** `notion db alter <id> --add "Priority SELECT(High,Medium,Low)" --rename "Status:Project Status"`
|
|
93
|
+
|
|
94
|
+
#### 9. Multi-operation content editing
|
|
95
|
+
**MCP:** `update_content` accepts an array of `{ old_str, new_str }` pairs (up to 100) in a single call, with `replace_all_matches` option.
|
|
96
|
+
**CLI:** `notion edit-page` supports single `--range` replacement. No batch search-and-replace.
|
|
97
|
+
**Why:** Agents making multiple edits to a page (updating several sections) currently need multiple CLI invocations.
|
|
98
|
+
**Suggested approach:** Support multiple `--replace "old...new"` flags or a JSON patch file.
|
|
99
|
+
|
|
100
|
+
#### 10. Duplicate pages
|
|
101
|
+
**MCP:** `duplicate-page` — async copy of any accessible page.
|
|
102
|
+
**CLI:** No equivalent.
|
|
103
|
+
**Why:** Templating workflows — copy a template page to start a new project/sprint.
|
|
104
|
+
**Suggested command:** `notion duplicate <id>`
|
|
105
|
+
|
|
106
|
+
### Tier 3 - Lower Impact (nice-to-have, niche workflows)
|
|
107
|
+
|
|
108
|
+
#### 11. Database views (create + update)
|
|
109
|
+
**MCP:** 10 view types (table, board, calendar, timeline, gallery, list, form, chart, map, dashboard) with DSL for filters, sorts, grouping.
|
|
110
|
+
**CLI:** No equivalent.
|
|
111
|
+
**Why:** Mostly a UI concern; agents rarely need to create views programmatically. But useful for project setup automation.
|
|
112
|
+
|
|
113
|
+
#### 12. Page icon and cover
|
|
114
|
+
**MCP:** Set emoji/custom emoji/image URL as icon; set image URL as cover on create and update.
|
|
115
|
+
**CLI:** No equivalent.
|
|
116
|
+
**Why:** Cosmetic but helps agents create polished pages. Low effort to add as flags.
|
|
117
|
+
**Suggested flags:** `--icon "🚀"` / `--cover "https://..."`
|
|
118
|
+
|
|
119
|
+
#### 13. Teams / teamspaces listing
|
|
120
|
+
**MCP:** `get-teams` with name search.
|
|
121
|
+
**CLI:** No equivalent.
|
|
122
|
+
**Why:** Rarely needed by agents. Useful for workspace discovery in large organizations.
|
|
123
|
+
**Suggested command:** `notion teams`
|
|
124
|
+
|
|
125
|
+
#### 14. Scoped search (within page / database / teamspace)
|
|
126
|
+
**MCP:** `page_url`, `data_source_url`, `teamspace_id` parameters scope search.
|
|
127
|
+
**CLI:** Global search only.
|
|
128
|
+
**Why:** Useful for agents working within a specific project area, but global search + filtering usually suffices.
|
|
129
|
+
**Suggested flags:** `--within <id>`
|
|
130
|
+
|
|
131
|
+
#### 15. Page verification
|
|
132
|
+
**MCP:** `update_verification` — mark pages as verified with optional expiry (Business/Enterprise only).
|
|
133
|
+
**CLI:** No equivalent.
|
|
134
|
+
**Why:** Enterprise-only feature, limited audience.
|
|
135
|
+
|
|
136
|
+
#### 16. Template application
|
|
137
|
+
**MCP:** Apply database templates on create and update (template content is async).
|
|
138
|
+
**CLI:** No equivalent.
|
|
139
|
+
**Why:** Useful for standardized page creation but requires database template discovery first.
|
|
140
|
+
**Suggested flags:** `--template <template-id>`
|
|
141
|
+
|
|
142
|
+
#### 17. Batch page creation
|
|
143
|
+
**MCP:** Create up to 100 pages in a single call.
|
|
144
|
+
**CLI:** One page per invocation.
|
|
145
|
+
**Why:** Performance optimization for bulk workflows. Can be scripted with shell loops for now.
|
|
146
|
+
|
|
147
|
+
#### 18. Advanced user lookup
|
|
148
|
+
**MCP:** Search users by name/email, fetch by ID, fetch authenticated user (`self`).
|
|
149
|
+
**CLI:** `notion users` lists all, no search or lookup.
|
|
150
|
+
**Suggested flags:** `notion users --search "john"` / `notion users --id <uuid>` / `notion users --me`
|
|
151
|
+
|
|
152
|
+
#### 19. Fetch page discussions inline
|
|
153
|
+
**MCP:** `include_discussions: true` on fetch shows discussion anchors in page content; `get-comments` with `include_all_blocks`, `include_resolved`.
|
|
154
|
+
**CLI:** `notion comments` shows page-level comments only, no block-level discussions, no resolved filter.
|
|
155
|
+
**Suggested flags:** `--all-blocks` / `--include-resolved`
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## What CLI Does That MCP Doesn't
|
|
160
|
+
|
|
161
|
+
The CLI isn't just chasing MCP parity — it has unique strengths:
|
|
162
|
+
|
|
163
|
+
| CLI Feature | MCP Equivalent |
|
|
164
|
+
|---|---|
|
|
165
|
+
| `notion open <id>` — open in browser | No equivalent |
|
|
166
|
+
| `notion ls` — list all accessible content | Must use search with empty query |
|
|
167
|
+
| `notion db schema` — human-readable schema | Must fetch database and parse response |
|
|
168
|
+
| `notion auth` — multi-profile management with OAuth | N/A (MCP uses connection-level auth) |
|
|
169
|
+
| `notion completion bash/zsh/fish` — shell completions | N/A |
|
|
170
|
+
| `--verbose` — debug API requests | N/A |
|
|
171
|
+
| Pipe-friendly output (stdin → page, table → stdout) | N/A (MCP is programmatic, not pipe-based) |
|
|
172
|
+
| URL → ID normalization everywhere | Both support this |
|
|
173
|
+
| `.notion.yaml` per-project config | N/A |
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Implementation Notes
|
|
178
|
+
|
|
179
|
+
- Tier 1 items (1-4) should be tackled before any Tier 2 work
|
|
180
|
+
- Items 1 and 2 can share infrastructure (property value parsing, `--prop` flag syntax)
|
|
181
|
+
- Item 3 (archive) is likely a small addition once property updates work (archive is a property)
|
|
182
|
+
- Items 6 and 8 (database create/alter) can share a schema DSL parser
|
|
183
|
+
- The CLI should NOT try to replicate MCP's SQL DDL syntax — a simpler flag-based approach fits CLI ergonomics better
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
*Last updated: 2026-03-23*
|
|
188
|
+
*CLI version compared: 0.6.0*
|
|
189
|
+
*MCP version compared: Official Notion MCP (2026-03)*
|
package/docs/README.agents.md
CHANGED
|
@@ -78,3 +78,45 @@ npm install -g @andrzejchm/notion-cli
|
|
|
78
78
|
curl -fsSL https://raw.githubusercontent.com/andrzejchm/notion-cli/main/docs/skills/using-notion-cli/SKILL.md \
|
|
79
79
|
-o ~/.config/opencode/skills/using-notion-cli/SKILL.md
|
|
80
80
|
```
|
|
81
|
+
|
|
82
|
+
## Commands Reference
|
|
83
|
+
|
|
84
|
+
### `notion update <id|url>`
|
|
85
|
+
|
|
86
|
+
Update properties on any Notion page (standalone or database entry).
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# Set a select property
|
|
90
|
+
notion update "$PAGE_ID" --prop "Status=Done"
|
|
91
|
+
|
|
92
|
+
# Set multiple properties at once
|
|
93
|
+
notion update "$PAGE_ID" --prop "Status=In Progress" --prop "Priority=High"
|
|
94
|
+
|
|
95
|
+
# Update the page title
|
|
96
|
+
notion update "$PAGE_ID" --title "New Page Title"
|
|
97
|
+
|
|
98
|
+
# Combine --title and --prop
|
|
99
|
+
notion update "$PAGE_ID" --title "Updated" --prop "Status=Done"
|
|
100
|
+
|
|
101
|
+
# Clear a property (set to empty)
|
|
102
|
+
notion update "$PAGE_ID" --prop "Status="
|
|
103
|
+
|
|
104
|
+
# Output the updated page as JSON
|
|
105
|
+
notion update "$PAGE_ID" --prop "Status=Done" --json
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Supported property types:** `title`, `rich_text`, `select`, `status`, `multi_select`, `number`, `checkbox`, `url`, `email`, `phone_number`, `date`
|
|
109
|
+
|
|
110
|
+
**`--prop` format details:**
|
|
111
|
+
|
|
112
|
+
| Type | Example |
|
|
113
|
+
|------|---------|
|
|
114
|
+
| `title` / `rich_text` | `--prop "Name=My Title"` |
|
|
115
|
+
| `select` / `status` | `--prop "Status=Done"` |
|
|
116
|
+
| `multi_select` | `--prop "Tags=design,eng,qa"` |
|
|
117
|
+
| `number` | `--prop "Count=42"` |
|
|
118
|
+
| `checkbox` | `--prop "Done=true"` or `--prop "Done=yes"` |
|
|
119
|
+
| `url` | `--prop "Link=https://example.com"` |
|
|
120
|
+
| `date` | `--prop "Due=2024-12-25"` or `--prop "Range=2024-01-01,2024-01-31"` |
|
|
121
|
+
|
|
122
|
+
Required integration capabilities: **Read content**, **Update content**
|
|
@@ -24,7 +24,8 @@ Pages must be shared with your integration: open page → `⋯` → **Add connec
|
|
|
24
24
|
|
|
25
25
|
**Integration capabilities** (set at notion.so/profile/integrations/internal → your integration → Capabilities):
|
|
26
26
|
- Read-only commands: **Read content** only
|
|
27
|
-
- `notion append`, `notion create-page`: also need **Insert content**
|
|
27
|
+
- `notion append`, `notion append --after`, `notion create-page`: also need **Insert content**
|
|
28
|
+
- `notion edit-page`: also need **Update content** + **Insert content**
|
|
28
29
|
- `notion comment`: also need **Read comments** + **Insert comments**
|
|
29
30
|
|
|
30
31
|
---
|
|
@@ -116,6 +117,35 @@ URL=$(notion create-page --parent <id|url> --title "Summary" -m "...") # captu
|
|
|
116
117
|
notion comment <id|url> -m "Reviewed and approved." # add comment to a page
|
|
117
118
|
```
|
|
118
119
|
|
|
120
|
+
#### Surgical Editing
|
|
121
|
+
|
|
122
|
+
Search-and-replace specific text, replace the full page, or insert content at a specific location.
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# Search-and-replace: find text and replace it
|
|
126
|
+
notion edit-page <id|url> --find "old text" --replace "new text"
|
|
127
|
+
|
|
128
|
+
# Multiple search-and-replace operations in one call
|
|
129
|
+
notion edit-page <id|url> --find "old1" --replace "new1" --find "old2" --replace "new2"
|
|
130
|
+
|
|
131
|
+
# Replace all occurrences (not just the first match)
|
|
132
|
+
notion edit-page <id|url> --find "TODO" --replace "DONE" --all
|
|
133
|
+
|
|
134
|
+
# Replace an entire page's content
|
|
135
|
+
notion edit-page <id|url> -m "# Replacement\nNew full-page content"
|
|
136
|
+
|
|
137
|
+
# Pipe replacement content from a file
|
|
138
|
+
cat updated-section.md | notion edit-page <id|url>
|
|
139
|
+
|
|
140
|
+
# Allow deletion of child pages/databases during replace
|
|
141
|
+
notion edit-page <id|url> -m "## Clean Slate" --allow-deleting-content
|
|
142
|
+
|
|
143
|
+
# Append after a matched section (inserts new blocks right after the match)
|
|
144
|
+
notion append <id|url> -m "New content" --after "## Status...end of status"
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
> **`--range` (deprecated):** The `--range` flag still works for backward compatibility but uses the older `replace_content_range` API. Prefer `--find`/`--replace` for targeted edits.
|
|
148
|
+
|
|
119
149
|
---
|
|
120
150
|
|
|
121
151
|
## ID Formats
|
|
@@ -155,6 +185,21 @@ echo "Created: $URL"
|
|
|
155
185
|
|
|
156
186
|
# Pipe command output into a new page
|
|
157
187
|
my-report-command | notion create-page --parent "$PAGE_ID" --title "Auto Report"
|
|
188
|
+
|
|
189
|
+
# Surgically update specific text on a page
|
|
190
|
+
# 1. Read the page to find the text you want to change
|
|
191
|
+
notion read "$PAGE_ID"
|
|
192
|
+
# 2. Use --find/--replace to swap specific text
|
|
193
|
+
notion edit-page "$PAGE_ID" --find "Status: In Progress" --replace "Status: Done"
|
|
194
|
+
|
|
195
|
+
# Multiple replacements in one call
|
|
196
|
+
notion edit-page "$PAGE_ID" \
|
|
197
|
+
--find "Status: In Progress" --replace "Status: Done" \
|
|
198
|
+
--find "Blocked: yes" --replace "Blocked: none"
|
|
199
|
+
|
|
200
|
+
# Insert a new sub-section after an existing section
|
|
201
|
+
notion append "$PAGE_ID" -m "## New Sub-section\nContent here" \
|
|
202
|
+
--after "## Existing Section...last line of section"
|
|
158
203
|
```
|
|
159
204
|
|
|
160
205
|
---
|
|
@@ -174,3 +219,9 @@ my-report-command | notion create-page --parent "$PAGE_ID" --title "Auto Report"
|
|
|
174
219
|
**`notion comment` returns "Insufficient permissions"** — Enable **Read comments** + **Insert comments** in integration capabilities: notion.so/profile/integrations/internal → your integration → Capabilities.
|
|
175
220
|
|
|
176
221
|
**`notion append` / `notion create-page` returns "Insufficient permissions"** — Enable **Insert content** in integration capabilities.
|
|
222
|
+
|
|
223
|
+
**`notion edit-page` returns "Insufficient permissions"** — Enable **Update content** in integration capabilities.
|
|
224
|
+
|
|
225
|
+
**`--find` text not found** — Run `notion read <id>` to see the exact page content. The `--find` value must match text on the page exactly.
|
|
226
|
+
|
|
227
|
+
**`--after` selector not found** — Run `notion read <id>` to see the exact page content. The selector must match real text: `"start...end"` with ~10 chars from the beginning and end of the target range.
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# Integration Testing Setup
|
|
2
|
+
|
|
3
|
+
This guide walks through setting up a Notion workspace for running integration tests against the `notion-cli` tool.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- A Notion account (free plan works)
|
|
8
|
+
- Node.js >= 22
|
|
9
|
+
- This repository cloned locally
|
|
10
|
+
|
|
11
|
+
## 1. Create a Notion Integration
|
|
12
|
+
|
|
13
|
+
1. Go to [https://www.notion.so/profile/integrations](https://www.notion.so/profile/integrations)
|
|
14
|
+
2. Click **"New integration"**
|
|
15
|
+
3. Name it `notion-cli-tests`
|
|
16
|
+
4. Select the workspace you want to use for testing
|
|
17
|
+
5. Under **Capabilities**, enable:
|
|
18
|
+
- **Read content**
|
|
19
|
+
- **Update content**
|
|
20
|
+
- **Insert content**
|
|
21
|
+
- **Read comments**
|
|
22
|
+
- **Create comments**
|
|
23
|
+
6. Click **Save**
|
|
24
|
+
7. Copy the **Internal Integration Token** (starts with `ntn_`) — you'll need it later
|
|
25
|
+
|
|
26
|
+
## 2. Create Fixture Pages and Databases
|
|
27
|
+
|
|
28
|
+
Create the following structure in your Notion workspace. All fixtures live under a single root page.
|
|
29
|
+
|
|
30
|
+
### Root Page
|
|
31
|
+
|
|
32
|
+
Create a page called **`notion-cli-test-fixtures`**. This is the parent for everything below.
|
|
33
|
+
|
|
34
|
+
### Fixture Table
|
|
35
|
+
|
|
36
|
+
| Fixture | Page/DB Name | Content |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| **Rich Content Page** | `Test: Rich Content` | See [Rich Content Page details](#rich-content-page) below |
|
|
39
|
+
| **Simple Page** | `Test: Simple Page` | Title + a single paragraph: `"This is a simple test page with a single paragraph."` |
|
|
40
|
+
| **Empty Page** | `Test: Empty Page` | Title only, no body content at all |
|
|
41
|
+
| **Task Database** | `Test: Task Database` | See [Task Database details](#task-database) below |
|
|
42
|
+
| **Empty Database** | `Test: Empty Database` | Same schema as Task Database, zero rows |
|
|
43
|
+
| **Comments Page** | `Test: Comments Page` | See [Comments Page details](#comments-page) below |
|
|
44
|
+
|
|
45
|
+
### Rich Content Page
|
|
46
|
+
|
|
47
|
+
Create a page called `Test: Rich Content` with the following blocks (in order):
|
|
48
|
+
|
|
49
|
+
1. **Heading 1**: `Main Heading`
|
|
50
|
+
2. **Paragraph**: `This is a paragraph with **bold**, *italic*, and ~~strikethrough~~ text.`
|
|
51
|
+
3. **Heading 2**: `Sub Heading`
|
|
52
|
+
4. **Bulleted list** with 3 items:
|
|
53
|
+
- `First item`
|
|
54
|
+
- `Second item`
|
|
55
|
+
- `Third item`
|
|
56
|
+
5. **Numbered list** with 3 items:
|
|
57
|
+
1. `Step one`
|
|
58
|
+
2. `Step two`
|
|
59
|
+
3. `Step three`
|
|
60
|
+
6. **Toggle block**: Summary `Click to expand`, content `Hidden content inside toggle`
|
|
61
|
+
7. **Callout block**: `This is an important callout` (use any emoji icon)
|
|
62
|
+
8. **Code block** (language: `javascript`):
|
|
63
|
+
```javascript
|
|
64
|
+
function hello() {
|
|
65
|
+
return "world";
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
9. **Table**: 3 columns × 2 data rows
|
|
69
|
+
|
|
70
|
+
| Name | Value | Notes |
|
|
71
|
+
|------|-------|-------|
|
|
72
|
+
| Alpha | 1 | First entry |
|
|
73
|
+
| Beta | 2 | Second entry |
|
|
74
|
+
|
|
75
|
+
10. **Child page**: `Nested Child Page` — with a single paragraph: `"Content inside nested page."`
|
|
76
|
+
|
|
77
|
+
### Task Database
|
|
78
|
+
|
|
79
|
+
Create an inline database called `Test: Task Database` with these properties:
|
|
80
|
+
|
|
81
|
+
| Property | Type | Configuration |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| **Title** | Title | (default title column) |
|
|
84
|
+
| **Status** | Select | Options: `Not Started`, `In Progress`, `Done` |
|
|
85
|
+
| **Priority** | Number | Number format: Number |
|
|
86
|
+
| **Due Date** | Date | — |
|
|
87
|
+
| **Tags** | Multi-select | Options: `bug`, `feature`, `docs`, `urgent` |
|
|
88
|
+
|
|
89
|
+
Add 5–10 rows with varied data. Example rows:
|
|
90
|
+
|
|
91
|
+
| Title | Status | Priority | Due Date | Tags |
|
|
92
|
+
|---|---|---|---|---|
|
|
93
|
+
| Fix login bug | In Progress | 1 | 2025-03-01 | bug, urgent |
|
|
94
|
+
| Add search feature | Not Started | 2 | 2025-04-15 | feature |
|
|
95
|
+
| Update README | Done | 3 | 2025-02-01 | docs |
|
|
96
|
+
| Refactor auth module | In Progress | 1 | 2025-03-10 | feature |
|
|
97
|
+
| Write API docs | Not Started | 2 | 2025-05-01 | docs |
|
|
98
|
+
|
|
99
|
+
### Empty Database
|
|
100
|
+
|
|
101
|
+
Create an inline database called `Test: Empty Database` with the **exact same schema** as the Task Database (Title, Status, Priority, Due Date, Tags) but **do not add any rows**.
|
|
102
|
+
|
|
103
|
+
### Comments Page
|
|
104
|
+
|
|
105
|
+
Create a page called `Test: Comments Page` with:
|
|
106
|
+
|
|
107
|
+
- **Body**: A single paragraph: `"This page is used to test comment retrieval."`
|
|
108
|
+
- **Page-level comments** (add 2–3 via the page comment area):
|
|
109
|
+
1. `"First test comment"`
|
|
110
|
+
2. `"Second test comment"`
|
|
111
|
+
3. `"Third test comment"` (optional)
|
|
112
|
+
- **Inline comment**: Select the word `"comment"` in the paragraph and add a discussion comment: `"Inline comment on selected text"`
|
|
113
|
+
|
|
114
|
+
## 3. Share the Root Page with the Integration
|
|
115
|
+
|
|
116
|
+
1. Open the `notion-cli-test-fixtures` page in Notion
|
|
117
|
+
2. Click **"Share"** (top right) or the **"···"** menu → **"Connections"**
|
|
118
|
+
3. Search for `notion-cli-tests` and add it
|
|
119
|
+
4. Confirm the integration has access — all child pages and databases inherit the connection
|
|
120
|
+
|
|
121
|
+
## 4. Extract Page and Database IDs
|
|
122
|
+
|
|
123
|
+
Each Notion page/database has a unique ID embedded in its URL.
|
|
124
|
+
|
|
125
|
+
**From a page URL:**
|
|
126
|
+
```
|
|
127
|
+
https://www.notion.so/Your-Page-Title-1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d
|
|
128
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
129
|
+
This is the page ID (32 hex chars)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Format it as a UUID: `1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d`
|
|
133
|
+
|
|
134
|
+
**From a database URL:**
|
|
135
|
+
```
|
|
136
|
+
https://www.notion.so/1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d?v=...
|
|
137
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
138
|
+
This is the database ID
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The 32-character hex string after the last `/` (and before any `?`) is the ID. You can use it with or without dashes.
|
|
142
|
+
|
|
143
|
+
Open each fixture page/database and extract its ID.
|
|
144
|
+
|
|
145
|
+
## 5. Configure `.env.test`
|
|
146
|
+
|
|
147
|
+
Copy the example file and fill in real values:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
cp .env.test.example .env.test
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Edit `.env.test` with the IDs you extracted:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
# Internal integration token (starts with ntn_)
|
|
157
|
+
NOTION_TEST_TOKEN=ntn_your_real_token
|
|
158
|
+
|
|
159
|
+
# Root page containing all fixtures
|
|
160
|
+
NOTION_TEST_ROOT_PAGE_ID=<id-of-notion-cli-test-fixtures-page>
|
|
161
|
+
|
|
162
|
+
# Rich Content Page
|
|
163
|
+
NOTION_FIXTURE_RICH_PAGE_ID=<id-of-test-rich-content-page>
|
|
164
|
+
|
|
165
|
+
# Simple Page
|
|
166
|
+
NOTION_FIXTURE_SIMPLE_PAGE_ID=<id-of-test-simple-page>
|
|
167
|
+
|
|
168
|
+
# Empty Page
|
|
169
|
+
NOTION_FIXTURE_EMPTY_PAGE_ID=<id-of-test-empty-page>
|
|
170
|
+
|
|
171
|
+
# Task Database
|
|
172
|
+
NOTION_FIXTURE_TASK_DB_ID=<id-of-test-task-database>
|
|
173
|
+
|
|
174
|
+
# Empty Database
|
|
175
|
+
NOTION_FIXTURE_EMPTY_DB_ID=<id-of-test-empty-database>
|
|
176
|
+
|
|
177
|
+
# Comments Page
|
|
178
|
+
NOTION_FIXTURE_COMMENTS_PAGE_ID=<id-of-test-comments-page>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Environment Variable Reference
|
|
182
|
+
|
|
183
|
+
| Variable | Description |
|
|
184
|
+
|---|---|
|
|
185
|
+
| `NOTION_TEST_TOKEN` | Internal integration token from step 1. The test runner passes this as `NOTION_API_TOKEN` to the CLI (the env var the CLI reads for auth — see `src/config/token.ts`). |
|
|
186
|
+
| `NOTION_TEST_ROOT_PAGE_ID` | ID of the `notion-cli-test-fixtures` root page. Used to verify integration access. |
|
|
187
|
+
| `NOTION_FIXTURE_RICH_PAGE_ID` | ID of the `Test: Rich Content` page. Tests heading, list, toggle, callout, code block, table, and nested child page rendering. |
|
|
188
|
+
| `NOTION_FIXTURE_SIMPLE_PAGE_ID` | ID of the `Test: Simple Page` page. Tests basic page read with title and a single paragraph. |
|
|
189
|
+
| `NOTION_FIXTURE_EMPTY_PAGE_ID` | ID of the `Test: Empty Page` page. Tests reading a page with no body content. |
|
|
190
|
+
| `NOTION_FIXTURE_TASK_DB_ID` | ID of the `Test: Task Database` database. Tests database queries, filtering, sorting across multiple property types. |
|
|
191
|
+
| `NOTION_FIXTURE_EMPTY_DB_ID` | ID of the `Test: Empty Database` database. Tests querying a database that returns zero results. |
|
|
192
|
+
| `NOTION_FIXTURE_COMMENTS_PAGE_ID` | ID of the `Test: Comments Page` page. Tests reading page-level and inline comments. |
|
|
193
|
+
|
|
194
|
+
> **Important:** The CLI authenticates via the `NOTION_API_TOKEN` environment variable (not `NOTION_TOKEN`). Integration tests store the token as `NOTION_TEST_TOKEN` and the test runner helper is responsible for passing it as `NOTION_API_TOKEN` when spawning CLI processes.
|
|
195
|
+
|
|
196
|
+
## 6. Install Optional Dependencies
|
|
197
|
+
|
|
198
|
+
Install `dotenv-cli` to load `.env.test` when running tests locally:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
npm install --save-dev dotenv-cli
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
This is already listed as a devDependency if you've run `npm install` after pulling this change.
|
|
205
|
+
|
|
206
|
+
## 7. Run Integration Tests
|
|
207
|
+
|
|
208
|
+
Run integration tests locally with the test environment loaded:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
npx dotenv -e .env.test -- npm run test:integration
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
This command:
|
|
215
|
+
1. Loads variables from `.env.test` into the environment
|
|
216
|
+
2. Runs `vitest run --config vitest.config.integration.ts`
|
|
217
|
+
3. Vitest picks up test files from `tests/integration/**/*.test.ts`
|
|
218
|
+
|
|
219
|
+
### Troubleshooting
|
|
220
|
+
|
|
221
|
+
| Problem | Solution |
|
|
222
|
+
|---|---|
|
|
223
|
+
| `Script "test:integration" not found` | Run `npm install` to refresh scripts |
|
|
224
|
+
| `401 Unauthorized` from Notion API | Verify `NOTION_TEST_TOKEN` is correct and the integration is still active |
|
|
225
|
+
| `404 Not Found` for a fixture | Verify the page/database ID is correct and the root page is shared with the integration |
|
|
226
|
+
| Tests time out | Default timeout is 30s per test. Check your network connection. |
|
|
227
|
+
| `NOTION_API_TOKEN` not set errors | Make sure you're using `npx dotenv -e .env.test --` before the npm command |
|
|
228
|
+
|
|
229
|
+
## CI Configuration
|
|
230
|
+
|
|
231
|
+
Integration tests run automatically on **pull requests** via the `integration` job in `.github/workflows/ci.yml`. They do not run on push events, so pushes to `main` are never blocked by integration test failures.
|
|
232
|
+
|
|
233
|
+
The job depends on the `ci` job (typecheck, lint, unit tests) passing first, then builds the CLI and runs `npm run test:integration`.
|
|
234
|
+
|
|
235
|
+
### Required GitHub Actions Secrets
|
|
236
|
+
|
|
237
|
+
Configure these secrets in your repository under **Settings → Secrets and variables → Actions**:
|
|
238
|
+
|
|
239
|
+
| Secret | Description |
|
|
240
|
+
|---|---|
|
|
241
|
+
| `NOTION_TEST_TOKEN` | Internal integration token (starts with `ntn_`) |
|
|
242
|
+
| `NOTION_TEST_ROOT_PAGE_ID` | ID of the `notion-cli-test-fixtures` root page |
|
|
243
|
+
| `NOTION_FIXTURE_RICH_PAGE_ID` | ID of the `Test: Rich Content` page |
|
|
244
|
+
| `NOTION_FIXTURE_SIMPLE_PAGE_ID` | ID of the `Test: Simple Page` page |
|
|
245
|
+
| `NOTION_FIXTURE_EMPTY_PAGE_ID` | ID of the `Test: Empty Page` page |
|
|
246
|
+
| `NOTION_FIXTURE_TASK_DB_ID` | ID of the `Test: Task Database` database |
|
|
247
|
+
| `NOTION_FIXTURE_EMPTY_DB_ID` | ID of the `Test: Empty Database` database |
|
|
248
|
+
| `NOTION_FIXTURE_COMMENTS_PAGE_ID` | ID of the `Test: Comments Page` page |
|
|
249
|
+
|
|
250
|
+
These are the same values from your `.env.test` file (see [section 5](#5-configure-envtest) above). Each secret maps to the corresponding environment variable that the test runner expects.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andrzejchm/notion-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Read Notion pages and databases from the terminal. Built for AI agents and developers.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"notion",
|
|
@@ -43,11 +43,12 @@
|
|
|
43
43
|
"format:write": "biome format --write .",
|
|
44
44
|
"check": "biome check .",
|
|
45
45
|
"check:write": "biome check --write .",
|
|
46
|
-
"ci": "npm run typecheck && npm run check"
|
|
46
|
+
"ci": "npm run typecheck && npm run check",
|
|
47
|
+
"test:integration": "vitest run --config vitest.config.integration.ts"
|
|
47
48
|
},
|
|
48
49
|
"dependencies": {
|
|
49
50
|
"@inquirer/prompts": "^8.3.0",
|
|
50
|
-
"@notionhq/client": "^5.
|
|
51
|
+
"@notionhq/client": "^5.14.0",
|
|
51
52
|
"chalk": "^5.6.0",
|
|
52
53
|
"commander": "^14.0.0",
|
|
53
54
|
"yaml": "^2.8.0"
|
|
@@ -56,6 +57,7 @@
|
|
|
56
57
|
"@biomejs/biome": "^2.4.4",
|
|
57
58
|
"@commander-js/extra-typings": "^14.0.0",
|
|
58
59
|
"@types/node": "^22",
|
|
60
|
+
"dotenv-cli": "^11.0.0",
|
|
59
61
|
"tsup": "^8.5.0",
|
|
60
62
|
"typescript": "~5.9.0",
|
|
61
63
|
"vitest": "^4.0.0"
|