@andrzejchm/notion-cli 0.4.0 → 0.5.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 +338 -739
- package/dist/cli.js.map +1 -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) {
|
|
@@ -775,13 +557,61 @@ async function addComment(client, pageId, text, options = {}) {
|
|
|
775
557
|
...options.asUser && { display_name: { type: "user" } }
|
|
776
558
|
});
|
|
777
559
|
}
|
|
778
|
-
async function
|
|
779
|
-
await client.
|
|
780
|
-
|
|
781
|
-
|
|
560
|
+
async function appendMarkdown(client, pageId, markdown) {
|
|
561
|
+
await client.pages.updateMarkdown({
|
|
562
|
+
page_id: pageId,
|
|
563
|
+
type: "insert_content",
|
|
564
|
+
insert_content: { content: markdown }
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
function countOccurrences(text, sub) {
|
|
568
|
+
let count = 0;
|
|
569
|
+
let pos = text.indexOf(sub, 0);
|
|
570
|
+
while (pos !== -1) {
|
|
571
|
+
count++;
|
|
572
|
+
pos = text.indexOf(sub, pos + 1);
|
|
573
|
+
}
|
|
574
|
+
return count;
|
|
575
|
+
}
|
|
576
|
+
function buildContentRange(content) {
|
|
577
|
+
const START_LEN = 15;
|
|
578
|
+
const STEP = 10;
|
|
579
|
+
if (content.length <= START_LEN * 2) {
|
|
580
|
+
return content;
|
|
581
|
+
}
|
|
582
|
+
const start = content.slice(0, START_LEN);
|
|
583
|
+
for (let endLen = START_LEN; endLen < content.length - START_LEN; endLen += STEP) {
|
|
584
|
+
const end = content.slice(-endLen);
|
|
585
|
+
if (countOccurrences(content, end) === 1) {
|
|
586
|
+
return `${start}...${end}`;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return content;
|
|
590
|
+
}
|
|
591
|
+
async function replaceMarkdown(client, pageId, newMarkdown) {
|
|
592
|
+
const current = await client.pages.retrieveMarkdown({ page_id: pageId });
|
|
593
|
+
const currentContent = current.markdown.trim();
|
|
594
|
+
if (!currentContent) {
|
|
595
|
+
if (newMarkdown.trim()) {
|
|
596
|
+
await client.pages.updateMarkdown({
|
|
597
|
+
page_id: pageId,
|
|
598
|
+
type: "insert_content",
|
|
599
|
+
insert_content: { content: newMarkdown }
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
await client.pages.updateMarkdown({
|
|
605
|
+
page_id: pageId,
|
|
606
|
+
type: "replace_content_range",
|
|
607
|
+
replace_content_range: {
|
|
608
|
+
content: newMarkdown,
|
|
609
|
+
content_range: buildContentRange(currentContent),
|
|
610
|
+
allow_deleting_content: true
|
|
611
|
+
}
|
|
782
612
|
});
|
|
783
613
|
}
|
|
784
|
-
async function createPage(client, parentId, title,
|
|
614
|
+
async function createPage(client, parentId, title, markdown) {
|
|
785
615
|
const response = await client.pages.create({
|
|
786
616
|
parent: { type: "page_id", page_id: parentId },
|
|
787
617
|
properties: {
|
|
@@ -789,29 +619,46 @@ async function createPage(client, parentId, title, blocks) {
|
|
|
789
619
|
title: [{ type: "text", text: { content: title, link: null } }]
|
|
790
620
|
}
|
|
791
621
|
},
|
|
792
|
-
|
|
622
|
+
...markdown.trim() ? { markdown } : {}
|
|
793
623
|
});
|
|
794
624
|
return response.url;
|
|
795
625
|
}
|
|
796
626
|
|
|
797
627
|
// src/commands/append.ts
|
|
628
|
+
async function readStdin() {
|
|
629
|
+
const chunks = [];
|
|
630
|
+
for await (const chunk of process.stdin) {
|
|
631
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
632
|
+
}
|
|
633
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
634
|
+
}
|
|
798
635
|
function appendCommand() {
|
|
799
636
|
const cmd = new Command("append");
|
|
800
|
-
cmd.description("append markdown content to a Notion page").argument("<id/url>", "Notion page ID or URL").
|
|
637
|
+
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").action(
|
|
801
638
|
withErrorHandling(async (idOrUrl, opts) => {
|
|
802
639
|
const { token, source } = await resolveToken();
|
|
803
640
|
reportTokenSource(source);
|
|
804
641
|
const client = createNotionClient(token);
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
if (
|
|
642
|
+
let markdown = "";
|
|
643
|
+
if (opts.message) {
|
|
644
|
+
markdown = opts.message;
|
|
645
|
+
} else if (!process.stdin.isTTY) {
|
|
646
|
+
markdown = await readStdin();
|
|
647
|
+
} else {
|
|
648
|
+
throw new CliError(
|
|
649
|
+
ErrorCodes.INVALID_ARG,
|
|
650
|
+
"No content to append.",
|
|
651
|
+
"Pass markdown via -m/--message or pipe it through stdin"
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
if (!markdown.trim()) {
|
|
809
655
|
process.stdout.write("Nothing to append.\n");
|
|
810
656
|
return;
|
|
811
657
|
}
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
658
|
+
const pageId = parseNotionId(idOrUrl);
|
|
659
|
+
const uuid = toUuid(pageId);
|
|
660
|
+
await appendMarkdown(client, uuid, markdown);
|
|
661
|
+
process.stdout.write("Appended.\n");
|
|
815
662
|
})
|
|
816
663
|
);
|
|
817
664
|
return cmd;
|
|
@@ -825,8 +672,8 @@ function authDefaultAction(authCmd2) {
|
|
|
825
672
|
}
|
|
826
673
|
|
|
827
674
|
// src/commands/auth/login.ts
|
|
828
|
-
import { input
|
|
829
|
-
import { Command as
|
|
675
|
+
import { input } from "@inquirer/prompts";
|
|
676
|
+
import { Command as Command2 } from "commander";
|
|
830
677
|
|
|
831
678
|
// src/oauth/oauth-flow.ts
|
|
832
679
|
import { spawn } from "child_process";
|
|
@@ -1071,108 +918,10 @@ Waiting for callback (up to 120 seconds)...
|
|
|
1071
918
|
});
|
|
1072
919
|
}
|
|
1073
920
|
|
|
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
921
|
// src/commands/auth/login.ts
|
|
1173
922
|
function loginCommand() {
|
|
1174
|
-
const cmd = new
|
|
1175
|
-
cmd.description("authenticate with Notion
|
|
923
|
+
const cmd = new Command2("login");
|
|
924
|
+
cmd.description("authenticate with Notion via OAuth").option("--profile <name>", "profile name to store credentials in").option(
|
|
1176
925
|
"--manual",
|
|
1177
926
|
"print auth URL instead of opening browser (for headless OAuth)"
|
|
1178
927
|
).action(
|
|
@@ -1184,76 +933,57 @@ function loginCommand() {
|
|
|
1184
933
|
"Use --manual flag to get an auth URL you can open in a browser"
|
|
1185
934
|
);
|
|
1186
935
|
}
|
|
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
|
-
if (isUpdate) {
|
|
1220
|
-
stderrWrite(dim(`Updating existing profile "${profileName}"...`));
|
|
1221
|
-
}
|
|
1222
|
-
await saveOAuthTokens(profileName, response);
|
|
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
|
-
}
|
|
936
|
+
const result = await runOAuthFlow({ manual: opts.manual });
|
|
937
|
+
const response = await exchangeCode(result.code);
|
|
938
|
+
const userName = response.owner?.user?.name ?? "unknown user";
|
|
939
|
+
const workspaceName = response.workspace_name ?? "unknown workspace";
|
|
940
|
+
const config = await readGlobalConfig();
|
|
941
|
+
const existingProfiles = config.profiles ?? {};
|
|
942
|
+
let profileName = opts.profile;
|
|
943
|
+
if (!profileName) {
|
|
944
|
+
const suggested = workspaceName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "default";
|
|
945
|
+
profileName = await input({
|
|
946
|
+
message: "Profile name to save this account under:",
|
|
947
|
+
default: suggested
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
const isUpdate = Boolean(existingProfiles[profileName]);
|
|
951
|
+
const isFirst = Object.keys(existingProfiles).length === 0;
|
|
952
|
+
if (isUpdate) {
|
|
953
|
+
stderrWrite(dim(`Updating existing profile "${profileName}"...`));
|
|
954
|
+
}
|
|
955
|
+
await saveOAuthTokens(profileName, response);
|
|
956
|
+
if (isFirst) {
|
|
957
|
+
const updated = await readGlobalConfig();
|
|
958
|
+
await writeGlobalConfig({
|
|
959
|
+
...updated,
|
|
960
|
+
active_profile: profileName
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
stderrWrite(
|
|
964
|
+
success(`\u2713 Logged in as ${userName} to workspace ${workspaceName}`)
|
|
965
|
+
);
|
|
966
|
+
stderrWrite(dim(`Saved as profile "${profileName}".`));
|
|
967
|
+
if (!isFirst && !isUpdate) {
|
|
1241
968
|
stderrWrite(
|
|
1242
969
|
dim(
|
|
1243
|
-
"
|
|
970
|
+
`Run "notion auth use ${profileName}" to switch to this profile.`
|
|
1244
971
|
)
|
|
1245
972
|
);
|
|
1246
|
-
} else {
|
|
1247
|
-
await runInitFlow();
|
|
1248
973
|
}
|
|
974
|
+
stderrWrite(
|
|
975
|
+
dim(
|
|
976
|
+
"Your comments and pages will now be attributed to your Notion account."
|
|
977
|
+
)
|
|
978
|
+
);
|
|
1249
979
|
})
|
|
1250
980
|
);
|
|
1251
981
|
return cmd;
|
|
1252
982
|
}
|
|
1253
983
|
|
|
1254
984
|
// src/commands/auth/logout.ts
|
|
1255
|
-
import { select
|
|
1256
|
-
import { Command as
|
|
985
|
+
import { select } from "@inquirer/prompts";
|
|
986
|
+
import { Command as Command3 } from "commander";
|
|
1257
987
|
function profileLabel(name, profile) {
|
|
1258
988
|
const parts = [];
|
|
1259
989
|
if (profile.oauth_access_token)
|
|
@@ -1266,7 +996,7 @@ function profileLabel(name, profile) {
|
|
|
1266
996
|
return `${bold(name)} ${dim(authDesc)}${workspace}`;
|
|
1267
997
|
}
|
|
1268
998
|
function logoutCommand() {
|
|
1269
|
-
const cmd = new
|
|
999
|
+
const cmd = new Command3("logout");
|
|
1270
1000
|
cmd.description("remove a profile and its credentials").option(
|
|
1271
1001
|
"--profile <name>",
|
|
1272
1002
|
"profile name to remove (skips interactive selector)"
|
|
@@ -1288,7 +1018,7 @@ function logoutCommand() {
|
|
|
1288
1018
|
"Use --profile <name> to specify the profile to remove"
|
|
1289
1019
|
);
|
|
1290
1020
|
}
|
|
1291
|
-
profileName = await
|
|
1021
|
+
profileName = await select({
|
|
1292
1022
|
message: "Which profile do you want to log out of?",
|
|
1293
1023
|
choices: profileNames.map((name) => ({
|
|
1294
1024
|
// biome-ignore lint/style/noNonNullAssertion: key is from Object.keys, always present
|
|
@@ -1324,9 +1054,9 @@ function logoutCommand() {
|
|
|
1324
1054
|
}
|
|
1325
1055
|
|
|
1326
1056
|
// src/commands/auth/status.ts
|
|
1327
|
-
import { Command as
|
|
1057
|
+
import { Command as Command4 } from "commander";
|
|
1328
1058
|
function statusCommand() {
|
|
1329
|
-
const cmd = new
|
|
1059
|
+
const cmd = new Command4("status");
|
|
1330
1060
|
cmd.description("show authentication status for the active profile").option("--profile <name>", "profile name to check").action(
|
|
1331
1061
|
withErrorHandling(async (opts) => {
|
|
1332
1062
|
let profileName = opts.profile;
|
|
@@ -1384,9 +1114,9 @@ function statusCommand() {
|
|
|
1384
1114
|
}
|
|
1385
1115
|
|
|
1386
1116
|
// src/commands/comment-add.ts
|
|
1387
|
-
import { Command as
|
|
1117
|
+
import { Command as Command5 } from "commander";
|
|
1388
1118
|
function commentAddCommand() {
|
|
1389
|
-
const cmd = new
|
|
1119
|
+
const cmd = new Command5("comment");
|
|
1390
1120
|
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
1121
|
withErrorHandling(async (idOrUrl, opts) => {
|
|
1392
1122
|
const { token, source } = await resolveToken();
|
|
@@ -1404,7 +1134,7 @@ function commentAddCommand() {
|
|
|
1404
1134
|
}
|
|
1405
1135
|
|
|
1406
1136
|
// src/commands/comments.ts
|
|
1407
|
-
import { Command as
|
|
1137
|
+
import { Command as Command6 } from "commander";
|
|
1408
1138
|
|
|
1409
1139
|
// src/output/format.ts
|
|
1410
1140
|
var _mode = "auto";
|
|
@@ -1489,7 +1219,7 @@ async function paginateResults(fetcher) {
|
|
|
1489
1219
|
|
|
1490
1220
|
// src/commands/comments.ts
|
|
1491
1221
|
function commentsCommand() {
|
|
1492
|
-
const cmd = new
|
|
1222
|
+
const cmd = new Command6("comments");
|
|
1493
1223
|
cmd.description("list comments on a Notion page").argument("<id/url>", "Notion page ID or URL").option("--json", "output as JSON").action(
|
|
1494
1224
|
withErrorHandling(async (idOrUrl, opts) => {
|
|
1495
1225
|
if (opts.json) setOutputMode("json");
|
|
@@ -1520,7 +1250,7 @@ function commentsCommand() {
|
|
|
1520
1250
|
}
|
|
1521
1251
|
|
|
1522
1252
|
// src/commands/completion.ts
|
|
1523
|
-
import { Command as
|
|
1253
|
+
import { Command as Command7 } from "commander";
|
|
1524
1254
|
var BASH_COMPLETION = `# notion bash completion
|
|
1525
1255
|
_notion_completion() {
|
|
1526
1256
|
local cur prev words cword
|
|
@@ -1622,7 +1352,7 @@ complete -c notion -n '__fish_seen_subcommand_from completion' -a zsh -d 'zsh co
|
|
|
1622
1352
|
complete -c notion -n '__fish_seen_subcommand_from completion' -a fish -d 'fish completion script'
|
|
1623
1353
|
`;
|
|
1624
1354
|
function completionCommand() {
|
|
1625
|
-
const cmd = new
|
|
1355
|
+
const cmd = new Command7("completion");
|
|
1626
1356
|
cmd.description("output shell completion script").argument("<shell>", "shell type (bash, zsh, fish)").action(
|
|
1627
1357
|
withErrorHandling(async (shell) => {
|
|
1628
1358
|
switch (shell) {
|
|
@@ -1648,8 +1378,8 @@ function completionCommand() {
|
|
|
1648
1378
|
}
|
|
1649
1379
|
|
|
1650
1380
|
// src/commands/create-page.ts
|
|
1651
|
-
import { Command as
|
|
1652
|
-
async function
|
|
1381
|
+
import { Command as Command8 } from "commander";
|
|
1382
|
+
async function readStdin2() {
|
|
1653
1383
|
const chunks = [];
|
|
1654
1384
|
for await (const chunk of process.stdin) {
|
|
1655
1385
|
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
@@ -1657,7 +1387,7 @@ async function readStdin() {
|
|
|
1657
1387
|
return Buffer.concat(chunks).toString("utf-8");
|
|
1658
1388
|
}
|
|
1659
1389
|
function createPageCommand() {
|
|
1660
|
-
const cmd = new
|
|
1390
|
+
const cmd = new Command8("create-page");
|
|
1661
1391
|
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
1392
|
"-m, --message <markdown>",
|
|
1663
1393
|
"inline markdown content for the page body"
|
|
@@ -1671,11 +1401,15 @@ function createPageCommand() {
|
|
|
1671
1401
|
if (opts.message) {
|
|
1672
1402
|
markdown = opts.message;
|
|
1673
1403
|
} else if (!process.stdin.isTTY) {
|
|
1674
|
-
markdown = await
|
|
1404
|
+
markdown = await readStdin2();
|
|
1675
1405
|
}
|
|
1676
|
-
const blocks = mdToBlocks(markdown);
|
|
1677
1406
|
const parentUuid = toUuid(parseNotionId(opts.parent));
|
|
1678
|
-
const url = await createPage(
|
|
1407
|
+
const url = await createPage(
|
|
1408
|
+
client,
|
|
1409
|
+
parentUuid,
|
|
1410
|
+
opts.title,
|
|
1411
|
+
markdown
|
|
1412
|
+
);
|
|
1679
1413
|
process.stdout.write(`${url}
|
|
1680
1414
|
`);
|
|
1681
1415
|
}
|
|
@@ -1685,7 +1419,7 @@ function createPageCommand() {
|
|
|
1685
1419
|
}
|
|
1686
1420
|
|
|
1687
1421
|
// src/commands/db/query.ts
|
|
1688
|
-
import { Command as
|
|
1422
|
+
import { Command as Command9 } from "commander";
|
|
1689
1423
|
|
|
1690
1424
|
// src/services/database.service.ts
|
|
1691
1425
|
import { isFullPage } from "@notionhq/client";
|
|
@@ -1879,7 +1613,7 @@ function autoSelectColumns(schema, entries) {
|
|
|
1879
1613
|
return selected;
|
|
1880
1614
|
}
|
|
1881
1615
|
function dbQueryCommand() {
|
|
1882
|
-
return new
|
|
1616
|
+
return new Command9("query").description("Query database entries with optional filtering and sorting").argument("<id>", "Notion database ID or URL").option(
|
|
1883
1617
|
"--filter <filter>",
|
|
1884
1618
|
'Filter entries (repeatable): --filter "Status=Done"',
|
|
1885
1619
|
collect,
|
|
@@ -1934,9 +1668,9 @@ function collect(value, previous) {
|
|
|
1934
1668
|
}
|
|
1935
1669
|
|
|
1936
1670
|
// src/commands/db/schema.ts
|
|
1937
|
-
import { Command as
|
|
1671
|
+
import { Command as Command10 } from "commander";
|
|
1938
1672
|
function dbSchemaCommand() {
|
|
1939
|
-
return new
|
|
1673
|
+
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
1674
|
withErrorHandling(async (id, options) => {
|
|
1941
1675
|
const { token } = await resolveToken();
|
|
1942
1676
|
const client = createNotionClient(token);
|
|
@@ -1959,9 +1693,156 @@ function dbSchemaCommand() {
|
|
|
1959
1693
|
);
|
|
1960
1694
|
}
|
|
1961
1695
|
|
|
1696
|
+
// src/commands/edit-page.ts
|
|
1697
|
+
import { Command as Command11 } from "commander";
|
|
1698
|
+
async function readStdin3() {
|
|
1699
|
+
const chunks = [];
|
|
1700
|
+
for await (const chunk of process.stdin) {
|
|
1701
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
1702
|
+
}
|
|
1703
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
1704
|
+
}
|
|
1705
|
+
function editPageCommand() {
|
|
1706
|
+
const cmd = new Command11("edit-page");
|
|
1707
|
+
cmd.description(
|
|
1708
|
+
"replace the entire content of a Notion page with new markdown"
|
|
1709
|
+
).argument("<id/url>", "Notion page ID or URL").option(
|
|
1710
|
+
"-m, --message <markdown>",
|
|
1711
|
+
"new markdown content for the page body"
|
|
1712
|
+
).action(
|
|
1713
|
+
withErrorHandling(async (idOrUrl, opts) => {
|
|
1714
|
+
const { token, source } = await resolveToken();
|
|
1715
|
+
reportTokenSource(source);
|
|
1716
|
+
const client = createNotionClient(token);
|
|
1717
|
+
let markdown = "";
|
|
1718
|
+
if (opts.message) {
|
|
1719
|
+
markdown = opts.message;
|
|
1720
|
+
} else if (!process.stdin.isTTY) {
|
|
1721
|
+
markdown = await readStdin3();
|
|
1722
|
+
if (!markdown.trim()) {
|
|
1723
|
+
throw new CliError(
|
|
1724
|
+
ErrorCodes.INVALID_ARG,
|
|
1725
|
+
"No content provided (stdin was empty).",
|
|
1726
|
+
"Pass markdown via -m/--message or pipe non-empty content through stdin"
|
|
1727
|
+
);
|
|
1728
|
+
}
|
|
1729
|
+
} else {
|
|
1730
|
+
throw new CliError(
|
|
1731
|
+
ErrorCodes.INVALID_ARG,
|
|
1732
|
+
"No content provided.",
|
|
1733
|
+
"Pass markdown via -m/--message or pipe it through stdin"
|
|
1734
|
+
);
|
|
1735
|
+
}
|
|
1736
|
+
const pageId = parseNotionId(idOrUrl);
|
|
1737
|
+
const uuid = toUuid(pageId);
|
|
1738
|
+
await replaceMarkdown(client, uuid, markdown);
|
|
1739
|
+
process.stdout.write("Page content replaced.\n");
|
|
1740
|
+
})
|
|
1741
|
+
);
|
|
1742
|
+
return cmd;
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
// src/commands/init.ts
|
|
1746
|
+
import { confirm, input as input2, password } from "@inquirer/prompts";
|
|
1747
|
+
import { Command as Command12 } from "commander";
|
|
1748
|
+
async function runInitFlow() {
|
|
1749
|
+
const profileName = await input2({
|
|
1750
|
+
message: "Profile name:",
|
|
1751
|
+
default: "default"
|
|
1752
|
+
});
|
|
1753
|
+
const token = await password({
|
|
1754
|
+
message: "Integration token (from notion.so/profile/integrations/internal):",
|
|
1755
|
+
mask: "*"
|
|
1756
|
+
});
|
|
1757
|
+
stderrWrite("Validating token...");
|
|
1758
|
+
const { workspaceName, workspaceId } = await validateToken(token);
|
|
1759
|
+
stderrWrite(success(`\u2713 Connected to workspace: ${bold(workspaceName)}`));
|
|
1760
|
+
const config = await readGlobalConfig();
|
|
1761
|
+
if (config.profiles?.[profileName]) {
|
|
1762
|
+
const replace = await confirm({
|
|
1763
|
+
message: `Profile "${profileName}" already exists. Replace?`,
|
|
1764
|
+
default: false
|
|
1765
|
+
});
|
|
1766
|
+
if (!replace) {
|
|
1767
|
+
stderrWrite("Aborted.");
|
|
1768
|
+
return;
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
const profiles = config.profiles ?? {};
|
|
1772
|
+
profiles[profileName] = {
|
|
1773
|
+
token,
|
|
1774
|
+
workspace_name: workspaceName,
|
|
1775
|
+
workspace_id: workspaceId
|
|
1776
|
+
};
|
|
1777
|
+
await writeGlobalConfig({
|
|
1778
|
+
...config,
|
|
1779
|
+
profiles,
|
|
1780
|
+
active_profile: profileName
|
|
1781
|
+
});
|
|
1782
|
+
stderrWrite(success(`Profile "${profileName}" saved and set as active.`));
|
|
1783
|
+
stderrWrite(dim("Checking integration access..."));
|
|
1784
|
+
try {
|
|
1785
|
+
const notion = createNotionClient(token);
|
|
1786
|
+
const probe = await notion.search({ page_size: 1 });
|
|
1787
|
+
if (probe.results.length === 0) {
|
|
1788
|
+
stderrWrite("");
|
|
1789
|
+
stderrWrite("\u26A0\uFE0F Your integration has no pages connected.");
|
|
1790
|
+
stderrWrite(" To grant access, open any Notion page or database:");
|
|
1791
|
+
stderrWrite(" 1. Click \xB7\xB7\xB7 (three dots) in the top-right corner");
|
|
1792
|
+
stderrWrite(' 2. Select "Connect to"');
|
|
1793
|
+
stderrWrite(` 3. Choose "${workspaceName}"`);
|
|
1794
|
+
stderrWrite(" Then re-run any notion command to confirm access.");
|
|
1795
|
+
} else {
|
|
1796
|
+
stderrWrite(
|
|
1797
|
+
success(
|
|
1798
|
+
`\u2713 Integration has access to content in ${bold(workspaceName)}.`
|
|
1799
|
+
)
|
|
1800
|
+
);
|
|
1801
|
+
}
|
|
1802
|
+
} catch {
|
|
1803
|
+
stderrWrite(
|
|
1804
|
+
dim("(Could not verify integration access \u2014 run `notion ls` to check)")
|
|
1805
|
+
);
|
|
1806
|
+
}
|
|
1807
|
+
stderrWrite("");
|
|
1808
|
+
stderrWrite(
|
|
1809
|
+
dim("Write commands (comment, append, create-page) require additional")
|
|
1810
|
+
);
|
|
1811
|
+
stderrWrite(dim("capabilities in your integration settings:"));
|
|
1812
|
+
stderrWrite(
|
|
1813
|
+
dim(" notion.so/profile/integrations/internal \u2192 your integration \u2192")
|
|
1814
|
+
);
|
|
1815
|
+
stderrWrite(
|
|
1816
|
+
dim(
|
|
1817
|
+
' Capabilities: enable "Read content", "Insert content", "Read comments", "Insert comments"'
|
|
1818
|
+
)
|
|
1819
|
+
);
|
|
1820
|
+
stderrWrite("");
|
|
1821
|
+
stderrWrite(
|
|
1822
|
+
dim("To post comments and create pages attributed to your user account:")
|
|
1823
|
+
);
|
|
1824
|
+
stderrWrite(dim(" notion auth login"));
|
|
1825
|
+
}
|
|
1826
|
+
function initCommand() {
|
|
1827
|
+
const cmd = new Command12("init");
|
|
1828
|
+
cmd.description("authenticate with Notion and save a profile").action(
|
|
1829
|
+
withErrorHandling(async () => {
|
|
1830
|
+
if (!process.stdin.isTTY) {
|
|
1831
|
+
throw new CliError(
|
|
1832
|
+
ErrorCodes.AUTH_NO_TOKEN,
|
|
1833
|
+
"Cannot run interactive init in non-TTY mode.",
|
|
1834
|
+
"Set NOTION_API_TOKEN environment variable or create .notion.yaml"
|
|
1835
|
+
);
|
|
1836
|
+
}
|
|
1837
|
+
await runInitFlow();
|
|
1838
|
+
})
|
|
1839
|
+
);
|
|
1840
|
+
return cmd;
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1962
1843
|
// src/commands/ls.ts
|
|
1963
1844
|
import { isFullPageOrDataSource } from "@notionhq/client";
|
|
1964
|
-
import { Command as
|
|
1845
|
+
import { Command as Command13 } from "commander";
|
|
1965
1846
|
function getTitle(item) {
|
|
1966
1847
|
if (item.object === "data_source") {
|
|
1967
1848
|
return item.title.map((t) => t.plain_text).join("") || "(untitled)";
|
|
@@ -1978,7 +1859,7 @@ function displayType(item) {
|
|
|
1978
1859
|
return item.object === "data_source" ? "database" : item.object;
|
|
1979
1860
|
}
|
|
1980
1861
|
function lsCommand() {
|
|
1981
|
-
const cmd = new
|
|
1862
|
+
const cmd = new Command13("ls");
|
|
1982
1863
|
cmd.description("list accessible Notion pages and databases").option(
|
|
1983
1864
|
"--type <type>",
|
|
1984
1865
|
"filter by object type (page or database)",
|
|
@@ -2041,10 +1922,10 @@ function lsCommand() {
|
|
|
2041
1922
|
// src/commands/open.ts
|
|
2042
1923
|
import { exec } from "child_process";
|
|
2043
1924
|
import { promisify } from "util";
|
|
2044
|
-
import { Command as
|
|
1925
|
+
import { Command as Command14 } from "commander";
|
|
2045
1926
|
var execAsync = promisify(exec);
|
|
2046
1927
|
function openCommand() {
|
|
2047
|
-
const cmd = new
|
|
1928
|
+
const cmd = new Command14("open");
|
|
2048
1929
|
cmd.description("open a Notion page in the default browser").argument("<id/url>", "Notion page ID or URL").action(
|
|
2049
1930
|
withErrorHandling(async (idOrUrl) => {
|
|
2050
1931
|
const id = parseNotionId(idOrUrl);
|
|
@@ -2060,9 +1941,9 @@ function openCommand() {
|
|
|
2060
1941
|
}
|
|
2061
1942
|
|
|
2062
1943
|
// src/commands/profile/list.ts
|
|
2063
|
-
import { Command as
|
|
1944
|
+
import { Command as Command15 } from "commander";
|
|
2064
1945
|
function profileListCommand() {
|
|
2065
|
-
const cmd = new
|
|
1946
|
+
const cmd = new Command15("list");
|
|
2066
1947
|
cmd.description("list all authentication profiles").action(
|
|
2067
1948
|
withErrorHandling(async () => {
|
|
2068
1949
|
const config = await readGlobalConfig();
|
|
@@ -2091,9 +1972,9 @@ function profileListCommand() {
|
|
|
2091
1972
|
}
|
|
2092
1973
|
|
|
2093
1974
|
// src/commands/profile/remove.ts
|
|
2094
|
-
import { Command as
|
|
1975
|
+
import { Command as Command16 } from "commander";
|
|
2095
1976
|
function profileRemoveCommand() {
|
|
2096
|
-
const cmd = new
|
|
1977
|
+
const cmd = new Command16("remove");
|
|
2097
1978
|
cmd.description("remove an authentication profile").argument("<name>", "profile name to remove").action(
|
|
2098
1979
|
withErrorHandling(async (name) => {
|
|
2099
1980
|
const config = await readGlobalConfig();
|
|
@@ -2119,9 +2000,9 @@ function profileRemoveCommand() {
|
|
|
2119
2000
|
}
|
|
2120
2001
|
|
|
2121
2002
|
// src/commands/profile/use.ts
|
|
2122
|
-
import { Command as
|
|
2003
|
+
import { Command as Command17 } from "commander";
|
|
2123
2004
|
function profileUseCommand() {
|
|
2124
|
-
const cmd = new
|
|
2005
|
+
const cmd = new Command17("use");
|
|
2125
2006
|
cmd.description("switch the active profile").argument("<name>", "profile name to activate").action(
|
|
2126
2007
|
withErrorHandling(async (name) => {
|
|
2127
2008
|
const config = await readGlobalConfig();
|
|
@@ -2144,264 +2025,7 @@ function profileUseCommand() {
|
|
|
2144
2025
|
}
|
|
2145
2026
|
|
|
2146
2027
|
// 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
|
-
}
|
|
2028
|
+
import { Command as Command18 } from "commander";
|
|
2405
2029
|
|
|
2406
2030
|
// src/output/markdown.ts
|
|
2407
2031
|
import { Chalk as Chalk2 } from "chalk";
|
|
@@ -2531,69 +2155,43 @@ function renderInline(text) {
|
|
|
2531
2155
|
}
|
|
2532
2156
|
|
|
2533
2157
|
// 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 };
|
|
2158
|
+
async function fetchPageMarkdown(client, pageId) {
|
|
2159
|
+
const [page, markdownResponse] = await Promise.all([
|
|
2160
|
+
client.pages.retrieve({ page_id: pageId }),
|
|
2161
|
+
client.pages.retrieveMarkdown({ page_id: pageId })
|
|
2162
|
+
]);
|
|
2163
|
+
return { page, markdown: markdownResponse.markdown };
|
|
2565
2164
|
}
|
|
2566
2165
|
|
|
2567
2166
|
// src/commands/read.ts
|
|
2568
2167
|
function readCommand() {
|
|
2569
|
-
return new
|
|
2570
|
-
withErrorHandling(
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2168
|
+
return new Command18("read").description("Read a Notion page as markdown").argument("<id>", "Notion page ID or URL").action(
|
|
2169
|
+
withErrorHandling(async (id) => {
|
|
2170
|
+
const { token } = await resolveToken();
|
|
2171
|
+
const client = createNotionClient(token);
|
|
2172
|
+
const pageId = parseNotionId(id);
|
|
2173
|
+
const pageWithMarkdown = await fetchPageMarkdown(client, pageId);
|
|
2174
|
+
const mode = getOutputMode();
|
|
2175
|
+
if (mode === "json") {
|
|
2176
|
+
process.stdout.write(
|
|
2177
|
+
`${JSON.stringify(pageWithMarkdown, null, 2)}
|
|
2579
2178
|
`
|
|
2580
|
-
|
|
2179
|
+
);
|
|
2180
|
+
} else {
|
|
2181
|
+
const { markdown } = pageWithMarkdown;
|
|
2182
|
+
if (mode === "md" || !isatty()) {
|
|
2183
|
+
process.stdout.write(markdown);
|
|
2581
2184
|
} else {
|
|
2582
|
-
|
|
2583
|
-
if (options.md || !isatty()) {
|
|
2584
|
-
process.stdout.write(markdown);
|
|
2585
|
-
} else {
|
|
2586
|
-
process.stdout.write(renderMarkdown(markdown));
|
|
2587
|
-
}
|
|
2185
|
+
process.stdout.write(renderMarkdown(markdown));
|
|
2588
2186
|
}
|
|
2589
2187
|
}
|
|
2590
|
-
)
|
|
2188
|
+
})
|
|
2591
2189
|
);
|
|
2592
2190
|
}
|
|
2593
2191
|
|
|
2594
2192
|
// src/commands/search.ts
|
|
2595
2193
|
import { isFullPageOrDataSource as isFullPageOrDataSource2 } from "@notionhq/client";
|
|
2596
|
-
import { Command as
|
|
2194
|
+
import { Command as Command19 } from "commander";
|
|
2597
2195
|
function getTitle2(item) {
|
|
2598
2196
|
if (item.object === "data_source") {
|
|
2599
2197
|
return item.title.map((t) => t.plain_text).join("") || "(untitled)";
|
|
@@ -2613,7 +2211,7 @@ function displayType2(item) {
|
|
|
2613
2211
|
return item.object === "data_source" ? "database" : item.object;
|
|
2614
2212
|
}
|
|
2615
2213
|
function searchCommand() {
|
|
2616
|
-
const cmd = new
|
|
2214
|
+
const cmd = new Command19("search");
|
|
2617
2215
|
cmd.description("search Notion workspace by keyword").argument("<query>", "search keyword").option(
|
|
2618
2216
|
"--type <type>",
|
|
2619
2217
|
"filter by object type (page or database)",
|
|
@@ -2671,7 +2269,7 @@ function searchCommand() {
|
|
|
2671
2269
|
}
|
|
2672
2270
|
|
|
2673
2271
|
// src/commands/users.ts
|
|
2674
|
-
import { Command as
|
|
2272
|
+
import { Command as Command20 } from "commander";
|
|
2675
2273
|
function getEmailOrWorkspace(user) {
|
|
2676
2274
|
if (user.type === "person") {
|
|
2677
2275
|
return user.person.email ?? "\u2014";
|
|
@@ -2683,7 +2281,7 @@ function getEmailOrWorkspace(user) {
|
|
|
2683
2281
|
return "\u2014";
|
|
2684
2282
|
}
|
|
2685
2283
|
function usersCommand() {
|
|
2686
|
-
const cmd = new
|
|
2284
|
+
const cmd = new Command20("users");
|
|
2687
2285
|
cmd.description("list all users in the workspace").option("--json", "output as JSON").action(
|
|
2688
2286
|
withErrorHandling(async (opts) => {
|
|
2689
2287
|
if (opts.json) setOutputMode("json");
|
|
@@ -2714,7 +2312,7 @@ var __dirname = dirname(__filename);
|
|
|
2714
2312
|
var pkg = JSON.parse(
|
|
2715
2313
|
readFileSync(join3(__dirname, "../package.json"), "utf-8")
|
|
2716
2314
|
);
|
|
2717
|
-
var program = new
|
|
2315
|
+
var program = new Command21();
|
|
2718
2316
|
program.name("notion").description("Notion CLI \u2014 read Notion pages and databases from the terminal").version(pkg.version);
|
|
2719
2317
|
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
2318
|
program.configureOutput({
|
|
@@ -2735,7 +2333,7 @@ program.hook("preAction", (thisCommand) => {
|
|
|
2735
2333
|
setOutputMode("md");
|
|
2736
2334
|
}
|
|
2737
2335
|
});
|
|
2738
|
-
var authCmd = new
|
|
2336
|
+
var authCmd = new Command21("auth").description("manage Notion authentication");
|
|
2739
2337
|
authCmd.action(authDefaultAction(authCmd));
|
|
2740
2338
|
authCmd.addCommand(loginCommand());
|
|
2741
2339
|
authCmd.addCommand(logoutCommand());
|
|
@@ -2745,7 +2343,7 @@ authCmd.addCommand(profileUseCommand());
|
|
|
2745
2343
|
authCmd.addCommand(profileRemoveCommand());
|
|
2746
2344
|
program.addCommand(authCmd);
|
|
2747
2345
|
program.addCommand(initCommand(), { hidden: true });
|
|
2748
|
-
var profileCmd = new
|
|
2346
|
+
var profileCmd = new Command21("profile").description(
|
|
2749
2347
|
"manage authentication profiles"
|
|
2750
2348
|
);
|
|
2751
2349
|
profileCmd.addCommand(profileListCommand());
|
|
@@ -2761,7 +2359,8 @@ program.addCommand(readCommand());
|
|
|
2761
2359
|
program.addCommand(commentAddCommand());
|
|
2762
2360
|
program.addCommand(appendCommand());
|
|
2763
2361
|
program.addCommand(createPageCommand());
|
|
2764
|
-
|
|
2362
|
+
program.addCommand(editPageCommand());
|
|
2363
|
+
var dbCmd = new Command21("db").description("Database operations");
|
|
2765
2364
|
dbCmd.addCommand(dbSchemaCommand());
|
|
2766
2365
|
dbCmd.addCommand(dbQueryCommand());
|
|
2767
2366
|
program.addCommand(dbCmd);
|