ntion 0.1.0

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 (42) hide show
  1. package/README.md +204 -0
  2. package/dist/audit/log.d.ts +10 -0
  3. package/dist/audit/log.js +18 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +939 -0
  6. package/dist/commands/context.d.ts +17 -0
  7. package/dist/commands/context.js +35 -0
  8. package/dist/commands/mutation.d.ts +8 -0
  9. package/dist/commands/mutation.js +102 -0
  10. package/dist/commands/output.d.ts +7 -0
  11. package/dist/commands/output.js +22 -0
  12. package/dist/concurrency/versioning.d.ts +1 -0
  13. package/dist/concurrency/versioning.js +5 -0
  14. package/dist/config/paths.d.ts +5 -0
  15. package/dist/config/paths.js +26 -0
  16. package/dist/config/store.d.ts +5 -0
  17. package/dist/config/store.js +72 -0
  18. package/dist/config/types.d.ts +141 -0
  19. package/dist/config/types.js +26 -0
  20. package/dist/contracts/envelope.d.ts +32 -0
  21. package/dist/contracts/envelope.js +35 -0
  22. package/dist/errors/cli-error.d.ts +11 -0
  23. package/dist/errors/cli-error.js +59 -0
  24. package/dist/errors/codes.d.ts +2 -0
  25. package/dist/errors/codes.js +9 -0
  26. package/dist/idempotency/store.d.ts +34 -0
  27. package/dist/idempotency/store.js +120 -0
  28. package/dist/notion/client.d.ts +17 -0
  29. package/dist/notion/client.js +140 -0
  30. package/dist/notion/mappers.d.ts +5 -0
  31. package/dist/notion/mappers.js +224 -0
  32. package/dist/notion/markdown.d.ts +1 -0
  33. package/dist/notion/markdown.js +237 -0
  34. package/dist/notion/properties.d.ts +7 -0
  35. package/dist/notion/properties.js +267 -0
  36. package/dist/notion/repository.d.ts +142 -0
  37. package/dist/notion/repository.js +998 -0
  38. package/dist/notion/types.d.ts +4 -0
  39. package/dist/notion/types.js +1 -0
  40. package/dist/utils/json.d.ts +3 -0
  41. package/dist/utils/json.js +32 -0
  42. package/package.json +34 -0
package/README.md ADDED
@@ -0,0 +1,204 @@
1
+ # notion-cli
2
+
3
+ Token-efficient Notion CLI built for AI agents like Claude Code and Codex.
4
+
5
+ One command where the official MCP needs twelve. Compact JSON where others dump kilobytes of noise. Zero native dependencies — runs instantly via `npx`.
6
+
7
+ ## Why ntion over the official Notion MCP?
8
+
9
+ The official Notion MCP server works, but it was designed for general-purpose access, not for token-constrained AI agents. Every extra byte in a response eats into your context window. Every extra round-trip adds latency and cost.
10
+
11
+ I benchmarked both tools side-by-side on real workflows:
12
+
13
+ ### Individual operations
14
+
15
+ | Operation | ntion | MCP | Ratio |
16
+ |---|---|---|---|
17
+ | Search (tasks, pages only) | 743 B | 1,019 B | **1.4x smaller** |
18
+ | Schema (Tasks DB) | 1,868 B | 6,026 B | **3.2x smaller** |
19
+ | Page properties only | 1,004 B | n/a | ntion-only |
20
+ | Page + content (markdown) | 1,227 B | 1,263 B | ~1.0x (tie) |
21
+
22
+ ### The real difference: workflows
23
+
24
+ A single "get all my tasks" workflow tells the whole story:
25
+
26
+ | Scale | ntion | MCP |
27
+ |---|---|---|
28
+ | 1 task | **1,938 B, 1 call** | 8,308 B, 3 calls (4.3x) |
29
+ | 10 tasks | **~5 KB, 1 call** | ~19.6 KB, 12 calls (3.9x) |
30
+ | 50 tasks | **~20 KB, 1 call** | ~70 KB, 52 calls (3.5x) |
31
+
32
+ ### Where the savings come from
33
+
34
+ 1. **Batch queries** — ntion returns N records in 1 call. The MCP requires 1 fetch per page. This is the dominant factor and it scales linearly.
35
+ 2. **No schema bloat** — MCP's database fetch includes ~2 KB of SQLite DDL, ~800 B of XML boilerplate, and ~1.4 KB of base64 `collectionPropertyOption://` URLs that are never used for reads. ntion returns only actionable data.
36
+ 3. **Markdown-first** — Page content defaults to markdown, matching what agents actually consume. No manual format negotiation needed.
37
+
38
+ ## Install
39
+
40
+ ```bash
41
+ npm install -g ntion
42
+ ```
43
+
44
+ Or run directly without installing:
45
+
46
+ ```bash
47
+ npx ntion --help
48
+ ```
49
+
50
+ No native compilation, no C++ toolchain required — installs in seconds.
51
+
52
+ ## Quick start
53
+
54
+ ### 1. Authenticate
55
+
56
+ Create an integration and grab your API key at [notion.so/profile/integrations](https://www.notion.so/profile/integrations).
57
+
58
+ ```bash
59
+ ntion auth
60
+ # Paste your Notion integration token when prompted — done.
61
+
62
+ # Or pass it directly (e.g. in scripts):
63
+ ntion auth --token "secret_xxx"
64
+
65
+ # CI alternative — read token from an environment variable:
66
+ ntion auth --token-env NOTION_API_KEY
67
+ ```
68
+
69
+ ### 2. Go
70
+
71
+ ```bash
72
+ # Find your databases
73
+ ntion data-sources list --query "tasks"
74
+
75
+ # Query all tasks in one call
76
+ ntion data-sources query --id <data_source_id> --view full
77
+
78
+ # Read a page with its content as markdown
79
+ ntion pages get --id <page_id> --include-content
80
+
81
+ # Search across your workspace
82
+ ntion search --query "release notes" --limit 25
83
+ ```
84
+
85
+ ## Commands
86
+
87
+ ### Search
88
+
89
+ ```bash
90
+ ntion search --query "release notes" --limit 25
91
+ ntion search --query "infra" --object page --created-after 2026-01-01T00:00:00Z
92
+ ntion search --query "oncall" --scope <page_or_data_source_id> --created-by <user_id>
93
+ ```
94
+
95
+ ### Data sources
96
+
97
+ ```bash
98
+ ntion data-sources list --query "tasks"
99
+ ntion data-sources get --id <data_source_id> --view full
100
+ ntion data-sources schema --id <data_source_id>
101
+ ntion data-sources query --id <data_source_id> \
102
+ --filter-json '{"property":"Status","status":{"equals":"In Progress"}}'
103
+ ```
104
+
105
+ ### Pages
106
+
107
+ ```bash
108
+ ntion pages get --id <page_id>
109
+ ntion pages get --id <page_id> --include-content --content-format markdown
110
+
111
+ ntion pages create \
112
+ --parent-data-source-id <data_source_id> \
113
+ --properties-json '{"Name":"Ship CLI","Status":"In Progress"}'
114
+
115
+ ntion pages create-bulk \
116
+ --parent-data-source-id <data_source_id> \
117
+ --items-json '[{"properties":{"Name":"Task A"}},{"properties":{"Name":"Task B"}}]' \
118
+ --concurrency 5
119
+
120
+ ntion pages update --id <page_id> --patch-json '{"Status":"Done"}'
121
+ ntion pages archive --id <page_id>
122
+ ntion pages unarchive --id <page_id>
123
+
124
+ ntion pages relate --from-id <page_id> --property Project --to-id <page_id>
125
+ ntion pages unrelate --from-id <page_id> --property Project --to-id <page_id>
126
+ ```
127
+
128
+ ### Blocks
129
+
130
+ ```bash
131
+ # Read as markdown (default)
132
+ ntion blocks get --id <page_or_block_id> --depth 1
133
+
134
+ # Append markdown content
135
+ ntion blocks append --id <page_or_block_id> --markdown "# Title\n\nHello"
136
+ ntion blocks append --id <page_or_block_id> --markdown-file ./notes.md
137
+
138
+ # Surgical insertion
139
+ ntion blocks insert --parent-id <page_or_block_id> --markdown "New intro" --position start
140
+ ntion blocks insert --parent-id <page_or_block_id> --markdown "After this" --after-id <block_id>
141
+
142
+ # Find and replace block ranges
143
+ ntion blocks select \
144
+ --scope-id <page_or_block_id> \
145
+ --selector-json '{"where":{"type":"paragraph","text_contains":"TODO"}}'
146
+
147
+ ntion blocks replace-range \
148
+ --scope-id <page_or_block_id> \
149
+ --start-selector-json '{"where":{"text_contains":"Start"}}' \
150
+ --end-selector-json '{"where":{"text_contains":"End"}}' \
151
+ --markdown "Replacement content"
152
+ ```
153
+
154
+ ### Health check
155
+
156
+ ```bash
157
+ ntion doctor
158
+ ```
159
+
160
+ ## Output format
161
+
162
+ Every response follows the same envelope:
163
+
164
+ ```json
165
+ {"ok": true, "data": {}, "meta": {"request_id": "..."}}
166
+ ```
167
+
168
+ ```json
169
+ {"ok": false, "error": {"code": "invalid_input", "message": "...", "retryable": false}, "meta": {"request_id": "..."}}
170
+ ```
171
+
172
+ Compact, deterministic, easy to parse — by humans or machines.
173
+
174
+ ## Design principles
175
+
176
+ - **Generic** — works with any Notion workspace, no hardcoded schema assumptions
177
+ - **Compact** — deterministic JSON envelopes, minimal bytes per response
178
+ - **Safe** — automatic idempotency for all mutations, built-in conflict detection
179
+ - **Fast** — zero native dependencies, internal schema caching, batch operations
180
+ - **Agent-friendly** — designed for AI agents that pay per token
181
+
182
+ ## Exit codes
183
+
184
+ | Code | Meaning |
185
+ |---|---|
186
+ | 0 | Success |
187
+ | 1 | Generic failure |
188
+ | 2 | Invalid input |
189
+ | 3 | Not found |
190
+ | 4 | Conflict |
191
+ | 5 | Retryable upstream error |
192
+ | 6 | Auth/config error |
193
+
194
+ ## Storage
195
+
196
+ Config and state are stored in `~/.config/ntion/` (or `$XDG_CONFIG_HOME/ntion/`):
197
+
198
+ - `config.json` — auth and defaults
199
+ - `idempotency.json` — short-lived mutation dedup cache (auto-pruned)
200
+ - `audit.log` — local mutation audit trail
201
+
202
+ ## License
203
+
204
+ MIT
@@ -0,0 +1,10 @@
1
+ export interface AuditEvent {
2
+ command: string;
3
+ entity?: string;
4
+ request_id: string;
5
+ idempotency_key?: string;
6
+ target_ids?: string[];
7
+ ok: boolean;
8
+ timestamp: string;
9
+ }
10
+ export declare function appendAuditLog(event: AuditEvent): Promise<void>;
@@ -0,0 +1,18 @@
1
+ import { appendFile } from "node:fs/promises";
2
+ import { createHash } from "node:crypto";
3
+ import { ensureConfigDir, getAuditLogPath } from "../config/paths.js";
4
+ function hashKey(value) {
5
+ if (!value) {
6
+ return undefined;
7
+ }
8
+ return createHash("sha256").update(value).digest("hex");
9
+ }
10
+ export async function appendAuditLog(event) {
11
+ ensureConfigDir();
12
+ const payload = {
13
+ ...event,
14
+ idempotency_key_hash: hashKey(event.idempotency_key),
15
+ };
16
+ delete payload.idempotency_key;
17
+ await appendFile(getAuditLogPath(), `${JSON.stringify(payload)}\n`, "utf8");
18
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};