Skip to main content

n-commit

Git-shaped sync between Notion database views and local folders of markdown files. Notion is the source of truth; you decide when to pull and push.

Install

pip install n-commit

First call opens a browser for OAuth. Tokens cache at ~/.config/n-commit/tokens/.

Onboarding

mkdir my-notion && cd my-notion

# one-shot: folder name is derived from the Notion title
n-commit add "https://www.notion.so/…?v=…"
n-commit add "https://www.notion.so/…?v=…" --as crm/people    # explicit folder

# batch: paste URLs in a loop (blank line or Ctrl-C ends)
n-commit add -i

# the first `add` creates n-commit.toml + .gitignore

To use the same workspace on another machine: copy n-commit.toml to a folder and run n-commit pull. (Git, scp, Dropbox — anything that moves a 1KB text file.)

Mental model

A workspace is a directory tree containing one n-commit.toml manifest at its root and one subfolder per binding. Each binding mirrors one Notion database view.

~/my-notion/
├── n-commit.toml          ← the manifest (committed)
├── .gitignore                ← ignores **/.notion/
├── interviews/
│   ├── _FIELDS.md            ← cheat sheet, regenerated on bind
│   ├── jane.md               ← row ↔ file
│   └── .notion/              ← cache (gitignored)
│       ├── schema.json
│       ├── fingerprints.json
│       └── mirror/jane.md    ← last-pulled bytes
└── crm/people/
    ├── _FIELDS.md
    ├── alice.md
    └── .notion/

Manifest:

[folders."interviews"]
view = "https://www.notion.so/…?v=…"

[folders."crm/people"]
view = "https://www.notion.so/…?v=…"

Workspace is found by walking up from cwd, like git finds .git/. One file describes every binding.

Verbs

add, remove, status, pull, push, restore. Run n-commit <verb> --help for the per-verb reference. API-call cost per verb:

Verb Reads remote Writes remote Writes local Default API calls
add yes (one-time) no yes 1 list_rows + 1 fetch_schema (+ N fetch_row if --init=push)
status only --remote no no 0 (or 1 list_rows per binding)
pull yes no yes (clean only) 1 list_rows per binding + N fetch_row (cache misses only)
push yes (drift check) yes yes (echo) 3/file (drift+update+echo); 2 with --force
restore no no yes 0

Conflict / drift playbook

  • pull → ConflictError (dirty file + remote also changed):
    • keep local: push <file> (will DriftError if remote also moved → --force)
    • take remote: restore <file> then re-pull
    • both diverged: open file, mirror at .notion/mirror/<rel>, hand-merge, push
  • push → DriftError (mirror diverged from current remote):
    • keep local: pull --force, re-merge, push. Or push --force.
    • take remote: restore <file>
  • status --remote surfaces conflicts early — cheapest place to catch them.

Layout

File Role
n_commit/core.py The five primitives + CLI entrypoint. All filesystem IO.
n_commit/manifest.py n-commit.toml read/write/find. Pure, no Notion deps.
n_commit/frontmatter.py Pure transforms: envelope parse, props ↔ YAML, body normalize, row fingerprint, slug.
n_commit/client.py Typed Notion MCP wrapper.
n_commit/auth.py Generic MCP OAuth (PKCE) with fcntl.flock to serialize refresh-token rotation.
n_commit/mcp_client.py Generic MCP transport + dynamic tool proxy.

Invariants

  • fetch_row is the single source of truth for row bytes. Never mix list_rows props with envelope-fetch props in the same path — they disagree on edge keys.
  • Mirror is always updated on pull, even on conflict. Working files are only overwritten if clean (or --force). Conflicts surface loudly via ConflictError.
  • Canonicalization for hashing. normalize_body strips presigned-S3 query strings so attachment URLs don't cause drift. CRLF → LF. Trailing whitespace stripped. Triple-newlines collapsed.
  • Read-only types are stripped on push: created_time, last_edited_time, rollup, formula.
  • notion_id is always 32-char undashed hex. Normalize at boundaries via url_to_id.
  • Comments don't bump Last edited time. Use pull <file> (always fetches) when you want comments.
  • query-database-view caps at 100 rows. Larger views truncate silently — pull <file> is the escape hatch for the tail.
  • Empty list_rows is a no-op. Not "everything was deleted".
  • OAuth refresh tokens are single-use. FileTokenStorage uses fcntl.flock so concurrent sessions don't race.
  • create_row validates the parent. Rejected property bags silently produce orphan pages on Notion's side; create_row re-fetches and verifies the parent matches.

Practices

  • frontmatter.py and manifest.py are pure. No IO outside their declared file.
  • core.py's primitives take view_url as an argument; only the CLI reads the manifest.
  • No imports inside functions. Module top only.
  • Explicit over compact. Readable loops beat dense comprehensions.

Metadata

Release files for n-commit 0.5.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for n-commit 0.5.1
File Size Uploaded
n_commit-0.5.1.tar.gz 34.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for n-commit 0.5.1
File Interpreter ABI Platform
n_commit-0.5.1-py3-none-any.whl Python 3 none any Details

Total release size: 74.2 kB

Release files / n_commit-0.5.1.tar.gz

Download URL n_commit-0.5.1.tar.gz
Size 34.7 kB
Tags Source
SHA-256 checksum
How to use checksums
1bd081d14c01d0573da50db08804d76f63b16eaa1c0186218f2c040a02f29179
BLAKE2b-256 checksum
How to use checksums
6f207263ae422b8be974349c44780e0be20a897994e115566e985741366ff879
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.3

Release files / n_commit-0.5.1-py3-none-any.whl

Download URL n_commit-0.5.1-py3-none-any.whl
Size 39.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
acacd87c327978851dc206ed9dd3236085166c54eafd55fad6b5738a45adb75f
BLAKE2b-256 checksum
How to use checksums
c0bda3739c254495020e04306b495bbecde051c87fd35ff0f41f0ac6c56821ab
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.3

Release history Release notifications | RSS feed

This release

0.5.1 This release

2 release files

0.5.0

2 release files

0.4.4

2 release files

0.4.3

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page