@andrzejchm/notion-cli 0.4.1 → 0.6.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/dist/cli.js +419 -755
- package/dist/cli.js.map +1 -1
- package/docs/skills/using-notion-cli/SKILL.md +42 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -4,229 +4,11 @@
|
|
|
4
4
|
import { readFileSync } from "fs";
|
|
5
5
|
import { dirname, join as join3 } from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
|
-
import { Command as
|
|
7
|
+
import { Command as Command21 } from "commander";
|
|
8
8
|
|
|
9
9
|
// src/commands/append.ts
|
|
10
10
|
import { Command } from "commander";
|
|
11
11
|
|
|
12
|
-
// src/blocks/md-to-blocks.ts
|
|
13
|
-
var INLINE_RE = /(\*\*[^*]+\*\*|_[^_]+_|\*[^*]+\*|`[^`]+`|\[[^\]]+\]\([^)]+\)|[^*_`[]+)/g;
|
|
14
|
-
function parseInlineMarkdown(text) {
|
|
15
|
-
const result = [];
|
|
16
|
-
let match;
|
|
17
|
-
INLINE_RE.lastIndex = 0;
|
|
18
|
-
while ((match = INLINE_RE.exec(text)) !== null) {
|
|
19
|
-
const segment = match[0];
|
|
20
|
-
if (segment.startsWith("**") && segment.endsWith("**")) {
|
|
21
|
-
const content = segment.slice(2, -2);
|
|
22
|
-
result.push({
|
|
23
|
-
type: "text",
|
|
24
|
-
text: { content, link: null },
|
|
25
|
-
annotations: {
|
|
26
|
-
bold: true,
|
|
27
|
-
italic: false,
|
|
28
|
-
strikethrough: false,
|
|
29
|
-
underline: false,
|
|
30
|
-
code: false,
|
|
31
|
-
color: "default"
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
if (segment.startsWith("_") && segment.endsWith("_") || segment.startsWith("*") && segment.endsWith("*")) {
|
|
37
|
-
const content = segment.slice(1, -1);
|
|
38
|
-
result.push({
|
|
39
|
-
type: "text",
|
|
40
|
-
text: { content, link: null },
|
|
41
|
-
annotations: {
|
|
42
|
-
bold: false,
|
|
43
|
-
italic: true,
|
|
44
|
-
strikethrough: false,
|
|
45
|
-
underline: false,
|
|
46
|
-
code: false,
|
|
47
|
-
color: "default"
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
if (segment.startsWith("`") && segment.endsWith("`")) {
|
|
53
|
-
const content = segment.slice(1, -1);
|
|
54
|
-
result.push({
|
|
55
|
-
type: "text",
|
|
56
|
-
text: { content, link: null },
|
|
57
|
-
annotations: {
|
|
58
|
-
bold: false,
|
|
59
|
-
italic: false,
|
|
60
|
-
strikethrough: false,
|
|
61
|
-
underline: false,
|
|
62
|
-
code: true,
|
|
63
|
-
color: "default"
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
const linkMatch = segment.match(/^\[([^\]]+)\]\(([^)]+)\)$/);
|
|
69
|
-
if (linkMatch) {
|
|
70
|
-
const [, label, url] = linkMatch;
|
|
71
|
-
result.push({
|
|
72
|
-
type: "text",
|
|
73
|
-
text: { content: label, link: { url } },
|
|
74
|
-
annotations: {
|
|
75
|
-
bold: false,
|
|
76
|
-
italic: false,
|
|
77
|
-
strikethrough: false,
|
|
78
|
-
underline: false,
|
|
79
|
-
code: false,
|
|
80
|
-
color: "default"
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
result.push({
|
|
86
|
-
type: "text",
|
|
87
|
-
text: { content: segment, link: null },
|
|
88
|
-
annotations: {
|
|
89
|
-
bold: false,
|
|
90
|
-
italic: false,
|
|
91
|
-
strikethrough: false,
|
|
92
|
-
underline: false,
|
|
93
|
-
code: false,
|
|
94
|
-
color: "default"
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
return result;
|
|
99
|
-
}
|
|
100
|
-
function makeRichText(text) {
|
|
101
|
-
return parseInlineMarkdown(text);
|
|
102
|
-
}
|
|
103
|
-
function mdToBlocks(md) {
|
|
104
|
-
if (!md) return [];
|
|
105
|
-
const lines = md.split("\n");
|
|
106
|
-
const blocks = [];
|
|
107
|
-
let inFence = false;
|
|
108
|
-
let fenceLang = "";
|
|
109
|
-
const fenceLines = [];
|
|
110
|
-
for (const line of lines) {
|
|
111
|
-
if (!inFence && line.startsWith("```")) {
|
|
112
|
-
inFence = true;
|
|
113
|
-
fenceLang = line.slice(3).trim() || "plain text";
|
|
114
|
-
fenceLines.length = 0;
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
if (inFence) {
|
|
118
|
-
if (line.startsWith("```")) {
|
|
119
|
-
const content = fenceLines.join("\n");
|
|
120
|
-
blocks.push({
|
|
121
|
-
type: "code",
|
|
122
|
-
code: {
|
|
123
|
-
rich_text: [
|
|
124
|
-
{
|
|
125
|
-
type: "text",
|
|
126
|
-
text: { content, link: null },
|
|
127
|
-
annotations: {
|
|
128
|
-
bold: false,
|
|
129
|
-
italic: false,
|
|
130
|
-
strikethrough: false,
|
|
131
|
-
underline: false,
|
|
132
|
-
code: false,
|
|
133
|
-
color: "default"
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
],
|
|
137
|
-
// biome-ignore lint/suspicious/noExplicitAny: Notion SDK language type is too narrow
|
|
138
|
-
language: fenceLang
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
inFence = false;
|
|
142
|
-
fenceLang = "";
|
|
143
|
-
fenceLines.length = 0;
|
|
144
|
-
} else {
|
|
145
|
-
fenceLines.push(line);
|
|
146
|
-
}
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
if (line.trim() === "") continue;
|
|
150
|
-
const h1 = line.match(/^# (.+)$/);
|
|
151
|
-
if (h1) {
|
|
152
|
-
blocks.push({
|
|
153
|
-
type: "heading_1",
|
|
154
|
-
heading_1: { rich_text: makeRichText(h1[1]) }
|
|
155
|
-
});
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
const h2 = line.match(/^## (.+)$/);
|
|
159
|
-
if (h2) {
|
|
160
|
-
blocks.push({
|
|
161
|
-
type: "heading_2",
|
|
162
|
-
heading_2: { rich_text: makeRichText(h2[1]) }
|
|
163
|
-
});
|
|
164
|
-
continue;
|
|
165
|
-
}
|
|
166
|
-
const h3 = line.match(/^### (.+)$/);
|
|
167
|
-
if (h3) {
|
|
168
|
-
blocks.push({
|
|
169
|
-
type: "heading_3",
|
|
170
|
-
heading_3: { rich_text: makeRichText(h3[1]) }
|
|
171
|
-
});
|
|
172
|
-
continue;
|
|
173
|
-
}
|
|
174
|
-
const bullet = line.match(/^[-*] (.+)$/);
|
|
175
|
-
if (bullet) {
|
|
176
|
-
blocks.push({
|
|
177
|
-
type: "bulleted_list_item",
|
|
178
|
-
bulleted_list_item: { rich_text: makeRichText(bullet[1]) }
|
|
179
|
-
});
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
182
|
-
const numbered = line.match(/^\d+\. (.+)$/);
|
|
183
|
-
if (numbered) {
|
|
184
|
-
blocks.push({
|
|
185
|
-
type: "numbered_list_item",
|
|
186
|
-
numbered_list_item: { rich_text: makeRichText(numbered[1]) }
|
|
187
|
-
});
|
|
188
|
-
continue;
|
|
189
|
-
}
|
|
190
|
-
const quote = line.match(/^> (.+)$/);
|
|
191
|
-
if (quote) {
|
|
192
|
-
blocks.push({
|
|
193
|
-
type: "quote",
|
|
194
|
-
quote: { rich_text: makeRichText(quote[1]) }
|
|
195
|
-
});
|
|
196
|
-
continue;
|
|
197
|
-
}
|
|
198
|
-
blocks.push({
|
|
199
|
-
type: "paragraph",
|
|
200
|
-
paragraph: { rich_text: makeRichText(line) }
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
if (inFence && fenceLines.length > 0) {
|
|
204
|
-
const content = fenceLines.join("\n");
|
|
205
|
-
blocks.push({
|
|
206
|
-
type: "code",
|
|
207
|
-
code: {
|
|
208
|
-
rich_text: [
|
|
209
|
-
{
|
|
210
|
-
type: "text",
|
|
211
|
-
text: { content, link: null },
|
|
212
|
-
annotations: {
|
|
213
|
-
bold: false,
|
|
214
|
-
italic: false,
|
|
215
|
-
strikethrough: false,
|
|
216
|
-
underline: false,
|
|
217
|
-
code: false,
|
|
218
|
-
color: "default"
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
],
|
|
222
|
-
// biome-ignore lint/suspicious/noExplicitAny: Notion SDK language type is too narrow
|
|
223
|
-
language: fenceLang
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
return blocks;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
12
|
// src/errors/cli-error.ts
|
|
231
13
|
var CliError = class extends Error {
|
|
232
14
|
constructor(code, message, suggestion, cause) {
|
|
@@ -662,6 +444,12 @@ function withErrorHandling(fn) {
|
|
|
662
444
|
});
|
|
663
445
|
}
|
|
664
446
|
|
|
447
|
+
// src/errors/notion-errors.ts
|
|
448
|
+
var SELECTOR_HINT = 'Use an ellipsis selector matching page content, e.g. "## Section...end of section". Run `notion read <id>` to see the page content.';
|
|
449
|
+
function isNotionValidationError(error2) {
|
|
450
|
+
return typeof error2 === "object" && error2 !== null && "code" in error2 && error2.code === "validation_error";
|
|
451
|
+
}
|
|
452
|
+
|
|
665
453
|
// src/notion/client.ts
|
|
666
454
|
import { APIErrorCode, Client, isNotionClientError } from "@notionhq/client";
|
|
667
455
|
async function validateToken(token) {
|
|
@@ -775,13 +563,79 @@ async function addComment(client, pageId, text, options = {}) {
|
|
|
775
563
|
...options.asUser && { display_name: { type: "user" } }
|
|
776
564
|
});
|
|
777
565
|
}
|
|
778
|
-
async function
|
|
779
|
-
await client.
|
|
780
|
-
|
|
781
|
-
|
|
566
|
+
async function appendMarkdown(client, pageId, markdown, options) {
|
|
567
|
+
await client.pages.updateMarkdown({
|
|
568
|
+
page_id: pageId,
|
|
569
|
+
type: "insert_content",
|
|
570
|
+
insert_content: {
|
|
571
|
+
content: markdown,
|
|
572
|
+
...options?.after != null && { after: options.after }
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
function countOccurrences(text, sub) {
|
|
577
|
+
if (!sub) return 0;
|
|
578
|
+
let count = 0;
|
|
579
|
+
let pos = text.indexOf(sub, 0);
|
|
580
|
+
while (pos !== -1) {
|
|
581
|
+
count++;
|
|
582
|
+
pos = text.indexOf(sub, pos + sub.length);
|
|
583
|
+
}
|
|
584
|
+
return count;
|
|
585
|
+
}
|
|
586
|
+
function buildContentRange(content) {
|
|
587
|
+
const START_LEN = 15;
|
|
588
|
+
const STEP = 10;
|
|
589
|
+
if (content.length <= START_LEN * 2) {
|
|
590
|
+
return content;
|
|
591
|
+
}
|
|
592
|
+
const start = content.slice(0, START_LEN);
|
|
593
|
+
for (let endLen = START_LEN; endLen < content.length - START_LEN; endLen += STEP) {
|
|
594
|
+
const end = content.slice(-endLen);
|
|
595
|
+
if (countOccurrences(content, end) === 1) {
|
|
596
|
+
return `${start}...${end}`;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return content;
|
|
600
|
+
}
|
|
601
|
+
async function replaceMarkdown(client, pageId, newMarkdown, options) {
|
|
602
|
+
const current = await client.pages.retrieveMarkdown({ page_id: pageId });
|
|
603
|
+
const currentContent = current.markdown.trim();
|
|
604
|
+
if (current.truncated && !options?.range) {
|
|
605
|
+
throw new CliError(
|
|
606
|
+
ErrorCodes.API_ERROR,
|
|
607
|
+
"Page content is too large for full-page replace (markdown was truncated by the API).",
|
|
608
|
+
"Use --range to replace a specific section instead."
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
if (!currentContent) {
|
|
612
|
+
if (options?.range) {
|
|
613
|
+
process.stderr.write(
|
|
614
|
+
"Warning: page is empty, --range ignored, content inserted as-is.\n"
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
if (newMarkdown.trim()) {
|
|
618
|
+
await client.pages.updateMarkdown({
|
|
619
|
+
page_id: pageId,
|
|
620
|
+
type: "insert_content",
|
|
621
|
+
insert_content: { content: newMarkdown }
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
const contentRange = options?.range ?? buildContentRange(currentContent);
|
|
627
|
+
const allowDeletingContent = options?.allowDeletingContent ?? options?.range == null;
|
|
628
|
+
await client.pages.updateMarkdown({
|
|
629
|
+
page_id: pageId,
|
|
630
|
+
type: "replace_content_range",
|
|
631
|
+
replace_content_range: {
|
|
632
|
+
content: newMarkdown,
|
|
633
|
+
content_range: contentRange,
|
|
634
|
+
allow_deleting_content: allowDeletingContent
|
|
635
|
+
}
|
|
782
636
|
});
|
|
783
637
|
}
|
|
784
|
-
async function createPage(client, parentId, title,
|
|
638
|
+
async function createPage(client, parentId, title, markdown) {
|
|
785
639
|
const response = await client.pages.create({
|
|
786
640
|
parent: { type: "page_id", page_id: parentId },
|
|
787
641
|
properties: {
|
|
@@ -789,30 +643,72 @@ async function createPage(client, parentId, title, blocks) {
|
|
|
789
643
|
title: [{ type: "text", text: { content: title, link: null } }]
|
|
790
644
|
}
|
|
791
645
|
},
|
|
792
|
-
|
|
646
|
+
...markdown.trim() ? { markdown } : {}
|
|
793
647
|
});
|
|
794
|
-
|
|
648
|
+
const url = "url" in response ? response.url : response.id;
|
|
649
|
+
return url;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// src/utils/stdin.ts
|
|
653
|
+
async function readStdin() {
|
|
654
|
+
const chunks = [];
|
|
655
|
+
for await (const chunk of process.stdin) {
|
|
656
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
657
|
+
}
|
|
658
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
795
659
|
}
|
|
796
660
|
|
|
797
661
|
// src/commands/append.ts
|
|
798
662
|
function appendCommand() {
|
|
799
663
|
const cmd = new Command("append");
|
|
800
|
-
cmd.description("append markdown content to a Notion page").argument("<id/url>", "Notion page ID or URL").
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
664
|
+
cmd.description("append markdown content to a Notion page").argument("<id/url>", "Notion page ID or URL").option("-m, --message <markdown>", "markdown content to append").option(
|
|
665
|
+
"--after <selector>",
|
|
666
|
+
'insert after matched content \u2014 ellipsis selector, e.g. "## Section...end of section"'
|
|
667
|
+
).action(
|
|
668
|
+
withErrorHandling(
|
|
669
|
+
async (idOrUrl, opts) => {
|
|
670
|
+
const { token, source } = await resolveToken();
|
|
671
|
+
reportTokenSource(source);
|
|
672
|
+
const client = createNotionClient(token);
|
|
673
|
+
let markdown = "";
|
|
674
|
+
if (opts.message) {
|
|
675
|
+
markdown = opts.message;
|
|
676
|
+
} else if (!process.stdin.isTTY) {
|
|
677
|
+
markdown = await readStdin();
|
|
678
|
+
} else {
|
|
679
|
+
throw new CliError(
|
|
680
|
+
ErrorCodes.INVALID_ARG,
|
|
681
|
+
"No content to append.",
|
|
682
|
+
"Pass markdown via -m/--message or pipe it through stdin"
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
if (!markdown.trim()) {
|
|
686
|
+
process.stdout.write("Nothing to append.\n");
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
const pageId = parseNotionId(idOrUrl);
|
|
690
|
+
const uuid = toUuid(pageId);
|
|
691
|
+
try {
|
|
692
|
+
await appendMarkdown(
|
|
693
|
+
client,
|
|
694
|
+
uuid,
|
|
695
|
+
markdown,
|
|
696
|
+
opts.after ? { after: opts.after } : void 0
|
|
697
|
+
);
|
|
698
|
+
} catch (error2) {
|
|
699
|
+
if (opts.after && isNotionValidationError(error2)) {
|
|
700
|
+
throw new CliError(
|
|
701
|
+
ErrorCodes.INVALID_ARG,
|
|
702
|
+
`Selector not found: "${opts.after}". ${error2.message}`,
|
|
703
|
+
SELECTOR_HINT,
|
|
704
|
+
error2
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
throw error2;
|
|
708
|
+
}
|
|
709
|
+
process.stdout.write("Appended.\n");
|
|
811
710
|
}
|
|
812
|
-
|
|
813
|
-
process.stdout.write(`Appended ${blocks.length} block(s).
|
|
814
|
-
`);
|
|
815
|
-
})
|
|
711
|
+
)
|
|
816
712
|
);
|
|
817
713
|
return cmd;
|
|
818
714
|
}
|
|
@@ -825,8 +721,8 @@ function authDefaultAction(authCmd2) {
|
|
|
825
721
|
}
|
|
826
722
|
|
|
827
723
|
// src/commands/auth/login.ts
|
|
828
|
-
import { input
|
|
829
|
-
import { Command as
|
|
724
|
+
import { input } from "@inquirer/prompts";
|
|
725
|
+
import { Command as Command2 } from "commander";
|
|
830
726
|
|
|
831
727
|
// src/oauth/oauth-flow.ts
|
|
832
728
|
import { spawn } from "child_process";
|
|
@@ -1071,108 +967,10 @@ Waiting for callback (up to 120 seconds)...
|
|
|
1071
967
|
});
|
|
1072
968
|
}
|
|
1073
969
|
|
|
1074
|
-
// src/commands/init.ts
|
|
1075
|
-
import { confirm, input, password } from "@inquirer/prompts";
|
|
1076
|
-
import { Command as Command2 } from "commander";
|
|
1077
|
-
async function runInitFlow() {
|
|
1078
|
-
const profileName = await input({
|
|
1079
|
-
message: "Profile name:",
|
|
1080
|
-
default: "default"
|
|
1081
|
-
});
|
|
1082
|
-
const token = await password({
|
|
1083
|
-
message: "Integration token (from notion.so/profile/integrations/internal):",
|
|
1084
|
-
mask: "*"
|
|
1085
|
-
});
|
|
1086
|
-
stderrWrite("Validating token...");
|
|
1087
|
-
const { workspaceName, workspaceId } = await validateToken(token);
|
|
1088
|
-
stderrWrite(success(`\u2713 Connected to workspace: ${bold(workspaceName)}`));
|
|
1089
|
-
const config = await readGlobalConfig();
|
|
1090
|
-
if (config.profiles?.[profileName]) {
|
|
1091
|
-
const replace = await confirm({
|
|
1092
|
-
message: `Profile "${profileName}" already exists. Replace?`,
|
|
1093
|
-
default: false
|
|
1094
|
-
});
|
|
1095
|
-
if (!replace) {
|
|
1096
|
-
stderrWrite("Aborted.");
|
|
1097
|
-
return;
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
const profiles = config.profiles ?? {};
|
|
1101
|
-
profiles[profileName] = {
|
|
1102
|
-
token,
|
|
1103
|
-
workspace_name: workspaceName,
|
|
1104
|
-
workspace_id: workspaceId
|
|
1105
|
-
};
|
|
1106
|
-
await writeGlobalConfig({
|
|
1107
|
-
...config,
|
|
1108
|
-
profiles,
|
|
1109
|
-
active_profile: profileName
|
|
1110
|
-
});
|
|
1111
|
-
stderrWrite(success(`Profile "${profileName}" saved and set as active.`));
|
|
1112
|
-
stderrWrite(dim("Checking integration access..."));
|
|
1113
|
-
try {
|
|
1114
|
-
const notion = createNotionClient(token);
|
|
1115
|
-
const probe = await notion.search({ page_size: 1 });
|
|
1116
|
-
if (probe.results.length === 0) {
|
|
1117
|
-
stderrWrite("");
|
|
1118
|
-
stderrWrite("\u26A0\uFE0F Your integration has no pages connected.");
|
|
1119
|
-
stderrWrite(" To grant access, open any Notion page or database:");
|
|
1120
|
-
stderrWrite(" 1. Click \xB7\xB7\xB7 (three dots) in the top-right corner");
|
|
1121
|
-
stderrWrite(' 2. Select "Connect to"');
|
|
1122
|
-
stderrWrite(` 3. Choose "${workspaceName}"`);
|
|
1123
|
-
stderrWrite(" Then re-run any notion command to confirm access.");
|
|
1124
|
-
} else {
|
|
1125
|
-
stderrWrite(
|
|
1126
|
-
success(
|
|
1127
|
-
`\u2713 Integration has access to content in ${bold(workspaceName)}.`
|
|
1128
|
-
)
|
|
1129
|
-
);
|
|
1130
|
-
}
|
|
1131
|
-
} catch {
|
|
1132
|
-
stderrWrite(
|
|
1133
|
-
dim("(Could not verify integration access \u2014 run `notion ls` to check)")
|
|
1134
|
-
);
|
|
1135
|
-
}
|
|
1136
|
-
stderrWrite("");
|
|
1137
|
-
stderrWrite(
|
|
1138
|
-
dim("Write commands (comment, append, create-page) require additional")
|
|
1139
|
-
);
|
|
1140
|
-
stderrWrite(dim("capabilities in your integration settings:"));
|
|
1141
|
-
stderrWrite(
|
|
1142
|
-
dim(" notion.so/profile/integrations/internal \u2192 your integration \u2192")
|
|
1143
|
-
);
|
|
1144
|
-
stderrWrite(
|
|
1145
|
-
dim(
|
|
1146
|
-
' Capabilities: enable "Read content", "Insert content", "Read comments", "Insert comments"'
|
|
1147
|
-
)
|
|
1148
|
-
);
|
|
1149
|
-
stderrWrite("");
|
|
1150
|
-
stderrWrite(
|
|
1151
|
-
dim("To post comments and create pages attributed to your user account:")
|
|
1152
|
-
);
|
|
1153
|
-
stderrWrite(dim(" notion auth login"));
|
|
1154
|
-
}
|
|
1155
|
-
function initCommand() {
|
|
1156
|
-
const cmd = new Command2("init");
|
|
1157
|
-
cmd.description("authenticate with Notion and save a profile").action(
|
|
1158
|
-
withErrorHandling(async () => {
|
|
1159
|
-
if (!process.stdin.isTTY) {
|
|
1160
|
-
throw new CliError(
|
|
1161
|
-
ErrorCodes.AUTH_NO_TOKEN,
|
|
1162
|
-
"Cannot run interactive init in non-TTY mode.",
|
|
1163
|
-
"Set NOTION_API_TOKEN environment variable or create .notion.yaml"
|
|
1164
|
-
);
|
|
1165
|
-
}
|
|
1166
|
-
await runInitFlow();
|
|
1167
|
-
})
|
|
1168
|
-
);
|
|
1169
|
-
return cmd;
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
970
|
// src/commands/auth/login.ts
|
|
1173
971
|
function loginCommand() {
|
|
1174
|
-
const cmd = new
|
|
1175
|
-
cmd.description("authenticate with Notion
|
|
972
|
+
const cmd = new Command2("login");
|
|
973
|
+
cmd.description("authenticate with Notion via OAuth").option("--profile <name>", "profile name to store credentials in").option(
|
|
1176
974
|
"--manual",
|
|
1177
975
|
"print auth URL instead of opening browser (for headless OAuth)"
|
|
1178
976
|
).action(
|
|
@@ -1184,76 +982,57 @@ function loginCommand() {
|
|
|
1184
982
|
"Use --manual flag to get an auth URL you can open in a browser"
|
|
1185
983
|
);
|
|
1186
984
|
}
|
|
1187
|
-
const
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
const
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
if (isFirst) {
|
|
1224
|
-
const updated = await readGlobalConfig();
|
|
1225
|
-
await writeGlobalConfig({
|
|
1226
|
-
...updated,
|
|
1227
|
-
active_profile: profileName
|
|
1228
|
-
});
|
|
1229
|
-
}
|
|
1230
|
-
stderrWrite(
|
|
1231
|
-
success(`\u2713 Logged in as ${userName} to workspace ${workspaceName}`)
|
|
1232
|
-
);
|
|
1233
|
-
stderrWrite(dim(`Saved as profile "${profileName}".`));
|
|
1234
|
-
if (!isFirst && !isUpdate) {
|
|
1235
|
-
stderrWrite(
|
|
1236
|
-
dim(
|
|
1237
|
-
`Run "notion auth use ${profileName}" to switch to this profile.`
|
|
1238
|
-
)
|
|
1239
|
-
);
|
|
1240
|
-
}
|
|
1241
|
-
stderrWrite(
|
|
1242
|
-
dim(
|
|
1243
|
-
"Your comments and pages will now be attributed to your Notion account."
|
|
1244
|
-
)
|
|
985
|
+
const result = await runOAuthFlow({ manual: opts.manual });
|
|
986
|
+
const response = await exchangeCode(result.code);
|
|
987
|
+
const userName = response.owner?.user?.name ?? "unknown user";
|
|
988
|
+
const workspaceName = response.workspace_name ?? "unknown workspace";
|
|
989
|
+
const config = await readGlobalConfig();
|
|
990
|
+
const existingProfiles = config.profiles ?? {};
|
|
991
|
+
let profileName = opts.profile;
|
|
992
|
+
if (!profileName) {
|
|
993
|
+
const suggested = workspaceName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "default";
|
|
994
|
+
profileName = await input({
|
|
995
|
+
message: "Profile name to save this account under:",
|
|
996
|
+
default: suggested
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
const isUpdate = Boolean(existingProfiles[profileName]);
|
|
1000
|
+
const isFirst = Object.keys(existingProfiles).length === 0;
|
|
1001
|
+
if (isUpdate) {
|
|
1002
|
+
stderrWrite(dim(`Updating existing profile "${profileName}"...`));
|
|
1003
|
+
}
|
|
1004
|
+
await saveOAuthTokens(profileName, response);
|
|
1005
|
+
if (isFirst) {
|
|
1006
|
+
const updated = await readGlobalConfig();
|
|
1007
|
+
await writeGlobalConfig({
|
|
1008
|
+
...updated,
|
|
1009
|
+
active_profile: profileName
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
stderrWrite(
|
|
1013
|
+
success(`\u2713 Logged in as ${userName} to workspace ${workspaceName}`)
|
|
1014
|
+
);
|
|
1015
|
+
stderrWrite(dim(`Saved as profile "${profileName}".`));
|
|
1016
|
+
if (!isFirst && !isUpdate) {
|
|
1017
|
+
stderrWrite(
|
|
1018
|
+
dim(
|
|
1019
|
+
`Run "notion auth use ${profileName}" to switch to this profile.`
|
|
1020
|
+
)
|
|
1245
1021
|
);
|
|
1246
|
-
} else {
|
|
1247
|
-
await runInitFlow();
|
|
1248
1022
|
}
|
|
1023
|
+
stderrWrite(
|
|
1024
|
+
dim(
|
|
1025
|
+
"Your comments and pages will now be attributed to your Notion account."
|
|
1026
|
+
)
|
|
1027
|
+
);
|
|
1249
1028
|
})
|
|
1250
1029
|
);
|
|
1251
1030
|
return cmd;
|
|
1252
1031
|
}
|
|
1253
1032
|
|
|
1254
1033
|
// src/commands/auth/logout.ts
|
|
1255
|
-
import { select
|
|
1256
|
-
import { Command as
|
|
1034
|
+
import { select } from "@inquirer/prompts";
|
|
1035
|
+
import { Command as Command3 } from "commander";
|
|
1257
1036
|
function profileLabel(name, profile) {
|
|
1258
1037
|
const parts = [];
|
|
1259
1038
|
if (profile.oauth_access_token)
|
|
@@ -1266,7 +1045,7 @@ function profileLabel(name, profile) {
|
|
|
1266
1045
|
return `${bold(name)} ${dim(authDesc)}${workspace}`;
|
|
1267
1046
|
}
|
|
1268
1047
|
function logoutCommand() {
|
|
1269
|
-
const cmd = new
|
|
1048
|
+
const cmd = new Command3("logout");
|
|
1270
1049
|
cmd.description("remove a profile and its credentials").option(
|
|
1271
1050
|
"--profile <name>",
|
|
1272
1051
|
"profile name to remove (skips interactive selector)"
|
|
@@ -1288,7 +1067,7 @@ function logoutCommand() {
|
|
|
1288
1067
|
"Use --profile <name> to specify the profile to remove"
|
|
1289
1068
|
);
|
|
1290
1069
|
}
|
|
1291
|
-
profileName = await
|
|
1070
|
+
profileName = await select({
|
|
1292
1071
|
message: "Which profile do you want to log out of?",
|
|
1293
1072
|
choices: profileNames.map((name) => ({
|
|
1294
1073
|
// biome-ignore lint/style/noNonNullAssertion: key is from Object.keys, always present
|
|
@@ -1324,9 +1103,9 @@ function logoutCommand() {
|
|
|
1324
1103
|
}
|
|
1325
1104
|
|
|
1326
1105
|
// src/commands/auth/status.ts
|
|
1327
|
-
import { Command as
|
|
1106
|
+
import { Command as Command4 } from "commander";
|
|
1328
1107
|
function statusCommand() {
|
|
1329
|
-
const cmd = new
|
|
1108
|
+
const cmd = new Command4("status");
|
|
1330
1109
|
cmd.description("show authentication status for the active profile").option("--profile <name>", "profile name to check").action(
|
|
1331
1110
|
withErrorHandling(async (opts) => {
|
|
1332
1111
|
let profileName = opts.profile;
|
|
@@ -1384,9 +1163,9 @@ function statusCommand() {
|
|
|
1384
1163
|
}
|
|
1385
1164
|
|
|
1386
1165
|
// src/commands/comment-add.ts
|
|
1387
|
-
import { Command as
|
|
1166
|
+
import { Command as Command5 } from "commander";
|
|
1388
1167
|
function commentAddCommand() {
|
|
1389
|
-
const cmd = new
|
|
1168
|
+
const cmd = new Command5("comment");
|
|
1390
1169
|
cmd.description("add a comment to a Notion page").argument("<id/url>", "Notion page ID or URL").requiredOption("-m, --message <text>", "comment text to post").action(
|
|
1391
1170
|
withErrorHandling(async (idOrUrl, opts) => {
|
|
1392
1171
|
const { token, source } = await resolveToken();
|
|
@@ -1404,7 +1183,7 @@ function commentAddCommand() {
|
|
|
1404
1183
|
}
|
|
1405
1184
|
|
|
1406
1185
|
// src/commands/comments.ts
|
|
1407
|
-
import { Command as
|
|
1186
|
+
import { Command as Command6 } from "commander";
|
|
1408
1187
|
|
|
1409
1188
|
// src/output/format.ts
|
|
1410
1189
|
var _mode = "auto";
|
|
@@ -1489,7 +1268,7 @@ async function paginateResults(fetcher) {
|
|
|
1489
1268
|
|
|
1490
1269
|
// src/commands/comments.ts
|
|
1491
1270
|
function commentsCommand() {
|
|
1492
|
-
const cmd = new
|
|
1271
|
+
const cmd = new Command6("comments");
|
|
1493
1272
|
cmd.description("list comments on a Notion page").argument("<id/url>", "Notion page ID or URL").option("--json", "output as JSON").action(
|
|
1494
1273
|
withErrorHandling(async (idOrUrl, opts) => {
|
|
1495
1274
|
if (opts.json) setOutputMode("json");
|
|
@@ -1520,7 +1299,7 @@ function commentsCommand() {
|
|
|
1520
1299
|
}
|
|
1521
1300
|
|
|
1522
1301
|
// src/commands/completion.ts
|
|
1523
|
-
import { Command as
|
|
1302
|
+
import { Command as Command7 } from "commander";
|
|
1524
1303
|
var BASH_COMPLETION = `# notion bash completion
|
|
1525
1304
|
_notion_completion() {
|
|
1526
1305
|
local cur prev words cword
|
|
@@ -1622,7 +1401,7 @@ complete -c notion -n '__fish_seen_subcommand_from completion' -a zsh -d 'zsh co
|
|
|
1622
1401
|
complete -c notion -n '__fish_seen_subcommand_from completion' -a fish -d 'fish completion script'
|
|
1623
1402
|
`;
|
|
1624
1403
|
function completionCommand() {
|
|
1625
|
-
const cmd = new
|
|
1404
|
+
const cmd = new Command7("completion");
|
|
1626
1405
|
cmd.description("output shell completion script").argument("<shell>", "shell type (bash, zsh, fish)").action(
|
|
1627
1406
|
withErrorHandling(async (shell) => {
|
|
1628
1407
|
switch (shell) {
|
|
@@ -1648,16 +1427,9 @@ function completionCommand() {
|
|
|
1648
1427
|
}
|
|
1649
1428
|
|
|
1650
1429
|
// src/commands/create-page.ts
|
|
1651
|
-
import { Command as
|
|
1652
|
-
async function readStdin() {
|
|
1653
|
-
const chunks = [];
|
|
1654
|
-
for await (const chunk of process.stdin) {
|
|
1655
|
-
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
1656
|
-
}
|
|
1657
|
-
return Buffer.concat(chunks).toString("utf-8");
|
|
1658
|
-
}
|
|
1430
|
+
import { Command as Command8 } from "commander";
|
|
1659
1431
|
function createPageCommand() {
|
|
1660
|
-
const cmd = new
|
|
1432
|
+
const cmd = new Command8("create-page");
|
|
1661
1433
|
cmd.description("create a new Notion page under a parent page").requiredOption("--parent <id/url>", "parent page ID or URL").requiredOption("--title <title>", "page title").option(
|
|
1662
1434
|
"-m, --message <markdown>",
|
|
1663
1435
|
"inline markdown content for the page body"
|
|
@@ -1673,9 +1445,13 @@ function createPageCommand() {
|
|
|
1673
1445
|
} else if (!process.stdin.isTTY) {
|
|
1674
1446
|
markdown = await readStdin();
|
|
1675
1447
|
}
|
|
1676
|
-
const blocks = mdToBlocks(markdown);
|
|
1677
1448
|
const parentUuid = toUuid(parseNotionId(opts.parent));
|
|
1678
|
-
const url = await createPage(
|
|
1449
|
+
const url = await createPage(
|
|
1450
|
+
client,
|
|
1451
|
+
parentUuid,
|
|
1452
|
+
opts.title,
|
|
1453
|
+
markdown
|
|
1454
|
+
);
|
|
1679
1455
|
process.stdout.write(`${url}
|
|
1680
1456
|
`);
|
|
1681
1457
|
}
|
|
@@ -1685,7 +1461,7 @@ function createPageCommand() {
|
|
|
1685
1461
|
}
|
|
1686
1462
|
|
|
1687
1463
|
// src/commands/db/query.ts
|
|
1688
|
-
import { Command as
|
|
1464
|
+
import { Command as Command9 } from "commander";
|
|
1689
1465
|
|
|
1690
1466
|
// src/services/database.service.ts
|
|
1691
1467
|
import { isFullPage } from "@notionhq/client";
|
|
@@ -1879,7 +1655,7 @@ function autoSelectColumns(schema, entries) {
|
|
|
1879
1655
|
return selected;
|
|
1880
1656
|
}
|
|
1881
1657
|
function dbQueryCommand() {
|
|
1882
|
-
return new
|
|
1658
|
+
return new Command9("query").description("Query database entries with optional filtering and sorting").argument("<id>", "Notion database ID or URL").option(
|
|
1883
1659
|
"--filter <filter>",
|
|
1884
1660
|
'Filter entries (repeatable): --filter "Status=Done"',
|
|
1885
1661
|
collect,
|
|
@@ -1934,9 +1710,9 @@ function collect(value, previous) {
|
|
|
1934
1710
|
}
|
|
1935
1711
|
|
|
1936
1712
|
// src/commands/db/schema.ts
|
|
1937
|
-
import { Command as
|
|
1713
|
+
import { Command as Command10 } from "commander";
|
|
1938
1714
|
function dbSchemaCommand() {
|
|
1939
|
-
return new
|
|
1715
|
+
return new Command10("schema").description("Show database schema (property names, types, and options)").argument("<id>", "Notion database ID or URL").option("--json", "Output raw JSON").action(
|
|
1940
1716
|
withErrorHandling(async (id, options) => {
|
|
1941
1717
|
const { token } = await resolveToken();
|
|
1942
1718
|
const client = createNotionClient(token);
|
|
@@ -1959,9 +1735,179 @@ function dbSchemaCommand() {
|
|
|
1959
1735
|
);
|
|
1960
1736
|
}
|
|
1961
1737
|
|
|
1738
|
+
// src/commands/edit-page.ts
|
|
1739
|
+
import { Command as Command11 } from "commander";
|
|
1740
|
+
function editPageCommand() {
|
|
1741
|
+
const cmd = new Command11("edit-page");
|
|
1742
|
+
cmd.description(
|
|
1743
|
+
"replace a Notion page's content \u2014 full page or a targeted section"
|
|
1744
|
+
).argument("<id/url>", "Notion page ID or URL").option(
|
|
1745
|
+
"-m, --message <markdown>",
|
|
1746
|
+
"new markdown content for the page body"
|
|
1747
|
+
).option(
|
|
1748
|
+
"--range <selector>",
|
|
1749
|
+
'ellipsis selector to replace only a section, e.g. "## My Section...last line"'
|
|
1750
|
+
).option(
|
|
1751
|
+
"--allow-deleting-content",
|
|
1752
|
+
"allow deletion when using --range (always true for full-page replace)"
|
|
1753
|
+
).action(
|
|
1754
|
+
withErrorHandling(async (idOrUrl, opts) => {
|
|
1755
|
+
const { token, source } = await resolveToken();
|
|
1756
|
+
reportTokenSource(source);
|
|
1757
|
+
const client = createNotionClient(token);
|
|
1758
|
+
if (opts.allowDeletingContent && !opts.range) {
|
|
1759
|
+
process.stderr.write(
|
|
1760
|
+
"Warning: --allow-deleting-content has no effect without --range (full-page replace always allows deletion).\n"
|
|
1761
|
+
);
|
|
1762
|
+
}
|
|
1763
|
+
let markdown = "";
|
|
1764
|
+
if (opts.message) {
|
|
1765
|
+
markdown = opts.message;
|
|
1766
|
+
} else if (!process.stdin.isTTY) {
|
|
1767
|
+
markdown = await readStdin();
|
|
1768
|
+
if (!markdown.trim()) {
|
|
1769
|
+
throw new CliError(
|
|
1770
|
+
ErrorCodes.INVALID_ARG,
|
|
1771
|
+
"No content provided (stdin was empty).",
|
|
1772
|
+
"Pass markdown via -m/--message or pipe non-empty content through stdin"
|
|
1773
|
+
);
|
|
1774
|
+
}
|
|
1775
|
+
} else {
|
|
1776
|
+
throw new CliError(
|
|
1777
|
+
ErrorCodes.INVALID_ARG,
|
|
1778
|
+
"No content provided.",
|
|
1779
|
+
"Pass markdown via -m/--message or pipe it through stdin"
|
|
1780
|
+
);
|
|
1781
|
+
}
|
|
1782
|
+
const pageId = parseNotionId(idOrUrl);
|
|
1783
|
+
const uuid = toUuid(pageId);
|
|
1784
|
+
try {
|
|
1785
|
+
if (opts.range) {
|
|
1786
|
+
await replaceMarkdown(client, uuid, markdown, {
|
|
1787
|
+
range: opts.range,
|
|
1788
|
+
allowDeletingContent: opts.allowDeletingContent ?? false
|
|
1789
|
+
});
|
|
1790
|
+
} else {
|
|
1791
|
+
await replaceMarkdown(client, uuid, markdown);
|
|
1792
|
+
}
|
|
1793
|
+
} catch (error2) {
|
|
1794
|
+
if (opts.range && isNotionValidationError(error2)) {
|
|
1795
|
+
throw new CliError(
|
|
1796
|
+
ErrorCodes.INVALID_ARG,
|
|
1797
|
+
`Selector not found: "${opts.range}". ${error2.message}`,
|
|
1798
|
+
SELECTOR_HINT,
|
|
1799
|
+
error2
|
|
1800
|
+
);
|
|
1801
|
+
}
|
|
1802
|
+
throw error2;
|
|
1803
|
+
}
|
|
1804
|
+
process.stdout.write("Page content replaced.\n");
|
|
1805
|
+
})
|
|
1806
|
+
);
|
|
1807
|
+
return cmd;
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
// src/commands/init.ts
|
|
1811
|
+
import { confirm, input as input2, password } from "@inquirer/prompts";
|
|
1812
|
+
import { Command as Command12 } from "commander";
|
|
1813
|
+
async function runInitFlow() {
|
|
1814
|
+
const profileName = await input2({
|
|
1815
|
+
message: "Profile name:",
|
|
1816
|
+
default: "default"
|
|
1817
|
+
});
|
|
1818
|
+
const token = await password({
|
|
1819
|
+
message: "Integration token (from notion.so/profile/integrations/internal):",
|
|
1820
|
+
mask: "*"
|
|
1821
|
+
});
|
|
1822
|
+
stderrWrite("Validating token...");
|
|
1823
|
+
const { workspaceName, workspaceId } = await validateToken(token);
|
|
1824
|
+
stderrWrite(success(`\u2713 Connected to workspace: ${bold(workspaceName)}`));
|
|
1825
|
+
const config = await readGlobalConfig();
|
|
1826
|
+
if (config.profiles?.[profileName]) {
|
|
1827
|
+
const replace = await confirm({
|
|
1828
|
+
message: `Profile "${profileName}" already exists. Replace?`,
|
|
1829
|
+
default: false
|
|
1830
|
+
});
|
|
1831
|
+
if (!replace) {
|
|
1832
|
+
stderrWrite("Aborted.");
|
|
1833
|
+
return;
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
const profiles = config.profiles ?? {};
|
|
1837
|
+
profiles[profileName] = {
|
|
1838
|
+
token,
|
|
1839
|
+
workspace_name: workspaceName,
|
|
1840
|
+
workspace_id: workspaceId
|
|
1841
|
+
};
|
|
1842
|
+
await writeGlobalConfig({
|
|
1843
|
+
...config,
|
|
1844
|
+
profiles,
|
|
1845
|
+
active_profile: profileName
|
|
1846
|
+
});
|
|
1847
|
+
stderrWrite(success(`Profile "${profileName}" saved and set as active.`));
|
|
1848
|
+
stderrWrite(dim("Checking integration access..."));
|
|
1849
|
+
try {
|
|
1850
|
+
const notion = createNotionClient(token);
|
|
1851
|
+
const probe = await notion.search({ page_size: 1 });
|
|
1852
|
+
if (probe.results.length === 0) {
|
|
1853
|
+
stderrWrite("");
|
|
1854
|
+
stderrWrite("\u26A0\uFE0F Your integration has no pages connected.");
|
|
1855
|
+
stderrWrite(" To grant access, open any Notion page or database:");
|
|
1856
|
+
stderrWrite(" 1. Click \xB7\xB7\xB7 (three dots) in the top-right corner");
|
|
1857
|
+
stderrWrite(' 2. Select "Connect to"');
|
|
1858
|
+
stderrWrite(` 3. Choose "${workspaceName}"`);
|
|
1859
|
+
stderrWrite(" Then re-run any notion command to confirm access.");
|
|
1860
|
+
} else {
|
|
1861
|
+
stderrWrite(
|
|
1862
|
+
success(
|
|
1863
|
+
`\u2713 Integration has access to content in ${bold(workspaceName)}.`
|
|
1864
|
+
)
|
|
1865
|
+
);
|
|
1866
|
+
}
|
|
1867
|
+
} catch {
|
|
1868
|
+
stderrWrite(
|
|
1869
|
+
dim("(Could not verify integration access \u2014 run `notion ls` to check)")
|
|
1870
|
+
);
|
|
1871
|
+
}
|
|
1872
|
+
stderrWrite("");
|
|
1873
|
+
stderrWrite(
|
|
1874
|
+
dim("Write commands (comment, append, create-page) require additional")
|
|
1875
|
+
);
|
|
1876
|
+
stderrWrite(dim("capabilities in your integration settings:"));
|
|
1877
|
+
stderrWrite(
|
|
1878
|
+
dim(" notion.so/profile/integrations/internal \u2192 your integration \u2192")
|
|
1879
|
+
);
|
|
1880
|
+
stderrWrite(
|
|
1881
|
+
dim(
|
|
1882
|
+
' Capabilities: enable "Read content", "Insert content", "Read comments", "Insert comments"'
|
|
1883
|
+
)
|
|
1884
|
+
);
|
|
1885
|
+
stderrWrite("");
|
|
1886
|
+
stderrWrite(
|
|
1887
|
+
dim("To post comments and create pages attributed to your user account:")
|
|
1888
|
+
);
|
|
1889
|
+
stderrWrite(dim(" notion auth login"));
|
|
1890
|
+
}
|
|
1891
|
+
function initCommand() {
|
|
1892
|
+
const cmd = new Command12("init");
|
|
1893
|
+
cmd.description("authenticate with Notion and save a profile").action(
|
|
1894
|
+
withErrorHandling(async () => {
|
|
1895
|
+
if (!process.stdin.isTTY) {
|
|
1896
|
+
throw new CliError(
|
|
1897
|
+
ErrorCodes.AUTH_NO_TOKEN,
|
|
1898
|
+
"Cannot run interactive init in non-TTY mode.",
|
|
1899
|
+
"Set NOTION_API_TOKEN environment variable or create .notion.yaml"
|
|
1900
|
+
);
|
|
1901
|
+
}
|
|
1902
|
+
await runInitFlow();
|
|
1903
|
+
})
|
|
1904
|
+
);
|
|
1905
|
+
return cmd;
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1962
1908
|
// src/commands/ls.ts
|
|
1963
1909
|
import { isFullPageOrDataSource } from "@notionhq/client";
|
|
1964
|
-
import { Command as
|
|
1910
|
+
import { Command as Command13 } from "commander";
|
|
1965
1911
|
function getTitle(item) {
|
|
1966
1912
|
if (item.object === "data_source") {
|
|
1967
1913
|
return item.title.map((t) => t.plain_text).join("") || "(untitled)";
|
|
@@ -1978,7 +1924,7 @@ function displayType(item) {
|
|
|
1978
1924
|
return item.object === "data_source" ? "database" : item.object;
|
|
1979
1925
|
}
|
|
1980
1926
|
function lsCommand() {
|
|
1981
|
-
const cmd = new
|
|
1927
|
+
const cmd = new Command13("ls");
|
|
1982
1928
|
cmd.description("list accessible Notion pages and databases").option(
|
|
1983
1929
|
"--type <type>",
|
|
1984
1930
|
"filter by object type (page or database)",
|
|
@@ -2041,10 +1987,10 @@ function lsCommand() {
|
|
|
2041
1987
|
// src/commands/open.ts
|
|
2042
1988
|
import { exec } from "child_process";
|
|
2043
1989
|
import { promisify } from "util";
|
|
2044
|
-
import { Command as
|
|
1990
|
+
import { Command as Command14 } from "commander";
|
|
2045
1991
|
var execAsync = promisify(exec);
|
|
2046
1992
|
function openCommand() {
|
|
2047
|
-
const cmd = new
|
|
1993
|
+
const cmd = new Command14("open");
|
|
2048
1994
|
cmd.description("open a Notion page in the default browser").argument("<id/url>", "Notion page ID or URL").action(
|
|
2049
1995
|
withErrorHandling(async (idOrUrl) => {
|
|
2050
1996
|
const id = parseNotionId(idOrUrl);
|
|
@@ -2060,9 +2006,9 @@ function openCommand() {
|
|
|
2060
2006
|
}
|
|
2061
2007
|
|
|
2062
2008
|
// src/commands/profile/list.ts
|
|
2063
|
-
import { Command as
|
|
2009
|
+
import { Command as Command15 } from "commander";
|
|
2064
2010
|
function profileListCommand() {
|
|
2065
|
-
const cmd = new
|
|
2011
|
+
const cmd = new Command15("list");
|
|
2066
2012
|
cmd.description("list all authentication profiles").action(
|
|
2067
2013
|
withErrorHandling(async () => {
|
|
2068
2014
|
const config = await readGlobalConfig();
|
|
@@ -2091,9 +2037,9 @@ function profileListCommand() {
|
|
|
2091
2037
|
}
|
|
2092
2038
|
|
|
2093
2039
|
// src/commands/profile/remove.ts
|
|
2094
|
-
import { Command as
|
|
2040
|
+
import { Command as Command16 } from "commander";
|
|
2095
2041
|
function profileRemoveCommand() {
|
|
2096
|
-
const cmd = new
|
|
2042
|
+
const cmd = new Command16("remove");
|
|
2097
2043
|
cmd.description("remove an authentication profile").argument("<name>", "profile name to remove").action(
|
|
2098
2044
|
withErrorHandling(async (name) => {
|
|
2099
2045
|
const config = await readGlobalConfig();
|
|
@@ -2119,9 +2065,9 @@ function profileRemoveCommand() {
|
|
|
2119
2065
|
}
|
|
2120
2066
|
|
|
2121
2067
|
// src/commands/profile/use.ts
|
|
2122
|
-
import { Command as
|
|
2068
|
+
import { Command as Command17 } from "commander";
|
|
2123
2069
|
function profileUseCommand() {
|
|
2124
|
-
const cmd = new
|
|
2070
|
+
const cmd = new Command17("use");
|
|
2125
2071
|
cmd.description("switch the active profile").argument("<name>", "profile name to activate").action(
|
|
2126
2072
|
withErrorHandling(async (name) => {
|
|
2127
2073
|
const config = await readGlobalConfig();
|
|
@@ -2144,264 +2090,7 @@ function profileUseCommand() {
|
|
|
2144
2090
|
}
|
|
2145
2091
|
|
|
2146
2092
|
// src/commands/read.ts
|
|
2147
|
-
import { Command as
|
|
2148
|
-
|
|
2149
|
-
// src/blocks/rich-text.ts
|
|
2150
|
-
function richTextToMd(richText) {
|
|
2151
|
-
return richText.map(segmentToMd).join("");
|
|
2152
|
-
}
|
|
2153
|
-
function segmentToMd(segment) {
|
|
2154
|
-
if (segment.type === "equation") {
|
|
2155
|
-
return `$${segment.equation.expression}$`;
|
|
2156
|
-
}
|
|
2157
|
-
if (segment.type === "mention") {
|
|
2158
|
-
const text = segment.plain_text;
|
|
2159
|
-
return segment.href ? `[${text}](${segment.href})` : text;
|
|
2160
|
-
}
|
|
2161
|
-
const annotated = applyAnnotations(segment.text.content, segment.annotations);
|
|
2162
|
-
return segment.text.link ? `[${annotated}](${segment.text.link.url})` : annotated;
|
|
2163
|
-
}
|
|
2164
|
-
function applyAnnotations(text, annotations) {
|
|
2165
|
-
let result = text;
|
|
2166
|
-
if (annotations.code) result = `\`${result}\``;
|
|
2167
|
-
if (annotations.strikethrough) result = `~~${result}~~`;
|
|
2168
|
-
if (annotations.italic) result = `_${result}_`;
|
|
2169
|
-
if (annotations.bold) result = `**${result}**`;
|
|
2170
|
-
return result;
|
|
2171
|
-
}
|
|
2172
|
-
|
|
2173
|
-
// src/blocks/converters.ts
|
|
2174
|
-
function indentChildren(childrenMd) {
|
|
2175
|
-
return `${childrenMd.split("\n").filter(Boolean).map((line) => ` ${line}`).join("\n")}
|
|
2176
|
-
`;
|
|
2177
|
-
}
|
|
2178
|
-
var converters = {
|
|
2179
|
-
paragraph(block) {
|
|
2180
|
-
const b = block;
|
|
2181
|
-
return `${richTextToMd(b.paragraph.rich_text)}
|
|
2182
|
-
`;
|
|
2183
|
-
},
|
|
2184
|
-
heading_1(block) {
|
|
2185
|
-
const b = block;
|
|
2186
|
-
return `# ${richTextToMd(b.heading_1.rich_text)}
|
|
2187
|
-
`;
|
|
2188
|
-
},
|
|
2189
|
-
heading_2(block) {
|
|
2190
|
-
const b = block;
|
|
2191
|
-
return `## ${richTextToMd(b.heading_2.rich_text)}
|
|
2192
|
-
`;
|
|
2193
|
-
},
|
|
2194
|
-
heading_3(block) {
|
|
2195
|
-
const b = block;
|
|
2196
|
-
return `### ${richTextToMd(b.heading_3.rich_text)}
|
|
2197
|
-
`;
|
|
2198
|
-
},
|
|
2199
|
-
bulleted_list_item(block, ctx) {
|
|
2200
|
-
const b = block;
|
|
2201
|
-
const text = richTextToMd(b.bulleted_list_item.rich_text);
|
|
2202
|
-
const header = `- ${text}
|
|
2203
|
-
`;
|
|
2204
|
-
if (ctx?.childrenMd) {
|
|
2205
|
-
return header + indentChildren(ctx.childrenMd);
|
|
2206
|
-
}
|
|
2207
|
-
return header;
|
|
2208
|
-
},
|
|
2209
|
-
numbered_list_item(block, ctx) {
|
|
2210
|
-
const b = block;
|
|
2211
|
-
const num = ctx?.listNumber ?? 1;
|
|
2212
|
-
return `${num}. ${richTextToMd(b.numbered_list_item.rich_text)}
|
|
2213
|
-
`;
|
|
2214
|
-
},
|
|
2215
|
-
to_do(block) {
|
|
2216
|
-
const b = block;
|
|
2217
|
-
const checkbox = b.to_do.checked ? "[x]" : "[ ]";
|
|
2218
|
-
return `- ${checkbox} ${richTextToMd(b.to_do.rich_text)}
|
|
2219
|
-
`;
|
|
2220
|
-
},
|
|
2221
|
-
code(block) {
|
|
2222
|
-
const b = block;
|
|
2223
|
-
const lang = b.code.language === "plain text" ? "" : b.code.language;
|
|
2224
|
-
const content = richTextToMd(b.code.rich_text);
|
|
2225
|
-
return `\`\`\`${lang}
|
|
2226
|
-
${content}
|
|
2227
|
-
\`\`\`
|
|
2228
|
-
`;
|
|
2229
|
-
},
|
|
2230
|
-
quote(block) {
|
|
2231
|
-
const b = block;
|
|
2232
|
-
return `> ${richTextToMd(b.quote.rich_text)}
|
|
2233
|
-
`;
|
|
2234
|
-
},
|
|
2235
|
-
divider() {
|
|
2236
|
-
return "---\n";
|
|
2237
|
-
},
|
|
2238
|
-
callout(block) {
|
|
2239
|
-
const b = block;
|
|
2240
|
-
const text = richTextToMd(b.callout.rich_text);
|
|
2241
|
-
const icon = b.callout.icon;
|
|
2242
|
-
if (icon?.type === "emoji") {
|
|
2243
|
-
return `> ${icon.emoji} ${text}
|
|
2244
|
-
`;
|
|
2245
|
-
}
|
|
2246
|
-
return `> ${text}
|
|
2247
|
-
`;
|
|
2248
|
-
},
|
|
2249
|
-
toggle(block, ctx) {
|
|
2250
|
-
const b = block;
|
|
2251
|
-
const header = `**${richTextToMd(b.toggle.rich_text)}**
|
|
2252
|
-
`;
|
|
2253
|
-
if (ctx?.childrenMd) {
|
|
2254
|
-
return header + ctx.childrenMd;
|
|
2255
|
-
}
|
|
2256
|
-
return header;
|
|
2257
|
-
},
|
|
2258
|
-
image(block) {
|
|
2259
|
-
const b = block;
|
|
2260
|
-
const caption = richTextToMd(b.image.caption);
|
|
2261
|
-
if (b.image.type === "file") {
|
|
2262
|
-
const url2 = b.image.file.url;
|
|
2263
|
-
const expiry = b.image.file.expiry_time;
|
|
2264
|
-
return ` <!-- expires: ${expiry} -->
|
|
2265
|
-
`;
|
|
2266
|
-
}
|
|
2267
|
-
const url = b.image.external.url;
|
|
2268
|
-
return `
|
|
2269
|
-
`;
|
|
2270
|
-
},
|
|
2271
|
-
bookmark(block) {
|
|
2272
|
-
const b = block;
|
|
2273
|
-
const caption = richTextToMd(b.bookmark.caption);
|
|
2274
|
-
const text = caption || b.bookmark.url;
|
|
2275
|
-
return `[${text}](${b.bookmark.url})
|
|
2276
|
-
`;
|
|
2277
|
-
},
|
|
2278
|
-
child_page(block) {
|
|
2279
|
-
const b = block;
|
|
2280
|
-
return `### ${b.child_page.title}
|
|
2281
|
-
`;
|
|
2282
|
-
},
|
|
2283
|
-
child_database(block) {
|
|
2284
|
-
const b = block;
|
|
2285
|
-
return `### ${b.child_database.title}
|
|
2286
|
-
`;
|
|
2287
|
-
},
|
|
2288
|
-
link_preview(block) {
|
|
2289
|
-
const b = block;
|
|
2290
|
-
return `[${b.link_preview.url}](${b.link_preview.url})
|
|
2291
|
-
`;
|
|
2292
|
-
}
|
|
2293
|
-
};
|
|
2294
|
-
function blockToMd(block, ctx) {
|
|
2295
|
-
const converter = converters[block.type];
|
|
2296
|
-
if (converter) {
|
|
2297
|
-
return converter(block, ctx);
|
|
2298
|
-
}
|
|
2299
|
-
return `<!-- unsupported block: ${block.type} -->
|
|
2300
|
-
`;
|
|
2301
|
-
}
|
|
2302
|
-
|
|
2303
|
-
// src/blocks/properties.ts
|
|
2304
|
-
function formatFormula(f) {
|
|
2305
|
-
if (f.type === "string") return f.string ?? "";
|
|
2306
|
-
if (f.type === "number") return f.number !== null ? String(f.number) : "";
|
|
2307
|
-
if (f.type === "boolean") return String(f.boolean);
|
|
2308
|
-
if (f.type === "date") return f.date?.start ?? "";
|
|
2309
|
-
return "";
|
|
2310
|
-
}
|
|
2311
|
-
function formatRollup(r) {
|
|
2312
|
-
if (r.type === "number") return r.number !== null ? String(r.number) : "";
|
|
2313
|
-
if (r.type === "date") return r.date?.start ?? "";
|
|
2314
|
-
if (r.type === "array") return `[${r.array.length} items]`;
|
|
2315
|
-
return "";
|
|
2316
|
-
}
|
|
2317
|
-
function formatUser(p) {
|
|
2318
|
-
return "name" in p && p.name ? p.name : p.id;
|
|
2319
|
-
}
|
|
2320
|
-
function formatPropertyValue(_name, prop) {
|
|
2321
|
-
switch (prop.type) {
|
|
2322
|
-
case "title":
|
|
2323
|
-
return prop.title.map((rt) => rt.plain_text).join("");
|
|
2324
|
-
case "rich_text":
|
|
2325
|
-
return prop.rich_text.map((rt) => rt.plain_text).join("");
|
|
2326
|
-
case "number":
|
|
2327
|
-
return prop.number !== null ? String(prop.number) : "";
|
|
2328
|
-
case "select":
|
|
2329
|
-
return prop.select?.name ?? "";
|
|
2330
|
-
case "status":
|
|
2331
|
-
return prop.status?.name ?? "";
|
|
2332
|
-
case "multi_select":
|
|
2333
|
-
return prop.multi_select.map((s) => s.name).join(", ");
|
|
2334
|
-
case "date":
|
|
2335
|
-
if (!prop.date) return "";
|
|
2336
|
-
return prop.date.end ? `${prop.date.start} \u2192 ${prop.date.end}` : prop.date.start;
|
|
2337
|
-
case "checkbox":
|
|
2338
|
-
return prop.checkbox ? "true" : "false";
|
|
2339
|
-
case "url":
|
|
2340
|
-
return prop.url ?? "";
|
|
2341
|
-
case "email":
|
|
2342
|
-
return prop.email ?? "";
|
|
2343
|
-
case "phone_number":
|
|
2344
|
-
return prop.phone_number ?? "";
|
|
2345
|
-
case "people":
|
|
2346
|
-
return prop.people.map(formatUser).join(", ");
|
|
2347
|
-
case "relation":
|
|
2348
|
-
return prop.relation.map((r) => r.id).join(", ");
|
|
2349
|
-
case "formula":
|
|
2350
|
-
return formatFormula(prop.formula);
|
|
2351
|
-
case "rollup":
|
|
2352
|
-
return formatRollup(prop.rollup);
|
|
2353
|
-
case "created_time":
|
|
2354
|
-
return prop.created_time;
|
|
2355
|
-
case "last_edited_time":
|
|
2356
|
-
return prop.last_edited_time;
|
|
2357
|
-
case "created_by":
|
|
2358
|
-
return "name" in prop.created_by ? prop.created_by.name ?? prop.created_by.id : prop.created_by.id;
|
|
2359
|
-
case "last_edited_by":
|
|
2360
|
-
return "name" in prop.last_edited_by ? prop.last_edited_by.name ?? prop.last_edited_by.id : prop.last_edited_by.id;
|
|
2361
|
-
case "files":
|
|
2362
|
-
return prop.files.map((f) => f.type === "external" ? f.external.url : f.name).join(", ");
|
|
2363
|
-
case "unique_id":
|
|
2364
|
-
return prop.unique_id.prefix ? `${prop.unique_id.prefix}-${prop.unique_id.number}` : String(prop.unique_id.number ?? "");
|
|
2365
|
-
default:
|
|
2366
|
-
return "";
|
|
2367
|
-
}
|
|
2368
|
-
}
|
|
2369
|
-
|
|
2370
|
-
// src/blocks/render.ts
|
|
2371
|
-
function buildPropertiesHeader(page) {
|
|
2372
|
-
const lines = ["---"];
|
|
2373
|
-
for (const [name, prop] of Object.entries(page.properties)) {
|
|
2374
|
-
const value = formatPropertyValue(name, prop);
|
|
2375
|
-
if (value) {
|
|
2376
|
-
lines.push(`${name}: ${value}`);
|
|
2377
|
-
}
|
|
2378
|
-
}
|
|
2379
|
-
lines.push("---", "");
|
|
2380
|
-
return lines.join("\n");
|
|
2381
|
-
}
|
|
2382
|
-
function renderBlockTree(blocks) {
|
|
2383
|
-
const parts = [];
|
|
2384
|
-
let listCounter = 0;
|
|
2385
|
-
for (const node of blocks) {
|
|
2386
|
-
if (node.block.type === "numbered_list_item") {
|
|
2387
|
-
listCounter++;
|
|
2388
|
-
} else {
|
|
2389
|
-
listCounter = 0;
|
|
2390
|
-
}
|
|
2391
|
-
const childrenMd = node.children.length > 0 ? renderBlockTree(node.children) : "";
|
|
2392
|
-
const md = blockToMd(node.block, {
|
|
2393
|
-
listNumber: node.block.type === "numbered_list_item" ? listCounter : void 0,
|
|
2394
|
-
childrenMd: childrenMd || void 0
|
|
2395
|
-
});
|
|
2396
|
-
parts.push(md);
|
|
2397
|
-
}
|
|
2398
|
-
return parts.join("");
|
|
2399
|
-
}
|
|
2400
|
-
function renderPageMarkdown({ page, blocks }) {
|
|
2401
|
-
const header = buildPropertiesHeader(page);
|
|
2402
|
-
const content = renderBlockTree(blocks);
|
|
2403
|
-
return header + content;
|
|
2404
|
-
}
|
|
2093
|
+
import { Command as Command18 } from "commander";
|
|
2405
2094
|
|
|
2406
2095
|
// src/output/markdown.ts
|
|
2407
2096
|
import { Chalk as Chalk2 } from "chalk";
|
|
@@ -2531,69 +2220,43 @@ function renderInline(text) {
|
|
|
2531
2220
|
}
|
|
2532
2221
|
|
|
2533
2222
|
// src/services/page.service.ts
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
if (depth >= maxDepth) return [];
|
|
2541
|
-
const rawBlocks = await collectPaginatedAPI(client.blocks.children.list, {
|
|
2542
|
-
block_id: blockId
|
|
2543
|
-
});
|
|
2544
|
-
const blocks = rawBlocks.filter(isFullBlock);
|
|
2545
|
-
const SKIP_RECURSE = /* @__PURE__ */ new Set(["child_page", "child_database"]);
|
|
2546
|
-
const nodes = [];
|
|
2547
|
-
for (let i = 0; i < blocks.length; i += MAX_CONCURRENT_REQUESTS) {
|
|
2548
|
-
const batch = blocks.slice(i, i + MAX_CONCURRENT_REQUESTS);
|
|
2549
|
-
const batchNodes = await Promise.all(
|
|
2550
|
-
batch.map(async (block) => {
|
|
2551
|
-
const children = block.has_children && !SKIP_RECURSE.has(block.type) ? await fetchBlockTree(client, block.id, depth + 1, maxDepth) : [];
|
|
2552
|
-
return { block, children };
|
|
2553
|
-
})
|
|
2554
|
-
);
|
|
2555
|
-
nodes.push(...batchNodes);
|
|
2556
|
-
}
|
|
2557
|
-
return nodes;
|
|
2558
|
-
}
|
|
2559
|
-
async function fetchPageWithBlocks(client, pageId) {
|
|
2560
|
-
const page = await client.pages.retrieve({
|
|
2561
|
-
page_id: pageId
|
|
2562
|
-
});
|
|
2563
|
-
const blocks = await fetchBlockTree(client, pageId, 0, 10);
|
|
2564
|
-
return { page, blocks };
|
|
2223
|
+
async function fetchPageMarkdown(client, pageId) {
|
|
2224
|
+
const [page, markdownResponse] = await Promise.all([
|
|
2225
|
+
client.pages.retrieve({ page_id: pageId }),
|
|
2226
|
+
client.pages.retrieveMarkdown({ page_id: pageId })
|
|
2227
|
+
]);
|
|
2228
|
+
return { page, markdown: markdownResponse.markdown };
|
|
2565
2229
|
}
|
|
2566
2230
|
|
|
2567
2231
|
// src/commands/read.ts
|
|
2568
2232
|
function readCommand() {
|
|
2569
|
-
return new
|
|
2570
|
-
withErrorHandling(
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2233
|
+
return new Command18("read").description("Read a Notion page as markdown").argument("<id>", "Notion page ID or URL").action(
|
|
2234
|
+
withErrorHandling(async (id) => {
|
|
2235
|
+
const { token } = await resolveToken();
|
|
2236
|
+
const client = createNotionClient(token);
|
|
2237
|
+
const pageId = parseNotionId(id);
|
|
2238
|
+
const pageWithMarkdown = await fetchPageMarkdown(client, pageId);
|
|
2239
|
+
const mode = getOutputMode();
|
|
2240
|
+
if (mode === "json") {
|
|
2241
|
+
process.stdout.write(
|
|
2242
|
+
`${JSON.stringify(pageWithMarkdown, null, 2)}
|
|
2579
2243
|
`
|
|
2580
|
-
|
|
2244
|
+
);
|
|
2245
|
+
} else {
|
|
2246
|
+
const { markdown } = pageWithMarkdown;
|
|
2247
|
+
if (mode === "md" || !isatty()) {
|
|
2248
|
+
process.stdout.write(markdown);
|
|
2581
2249
|
} else {
|
|
2582
|
-
|
|
2583
|
-
if (options.md || !isatty()) {
|
|
2584
|
-
process.stdout.write(markdown);
|
|
2585
|
-
} else {
|
|
2586
|
-
process.stdout.write(renderMarkdown(markdown));
|
|
2587
|
-
}
|
|
2250
|
+
process.stdout.write(renderMarkdown(markdown));
|
|
2588
2251
|
}
|
|
2589
2252
|
}
|
|
2590
|
-
)
|
|
2253
|
+
})
|
|
2591
2254
|
);
|
|
2592
2255
|
}
|
|
2593
2256
|
|
|
2594
2257
|
// src/commands/search.ts
|
|
2595
2258
|
import { isFullPageOrDataSource as isFullPageOrDataSource2 } from "@notionhq/client";
|
|
2596
|
-
import { Command as
|
|
2259
|
+
import { Command as Command19 } from "commander";
|
|
2597
2260
|
function getTitle2(item) {
|
|
2598
2261
|
if (item.object === "data_source") {
|
|
2599
2262
|
return item.title.map((t) => t.plain_text).join("") || "(untitled)";
|
|
@@ -2613,7 +2276,7 @@ function displayType2(item) {
|
|
|
2613
2276
|
return item.object === "data_source" ? "database" : item.object;
|
|
2614
2277
|
}
|
|
2615
2278
|
function searchCommand() {
|
|
2616
|
-
const cmd = new
|
|
2279
|
+
const cmd = new Command19("search");
|
|
2617
2280
|
cmd.description("search Notion workspace by keyword").argument("<query>", "search keyword").option(
|
|
2618
2281
|
"--type <type>",
|
|
2619
2282
|
"filter by object type (page or database)",
|
|
@@ -2671,7 +2334,7 @@ function searchCommand() {
|
|
|
2671
2334
|
}
|
|
2672
2335
|
|
|
2673
2336
|
// src/commands/users.ts
|
|
2674
|
-
import { Command as
|
|
2337
|
+
import { Command as Command20 } from "commander";
|
|
2675
2338
|
function getEmailOrWorkspace(user) {
|
|
2676
2339
|
if (user.type === "person") {
|
|
2677
2340
|
return user.person.email ?? "\u2014";
|
|
@@ -2683,7 +2346,7 @@ function getEmailOrWorkspace(user) {
|
|
|
2683
2346
|
return "\u2014";
|
|
2684
2347
|
}
|
|
2685
2348
|
function usersCommand() {
|
|
2686
|
-
const cmd = new
|
|
2349
|
+
const cmd = new Command20("users");
|
|
2687
2350
|
cmd.description("list all users in the workspace").option("--json", "output as JSON").action(
|
|
2688
2351
|
withErrorHandling(async (opts) => {
|
|
2689
2352
|
if (opts.json) setOutputMode("json");
|
|
@@ -2714,7 +2377,7 @@ var __dirname = dirname(__filename);
|
|
|
2714
2377
|
var pkg = JSON.parse(
|
|
2715
2378
|
readFileSync(join3(__dirname, "../package.json"), "utf-8")
|
|
2716
2379
|
);
|
|
2717
|
-
var program = new
|
|
2380
|
+
var program = new Command21();
|
|
2718
2381
|
program.name("notion").description("Notion CLI \u2014 read Notion pages and databases from the terminal").version(pkg.version);
|
|
2719
2382
|
program.option("--verbose", "show API requests/responses").option("--color", "force color output").option("--json", "force JSON output (overrides TTY detection)").option("--md", "force markdown output for page content");
|
|
2720
2383
|
program.configureOutput({
|
|
@@ -2735,7 +2398,7 @@ program.hook("preAction", (thisCommand) => {
|
|
|
2735
2398
|
setOutputMode("md");
|
|
2736
2399
|
}
|
|
2737
2400
|
});
|
|
2738
|
-
var authCmd = new
|
|
2401
|
+
var authCmd = new Command21("auth").description("manage Notion authentication");
|
|
2739
2402
|
authCmd.action(authDefaultAction(authCmd));
|
|
2740
2403
|
authCmd.addCommand(loginCommand());
|
|
2741
2404
|
authCmd.addCommand(logoutCommand());
|
|
@@ -2745,7 +2408,7 @@ authCmd.addCommand(profileUseCommand());
|
|
|
2745
2408
|
authCmd.addCommand(profileRemoveCommand());
|
|
2746
2409
|
program.addCommand(authCmd);
|
|
2747
2410
|
program.addCommand(initCommand(), { hidden: true });
|
|
2748
|
-
var profileCmd = new
|
|
2411
|
+
var profileCmd = new Command21("profile").description(
|
|
2749
2412
|
"manage authentication profiles"
|
|
2750
2413
|
);
|
|
2751
2414
|
profileCmd.addCommand(profileListCommand());
|
|
@@ -2761,7 +2424,8 @@ program.addCommand(readCommand());
|
|
|
2761
2424
|
program.addCommand(commentAddCommand());
|
|
2762
2425
|
program.addCommand(appendCommand());
|
|
2763
2426
|
program.addCommand(createPageCommand());
|
|
2764
|
-
|
|
2427
|
+
program.addCommand(editPageCommand());
|
|
2428
|
+
var dbCmd = new Command21("db").description("Database operations");
|
|
2765
2429
|
dbCmd.addCommand(dbSchemaCommand());
|
|
2766
2430
|
dbCmd.addCommand(dbQueryCommand());
|
|
2767
2431
|
program.addCommand(dbCmd);
|