@yeongjaeyou/claude-code-config 0.4.0 → 0.5.1
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/.claude/commands/ask-codex.md +131 -345
- package/.claude/commands/ask-deepwiki.md +15 -15
- package/.claude/commands/ask-gemini.md +134 -352
- package/.claude/commands/code-review.md +41 -40
- package/.claude/commands/commit-and-push.md +35 -36
- package/.claude/commands/council.md +318 -0
- package/.claude/commands/edit-notebook.md +34 -33
- package/.claude/commands/gh/create-issue-label.md +19 -17
- package/.claude/commands/gh/decompose-issue.md +66 -65
- package/.claude/commands/gh/init-project.md +46 -52
- package/.claude/commands/gh/post-merge.md +74 -79
- package/.claude/commands/gh/resolve-issue.md +38 -46
- package/.claude/commands/plan.md +15 -14
- package/.claude/commands/tm/convert-prd.md +53 -53
- package/.claude/commands/tm/post-merge.md +92 -112
- package/.claude/commands/tm/resolve-issue.md +148 -154
- package/.claude/commands/tm/review-prd-with-codex.md +272 -279
- package/.claude/commands/tm/sync-to-github.md +189 -212
- package/.claude/guidelines/cv-guidelines.md +30 -0
- package/.claude/guidelines/id-reference.md +34 -0
- package/.claude/guidelines/work-guidelines.md +17 -0
- package/.claude/skills/notion-md-uploader/SKILL.md +252 -0
- package/.claude/skills/notion-md-uploader/references/notion_block_types.md +323 -0
- package/.claude/skills/notion-md-uploader/references/setup_guide.md +156 -0
- package/.claude/skills/notion-md-uploader/scripts/__pycache__/markdown_parser.cpython-311.pyc +0 -0
- package/.claude/skills/notion-md-uploader/scripts/__pycache__/notion_client.cpython-311.pyc +0 -0
- package/.claude/skills/notion-md-uploader/scripts/__pycache__/notion_converter.cpython-311.pyc +0 -0
- package/.claude/skills/notion-md-uploader/scripts/markdown_parser.py +607 -0
- package/.claude/skills/notion-md-uploader/scripts/notion_client.py +337 -0
- package/.claude/skills/notion-md-uploader/scripts/notion_converter.py +477 -0
- package/.claude/skills/notion-md-uploader/scripts/upload_md.py +298 -0
- package/.claude/skills/skill-creator/LICENSE.txt +202 -0
- package/.claude/skills/skill-creator/SKILL.md +209 -0
- package/.claude/skills/skill-creator/scripts/init_skill.py +303 -0
- package/.claude/skills/skill-creator/scripts/package_skill.py +110 -0
- package/.claude/skills/skill-creator/scripts/quick_validate.py +65 -0
- package/README.md +159 -129
- package/package.json +1 -1
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: notion-md-uploader
|
|
3
|
+
description: Upload Markdown files to Notion pages with full formatting support including headings, lists, code blocks, images, tables, callouts, and todos. This skill should be used when users want to publish documentation, reports, or notes from Markdown files to their Notion workspace with preserved formatting and automatic image uploads.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Notion Markdown Uploader
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
To upload Markdown files to Notion with full formatting preservation, use this skill. It converts Markdown syntax to Notion blocks and handles local image uploads automatically via the Notion File Upload API.
|
|
11
|
+
|
|
12
|
+
## Prerequisites
|
|
13
|
+
|
|
14
|
+
Before using this skill, ensure the following setup is complete:
|
|
15
|
+
|
|
16
|
+
### 1. Notion Integration Setup
|
|
17
|
+
|
|
18
|
+
1. Visit https://www.notion.so/my-integrations
|
|
19
|
+
2. Click "+ New integration"
|
|
20
|
+
3. Configure the integration:
|
|
21
|
+
- Name: "MD Uploader" (or preferred name)
|
|
22
|
+
- Associated workspace: Select target workspace
|
|
23
|
+
- Capabilities: Enable Read, Update, and Insert content
|
|
24
|
+
4. Click "Submit" and copy the API key
|
|
25
|
+
|
|
26
|
+
### 2. Environment Configuration
|
|
27
|
+
|
|
28
|
+
Set the API key in the environment:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# In .env file
|
|
32
|
+
NOTION_API_KEY=ntn_xxxxx
|
|
33
|
+
|
|
34
|
+
# Or export directly
|
|
35
|
+
export NOTION_API_KEY=ntn_xxxxx
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 3. Page Access
|
|
39
|
+
|
|
40
|
+
Share the target parent page with the integration:
|
|
41
|
+
|
|
42
|
+
1. Open the Notion page where documents will be uploaded
|
|
43
|
+
2. Click "..." menu (top right)
|
|
44
|
+
3. Select "Add connections"
|
|
45
|
+
4. Find and select the integration
|
|
46
|
+
|
|
47
|
+
## Quick Start
|
|
48
|
+
|
|
49
|
+
### Basic Upload
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
uv run python .claude/skills/notion-md-uploader/scripts/upload_md.py \
|
|
53
|
+
docs/report.md \
|
|
54
|
+
--parent-page-id "abc123def456"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### With Custom Title
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
uv run python .claude/skills/notion-md-uploader/scripts/upload_md.py \
|
|
61
|
+
README.md \
|
|
62
|
+
--parent-page-id "abc123def456" \
|
|
63
|
+
--title "Project Documentation"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Dry Run (Preview)
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
uv run python .claude/skills/notion-md-uploader/scripts/upload_md.py \
|
|
70
|
+
docs/analysis.md \
|
|
71
|
+
--parent-page-id "abc123def456" \
|
|
72
|
+
--dry-run
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Supported Markdown Elements
|
|
76
|
+
|
|
77
|
+
| Element | Markdown Syntax | Notion Block |
|
|
78
|
+
|---------|----------------|--------------|
|
|
79
|
+
| Heading 1 | `# Title` | heading_1 |
|
|
80
|
+
| Heading 2 | `## Subtitle` | heading_2 |
|
|
81
|
+
| Heading 3 | `### Section` | heading_3 |
|
|
82
|
+
| Paragraph | Plain text | paragraph |
|
|
83
|
+
| Bold | `**text**` | bold annotation |
|
|
84
|
+
| Italic | `*text*` | italic annotation |
|
|
85
|
+
| Strikethrough | `~~text~~` | strikethrough annotation |
|
|
86
|
+
| Inline Code | `` `code` `` | code annotation |
|
|
87
|
+
| Link | `[text](url)` | link |
|
|
88
|
+
| Bulleted List | `- item` | bulleted_list_item |
|
|
89
|
+
| Numbered List | `1. item` | numbered_list_item |
|
|
90
|
+
| Code Block | ` ```lang ``` ` | code (with language) |
|
|
91
|
+
| Quote | `> text` | quote |
|
|
92
|
+
| Divider | `---` | divider |
|
|
93
|
+
| Image | `` | image (with upload) |
|
|
94
|
+
| Table | `\| A \| B \|` | table + table_row |
|
|
95
|
+
| Todo | `- [ ] task` | to_do |
|
|
96
|
+
| Callout | `> [!NOTE]` | callout |
|
|
97
|
+
|
|
98
|
+
## Image Handling
|
|
99
|
+
|
|
100
|
+
### External URLs
|
|
101
|
+
|
|
102
|
+
Images with HTTP/HTTPS URLs are embedded directly:
|
|
103
|
+
|
|
104
|
+
```markdown
|
|
105
|
+

|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Local Images
|
|
109
|
+
|
|
110
|
+
Local images are automatically uploaded to Notion:
|
|
111
|
+
|
|
112
|
+
```markdown
|
|
113
|
+

|
|
114
|
+

|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The uploader:
|
|
118
|
+
1. Detects local image paths
|
|
119
|
+
2. Creates a Notion File Upload object
|
|
120
|
+
3. Uploads the image binary
|
|
121
|
+
4. Attaches the uploaded file to an image block
|
|
122
|
+
|
|
123
|
+
Supported formats: `.png`, `.jpg`, `.jpeg`, `.gif`, `.svg`, `.webp`
|
|
124
|
+
|
|
125
|
+
## Finding Parent Page ID
|
|
126
|
+
|
|
127
|
+
### Option 1: From URL
|
|
128
|
+
|
|
129
|
+
Copy the page URL and extract the ID:
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
https://www.notion.so/workspace/Page-Title-abc123def456ghi789
|
|
133
|
+
^^^^^^^^^^^^^^^^^^^^^^^^
|
|
134
|
+
This is the page ID (32 chars)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Option 2: Use Search API
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
# The script accepts full URLs
|
|
141
|
+
uv run python .claude/skills/notion-md-uploader/scripts/upload_md.py \
|
|
142
|
+
report.md \
|
|
143
|
+
--parent-page-id "https://www.notion.so/workspace/Reports-abc123"
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Workflow Decision Tree
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
User wants to upload Markdown to Notion
|
|
150
|
+
|
|
|
151
|
+
+-- Is NOTION_API_KEY set?
|
|
152
|
+
| +-- No: Set up integration (see Prerequisites)
|
|
153
|
+
| +-- Yes: Continue
|
|
154
|
+
|
|
|
155
|
+
+-- Is parent page shared with integration?
|
|
156
|
+
| +-- No: Add connection to page
|
|
157
|
+
| +-- Yes: Continue
|
|
158
|
+
|
|
|
159
|
+
+-- Run upload script
|
|
160
|
+
+-- Has local images?
|
|
161
|
+
| +-- Yes: Images uploaded automatically
|
|
162
|
+
| +-- No: Continue
|
|
163
|
+
|
|
|
164
|
+
+-- Success: Page created with URL
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Scripts
|
|
168
|
+
|
|
169
|
+
### upload_md.py
|
|
170
|
+
|
|
171
|
+
Main upload script. Run with `--help` for full options:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
uv run python .claude/skills/notion-md-uploader/scripts/upload_md.py --help
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Arguments:
|
|
178
|
+
- `md_file`: Path to Markdown file (required)
|
|
179
|
+
- `--parent-page-id`, `-p`: Notion parent page ID or URL (required)
|
|
180
|
+
- `--title`, `-t`: Custom page title (optional)
|
|
181
|
+
- `--dry-run`: Preview without uploading (optional)
|
|
182
|
+
|
|
183
|
+
### notion_client.py
|
|
184
|
+
|
|
185
|
+
Low-level Notion API client. Can be imported for custom workflows:
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
from scripts.notion_client import NotionClient
|
|
189
|
+
|
|
190
|
+
client = NotionClient()
|
|
191
|
+
page = client.create_page(
|
|
192
|
+
parent_page_id="abc123",
|
|
193
|
+
title="My Page",
|
|
194
|
+
children=[...]
|
|
195
|
+
)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### markdown_parser.py
|
|
199
|
+
|
|
200
|
+
Markdown parser for converting to AST. Useful for custom processing:
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
from scripts.markdown_parser import MarkdownParser
|
|
204
|
+
|
|
205
|
+
parser = MarkdownParser()
|
|
206
|
+
blocks = parser.parse(markdown_text)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### notion_converter.py
|
|
210
|
+
|
|
211
|
+
Converts parsed Markdown blocks to Notion API format:
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
from scripts.notion_converter import NotionBlockConverter
|
|
215
|
+
|
|
216
|
+
converter = NotionBlockConverter(image_uploader=upload_func)
|
|
217
|
+
notion_blocks = converter.convert_blocks(blocks)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Limitations
|
|
221
|
+
|
|
222
|
+
1. **Block Limit**: Notion API allows max 100 blocks per request. The script handles this by chunking.
|
|
223
|
+
2. **File Size**: Images must be under 20MB (5MB for free workspaces)
|
|
224
|
+
3. **Nested Lists**: Currently flattened (nested items become top-level)
|
|
225
|
+
4. **Complex Tables**: Cell formatting may be simplified
|
|
226
|
+
5. **Attachments**: Only images are auto-uploaded; other files need manual handling
|
|
227
|
+
|
|
228
|
+
## Troubleshooting
|
|
229
|
+
|
|
230
|
+
### "Could not find page" Error
|
|
231
|
+
|
|
232
|
+
The parent page is not shared with the integration:
|
|
233
|
+
1. Open the page in Notion
|
|
234
|
+
2. Click "..." > "Add connections"
|
|
235
|
+
3. Select your integration
|
|
236
|
+
|
|
237
|
+
### "unauthorized" Error
|
|
238
|
+
|
|
239
|
+
API key is invalid or not set:
|
|
240
|
+
1. Check `NOTION_API_KEY` in `.env`
|
|
241
|
+
2. Verify the key at https://www.notion.so/my-integrations
|
|
242
|
+
|
|
243
|
+
### Image Upload Failed
|
|
244
|
+
|
|
245
|
+
1. Check file exists at the specified path
|
|
246
|
+
2. Verify file size is under limit
|
|
247
|
+
3. Ensure file format is supported
|
|
248
|
+
|
|
249
|
+
## References
|
|
250
|
+
|
|
251
|
+
- `references/notion_block_types.md` - Detailed Notion block type reference
|
|
252
|
+
- `references/setup_guide.md` - Step-by-step integration setup guide
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
# Notion Block Types Reference
|
|
2
|
+
|
|
3
|
+
This document provides a comprehensive reference for Notion API block types used by the uploader.
|
|
4
|
+
|
|
5
|
+
## Block Object Structure
|
|
6
|
+
|
|
7
|
+
Every Notion block follows this base structure:
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"object": "block",
|
|
12
|
+
"id": "block-uuid",
|
|
13
|
+
"type": "block_type",
|
|
14
|
+
"created_time": "2024-01-01T00:00:00.000Z",
|
|
15
|
+
"last_edited_time": "2024-01-01T00:00:00.000Z",
|
|
16
|
+
"has_children": false,
|
|
17
|
+
"{block_type}": {
|
|
18
|
+
// type-specific properties
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Text Block Types
|
|
24
|
+
|
|
25
|
+
### Paragraph
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"type": "paragraph",
|
|
30
|
+
"paragraph": {
|
|
31
|
+
"rich_text": [
|
|
32
|
+
{
|
|
33
|
+
"type": "text",
|
|
34
|
+
"text": { "content": "Paragraph text" },
|
|
35
|
+
"annotations": {
|
|
36
|
+
"bold": false,
|
|
37
|
+
"italic": false,
|
|
38
|
+
"strikethrough": false,
|
|
39
|
+
"underline": false,
|
|
40
|
+
"code": false,
|
|
41
|
+
"color": "default"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"color": "default"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Headings
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
// heading_1, heading_2, heading_3
|
|
54
|
+
{
|
|
55
|
+
"type": "heading_1",
|
|
56
|
+
"heading_1": {
|
|
57
|
+
"rich_text": [{"type": "text", "text": {"content": "Heading"}}],
|
|
58
|
+
"color": "default",
|
|
59
|
+
"is_toggleable": false
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Quote
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"type": "quote",
|
|
69
|
+
"quote": {
|
|
70
|
+
"rich_text": [{"type": "text", "text": {"content": "Quote text"}}],
|
|
71
|
+
"color": "default"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Callout
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"type": "callout",
|
|
81
|
+
"callout": {
|
|
82
|
+
"rich_text": [{"type": "text", "text": {"content": "Note content"}}],
|
|
83
|
+
"icon": {"type": "emoji", "emoji": "i"},
|
|
84
|
+
"color": "default"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## List Block Types
|
|
90
|
+
|
|
91
|
+
### Bulleted List Item
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"type": "bulleted_list_item",
|
|
96
|
+
"bulleted_list_item": {
|
|
97
|
+
"rich_text": [{"type": "text", "text": {"content": "Item"}}],
|
|
98
|
+
"color": "default",
|
|
99
|
+
"children": [] // optional nested blocks
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Numbered List Item
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"type": "numbered_list_item",
|
|
109
|
+
"numbered_list_item": {
|
|
110
|
+
"rich_text": [{"type": "text", "text": {"content": "Item"}}],
|
|
111
|
+
"color": "default",
|
|
112
|
+
"children": []
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### To-Do
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
{
|
|
121
|
+
"type": "to_do",
|
|
122
|
+
"to_do": {
|
|
123
|
+
"rich_text": [{"type": "text", "text": {"content": "Task"}}],
|
|
124
|
+
"checked": false,
|
|
125
|
+
"color": "default"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Code Block
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"type": "code",
|
|
135
|
+
"code": {
|
|
136
|
+
"rich_text": [{"type": "text", "text": {"content": "print('hello')"}}],
|
|
137
|
+
"language": "python",
|
|
138
|
+
"caption": []
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Supported Languages
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
abap, arduino, bash, basic, c, clojure, coffeescript, c++, c#, css,
|
|
147
|
+
dart, diff, docker, elixir, elm, erlang, flow, fortran, f#, gherkin,
|
|
148
|
+
glsl, go, graphql, groovy, haskell, html, java, javascript, json,
|
|
149
|
+
julia, kotlin, latex, less, lisp, livescript, lua, makefile, markdown,
|
|
150
|
+
markup, matlab, mermaid, nix, objective-c, ocaml, pascal, perl, php,
|
|
151
|
+
plain text, powershell, prolog, protobuf, python, r, reason, ruby,
|
|
152
|
+
rust, sass, scala, scheme, scss, shell, sql, swift, typescript,
|
|
153
|
+
vb.net, verilog, vhdl, visual basic, webassembly, xml, yaml
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Media Block Types
|
|
157
|
+
|
|
158
|
+
### Image
|
|
159
|
+
|
|
160
|
+
External URL:
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"type": "image",
|
|
164
|
+
"image": {
|
|
165
|
+
"type": "external",
|
|
166
|
+
"external": {"url": "https://example.com/image.png"},
|
|
167
|
+
"caption": []
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
File Upload:
|
|
173
|
+
```json
|
|
174
|
+
{
|
|
175
|
+
"type": "image",
|
|
176
|
+
"image": {
|
|
177
|
+
"type": "file_upload",
|
|
178
|
+
"file_upload": {"id": "file-upload-uuid"},
|
|
179
|
+
"caption": []
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### File
|
|
185
|
+
|
|
186
|
+
```json
|
|
187
|
+
{
|
|
188
|
+
"type": "file",
|
|
189
|
+
"file": {
|
|
190
|
+
"type": "file_upload",
|
|
191
|
+
"file_upload": {"id": "file-upload-uuid"},
|
|
192
|
+
"name": "document.pdf"
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Table Block Types
|
|
198
|
+
|
|
199
|
+
### Table
|
|
200
|
+
|
|
201
|
+
```json
|
|
202
|
+
{
|
|
203
|
+
"type": "table",
|
|
204
|
+
"table": {
|
|
205
|
+
"table_width": 3,
|
|
206
|
+
"has_column_header": true,
|
|
207
|
+
"has_row_header": false,
|
|
208
|
+
"children": [/* table_row blocks */]
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Table Row
|
|
214
|
+
|
|
215
|
+
```json
|
|
216
|
+
{
|
|
217
|
+
"type": "table_row",
|
|
218
|
+
"table_row": {
|
|
219
|
+
"cells": [
|
|
220
|
+
[{"type": "text", "text": {"content": "Cell 1"}}],
|
|
221
|
+
[{"type": "text", "text": {"content": "Cell 2"}}],
|
|
222
|
+
[{"type": "text", "text": {"content": "Cell 3"}}]
|
|
223
|
+
]
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Other Block Types
|
|
229
|
+
|
|
230
|
+
### Divider
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
{
|
|
234
|
+
"type": "divider",
|
|
235
|
+
"divider": {}
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Bookmark
|
|
240
|
+
|
|
241
|
+
```json
|
|
242
|
+
{
|
|
243
|
+
"type": "bookmark",
|
|
244
|
+
"bookmark": {
|
|
245
|
+
"url": "https://example.com",
|
|
246
|
+
"caption": []
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Rich Text Object
|
|
252
|
+
|
|
253
|
+
Rich text objects are used within blocks for formatted text:
|
|
254
|
+
|
|
255
|
+
```json
|
|
256
|
+
{
|
|
257
|
+
"type": "text",
|
|
258
|
+
"text": {
|
|
259
|
+
"content": "Text content",
|
|
260
|
+
"link": {"url": "https://example.com"} // optional
|
|
261
|
+
},
|
|
262
|
+
"annotations": {
|
|
263
|
+
"bold": false,
|
|
264
|
+
"italic": false,
|
|
265
|
+
"strikethrough": false,
|
|
266
|
+
"underline": false,
|
|
267
|
+
"code": false,
|
|
268
|
+
"color": "default"
|
|
269
|
+
},
|
|
270
|
+
"plain_text": "Text content",
|
|
271
|
+
"href": null
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Available Colors
|
|
276
|
+
|
|
277
|
+
```
|
|
278
|
+
default, gray, brown, orange, yellow, green, blue, purple, pink, red
|
|
279
|
+
gray_background, brown_background, orange_background, yellow_background,
|
|
280
|
+
green_background, blue_background, purple_background, pink_background, red_background
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## File Upload Object
|
|
284
|
+
|
|
285
|
+
Used for uploading local files to Notion:
|
|
286
|
+
|
|
287
|
+
```json
|
|
288
|
+
{
|
|
289
|
+
"object": "file_upload",
|
|
290
|
+
"id": "uuid",
|
|
291
|
+
"status": "uploaded",
|
|
292
|
+
"filename": "image.png",
|
|
293
|
+
"content_type": "image/png",
|
|
294
|
+
"content_length": 12345
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## API Endpoints
|
|
299
|
+
|
|
300
|
+
| Endpoint | Method | Description |
|
|
301
|
+
|----------|--------|-------------|
|
|
302
|
+
| `/v1/pages` | POST | Create a new page |
|
|
303
|
+
| `/v1/blocks/{id}/children` | PATCH | Append blocks to page |
|
|
304
|
+
| `/v1/blocks/{id}/children` | GET | Retrieve child blocks |
|
|
305
|
+
| `/v1/file_uploads` | POST | Create file upload |
|
|
306
|
+
| `/v1/file_uploads/{id}/send` | POST | Upload file content |
|
|
307
|
+
|
|
308
|
+
## Rate Limits
|
|
309
|
+
|
|
310
|
+
- Average: 3 requests per second
|
|
311
|
+
- Burst: Up to 3 concurrent requests
|
|
312
|
+
- Block limit: 100 blocks per request
|
|
313
|
+
|
|
314
|
+
## Error Codes
|
|
315
|
+
|
|
316
|
+
| Code | Description |
|
|
317
|
+
|------|-------------|
|
|
318
|
+
| 400 | Invalid request body |
|
|
319
|
+
| 401 | Invalid API key |
|
|
320
|
+
| 403 | Access denied to resource |
|
|
321
|
+
| 404 | Resource not found |
|
|
322
|
+
| 429 | Rate limit exceeded |
|
|
323
|
+
| 500 | Internal server error |
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Notion Integration Setup Guide
|
|
2
|
+
|
|
3
|
+
This guide walks through setting up a Notion integration for the Markdown uploader.
|
|
4
|
+
|
|
5
|
+
## Step 1: Create a Notion Integration
|
|
6
|
+
|
|
7
|
+
1. Go to https://www.notion.so/my-integrations
|
|
8
|
+
2. Click "+ New integration"
|
|
9
|
+
3. Fill in the form:
|
|
10
|
+
- **Name**: Choose a descriptive name (e.g., "MD Uploader")
|
|
11
|
+
- **Logo**: Optional
|
|
12
|
+
- **Associated workspace**: Select your target workspace
|
|
13
|
+
4. Click "Submit"
|
|
14
|
+
|
|
15
|
+
## Step 2: Configure Capabilities
|
|
16
|
+
|
|
17
|
+
After creation, configure the integration's capabilities:
|
|
18
|
+
|
|
19
|
+
### Content Capabilities
|
|
20
|
+
|
|
21
|
+
Enable all of these for full functionality:
|
|
22
|
+
|
|
23
|
+
- **Read content**: Required for verifying pages
|
|
24
|
+
- **Update content**: Required for appending blocks
|
|
25
|
+
- **Insert content**: Required for creating pages and uploading files
|
|
26
|
+
|
|
27
|
+
### User Capabilities
|
|
28
|
+
|
|
29
|
+
- **Read user information including email addresses**: Not required
|
|
30
|
+
- **No user information**: Sufficient for this tool
|
|
31
|
+
|
|
32
|
+
### Comment Capabilities
|
|
33
|
+
|
|
34
|
+
- Not required for the uploader
|
|
35
|
+
|
|
36
|
+
## Step 3: Copy the API Key
|
|
37
|
+
|
|
38
|
+
1. On the integration page, find the "Internal Integration Secret"
|
|
39
|
+
2. Click "Show" then "Copy"
|
|
40
|
+
3. Store this securely - it cannot be retrieved again
|
|
41
|
+
|
|
42
|
+
## Step 4: Set Environment Variable
|
|
43
|
+
|
|
44
|
+
### Option A: .env File (Recommended)
|
|
45
|
+
|
|
46
|
+
Create or edit `.env` in your project root:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
NOTION_API_KEY=ntn_xxxxxxxxxxxxxxxxxxxxx
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Option B: Export in Shell
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
export NOTION_API_KEY=ntn_xxxxxxxxxxxxxxxxxxxxx
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Option C: Session-specific
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
NOTION_API_KEY=ntn_xxx uv run python upload_md.py ...
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Step 5: Share Pages with Integration
|
|
65
|
+
|
|
66
|
+
The integration can only access pages explicitly shared with it.
|
|
67
|
+
|
|
68
|
+
### Sharing a Single Page
|
|
69
|
+
|
|
70
|
+
1. Open the target page in Notion
|
|
71
|
+
2. Click the "..." menu (top right corner)
|
|
72
|
+
3. Select "Add connections"
|
|
73
|
+
4. Search for your integration name
|
|
74
|
+
5. Click to add
|
|
75
|
+
|
|
76
|
+
### Sharing a Database
|
|
77
|
+
|
|
78
|
+
Same process as above, but on the database page.
|
|
79
|
+
|
|
80
|
+
### Sharing Child Pages
|
|
81
|
+
|
|
82
|
+
When you share a parent page, all child pages are also accessible.
|
|
83
|
+
|
|
84
|
+
## Step 6: Find Page ID
|
|
85
|
+
|
|
86
|
+
### From Browser URL
|
|
87
|
+
|
|
88
|
+
1. Open the page in Notion
|
|
89
|
+
2. Copy the URL: `https://www.notion.so/Page-Title-abc123def456ghi789jkl012`
|
|
90
|
+
3. The ID is the 32-character string at the end: `abc123def456ghi789jkl012`
|
|
91
|
+
|
|
92
|
+
### From Share Link
|
|
93
|
+
|
|
94
|
+
1. Click "Share" > "Copy link"
|
|
95
|
+
2. Extract the ID from the URL
|
|
96
|
+
|
|
97
|
+
### Format Requirements
|
|
98
|
+
|
|
99
|
+
The script accepts:
|
|
100
|
+
- Raw 32-character ID: `abc123def456ghi789jkl012`
|
|
101
|
+
- UUID format: `abc123de-f456-ghi7-89jk-l012mnopqrst`
|
|
102
|
+
- Full URL: `https://www.notion.so/...`
|
|
103
|
+
|
|
104
|
+
## Verification
|
|
105
|
+
|
|
106
|
+
Test the setup:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
uv run python .claude/skills/notion-md-uploader/scripts/upload_md.py \
|
|
110
|
+
--help
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Then try a dry run:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
uv run python .claude/skills/notion-md-uploader/scripts/upload_md.py \
|
|
117
|
+
README.md \
|
|
118
|
+
--parent-page-id YOUR_PAGE_ID \
|
|
119
|
+
--dry-run
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Troubleshooting
|
|
123
|
+
|
|
124
|
+
### "unauthorized" Error
|
|
125
|
+
|
|
126
|
+
1. Verify the API key is correct
|
|
127
|
+
2. Check there are no extra spaces or newlines
|
|
128
|
+
3. Regenerate the key if needed
|
|
129
|
+
|
|
130
|
+
### "Could not find page" Error
|
|
131
|
+
|
|
132
|
+
1. Confirm the page ID is correct
|
|
133
|
+
2. Ensure the page is shared with the integration
|
|
134
|
+
3. Check the integration has the required capabilities
|
|
135
|
+
|
|
136
|
+
### "validation_error" for File Upload
|
|
137
|
+
|
|
138
|
+
1. File may exceed size limit (20MB, or 5MB for free plans)
|
|
139
|
+
2. File type may not be supported
|
|
140
|
+
3. Check the file exists at the specified path
|
|
141
|
+
|
|
142
|
+
## Security Best Practices
|
|
143
|
+
|
|
144
|
+
1. **Never commit API keys**: Add `.env` to `.gitignore`
|
|
145
|
+
2. **Use environment variables**: Don't hardcode keys
|
|
146
|
+
3. **Limit access**: Only share necessary pages
|
|
147
|
+
4. **Rotate keys**: Regenerate periodically
|
|
148
|
+
5. **Monitor usage**: Check integration logs in Notion
|
|
149
|
+
|
|
150
|
+
## Workspace Limits
|
|
151
|
+
|
|
152
|
+
| Feature | Free | Plus/Business |
|
|
153
|
+
|---------|------|---------------|
|
|
154
|
+
| Max file size | 5 MB | 5 GB |
|
|
155
|
+
| API rate limit | 3 req/s | 3 req/s |
|
|
156
|
+
| Blocks per request | 100 | 100 |
|