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 +10 -0
- package/LICENSE +21 -0
- package/README.md +142 -0
- package/SKILL.md +77 -0
- package/bun.lock +92 -0
- package/docs/azure-app-registration.md +80 -0
- package/docs/development-notes.md +84 -0
- package/docs/graph-api-endpoints.md +95 -0
- package/docs/local-search-architecture.md +132 -0
- package/docs/onen0te-cli-analysis.md +130 -0
- package/docs/setup.md +141 -0
- package/docs/zhihu-intro.md +92 -0
- package/package.json +22 -0
- package/src/auth.ts +179 -0
- package/src/cache.ts +763 -0
- package/src/graph.ts +264 -0
- package/src/index.ts +408 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Local Page-Level Search Architecture
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
|
|
5
|
+
The Microsoft Graph OneNote API has a hard limit: when a OneDrive for Business document library contains more than 5,000 OneNote items, the API returns error 10008 and blocks all section/page listing endpoints. This affects accounts with many notebooks.
|
|
6
|
+
|
|
7
|
+
The Graph Search API (`/search/query`) only returns section-level (.one file) results — it cannot identify individual pages within a section.
|
|
8
|
+
|
|
9
|
+
## Solution: Local Cache + Binary Text Extraction
|
|
10
|
+
|
|
11
|
+
### How it works
|
|
12
|
+
|
|
13
|
+
1. **Sync** (`onenote sync`): Downloads all `.one` section files from OneDrive to a local cache directory (`.onenote/cache/`)
|
|
14
|
+
2. **Extract**: Parses the MS-ONESTORE binary format to extract readable text blocks, then segments them into pages based on binary gaps
|
|
15
|
+
3. **Search** (`onenote search <query>`): Searches the local cache for matching text at the page level
|
|
16
|
+
|
|
17
|
+
### Cache Structure
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
.onenote/
|
|
21
|
+
cache/
|
|
22
|
+
{Notebook Name}/
|
|
23
|
+
{Section Name}.json # Extracted pages with text
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Each `.json` file contains:
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"section": "Section Name",
|
|
30
|
+
"notebook": "Notebook Name",
|
|
31
|
+
"webUrl": "https://...Doc.aspx?sourcedoc={GUID}&...",
|
|
32
|
+
"pages": [
|
|
33
|
+
{ "title": "Page Title", "body": "Full text content..." },
|
|
34
|
+
...
|
|
35
|
+
],
|
|
36
|
+
"cachedAt": "2025-01-01T00:00:00.000Z"
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Binary-Based Position Search
|
|
41
|
+
|
|
42
|
+
For accurate page attribution, the cache stores the original `.one` binary alongside extracted metadata. Search works by:
|
|
43
|
+
|
|
44
|
+
1. Searching the binary directly for the query string (both UTF-8 and UTF-16LE encodings)
|
|
45
|
+
2. For each match position, finding the nearest preceding page GUID anchor
|
|
46
|
+
3. Returning that page as the result
|
|
47
|
+
|
|
48
|
+
This bypasses the imperfect text-block-to-page heuristics and gives accurate page attribution because the binary positions are ground truth — the matched text is physically located near the page anchor it belongs to.
|
|
49
|
+
|
|
50
|
+
### .one Binary Text Extraction
|
|
51
|
+
|
|
52
|
+
The MS-ONESTORE format (`.one` files) stores text as UTF-8 and UTF-16LE encoded strings interspersed with binary data. The extraction algorithm:
|
|
53
|
+
|
|
54
|
+
1. Read the file twice — once as UTF-8 (ASCII content) and once as UTF-16LE (CJK and other Unicode)
|
|
55
|
+
2. Find contiguous runs of printable characters
|
|
56
|
+
3. Filter runs that don't contain enough "common" characters (ratio threshold)
|
|
57
|
+
4. Use the page GUID anchors (see below) to assign each text block to the correct page
|
|
58
|
+
|
|
59
|
+
### Page GUID Extraction
|
|
60
|
+
|
|
61
|
+
Page-level URLs require knowing each page's UUID. We extract these from the binary using a pattern observed in MS-ONESTORE files:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
[UTF-16LE title text] 00 00 [10 00 00 00] [16-byte page GUID]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The `10 00 00 00` is a uint32-LE size marker meaning "16 bytes follow", and the next 16 bytes are a UUIDv4 (version=4, variant=8-B). The text immediately preceding (UTF-16LE encoded) is the page title.
|
|
68
|
+
|
|
69
|
+
For each page GUID found, all text blocks at later offsets up to the next anchor are assigned to that page. This anchor-based grouping correctly maps content (including text in non-Latin scripts) to the right page even when the file structure is fragmented.
|
|
70
|
+
|
|
71
|
+
### Page-Level URL Format
|
|
72
|
+
|
|
73
|
+
OneNote Online supports deep linking to specific pages via:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
{sectionUrl}&wd=target({pageTitle}|{pageGuid}/)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Where:
|
|
80
|
+
- `{sectionUrl}` is the SharePoint Doc.aspx URL with the section's `sourcedoc` GUID
|
|
81
|
+
- `{pageTitle}` is the page title with `)` and `|` characters escaped as `\)` and `\|`
|
|
82
|
+
- `{pageGuid}` is the page's UUIDv4
|
|
83
|
+
- Trailing `/` is required
|
|
84
|
+
|
|
85
|
+
The full `wd=` parameter is then URL-encoded with strict encoding (parens encoded as `%28`/`%29`).
|
|
86
|
+
|
|
87
|
+
### Search Output
|
|
88
|
+
|
|
89
|
+
Each result shows:
|
|
90
|
+
- **Page title** — extracted from the page anchor
|
|
91
|
+
- **Section and notebook** — which section/notebook contains the match
|
|
92
|
+
- **Context snippet** — text surrounding the match with keyword highlighted in `**bold**`
|
|
93
|
+
- **URL** — page-level OneNote Online deep link
|
|
94
|
+
|
|
95
|
+
### Page-Level URL Resolution (Official URLs)
|
|
96
|
+
|
|
97
|
+
We use the OneNote Graph API endpoint `GET /me/onenote/sections/0-{guid}/pages` to fetch the official `links.oneNoteWebUrl.href` for each page. This endpoint works even when the 5,000-item document library limit blocks `/me/onenote/pages` and `/me/onenote/sections` listing — because the `0-{guid}` ID prefix targets the section directly via its sourcedoc GUID (extracted from the OneDrive driveItem webUrl).
|
|
98
|
+
|
|
99
|
+
Official OneNote URLs use the format:
|
|
100
|
+
```
|
|
101
|
+
{driveRootPath}/{notebook}?wd=target({sectionFile}|{sectionGroupGuid}/{pageTitle}|{pageGuid}/)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Unlike the simpler `Doc.aspx?sourcedoc=...&wd=target(...)` format, this URL **bypasses OneNote Online's session caching** and navigates directly to the specified page on first load. Verified working via browser automation testing.
|
|
105
|
+
|
|
106
|
+
The page navigation GUID (used in `wd=target`) is extracted from the `oneNoteWebUrl` URL itself (the last UUID in the URL), since the API's page `id` field uses a different identifier format (`1-{32hexchars}!{counter}-{sectionGuid}`) that doesn't match the navigation GUID.
|
|
107
|
+
|
|
108
|
+
### Limitations
|
|
109
|
+
|
|
110
|
+
- **Page coverage**: Our binary parser detects ~50% of pages in some sections (those whose GUID-title binary pattern matches our heuristic). The other pages exist in the cache but without a precise GUID, so search results for them fall back to section-level URLs.
|
|
111
|
+
- **Cache freshness**: Cache is valid for 1 hour by default. Run `onenote sync` to refresh.
|
|
112
|
+
- **Binary parsing heuristic**: The UTF-8 text extraction may miss some content or include some binary noise. Page boundary detection is approximate.
|
|
113
|
+
- **Large sections**: Sections with thousands of pages (e.g., 5000+ extracted "pages") may have over-segmented results due to the binary gap heuristic.
|
|
114
|
+
|
|
115
|
+
### Alternative Approaches Investigated
|
|
116
|
+
|
|
117
|
+
| Approach | Result |
|
|
118
|
+
|---|---|
|
|
119
|
+
| OneNote API `/me/onenote/pages` | Blocked by 5000 item limit (error 10008) |
|
|
120
|
+
| Graph Search API `driveItem` | Section-level only, no page granularity |
|
|
121
|
+
| Graph Search API `listItem` | Section-level only (pages not individually indexed) |
|
|
122
|
+
| OneDrive HTML conversion (`?format=html`) | Not supported for .one files (406) |
|
|
123
|
+
| SharePoint REST search | Requires separate OAuth scope, returns section-level |
|
|
124
|
+
| Site-specific OneNote API | Same 5000 limit applies |
|
|
125
|
+
| Beta Graph API endpoints | Same limitations |
|
|
126
|
+
|
|
127
|
+
### Future Improvements
|
|
128
|
+
|
|
129
|
+
- Implement proper MS-ONESTORE parser for accurate page extraction with page GUIDs
|
|
130
|
+
- Use page GUIDs to construct page-level deep links: `Doc.aspx?sourcedoc={guid}&wd=target(section|/pageTitle)`
|
|
131
|
+
- Incremental sync (only download changed sections based on `lastModifiedDateTime`)
|
|
132
|
+
- SQLite or full-text search index for faster queries on large caches
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# onen0te-cli UX Analysis
|
|
2
|
+
|
|
3
|
+
Analysis of [fatihdumanli/onen0te-cli](https://github.com/fatihdumanli/onen0te-cli) — a Go-based OneNote CLI tool. Key takeaways for informing our `onenote-cli` design.
|
|
4
|
+
|
|
5
|
+
## Command Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
nnote new [-i "text" | -f file] [-a alias] [-t title] Create a note
|
|
9
|
+
nnote browse Interactive notebook/section/page navigation
|
|
10
|
+
nnote search <phrase> Search across all notebooks
|
|
11
|
+
nnote alias new [name] Create a section alias
|
|
12
|
+
nnote alias list List all aliases
|
|
13
|
+
nnote alias remove <name> Delete an alias
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Auth Flow
|
|
17
|
+
|
|
18
|
+
- Uses **OAuth 2.0 Authorization Code Flow** (not device code)
|
|
19
|
+
- Starts a local HTTP server on `localhost:5992` for the redirect callback
|
|
20
|
+
- Opens system browser for login
|
|
21
|
+
- Tokens stored in Bitcask DB at `/tmp/nnote`
|
|
22
|
+
- Auto-refreshes expired tokens silently before each API call
|
|
23
|
+
- On first run, prompts: "You haven't setup a Onenote account yet, would you like to setup one now?"
|
|
24
|
+
|
|
25
|
+
Comparison with our approach:
|
|
26
|
+
- We use **device code flow** which works better in SSH/headless environments
|
|
27
|
+
- They use browser redirect which is more seamless on desktop
|
|
28
|
+
- Both auto-refresh tokens
|
|
29
|
+
|
|
30
|
+
## Key UX Patterns Worth Adopting
|
|
31
|
+
|
|
32
|
+
### 1. Interactive Selection Prompts
|
|
33
|
+
|
|
34
|
+
When creating a note without an alias, the user is prompted to select a notebook, then a section via interactive dropdown (uses `survey` library). This is much better than requiring users to copy-paste IDs.
|
|
35
|
+
|
|
36
|
+
### 2. Alias System
|
|
37
|
+
|
|
38
|
+
Maps a short name to a notebook+section pair. Avoids repeated interactive selection for frequently-used sections. Example:
|
|
39
|
+
```
|
|
40
|
+
nnote new -a work -i "Meeting notes"
|
|
41
|
+
```
|
|
42
|
+
After saving, if no alias exists for the section, suggests creating one.
|
|
43
|
+
|
|
44
|
+
### 3. Browse Mode (Interactive Navigation Loop)
|
|
45
|
+
|
|
46
|
+
`nnote browse` creates an interactive loop:
|
|
47
|
+
1. Select notebook
|
|
48
|
+
2. Select section
|
|
49
|
+
3. Select page
|
|
50
|
+
4. View content (HTML rendered to text)
|
|
51
|
+
5. Menu: back to sections / notebooks / open in browser / open in OneNote client / exit
|
|
52
|
+
|
|
53
|
+
Uses emoji indicators in menus for visual scanning.
|
|
54
|
+
|
|
55
|
+
### 4. Multiple Note Input Methods
|
|
56
|
+
|
|
57
|
+
- `-i "text"` — Inline text
|
|
58
|
+
- `-f /path/to/file` — Import from file
|
|
59
|
+
- No flags — Opens `$EDITOR` for composing
|
|
60
|
+
|
|
61
|
+
### 5. Spinner Animations
|
|
62
|
+
|
|
63
|
+
Shows spinners during API calls (GetNotebooks, GetSections, SaveNote, etc.) with success/fail status on completion.
|
|
64
|
+
|
|
65
|
+
### 6. Styled Output
|
|
66
|
+
|
|
67
|
+
- Color-coded messages: green=success, red=error, yellow=warning
|
|
68
|
+
- Table rendering for structured data (aliases, notebooks)
|
|
69
|
+
- Breadcrumb display with metadata when viewing pages
|
|
70
|
+
|
|
71
|
+
## Architecture Decisions
|
|
72
|
+
|
|
73
|
+
### Storage
|
|
74
|
+
|
|
75
|
+
- Uses **Bitcask** (embedded key-value store) at `/tmp/nnote`
|
|
76
|
+
- Stores both OAuth tokens and aliases in the same DB
|
|
77
|
+
- No config files — minimal setup burden
|
|
78
|
+
- Concern: `/tmp` is volatile on some systems; better to use `~/.config/` or `~/.local/`
|
|
79
|
+
|
|
80
|
+
### API Layer
|
|
81
|
+
|
|
82
|
+
- Custom REST client wrapper over `net/http`
|
|
83
|
+
- Endpoints used:
|
|
84
|
+
- `GET /me/onenote/notebooks` — list notebooks
|
|
85
|
+
- `GET /me/onenote/notebooks/{id}/sections` — list sections
|
|
86
|
+
- `GET /me/onenote/sections/{id}/pages` — list pages
|
|
87
|
+
- `GET /me/onenote/pages/{id}/content` — get page HTML
|
|
88
|
+
- `POST /me/onenote/sections/{id}/pages` — create page
|
|
89
|
+
- `GET /me/onenote/pages?search=...` — search (undocumented/deprecated?)
|
|
90
|
+
- Uses `html2text` library to convert page HTML to terminal-readable text
|
|
91
|
+
|
|
92
|
+
### Error Handling
|
|
93
|
+
|
|
94
|
+
- All errors wrapped with context via `pkg/errors`
|
|
95
|
+
- Exit codes for different failure modes (0-7)
|
|
96
|
+
- Styled error messages (not raw stack traces)
|
|
97
|
+
|
|
98
|
+
### Dependencies (Go)
|
|
99
|
+
|
|
100
|
+
| Library | Purpose |
|
|
101
|
+
|---------|---------|
|
|
102
|
+
| spf13/cobra | CLI framework |
|
|
103
|
+
| AlecAivazis/survey | Interactive prompts |
|
|
104
|
+
| pterm/pterm | Tables, spinners, styled output |
|
|
105
|
+
| k3a/html2text | HTML to text rendering |
|
|
106
|
+
| prologic/bitcask | Key-value storage |
|
|
107
|
+
| pkg/errors | Error wrapping |
|
|
108
|
+
|
|
109
|
+
## Notable Gaps
|
|
110
|
+
|
|
111
|
+
- No pagination for large result sets
|
|
112
|
+
- No retry logic for HTTP 503/504 (marked as TODO)
|
|
113
|
+
- No handling of the 5000-item SharePoint limit (would fail silently)
|
|
114
|
+
- Search uses an older/undocumented Graph API pattern
|
|
115
|
+
- Hardcoded OAuth client ID — not configurable
|
|
116
|
+
- Storage path `/tmp/nnote` is volatile
|
|
117
|
+
- No `--json` output option for scripting
|
|
118
|
+
|
|
119
|
+
## Ideas for onenote-cli
|
|
120
|
+
|
|
121
|
+
Based on this analysis, features worth considering for our CLI:
|
|
122
|
+
|
|
123
|
+
1. **Interactive prompts** — Use `@inquirer/prompts` or similar for notebook/section selection instead of requiring raw IDs
|
|
124
|
+
2. **Alias system** — Map short names to notebook+section pairs for quick note creation
|
|
125
|
+
3. **Browse mode** — Interactive navigation loop through notebooks → sections → pages
|
|
126
|
+
4. **HTML to text rendering** — Use `html-to-text` or `turndown` for terminal display of page content
|
|
127
|
+
5. **Spinner/progress indicators** — Show progress during API calls
|
|
128
|
+
6. **Open in browser/client** — Use the `links.oneNoteWebUrl` and `oneNoteClientUrl` from notebook metadata
|
|
129
|
+
7. **$EDITOR integration** — Launch editor for composing notes
|
|
130
|
+
8. **--json flag** — Output JSON for scripting/piping (improvement over onen0te-cli)
|
package/docs/setup.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# onenote-cli Authentication Setup Guide
|
|
2
|
+
|
|
3
|
+
This guide walks you through setting up Azure AD authentication for `onenote-cli` to access Microsoft OneNote via the Graph API.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- A Microsoft account (personal or organizational)
|
|
8
|
+
- An Azure account with an active subscription — [create one for free](https://azure.microsoft.com/pricing/purchase-options/azure-account)
|
|
9
|
+
- [Bun](https://bun.sh) runtime installed
|
|
10
|
+
|
|
11
|
+
## Step 1: Register an Azure AD Application
|
|
12
|
+
|
|
13
|
+
1. Sign in to the [Microsoft Entra admin center](https://entra.microsoft.com)
|
|
14
|
+
2. Navigate to **Entra ID** > **App registrations** > **New registration**
|
|
15
|
+
3. Fill in the registration form:
|
|
16
|
+
- **Name**: `onenote-cli`
|
|
17
|
+
- **Supported account types**: Select **"Accounts in any organizational directory and personal Microsoft accounts"**
|
|
18
|
+
(This allows both work/school and personal Microsoft accounts)
|
|
19
|
+
- **Redirect URI**: Leave blank for now
|
|
20
|
+
4. Click **Register**
|
|
21
|
+
5. On the Overview page, copy the **Application (client) ID** — you will need this
|
|
22
|
+
|
|
23
|
+
## Step 2: Configure Platform for Device Code Flow
|
|
24
|
+
|
|
25
|
+
1. In your app registration, go to **Authentication** > **Add a platform**
|
|
26
|
+
2. Select **Mobile and desktop applications**
|
|
27
|
+
3. Check the box for `https://login.microsoftonline.com/common/oauth2/nativeclient`
|
|
28
|
+
4. Click **Configure**
|
|
29
|
+
5. Scroll down and set **Allow public client flows** to **Yes**
|
|
30
|
+
6. Click **Save**
|
|
31
|
+
|
|
32
|
+
> Device code flow requires "Allow public client flows" to be enabled. Without this, authentication will fail.
|
|
33
|
+
|
|
34
|
+
## Step 3: Configure API Permissions
|
|
35
|
+
|
|
36
|
+
1. In your app registration, go to **API permissions** > **Add a permission**
|
|
37
|
+
2. Select **Microsoft Graph** > **Delegated permissions**
|
|
38
|
+
3. Search and add the following permissions:
|
|
39
|
+
- `Notes.Read` — Read user OneNote notebooks
|
|
40
|
+
- `Notes.ReadWrite` — Read and write user OneNote notebooks
|
|
41
|
+
- `Notes.Read.All` — Read all notebooks the user can access
|
|
42
|
+
- `Notes.ReadWrite.All` — Read and write all notebooks the user can access
|
|
43
|
+
4. Click **Add permissions**
|
|
44
|
+
|
|
45
|
+
> For organizational accounts, an admin may need to **Grant admin consent** for these permissions.
|
|
46
|
+
|
|
47
|
+
## Step 4: Configure onenote-cli
|
|
48
|
+
|
|
49
|
+
You have two options to provide credentials:
|
|
50
|
+
|
|
51
|
+
### Option A: Environment variables (recommended)
|
|
52
|
+
|
|
53
|
+
Copy `.env.example` to `.env.local` and fill in your client ID:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
cp .env.example .env.local
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Edit `.env.local`:
|
|
60
|
+
|
|
61
|
+
```env
|
|
62
|
+
ONENOTE_CLIENT_ID=your-actual-client-id-here
|
|
63
|
+
ONENOTE_AUTHORITY=https://login.microsoftonline.com/common
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Option B: Config file
|
|
67
|
+
|
|
68
|
+
The CLI also reads from `~/.onenote-cli/config.json`. This file is auto-created on first run:
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"clientId": "your-actual-client-id-here",
|
|
73
|
+
"authority": "https://login.microsoftonline.com/common"
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
> Environment variables take priority over the config file.
|
|
78
|
+
|
|
79
|
+
## Step 5: Login
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
bun run src/index.ts login
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
This initiates the **device code flow**:
|
|
86
|
+
|
|
87
|
+
1. The CLI prints a URL and a code
|
|
88
|
+
2. Open the URL in your browser
|
|
89
|
+
3. Enter the code and sign in with your Microsoft account
|
|
90
|
+
4. Grant the requested permissions
|
|
91
|
+
5. Return to the terminal — you should see "Login successful!"
|
|
92
|
+
|
|
93
|
+
Tokens are cached at `~/.onenote-cli/msal-cache.json` so you don't need to login every time.
|
|
94
|
+
|
|
95
|
+
## Step 6: Verify
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# List your notebooks
|
|
99
|
+
bun run src/index.ts notebooks list
|
|
100
|
+
|
|
101
|
+
# List all sections
|
|
102
|
+
bun run src/index.ts sections list
|
|
103
|
+
|
|
104
|
+
# List pages
|
|
105
|
+
bun run src/index.ts pages list
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Authority URL Reference
|
|
109
|
+
|
|
110
|
+
| Scenario | Authority URL |
|
|
111
|
+
|---|---|
|
|
112
|
+
| Multi-tenant + personal accounts | `https://login.microsoftonline.com/common` |
|
|
113
|
+
| Organizational accounts only (any tenant) | `https://login.microsoftonline.com/organizations` |
|
|
114
|
+
| Personal Microsoft accounts only | `https://login.microsoftonline.com/consumers` |
|
|
115
|
+
| Single tenant only | `https://login.microsoftonline.com/{tenant-id}` |
|
|
116
|
+
|
|
117
|
+
## Logout
|
|
118
|
+
|
|
119
|
+
To clear cached tokens:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
bun run src/index.ts logout
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Troubleshooting
|
|
126
|
+
|
|
127
|
+
### "AADSTS7000218: The request body must contain the following parameter: 'client_assertion' or 'client_secret'"
|
|
128
|
+
|
|
129
|
+
→ Make sure **Allow public client flows** is set to **Yes** in your app's Authentication settings.
|
|
130
|
+
|
|
131
|
+
### "AADSTS65001: The user or administrator has not consented to use the application"
|
|
132
|
+
|
|
133
|
+
→ An admin needs to grant consent for the API permissions, or the user needs to consent during login.
|
|
134
|
+
|
|
135
|
+
### "AADSTS700016: Application with identifier '...' was not found"
|
|
136
|
+
|
|
137
|
+
→ Double-check that your `ONENOTE_CLIENT_ID` matches the Application (client) ID from Azure portal.
|
|
138
|
+
|
|
139
|
+
### Token expired
|
|
140
|
+
|
|
141
|
+
Tokens are automatically refreshed using the cached refresh token. If refresh fails, run `onenote login` again.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# onenote-cli: 让你的 OneNote 笔记在 AI 时代存活下来
|
|
2
|
+
|
|
3
|
+
你的 OneNote 笔记本里藏着多少年的记忆?工作笔记、学习资料、日记、灵感碎片……这些内容,AI 能帮你搜索吗?
|
|
4
|
+
|
|
5
|
+
答案是:现在可以了。
|
|
6
|
+
|
|
7
|
+
onenote-cli 是一个用 Bun + TypeScript 构建的命令行工具,通过 Microsoft Graph API 直接操作你的 OneNote 笔记本。最关键的功能:**全文搜索,精确到页面级别,点击 URL 直接跳转到匹配的那一页。**
|
|
8
|
+
|
|
9
|
+
## 为什么需要这个?
|
|
10
|
+
|
|
11
|
+
OneNote 的搜索功能只能在桌面端或网页端使用,无法被 AI 工具调用。当你想让 AI 帮你找一条几年前的笔记时,它做不到——因为 OneNote 没有暴露搜索 API 给第三方。
|
|
12
|
+
|
|
13
|
+
更糟的是,如果你的 OneDrive 里有超过 5000 个 OneNote 项目(笔记本+分区+分区组),微软的 Graph API 会直接返回 403 错误,连列出分区都做不到。
|
|
14
|
+
|
|
15
|
+
onenote-cli 解决了这两个问题。
|
|
16
|
+
|
|
17
|
+
## 它能做什么?
|
|
18
|
+
|
|
19
|
+
### 全文搜索(精确到页面)
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
$ onenote search "项目计划"
|
|
23
|
+
|
|
24
|
+
# (20240315) Q2 项目计划
|
|
25
|
+
Section: Work Notes | Notebook: My Notebook
|
|
26
|
+
...下季度**项目计划**:1. 完成用户认证模块重构 2. 上线新的推荐算法...
|
|
27
|
+
https://onenote.com/...?wd=target(...)
|
|
28
|
+
|
|
29
|
+
3 page-level results found.
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
搜索结果直接给出 OneNote Online 的页面级 URL,点击即可跳转到对应页面。不是打开整个分区,而是精确到那一页。
|
|
33
|
+
|
|
34
|
+
### 笔记本管理
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
$ onenote notebooks list
|
|
38
|
+
$ onenote sections list -n <notebook-id>
|
|
39
|
+
$ onenote pages create -s <section-id> -t "Meeting Notes" -b "<p>内容</p>"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 5000 项目限制的解决方案
|
|
43
|
+
|
|
44
|
+
当 Graph API 因为 SharePoint 文档库超过 5000 项而返回 403 时,onenote-cli 会:
|
|
45
|
+
|
|
46
|
+
1. 通过 OneDrive API 直接下载 `.one` 二进制文件
|
|
47
|
+
2. 从二进制中提取页面内容(支持 UTF-8 和 UTF-16LE,中日韩文字都能搜到)
|
|
48
|
+
3. 提取页面 GUID,通过 OneNote API 获取官方页面 URL
|
|
49
|
+
4. 构建本地缓存索引
|
|
50
|
+
|
|
51
|
+
### AI 集成
|
|
52
|
+
|
|
53
|
+
作为 Claude Code Skill 一键安装:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
$ npx skills add snomiao/onenote-cli
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
安装后,AI 可以直接搜索你的 OneNote 笔记,输出 Markdown 格式的结果:
|
|
60
|
+
|
|
61
|
+
```markdown
|
|
62
|
+
[Q2 项目计划](https://onenote.com/...?wd=target(...))
|
|
63
|
+
Work Notes | My Notebook
|
|
64
|
+
...下季度**项目计划**:1. 完成用户认证模块重构...
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 技术亮点
|
|
68
|
+
|
|
69
|
+
- **设备代码流认证**:支持 SSH / 无头环境
|
|
70
|
+
- **.one 二进制解析**:从 MS-ONESTORE 格式中提取页面文本和 GUID
|
|
71
|
+
- **官方页面 URL**:通过 `/me/onenote/sections/0-{guid}/pages` 端点获取,绕过 OneNote Online 的会话缓存
|
|
72
|
+
- **跨目录运行**:`.env.local` 和缓存从包目录自动加载
|
|
73
|
+
|
|
74
|
+
## 开始使用
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
git clone https://github.com/snomiao/onenote-cli.git
|
|
78
|
+
cd onenote-cli
|
|
79
|
+
bun install
|
|
80
|
+
cp .env.example .env.local # 填入你的 Azure AD Client ID
|
|
81
|
+
bun run src/index.ts auth login
|
|
82
|
+
bun run src/index.ts sync
|
|
83
|
+
bun run src/index.ts search "你想找的内容"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
详细的 Azure AD 配置步骤见 GitHub 仓库的 [docs/setup.md](https://github.com/snomiao/onenote-cli/blob/main/docs/setup.md)。
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
**GitHub**: https://github.com/snomiao/onenote-cli
|
|
91
|
+
|
|
92
|
+
MIT License | by snomiao
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "onenote-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI tool to operate Microsoft OneNote via Graph API",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"onenote-cli": "./src/index.ts",
|
|
8
|
+
"onenote": "./src/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "bun run src/index.ts"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@azure/msal-node": "^2.16.0",
|
|
15
|
+
"yargs": "^17.7.2"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/yargs": "^17.0.33",
|
|
19
|
+
"typescript": "^5.7.0"
|
|
20
|
+
},
|
|
21
|
+
"license": "MIT"
|
|
22
|
+
}
|