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}