Notion import runbook¶
ops/planner/import_notion.py is a one-off, owner-run script (planner slice 2)
that copies the old Notion task list into Jarvis's tasks once. Every page
whose status is still open becomes one task (source: notion_import,
source_ref: the Notion page id, state: open); a date becomes a soft
due date, a project is matched by name against the existing projects.
Pages already imported are skipped, so running it twice creates nothing new.
It is a single stdlib file (like seed_projects.py), so it runs on the box
with no repo checkout. It only reads Notion; it never changes it.
Prerequisites¶
- The planner schema (
modules/planner/schema/pb_migrations/4_tasks.js) is deployed. - Projects are seeded (
ops/planner/seed_projects.py) or created in the app, so Notion project names have something to match.
1. Create the Notion integration¶
- Open https://www.notion.so/profile/integrations, New integration, type Internal, workspace: the one holding the old list.
- Capabilities: Read content only (no update, no insert, no user information).
- Copy the Internal Integration Secret. It is the
NOTION_TOKEN; it never goes into the repo, a chat or an issue.
2. Share the database with it¶
Open the database as a full page, ⋯ › Connections › Connect to, pick the
integration. If the project property is a relation, share the related
database too. Otherwise those tasks import with no project and the dry run
counts them under unmapped_projects as (relation not shared).
The database id is the 32 hex characters at the end of its URL, before ?v=.
--database accepts the id or the whole URL.
3. Copy the script and store the token¶
ssh mailserver 'mkdir -p /root/notion-import'
scp ops/planner/import_notion.py mailserver:/root/notion-import/
# on the box, in an interactive shell (the token never reaches shell history)
ssh -t mailserver
cd /root/notion-import
( umask 077; read -rs -p 'Notion token: ' t; printf 'NOTION_TOKEN=%s\n' "$t" > notion.env ); echo
4. Inspect the database¶
python3 import_notion.py --database <DB_ID> --notion-env notion.env --inspect
Prints each property's name and type, and the option count and labels of
select/status properties (pick --open-values from them). It does not touch
PocketBase. Pick:
| Flag | Property type | Default |
|---|---|---|
--title-prop |
title | the database's title property |
--status-prop |
status, select or checkbox (checked = done) | none: every page is open |
--open-values |
the status labels that mean open | Not started,In progress |
--project-prop |
select, multi_select (first value) or relation (related page title) | none |
--due-prop |
date | none |
--notes-prop |
rich_text | none |
Status and project names match case- and whitespace-insensitively.
5. Dry run¶
python3 import_notion.py --database <DB_ID> --notion-env notion.env --env-file /opt/jarvis/.env \
--status-prop Status --project-prop Project --due-prop Due --notes-prop Notes
No writes. Prints counts only, never a page title:
{"pages": 120, "open": 41, "planned": 41, "imported": 0, "skipped_existing": 0,
"unmapped_projects": 3, "per_project": {"Site refonte": 12, "(none)": 29}, "failed": 0}
unmapped_projects counts Notion project names with no Jarvis project; those
tasks import with no project ((none)). Add --show-unmapped to list the
names, then create the projects you want in the app and dry-run again.
6. Apply¶
python3 import_notion.py <same flags as the dry run> --apply
The undo file (notion-import-undo-<timestamp>.jsonl, or --undo-file PATH)
is created before the first task, never overwritten, and gets one created id
per line. Its path is printed on stderr. Copy it off the box:
scp mailserver:/root/notion-import/notion-import-undo-*.jsonl .
A non-zero failed exits 1; re-run the same command, the imported pages are
skipped.
7. Undo¶
python3 import_notion.py --undo notion-import-undo-<timestamp>.jsonl --env-file /opt/jarvis/.env
tasks rows cannot be deleted by the app user, so undo signs in as the
PocketBase superuser (SUPERUSER_EMAIL / SUPERUSER_PASSWORD). It deletes
only the listed ids that are still Notion imports and prints
{"deleted": n, "skipped": n, "failed": n}. Ids already gone are skipped, so
running undo twice is harmless. A line whose id is not a PocketBase record id
(15 lowercase letters or digits) counts as failed and is never sent. Tasks
you already finished in the app are deleted too. Notion is untouched, so a
later --apply imports again.
8. Clean up¶
shred -u /root/notion-import/notion.env
Then delete the integration in Notion (Integrations › ⋯ › Delete), which revokes the token. Keep the undo file until you are happy with the list.