notion-agent-cli 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. notion_agent_cli-0.1.1/.claude/skills/notion-agent/SKILL.md +132 -0
  2. notion_agent_cli-0.1.1/.github/workflows/release.yml +46 -0
  3. notion_agent_cli-0.1.1/.github/workflows/test.yml +30 -0
  4. notion_agent_cli-0.1.1/.gitignore +35 -0
  5. notion_agent_cli-0.1.1/LICENSE +21 -0
  6. notion_agent_cli-0.1.1/PKG-INFO +245 -0
  7. notion_agent_cli-0.1.1/README.md +208 -0
  8. notion_agent_cli-0.1.1/docs/01-notion-chat-protocol.md +525 -0
  9. notion_agent_cli-0.1.1/docs/02-architecture.md +95 -0
  10. notion_agent_cli-0.1.1/docs/AGENTS.md +259 -0
  11. notion_agent_cli-0.1.1/docs/NEXT-SESSION.md +172 -0
  12. notion_agent_cli-0.1.1/docs/RELEASING.md +91 -0
  13. notion_agent_cli-0.1.1/docs/ROADMAP.md +147 -0
  14. notion_agent_cli-0.1.1/docs/STATUS.md +245 -0
  15. notion_agent_cli-0.1.1/pyproject.toml +81 -0
  16. notion_agent_cli-0.1.1/src/notion_agent_cli/__init__.py +28 -0
  17. notion_agent_cli-0.1.1/src/notion_agent_cli/account.py +141 -0
  18. notion_agent_cli-0.1.1/src/notion_agent_cli/agents.py +129 -0
  19. notion_agent_cli-0.1.1/src/notion_agent_cli/bootstrap.py +253 -0
  20. notion_agent_cli-0.1.1/src/notion_agent_cli/cli/__init__.py +6 -0
  21. notion_agent_cli-0.1.1/src/notion_agent_cli/cli/__main__.py +764 -0
  22. notion_agent_cli-0.1.1/src/notion_agent_cli/exceptions.py +47 -0
  23. notion_agent_cli-0.1.1/src/notion_agent_cli/models.py +167 -0
  24. notion_agent_cli-0.1.1/src/notion_agent_cli/ndjson.py +301 -0
  25. notion_agent_cli-0.1.1/src/notion_agent_cli/profile.py +196 -0
  26. notion_agent_cli-0.1.1/src/notion_agent_cli/provider.py +550 -0
  27. notion_agent_cli-0.1.1/src/notion_agent_cli/serve.py +318 -0
  28. notion_agent_cli-0.1.1/src/notion_agent_cli/thread_state.py +73 -0
  29. notion_agent_cli-0.1.1/src/notion_agent_cli/transcript.py +310 -0
  30. notion_agent_cli-0.1.1/src/notion_agent_cli/types.py +27 -0
  31. notion_agent_cli-0.1.1/tests/__init__.py +0 -0
  32. notion_agent_cli-0.1.1/tests/fixtures/get_available_models.json +282 -0
  33. notion_agent_cli-0.1.1/tests/fixtures/get_custom_agents.json +273 -0
  34. notion_agent_cli-0.1.1/tests/test_agents.py +224 -0
  35. notion_agent_cli-0.1.1/tests/test_bootstrap.py +297 -0
  36. notion_agent_cli-0.1.1/tests/test_cli_doctor.py +52 -0
  37. notion_agent_cli-0.1.1/tests/test_cli_init.py +74 -0
  38. notion_agent_cli-0.1.1/tests/test_error_codes.py +305 -0
  39. notion_agent_cli-0.1.1/tests/test_models_refresh.py +253 -0
  40. notion_agent_cli-0.1.1/tests/test_notion_agent_cli.py +911 -0
  41. notion_agent_cli-0.1.1/tests/test_profile.py +220 -0
  42. notion_agent_cli-0.1.1/tests/test_serve.py +330 -0
  43. notion_agent_cli-0.1.1/tests/test_thread_state.py +90 -0
@@ -0,0 +1,132 @@
1
+ ---
2
+ name: notion-agent
3
+ description: Ask Notion's ✦ AI / Custom Agent (e.g. Jarvis) via the local `notion-agent` CLI. Use when the user wants a workspace-aware answer from their bound Notion agent, wants to pipe a draft through Notion AI, list their agents / threads, or continue a prior thread. Trigger phrases include "ask Jarvis", "Notion AI", "via the chat panel", "notion-agent", "Custom Agent".
4
+ ---
5
+
6
+ ## When to use
7
+
8
+ - The user wants an answer that depends on **their Notion workspace**
9
+ (their notes, their Custom Agent's instructions / skill pages) and a
10
+ public Claude can't see that context.
11
+ - The user mentions a Custom Agent by name ("Jarvis", "the chat panel
12
+ one", "my Notion agent") — that's a workspace-bound persona, not a
13
+ general assistant.
14
+ - The user wants to dump a draft into Notion's ✦ AI for them to react
15
+ to inside Notion (the conversation surfaces in the chat panel under
16
+ the bound persona).
17
+
18
+ Skip this skill for general programming / world-knowledge questions —
19
+ answer those directly. Notion's chat costs the user API quota.
20
+
21
+ ## Quick start
22
+
23
+ ```bash
24
+ # One-shot chat — prints the assistant reply to stdout
25
+ notion-agent chat "summarize my last week of project notes"
26
+
27
+ # Streaming output for long replies
28
+ notion-agent chat --stream "draft a status update from this week's notes"
29
+
30
+ # Structured JSON (text + usage + thread_id) for piping into other tools
31
+ notion-agent chat --json "what tickets did I close in May?"
32
+ ```
33
+
34
+ Check `notion-agent --help` for the full subcommand list. Six
35
+ subcommands: `init`, `chat`, `doctor`, `models refresh`, `agents list`,
36
+ `threads list`.
37
+
38
+ ## Pre-flight
39
+
40
+ Before the first call, the user needs an account file at
41
+ `~/.notionagents/notion_account.json`. If chat errors with
42
+ `[account_missing]`:
43
+
44
+ ```bash
45
+ # Paste the full document.cookie string from a logged-in Notion tab
46
+ notion-agent init --cookie "token_v2=...; notion_user_id=...; ..."
47
+ ```
48
+
49
+ `init` auto-extracts `token_v2` / `notion_user_id` / `notion_browser_id`
50
+ from the pasted cookie string. Add `--agent-name "Jarvis"
51
+ --agent-page-id <uuid>` to bind to a Custom Agent so threads surface in
52
+ the chat panel.
53
+
54
+ If chat errors with `[auth_invalid]`, the `token_v2` cookie expired —
55
+ re-run `init` with a fresh one.
56
+
57
+ ## Multi-turn continuation
58
+
59
+ ```bash
60
+ # Turn 1 — get the thread_id from --json output
61
+ THREAD=$(notion-agent chat --json "start a brainstorm on X" | jq -r .thread_id)
62
+
63
+ # Turn 2+ — pass --thread-id to continue
64
+ notion-agent chat --thread-id "$THREAD" "now narrow down to 3 ideas"
65
+ notion-agent chat --thread-id "$THREAD" "expand idea 1"
66
+ ```
67
+
68
+ State is stored under `~/.notionagents/threads/<thread-id>.json`.
69
+ The thread's original model is locked — `--model` is ignored on
70
+ continuation. If state is missing the error is `[thread_state_missing]`
71
+ and the user should drop `--thread-id` to start fresh.
72
+
73
+ ## Looking up agent / thread ids
74
+
75
+ ```bash
76
+ notion-agent agents list --limit 10 # 19-ish agents, page_id + activity
77
+ notion-agent threads list --limit 10 # recent threads, newest first
78
+ notion-agent threads list --agent <id> # threads under one agent
79
+ ```
80
+
81
+ JSON variants (`--json`) for both. Use `agents list` when the user asks
82
+ to bind a new Custom Agent during `init` — the page_id column is what
83
+ goes into `--agent-page-id`.
84
+
85
+ ## Troubleshooting
86
+
87
+ ```bash
88
+ notion-agent doctor # human-readable 6-step report
89
+ notion-agent doctor --json # structured, for cron / automation
90
+ ```
91
+
92
+ `doctor` checks: account file readable, required fields, Custom Agent
93
+ binding (informational), `/loadUserContent` ping, bound space_id still
94
+ in workspace list, user model map present. Each line tagged
95
+ `[ok] / [FAIL] / [..]`.
96
+
97
+ If Notion rotates internal model ids (the `apricot-sorbet-high` style):
98
+
99
+ ```bash
100
+ notion-agent models refresh # writes ~/.notionagents/models.json
101
+ ```
102
+
103
+ ## Don't
104
+
105
+ - **Don't paste real `token_v2` values into prompts** — `init` already
106
+ bound it, and the CLI handles auth transparently. The token is
107
+ long-lived (~months); only re-run `init` when `doctor` says
108
+ `[auth_invalid]`.
109
+ - **Don't loop more than ~3 chats per user task** — each call is a real
110
+ Notion API hit consuming the operator's quota. If a batch run is
111
+ unavoidable, ask the user first.
112
+ - **Don't write to `~/.notionagents/`** directly — the CLI owns it.
113
+ Account file, models map, and thread state are all CLI-managed.
114
+ - **Don't switch `--model` mid-thread** — the flag is silently ignored
115
+ on `--thread-id` continuations because Notion locks the model to
116
+ turn 1. If the user wants a different model, start a new thread.
117
+
118
+ ## ErrorCode reference (for `--json` consumers)
119
+
120
+ | Code | Meaning | Action |
121
+ |---|---|---|
122
+ | `account_missing` | No file at `~/.notionagents/notion_account.json` | Run `init` |
123
+ | `account_invalid` | File present but missing required fields | Re-run `init` |
124
+ | `auth_invalid` | 401/403 — `token_v2` expired or rejected | Re-run `init` with fresh cookie |
125
+ | `thread_state_missing`| `--thread-id` used but no saved state | Drop `--thread-id` to start fresh |
126
+ | `premium_required` | Notion premium feature gated | Tell the user their plan blocks this |
127
+ | `empty_text` | 200 but empty stream | Likely transient — retry once |
128
+ | `http_error` | Non-200 from Notion | Likely transient — retry once |
129
+ | `transport` | Network failure | Check connectivity |
130
+
131
+ `NotionAgentError.code` is a `StrEnum` — branching on the literal
132
+ string is fine.
@@ -0,0 +1,46 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ build:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: '3.11'
19
+ - name: Install build tooling
20
+ run: python -m pip install --upgrade pip build twine
21
+ - name: Build sdist + wheel
22
+ run: python -m build
23
+ - name: Verify metadata + README rendering
24
+ run: twine check --strict dist/*
25
+ - uses: actions/upload-artifact@v4
26
+ with:
27
+ name: dist
28
+ path: dist/
29
+
30
+ publish:
31
+ needs: build
32
+ runs-on: ubuntu-latest
33
+ # Must match the Pending Publisher environment configured on PyPI.
34
+ # Configure required reviewers on this environment in GitHub repo
35
+ # settings to gate the upload step.
36
+ environment:
37
+ name: pypi
38
+ url: https://pypi.org/project/notion-agent-cli/
39
+ permissions:
40
+ id-token: write # OIDC token for Trusted Publishing
41
+ steps:
42
+ - uses: actions/download-artifact@v4
43
+ with:
44
+ name: dist
45
+ path: dist/
46
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,30 @@
1
+ name: Test
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ test:
14
+ runs-on: ubuntu-latest
15
+ strategy:
16
+ fail-fast: false
17
+ matrix:
18
+ python-version: ['3.11', '3.12']
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ - uses: actions/setup-python@v5
22
+ with:
23
+ python-version: ${{ matrix.python-version }}
24
+ cache: pip
25
+ - name: Install (with dev extra)
26
+ run: python -m pip install -e ".[dev]"
27
+ - name: Lint
28
+ run: ruff check src tests
29
+ - name: Test
30
+ run: pytest -q
@@ -0,0 +1,35 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ *.egg-info/
7
+ .eggs/
8
+ build/
9
+ dist/
10
+ .pytest_cache/
11
+ .ruff_cache/
12
+ .mypy_cache/
13
+ .coverage
14
+ htmlcov/
15
+
16
+ # Virtualenvs
17
+ .venv/
18
+ venv/
19
+ env/
20
+
21
+ # IDE
22
+ .vscode/
23
+ .idea/
24
+ *.swp
25
+
26
+ # OS
27
+ .DS_Store
28
+ Thumbs.db
29
+
30
+ # Notion CLI artifacts (real credentials — never commit)
31
+ notion_account.json
32
+ .notion-agent/
33
+
34
+ # Local session state (oh-my-claudecode, etc.)
35
+ .omc/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 chenyqthu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,245 @@
1
+ Metadata-Version: 2.4
2
+ Name: notion-agent-cli
3
+ Version: 0.1.1
4
+ Summary: Call Notion's reverse-engineered ✦ AI / Custom Agent endpoint from the command line
5
+ Project-URL: Homepage, https://github.com/chenyqthu/notion-agent-cli
6
+ Project-URL: Source, https://github.com/chenyqthu/notion-agent-cli
7
+ Project-URL: Issues, https://github.com/chenyqthu/notion-agent-cli/issues
8
+ Project-URL: Changelog, https://github.com/chenyqthu/notion-agent-cli/releases
9
+ Author: chenyqthu
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: ai,cli,custom-agent,jarvis,notion
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: MacOS
18
+ Classifier: Operating System :: POSIX :: Linux
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Communications :: Chat
22
+ Classifier: Topic :: Office/Business
23
+ Requires-Python: >=3.11
24
+ Requires-Dist: brotli>=1.1
25
+ Requires-Dist: httpx>=0.27
26
+ Provides-Extra: dev
27
+ Requires-Dist: fastapi>=0.110; extra == 'dev'
28
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
29
+ Requires-Dist: pytest-timeout>=2.3; extra == 'dev'
30
+ Requires-Dist: pytest>=8.0; extra == 'dev'
31
+ Requires-Dist: ruff>=0.4; extra == 'dev'
32
+ Requires-Dist: uvicorn[standard]>=0.27; extra == 'dev'
33
+ Provides-Extra: serve
34
+ Requires-Dist: fastapi>=0.110; extra == 'serve'
35
+ Requires-Dist: uvicorn[standard]>=0.27; extra == 'serve'
36
+ Description-Content-Type: text/markdown
37
+
38
+ # notion-agent-cli
39
+
40
+ > 把你的 Notion ✦ AI / Custom Agent 变成一条 shell 命令。
41
+
42
+ A small Python CLI + library that talks **directly** to Notion's internal
43
+ `POST /api/v3/runInferenceTranscript` endpoint using a `token_v2` cookie
44
+ copied from a logged-in browser session. No `notion_manager` Go proxy, no
45
+ account pool — just your own session.
46
+
47
+ The conversations show up in Notion's ✦ AI chat panel under whichever
48
+ Custom Agent persona you bind (e.g. Jarvis), so you can switch between
49
+ talking to your agent from Notion's UI and from the shell.
50
+
51
+ ## Status
52
+
53
+ **v0.1.0 — first tagged release.** The library is live-validated against
54
+ Notion's real endpoint and adopted by at least one downstream project
55
+ ([NotionAgents](https://github.com/chenyqthu/NotionAgents) uses
56
+ `NotionAgentClient` as its inference backend). See
57
+ [`docs/STATUS.md`](docs/STATUS.md) for the full live-test ledger and
58
+ [`docs/ROADMAP.md`](docs/ROADMAP.md) for what's still planned.
59
+
60
+ ## Install
61
+
62
+ ```bash
63
+ pipx install notion-agent-cli # recommended — isolated venv, one-line upgrade
64
+ # or
65
+ pip install notion-agent-cli # if you don't use pipx
66
+ ```
67
+
68
+ For the optional FastAPI HTTP wrapper (`notion-agent serve`):
69
+
70
+ ```bash
71
+ pipx install 'notion-agent-cli[serve]'
72
+ ```
73
+
74
+ For local development from a clone, `pip install -e ".[dev]"`. The
75
+ release flow is in [`docs/RELEASING.md`](docs/RELEASING.md).
76
+
77
+ ## Quickstart
78
+
79
+ ```bash
80
+ # One-shot setup: paste the full `document.cookie` string copied from
81
+ # a logged-in Notion tab (DevTools → Application → Cookies → notion.so,
82
+ # right-click any row → "Copy all as ..."). The CLI extracts token_v2 /
83
+ # notion_user_id / notion_browser_id, calls /api/v3/loadUserContent to
84
+ # fetch workspace metadata, and picks the (only) workspace for you.
85
+ notion-agent init --cookie "notion_browser_id=...; notion_user_id=...; token_v2=v03%3A..."
86
+
87
+ # Want to keep the cookie out of shell history? Read it from stdin:
88
+ pbpaste | notion-agent init --cookie -
89
+
90
+ # Chat (returns the full reply after streaming completes).
91
+ notion-agent chat "用一句话确认你在线。"
92
+ ```
93
+
94
+ For Jarvis-style custom agent binding (so threads surface in the ✦ AI
95
+ chat panel under your agent name), grab the agent's instructions page id
96
+ from its URL and pass it to `init`:
97
+
98
+ ```bash
99
+ notion-agent init --cookie "..." \
100
+ --agent-name Jarvis \
101
+ --agent-page-id 2e115375-830d-8184-bf4d-f427d847c6bc
102
+ ```
103
+
104
+ ## CLI surface
105
+
106
+ ```text
107
+ notion-agent init Bootstrap notion_account.json from a token_v2 cookie.
108
+ notion-agent chat Send one prompt, print the reply (text / --json / --stream / --ndjson).
109
+ notion-agent doctor Validate the account file and ping /loadUserContent.
110
+ notion-agent models refresh — pull the current alias→Notion-id map.
111
+ notion-agent agents list — custom agents in the bound workspace.
112
+ notion-agent threads list — recent chat threads (filter by --agent).
113
+ notion-agent profile list / current / use <name> / migrate <name>
114
+ notion-agent serve Local FastAPI server (optional [serve] extra).
115
+ ```
116
+
117
+ Run `notion-agent <sub> --help` for flag details. Every subcommand
118
+ accepts `--account PATH` to point at a non-default credential file.
119
+
120
+ ## Continuation (multi-turn chats)
121
+
122
+ `chat` saves per-thread continuation state under
123
+ `~/.notionagents/threads/<thread-id>.json` after every successful turn,
124
+ so the next turn can be a follow-up in the same chat-panel thread:
125
+
126
+ ```bash
127
+ # Turn 1 — capture the thread id.
128
+ THREAD=$(notion-agent chat --json "summarize my day so far" | jq -r .thread_id)
129
+
130
+ # Turn 2 — reuse it.
131
+ notion-agent chat --thread-id "$THREAD" "now translate it to chinese"
132
+ ```
133
+
134
+ The thread's model is locked to whatever turn 1 chose — passing
135
+ `--model` on a `--thread-id` continuation is silently ignored, since
136
+ mid-thread model switches occasionally make Notion reject the partial
137
+ transcript.
138
+
139
+ ## Multi-workspace profiles
140
+
141
+ Run `notion-agent init` once per workspace into a named profile file,
142
+ then swap between them with `profile use`:
143
+
144
+ ```bash
145
+ notion-agent init --cookie "..." --account ~/.notionagents/tplink.json
146
+ notion-agent init --cookie "..." --account ~/.notionagents/personal.json
147
+ notion-agent profile use tplink # symlinks notion_account.json → tplink.json
148
+ notion-agent profile current # prints "tplink"
149
+ notion-agent profile list # both, * marks active
150
+ ```
151
+
152
+ If you started before profiles existed, `profile migrate <name>` renames
153
+ your existing `notion_account.json` into a named profile and replaces
154
+ the original with a symlink — non-destructive, no re-init required.
155
+
156
+ ## Local HTTP server
157
+
158
+ `notion-agent serve` wraps the CLI in a small FastAPI app for
159
+ orchestrators (n8n, Make, internal Go services) that prefer HTTP over
160
+ shell-out. Install with the `[serve]` extra:
161
+
162
+ ```bash
163
+ pip install -e ".[serve]"
164
+ notion-agent serve --host 127.0.0.1 --port 8000
165
+
166
+ # In another shell:
167
+ curl -sN -X POST localhost:8000/chat \
168
+ -H 'content-type: application/json' \
169
+ -d '{"prompt":"hi","stream":true}' # SSE; data: {"text":"..."} per chunk
170
+ curl -s localhost:8000/healthz
171
+ curl -s localhost:8000/agents
172
+ ```
173
+
174
+ Endpoints: `POST /chat` (blocking JSON or SSE), `GET /healthz` (doctor's
175
+ checks, 200 / 503), `GET /agents`, `GET /threads`. Error codes match
176
+ the library's [`ErrorCode`](src/notion_agent_cli/exceptions.py) taxonomy
177
+ so callers branch on `code` without parsing messages.
178
+
179
+ ## Calling this from an AI agent
180
+
181
+ Two paths, pick the one that matches your runtime:
182
+
183
+ - **Claude Code** — the repo ships a skill at
184
+ [`.claude/skills/notion-agent/`](.claude/skills/notion-agent/). Open
185
+ this repo (or a downstream project that depends on the CLI) and the
186
+ skill auto-loads via its frontmatter trigger phrases ("ask Jarvis",
187
+ "Notion AI", "via the chat panel").
188
+ - **Anything else** (Codex, Gemini, openclaw, your own orchestrator) —
189
+ read [`docs/AGENTS.md`](docs/AGENTS.md). It has a paste-ready
190
+ system-prompt snippet at the bottom covering the CLI contract,
191
+ preflight, error codes, and safety rules in ~40 lines.
192
+
193
+ Both paths shell out to the same `notion-agent` binary — make sure it
194
+ is on `$PATH` for whatever runtime you use.
195
+
196
+ ## Why this exists
197
+
198
+ Notion's Custom Agents went paid in May 2026 ($10 / 1k credits,
199
+ 30–60 credits per run, Business+ only). Their built-in ✦ AI chat is
200
+ free for any Plus seat but only addressable from the UI — not from a
201
+ cron job, a webhook, or a shell pipe. This CLI bridges that gap by
202
+ mirroring exactly what the SPA sends, so you get the same
203
+ free-quota model from `bash` / `python` / wherever.
204
+
205
+ Reverse-engineering notes + the captured chat-panel traffic that this
206
+ implementation is built against live in
207
+ [`docs/01-notion-chat-protocol.md`](docs/01-notion-chat-protocol.md).
208
+
209
+ ## Library use
210
+
211
+ ```python
212
+ import asyncio
213
+ from notion_agent_cli import NotionAgentClient
214
+
215
+ async def main():
216
+ client = NotionAgentClient("~/.notionagents/notion_account.json")
217
+ try:
218
+ resp = await client.complete(prompt="今天周几?")
219
+ print(resp.text)
220
+ print(f"usage: in={resp.usage.input_tokens} out={resp.usage.output_tokens}")
221
+
222
+ # Reuse the thread for a follow-up:
223
+ followup = await client.complete(
224
+ prompt="and yesterday?", thread_id=resp.thread_id,
225
+ )
226
+ print(followup.text)
227
+ finally:
228
+ await client.aclose()
229
+
230
+ asyncio.run(main())
231
+ ```
232
+
233
+ Async streaming consumers can pass `on_text_delta=` (sync) or
234
+ `on_text_delta_async=` (coroutine, awaited per chunk — used by the
235
+ FastAPI SSE branch). For raw NDJSON passthrough use
236
+ `client.stream_lines(...)`.
237
+
238
+ The public surface kept stable across v0.1: `NotionAgentClient`,
239
+ `NotionAgentError`, `ChatResponse`, `TokenUsage`, `ErrorCode`. Sub-modules
240
+ (`transcript`, `ndjson`, `account`, `models`, `profile`, `serve`) are
241
+ importable but their internals can shift between minor releases.
242
+
243
+ ## License
244
+
245
+ MIT. See [`LICENSE`](LICENSE).