| name | notion-briefs |
| description | Set up (or repair) the cross-project Notion "Briefs" system in a new environment — install the publisher CLI globally, solicit a Notion integration key, attach to a user-chosen Notion page, create an inline Briefs database, and register the tool in the global CLAUDE.md so every project can publish briefs/shift reports to one shared, newest-first database. Use when the user says "set up Notion briefs," "install the briefs tool here," "I need briefs in this environment," or when a brief publish fails because nothing is configured. |
| metadata | {"version":"1.0.0"} |
Notion Briefs — setup
This skill installs a cross-project brief/shift-report publisher backed by a single Notion database, and wires it into the environment so any project's agent can use it. It is portable: run it once per machine/environment the user works in. The end state mirrors the reference setup — an inline "Briefs" table living on a Notion page the user owns, fed by a project-neutral CLI.
The CLI and a README ship inside this skill (notion_briefs.py, README.md) — they are the source of truth; the install step copies them out to a global location.
What you will end up with
~/.config/ai-briefs/notion_briefs.py — the publisher (stdlib only, no pip deps)
~/.config/ai-briefs/token — the Notion integration secret (chmod 600)
~/.config/ai-briefs/config.json — {"briefs_db": "<id>"}, written by bootstrap
- A "Briefs" inline database on a Notion page the user picks (props: Name, Date, Type, Status, and a Project Overview relation added by
bootstrap-projects)
- A pointer block in the global
~/.claude/CLAUDE.md so every project discovers the tool
Idempotency — check before you act
Before doing anything, check what already exists and only fill gaps:
ls -la ~/.config/ai-briefs/ 2>/dev/null
cat ~/.config/ai-briefs/config.json 2>/dev/null
test -s ~/.config/ai-briefs/token && echo "token present" || echo "need token"
grep -q "ai-briefs/notion_briefs.py" ~/.claude/CLAUDE.md && echo "CLAUDE.md pointer present" || echo "need CLAUDE.md pointer"
If everything is present and notion_briefs.py list works, there's nothing to do — report that and stop. Otherwise do only the missing steps.
Step 1 — Install the CLI globally
Copy the bundled files out of the skill to the global config dir (this dir is intentionally not tied to any repo, so every project shares it):
mkdir -p ~/.config/ai-briefs
cp "<skill-dir>/notion_briefs.py" ~/.config/ai-briefs/notion_briefs.py
cp "<skill-dir>/README.md" ~/.config/ai-briefs/README.md
chmod +x ~/.config/ai-briefs/notion_briefs.py
(<skill-dir> is this skill's own directory — the same folder this SKILL.md lives in.)
Step 2 — Get a Notion integration key (solicit from the user)
Talking to Notion's API requires an internal integration (a Notion "application") and its secret token. If ~/.config/ai-briefs/token is missing or empty, walk the user through creating one — do not invent or guess a key:
To let me publish into Notion, I need an integration key. Please:
- Go to https://www.notion.so/my-integrations → New integration.
- Pick Internal integration, name it (e.g. "AI Briefs"), select your workspace, and submit.
- Copy the Internal Integration Secret (starts with
ntn_… or secret_…) and paste it here.
When the user provides it, write it to a file (never leave it only in chat; tell them they can rotate it later):
umask 077
printf '%s' '<PASTED_SECRET>' > ~/.config/ai-briefs/token
chmod 600 ~/.config/ai-briefs/token
Validate it before continuing:
curl -s https://api.notion.com/v1/users/me \
-H "Authorization: Bearer $(cat ~/.config/ai-briefs/token)" \
-H "Notion-Version: 2022-06-28" | head -c 300
A valid key returns a bot user object; an error means the key is wrong — re-solicit.
Step 3 — Solicit the target page URL (different per environment — NEVER assume)
The page is environment/client-specific. Do not reuse a UUID from another setup or hardcode the reference workspace's page. Always ask the user for the URL of the page in this workspace that the Briefs table should live on.
Where should the Briefs table live? In Notion:
- Create (or pick) a page you own — e.g. call it "AI Briefs".
- Open it → ••• (top-right) → Connections → add your integration (the one whose key you gave me). This is mandatory — Notion only lets an integration see pages explicitly connected to it, and it cannot be scripted.
- Copy the page URL (Share → Copy link) and paste it here.
Verify the integration can actually see that page before bootstrapping (catches the "forgot to connect" case):
PAGE_ID=$(python3 -c "import re,sys;print(re.search(r'[0-9a-fA-F]{32}', sys.argv[1].replace('-','')).group())" '<PASTED_PAGE_URL>')
curl -s "https://api.notion.com/v1/pages/$PAGE_ID" \
-H "Authorization: Bearer $(cat ~/.config/ai-briefs/token)" \
-H "Notion-Version: 2022-06-28" | head -c 300
If it returns object_not_found, the integration isn't connected to the page yet — ask the user to do the Connections step, then retry. Do not proceed past this gate.
Step 4 — Bootstrap the inline database
python3 ~/.config/ai-briefs/notion_briefs.py bootstrap --parent '<PASTED_PAGE_URL>'
python3 ~/.config/ai-briefs/notion_briefs.py bootstrap-projects
The first command creates the "Briefs" database inline (a live table on the page), saves its id to config.json, and prints the database URL. Properties: Name · Date · Type (Brief / Shift Report / Plan — two distinct lifecycles share this DB, so the type matters) · Status (In Progress / Ready / Superseded / Done — the brief lifecycle, see README) · Project Overview (a relation to the Project Overviews DB, added by bootstrap-projects; the legacy free-text Project select has been retired). New Type/Status names and Project Overview pages auto-register on write, so new projects need no schema change. bootstrap-projects is required — publish hard-fails without a configured projects_db.
Tell the user two UI-only tweaks they may want (the API can't set view sort/filter): set the table's default Sort → Date, Descending so newest floats to top, and add per-project filtered views as they accumulate.
Step 5 — Register the tool in the global CLAUDE.md
So every project's agent discovers the tool without being told, ensure ~/.claude/CLAUDE.md contains a pointer. If the grep in the idempotency check showed it missing, append this section:
## Briefs & shift reports go to Notion (cross-project)
Briefs, shift reports, and plans for **any** project publish to one shared Notion "Briefs" database via the project-neutral CLI at **`~/.config/ai-briefs/notion_briefs.py`** (see that dir's `README.md`). One database, newest-first, tagged by `--project`; new project names auto-register, so this works the same from every repo without per-project setup. Prefer this over leaving a local `test-plans/*.md` brief. Publish at the end of a graveyard shift or a substantive session, then hand me the page URL — that page becomes where we iterate on the brief. The token lives in `~/.config/ai-briefs/token` (never paste it in chat). **If the tool isn't configured here** (no token / no `config.json`), run the **notion-briefs** setup skill — solicit a Notion integration key and the target page URL first.
Step 6 — Verify end-to-end
Prove the chain before declaring done — don't hand back an unverified setup:
printf '# Setup smoke test\n\nNotion Briefs configured in this environment.\n' > /tmp/brief-smoke.md
python3 ~/.config/ai-briefs/notion_briefs.py publish \
--project "Setup" --date "$(date +%F)" --title "Setup smoke test" \
--type Brief --status Ready --file /tmp/brief-smoke.md
python3 ~/.config/ai-briefs/notion_briefs.py list
Hand the user the database/page URL and tell them the smoke-test row is safe to delete. If date isn't desirable, pass an explicit --date YYYY-MM-DD.
Daily use (after setup)
python3 ~/.config/ai-briefs/notion_briefs.py projects
python3 ~/.config/ai-briefs/notion_briefs.py publish \
--project "<Project>" --date YYYY-MM-DD --title "Shift Report YYYY-MM-DD" \
--type "Shift Report" --status "In Progress" --file report.md
python3 ~/.config/ai-briefs/notion_briefs.py list [--project "<Project>"] [--status Ready]
python3 ~/.config/ai-briefs/notion_briefs.py append <url> --file closeout.md --divider
python3 ~/.config/ai-briefs/notion_briefs.py set-status <url> --status Superseded
python3 ~/.config/ai-briefs/notion_briefs.py set-status <url> --status Done
--type is required on every publish — Brief (a durable, multi-shift strategy/topic doc that gets decomposed into a GitHub epic, then retires) vs Shift Report (a per-shift execution record) vs Plan. An untyped row hides the distinction and is a bug.
Structure every report decisions-first. The reader opens it to answer "what needs a call from me?" — so the open 🟥/🟢 decisions go at the very TOP, above the fold, before any shipped-log or status recap. Never bury a decision below done-work. Full rule in README.md → Structure: lead with the decisions.
The brief is a living lifecycle object with four states: In Progress → Ready → (Superseded | Done). At a shift START, flip the prior shift report Ready → Superseded and create the new report In Progress (the plan); at shift END flip In Progress → Ready. A report stuck In Progress = a shift that may have died — a signal to pick it back up. When retiring, choose by where the work went: Superseded = replaced by a newer doc OR a Brief that graduated into a tracked epic (the work lives on elsewhere); Done = genuinely finished, nothing carried forward. The graveyard-shift skill drives the boundary transitions; the shift-change skill closes out. Full lifecycle + Type taxonomy in README.md.
One brief needing attention per project — the listing points, it does not archive. The "needs your eyes now" state (Ready — the canonical review-awaiting status for both briefs and shift reports) is not a bucket of every live document; it is a pointer to the single brief you want the user's attention on right now. This is the listing's core job. Whenever you publish a brief that becomes the current decision surface, retire the prior one in the same breath (Superseded if its work lives on elsewhere, Done if finished). Whenever an older brief's conclusions have already been captured elsewhere (a newer brief, the Project Overview, or GitHub issues), retire it — its job is done. Before ending any turn that touched briefs, run list --status Ready --project "<P>" and confirm exactly one thing is asking for attention. A listing with several attention-state rows is a failure, not thoroughness: it makes the user hunt for the decision instead of being handed it. If two genuinely compete, that is a signal to merge them into one decision-ready brief, not to leave both live.
GitHub auto-linking (publish + append)
Bare issue/PR refs like #308 in the markdown are auto-linked to GitHub so the user can click the number straight into the issue/PR — no manual linking needed. The repo is auto-detected from the cwd's git remote (so just run the publish from inside the project repo); override with --github-repo owner/repo if publishing from elsewhere. GitHub's /issues/<n> URL redirects to /pull/<n> for PRs, so issues and PRs both link correctly without distinguishing them. Refs inside `backticks` are left alone, and explicit [text](url) markdown links render as clickable links too. Write reports with plain #NNN — the tooling handles the linking.
Notes / gotchas
- Internal integrations cannot create workspace-level/root pages — the database must nest under an existing page the user shared with the integration (Step 3). That's why we solicit a page URL instead of making one.
- The Connections step (Step 3.2) is the one thing that can't be scripted. Most setup failures are a forgotten connection — the page verify in Step 3 catches it.
- Token hygiene: the secret lives only in
~/.config/ai-briefs/token (chmod 600). If it ever leaks into a transcript, the user can rotate it at notion.so/my-integrations and overwrite the file.
- The CLI is stdlib-only (urllib + json) and honors a
NOTION_TOKEN env override if set, else the token file.