--- name: notion-writer description: | Markdown to Notion page writer with database row creation support. Triggers: write to Notion, export to Notion, push content, create Notion page. --- # Notion Writer Skill Push markdown content to Notion pages or databases via Claude Code. ## Prerequisites - Python virtual environment at `~/Project/our-claude-skills/custom-skills/02-notion-writer/code/scripts/venv` - Notion integration token (preferred: stored in 1Password — see [Credential handling](#credential-handling) below) - Target pages/databases must be shared with the integration in Notion (Database/Page → ⋯ → Connections → add integration) ## Quick Start ```bash cd ~/Project/our-claude-skills/custom-skills/02-notion-writer/code/scripts source venv/bin/activate ``` ## Credential handling **Do NOT store the Notion API key in a `.env` file.** A long-lived plaintext secret on disk is unnecessary risk — Notion integration tokens grant write access to every page/database the integration is connected to. ### Preferred: fetch from 1Password at runtime Store the token once in 1Password (Item: `Notion - Claude Agent`, Vault: `Development`, Field: `api-key`), then fetch on each invocation. The token lives only in process memory — never on disk, never in shell history. ```bash # One-shot push — token fetched + used + discarded in a single shell line NOTION_API_KEY="$(op read 'op://Development/Notion - Claude Agent/api-key')" \ python notion_writer.py --test ``` For repeated use in the same session, scope the variable to a subshell so it leaves no trace: ```bash ( export NOTION_API_KEY="$(op read 'op://Development/Notion - Claude Agent/api-key')" python notion_writer.py --database "$DB_URL" --title "Foo" --file foo.md python notion_writer.py --database "$DB_URL" --title "Bar" --file bar.md ) # NOTION_API_KEY is gone from the parent shell ``` ### Field name conventions in 1Password When you create the Notion integration item, use these field names: | Field | Value | |---|---| | `api-key` (CONCEALED) | The integration token (`ntn_...` for current API, `secret_...` for legacy) | | `username` | Integration display name (e.g. "Claude Agent") | | `hostname` | `https://api.notion.com` | | `notesPlain` | Vault for which workspace + which databases this integration is connected to | The script reads `NOTION_API_KEY` from the environment — it does not call `op` itself. This keeps the script free of `op`-specific dependencies and lets the same script work in CI/CD environments that inject the secret differently (GitHub Actions secrets, Vault, etc.). ### Fallback: `.env` file (discouraged) If you must use a file (e.g. running on a host without 1Password CLI), place it at `~/Project/our-claude-skills/custom-skills/32-notion-writer/code/.env` with: ``` NOTION_API_KEY=ntn_xxxxxxxx_paste_your_token_here ``` Set the file to mode `600` (`chmod 600 .env`) and never commit it. Add `.env` to `.gitignore` if not already. ### What NEVER to do - ❌ Echo the token to stdout: `echo "$NOTION_API_KEY"` (lands in terminal scrollback + shell history if the var was inline) - ❌ Pass the token as a CLI argument: `--api-key "ntn_..."` (visible in `ps`, history, telemetry) - ❌ Commit a `.env` file to git, even briefly (commit history is forever) - ❌ Paste the token into chat / issues / PRs — assume any pasted secret is compromised within minutes ### Token rotation If a token is exposed (or you reasonably suspect it might be): 1. Go to → open the integration → **Reset** the secret 2. Update the 1Password item's `api-key` field with the new token 3. Verify with `NOTION_API_KEY="$(op read ...)" python notion_writer.py --test` 4. The old token is invalidated immediately — any leftover scripts using it will fail loudly ## Commands ### Test Connection ```bash python notion_writer.py --test ``` ### List Accessible Content ```bash python notion_writer.py --list python notion_writer.py --list --filter pages python notion_writer.py --list --filter databases ``` ### Get Page/Database Info ```bash python notion_writer.py -p PAGE_URL --info python notion_writer.py -d DATABASE_URL --info ``` ### Write to Page ```bash # Append content python notion_writer.py -p PAGE_URL -f content.md # Replace content python notion_writer.py -p PAGE_URL -f content.md --replace # From stdin cat report.md | python notion_writer.py -p PAGE_URL --stdin ``` ### Create Database Row ```bash python notion_writer.py -d DATABASE_URL -t "Entry Title" -f content.md ``` ## Supported Markdown | Markdown | Notion Block | |----------|--------------| | `# Heading` | Heading 1 | | `## Heading` | Heading 2 | | `### Heading` | Heading 3 | | `- item` | Bulleted list | | `1. item` | Numbered list | | `- [ ] task` | To-do (unchecked) | | `- [x] task` | To-do (checked) | | `> quote` | Quote | | `` ```code``` `` | Code block | | `---` | Divider | | Paragraphs | Paragraph | ## Workflow Example Integrate with Jamie YouTube Manager to log video info: ```bash # Check video and save to markdown python jamie_youtube_api_test.py VIDEO_URL # Write to Notion python notion_writer.py -p LOG_PAGE_URL -f output/video_status.md ```