Save information from supported sites into Notion or local Markdown without exposing secrets or filesystem paths in the browser. The project currently supports YouTube transcript capture and Gmail email-content capture, with room to expand further over time.
This repository currently contains:
- a Chromium Manifest V3 extension that adds save buttons on supported pages like YouTube and Gmail
- a small local Node.js API that writes captured content into Notion or backend-configured Markdown files
Most browser-side Notion integrations force you to put a secret in the extension itself. This project avoids that by sending captured page data to a local backend, which then talks to Notion using your private integration token.
- Uses a local-first architecture so browser-side code never needs your Notion secret
- Injects a save button into YouTube's watch-page action bar
- Injects a Gmail-style save button into open Gmail message action bars
- Adds right-click actions for clean web-page capture and selected-text capture
- Opens the transcript panel automatically when needed
- Extracts transcript text across multiple known YouTube DOM variants
- Extracts readable email content from the active Gmail message body
- Extracts readable page content with a Reader-style parser and a high-density page-section fallback
- Opens an editable preview before saving generic page and selected-text captures
- Sends captured data to a local backend for persistence into Notion or local Markdown
- Creates a new Notion page or recreates an existing one based on your dedupe mapping
- Writes local Markdown files with YAML frontmatter when
localMarkdownis selected - Preserves a simple load-unpacked development flow
.
├── extension/
│ ├── manifest.json
│ ├── pages/
│ │ └── options/
│ ├── scripts/
│ │ ├── background.js
│ │ ├── content/
│ │ └── shared/
│ └── styles/
├── src/
│ └── backend/
│ ├── config/
│ ├── lib/
│ ├── middleware/
│ ├── routes/
│ └── services/
├── .env.example
└── package.json
- Node.js 18+
- Chrome, Chromium, or another Chromium-based browser
- A Notion integration with access to your target database, if saving to Notion
- A local folder configured in the backend, if saving to local Markdown
npm install
cp .env.example .env
bash scripts/install_hooks.shThe hook installer adds the local Docs Impact git hooks for this clone: an
advisory pre-commit reminder and a blocking commit-msg gate when documented
hot paths change without a docs update or Docs-Impact: none - <reason> trailer.
The extension requires an explicit destination before saving:
notion: writes captures into Notion using the backend's Notion tokenlocalMarkdown: writes.mdfiles into the backend folder configured byLOCAL_MARKDOWN_ROOT
Existing users must open the extension options page and choose a destination after upgrading.
- Go to Notion integrations
- Create an internal integration
- Copy the integration token
- Share your destination database with that integration
Skip this step if you only plan to save local Markdown.
Fill in .env:
NOTION_TOKEN: required for Notion saves, your Notion integration secretHOST: optional, defaults to127.0.0.1PORT: optional, defaults to8787DEFAULT_NOTION_DATABASE_ID: optional fallback database ID for Notion savesLOCAL_MARKDOWN_ROOT: required for local Markdown saves; must be an absolute path or start with~/, point to an existing directory, and be writable by the backend
Start the server:
npm run startThe API will be available at http://127.0.0.1:8787 unless you change HOST.
If you want the backend to keep running without an open terminal, use a launchd agent.
- Create
~/Library/LaunchAgents/com.example.knowledge-gpt.plist - Paste in:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.example.knowledge-gpt</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/node</string>
<string>/path/to/knowledge-gpt/src/backend/server.js</string>
</array>
<key>WorkingDirectory</key>
<string>/path/to/knowledge-gpt</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/path/to/knowledge-gpt/server.log</string>
<key>StandardErrorPath</key>
<string>/path/to/knowledge-gpt/server-error.log</string>
</dict>
</plist>- Load the service:
launchctl load ~/Library/LaunchAgents/com.example.knowledge-gpt.plist
launchctl start com.example.knowledge-gpt- Verify that it is running:
launchctl list | grep knowledge-gpt
curl http://127.0.0.1:8787/health
tail -f "/path/to/knowledge-gpt/server.log"Notes:
launchdrestarts the backend if it exits- the backend will not run while the Mac is asleep
- replace
/path/to/knowledge-gptwith the actual path to your cloned repository - replace
com.example.knowledge-gptwith your own preferred LaunchAgent label if you want - if
nodeis installed somewhere else on your machine, runwhich nodeand update the plist path
If you change the backend source code, restart the launchd service to apply the changes:
launchctl unload ~/Library/LaunchAgents/com.example.knowledge-gpt.plist
launchctl load ~/Library/LaunchAgents/com.example.knowledge-gpt.plist
launchctl start com.example.knowledge-gptYou can then confirm the new version is running with:
curl http://127.0.0.1:8787/health
tail -n 50 "/path/to/knowledge-gpt/server.log"For local Markdown, the browser never sends a folder, filename, extension, or subpath. The backend generates direct-child .md filenames and writes with exclusive create semantics so existing files are not overwritten.
- Open
chrome://extensions - Enable Developer mode
- Click
Load unpacked - Select the repository's
extension/directory
The manifest pins a stable unpacked-extension ID with a public key, so saved options survive normal extension reloads and loading the extension from another local workspace path.
Open the extension options page and set:
- Storage destination:
NotionorLocal Markdown - Backend URL, for example
http://127.0.0.1:8787 - For Notion: Notion database ID, or leave it blank if you set
DEFAULT_NOTION_DATABASE_ID - For Notion: property names for the fields you want this integration to populate
- For Local Markdown: make sure
/healthreports thatLOCAL_MARKDOWN_ROOTis set
After the extension is loaded, clicking the Knowledge GPT toolbar icon opens this options page directly.
The current implementation supports:
- YouTube watch pages for transcript capture
- Gmail message views for email-content capture
- Generic web pages through the right-click
Save clean pageaction - Highlighted page text through the right-click
Save selected textaction
Generic page capture injects the capture script only after a user invokes the context menu. It uses Mozilla Readability against a cloned document to extract the main article-like content without page chrome, CSS, scripts, nav, and ads. If Readability cannot produce enough content, it falls back to scoring likely content containers such as article, main, and high-density page sections. Both generic flows show a preview dialog where the title, source, and content can be edited before saving.
At minimum, configure:
- a title property
Recommended optional mappings:
- a URL property
- an external ID property for stronger deduplication
- a select property for
Creator / Source - a select property for
Source Type - a date property for the last synced timestamp
The current YouTube flow writes the full transcript into the Notion page body as code blocks under a
Transcriptheading. The Gmail flow writes the extracted email body into the page body as code blocks under aContentheading. Generic page captures use anArticleheading, and selected-text captures use aSelectionheading.
Local Markdown files are named with this pattern:
YYYY-MM-DD-<contentType-or-sourceType>-<slug-title>-<short-externalId>.md
If a generated filename already exists, the backend writes -2, -3, and so on. Each file includes YAML frontmatter for available metadata and a Markdown body headed by Transcript, Content, Article, or Selection.
Returns a simple status payload for the local backend, including booleans such as hasNotionToken, hasDefaultDatabaseId, and hasLocalMarkdownRoot. The local Markdown flag is only true when LOCAL_MARKDOWN_ROOT exists and is writable. It does not expose the configured local Markdown path.
Accepts YouTube transcript saves using the shared capture schema.
Notion request:
{
"storageDestination": "notion",
"videoId": "abc123",
"url": "https://www.youtube.com/watch?v=abc123",
"title": "Example video",
"channel": "Example creator",
"transcript": "First line\nSecond line",
"capturedAt": "2026-05-05T10:00:00.000Z",
"databaseId": "optional-database-id",
"propertyMapping": {
"title": "Title",
"videoUrl": "URL",
"videoId": "External ID",
"channel": "Creator / Source",
"sourceType": "Source Type",
"lastSyncedAt": "Last Synced At"
}
}Accepts:
{
"storageDestination": "localMarkdown",
"externalId": "abc123",
"url": "https://example.com/item/abc123",
"title": "Example title",
"source": "Example source",
"sourceType": "Newsletter",
"content": "First line\nSecond line",
"contentType": "email",
"capturedAt": "2026-05-05T10:00:00.000Z"
}For Notion saves, include the Notion fields:
{
"storageDestination": "notion",
"externalId": "abc123",
"url": "https://example.com/item/abc123",
"title": "Example title",
"source": "Example source",
"sourceType": "Newsletter",
"content": "First line\nSecond line",
"contentType": "email",
"capturedAt": "2026-05-05T10:00:00.000Z",
"databaseId": "optional-database-id",
"propertyMapping": {
"title": "Title",
"videoUrl": "URL",
"videoId": "External ID",
"channel": "Creator / Source",
"sourceType": "Source Type",
"lastSyncedAt": "Last Synced At"
}
}Run the backend in watch mode:
npm run devUse npm run dev during active local development if you want the server to restart automatically when backend files change. Use the launchd setup above when you want the server to keep running in the background between terminal sessions.
Run syntax checks:
npm run checkRun tests:
npm testRun the documentation guardrails directly:
bash scripts/docs_impact_guard.sh --worktree
bash scripts/docs_consistency_check.shDurable product and implementation specs live in specs/. Start with
specs/README.md for the format and specs/0001-capture-and-storage-platform.md
for the current capture and storage platform contract.
Before publishing publicly, you will probably still want to add:
- a screenshot or demo GIF
- a real repository URL in
package.json - GitHub issue templates or contribution automation if you want outside contributors