@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,477 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Notion Block Converter.
|
|
4
|
+
|
|
5
|
+
Converts parsed Markdown blocks to Notion API block objects.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from markdown_parser import BlockType, InlineStyle, MarkdownBlock
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class NotionBlockConverter:
|
|
15
|
+
"""Converts MarkdownBlock objects to Notion API block format."""
|
|
16
|
+
|
|
17
|
+
# Emoji mapping for callout types
|
|
18
|
+
CALLOUT_ICONS = {
|
|
19
|
+
"note": "information_source",
|
|
20
|
+
"warning": "warning",
|
|
21
|
+
"tip": "bulb",
|
|
22
|
+
"important": "star",
|
|
23
|
+
"caution": "warning",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# Language mapping for code blocks
|
|
27
|
+
LANGUAGE_MAP = {
|
|
28
|
+
"py": "python",
|
|
29
|
+
"js": "javascript",
|
|
30
|
+
"ts": "typescript",
|
|
31
|
+
"sh": "shell",
|
|
32
|
+
"bash": "shell",
|
|
33
|
+
"yml": "yaml",
|
|
34
|
+
"": "plain text",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
image_uploader: Any | None = None,
|
|
40
|
+
base_path: str = "",
|
|
41
|
+
):
|
|
42
|
+
"""Initialize the converter.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
image_uploader: Optional callable that uploads images and returns file_upload_id
|
|
46
|
+
base_path: Base path for resolving relative image paths
|
|
47
|
+
"""
|
|
48
|
+
self.image_uploader = image_uploader
|
|
49
|
+
self.base_path = Path(base_path) if base_path else Path.cwd()
|
|
50
|
+
|
|
51
|
+
def convert_blocks(
|
|
52
|
+
self,
|
|
53
|
+
blocks: list[MarkdownBlock],
|
|
54
|
+
) -> list[dict[str, Any]]:
|
|
55
|
+
"""Convert a list of MarkdownBlocks to Notion blocks.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
blocks: List of parsed MarkdownBlock objects
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
List of Notion block objects
|
|
62
|
+
"""
|
|
63
|
+
notion_blocks = []
|
|
64
|
+
for block in blocks:
|
|
65
|
+
converted = self.convert_block(block)
|
|
66
|
+
if converted:
|
|
67
|
+
if isinstance(converted, list):
|
|
68
|
+
notion_blocks.extend(converted)
|
|
69
|
+
else:
|
|
70
|
+
notion_blocks.append(converted)
|
|
71
|
+
return notion_blocks
|
|
72
|
+
|
|
73
|
+
def convert_block(
|
|
74
|
+
self,
|
|
75
|
+
block: MarkdownBlock,
|
|
76
|
+
) -> dict[str, Any] | list[dict[str, Any]] | None:
|
|
77
|
+
"""Convert a single MarkdownBlock to Notion block(s).
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
block: Parsed MarkdownBlock object
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Notion block object, list of blocks, or None
|
|
84
|
+
"""
|
|
85
|
+
converters = {
|
|
86
|
+
BlockType.HEADING1: self._convert_heading,
|
|
87
|
+
BlockType.HEADING2: self._convert_heading,
|
|
88
|
+
BlockType.HEADING3: self._convert_heading,
|
|
89
|
+
BlockType.PARAGRAPH: self._convert_paragraph,
|
|
90
|
+
BlockType.BULLETED_LIST: self._convert_bulleted_list,
|
|
91
|
+
BlockType.NUMBERED_LIST: self._convert_numbered_list,
|
|
92
|
+
BlockType.CODE_BLOCK: self._convert_code_block,
|
|
93
|
+
BlockType.QUOTE: self._convert_quote,
|
|
94
|
+
BlockType.DIVIDER: self._convert_divider,
|
|
95
|
+
BlockType.IMAGE: self._convert_image,
|
|
96
|
+
BlockType.TABLE: self._convert_table,
|
|
97
|
+
BlockType.TODO: self._convert_todo,
|
|
98
|
+
BlockType.CALLOUT: self._convert_callout,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
converter = converters.get(block.block_type)
|
|
102
|
+
if converter:
|
|
103
|
+
return converter(block)
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
def _convert_rich_text(
|
|
107
|
+
self,
|
|
108
|
+
content: list[InlineStyle] | str,
|
|
109
|
+
) -> list[dict[str, Any]]:
|
|
110
|
+
"""Convert inline content to Notion rich_text array.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
content: InlineStyle list or plain string
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Notion rich_text array
|
|
117
|
+
"""
|
|
118
|
+
if isinstance(content, str):
|
|
119
|
+
return [{"type": "text", "text": {"content": content}}]
|
|
120
|
+
|
|
121
|
+
rich_text = []
|
|
122
|
+
for style in content:
|
|
123
|
+
text_obj: dict[str, Any] = {
|
|
124
|
+
"type": "text",
|
|
125
|
+
"text": {"content": style.text},
|
|
126
|
+
"annotations": {
|
|
127
|
+
"bold": style.bold,
|
|
128
|
+
"italic": style.italic,
|
|
129
|
+
"strikethrough": style.strikethrough,
|
|
130
|
+
"underline": False,
|
|
131
|
+
"code": style.code,
|
|
132
|
+
"color": "default",
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
# Only add link if it's a valid HTTP(S) URL
|
|
136
|
+
if style.link and style.link.startswith(("http://", "https://")):
|
|
137
|
+
text_obj["text"]["link"] = {"url": style.link}
|
|
138
|
+
|
|
139
|
+
rich_text.append(text_obj)
|
|
140
|
+
|
|
141
|
+
return rich_text if rich_text else [{"type": "text", "text": {"content": ""}}]
|
|
142
|
+
|
|
143
|
+
def _convert_heading(
|
|
144
|
+
self,
|
|
145
|
+
block: MarkdownBlock,
|
|
146
|
+
) -> dict[str, Any]:
|
|
147
|
+
"""Convert heading block."""
|
|
148
|
+
heading_map = {
|
|
149
|
+
BlockType.HEADING1: "heading_1",
|
|
150
|
+
BlockType.HEADING2: "heading_2",
|
|
151
|
+
BlockType.HEADING3: "heading_3",
|
|
152
|
+
}
|
|
153
|
+
heading_type = heading_map[block.block_type]
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
"object": "block",
|
|
157
|
+
"type": heading_type,
|
|
158
|
+
heading_type: {
|
|
159
|
+
"rich_text": self._convert_rich_text(block.content),
|
|
160
|
+
"color": "default",
|
|
161
|
+
"is_toggleable": False,
|
|
162
|
+
},
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
def _convert_paragraph(
|
|
166
|
+
self,
|
|
167
|
+
block: MarkdownBlock,
|
|
168
|
+
) -> dict[str, Any]:
|
|
169
|
+
"""Convert paragraph block."""
|
|
170
|
+
return {
|
|
171
|
+
"object": "block",
|
|
172
|
+
"type": "paragraph",
|
|
173
|
+
"paragraph": {
|
|
174
|
+
"rich_text": self._convert_rich_text(block.content),
|
|
175
|
+
"color": "default",
|
|
176
|
+
},
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
def _convert_bulleted_list(
|
|
180
|
+
self,
|
|
181
|
+
block: MarkdownBlock,
|
|
182
|
+
) -> dict[str, Any]:
|
|
183
|
+
"""Convert bulleted list item."""
|
|
184
|
+
return {
|
|
185
|
+
"object": "block",
|
|
186
|
+
"type": "bulleted_list_item",
|
|
187
|
+
"bulleted_list_item": {
|
|
188
|
+
"rich_text": self._convert_rich_text(block.content),
|
|
189
|
+
"color": "default",
|
|
190
|
+
},
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
def _convert_numbered_list(
|
|
194
|
+
self,
|
|
195
|
+
block: MarkdownBlock,
|
|
196
|
+
) -> dict[str, Any]:
|
|
197
|
+
"""Convert numbered list item."""
|
|
198
|
+
return {
|
|
199
|
+
"object": "block",
|
|
200
|
+
"type": "numbered_list_item",
|
|
201
|
+
"numbered_list_item": {
|
|
202
|
+
"rich_text": self._convert_rich_text(block.content),
|
|
203
|
+
"color": "default",
|
|
204
|
+
},
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
def _convert_code_block(
|
|
208
|
+
self,
|
|
209
|
+
block: MarkdownBlock,
|
|
210
|
+
) -> dict[str, Any]:
|
|
211
|
+
"""Convert code block."""
|
|
212
|
+
language = block.metadata.get("language", "plain text")
|
|
213
|
+
language = self.LANGUAGE_MAP.get(language, language)
|
|
214
|
+
|
|
215
|
+
# Notion has specific language values
|
|
216
|
+
valid_languages = {
|
|
217
|
+
"abap", "arduino", "bash", "basic", "c", "clojure", "coffeescript",
|
|
218
|
+
"c++", "c#", "css", "dart", "diff", "docker", "elixir", "elm",
|
|
219
|
+
"erlang", "flow", "fortran", "f#", "gherkin", "glsl", "go", "graphql",
|
|
220
|
+
"groovy", "haskell", "html", "java", "javascript", "json", "julia",
|
|
221
|
+
"kotlin", "latex", "less", "lisp", "livescript", "lua", "makefile",
|
|
222
|
+
"markdown", "markup", "matlab", "mermaid", "nix", "objective-c",
|
|
223
|
+
"ocaml", "pascal", "perl", "php", "plain text", "powershell",
|
|
224
|
+
"prolog", "protobuf", "python", "r", "reason", "ruby", "rust",
|
|
225
|
+
"sass", "scala", "scheme", "scss", "shell", "sql", "swift",
|
|
226
|
+
"typescript", "vb.net", "verilog", "vhdl", "visual basic",
|
|
227
|
+
"webassembly", "xml", "yaml", "java/c/c++/c#",
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if language.lower() not in valid_languages:
|
|
231
|
+
language = "plain text"
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
"object": "block",
|
|
235
|
+
"type": "code",
|
|
236
|
+
"code": {
|
|
237
|
+
"rich_text": [
|
|
238
|
+
{"type": "text", "text": {"content": block.content}}
|
|
239
|
+
],
|
|
240
|
+
"language": language.lower(),
|
|
241
|
+
"caption": [],
|
|
242
|
+
},
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
def _convert_quote(
|
|
246
|
+
self,
|
|
247
|
+
block: MarkdownBlock,
|
|
248
|
+
) -> dict[str, Any]:
|
|
249
|
+
"""Convert quote block."""
|
|
250
|
+
return {
|
|
251
|
+
"object": "block",
|
|
252
|
+
"type": "quote",
|
|
253
|
+
"quote": {
|
|
254
|
+
"rich_text": self._convert_rich_text(block.content),
|
|
255
|
+
"color": "default",
|
|
256
|
+
},
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
def _convert_divider(
|
|
260
|
+
self,
|
|
261
|
+
block: MarkdownBlock,
|
|
262
|
+
) -> dict[str, Any]:
|
|
263
|
+
"""Convert divider block."""
|
|
264
|
+
return {
|
|
265
|
+
"object": "block",
|
|
266
|
+
"type": "divider",
|
|
267
|
+
"divider": {},
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
def _convert_image(
|
|
271
|
+
self,
|
|
272
|
+
block: MarkdownBlock,
|
|
273
|
+
) -> dict[str, Any]:
|
|
274
|
+
"""Convert image block."""
|
|
275
|
+
url = block.metadata.get("url", "")
|
|
276
|
+
alt_text = block.content if isinstance(block.content, str) else ""
|
|
277
|
+
|
|
278
|
+
# Check if it's a local file or URL
|
|
279
|
+
if url.startswith(("http://", "https://")):
|
|
280
|
+
# External URL
|
|
281
|
+
return {
|
|
282
|
+
"object": "block",
|
|
283
|
+
"type": "image",
|
|
284
|
+
"image": {
|
|
285
|
+
"type": "external",
|
|
286
|
+
"external": {"url": url},
|
|
287
|
+
"caption": [
|
|
288
|
+
{"type": "text", "text": {"content": alt_text}}
|
|
289
|
+
] if alt_text else [],
|
|
290
|
+
},
|
|
291
|
+
}
|
|
292
|
+
else:
|
|
293
|
+
# Local file - needs upload
|
|
294
|
+
local_path = self.base_path / url
|
|
295
|
+
if self.image_uploader and local_path.exists():
|
|
296
|
+
try:
|
|
297
|
+
file_upload_id = self.image_uploader(str(local_path))
|
|
298
|
+
return {
|
|
299
|
+
"object": "block",
|
|
300
|
+
"type": "image",
|
|
301
|
+
"image": {
|
|
302
|
+
"type": "file_upload",
|
|
303
|
+
"file_upload": {"id": file_upload_id},
|
|
304
|
+
"caption": [
|
|
305
|
+
{"type": "text", "text": {"content": alt_text}}
|
|
306
|
+
] if alt_text else [],
|
|
307
|
+
},
|
|
308
|
+
}
|
|
309
|
+
except Exception as e:
|
|
310
|
+
# Fall back to paragraph with error message
|
|
311
|
+
return {
|
|
312
|
+
"object": "block",
|
|
313
|
+
"type": "paragraph",
|
|
314
|
+
"paragraph": {
|
|
315
|
+
"rich_text": [
|
|
316
|
+
{
|
|
317
|
+
"type": "text",
|
|
318
|
+
"text": {"content": f"[Image upload failed: {url}] - {e}"},
|
|
319
|
+
"annotations": {"italic": True, "color": "red"},
|
|
320
|
+
}
|
|
321
|
+
],
|
|
322
|
+
},
|
|
323
|
+
}
|
|
324
|
+
else:
|
|
325
|
+
# No uploader or file not found
|
|
326
|
+
return {
|
|
327
|
+
"object": "block",
|
|
328
|
+
"type": "paragraph",
|
|
329
|
+
"paragraph": {
|
|
330
|
+
"rich_text": [
|
|
331
|
+
{
|
|
332
|
+
"type": "text",
|
|
333
|
+
"text": {"content": f"[Image: {url}]"},
|
|
334
|
+
"annotations": {"italic": True, "bold": False, "strikethrough": False, "underline": False, "code": False, "color": "gray"},
|
|
335
|
+
}
|
|
336
|
+
],
|
|
337
|
+
},
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
def _convert_table(
|
|
341
|
+
self,
|
|
342
|
+
block: MarkdownBlock,
|
|
343
|
+
) -> list[dict[str, Any]]:
|
|
344
|
+
"""Convert table block to Notion table blocks."""
|
|
345
|
+
rows = block.metadata.get("rows", [])
|
|
346
|
+
has_header = block.metadata.get("has_header", True)
|
|
347
|
+
column_count = block.metadata.get("column_count", 0)
|
|
348
|
+
|
|
349
|
+
if not rows or column_count == 0:
|
|
350
|
+
return []
|
|
351
|
+
|
|
352
|
+
# Create table block with children
|
|
353
|
+
table_rows = []
|
|
354
|
+
for row in rows:
|
|
355
|
+
# Ensure row has correct number of cells
|
|
356
|
+
cells = row[:column_count]
|
|
357
|
+
while len(cells) < column_count:
|
|
358
|
+
cells.append("")
|
|
359
|
+
|
|
360
|
+
table_row = {
|
|
361
|
+
"object": "block",
|
|
362
|
+
"type": "table_row",
|
|
363
|
+
"table_row": {
|
|
364
|
+
"cells": [
|
|
365
|
+
[{"type": "text", "text": {"content": cell}}]
|
|
366
|
+
for cell in cells
|
|
367
|
+
]
|
|
368
|
+
},
|
|
369
|
+
}
|
|
370
|
+
table_rows.append(table_row)
|
|
371
|
+
|
|
372
|
+
table_block = {
|
|
373
|
+
"object": "block",
|
|
374
|
+
"type": "table",
|
|
375
|
+
"table": {
|
|
376
|
+
"table_width": column_count,
|
|
377
|
+
"has_column_header": has_header,
|
|
378
|
+
"has_row_header": False,
|
|
379
|
+
"children": table_rows,
|
|
380
|
+
},
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return [table_block]
|
|
384
|
+
|
|
385
|
+
def _convert_todo(
|
|
386
|
+
self,
|
|
387
|
+
block: MarkdownBlock,
|
|
388
|
+
) -> dict[str, Any]:
|
|
389
|
+
"""Convert todo/checkbox block."""
|
|
390
|
+
checked = block.metadata.get("checked", False)
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
"object": "block",
|
|
394
|
+
"type": "to_do",
|
|
395
|
+
"to_do": {
|
|
396
|
+
"rich_text": self._convert_rich_text(block.content),
|
|
397
|
+
"checked": checked,
|
|
398
|
+
"color": "default",
|
|
399
|
+
},
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
def _convert_callout(
|
|
403
|
+
self,
|
|
404
|
+
block: MarkdownBlock,
|
|
405
|
+
) -> dict[str, Any]:
|
|
406
|
+
"""Convert callout block."""
|
|
407
|
+
callout_type = block.metadata.get("type", "note")
|
|
408
|
+
|
|
409
|
+
# Emoji mapping
|
|
410
|
+
emoji_map = {
|
|
411
|
+
"note": "information_source",
|
|
412
|
+
"warning": "warning",
|
|
413
|
+
"tip": "bulb",
|
|
414
|
+
"important": "star",
|
|
415
|
+
"caution": "construction",
|
|
416
|
+
}
|
|
417
|
+
emoji = emoji_map.get(callout_type, "memo")
|
|
418
|
+
|
|
419
|
+
return {
|
|
420
|
+
"object": "block",
|
|
421
|
+
"type": "callout",
|
|
422
|
+
"callout": {
|
|
423
|
+
"rich_text": self._convert_rich_text(block.content),
|
|
424
|
+
"icon": {"type": "emoji", "emoji": self._get_emoji(emoji)},
|
|
425
|
+
"color": "default",
|
|
426
|
+
},
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
def _get_emoji(self, name: str) -> str:
|
|
430
|
+
"""Get emoji character from name."""
|
|
431
|
+
emoji_map = {
|
|
432
|
+
"information_source": "ℹ️",
|
|
433
|
+
"warning": "⚠️",
|
|
434
|
+
"bulb": "💡",
|
|
435
|
+
"star": "⭐",
|
|
436
|
+
"construction": "🚧",
|
|
437
|
+
"memo": "📝",
|
|
438
|
+
}
|
|
439
|
+
return emoji_map.get(name, "ℹ️")
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def main():
|
|
443
|
+
"""Test the converter."""
|
|
444
|
+
from markdown_parser import MarkdownParser
|
|
445
|
+
|
|
446
|
+
test_md = """# Test Document
|
|
447
|
+
|
|
448
|
+
This is a **bold** paragraph with *italic* text.
|
|
449
|
+
|
|
450
|
+
## Features
|
|
451
|
+
|
|
452
|
+
- Item 1
|
|
453
|
+
- Item 2
|
|
454
|
+
|
|
455
|
+
```python
|
|
456
|
+
print("Hello")
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
> Quote text
|
|
460
|
+
|
|
461
|
+
| A | B |
|
|
462
|
+
|---|---|
|
|
463
|
+
| 1 | 2 |
|
|
464
|
+
"""
|
|
465
|
+
|
|
466
|
+
parser = MarkdownParser()
|
|
467
|
+
blocks = parser.parse(test_md)
|
|
468
|
+
|
|
469
|
+
converter = NotionBlockConverter()
|
|
470
|
+
notion_blocks = converter.convert_blocks(blocks)
|
|
471
|
+
|
|
472
|
+
import json
|
|
473
|
+
print(json.dumps(notion_blocks, indent=2))
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
if __name__ == "__main__":
|
|
477
|
+
main()
|