onenote-cli 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.
package/.env.example ADDED
@@ -0,0 +1,10 @@
1
+ # Azure AD App Registration
2
+ # Register at: https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps
3
+ # 1. New registration -> Name: onenote-cli
4
+ # 2. Supported account types: Personal Microsoft accounts + Organizational
5
+ # 3. Platform: Mobile and desktop applications
6
+ # 4. Copy Application (client) ID here:
7
+ ONENOTE_CLIENT_ID=YOUR_CLIENT_ID
8
+
9
+ # Authority URL (default: common for multi-tenant + personal accounts)
10
+ ONENOTE_AUTHORITY=https://login.microsoftonline.com/common
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 sno
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.
package/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # onenote-cli
2
+
3
+ **Make your OneNote notebooks survive in the age of AI.**
4
+
5
+ A CLI that lets AI agents (and humans) search, read, and operate your OneNote — with full-text page-level search and deep links that open directly to the matching page.
6
+
7
+ ## Features
8
+
9
+ - **Page-level search** — search inside all your OneNote pages, get results with URLs that open directly to the matching page in OneNote Online
10
+ - **Notebooks / Sections / Pages** — list, get, create, delete via Graph API
11
+ - **5,000-item workaround** — when Graph OneNote API is blocked by the SharePoint document library limit, automatically falls back to OneDrive file API + local binary parsing
12
+ - **Local cache** — downloads `.one` files, extracts text (UTF-8 + UTF-16LE), builds a searchable index
13
+ - **Official OneNote URLs** — resolves page GUIDs via `GET /me/onenote/sections/0-{guid}/pages` to get URLs that bypass OneNote Online's session caching
14
+ - **Device code flow auth** — works in SSH / headless / terminal environments
15
+ - **Cross-directory** — `.env.local` and cache are loaded from the package directory, so `onenote` works from any working directory
16
+
17
+ ## Install
18
+
19
+ ### As an AI Agent Skill
20
+
21
+ ```bash
22
+ # Claude Code / OpenClaw / Codex / Cursor / any SKILL.md-compatible agent
23
+ npx skills add snomiao/onenote-cli
24
+ ```
25
+
26
+ ### Manual
27
+
28
+ ```bash
29
+ git clone https://github.com/snomiao/onenote-cli.git
30
+ cd onenote-cli
31
+ bun install
32
+ ```
33
+
34
+ ## Setup
35
+
36
+ 1. Register an Azure AD app at [entra.microsoft.com](https://entra.microsoft.com) (see [docs/setup.md](docs/setup.md) for full walkthrough)
37
+ 2. Copy `.env.example` to `.env.local` and set your Client ID:
38
+ ```bash
39
+ cp .env.example .env.local
40
+ # Edit .env.local with your Application (client) ID
41
+ ```
42
+ 3. Login:
43
+ ```bash
44
+ bun run src/index.ts auth login
45
+ ```
46
+
47
+ ## Usage
48
+
49
+ ```bash
50
+ # Auth
51
+ onenote auth login # Login via device code flow
52
+ onenote auth whoami # Show current user
53
+ onenote auth setup # Print setup instructions
54
+ onenote auth logout # Clear tokens
55
+
56
+ # Notebooks
57
+ onenote notebooks list
58
+ onenote notebooks get <id>
59
+ onenote notebooks create <name>
60
+
61
+ # Sections (falls back to OneDrive if Graph API is blocked)
62
+ onenote sections list -n <notebook-id>
63
+ onenote sections create -n <notebook-id> --name <name>
64
+
65
+ # Pages
66
+ onenote pages list -s <section-id>
67
+ onenote pages get <id>
68
+ onenote pages content <id>
69
+ onenote pages create -s <section-id> -t <title> -b "<html>"
70
+ onenote pages delete <id>
71
+
72
+ # Search
73
+ onenote sync # Download and cache all sections
74
+ onenote search <query> # Full-text page-level search (local)
75
+ onenote search <query> -o # Online section-level search (Graph API)
76
+ ```
77
+
78
+ ### Search Example
79
+
80
+ ```
81
+ $ onenote search 年金番号
82
+
83
+ # (20250303) nenkin bangou screenshot @mynaportal
84
+ Section: MyNumberCard-マイナンバカード | Notebook: sno@ja
85
+ 基础**年金番号** ...
86
+ https://snomiao-my.sharepoint.com/.../Notebooks/sno@ja?wd=target(...)
87
+
88
+ 2 page-level results found.
89
+ ```
90
+
91
+ Clicking the URL opens OneNote Online directly on the matching page.
92
+
93
+ ## How Search Works
94
+
95
+ 1. `onenote sync` downloads all `.one` section files from OneDrive
96
+ 2. Extracts page GUIDs from the MS-ONESTORE binary format
97
+ 3. Fetches official page URLs via OneNote Graph API (`/me/onenote/sections/0-{guid}/pages`)
98
+ 4. On search, scans the binary for matches (UTF-8 and UTF-16LE), attributes each match to the correct page via context-based anchor lookup
99
+ 5. Returns results with official OneNote URLs that navigate directly to the page
100
+
101
+ See [docs/local-search-architecture.md](docs/local-search-architecture.md) for the full technical design.
102
+
103
+ ## API Permissions Required
104
+
105
+ | Permission | Type | Purpose |
106
+ |---|---|---|
107
+ | `Notes.Read` | Delegated | Read notebooks |
108
+ | `Notes.ReadWrite` | Delegated | Create/modify pages |
109
+ | `Notes.ReadWrite.All` | Delegated | Access all notebooks |
110
+ | `Files.Read` | Delegated | Download .one files from OneDrive |
111
+ | `Files.Read.All` | Delegated | Access all accessible files |
112
+ | `Sites.Read.All` | Delegated | Search via SharePoint listItem API |
113
+
114
+ ## File Structure
115
+
116
+ ```
117
+ src/
118
+ index.ts CLI entry point (yargs)
119
+ auth.ts MSAL device code flow + .env.local auto-loader
120
+ graph.ts Microsoft Graph API client (OneNote + OneDrive)
121
+ cache.ts Local cache, .one binary parser, page GUID extraction
122
+ docs/
123
+ setup.md Azure AD registration walkthrough
124
+ local-search-architecture.md Technical design of local search
125
+ graph-api-endpoints.md OneNote Graph API reference
126
+ development-notes.md Lessons learned
127
+ onen0te-cli-analysis.md Competitor UX analysis
128
+ ```
129
+
130
+ ## Configuration
131
+
132
+ | Source | Location | Priority |
133
+ |---|---|---|
134
+ | `.env.local` | Package root (auto-loaded) | Highest |
135
+ | `~/.onenote-cli/config.json` | Home directory | Fallback |
136
+ | `ONENOTE_CLIENT_ID` env var | Shell environment | Overrides all |
137
+
138
+ Cache location: `<package>/.onenote/cache/` (override with `ONENOTE_CACHE_DIR`)
139
+
140
+ ## License
141
+
142
+ MIT
package/SKILL.md ADDED
@@ -0,0 +1,77 @@
1
+ ---
2
+ name: onenote-cli
3
+ description: Make your OneNote notebooks survive in the age of AI. Search and operate OneNote from CLI via Microsoft Graph API. Use when the user wants to search OneNote notes, list notebooks, read pages, or manage OneNote content.
4
+ ---
5
+
6
+ # onenote-cli
7
+
8
+ CLI for Microsoft OneNote via Microsoft Graph. Built with Bun + yargs + MSAL.
9
+
10
+ ## Quick Reference
11
+
12
+ ```bash
13
+ onenote auth login # Device code flow login
14
+ onenote auth logout # Clear cached tokens
15
+ onenote auth whoami # Show current user
16
+ onenote auth setup # Show OAuth setup instructions
17
+
18
+ onenote notebooks list # List notebooks
19
+ onenote notebooks get <id> # Get notebook by ID
20
+ onenote notebooks create <name> # Create notebook
21
+
22
+ onenote sections list -n <nb-id> # List sections in a notebook
23
+ onenote sections create -n <nb> --name <name>
24
+
25
+ onenote pages list -s <sec-id> # List pages in a section
26
+ onenote pages content <id> # Get page HTML content
27
+ onenote pages create -s <sec> -t <title> -b <html>
28
+ onenote pages delete <id>
29
+
30
+ onenote sync # Build local cache (.one binary + page index)
31
+ onenote search <query> # Full-text search across cached pages
32
+ onenote search <query> --online # Online section-level search via Graph
33
+ ```
34
+
35
+ ## Setup
36
+
37
+ See [docs/setup.md](docs/setup.md) for full Azure AD app registration walkthrough.
38
+
39
+ Quick setup:
40
+ 1. Register app at https://entra.microsoft.com → App registrations → New
41
+ 2. Supported account types: "Accounts in any organizational directory and personal Microsoft accounts"
42
+ 3. Add platform: Mobile and desktop applications, redirect URI `https://login.microsoftonline.com/common/oauth2/nativeclient`
43
+ 4. Settings → Allow public client flows: **Yes**
44
+ 5. API permissions → Microsoft Graph → Delegated: `Notes.Read`, `Notes.ReadWrite`, `Notes.ReadWrite.All`, `Files.Read`, `Files.Read.All`, `Sites.Read.All`
45
+ 6. Copy Application (client) ID into `.env.local`:
46
+ ```env
47
+ ONENOTE_CLIENT_ID=your-client-id-here
48
+ ONENOTE_AUTHORITY=https://login.microsoftonline.com/common
49
+ ```
50
+
51
+ ## How Search Works
52
+
53
+ `onenote search <query>` returns page-level results with **official OneNote URLs** that navigate directly to the matching page (bypassing OneNote Online's session caching).
54
+
55
+ The search:
56
+ 1. Downloads `.one` files via OneDrive (cached locally in `<package>/.onenote/cache/`)
57
+ 2. Extracts page GUIDs from the binary
58
+ 3. Resolves official `oneNoteWebUrl` via `/me/onenote/sections/0-{guid}/pages` (works around the 5,000-item document library limit)
59
+ 4. Searches the binary for the query (UTF-8 + UTF-16LE) and attributes matches to the correct page using context-based lookup
60
+
61
+ See [docs/local-search-architecture.md](docs/local-search-architecture.md) for details.
62
+
63
+ ## File Locations
64
+
65
+ - `.env.local` — at the package root (auto-loaded via `import.meta.dir`)
66
+ - Token cache — `~/.onenote-cli/msal-cache.json`
67
+ - Config fallback — `~/.onenote-cli/config.json`
68
+ - Page cache — `<package>/.onenote/cache/` (override with `ONENOTE_CACHE_DIR`)
69
+
70
+ ## Common Issues
71
+
72
+ | Error | Fix |
73
+ |---|---|
74
+ | `AADSTS7000218` | Enable "Allow public client flows" in app Authentication settings |
75
+ | `AADSTS65001` | Admin consent required for API permissions, or accept consent during login |
76
+ | `Graph API 403: error 10008` | Document library has > 5,000 OneNote items; use `onenote sync` and local search instead |
77
+ | Cache empty | Run `onenote sync` (auto-runs on first search) |
package/bun.lock ADDED
@@ -0,0 +1,92 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "configVersion": 1,
4
+ "workspaces": {
5
+ "": {
6
+ "name": "onenote-cli",
7
+ "dependencies": {
8
+ "@azure/msal-node": "^2.16.0",
9
+ "yargs": "^17.7.2",
10
+ },
11
+ "devDependencies": {
12
+ "@types/yargs": "^17.0.33",
13
+ "typescript": "^5.7.0",
14
+ },
15
+ },
16
+ },
17
+ "packages": {
18
+ "@azure/msal-common": ["@azure/msal-common@14.16.1", "", {}, "sha512-nyxsA6NA4SVKh5YyRpbSXiMr7oQbwark7JU9LMeg6tJYTSPyAGkdx61wPT4gyxZfxlSxMMEyAsWaubBlNyIa1w=="],
19
+
20
+ "@azure/msal-node": ["@azure/msal-node@2.16.3", "", { "dependencies": { "@azure/msal-common": "14.16.1", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" } }, "sha512-CO+SE4weOsfJf+C5LM8argzvotrXw252/ZU6SM2Tz63fEblhH1uuVaaO4ISYFuN4Q6BhTo7I3qIdi8ydUQCqhw=="],
21
+
22
+ "@types/yargs": ["@types/yargs@17.0.35", "", { "dependencies": { "@types/yargs-parser": "*" } }, "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg=="],
23
+
24
+ "@types/yargs-parser": ["@types/yargs-parser@21.0.3", "", {}, "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="],
25
+
26
+ "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
27
+
28
+ "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
29
+
30
+ "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
31
+
32
+ "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
33
+
34
+ "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
35
+
36
+ "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
37
+
38
+ "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
39
+
40
+ "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
41
+
42
+ "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
43
+
44
+ "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
45
+
46
+ "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
47
+
48
+ "jsonwebtoken": ["jsonwebtoken@9.0.3", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="],
49
+
50
+ "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
51
+
52
+ "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="],
53
+
54
+ "lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="],
55
+
56
+ "lodash.isboolean": ["lodash.isboolean@3.0.3", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="],
57
+
58
+ "lodash.isinteger": ["lodash.isinteger@4.0.4", "", {}, "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="],
59
+
60
+ "lodash.isnumber": ["lodash.isnumber@3.0.3", "", {}, "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="],
61
+
62
+ "lodash.isplainobject": ["lodash.isplainobject@4.0.6", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="],
63
+
64
+ "lodash.isstring": ["lodash.isstring@4.0.1", "", {}, "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="],
65
+
66
+ "lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="],
67
+
68
+ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
69
+
70
+ "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
71
+
72
+ "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
73
+
74
+ "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
75
+
76
+ "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
77
+
78
+ "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
79
+
80
+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
81
+
82
+ "uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
83
+
84
+ "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
85
+
86
+ "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
87
+
88
+ "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
89
+
90
+ "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
91
+ }
92
+ }
@@ -0,0 +1,80 @@
1
+ # Azure AD App Registration for OneNote CLI
2
+
3
+ This document describes how to register an Azure AD application for use with `onenote-cli`, which uses the Microsoft Graph OneNote API with delegated (device code flow) authentication.
4
+
5
+ ## Prerequisites
6
+
7
+ - A Microsoft account (personal or organizational)
8
+ - Access to [Microsoft Entra admin center](https://entra.microsoft.com)
9
+
10
+ ## Step 1: Create the App Registration
11
+
12
+ 1. Go to [Microsoft Entra admin center](https://entra.microsoft.com)
13
+ 2. Navigate to **Entra ID** > **App registrations** > **New registration**
14
+ 3. Fill in:
15
+ - **Name**: `onenote-cli`
16
+ - **Supported account types**: "Accounts in any organizational directory and personal Microsoft accounts"
17
+ - **Redirect URI**: Leave blank (configured in the next step)
18
+ 4. Click **Register**
19
+ 5. Copy the **Application (client) ID** from the Overview page
20
+
21
+ ## Step 2: Configure Authentication Platform
22
+
23
+ 1. Go to **Authentication** tab in your app registration
24
+ 2. Click **Add a platform** > **Mobile and desktop applications**
25
+ 3. Check these redirect URIs:
26
+ - `https://login.microsoftonline.com/common/oauth2/nativeclient`
27
+ - `msal{client-id}://auth` (MSAL-specific URI)
28
+ 4. Click **Configure**
29
+
30
+ ## Step 3: Enable Public Client Flows
31
+
32
+ 1. In the **Authentication** tab, go to the **Settings** sub-tab
33
+ 2. Find **Allow public client flows** and set it to **Enabled**
34
+ 3. Click **Save**
35
+
36
+ This is required for device code flow authentication. Without it, you will get error `AADSTS7000218`.
37
+
38
+ ## Step 4: Add API Permissions
39
+
40
+ 1. Go to **API permissions** > **Add a permission**
41
+ 2. Select **Microsoft Graph** > **Delegated permissions**
42
+ 3. Search for "Notes" and expand the **Notes** group
43
+ 4. Select:
44
+ - `Notes.Read` — Read user OneNote notebooks
45
+ - `Notes.ReadWrite` — Read and write user OneNote notebooks
46
+ - `Notes.ReadWrite.All` — Read and write all notebooks the user can access
47
+ 5. Click **Add permissions**
48
+
49
+ The default `User.Read` permission is already included.
50
+
51
+ ## Step 5: Configure the CLI
52
+
53
+ Set the client ID via environment variable or config file:
54
+
55
+ ```bash
56
+ # Option A: .env.local
57
+ ONENOTE_CLIENT_ID=your-client-id-here
58
+ ONENOTE_AUTHORITY=https://login.microsoftonline.com/common
59
+
60
+ # Option B: ~/.onenote-cli/config.json
61
+ {
62
+ "clientId": "your-client-id-here",
63
+ "authority": "https://login.microsoftonline.com/common"
64
+ }
65
+ ```
66
+
67
+ Environment variables take priority over the config file.
68
+
69
+ ## Step 6: Login and Verify
70
+
71
+ ```bash
72
+ bun run src/index.ts login
73
+ bun run src/index.ts notebooks list
74
+ ```
75
+
76
+ ## Notes
77
+
78
+ - The Microsoft Graph OneNote API **does not support app-only authentication** as of March 2025. Only delegated (user) authentication is supported.
79
+ - The authority URL `https://login.microsoftonline.com/common` supports both organizational and personal Microsoft accounts.
80
+ - Tokens are cached at `~/.onenote-cli/msal-cache.json` and automatically refreshed.
@@ -0,0 +1,84 @@
1
+ # Development Notes
2
+
3
+ Lessons learned and implementation notes from building `onenote-cli`.
4
+
5
+ ## Architecture Decisions
6
+
7
+ ### Bun + Yargs
8
+
9
+ - Bun is used as the runtime, allowing direct execution of TypeScript without a build step
10
+ - Yargs provides a natural command/subcommand CLI structure
11
+ - The `bin` field in package.json points directly to `src/index.ts` — no compilation needed
12
+
13
+ ### MSAL Node for Authentication
14
+
15
+ - `@azure/msal-node` provides the `PublicClientApplication` class for device code flow
16
+ - Token caching is implemented via MSAL's `cachePlugin` interface, persisting to `~/.onenote-cli/msal-cache.json`
17
+ - Silent token acquisition is attempted first (using cached refresh tokens), falling back to device code flow
18
+
19
+ ### Configuration Priority
20
+
21
+ Configuration is resolved in this order:
22
+ 1. Environment variables (`ONENOTE_CLIENT_ID`, `ONENOTE_AUTHORITY`)
23
+ 2. Config file (`~/.onenote-cli/config.json`)
24
+ 3. Default values (which prompt the user to configure)
25
+
26
+ This allows both `.env.local` for development and the config file for installed usage.
27
+
28
+ ## Implementation Details
29
+
30
+ ### Device Code Flow
31
+
32
+ The device code flow is ideal for CLI applications because:
33
+ - No web server or redirect handler is needed
34
+ - Works in headless/SSH environments
35
+ - User authenticates in any browser, even on a different device
36
+
37
+ Flow:
38
+ 1. CLI requests a device code from Azure AD
39
+ 2. Azure AD returns a code and a URL
40
+ 3. User opens the URL in a browser and enters the code
41
+ 4. User authenticates and consents to permissions
42
+ 5. CLI polls Azure AD and receives tokens once authentication completes
43
+
44
+ ### Page Creation
45
+
46
+ OneNote pages are created by POSTing raw HTML (Content-Type: `text/html`), not JSON. The HTML must include a `<title>` in the `<head>` and content in the `<body>`. This is different from most Graph API endpoints which use JSON.
47
+
48
+ ### App-Only Auth Not Supported
49
+
50
+ As of March 2025, Microsoft Graph OneNote API no longer supports app-only (client credentials) authentication. All access must use delegated permissions with a signed-in user. This means the CLI must always go through the device code flow for initial authentication.
51
+
52
+ ## Azure Portal Navigation Tips
53
+
54
+ ### Entra Admin Center vs Azure Portal
55
+
56
+ - App registrations are managed in the [Microsoft Entra admin center](https://entra.microsoft.com), not the classic Azure portal
57
+ - The path is: **Entra ID** > **App registrations**
58
+ - Direct URL: `https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade`
59
+
60
+ ### Common Setup Mistakes
61
+
62
+ 1. **Forgetting to enable public client flows** — Device code flow requires "Allow public client flows" to be set to Yes in Authentication > Settings. Without this, authentication fails with `AADSTS7000218`.
63
+
64
+ 2. **Wrong account type selection** — For a CLI that should work with both personal and work accounts, select "Accounts in any organizational directory and personal Microsoft accounts".
65
+
66
+ 3. **Missing redirect URI** — While device code flow doesn't strictly require a redirect URI, adding `https://login.microsoftonline.com/common/oauth2/nativeclient` ensures compatibility with the MSAL library.
67
+
68
+ 4. **Not adding API permissions** — The default `User.Read` permission is not enough. You must explicitly add `Notes.Read` / `Notes.ReadWrite` delegated permissions under Microsoft Graph.
69
+
70
+ ## Troubleshooting
71
+
72
+ ### Token Refresh Failures
73
+
74
+ If silent token acquisition fails after a long period, the refresh token may have expired. Run `onenote login` again to re-authenticate.
75
+
76
+ ### Graph API 403 Errors
77
+
78
+ Usually means the required permission was not consented. Check that:
79
+ - The permission is added in the app registration
80
+ - The user consented during login (or admin consent was granted)
81
+
82
+ ### Graph API 404 Errors
83
+
84
+ OneNote resources use opaque IDs. If a notebook/section/page was deleted or moved, its ID becomes invalid.
@@ -0,0 +1,95 @@
1
+ # Microsoft Graph OneNote API Endpoints
2
+
3
+ Reference for the OneNote REST API endpoints used by `onenote-cli`.
4
+
5
+ ## Base URL
6
+
7
+ ```
8
+ https://graph.microsoft.com/v1.0/me/onenote/
9
+ ```
10
+
11
+ ## Notebooks
12
+
13
+ | Method | Endpoint | Description |
14
+ |--------|----------|-------------|
15
+ | GET | `/me/onenote/notebooks` | List all notebooks |
16
+ | GET | `/me/onenote/notebooks/{id}` | Get a notebook by ID |
17
+ | POST | `/me/onenote/notebooks` | Create a notebook (`{ "displayName": "..." }`) |
18
+
19
+ ## Sections
20
+
21
+ | Method | Endpoint | Description |
22
+ |--------|----------|-------------|
23
+ | GET | `/me/onenote/sections` | List all sections |
24
+ | GET | `/me/onenote/notebooks/{id}/sections` | List sections in a notebook |
25
+ | GET | `/me/onenote/sections/{id}` | Get a section by ID |
26
+ | POST | `/me/onenote/notebooks/{id}/sections` | Create a section (`{ "displayName": "..." }`) |
27
+
28
+ ## Section Groups
29
+
30
+ | Method | Endpoint | Description |
31
+ |--------|----------|-------------|
32
+ | GET | `/me/onenote/sectionGroups` | List all section groups |
33
+ | GET | `/me/onenote/notebooks/{id}/sectionGroups` | List section groups in a notebook |
34
+
35
+ ## Pages
36
+
37
+ | Method | Endpoint | Description |
38
+ |--------|----------|-------------|
39
+ | GET | `/me/onenote/pages` | List all pages |
40
+ | GET | `/me/onenote/sections/{id}/pages` | List pages in a section |
41
+ | GET | `/me/onenote/pages/{id}` | Get page metadata |
42
+ | GET | `/me/onenote/pages/{id}/content` | Get page HTML content |
43
+ | POST | `/me/onenote/sections/{id}/pages` | Create a page (Content-Type: text/html) |
44
+ | DELETE | `/me/onenote/pages/{id}` | Delete a page |
45
+
46
+ ## Search
47
+
48
+ | Method | Endpoint | Description |
49
+ |--------|----------|-------------|
50
+ | GET | `/me/onenote/pages?$search={query}` | Search pages by content |
51
+
52
+ ## Creating Pages
53
+
54
+ Pages are created by POSTing HTML to a section:
55
+
56
+ ```http
57
+ POST /me/onenote/sections/{section-id}/pages
58
+ Content-Type: text/html
59
+
60
+ <!DOCTYPE html>
61
+ <html>
62
+ <head>
63
+ <title>Page Title</title>
64
+ </head>
65
+ <body>
66
+ <p>Page content here</p>
67
+ </body>
68
+ </html>
69
+ ```
70
+
71
+ ## Required Permissions (Delegated)
72
+
73
+ | Permission | Description |
74
+ |------------|-------------|
75
+ | `Notes.Read` | Read notebooks |
76
+ | `Notes.ReadWrite` | Read and write notebooks |
77
+ | `Notes.Read.All` | Read all accessible notebooks |
78
+ | `Notes.ReadWrite.All` | Read and write all accessible notebooks |
79
+
80
+ ## Additional Scopes
81
+
82
+ Notebooks can also be accessed via group or SharePoint site contexts:
83
+
84
+ ```
85
+ /groups/{id}/onenote/{notebooks|sections|pages}
86
+ /sites/{id}/onenote/{notebooks|sections|pages}
87
+ ```
88
+
89
+ These require corresponding group/site read permissions.
90
+
91
+ ## References
92
+
93
+ - [OneNote API overview](https://learn.microsoft.com/en-us/graph/integrate-with-onenote)
94
+ - [OneNote REST API reference](https://learn.microsoft.com/en-us/graph/api/resources/onenote-api-overview)
95
+ - [Graph Explorer](https://developer.microsoft.com/graph/graph-explorer)