Skip to main content

onenote_cli/
schema.rs

1use crate::{error::CONTRACTS, model::FIELDS};
2use serde_json::{Value, json};
3
4fn arg(name: &str, kind: &str, required: bool) -> Value {
5    json!({"name":name,"type":kind,"required":required})
6}
7pub fn capabilities() -> Value {
8    json!({"backend":"desktop", "authentication":"existing Windows OneNote desktop session", "platforms":["windows","wsl"],
9        "requires":"Windows OneNote desktop and Windows PowerShell 5.1; WSL requires Windows interop", "writes":true,
10        "supported":["notebooks list","sections list","pages list","pages read","search","pages create","pages append"],
11        "limitations":["Only notebooks open in the desktop application", "Reads desktop state; does not guarantee fresh cloud sync", "Page Markdown is a text extraction; layout, ink, and binary content are not exported", "Pagination is positional; edits between requests can shift results", "COM retrieves a hierarchy before local pagination; bridge output is capped at 32 MiB", "Indexed search requires Windows Search; unindexed pages are omitted and reported via indexing_pending and unindexed_count", "search --scan reads titles and text directly with case-insensitive literal, whitespace-normalized matching; no ink, OCR, or attachment search", "Scans default to 100 pages, maximum 500, with a 20-second soft budget and 45-second bridge timeout; use next_scan_offset separately from result pagination; incomplete reports unread pages or locked sections", "Writes accept plain text up to 1 MiB; no Markdown or HTML interpretation", "Create and append are not idempotent; inspect OneNote before retrying any uncertain result", "Write verification checks desktop state, not cloud synchronization"]})
12}
13pub fn generate(filter: Option<&str>) -> Value {
14    let mut commands = Vec::new();
15    for (name, description, extra) in [
16        ("notebooks list", "List open notebooks", vec![]),
17        (
18            "sections list",
19            "List sections recursively within a notebook",
20            vec![arg("--notebook", "string", true)],
21        ),
22        (
23            "pages list",
24            "List pages within a section",
25            vec![arg("--section", "string", true)],
26        ),
27        (
28            "search",
29            "Search using OneNote syntax, or literal text with --scan",
30            vec![
31                arg("query", "string", true),
32                arg("--scope", "string", false),
33                arg("--scan", "boolean", false),
34                arg("--scan-limit", "integer", false),
35                arg("--scan-offset", "integer", false),
36            ],
37        ),
38    ] {
39        let mut args = vec![
40            arg("--limit", "integer", false),
41            arg("--offset", "integer", false),
42            arg("--fields", "string", false),
43        ];
44        args.extend(extra);
45        commands.push(json!({"name":name,"description":description,"effects":"read_only","cardinality":"unbounded",
46            "pagination":{"style":"offset","offset_arg":"--offset","limit_arg":"--limit"},"fields_arg":"--fields","args":args,
47            "output_fields":FIELDS.iter().map(|f| json!({"name":f,"type":if *f == "locked" {"boolean"} else {"string"},"nullable":matches!(*f,"parent_id"|"last_modified")})).collect::<Vec<_>>()}));
48    }
49    for (name, description, mut args) in [
50        (
51            "pages create",
52            "Create a page with verified plain-text content",
53            vec![
54                arg("--section", "string", true),
55                arg("--title", "string", true),
56            ],
57        ),
58        (
59            "pages append",
60            "Append a new text block with a modification-time check and read-back verification",
61            vec![arg("id", "string", true)],
62        ),
63    ] {
64        args.extend([
65            arg("--text", "string", false),
66            arg("--file", "string", false),
67            arg("--dry-run", "boolean", false),
68        ]);
69        commands.push(json!({"name":name,"description":description,"effects":"non_idempotent","cardinality":"single","args":args,"stdout_schema":{"type":"object"}}));
70    }
71    for (name, description, args) in [
72        (
73            "pages read",
74            "Read page text as Markdown; optionally include XML",
75            vec![arg("id", "string", true), arg("--xml", "boolean", false)],
76        ),
77        (
78            "doctor",
79            "Diagnose the local platform and desktop bridge",
80            vec![arg("--offline", "boolean", false)],
81        ),
82        (
83            "capabilities",
84            "Describe desktop capabilities and limitations",
85            vec![],
86        ),
87        (
88            "schema",
89            "Emit the offline CLI Spec v0.3 contract",
90            vec![arg("--command", "string", false)],
91        ),
92    ] {
93        commands.push(json!({"name":name,"description":description,"effects":"read_only","cardinality":"single","stdout_schema":{"type":"object"},"args":args}));
94    }
95    commands.push(json!({"name":"completions","description":"Generate shell completions","effects":"read_only","output_kind":"opaque","media_type":"text/plain","args":[arg("shell","string",true)]}));
96    if let Some(filter) = filter {
97        commands.retain(|c| c["name"] == filter);
98    }
99    json!({"$schema":"https://clispec.dev/schema/v0.3.json","clispec":"0.3","name":"onenote","version":env!("CARGO_PKG_VERSION"),
100        "description":"Read and capture OneNote notes through the Windows desktop application","output":{"tty":"text","piped":"json"},
101        "global_args":[{"name":"--output","short":"-o","type":"string","enum":["auto","text","json"],"default":"auto"},{"name":"--no-color","type":"boolean"}],
102        "commands":commands,"errors":CONTRACTS.iter().map(|(kind,code,description)|json!({"kind":kind,"exit_code":code,"retryable":false,"description":description})).collect::<Vec<_>>(),
103        "extensions":capabilities()})
104}