onenote-cli
Use your OneNote notebooks from the terminal through the Windows desktop application, using its existing signed-in session. No Graph connection, Entra app registration, or separate OAuth consent is required by this backend.
Discover notebooks and sections, list and search pages, read page content, and create or append plain-text notes. It has readable terminal output, JSON when piped, bounded collections, shell completions, and an offline CLI Spec contract.
Requirements
- Windows OneNote desktop with your notebooks already open and accessible.
- Windows PowerShell 5.1 (
powershell.exe) on PATH. - Run under the Windows user who uses OneNote. WSL is supported through Windows interop; OneNote still runs on the Windows side.
OneNote for macOS and the browser do not expose this COM interface. Build and offline commands work on other platforms. Existing notebook permissions, password protection, and organizational desktop policies still apply.
Install
The package is named onenote-cli on both crates.io and PyPI. Both install the
onenote command:
uv tool install onenote-cli
# Or, with Rust installed:
cargo install onenote-cli --locked
The initial wheels support x86-64 Windows and x86-64 Linux (glibc 2.17 or later). Linux desktop access requires WSL with Windows interop; ordinary Linux can run offline commands only. The wheel contains the native executable, so installing it needs no Rust compiler. To install a downloaded wheel:
uv tool install --no-build --no-index ./onenote_cli-0.1.1-py3-none-win_amd64.whl
If you installed the earlier onenote-cli-rs package, switch names with:
uv tool uninstall onenote-cli-rs
uv tool install onenote-cli
The original onenote-cli-rs 0.1.0 package remains available and unchanged.
Run uv tool update-shell if onenote is not on PATH, then open a new terminal.
For WSL2, install uv inside the Linux distribution and use its Linux wheel.
Check that powershell.exe is on PATH and that Windows OneNote is open under the
same Windows user. Run onenote doctor and onenote search 'test' --scan from
that WSL terminal. Linux wheel installation has been tested in a container;
live WSL2 access remains unverified because the kiosk reports virtualization
unavailable and WSL installation failed. Native Windows uv installation is
validated separately; this is not evidence of working WSL2 interop.
Build and start
Requires Rust 1.90 or later. From this directory:
cargo install --path . --locked
onenote doctor
onenote notebooks list
onenote sections list --notebook '{NOTEBOOK-ID}'
onenote pages list --section '{SECTION-ID}'
onenote pages read '{PAGE-ID}'
onenote search 'quarterly review' --scan
Copy IDs from the preceding command. These are desktop OneNote IDs, not Graph IDs. Quote them in your shell. Sections inside section groups are included and identified by their hierarchy path. Recycle-bin content is excluded from lists.
onenote search 'budget AND review' --scope '{NOTEBOOK-ID}' --limit 10
onenote pages list --section '{SECTION-ID}' --limit 10 --offset 10
onenote pages list --section '{SECTION-ID}' --fields id,name --output json
onenote pages read '{PAGE-ID}' --xml --output json
onenote doctor --offline
onenote schema --command 'pages read'
onenote completions powershell
Collections return items, total, next_offset, and truncated. Reuse the same
command and filters with --offset set to next_offset; stop when it is null.
The default limit is 25, maximum 100. Pagination is positional: changes between
requests can shift items. OneNote supplies the hierarchy before local pagination;
the bridge response is capped at 32 MiB and commands time out after 45 seconds.
Page results include markdown, extracted from OneNote text blocks. This is not
a layout-preserving export: tables, tags, and outline nesting may lose structure;
ink, images, and attachment bytes are not exported. --xml includes the original
page XML without binary payloads. Reads reflect desktop state and do not force
cloud synchronization. Search uses OneNote's search syntax and requires the
Windows Search (WSearch) service to be running. It does not navigate the desktop
UI. Unindexed pages are excluded from matches: results include indexing_pending
and unindexed_count so an empty result cannot hide pending indexing. New or
changed pages may take time to become searchable. Listing and reading pages do
not depend on the search index.
Search without Windows indexing
onenote search 'quarterly review' --scan --scope '{SECTION-ID}'
onenote search 'quarterly review' --scan --scope '{NOTEBOOK-ID}' --scan-limit 100
onenote search 'quarterly review' --scan --scope '{NOTEBOOK-ID}' --scan-offset 100
--scan reads titles and page text directly, with case-insensitive literal
substring matching and whitespace normalization. It does not interpret Boolean
operators, wildcards, or OneNote search syntax. HTML formatting is removed and
entities decoded; link destinations, ink, OCR, and attachment content are not
searched. It needs no Windows Search service. Use a section scope for fast,
focused searches; without a scope it scans all open notebooks.
Each request attempts at most 100 pages by default (--scan-limit accepts 1–500).
A scan stops starting page reads after 20 seconds or when its XML response budget
reaches 4,194,304 UTF-16 code units (before JSON escaping). An individual blocked COM call can still hit
the bridge's 45-second timeout, which returns an error rather than partial results.
Oversized pages and read failures appear in skipped_pages; locked sections are
never read and are counted in skipped_locked_sections.
total counts matches in the current scan batch. --limit, --offset, and
next_offset paginate those matches. After consuming them, use
next_scan_offset as --scan-offset and reset --offset to zero to scan the next
batch. Keep the query, scope, and scan limit unchanged. These cursors are
positional, not snapshots; edits or timing differences between requests can shift
results. Narrow the scope if a scan cannot advance its cursor.
incomplete: true means there are more candidate pages, skipped pages, or locked
sections. next_scan_offset: null means no more candidates remain, but skipped
content can still make results incomplete. scanned_pages, attempted_pages,
and candidate_pages describe coverage; candidate counts exclude locked and
recycled pages. Partial results exit successfully with this metadata. Terminal
output also explicitly labels incomplete results.
--output auto selects text on a terminal and JSON when piped. Use -o text to
force readable text or -o json for automation. --no-color and NO_COLOR
disable color. Errors go to stderr as structured JSON in JSON mode, leaving
stdout empty. Exit codes: 1 local failure, 2 invalid input/unsupported platform,
4 resource not found, 5 desktop bridge failure, 6 write conflict, 7 uncertain write. OneNote COM failures, including
inaccessible IDs and locked sections, are reported as desktop errors with the
original diagnostic. Locked-section errors also explain how to unlock the section
in the desktop application.
Create and append
onenote pages create --section '{SECTION-ID}' --title 'Meeting notes' --file notes.txt --dry-run
onenote pages create --section '{SECTION-ID}' --title 'Meeting notes' --file notes.txt
onenote pages append '{PAGE-ID}' --text 'Follow up on the proposal.'
cat notes.txt | onenote pages append '{PAGE-ID}' --file -
Supply exactly one of --text or --file. Content is UTF-8 plain text, limited
to 1 MiB; titles are limited to 512 bytes. Markdown and HTML are treated as
literal text. Input line breaks become separate OneNote text elements. A UTF-8
BOM is removed and CRLF/CR line endings are normalized. --dry-run works offline
and prints the request without checking whether the target exists or is writable.
Create and append are explicit write commands and do not ask for confirmation.
Append places a new outline below existing content, without sending existing
outlines, images, or attachments back to OneNote. Every update uses the page's
last-modified timestamp and force=false. A concurrent edit returns conflict
(exit 6) rather than overwriting content.
Success includes the page ID, the resulting Markdown, operation, and
verified: true. Verification reads the page back, checks the new text (ignoring
rendering whitespace differences), and checks that existing page-level object IDs
remain. Create also checks the title. This verifies desktop state, not cloud sync.
These operations are not idempotent: repeating a successful command creates
another page or appends another block. Creation is a two-step desktop operation;
a failure after creating the page can leave a blank or partially populated page.
A timeout, interrupted response, or failed verification returns write_uncertain
(exit 7). Inspect OneNote before retrying. When known, the error includes
page_id for recovery. The CLI never retries a write or automatically deletes a
partially created page.
Implementation and validation
The Rust CLI passes JSON over stdin to a bundled PowerShell script. User text is
never interpolated into executable code. Read commands use GetHierarchy,
FindPages, and GetPageContent. The separate write bridge adds CreateNewPage
and UpdatePageContent. Both release their COM reference and never quit the
user's OneNote application.
cargo fmt -- --check
cargo clippy --locked --all-targets -- -D warnings
cargo test --locked --all-targets
On Windows, tests exercise the production script using a mock COM object. On other platforms with PowerShell installed:
ONENOTE_TEST_POWERSHELL=/path/to/pwsh cargo test --test desktop_bridge --test desktop_write -- --ignored
Mock tests cannot verify a real OneNote installation. Before release, run
doctor, notebook/section/page discovery, search, and page reading against a
Windows desktop profile, including a nested section and a locked section.
The initial kiosk smoke test passed discovery, nested sections, pagination,
field projection, Unicode page reads, Markdown/XML output, and pending-index
search behavior. Live create/append checks also passed for Unicode, literal
special characters, line breaks, existing-content preservation, and dry-run.
A real password-locked section was verified: discovery reports locked: true,
page listing requests an unlock, and creation is refused before mutation.
Page read/append errors for locked content are covered by COM-error fixtures.
Live --scan checks passed positive title and body matches, Unicode, negative
queries, bounded continuation, and locked-section omission with incomplete-result
metadata.
Indexed search remains an environment limitation: the kiosk's OneNote
crawler currently fails with 0x80004005 despite a running Windows Search service
and a targeted re-crawl. A normal OneNote restart and Office Quick Repair also
completed without resolving the crawler failure. A full kiosk reboot cleared
pending Windows servicing but did not fix indexing. Post-reboot checks passed
doctor, scratch-page discovery and reading, and confirmed that LockedSmoke
remains locked. Windows Search now uses delayed automatic startup on the test
kiosk. It must remain
enabled in the kiosk configuration; a provisioning run that disables WSearch
will break search again.
Microsoft references: Application interface and COM enumerations.
Prepare a release locally
The initial standalone package targets x86-64 Windows. On Windows, build and package it with:
cargo build --release --locked --target x86_64-pc-windows-msvc
python scripts/package.py --target x86_64-pc-windows-msvc --output dist
Cross-builds with cargo zigbuild --release --locked --target x86_64-pc-windows-gnu can be packaged with the corresponding GNU target.
The script verifies the executable architecture and archive contents, includes
this README, the license, and changelog, and writes a SHA-256 sidecar. Extract the
archive and verify onenote.exe --version, doctor, and the live read/write
smoke checks on Windows before publishing. Packaging does not publish or tag.
Registry packages are built with Maturin's binary bindings. Build the Windows
wheel on Windows or the Linux wheel on Linux; maturin build --release --locked --out dist uses the current platform. For a portable Linux wheel use the
manylinux build in .github/workflows/release.yml.
The release workflow tests wheel installation with uv tool install --no-build --no-index, validates the Windows/Linux wheel matrix and both source packages,
and creates SHA256SUMS. A tag builds packages but does not publish. Publication
requires an explicit workflow dispatch on that version tag with publish=true
and repository secrets CARGO_REGISTRY_TOKEN and PYPI_API_TOKEN.
Maintainers must configure those secrets and confirm registry ownership before publishing. If a registry publication partially succeeds, preserve that version and tag and use a patch release for further changes; do not overwrite already published artifacts.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file onenote_cli-0.1.1.tar.gz.
File metadata
- Download URL: onenote_cli-0.1.1.tar.gz
- Upload date:
- Size: 66.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dae7ed5073d820a22002d46244e0becabb6bae934b362d03078b4c2590fe7616
|
|
| MD5 |
45793bbe001293d7d38aed9ba388fd6b
|
|
| BLAKE2b-256 |
a584ecf8bce1353d3da6ccd87b99e72c4c0db6cf6a755a5dc4c22a2110177f70
|
File details
Details for the file onenote_cli-0.1.1-py3-none-win_amd64.whl.
File metadata
- Download URL: onenote_cli-0.1.1-py3-none-win_amd64.whl
- Upload date:
- Size: 1.8 MB
- Tags: Python 3, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a24256fbe83369dbec711fe5cc9928748d7f6b42977d9635e8b79cc4f87eb8b0
|
|
| MD5 |
7b6732cd81f2951a3c315b6099ac91db
|
|
| BLAKE2b-256 |
63ad9aa5624677d7d97d4d49f6eafcd80118576c525846011ae42d7bd12076b9
|
File details
Details for the file onenote_cli-0.1.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: onenote_cli-0.1.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 1.9 MB
- Tags: Python 3, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
89812049467c1afedbcebebd9eb364099126ef5d80e87bdfccacdfb180d5e21d
|
|
| MD5 |
7f7b5851896002babf78adbe1187c37d
|
|
| BLAKE2b-256 |
f7070cd1120398c12e249b7e6d93abeafcf6ccb7a2263d5a81ffde939d4b201f
|