@andrzejchm/notion-cli 0.11.0 → 0.12.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/.agents/skills/using-notion-cli/SKILL.md +55 -6
- package/README.md +25 -0
- package/dist/cli.js +448 -129
- package/dist/cli.js.map +1 -1
- package/docs/README.agents.md +60 -0
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { readFileSync as readFileSync2 } from "fs";
|
|
5
5
|
import { dirname as dirname2, join as join4 } from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
|
-
import { Command as
|
|
7
|
+
import { Command as Command27 } from "commander";
|
|
8
8
|
|
|
9
9
|
// src/commands/append.ts
|
|
10
10
|
import { Command } from "commander";
|
|
@@ -542,7 +542,196 @@ function reportTokenSource(source) {
|
|
|
542
542
|
stderrWrite(dim(`Using token from ${source}`));
|
|
543
543
|
}
|
|
544
544
|
|
|
545
|
+
// src/services/upload.service.ts
|
|
546
|
+
import { createReadStream, existsSync, statSync } from "fs";
|
|
547
|
+
import { basename, extname } from "path";
|
|
548
|
+
var PART_SIZE = 20 * 1024 * 1024;
|
|
549
|
+
var MIME_MAP = {
|
|
550
|
+
".png": "image/png",
|
|
551
|
+
".jpg": "image/jpeg",
|
|
552
|
+
".jpeg": "image/jpeg",
|
|
553
|
+
".gif": "image/gif",
|
|
554
|
+
".webp": "image/webp",
|
|
555
|
+
".svg": "image/svg+xml",
|
|
556
|
+
".bmp": "image/bmp",
|
|
557
|
+
".ico": "image/x-icon",
|
|
558
|
+
".tiff": "image/tiff",
|
|
559
|
+
".tif": "image/tiff",
|
|
560
|
+
".heic": "image/heic",
|
|
561
|
+
".avif": "image/avif",
|
|
562
|
+
".pdf": "application/pdf",
|
|
563
|
+
".mp3": "audio/mpeg",
|
|
564
|
+
".wav": "audio/wav",
|
|
565
|
+
".ogg": "audio/ogg",
|
|
566
|
+
".m4a": "audio/mp4",
|
|
567
|
+
".flac": "audio/flac",
|
|
568
|
+
".aac": "audio/aac",
|
|
569
|
+
".mp4": "video/mp4",
|
|
570
|
+
".mov": "video/quicktime",
|
|
571
|
+
".avi": "video/x-msvideo",
|
|
572
|
+
".webm": "video/webm",
|
|
573
|
+
".mkv": "video/x-matroska",
|
|
574
|
+
".csv": "text/csv",
|
|
575
|
+
".json": "application/json",
|
|
576
|
+
".txt": "text/plain",
|
|
577
|
+
".md": "text/markdown",
|
|
578
|
+
".html": "text/html",
|
|
579
|
+
".xml": "application/xml",
|
|
580
|
+
".zip": "application/zip",
|
|
581
|
+
".gz": "application/gzip",
|
|
582
|
+
".doc": "application/msword",
|
|
583
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
584
|
+
".xls": "application/vnd.ms-excel",
|
|
585
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
586
|
+
".ppt": "application/vnd.ms-powerpoint",
|
|
587
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
588
|
+
};
|
|
589
|
+
function detectMimeType(filePath) {
|
|
590
|
+
const ext = extname(filePath).toLowerCase();
|
|
591
|
+
return MIME_MAP[ext] ?? "application/octet-stream";
|
|
592
|
+
}
|
|
593
|
+
function resolveBlockType(contentType) {
|
|
594
|
+
const base = contentType.split(";")[0].trim().toLowerCase();
|
|
595
|
+
if (base.startsWith("image/")) return "image";
|
|
596
|
+
if (base.startsWith("audio/")) return "audio";
|
|
597
|
+
if (base.startsWith("video/")) return "video";
|
|
598
|
+
if (base === "application/pdf") return "pdf";
|
|
599
|
+
return "file";
|
|
600
|
+
}
|
|
601
|
+
async function readStreamChunk(filePath, start, end) {
|
|
602
|
+
return new Promise((resolve, reject) => {
|
|
603
|
+
const chunks = [];
|
|
604
|
+
const stream = createReadStream(filePath, { start, end });
|
|
605
|
+
stream.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
606
|
+
stream.on("end", () => resolve(Buffer.concat(chunks)));
|
|
607
|
+
stream.on("error", reject);
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
async function uploadSinglePart(client, filePath, filename, contentType, fileSize) {
|
|
611
|
+
const upload = await client.fileUploads.create({
|
|
612
|
+
mode: "single_part",
|
|
613
|
+
filename,
|
|
614
|
+
content_type: contentType
|
|
615
|
+
});
|
|
616
|
+
const data = await readStreamChunk(filePath, 0, fileSize - 1);
|
|
617
|
+
const blob = new Blob(
|
|
618
|
+
[
|
|
619
|
+
data.buffer.slice(
|
|
620
|
+
data.byteOffset,
|
|
621
|
+
data.byteOffset + data.byteLength
|
|
622
|
+
)
|
|
623
|
+
],
|
|
624
|
+
{ type: contentType }
|
|
625
|
+
);
|
|
626
|
+
await client.fileUploads.send({
|
|
627
|
+
file_upload_id: upload.id,
|
|
628
|
+
file: { data: blob, filename }
|
|
629
|
+
});
|
|
630
|
+
return upload.id;
|
|
631
|
+
}
|
|
632
|
+
async function uploadMultiPart(client, filePath, filename, contentType, fileSize) {
|
|
633
|
+
const numberOfParts = Math.ceil(fileSize / PART_SIZE);
|
|
634
|
+
const upload = await client.fileUploads.create({
|
|
635
|
+
mode: "multi_part",
|
|
636
|
+
filename,
|
|
637
|
+
content_type: contentType,
|
|
638
|
+
number_of_parts: numberOfParts
|
|
639
|
+
});
|
|
640
|
+
for (let part = 0; part < numberOfParts; part++) {
|
|
641
|
+
const start = part * PART_SIZE;
|
|
642
|
+
const end = Math.min(start + PART_SIZE, fileSize) - 1;
|
|
643
|
+
const data = await readStreamChunk(filePath, start, end);
|
|
644
|
+
const blob = new Blob(
|
|
645
|
+
[
|
|
646
|
+
data.buffer.slice(
|
|
647
|
+
data.byteOffset,
|
|
648
|
+
data.byteOffset + data.byteLength
|
|
649
|
+
)
|
|
650
|
+
],
|
|
651
|
+
{ type: contentType }
|
|
652
|
+
);
|
|
653
|
+
await client.fileUploads.send({
|
|
654
|
+
file_upload_id: upload.id,
|
|
655
|
+
file: { data: blob, filename },
|
|
656
|
+
part_number: String(part + 1)
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
await client.fileUploads.complete({ file_upload_id: upload.id });
|
|
660
|
+
return upload.id;
|
|
661
|
+
}
|
|
662
|
+
async function uploadFile(client, filePath) {
|
|
663
|
+
const filename = basename(filePath);
|
|
664
|
+
const contentType = detectMimeType(filePath);
|
|
665
|
+
const { size: fileSize } = statSync(filePath);
|
|
666
|
+
if (fileSize === 0) {
|
|
667
|
+
throw new CliError(
|
|
668
|
+
ErrorCodes.INVALID_ARG,
|
|
669
|
+
`Cannot upload empty file: ${filename}`,
|
|
670
|
+
"Provide a file with content"
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
const fileUploadId = fileSize <= PART_SIZE ? await uploadSinglePart(
|
|
674
|
+
client,
|
|
675
|
+
filePath,
|
|
676
|
+
filename,
|
|
677
|
+
contentType,
|
|
678
|
+
fileSize
|
|
679
|
+
) : await uploadMultiPart(
|
|
680
|
+
client,
|
|
681
|
+
filePath,
|
|
682
|
+
filename,
|
|
683
|
+
contentType,
|
|
684
|
+
fileSize
|
|
685
|
+
);
|
|
686
|
+
return { fileUploadId, filename, contentType };
|
|
687
|
+
}
|
|
688
|
+
function buildFileBlock(fileUploadId, blockType, caption) {
|
|
689
|
+
const captionRichText = caption ? [{ type: "text", text: { content: caption } }] : [];
|
|
690
|
+
const fileContent = {
|
|
691
|
+
file_upload: { id: fileUploadId },
|
|
692
|
+
type: "file_upload",
|
|
693
|
+
caption: captionRichText
|
|
694
|
+
};
|
|
695
|
+
switch (blockType) {
|
|
696
|
+
case "image":
|
|
697
|
+
return { type: "image", image: fileContent };
|
|
698
|
+
case "audio":
|
|
699
|
+
return { type: "audio", audio: fileContent };
|
|
700
|
+
case "video":
|
|
701
|
+
return { type: "video", video: fileContent };
|
|
702
|
+
case "pdf":
|
|
703
|
+
return { type: "pdf", pdf: fileContent };
|
|
704
|
+
case "file":
|
|
705
|
+
return { type: "file", file: fileContent };
|
|
706
|
+
default:
|
|
707
|
+
throw new CliError(
|
|
708
|
+
ErrorCodes.INVALID_ARG,
|
|
709
|
+
`Unknown block type: ${blockType}`,
|
|
710
|
+
"Valid types are: image, file, pdf, audio, video"
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
async function uploadFilesAsBlocks(files, opts, client) {
|
|
715
|
+
for (const filePath of files) {
|
|
716
|
+
if (!existsSync(filePath)) {
|
|
717
|
+
throw new CliError(
|
|
718
|
+
ErrorCodes.INVALID_ARG,
|
|
719
|
+
`File not found: ${filePath}`,
|
|
720
|
+
"Provide a valid file path"
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
return Promise.all(
|
|
725
|
+
files.map(async (filePath) => {
|
|
726
|
+
const result = await uploadFile(client, filePath);
|
|
727
|
+
const blockType = opts.type ?? resolveBlockType(result.contentType);
|
|
728
|
+
return buildFileBlock(result.fileUploadId, blockType, opts.caption);
|
|
729
|
+
})
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
|
|
545
733
|
// src/services/write.service.ts
|
|
734
|
+
import { existsSync as existsSync2 } from "fs";
|
|
546
735
|
async function addComment(client, target, text, options = {}) {
|
|
547
736
|
const richText = [
|
|
548
737
|
{
|
|
@@ -661,22 +850,44 @@ async function replaceMarkdown(client, pageId, newMarkdown, options) {
|
|
|
661
850
|
}
|
|
662
851
|
});
|
|
663
852
|
}
|
|
664
|
-
function buildIconCover(options) {
|
|
853
|
+
async function buildIconCover(client, options) {
|
|
665
854
|
const result = {};
|
|
666
855
|
if (options?.icon) {
|
|
667
856
|
const isUrl = /^https?:\/\//i.test(options.icon);
|
|
668
857
|
if (isUrl) {
|
|
669
858
|
result.icon = { type: "external", external: { url: options.icon } };
|
|
859
|
+
} else if (existsSync2(options.icon)) {
|
|
860
|
+
const uploaded = await uploadFile(client, options.icon);
|
|
861
|
+
result.icon = {
|
|
862
|
+
type: "file_upload",
|
|
863
|
+
file_upload: { id: uploaded.fileUploadId }
|
|
864
|
+
};
|
|
670
865
|
} else {
|
|
671
866
|
result.icon = { type: "emoji", emoji: options.icon };
|
|
672
867
|
}
|
|
673
868
|
}
|
|
674
869
|
if (options?.cover) {
|
|
675
|
-
|
|
870
|
+
const isUrl = /^https?:\/\//i.test(options.cover);
|
|
871
|
+
if (isUrl) {
|
|
872
|
+
result.cover = { type: "external", external: { url: options.cover } };
|
|
873
|
+
} else if (existsSync2(options.cover)) {
|
|
874
|
+
const uploaded = await uploadFile(client, options.cover);
|
|
875
|
+
result.cover = {
|
|
876
|
+
type: "file_upload",
|
|
877
|
+
file_upload: { id: uploaded.fileUploadId }
|
|
878
|
+
};
|
|
879
|
+
} else {
|
|
880
|
+
throw new CliError(
|
|
881
|
+
ErrorCodes.INVALID_ARG,
|
|
882
|
+
`Cover not found: "${options.cover}" is not a valid URL or existing file path.`,
|
|
883
|
+
"Provide an http(s):// URL or a valid local file path for --cover"
|
|
884
|
+
);
|
|
885
|
+
}
|
|
676
886
|
}
|
|
677
887
|
return result;
|
|
678
888
|
}
|
|
679
889
|
async function createPage(client, parentId, title, markdown, options) {
|
|
890
|
+
const iconCover = await buildIconCover(client, options);
|
|
680
891
|
const response = await client.pages.create({
|
|
681
892
|
parent: { type: "page_id", page_id: parentId },
|
|
682
893
|
properties: {
|
|
@@ -685,7 +896,7 @@ async function createPage(client, parentId, title, markdown, options) {
|
|
|
685
896
|
}
|
|
686
897
|
},
|
|
687
898
|
...markdown.trim() ? { markdown } : {},
|
|
688
|
-
...
|
|
899
|
+
...iconCover
|
|
689
900
|
});
|
|
690
901
|
const url = "url" in response ? response.url : response.id;
|
|
691
902
|
return url;
|
|
@@ -697,11 +908,12 @@ async function createPageInDatabase(client, databaseId, titlePropName, title, ex
|
|
|
697
908
|
title: [{ type: "text", text: { content: title, link: null } }]
|
|
698
909
|
}
|
|
699
910
|
};
|
|
911
|
+
const iconCover = await buildIconCover(client, options);
|
|
700
912
|
const response = await client.pages.create({
|
|
701
913
|
parent: { type: "database_id", database_id: databaseId },
|
|
702
914
|
properties,
|
|
703
915
|
...markdown.trim() ? { markdown } : {},
|
|
704
|
-
...
|
|
916
|
+
...iconCover
|
|
705
917
|
});
|
|
706
918
|
const url = "url" in response ? response.url : response.id;
|
|
707
919
|
return url;
|
|
@@ -717,54 +929,92 @@ async function readStdin() {
|
|
|
717
929
|
}
|
|
718
930
|
|
|
719
931
|
// src/commands/append.ts
|
|
932
|
+
function collectFiles(val, acc) {
|
|
933
|
+
acc.push(val);
|
|
934
|
+
return acc;
|
|
935
|
+
}
|
|
936
|
+
async function resolveMarkdown(message, hasFiles) {
|
|
937
|
+
if (message) return message;
|
|
938
|
+
if (!process.stdin.isTTY && !hasFiles) return readStdin();
|
|
939
|
+
if (!hasFiles) {
|
|
940
|
+
throw new CliError(
|
|
941
|
+
ErrorCodes.INVALID_ARG,
|
|
942
|
+
"No content to append.",
|
|
943
|
+
"Pass markdown via -m/--message, pipe it through stdin, or use --file to attach files"
|
|
944
|
+
);
|
|
945
|
+
}
|
|
946
|
+
return "";
|
|
947
|
+
}
|
|
948
|
+
async function appendMarkdownWithErrorHandling(client, uuid, markdown, after) {
|
|
949
|
+
try {
|
|
950
|
+
await appendMarkdown(client, uuid, markdown, after ? { after } : void 0);
|
|
951
|
+
} catch (error2) {
|
|
952
|
+
if (after && isNotionValidationError(error2)) {
|
|
953
|
+
throw new CliError(
|
|
954
|
+
ErrorCodes.INVALID_ARG,
|
|
955
|
+
`Selector not found: "${after}". ${error2.message}`,
|
|
956
|
+
SELECTOR_HINT,
|
|
957
|
+
error2
|
|
958
|
+
);
|
|
959
|
+
}
|
|
960
|
+
throw error2;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
async function appendFileBlocks(client, uuid, filePaths) {
|
|
964
|
+
const blocks = await uploadFilesAsBlocks(filePaths, {}, client);
|
|
965
|
+
await client.blocks.children.append({
|
|
966
|
+
block_id: uuid,
|
|
967
|
+
children: blocks
|
|
968
|
+
});
|
|
969
|
+
}
|
|
720
970
|
function appendCommand() {
|
|
721
971
|
const cmd = new Command("append");
|
|
722
972
|
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(
|
|
723
973
|
"--after <selector>",
|
|
724
974
|
'insert after matched content \u2014 ellipsis selector, e.g. "## Section...end of section"'
|
|
975
|
+
).option(
|
|
976
|
+
"--file <path>",
|
|
977
|
+
"attach a local file to the page (repeatable)",
|
|
978
|
+
collectFiles,
|
|
979
|
+
[]
|
|
725
980
|
).action(
|
|
726
981
|
withErrorHandling(
|
|
727
982
|
async (idOrUrl, opts) => {
|
|
728
983
|
const { token, source } = await resolveToken();
|
|
729
984
|
reportTokenSource(source);
|
|
730
985
|
const client = createNotionClient(token);
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
markdown = await readStdin();
|
|
736
|
-
} else {
|
|
986
|
+
const hasFiles = opts.file.length > 0;
|
|
987
|
+
const markdown = await resolveMarkdown(opts.message, hasFiles);
|
|
988
|
+
const uuid = toUuid(parseNotionId(idOrUrl));
|
|
989
|
+
if (opts.after && !markdown.trim()) {
|
|
737
990
|
throw new CliError(
|
|
738
991
|
ErrorCodes.INVALID_ARG,
|
|
739
|
-
"
|
|
740
|
-
"
|
|
992
|
+
"--after requires markdown content (-m or stdin)",
|
|
993
|
+
"Provide markdown via -m/--message or pipe it through stdin"
|
|
741
994
|
);
|
|
742
995
|
}
|
|
743
|
-
if (
|
|
744
|
-
|
|
745
|
-
return;
|
|
746
|
-
}
|
|
747
|
-
const pageId = parseNotionId(idOrUrl);
|
|
748
|
-
const uuid = toUuid(pageId);
|
|
749
|
-
try {
|
|
750
|
-
await appendMarkdown(
|
|
996
|
+
if (markdown.trim()) {
|
|
997
|
+
await appendMarkdownWithErrorHandling(
|
|
751
998
|
client,
|
|
752
999
|
uuid,
|
|
753
1000
|
markdown,
|
|
754
|
-
opts.after
|
|
1001
|
+
opts.after
|
|
755
1002
|
);
|
|
756
|
-
} catch (error2) {
|
|
757
|
-
if (opts.after && isNotionValidationError(error2)) {
|
|
758
|
-
throw new CliError(
|
|
759
|
-
ErrorCodes.INVALID_ARG,
|
|
760
|
-
`Selector not found: "${opts.after}". ${error2.message}`,
|
|
761
|
-
SELECTOR_HINT,
|
|
762
|
-
error2
|
|
763
|
-
);
|
|
764
|
-
}
|
|
765
|
-
throw error2;
|
|
766
1003
|
}
|
|
767
|
-
|
|
1004
|
+
if (hasFiles) {
|
|
1005
|
+
await appendFileBlocks(client, uuid, opts.file);
|
|
1006
|
+
}
|
|
1007
|
+
if (hasFiles && markdown.trim()) {
|
|
1008
|
+
process.stdout.write(
|
|
1009
|
+
`Appended and attached ${opts.file.length} file(s).
|
|
1010
|
+
`
|
|
1011
|
+
);
|
|
1012
|
+
} else if (hasFiles) {
|
|
1013
|
+
process.stdout.write(`Attached ${opts.file.length} file(s).
|
|
1014
|
+
`);
|
|
1015
|
+
} else {
|
|
1016
|
+
process.stdout.write("Appended.\n");
|
|
1017
|
+
}
|
|
768
1018
|
}
|
|
769
1019
|
)
|
|
770
1020
|
);
|
|
@@ -867,6 +1117,49 @@ function archiveCommand() {
|
|
|
867
1117
|
return cmd;
|
|
868
1118
|
}
|
|
869
1119
|
|
|
1120
|
+
// src/commands/attach.ts
|
|
1121
|
+
import { Command as Command3, Option } from "commander";
|
|
1122
|
+
function attachCommand() {
|
|
1123
|
+
const cmd = new Command3("attach");
|
|
1124
|
+
cmd.description("upload and attach file(s) to a Notion page").argument("<id/url>", "Notion page ID or URL").argument("<file>", "file to attach").argument("[files...]", "additional files to attach").option("--caption <text>", "caption for the file block(s)").addOption(
|
|
1125
|
+
new Option(
|
|
1126
|
+
"--type <type>",
|
|
1127
|
+
"override auto-detected block type (image|file|pdf|audio|video)"
|
|
1128
|
+
).choices(["image", "file", "pdf", "audio", "video"])
|
|
1129
|
+
).action(
|
|
1130
|
+
withErrorHandling(
|
|
1131
|
+
async (idOrUrl, firstFile, extraFiles, opts) => {
|
|
1132
|
+
const { token, source } = await resolveToken();
|
|
1133
|
+
reportTokenSource(source);
|
|
1134
|
+
const client = createNotionClient(token);
|
|
1135
|
+
const pageId = toUuid(parseNotionId(idOrUrl));
|
|
1136
|
+
const allFiles = [firstFile, ...extraFiles];
|
|
1137
|
+
const blocks = await uploadFilesAsBlocks(
|
|
1138
|
+
allFiles,
|
|
1139
|
+
{ caption: opts.caption, type: opts.type },
|
|
1140
|
+
client
|
|
1141
|
+
);
|
|
1142
|
+
const response = await client.blocks.children.append({
|
|
1143
|
+
block_id: pageId,
|
|
1144
|
+
children: blocks
|
|
1145
|
+
});
|
|
1146
|
+
const mode = getOutputMode();
|
|
1147
|
+
if (mode === "json") {
|
|
1148
|
+
process.stdout.write(`${formatJSON(response)}
|
|
1149
|
+
`);
|
|
1150
|
+
} else {
|
|
1151
|
+
const pageUrl = `https://www.notion.so/${parseNotionId(idOrUrl)}`;
|
|
1152
|
+
process.stdout.write(
|
|
1153
|
+
`Attached ${allFiles.length} file(s) to ${pageUrl}
|
|
1154
|
+
`
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
)
|
|
1159
|
+
);
|
|
1160
|
+
return cmd;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
870
1163
|
// src/commands/auth/index.ts
|
|
871
1164
|
function authDefaultAction(authCmd2) {
|
|
872
1165
|
return async () => {
|
|
@@ -876,7 +1169,7 @@ function authDefaultAction(authCmd2) {
|
|
|
876
1169
|
|
|
877
1170
|
// src/commands/auth/login.ts
|
|
878
1171
|
import { input } from "@inquirer/prompts";
|
|
879
|
-
import { Command as
|
|
1172
|
+
import { Command as Command4 } from "commander";
|
|
880
1173
|
|
|
881
1174
|
// src/oauth/oauth-flow.ts
|
|
882
1175
|
import { spawn } from "child_process";
|
|
@@ -1123,7 +1416,7 @@ Waiting for callback (up to 120 seconds)...
|
|
|
1123
1416
|
|
|
1124
1417
|
// src/commands/auth/login.ts
|
|
1125
1418
|
function loginCommand() {
|
|
1126
|
-
const cmd = new
|
|
1419
|
+
const cmd = new Command4("login");
|
|
1127
1420
|
cmd.description("authenticate with Notion via OAuth").option("--profile <name>", "profile name to store credentials in").option(
|
|
1128
1421
|
"--manual",
|
|
1129
1422
|
"print auth URL instead of opening browser (for headless OAuth)"
|
|
@@ -1186,7 +1479,7 @@ function loginCommand() {
|
|
|
1186
1479
|
|
|
1187
1480
|
// src/commands/auth/logout.ts
|
|
1188
1481
|
import { select } from "@inquirer/prompts";
|
|
1189
|
-
import { Command as
|
|
1482
|
+
import { Command as Command5 } from "commander";
|
|
1190
1483
|
function profileLabel(name, profile) {
|
|
1191
1484
|
const parts = [];
|
|
1192
1485
|
if (profile.oauth_access_token)
|
|
@@ -1199,7 +1492,7 @@ function profileLabel(name, profile) {
|
|
|
1199
1492
|
return `${bold(name)} ${dim(authDesc)}${workspace}`;
|
|
1200
1493
|
}
|
|
1201
1494
|
function logoutCommand() {
|
|
1202
|
-
const cmd = new
|
|
1495
|
+
const cmd = new Command5("logout");
|
|
1203
1496
|
cmd.description("remove a profile and its credentials").option(
|
|
1204
1497
|
"--profile <name>",
|
|
1205
1498
|
"profile name to remove (skips interactive selector)"
|
|
@@ -1257,9 +1550,9 @@ function logoutCommand() {
|
|
|
1257
1550
|
}
|
|
1258
1551
|
|
|
1259
1552
|
// src/commands/auth/status.ts
|
|
1260
|
-
import { Command as
|
|
1553
|
+
import { Command as Command6 } from "commander";
|
|
1261
1554
|
function statusCommand() {
|
|
1262
|
-
const cmd = new
|
|
1555
|
+
const cmd = new Command6("status");
|
|
1263
1556
|
cmd.description("show authentication status for the active profile").option("--profile <name>", "profile name to check").action(
|
|
1264
1557
|
withErrorHandling(async (opts) => {
|
|
1265
1558
|
let profileName = opts.profile;
|
|
@@ -1317,7 +1610,7 @@ function statusCommand() {
|
|
|
1317
1610
|
}
|
|
1318
1611
|
|
|
1319
1612
|
// src/commands/comment-add.ts
|
|
1320
|
-
import { Command as
|
|
1613
|
+
import { Command as Command7 } from "commander";
|
|
1321
1614
|
function resolveTarget(idOrUrl, opts) {
|
|
1322
1615
|
const targetCount = [idOrUrl, opts.replyTo, opts.block].filter(
|
|
1323
1616
|
Boolean
|
|
@@ -1345,7 +1638,7 @@ function resolveTarget(idOrUrl, opts) {
|
|
|
1345
1638
|
);
|
|
1346
1639
|
}
|
|
1347
1640
|
function commentAddCommand() {
|
|
1348
|
-
const cmd = new
|
|
1641
|
+
const cmd = new Command7("comment");
|
|
1349
1642
|
cmd.description("add a comment to a Notion page, block, or discussion thread").argument("[id/url]", "Notion page ID or URL").requiredOption("-m, --message <text>", "comment text to post").option(
|
|
1350
1643
|
"--reply-to <discussion-id>",
|
|
1351
1644
|
"reply to an existing discussion thread"
|
|
@@ -1367,7 +1660,7 @@ function commentAddCommand() {
|
|
|
1367
1660
|
}
|
|
1368
1661
|
|
|
1369
1662
|
// src/commands/comments.ts
|
|
1370
|
-
import { Command as
|
|
1663
|
+
import { Command as Command8 } from "commander";
|
|
1371
1664
|
|
|
1372
1665
|
// src/output/paginate.ts
|
|
1373
1666
|
async function paginateResults(fetcher) {
|
|
@@ -1393,7 +1686,7 @@ function formatParent(parent) {
|
|
|
1393
1686
|
}
|
|
1394
1687
|
}
|
|
1395
1688
|
function commentsCommand() {
|
|
1396
|
-
const cmd = new
|
|
1689
|
+
const cmd = new Command8("comments");
|
|
1397
1690
|
cmd.description("list comments on a Notion page").argument("<id/url>", "Notion page ID or URL").option("--json", "output as JSON").action(
|
|
1398
1691
|
withErrorHandling(async (idOrUrl, opts) => {
|
|
1399
1692
|
if (opts.json) setOutputMode("json");
|
|
@@ -1430,7 +1723,7 @@ function commentsCommand() {
|
|
|
1430
1723
|
}
|
|
1431
1724
|
|
|
1432
1725
|
// src/commands/completion.ts
|
|
1433
|
-
import { Command as
|
|
1726
|
+
import { Command as Command9 } from "commander";
|
|
1434
1727
|
var BASH_COMPLETION = `# notion bash completion
|
|
1435
1728
|
_notion_completion() {
|
|
1436
1729
|
local cur prev words cword
|
|
@@ -1534,7 +1827,7 @@ complete -c notion -n '__fish_seen_subcommand_from completion' -a zsh -d 'zsh co
|
|
|
1534
1827
|
complete -c notion -n '__fish_seen_subcommand_from completion' -a fish -d 'fish completion script'
|
|
1535
1828
|
`;
|
|
1536
1829
|
function completionCommand() {
|
|
1537
|
-
const cmd = new
|
|
1830
|
+
const cmd = new Command9("completion");
|
|
1538
1831
|
cmd.description("output shell completion script").argument("<shell>", "shell type (bash, zsh, fish)").action(
|
|
1539
1832
|
withErrorHandling(async (shell) => {
|
|
1540
1833
|
switch (shell) {
|
|
@@ -1560,7 +1853,7 @@ function completionCommand() {
|
|
|
1560
1853
|
}
|
|
1561
1854
|
|
|
1562
1855
|
// src/commands/create-page.ts
|
|
1563
|
-
import { Command as
|
|
1856
|
+
import { Command as Command10 } from "commander";
|
|
1564
1857
|
|
|
1565
1858
|
// src/services/database.service.ts
|
|
1566
1859
|
import { isFullPage } from "@notionhq/client";
|
|
@@ -1848,6 +2141,7 @@ function displayPropertyValue(prop) {
|
|
|
1848
2141
|
}
|
|
1849
2142
|
|
|
1850
2143
|
// src/services/update.service.ts
|
|
2144
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1851
2145
|
var UNSUPPORTED_TYPES = /* @__PURE__ */ new Set([
|
|
1852
2146
|
"relation",
|
|
1853
2147
|
"formula",
|
|
@@ -1856,7 +2150,6 @@ var UNSUPPORTED_TYPES = /* @__PURE__ */ new Set([
|
|
|
1856
2150
|
"created_by",
|
|
1857
2151
|
"last_edited_time",
|
|
1858
2152
|
"last_edited_by",
|
|
1859
|
-
"files",
|
|
1860
2153
|
"unique_id",
|
|
1861
2154
|
"verification",
|
|
1862
2155
|
"button"
|
|
@@ -1866,7 +2159,7 @@ function buildPropertyUpdate(propName, propType, value) {
|
|
|
1866
2159
|
throw new CliError(
|
|
1867
2160
|
ErrorCodes.INVALID_ARG,
|
|
1868
2161
|
`Property "${propName}" has type "${propType}" which cannot be set via the CLI.`,
|
|
1869
|
-
"Supported types: title, rich_text, select, status, multi_select, number, checkbox, url, email, phone_number, date"
|
|
2162
|
+
"Supported types: title, rich_text, select, status, multi_select, number, checkbox, url, email, phone_number, date, files"
|
|
1870
2163
|
);
|
|
1871
2164
|
}
|
|
1872
2165
|
if (value === "") {
|
|
@@ -1912,41 +2205,54 @@ function buildPropertyUpdate(propName, propType, value) {
|
|
|
1912
2205
|
const end = parts[1]?.trim();
|
|
1913
2206
|
return { date: end ? { start, end } : { start } };
|
|
1914
2207
|
}
|
|
2208
|
+
case "files":
|
|
2209
|
+
throw new CliError(
|
|
2210
|
+
ErrorCodes.INVALID_ARG,
|
|
2211
|
+
`Property "${propName}" has type "files" which requires async upload \u2014 use buildPropertiesPayloadAsync instead.`,
|
|
2212
|
+
"Use buildPropertiesPayloadAsync with a Notion client to handle file uploads"
|
|
2213
|
+
);
|
|
1915
2214
|
default:
|
|
1916
2215
|
throw new CliError(
|
|
1917
2216
|
ErrorCodes.INVALID_ARG,
|
|
1918
2217
|
`Property "${propName}" has unsupported type "${propType}".`,
|
|
1919
|
-
"Supported types: title, rich_text, select, status, multi_select, number, checkbox, url, email, phone_number, date"
|
|
2218
|
+
"Supported types: title, rich_text, select, status, multi_select, number, checkbox, url, email, phone_number, date, files"
|
|
1920
2219
|
);
|
|
1921
2220
|
}
|
|
1922
2221
|
}
|
|
1923
|
-
function
|
|
2222
|
+
function normalizeSchema(schemaOrPage) {
|
|
1924
2223
|
const isPage = "object" in schemaOrPage && schemaOrPage.object === "page";
|
|
1925
|
-
|
|
2224
|
+
return isPage ? schemaOrPage.properties : schemaOrPage;
|
|
2225
|
+
}
|
|
2226
|
+
function parsePropString(propString) {
|
|
2227
|
+
const eqIdx = propString.indexOf("=");
|
|
2228
|
+
if (eqIdx === -1) {
|
|
2229
|
+
throw new CliError(
|
|
2230
|
+
ErrorCodes.INVALID_ARG,
|
|
2231
|
+
`Invalid --prop value: "${propString}". Expected format: "PropertyName=Value".`,
|
|
2232
|
+
'Example: --prop "Status=Done"'
|
|
2233
|
+
);
|
|
2234
|
+
}
|
|
2235
|
+
return [propString.slice(0, eqIdx).trim(), propString.slice(eqIdx + 1)];
|
|
2236
|
+
}
|
|
2237
|
+
function lookupSchemaProp(propName, schema) {
|
|
2238
|
+
const schemaProp = schema[propName];
|
|
2239
|
+
if (!schemaProp) {
|
|
2240
|
+
const available = Object.keys(schema).join(", ");
|
|
2241
|
+
throw new CliError(
|
|
2242
|
+
ErrorCodes.INVALID_ARG,
|
|
2243
|
+
`Property "${propName}" not found on this page.`,
|
|
2244
|
+
`Available properties: ${available}`
|
|
2245
|
+
);
|
|
2246
|
+
}
|
|
2247
|
+
return schemaProp;
|
|
2248
|
+
}
|
|
2249
|
+
function buildPropertiesPayload(propStrings, schemaOrPage) {
|
|
2250
|
+
const schema = normalizeSchema(schemaOrPage);
|
|
1926
2251
|
const result = {};
|
|
1927
2252
|
for (const propString of propStrings) {
|
|
1928
|
-
const
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
ErrorCodes.INVALID_ARG,
|
|
1932
|
-
`Invalid --prop value: "${propString}". Expected format: "PropertyName=Value".`,
|
|
1933
|
-
'Example: --prop "Status=Done"'
|
|
1934
|
-
);
|
|
1935
|
-
}
|
|
1936
|
-
const propName = propString.slice(0, eqIdx).trim();
|
|
1937
|
-
const value = propString.slice(eqIdx + 1);
|
|
1938
|
-
const schemaProp = schema[propName];
|
|
1939
|
-
if (!schemaProp) {
|
|
1940
|
-
const available = Object.keys(schema).join(", ");
|
|
1941
|
-
throw new CliError(
|
|
1942
|
-
ErrorCodes.INVALID_ARG,
|
|
1943
|
-
`Property "${propName}" not found on this page.`,
|
|
1944
|
-
`Available properties: ${available}`
|
|
1945
|
-
);
|
|
1946
|
-
}
|
|
1947
|
-
const propType = schemaProp.type;
|
|
1948
|
-
const payload = buildPropertyUpdate(propName, propType, value);
|
|
1949
|
-
result[propName] = payload;
|
|
2253
|
+
const [propName, value] = parsePropString(propString);
|
|
2254
|
+
const schemaProp = lookupSchemaProp(propName, schema);
|
|
2255
|
+
result[propName] = buildPropertyUpdate(propName, schemaProp.type, value);
|
|
1950
2256
|
}
|
|
1951
2257
|
return result;
|
|
1952
2258
|
}
|
|
@@ -1959,7 +2265,7 @@ async function updatePageProperties(client, pageId, properties) {
|
|
|
1959
2265
|
}
|
|
1960
2266
|
|
|
1961
2267
|
// src/commands/create-page.ts
|
|
1962
|
-
function
|
|
2268
|
+
function collectValues(val, acc) {
|
|
1963
2269
|
acc.push(val);
|
|
1964
2270
|
return acc;
|
|
1965
2271
|
}
|
|
@@ -1971,16 +2277,21 @@ async function tryGetDatabaseSchema(client, uuid) {
|
|
|
1971
2277
|
}
|
|
1972
2278
|
}
|
|
1973
2279
|
function createPageCommand() {
|
|
1974
|
-
const cmd = new
|
|
2280
|
+
const cmd = new Command10("create-page");
|
|
1975
2281
|
cmd.description("create a new Notion page under a parent page or database").requiredOption("--parent <id/url>", "parent page or database ID/URL").requiredOption("--title <title>", "page title").option(
|
|
1976
2282
|
"-m, --message <markdown>",
|
|
1977
2283
|
"inline markdown content for the page body"
|
|
1978
2284
|
).option(
|
|
1979
2285
|
"--prop <property=value>",
|
|
1980
2286
|
"set a property value (repeatable, database parents only)",
|
|
1981
|
-
|
|
2287
|
+
collectValues,
|
|
2288
|
+
[]
|
|
2289
|
+
).option("--icon <emoji-or-url>", "page icon \u2014 emoji character or image URL").option("--cover <url>", "page cover image URL").option(
|
|
2290
|
+
"--file <path>",
|
|
2291
|
+
"attach a local file to the page after creation (repeatable)",
|
|
2292
|
+
collectValues,
|
|
1982
2293
|
[]
|
|
1983
|
-
).
|
|
2294
|
+
).action(
|
|
1984
2295
|
withErrorHandling(async (opts) => {
|
|
1985
2296
|
const { token, source } = await resolveToken();
|
|
1986
2297
|
reportTokenSource(source);
|
|
@@ -1988,12 +2299,13 @@ function createPageCommand() {
|
|
|
1988
2299
|
let markdown = "";
|
|
1989
2300
|
if (opts.message) {
|
|
1990
2301
|
markdown = opts.message;
|
|
1991
|
-
} else if (!process.stdin.isTTY) {
|
|
2302
|
+
} else if (!process.stdin.isTTY && opts.file.length === 0) {
|
|
1992
2303
|
markdown = await readStdin();
|
|
1993
2304
|
}
|
|
1994
2305
|
const parentUuid = toUuid(parseNotionId(opts.parent));
|
|
1995
2306
|
const iconCover = { icon: opts.icon, cover: opts.cover };
|
|
1996
2307
|
const dbSchema = await tryGetDatabaseSchema(client, parentUuid);
|
|
2308
|
+
let createdPageUrl;
|
|
1997
2309
|
if (dbSchema) {
|
|
1998
2310
|
const titleEntry = Object.entries(dbSchema.properties).find(
|
|
1999
2311
|
([, prop]) => prop.type === "title"
|
|
@@ -2007,7 +2319,7 @@ function createPageCommand() {
|
|
|
2007
2319
|
}
|
|
2008
2320
|
const [titlePropName] = titleEntry;
|
|
2009
2321
|
const extraProperties = opts.prop.length > 0 ? buildPropertiesPayload(opts.prop, dbSchema.properties) : {};
|
|
2010
|
-
|
|
2322
|
+
createdPageUrl = await createPageInDatabase(
|
|
2011
2323
|
client,
|
|
2012
2324
|
dbSchema.databaseId,
|
|
2013
2325
|
titlePropName,
|
|
@@ -2016,8 +2328,6 @@ function createPageCommand() {
|
|
|
2016
2328
|
markdown,
|
|
2017
2329
|
iconCover
|
|
2018
2330
|
);
|
|
2019
|
-
process.stdout.write(`${url}
|
|
2020
|
-
`);
|
|
2021
2331
|
} else {
|
|
2022
2332
|
if (opts.prop.length > 0) {
|
|
2023
2333
|
throw new CliError(
|
|
@@ -2026,32 +2336,40 @@ function createPageCommand() {
|
|
|
2026
2336
|
"To set properties, use a database ID/URL as --parent"
|
|
2027
2337
|
);
|
|
2028
2338
|
}
|
|
2029
|
-
|
|
2339
|
+
createdPageUrl = await createPage(
|
|
2030
2340
|
client,
|
|
2031
2341
|
parentUuid,
|
|
2032
2342
|
opts.title,
|
|
2033
2343
|
markdown,
|
|
2034
2344
|
iconCover
|
|
2035
2345
|
);
|
|
2036
|
-
process.stdout.write(`${url}
|
|
2037
|
-
`);
|
|
2038
2346
|
}
|
|
2347
|
+
if (opts.file.length > 0) {
|
|
2348
|
+
const createdPageId = toUuid(parseNotionId(createdPageUrl));
|
|
2349
|
+
const blocks = await uploadFilesAsBlocks(opts.file, {}, client);
|
|
2350
|
+
await client.blocks.children.append({
|
|
2351
|
+
block_id: createdPageId,
|
|
2352
|
+
children: blocks
|
|
2353
|
+
});
|
|
2354
|
+
}
|
|
2355
|
+
process.stdout.write(`${createdPageUrl}
|
|
2356
|
+
`);
|
|
2039
2357
|
})
|
|
2040
2358
|
);
|
|
2041
2359
|
return cmd;
|
|
2042
2360
|
}
|
|
2043
2361
|
|
|
2044
2362
|
// src/commands/db/create.ts
|
|
2045
|
-
import { Command as
|
|
2046
|
-
function
|
|
2363
|
+
import { Command as Command11 } from "commander";
|
|
2364
|
+
function collectProps(val, acc) {
|
|
2047
2365
|
acc.push(val);
|
|
2048
2366
|
return acc;
|
|
2049
2367
|
}
|
|
2050
2368
|
function dbCreateCommand() {
|
|
2051
|
-
return new
|
|
2369
|
+
return new Command11("create").description("Create a new database under a parent page").requiredOption("--parent <id/url>", "parent page ID or URL").requiredOption("--title <title>", "database title").option(
|
|
2052
2370
|
"--prop <definition>",
|
|
2053
2371
|
'property definition (repeatable): --prop "Name:type:options"',
|
|
2054
|
-
|
|
2372
|
+
collectProps,
|
|
2055
2373
|
[]
|
|
2056
2374
|
).action(
|
|
2057
2375
|
withErrorHandling(async (opts) => {
|
|
@@ -2080,7 +2398,7 @@ function dbCreateCommand() {
|
|
|
2080
2398
|
}
|
|
2081
2399
|
|
|
2082
2400
|
// src/commands/db/query.ts
|
|
2083
|
-
import { Command as
|
|
2401
|
+
import { Command as Command12 } from "commander";
|
|
2084
2402
|
var SKIP_TYPES_IN_AUTO = /* @__PURE__ */ new Set(["relation", "rich_text", "people"]);
|
|
2085
2403
|
function autoSelectColumns(schema, entries) {
|
|
2086
2404
|
const termWidth = process.stdout.columns || 120;
|
|
@@ -2108,7 +2426,7 @@ function autoSelectColumns(schema, entries) {
|
|
|
2108
2426
|
return selected;
|
|
2109
2427
|
}
|
|
2110
2428
|
function dbQueryCommand() {
|
|
2111
|
-
return new
|
|
2429
|
+
return new Command12("query").description("Query database entries with optional filtering and sorting").argument("<id>", "Notion database ID or URL").option(
|
|
2112
2430
|
"--filter <filter>",
|
|
2113
2431
|
'Filter entries (repeatable): --filter "Status=Done"',
|
|
2114
2432
|
collect,
|
|
@@ -2163,9 +2481,9 @@ function collect(value, previous) {
|
|
|
2163
2481
|
}
|
|
2164
2482
|
|
|
2165
2483
|
// src/commands/db/schema.ts
|
|
2166
|
-
import { Command as
|
|
2484
|
+
import { Command as Command13 } from "commander";
|
|
2167
2485
|
function dbSchemaCommand() {
|
|
2168
|
-
return new
|
|
2486
|
+
return new Command13("schema").description("Show database schema (property names, types, and options)").argument("<id>", "Notion database ID or URL").action(
|
|
2169
2487
|
withErrorHandling(async (id) => {
|
|
2170
2488
|
const { token } = await resolveToken();
|
|
2171
2489
|
const client = createNotionClient(token);
|
|
@@ -2189,13 +2507,13 @@ function dbSchemaCommand() {
|
|
|
2189
2507
|
}
|
|
2190
2508
|
|
|
2191
2509
|
// src/commands/edit-page.ts
|
|
2192
|
-
import { Command as
|
|
2510
|
+
import { Command as Command14 } from "commander";
|
|
2193
2511
|
function collect2(val, acc) {
|
|
2194
2512
|
acc.push(val);
|
|
2195
2513
|
return acc;
|
|
2196
2514
|
}
|
|
2197
2515
|
function editPageCommand() {
|
|
2198
|
-
const cmd = new
|
|
2516
|
+
const cmd = new Command14("edit-page");
|
|
2199
2517
|
cmd.description(
|
|
2200
2518
|
"replace a Notion page's content \u2014 full page or a targeted section"
|
|
2201
2519
|
).argument("<id/url>", "Notion page ID or URL").option(
|
|
@@ -2293,7 +2611,7 @@ function editPageCommand() {
|
|
|
2293
2611
|
|
|
2294
2612
|
// src/commands/init.ts
|
|
2295
2613
|
import { confirm, input as input2, password } from "@inquirer/prompts";
|
|
2296
|
-
import { Command as
|
|
2614
|
+
import { Command as Command15 } from "commander";
|
|
2297
2615
|
async function runInitFlow() {
|
|
2298
2616
|
const profileName = await input2({
|
|
2299
2617
|
message: "Profile name:",
|
|
@@ -2373,7 +2691,7 @@ async function runInitFlow() {
|
|
|
2373
2691
|
stderrWrite(dim(" notion auth login"));
|
|
2374
2692
|
}
|
|
2375
2693
|
function initCommand() {
|
|
2376
|
-
const cmd = new
|
|
2694
|
+
const cmd = new Command15("init");
|
|
2377
2695
|
cmd.description("authenticate with Notion and save a profile").action(
|
|
2378
2696
|
withErrorHandling(async () => {
|
|
2379
2697
|
if (!process.stdin.isTTY) {
|
|
@@ -2391,7 +2709,7 @@ function initCommand() {
|
|
|
2391
2709
|
|
|
2392
2710
|
// src/commands/ls.ts
|
|
2393
2711
|
import { isFullPageOrDataSource } from "@notionhq/client";
|
|
2394
|
-
import { Command as
|
|
2712
|
+
import { Command as Command16 } from "commander";
|
|
2395
2713
|
function getTitle(item) {
|
|
2396
2714
|
if (item.object === "data_source") {
|
|
2397
2715
|
return item.title.map((t) => t.plain_text).join("") || "(untitled)";
|
|
@@ -2408,7 +2726,7 @@ function displayType(item) {
|
|
|
2408
2726
|
return item.object === "data_source" ? "database" : item.object;
|
|
2409
2727
|
}
|
|
2410
2728
|
function lsCommand() {
|
|
2411
|
-
const cmd = new
|
|
2729
|
+
const cmd = new Command16("ls");
|
|
2412
2730
|
cmd.description("list accessible Notion pages and databases").option(
|
|
2413
2731
|
"--type <type>",
|
|
2414
2732
|
"filter by object type (page or database)",
|
|
@@ -2482,9 +2800,9 @@ function lsCommand() {
|
|
|
2482
2800
|
}
|
|
2483
2801
|
|
|
2484
2802
|
// src/commands/move.ts
|
|
2485
|
-
import { Command as
|
|
2803
|
+
import { Command as Command17 } from "commander";
|
|
2486
2804
|
function moveCommand() {
|
|
2487
|
-
const cmd = new
|
|
2805
|
+
const cmd = new Command17("move");
|
|
2488
2806
|
cmd.description("move pages to a new parent").argument("<ids/urls...>", "Notion page IDs or URLs to move").option("--to <id/url>", "target page parent ID or URL").option(
|
|
2489
2807
|
"--to-db <id/url>",
|
|
2490
2808
|
"target database parent ID or URL (resolves to data source)"
|
|
@@ -2542,10 +2860,10 @@ function moveCommand() {
|
|
|
2542
2860
|
// src/commands/open.ts
|
|
2543
2861
|
import { exec } from "child_process";
|
|
2544
2862
|
import { promisify } from "util";
|
|
2545
|
-
import { Command as
|
|
2863
|
+
import { Command as Command18 } from "commander";
|
|
2546
2864
|
var execAsync = promisify(exec);
|
|
2547
2865
|
function openCommand() {
|
|
2548
|
-
const cmd = new
|
|
2866
|
+
const cmd = new Command18("open");
|
|
2549
2867
|
cmd.description("open a Notion page in the default browser").argument("<id/url>", "Notion page ID or URL").action(
|
|
2550
2868
|
withErrorHandling(async (idOrUrl) => {
|
|
2551
2869
|
const id = parseNotionId(idOrUrl);
|
|
@@ -2561,9 +2879,9 @@ function openCommand() {
|
|
|
2561
2879
|
}
|
|
2562
2880
|
|
|
2563
2881
|
// src/commands/profile/list.ts
|
|
2564
|
-
import { Command as
|
|
2882
|
+
import { Command as Command19 } from "commander";
|
|
2565
2883
|
function profileListCommand() {
|
|
2566
|
-
const cmd = new
|
|
2884
|
+
const cmd = new Command19("list");
|
|
2567
2885
|
cmd.description("list all authentication profiles").action(
|
|
2568
2886
|
withErrorHandling(async () => {
|
|
2569
2887
|
const config = await readGlobalConfig();
|
|
@@ -2592,9 +2910,9 @@ function profileListCommand() {
|
|
|
2592
2910
|
}
|
|
2593
2911
|
|
|
2594
2912
|
// src/commands/profile/remove.ts
|
|
2595
|
-
import { Command as
|
|
2913
|
+
import { Command as Command20 } from "commander";
|
|
2596
2914
|
function profileRemoveCommand() {
|
|
2597
|
-
const cmd = new
|
|
2915
|
+
const cmd = new Command20("remove");
|
|
2598
2916
|
cmd.description("remove an authentication profile").argument("<name>", "profile name to remove").action(
|
|
2599
2917
|
withErrorHandling(async (name) => {
|
|
2600
2918
|
const config = await readGlobalConfig();
|
|
@@ -2620,9 +2938,9 @@ function profileRemoveCommand() {
|
|
|
2620
2938
|
}
|
|
2621
2939
|
|
|
2622
2940
|
// src/commands/profile/use.ts
|
|
2623
|
-
import { Command as
|
|
2941
|
+
import { Command as Command21 } from "commander";
|
|
2624
2942
|
function profileUseCommand() {
|
|
2625
|
-
const cmd = new
|
|
2943
|
+
const cmd = new Command21("use");
|
|
2626
2944
|
cmd.description("switch the active profile").argument("<name>", "profile name to activate").action(
|
|
2627
2945
|
withErrorHandling(async (name) => {
|
|
2628
2946
|
const config = await readGlobalConfig();
|
|
@@ -2645,7 +2963,7 @@ function profileUseCommand() {
|
|
|
2645
2963
|
}
|
|
2646
2964
|
|
|
2647
2965
|
// src/commands/read.ts
|
|
2648
|
-
import { Command as
|
|
2966
|
+
import { Command as Command22 } from "commander";
|
|
2649
2967
|
|
|
2650
2968
|
// src/output/markdown.ts
|
|
2651
2969
|
import { Chalk as Chalk2 } from "chalk";
|
|
@@ -2785,7 +3103,7 @@ async function fetchPageMarkdown(client, pageId) {
|
|
|
2785
3103
|
|
|
2786
3104
|
// src/commands/read.ts
|
|
2787
3105
|
function readCommand() {
|
|
2788
|
-
return new
|
|
3106
|
+
return new Command22("read").description("Read a Notion page as markdown").argument("<id>", "Notion page ID or URL").action(
|
|
2789
3107
|
withErrorHandling(async (id) => {
|
|
2790
3108
|
const { token } = await resolveToken();
|
|
2791
3109
|
const client = createNotionClient(token);
|
|
@@ -2811,7 +3129,7 @@ function readCommand() {
|
|
|
2811
3129
|
|
|
2812
3130
|
// src/commands/search.ts
|
|
2813
3131
|
import { isFullPageOrDataSource as isFullPageOrDataSource2 } from "@notionhq/client";
|
|
2814
|
-
import { Command as
|
|
3132
|
+
import { Command as Command23 } from "commander";
|
|
2815
3133
|
function getTitle2(item) {
|
|
2816
3134
|
if (item.object === "data_source") {
|
|
2817
3135
|
return item.title.map((t) => t.plain_text).join("") || "(untitled)";
|
|
@@ -2831,7 +3149,7 @@ function displayType2(item) {
|
|
|
2831
3149
|
return item.object === "data_source" ? "database" : item.object;
|
|
2832
3150
|
}
|
|
2833
3151
|
function searchCommand() {
|
|
2834
|
-
const cmd = new
|
|
3152
|
+
const cmd = new Command23("search");
|
|
2835
3153
|
cmd.description("search Notion workspace by keyword").argument("<query>", "search keyword").option(
|
|
2836
3154
|
"--type <type>",
|
|
2837
3155
|
"filter by object type (page or database)",
|
|
@@ -2904,7 +3222,7 @@ function searchCommand() {
|
|
|
2904
3222
|
// src/commands/skill.ts
|
|
2905
3223
|
import {
|
|
2906
3224
|
copyFileSync,
|
|
2907
|
-
existsSync,
|
|
3225
|
+
existsSync as existsSync4,
|
|
2908
3226
|
mkdirSync,
|
|
2909
3227
|
readFileSync,
|
|
2910
3228
|
realpathSync
|
|
@@ -2912,7 +3230,7 @@ import {
|
|
|
2912
3230
|
import { homedir as homedir2 } from "os";
|
|
2913
3231
|
import { dirname, join as join3 } from "path";
|
|
2914
3232
|
import chalk from "chalk";
|
|
2915
|
-
import { Command as
|
|
3233
|
+
import { Command as Command24 } from "commander";
|
|
2916
3234
|
function skillPath() {
|
|
2917
3235
|
if (!process.argv[1]) {
|
|
2918
3236
|
throw new Error("Cannot determine install path. Run with: notion skill");
|
|
@@ -2931,7 +3249,7 @@ function skillPath() {
|
|
|
2931
3249
|
)
|
|
2932
3250
|
];
|
|
2933
3251
|
for (const c2 of candidates) {
|
|
2934
|
-
if (
|
|
3252
|
+
if (existsSync4(c2)) return c2;
|
|
2935
3253
|
}
|
|
2936
3254
|
throw new Error(
|
|
2937
3255
|
"SKILL.md not found. Reinstall with: npm install -g @andrzejchm/notion-cli"
|
|
@@ -2952,11 +3270,11 @@ function getAgentTargets() {
|
|
|
2952
3270
|
];
|
|
2953
3271
|
return targets.map((t) => ({
|
|
2954
3272
|
...t,
|
|
2955
|
-
detected:
|
|
3273
|
+
detected: existsSync4(dirname(t.dir))
|
|
2956
3274
|
}));
|
|
2957
3275
|
}
|
|
2958
3276
|
function installTo(source, target) {
|
|
2959
|
-
if (!
|
|
3277
|
+
if (!existsSync4(target.dir)) mkdirSync(target.dir, { recursive: true });
|
|
2960
3278
|
const dest = join3(target.dir, "SKILL.md");
|
|
2961
3279
|
copyFileSync(source, dest);
|
|
2962
3280
|
return dest;
|
|
@@ -2999,7 +3317,7 @@ function installNonInteractive(source, targets) {
|
|
|
2999
3317
|
}
|
|
3000
3318
|
}
|
|
3001
3319
|
function skillCommand() {
|
|
3002
|
-
const cmd = new
|
|
3320
|
+
const cmd = new Command24("skill");
|
|
3003
3321
|
cmd.description("Install the agent skill file for your coding agents").option("--print", "Print the skill file content instead of installing").option("--path <path>", "Install to a specific file path").action(
|
|
3004
3322
|
withErrorHandling(async (opts) => {
|
|
3005
3323
|
if (opts.print) {
|
|
@@ -3009,7 +3327,7 @@ function skillCommand() {
|
|
|
3009
3327
|
const source = skillPath();
|
|
3010
3328
|
if (opts.path) {
|
|
3011
3329
|
const dir = dirname(opts.path);
|
|
3012
|
-
if (!
|
|
3330
|
+
if (!existsSync4(dir)) mkdirSync(dir, { recursive: true });
|
|
3013
3331
|
copyFileSync(source, opts.path);
|
|
3014
3332
|
process.stdout.write(`Installed to ${opts.path}
|
|
3015
3333
|
`);
|
|
@@ -3028,17 +3346,17 @@ function skillCommand() {
|
|
|
3028
3346
|
}
|
|
3029
3347
|
|
|
3030
3348
|
// src/commands/update.ts
|
|
3031
|
-
import { Command as
|
|
3032
|
-
function
|
|
3349
|
+
import { Command as Command25 } from "commander";
|
|
3350
|
+
function collectProps2(val, acc) {
|
|
3033
3351
|
acc.push(val);
|
|
3034
3352
|
return acc;
|
|
3035
3353
|
}
|
|
3036
3354
|
function updateCommand() {
|
|
3037
|
-
const cmd = new
|
|
3355
|
+
const cmd = new Command25("update");
|
|
3038
3356
|
cmd.description("update properties on a Notion page").argument("<id/url>", "Notion page ID or URL").option(
|
|
3039
3357
|
"--prop <property=value>",
|
|
3040
3358
|
"set a property value (repeatable)",
|
|
3041
|
-
|
|
3359
|
+
collectProps2,
|
|
3042
3360
|
[]
|
|
3043
3361
|
).option("--title <title>", "set the page title").action(
|
|
3044
3362
|
withErrorHandling(async (idOrUrl, opts) => {
|
|
@@ -3092,7 +3410,7 @@ function updateCommand() {
|
|
|
3092
3410
|
}
|
|
3093
3411
|
|
|
3094
3412
|
// src/commands/users.ts
|
|
3095
|
-
import { Command as
|
|
3413
|
+
import { Command as Command26 } from "commander";
|
|
3096
3414
|
function getEmailOrWorkspace(user) {
|
|
3097
3415
|
if (user.type === "person") {
|
|
3098
3416
|
return user.person.email ?? "\u2014";
|
|
@@ -3104,7 +3422,7 @@ function getEmailOrWorkspace(user) {
|
|
|
3104
3422
|
return "\u2014";
|
|
3105
3423
|
}
|
|
3106
3424
|
function usersCommand() {
|
|
3107
|
-
const cmd = new
|
|
3425
|
+
const cmd = new Command26("users");
|
|
3108
3426
|
cmd.description("list all users in the workspace").option("--json", "output as JSON").action(
|
|
3109
3427
|
withErrorHandling(async (opts) => {
|
|
3110
3428
|
if (opts.json) setOutputMode("json");
|
|
@@ -3135,7 +3453,7 @@ var __dirname = dirname2(__filename);
|
|
|
3135
3453
|
var pkg = JSON.parse(
|
|
3136
3454
|
readFileSync2(join4(__dirname, "../package.json"), "utf-8")
|
|
3137
3455
|
);
|
|
3138
|
-
var program = new
|
|
3456
|
+
var program = new Command27();
|
|
3139
3457
|
program.name("notion").description("Notion CLI \u2014 read Notion pages and databases from the terminal").version(pkg.version);
|
|
3140
3458
|
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");
|
|
3141
3459
|
program.configureOutput({
|
|
@@ -3156,7 +3474,7 @@ program.hook("preAction", (thisCommand) => {
|
|
|
3156
3474
|
setOutputMode("md");
|
|
3157
3475
|
}
|
|
3158
3476
|
});
|
|
3159
|
-
var authCmd = new
|
|
3477
|
+
var authCmd = new Command27("auth").description("manage Notion authentication");
|
|
3160
3478
|
authCmd.action(authDefaultAction(authCmd));
|
|
3161
3479
|
authCmd.addCommand(loginCommand());
|
|
3162
3480
|
authCmd.addCommand(logoutCommand());
|
|
@@ -3166,7 +3484,7 @@ authCmd.addCommand(profileUseCommand());
|
|
|
3166
3484
|
authCmd.addCommand(profileRemoveCommand());
|
|
3167
3485
|
program.addCommand(authCmd);
|
|
3168
3486
|
program.addCommand(initCommand(), { hidden: true });
|
|
3169
|
-
var profileCmd = new
|
|
3487
|
+
var profileCmd = new Command27("profile").description(
|
|
3170
3488
|
"manage authentication profiles"
|
|
3171
3489
|
);
|
|
3172
3490
|
profileCmd.addCommand(profileListCommand());
|
|
@@ -3181,12 +3499,13 @@ program.addCommand(commentsCommand());
|
|
|
3181
3499
|
program.addCommand(readCommand());
|
|
3182
3500
|
program.addCommand(commentAddCommand());
|
|
3183
3501
|
program.addCommand(appendCommand());
|
|
3502
|
+
program.addCommand(attachCommand());
|
|
3184
3503
|
program.addCommand(createPageCommand());
|
|
3185
3504
|
program.addCommand(editPageCommand());
|
|
3186
3505
|
program.addCommand(updateCommand());
|
|
3187
3506
|
program.addCommand(archiveCommand());
|
|
3188
3507
|
program.addCommand(moveCommand());
|
|
3189
|
-
var dbCmd = new
|
|
3508
|
+
var dbCmd = new Command27("db").description("Database operations");
|
|
3190
3509
|
dbCmd.addCommand(dbCreateCommand());
|
|
3191
3510
|
dbCmd.addCommand(dbSchemaCommand());
|
|
3192
3511
|
dbCmd.addCommand(dbQueryCommand());
|