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/src/graph.ts ADDED
@@ -0,0 +1,264 @@
1
+ import { getAccessToken } from "./auth";
2
+
3
+ const GRAPH_BASE = "https://graph.microsoft.com/v1.0";
4
+
5
+ interface GraphResponse<T> {
6
+ value: T[];
7
+ "@odata.nextLink"?: string;
8
+ }
9
+
10
+ async function graphFetch(path: string, init?: RequestInit): Promise<Response> {
11
+ const token = await getAccessToken();
12
+ const url = path.startsWith("http") ? path : `${GRAPH_BASE}${path}`;
13
+ const res = await fetch(url, {
14
+ ...init,
15
+ headers: {
16
+ Authorization: `Bearer ${token}`,
17
+ "Content-Type": "application/json",
18
+ ...init?.headers,
19
+ },
20
+ });
21
+ if (!res.ok) {
22
+ const body = await res.text();
23
+ let code = "";
24
+ let message = "";
25
+ try {
26
+ const err = JSON.parse(body);
27
+ code = err.error?.code ?? "";
28
+ message = err.error?.message ?? body;
29
+ } catch {
30
+ message = body;
31
+ }
32
+ const error = new Error(`Graph API ${res.status}: ${message}`) as any;
33
+ error.statusCode = res.status;
34
+ error.graphCode = code;
35
+ throw error;
36
+ }
37
+ return res;
38
+ }
39
+
40
+ function is5000LimitError(err: any): boolean {
41
+ return err?.graphCode === "10008" || err?.graphCode === "20102";
42
+ }
43
+
44
+ // --- Notebook path helpers ---
45
+
46
+ function getNotebookDrivePath(notebook: any): string | null {
47
+ const webUrl = notebook.links?.oneNoteWebUrl?.href;
48
+ if (!webUrl) return null;
49
+ const match = decodeURIComponent(new URL(webUrl).pathname).match(/Documents\/(.+)/);
50
+ return match?.[1] ?? null;
51
+ }
52
+
53
+ async function listNotebookFilesViaDrive(notebook: any): Promise<any[]> {
54
+ const drivePath = getNotebookDrivePath(notebook);
55
+ if (!drivePath) return [];
56
+ const encoded = drivePath.split("/").map(s => encodeURIComponent(s)).join("/");
57
+ const res = await graphFetch(
58
+ `/me/drive/root:/${encoded}:/children?$select=name,id,file,folder,size,lastModifiedDateTime,createdDateTime`
59
+ );
60
+ const data: GraphResponse<any> = await res.json();
61
+ return data.value;
62
+ }
63
+
64
+ // --- Notebooks ---
65
+
66
+ export async function listNotebooks() {
67
+ const res = await graphFetch("/me/onenote/notebooks");
68
+ const data: GraphResponse<any> = await res.json();
69
+ return data.value;
70
+ }
71
+
72
+ export async function getNotebook(id: string) {
73
+ const res = await graphFetch(`/me/onenote/notebooks/${id}`);
74
+ return res.json();
75
+ }
76
+
77
+ export async function createNotebook(displayName: string) {
78
+ const res = await graphFetch("/me/onenote/notebooks", {
79
+ method: "POST",
80
+ body: JSON.stringify({ displayName }),
81
+ });
82
+ return res.json();
83
+ }
84
+
85
+ // --- Sections ---
86
+
87
+ export async function listSections(notebookId?: string) {
88
+ try {
89
+ const path = notebookId
90
+ ? `/me/onenote/notebooks/${notebookId}/sections`
91
+ : "/me/onenote/sections";
92
+ const res = await graphFetch(path);
93
+ const data: GraphResponse<any> = await res.json();
94
+ return data.value;
95
+ } catch (err: any) {
96
+ if (!is5000LimitError(err) || !notebookId) throw err;
97
+ // Fallback: list .one files via OneDrive
98
+ const notebook = await getNotebook(notebookId);
99
+ const files = await listNotebookFilesViaDrive(notebook);
100
+ return files
101
+ .filter((f: any) => f.name?.endsWith(".one"))
102
+ .map((f: any) => ({
103
+ id: f.id,
104
+ displayName: f.name.replace(/\.one$/, ""),
105
+ createdDateTime: f.createdDateTime,
106
+ lastModifiedDateTime: f.lastModifiedDateTime,
107
+ _source: "onedrive",
108
+ }));
109
+ }
110
+ }
111
+
112
+ export async function getSection(id: string) {
113
+ const res = await graphFetch(`/me/onenote/sections/${id}`);
114
+ return res.json();
115
+ }
116
+
117
+ export async function createSection(notebookId: string, displayName: string) {
118
+ const res = await graphFetch(`/me/onenote/notebooks/${notebookId}/sections`, {
119
+ method: "POST",
120
+ body: JSON.stringify({ displayName }),
121
+ });
122
+ return res.json();
123
+ }
124
+
125
+ // --- Section Groups ---
126
+
127
+ export async function listSectionGroups(notebookId?: string) {
128
+ try {
129
+ const path = notebookId
130
+ ? `/me/onenote/notebooks/${notebookId}/sectionGroups`
131
+ : "/me/onenote/sectionGroups";
132
+ const res = await graphFetch(path);
133
+ const data: GraphResponse<any> = await res.json();
134
+ return data.value;
135
+ } catch (err: any) {
136
+ if (!is5000LimitError(err) || !notebookId) throw err;
137
+ // Fallback: list folders via OneDrive
138
+ const notebook = await getNotebook(notebookId);
139
+ const files = await listNotebookFilesViaDrive(notebook);
140
+ return files
141
+ .filter((f: any) => f.folder && !f.name?.startsWith("OneNote_") && f.name !== "deletePending")
142
+ .map((f: any) => ({
143
+ id: f.id,
144
+ displayName: f.name,
145
+ createdDateTime: f.createdDateTime,
146
+ _source: "onedrive",
147
+ }));
148
+ }
149
+ }
150
+
151
+ // --- Pages ---
152
+
153
+ export async function listPages(sectionId?: string) {
154
+ const path = sectionId
155
+ ? `/me/onenote/sections/${sectionId}/pages`
156
+ : "/me/onenote/pages";
157
+ const res = await graphFetch(path);
158
+ const data: GraphResponse<any> = await res.json();
159
+ return data.value;
160
+ }
161
+
162
+ export async function getPage(id: string) {
163
+ const res = await graphFetch(`/me/onenote/pages/${id}`);
164
+ return res.json();
165
+ }
166
+
167
+ export async function getPageContent(id: string): Promise<string> {
168
+ const res = await graphFetch(`/me/onenote/pages/${id}/content`);
169
+ return res.text();
170
+ }
171
+
172
+ export async function createPage(sectionId: string, title: string, htmlBody: string) {
173
+ const html = `
174
+ <!DOCTYPE html>
175
+ <html>
176
+ <head>
177
+ <title>${title}</title>
178
+ </head>
179
+ <body>
180
+ ${htmlBody}
181
+ </body>
182
+ </html>`;
183
+
184
+ const res = await graphFetch(`/me/onenote/sections/${sectionId}/pages`, {
185
+ method: "POST",
186
+ headers: { "Content-Type": "text/html" },
187
+ body: html,
188
+ });
189
+ return res.json();
190
+ }
191
+
192
+ export async function deletePage(id: string) {
193
+ await graphFetch(`/me/onenote/pages/${id}`, { method: "DELETE" });
194
+ }
195
+
196
+ // --- Search ---
197
+
198
+ async function resolveOneNoteUrl(filePath: string): Promise<string> {
199
+ // Get the driveItem to obtain the proper Doc.aspx URL with sourcedoc GUID
200
+ try {
201
+ const match = decodeURIComponent(new URL(filePath).pathname).match(/Documents\/(.+)/);
202
+ if (!match) return filePath;
203
+ const encoded = match[1].split("/").map((s) => encodeURIComponent(s)).join("/");
204
+ const res = await graphFetch(`/me/drive/root:/${encoded}?$select=webUrl`);
205
+ const item = (await res.json()) as any;
206
+ // webUrl has format: ...Doc.aspx?sourcedoc={GUID}&file=name.one&action=edit...
207
+ // Clean it up
208
+ return item.webUrl?.split("&mobileredirect")[0] ?? filePath;
209
+ } catch {
210
+ return filePath;
211
+ }
212
+ }
213
+
214
+ export async function searchPages(query: string) {
215
+ // Use Microsoft Graph Search API with listItem to search OneNote content (full-text)
216
+ const res = await graphFetch("/search/query", {
217
+ method: "POST",
218
+ body: JSON.stringify({
219
+ requests: [
220
+ {
221
+ entityTypes: ["listItem"],
222
+ query: { queryString: `${query} FileExtension:one` },
223
+ from: 0,
224
+ size: 25,
225
+ fields: ["title", "path", "parentLink", "spWebUrl"],
226
+ },
227
+ ],
228
+ }),
229
+ });
230
+ const data = (await res.json()) as any;
231
+ const hits = data.value?.[0]?.hitsContainers?.[0]?.hits ?? [];
232
+ const results = hits
233
+ .map((hit: any) => {
234
+ const r = hit.resource ?? {};
235
+ const fields = r.fields ?? {};
236
+ const rawSummary = (hit.summary ?? "")
237
+ .replace(/<c0>/g, "**")
238
+ .replace(/<\/c0>/g, "**")
239
+ .replace(/<ddd\/>/g, "...")
240
+ .replace(/<[^>]*>/g, "")
241
+ .trim();
242
+ const filePath = fields.path ?? r.webUrl ?? "";
243
+ const pathMatch = filePath.match(/Documents\/(?:Notebooks\/)?([^/]+)\//);
244
+ const notebook = pathMatch ? decodeURIComponent(pathMatch[1]) : "";
245
+ return {
246
+ id: r.id ?? hit.hitId,
247
+ section: fields.title ?? (r.name ?? "").replace(/\.one$/, ""),
248
+ notebook,
249
+ summary: rawSummary,
250
+ filePath,
251
+ url: "",
252
+ lastModifiedDateTime: r.lastModifiedDateTime ?? "",
253
+ };
254
+ })
255
+ .filter((r: any) => r.summary.length > 0);
256
+
257
+ // Resolve proper OneNote Online URLs in parallel
258
+ await Promise.all(
259
+ results.map(async (r: any) => {
260
+ r.url = await resolveOneNoteUrl(r.filePath);
261
+ })
262
+ );
263
+ return results;
264
+ }
package/src/index.ts ADDED
@@ -0,0 +1,408 @@
1
+ #!/usr/bin/env bun
2
+ import yargs from "yargs";
3
+ import { hideBin } from "yargs/helpers";
4
+ import * as graph from "./graph";
5
+ import { logout, whoami } from "./auth";
6
+ import { syncCache, searchLocal, isCacheEmpty } from "./cache";
7
+
8
+ const isTTY = process.stdout.isTTY ?? false;
9
+
10
+ // ANSI color helpers (only when TTY)
11
+ const bold = (s: string) => (isTTY ? `\x1b[1m${s}\x1b[0m` : s);
12
+ const dim = (s: string) => (isTTY ? `\x1b[2m${s}\x1b[0m` : s);
13
+ const yellow = (s: string) => (isTTY ? `\x1b[33m${s}\x1b[0m` : s);
14
+ const cyan = (s: string) => (isTTY ? `\x1b[36m${s}\x1b[0m` : s);
15
+ const green = (s: string) => (isTTY ? `\x1b[32m${s}\x1b[0m` : s);
16
+ const magenta = (s: string) => (isTTY ? `\x1b[35m${s}\x1b[0m` : s);
17
+
18
+ // OSC 8 hyperlink (clickable in supported terminals), markdown in non-TTY
19
+ function link(url: string, text: string): string {
20
+ if (!isTTY) return `[${text}](${url})`;
21
+ return `\x1b]8;;${url}\x1b\\${text}\x1b]8;;\x1b\\`;
22
+ }
23
+
24
+ function formatTable(items: any[], columns: { key: string; label: string }[]) {
25
+ if (!items || items.length === 0) {
26
+ console.log("No results found.");
27
+ return;
28
+ }
29
+ const widths = columns.map((col) =>
30
+ Math.max(col.label.length, ...items.map((item) => String(item[col.key] ?? "").length))
31
+ );
32
+ const header = columns.map((col, i) => col.label.padEnd(widths[i])).join(" ");
33
+ const separator = widths.map((w) => "-".repeat(w)).join(" ");
34
+ console.log(header);
35
+ console.log(separator);
36
+ for (const item of items) {
37
+ const row = columns.map((col, i) => String(item[col.key] ?? "").padEnd(widths[i])).join(" ");
38
+ console.log(row);
39
+ }
40
+ }
41
+
42
+ yargs(hideBin(process.argv))
43
+ .scriptName("onenote")
44
+ .usage("$0 <command> [options]")
45
+ .demandCommand(1)
46
+
47
+ // --- notebooks ---
48
+ .command(
49
+ "notebooks",
50
+ "Manage notebooks",
51
+ (yargs) =>
52
+ yargs
53
+ .command(
54
+ "list",
55
+ "List all notebooks",
56
+ () => {},
57
+ async () => {
58
+ const notebooks = await graph.listNotebooks();
59
+ formatTable(notebooks, [
60
+ { key: "id", label: "ID" },
61
+ { key: "displayName", label: "Name" },
62
+ { key: "createdDateTime", label: "Created" },
63
+ { key: "lastModifiedDateTime", label: "Modified" },
64
+ ]);
65
+ }
66
+ )
67
+ .command(
68
+ "get <id>",
69
+ "Get a notebook by ID",
70
+ (y) => y.positional("id", { type: "string", demandOption: true }),
71
+ async (argv) => {
72
+ const nb = await graph.getNotebook(argv.id as string);
73
+ console.log(JSON.stringify(nb, null, 2));
74
+ }
75
+ )
76
+ .command(
77
+ "create <name>",
78
+ "Create a new notebook",
79
+ (y) => y.positional("name", { type: "string", demandOption: true }),
80
+ async (argv) => {
81
+ const nb = await graph.createNotebook(argv.name as string);
82
+ console.log("Created notebook:", nb.displayName);
83
+ console.log("ID:", nb.id);
84
+ }
85
+ )
86
+ .demandCommand(1),
87
+ () => {}
88
+ )
89
+
90
+ // --- sections ---
91
+ .command(
92
+ "sections",
93
+ "Manage sections",
94
+ (yargs) =>
95
+ yargs
96
+ .command(
97
+ "list",
98
+ "List sections",
99
+ (y) => y.option("notebook-id", { type: "string", alias: "n", describe: "Filter by notebook ID" }),
100
+ async (argv) => {
101
+ const sections = await graph.listSections(argv.notebookId as string | undefined);
102
+ formatTable(sections, [
103
+ { key: "id", label: "ID" },
104
+ { key: "displayName", label: "Name" },
105
+ { key: "createdDateTime", label: "Created" },
106
+ ]);
107
+ }
108
+ )
109
+ .command(
110
+ "get <id>",
111
+ "Get a section by ID",
112
+ (y) => y.positional("id", { type: "string", demandOption: true }),
113
+ async (argv) => {
114
+ const section = await graph.getSection(argv.id as string);
115
+ console.log(JSON.stringify(section, null, 2));
116
+ }
117
+ )
118
+ .command(
119
+ "create",
120
+ "Create a new section in a notebook",
121
+ (y) =>
122
+ y
123
+ .option("notebook-id", { type: "string", alias: "n", demandOption: true, describe: "Notebook ID" })
124
+ .option("name", { type: "string", demandOption: true, describe: "Section name" }),
125
+ async (argv) => {
126
+ const section = await graph.createSection(argv.notebookId as string, argv.name as string);
127
+ console.log("Created section:", section.displayName);
128
+ console.log("ID:", section.id);
129
+ }
130
+ )
131
+ .demandCommand(1),
132
+ () => {}
133
+ )
134
+
135
+ // --- section-groups ---
136
+ .command(
137
+ "section-groups",
138
+ "Manage section groups",
139
+ (yargs) =>
140
+ yargs
141
+ .command(
142
+ "list",
143
+ "List section groups",
144
+ (y) => y.option("notebook-id", { type: "string", alias: "n", describe: "Filter by notebook ID" }),
145
+ async (argv) => {
146
+ const groups = await graph.listSectionGroups(argv.notebookId as string | undefined);
147
+ formatTable(groups, [
148
+ { key: "id", label: "ID" },
149
+ { key: "displayName", label: "Name" },
150
+ { key: "createdDateTime", label: "Created" },
151
+ ]);
152
+ }
153
+ )
154
+ .demandCommand(1),
155
+ () => {}
156
+ )
157
+
158
+ // --- pages ---
159
+ .command(
160
+ "pages",
161
+ "Manage pages",
162
+ (yargs) =>
163
+ yargs
164
+ .command(
165
+ "list",
166
+ "List pages",
167
+ (y) => y.option("section-id", { type: "string", alias: "s", describe: "Filter by section ID" }),
168
+ async (argv) => {
169
+ const pages = await graph.listPages(argv.sectionId as string | undefined);
170
+ formatTable(pages, [
171
+ { key: "id", label: "ID" },
172
+ { key: "title", label: "Title" },
173
+ { key: "createdDateTime", label: "Created" },
174
+ { key: "lastModifiedDateTime", label: "Modified" },
175
+ ]);
176
+ }
177
+ )
178
+ .command(
179
+ "get <id>",
180
+ "Get page metadata",
181
+ (y) => y.positional("id", { type: "string", demandOption: true }),
182
+ async (argv) => {
183
+ const page = await graph.getPage(argv.id as string);
184
+ console.log(JSON.stringify(page, null, 2));
185
+ }
186
+ )
187
+ .command(
188
+ "content <id>",
189
+ "Get page HTML content",
190
+ (y) => y.positional("id", { type: "string", demandOption: true }),
191
+ async (argv) => {
192
+ const html = await graph.getPageContent(argv.id as string);
193
+ console.log(html);
194
+ }
195
+ )
196
+ .command(
197
+ "create",
198
+ "Create a new page",
199
+ (y) =>
200
+ y
201
+ .option("section-id", { type: "string", alias: "s", demandOption: true, describe: "Section ID" })
202
+ .option("title", { type: "string", alias: "t", demandOption: true, describe: "Page title" })
203
+ .option("body", { type: "string", alias: "b", default: "<p></p>", describe: "HTML body content" }),
204
+ async (argv) => {
205
+ const page = await graph.createPage(
206
+ argv.sectionId as string,
207
+ argv.title as string,
208
+ argv.body as string
209
+ );
210
+ console.log("Created page:", page.title);
211
+ console.log("ID:", page.id);
212
+ }
213
+ )
214
+ .command(
215
+ "delete <id>",
216
+ "Delete a page",
217
+ (y) => y.positional("id", { type: "string", demandOption: true }),
218
+ async (argv) => {
219
+ await graph.deletePage(argv.id as string);
220
+ console.log("Page deleted.");
221
+ }
222
+ )
223
+ .demandCommand(1),
224
+ () => {}
225
+ )
226
+
227
+ // --- sync ---
228
+ .command(
229
+ "sync",
230
+ "Download and cache all OneNote sections for local search",
231
+ () => {},
232
+ async () => {
233
+ await syncCache();
234
+ }
235
+ )
236
+
237
+ // --- search ---
238
+ .command(
239
+ "search <query>",
240
+ "Full-text search across OneNote pages (uses local cache)",
241
+ (y) =>
242
+ y
243
+ .positional("query", { type: "string", demandOption: true })
244
+ .option("online", { type: "boolean", alias: "o", describe: "Use online Graph Search API (section-level)" }),
245
+ async (argv) => {
246
+ if (argv.online) {
247
+ const results = await graph.searchPages(argv.query as string);
248
+ if (!results || results.length === 0) {
249
+ console.log("No results found.");
250
+ return;
251
+ }
252
+ for (const r of results) {
253
+ const heading = r.notebook ? `${r.section} (${r.notebook})` : r.section;
254
+ console.log(`# ${heading}`);
255
+ if (r.summary) console.log(` ${r.summary}`);
256
+ if (r.url) console.log(` ${r.url}`);
257
+ console.log();
258
+ }
259
+ console.log(`${results.length} results found.`);
260
+ return;
261
+ }
262
+
263
+ // Auto-sync if cache is empty
264
+ if (await isCacheEmpty()) {
265
+ console.log("Cache is empty. Syncing notebooks...");
266
+ await syncCache();
267
+ }
268
+
269
+ // Local page-level search
270
+ // Strip surrounding quotes from query (shell may pass "word" or 'word')
271
+ const query = (argv.query as string).replace(/^["']|["']$/g, "");
272
+ const results = await searchLocal(query);
273
+ if (results.length === 0) {
274
+ console.log("No results found.");
275
+ return;
276
+ }
277
+ // Clean snippet text: remove binary noise characters
278
+ const cleanSnippet = (s: string) =>
279
+ s.replace(/[\u0000-\u001F\u007F-\u009F]/g, " ")
280
+ .replace(/[^\x20-\x7E\u00A0-\u024F\u0370-\u058F\u0600-\u06FF\u3000-\u30FF\u3400-\u9FFF\uAC00-\uD7AF\uFF00-\uFFEF\s.,;:!?@#\-_()[\]{}'"\/\\=+<>|~`^&*%$\u2000-\u206F]/g, "")
281
+ .replace(/\s{2,}/g, " ")
282
+ .trim();
283
+
284
+ const lowerQuery = query.toLowerCase();
285
+ for (const r of results) {
286
+ // Skip results with garbage/attachment titles
287
+ if (/^\.[a-z0-9]{2,5}$/i.test(r.title.trim())) continue;
288
+ const printable = r.title.replace(/[^\x20-\x7E\u3000-\u30FF\u4E00-\u9FFF\uAC00-\uD7AF\uFF00-\uFFEF\u00A0-\u024F]/g, "");
289
+ if (printable.length < 3 || printable.length / r.title.length < 0.4) continue;
290
+
291
+ // Title as clickable hyperlink (OSC 8 in TTY, markdown in non-TTY)
292
+ const displayTitle = r.webUrl ? link(r.webUrl, bold(cyan(r.title))) : bold(r.title);
293
+ console.log(displayTitle);
294
+ console.log(` ${dim(r.section)} ${dim("|")} ${dim(r.notebook)}`);
295
+
296
+ // Show context around the match with highlighted keyword
297
+ const body = cleanSnippet(r.body);
298
+ const idx = body.toLowerCase().indexOf(lowerQuery);
299
+ if (idx >= 0) {
300
+ const start = Math.max(0, idx - 40);
301
+ const end = Math.min(body.length, idx + query.length + 80);
302
+ const before = body.slice(start, idx);
303
+ const match = body.slice(idx, idx + query.length);
304
+ const after = body.slice(idx + query.length, end);
305
+ const snippet = (start > 0 ? "..." : "") + before + yellow(bold(match)) + after + (end < body.length ? "..." : "");
306
+ console.log(` ${snippet}`);
307
+ }
308
+ console.log();
309
+ }
310
+ console.log(green(`${results.length} page-level results found.`));
311
+ }
312
+ )
313
+
314
+ // --- auth ---
315
+ .command(
316
+ "auth",
317
+ "Manage authentication",
318
+ (yargs) =>
319
+ yargs
320
+ .command(
321
+ "login",
322
+ "Login to Microsoft account (device code flow)",
323
+ () => {},
324
+ async () => {
325
+ const { getAccessToken } = await import("./auth");
326
+ await getAccessToken();
327
+ console.log("Login successful!");
328
+ }
329
+ )
330
+ .command(
331
+ "logout",
332
+ "Clear cached authentication tokens",
333
+ () => {},
334
+ async () => {
335
+ await logout();
336
+ }
337
+ )
338
+ .command(
339
+ "whoami",
340
+ "Show current authenticated user",
341
+ () => {},
342
+ async () => {
343
+ await whoami();
344
+ }
345
+ )
346
+ .command(
347
+ "setup",
348
+ "Guide for setting up OAuth client credentials",
349
+ () => {},
350
+ async () => {
351
+ console.log(`
352
+ === onenote-cli OAuth Setup ===
353
+
354
+ 1. Register an Azure AD app:
355
+ https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps
356
+
357
+ - Click "New registration"
358
+ - Name: onenote-cli
359
+ - Supported account types: "Accounts in any organizational directory and personal Microsoft accounts"
360
+ - Click "Register"
361
+
362
+ 2. Configure authentication:
363
+ - Go to "Authentication" > "Add a platform" > "Mobile and desktop applications"
364
+ - Check: https://login.microsoftonline.com/common/oauth2/nativeclient
365
+ - Enable "Allow public client flows" in Settings tab
366
+ - Click "Save"
367
+
368
+ 3. Add API permissions:
369
+ - Go to "API permissions" > "Add a permission" > "Microsoft Graph" > "Delegated permissions"
370
+ - Add: Notes.Read, Notes.ReadWrite, Notes.ReadWrite.All
371
+ - Click "Add permissions"
372
+
373
+ 4. Copy your Application (client) ID and set it:
374
+
375
+ Option A — .env.local:
376
+ ONENOTE_CLIENT_ID=<your-client-id>
377
+ ONENOTE_AUTHORITY=https://login.microsoftonline.com/common
378
+
379
+ Option B — ~/.onenote-cli/config.json:
380
+ { "clientId": "<your-client-id>", "authority": "https://login.microsoftonline.com/common" }
381
+
382
+ 5. Login:
383
+ onenote auth login
384
+ `);
385
+ }
386
+ )
387
+ .demandCommand(1),
388
+ () => {}
389
+ )
390
+
391
+ .strict()
392
+ .help()
393
+ .alias("h", "help")
394
+ .version()
395
+ .alias("v", "version")
396
+ .showHelpOnFail(true)
397
+ .fail((msg, err, yargs) => {
398
+ if (err) {
399
+ console.error("Error:", err.message);
400
+ process.exit(1);
401
+ }
402
+ if (msg) {
403
+ yargs.showHelp();
404
+ console.error("\n" + msg);
405
+ process.exit(1);
406
+ }
407
+ })
408
+ .parse();
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "outDir": "dist",
10
+ "rootDir": "src",
11
+ "types": ["bun-types"]
12
+ },
13
+ "include": ["src"]
14
+ }