@andrzejchm/notion-cli 0.10.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/dist/cli.js CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { readFileSync } from "fs";
5
- import { dirname, join as join3 } from "path";
4
+ import { readFileSync as readFileSync2 } from "fs";
5
+ import { dirname as dirname2, join as join4 } from "path";
6
6
  import { fileURLToPath } from "url";
7
- import { Command as Command25 } from "commander";
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
- result.cover = { type: "external", external: { url: options.cover } };
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
- ...buildIconCover(options)
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
- ...buildIconCover(options)
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
- let markdown = "";
732
- if (opts.message) {
733
- markdown = opts.message;
734
- } else if (!process.stdin.isTTY) {
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
- "No content to append.",
740
- "Pass markdown via -m/--message or pipe it through stdin"
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 (!markdown.trim()) {
744
- process.stdout.write("Nothing to append.\n");
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 ? { after: opts.after } : void 0
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
- process.stdout.write("Appended.\n");
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 Command3 } from "commander";
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 Command3("login");
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 Command4 } from "commander";
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 Command4("logout");
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 Command5 } from "commander";
1553
+ import { Command as Command6 } from "commander";
1261
1554
  function statusCommand() {
1262
- const cmd = new Command5("status");
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 Command6 } from "commander";
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 Command6("comment");
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 Command7 } from "commander";
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 Command7("comments");
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,13 +1723,13 @@ function commentsCommand() {
1430
1723
  }
1431
1724
 
1432
1725
  // src/commands/completion.ts
1433
- import { Command as Command8 } from "commander";
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
1437
1730
  _init_completion || return
1438
1731
 
1439
- local commands="init profile completion --help --version --verbose --color"
1732
+ local commands="init profile completion skill --help --version --verbose --color"
1440
1733
  local profile_commands="list use remove"
1441
1734
 
1442
1735
  case "$prev" in
@@ -1469,6 +1762,7 @@ _notion() {
1469
1762
  'init:authenticate with Notion and save a profile'
1470
1763
  'profile:manage authentication profiles'
1471
1764
  'completion:output shell completion script'
1765
+ 'skill:install the agent skill file'
1472
1766
  )
1473
1767
 
1474
1768
  local -a global_opts
@@ -1520,6 +1814,7 @@ complete -c notion -l color -d 'force color output'
1520
1814
  complete -c notion -n '__fish_use_subcommand' -a init -d 'authenticate with Notion and save a profile'
1521
1815
  complete -c notion -n '__fish_use_subcommand' -a profile -d 'manage authentication profiles'
1522
1816
  complete -c notion -n '__fish_use_subcommand' -a completion -d 'output shell completion script'
1817
+ complete -c notion -n '__fish_use_subcommand' -a skill -d 'install the agent skill file'
1523
1818
 
1524
1819
  # profile subcommands
1525
1820
  complete -c notion -n '__fish_seen_subcommand_from profile' -a list -d 'list all authentication profiles'
@@ -1532,7 +1827,7 @@ complete -c notion -n '__fish_seen_subcommand_from completion' -a zsh -d 'zsh co
1532
1827
  complete -c notion -n '__fish_seen_subcommand_from completion' -a fish -d 'fish completion script'
1533
1828
  `;
1534
1829
  function completionCommand() {
1535
- const cmd = new Command8("completion");
1830
+ const cmd = new Command9("completion");
1536
1831
  cmd.description("output shell completion script").argument("<shell>", "shell type (bash, zsh, fish)").action(
1537
1832
  withErrorHandling(async (shell) => {
1538
1833
  switch (shell) {
@@ -1558,7 +1853,7 @@ function completionCommand() {
1558
1853
  }
1559
1854
 
1560
1855
  // src/commands/create-page.ts
1561
- import { Command as Command9 } from "commander";
1856
+ import { Command as Command10 } from "commander";
1562
1857
 
1563
1858
  // src/services/database.service.ts
1564
1859
  import { isFullPage } from "@notionhq/client";
@@ -1846,6 +2141,7 @@ function displayPropertyValue(prop) {
1846
2141
  }
1847
2142
 
1848
2143
  // src/services/update.service.ts
2144
+ import { existsSync as existsSync3 } from "fs";
1849
2145
  var UNSUPPORTED_TYPES = /* @__PURE__ */ new Set([
1850
2146
  "relation",
1851
2147
  "formula",
@@ -1854,7 +2150,6 @@ var UNSUPPORTED_TYPES = /* @__PURE__ */ new Set([
1854
2150
  "created_by",
1855
2151
  "last_edited_time",
1856
2152
  "last_edited_by",
1857
- "files",
1858
2153
  "unique_id",
1859
2154
  "verification",
1860
2155
  "button"
@@ -1864,7 +2159,7 @@ function buildPropertyUpdate(propName, propType, value) {
1864
2159
  throw new CliError(
1865
2160
  ErrorCodes.INVALID_ARG,
1866
2161
  `Property "${propName}" has type "${propType}" which cannot be set via the CLI.`,
1867
- "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"
1868
2163
  );
1869
2164
  }
1870
2165
  if (value === "") {
@@ -1910,41 +2205,54 @@ function buildPropertyUpdate(propName, propType, value) {
1910
2205
  const end = parts[1]?.trim();
1911
2206
  return { date: end ? { start, end } : { start } };
1912
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
+ );
1913
2214
  default:
1914
2215
  throw new CliError(
1915
2216
  ErrorCodes.INVALID_ARG,
1916
2217
  `Property "${propName}" has unsupported type "${propType}".`,
1917
- "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"
1918
2219
  );
1919
2220
  }
1920
2221
  }
1921
- function buildPropertiesPayload(propStrings, schemaOrPage) {
2222
+ function normalizeSchema(schemaOrPage) {
1922
2223
  const isPage = "object" in schemaOrPage && schemaOrPage.object === "page";
1923
- const schema = isPage ? schemaOrPage.properties : schemaOrPage;
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);
1924
2251
  const result = {};
1925
2252
  for (const propString of propStrings) {
1926
- const eqIdx = propString.indexOf("=");
1927
- if (eqIdx === -1) {
1928
- throw new CliError(
1929
- ErrorCodes.INVALID_ARG,
1930
- `Invalid --prop value: "${propString}". Expected format: "PropertyName=Value".`,
1931
- 'Example: --prop "Status=Done"'
1932
- );
1933
- }
1934
- const propName = propString.slice(0, eqIdx).trim();
1935
- const value = propString.slice(eqIdx + 1);
1936
- const schemaProp = schema[propName];
1937
- if (!schemaProp) {
1938
- const available = Object.keys(schema).join(", ");
1939
- throw new CliError(
1940
- ErrorCodes.INVALID_ARG,
1941
- `Property "${propName}" not found on this page.`,
1942
- `Available properties: ${available}`
1943
- );
1944
- }
1945
- const propType = schemaProp.type;
1946
- const payload = buildPropertyUpdate(propName, propType, value);
1947
- result[propName] = payload;
2253
+ const [propName, value] = parsePropString(propString);
2254
+ const schemaProp = lookupSchemaProp(propName, schema);
2255
+ result[propName] = buildPropertyUpdate(propName, schemaProp.type, value);
1948
2256
  }
1949
2257
  return result;
1950
2258
  }
@@ -1957,7 +2265,7 @@ async function updatePageProperties(client, pageId, properties) {
1957
2265
  }
1958
2266
 
1959
2267
  // src/commands/create-page.ts
1960
- function collectProps(val, acc) {
2268
+ function collectValues(val, acc) {
1961
2269
  acc.push(val);
1962
2270
  return acc;
1963
2271
  }
@@ -1969,16 +2277,21 @@ async function tryGetDatabaseSchema(client, uuid) {
1969
2277
  }
1970
2278
  }
1971
2279
  function createPageCommand() {
1972
- const cmd = new Command9("create-page");
2280
+ const cmd = new Command10("create-page");
1973
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(
1974
2282
  "-m, --message <markdown>",
1975
2283
  "inline markdown content for the page body"
1976
2284
  ).option(
1977
2285
  "--prop <property=value>",
1978
2286
  "set a property value (repeatable, database parents only)",
1979
- collectProps,
2287
+ collectValues,
1980
2288
  []
1981
- ).option("--icon <emoji-or-url>", "page icon \u2014 emoji character or image URL").option("--cover <url>", "page cover image URL").action(
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,
2293
+ []
2294
+ ).action(
1982
2295
  withErrorHandling(async (opts) => {
1983
2296
  const { token, source } = await resolveToken();
1984
2297
  reportTokenSource(source);
@@ -1986,12 +2299,13 @@ function createPageCommand() {
1986
2299
  let markdown = "";
1987
2300
  if (opts.message) {
1988
2301
  markdown = opts.message;
1989
- } else if (!process.stdin.isTTY) {
2302
+ } else if (!process.stdin.isTTY && opts.file.length === 0) {
1990
2303
  markdown = await readStdin();
1991
2304
  }
1992
2305
  const parentUuid = toUuid(parseNotionId(opts.parent));
1993
2306
  const iconCover = { icon: opts.icon, cover: opts.cover };
1994
2307
  const dbSchema = await tryGetDatabaseSchema(client, parentUuid);
2308
+ let createdPageUrl;
1995
2309
  if (dbSchema) {
1996
2310
  const titleEntry = Object.entries(dbSchema.properties).find(
1997
2311
  ([, prop]) => prop.type === "title"
@@ -2005,7 +2319,7 @@ function createPageCommand() {
2005
2319
  }
2006
2320
  const [titlePropName] = titleEntry;
2007
2321
  const extraProperties = opts.prop.length > 0 ? buildPropertiesPayload(opts.prop, dbSchema.properties) : {};
2008
- const url = await createPageInDatabase(
2322
+ createdPageUrl = await createPageInDatabase(
2009
2323
  client,
2010
2324
  dbSchema.databaseId,
2011
2325
  titlePropName,
@@ -2014,8 +2328,6 @@ function createPageCommand() {
2014
2328
  markdown,
2015
2329
  iconCover
2016
2330
  );
2017
- process.stdout.write(`${url}
2018
- `);
2019
2331
  } else {
2020
2332
  if (opts.prop.length > 0) {
2021
2333
  throw new CliError(
@@ -2024,32 +2336,40 @@ function createPageCommand() {
2024
2336
  "To set properties, use a database ID/URL as --parent"
2025
2337
  );
2026
2338
  }
2027
- const url = await createPage(
2339
+ createdPageUrl = await createPage(
2028
2340
  client,
2029
2341
  parentUuid,
2030
2342
  opts.title,
2031
2343
  markdown,
2032
2344
  iconCover
2033
2345
  );
2034
- process.stdout.write(`${url}
2035
- `);
2036
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
+ `);
2037
2357
  })
2038
2358
  );
2039
2359
  return cmd;
2040
2360
  }
2041
2361
 
2042
2362
  // src/commands/db/create.ts
2043
- import { Command as Command10 } from "commander";
2044
- function collectProps2(val, acc) {
2363
+ import { Command as Command11 } from "commander";
2364
+ function collectProps(val, acc) {
2045
2365
  acc.push(val);
2046
2366
  return acc;
2047
2367
  }
2048
2368
  function dbCreateCommand() {
2049
- return new Command10("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(
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(
2050
2370
  "--prop <definition>",
2051
2371
  'property definition (repeatable): --prop "Name:type:options"',
2052
- collectProps2,
2372
+ collectProps,
2053
2373
  []
2054
2374
  ).action(
2055
2375
  withErrorHandling(async (opts) => {
@@ -2078,7 +2398,7 @@ function dbCreateCommand() {
2078
2398
  }
2079
2399
 
2080
2400
  // src/commands/db/query.ts
2081
- import { Command as Command11 } from "commander";
2401
+ import { Command as Command12 } from "commander";
2082
2402
  var SKIP_TYPES_IN_AUTO = /* @__PURE__ */ new Set(["relation", "rich_text", "people"]);
2083
2403
  function autoSelectColumns(schema, entries) {
2084
2404
  const termWidth = process.stdout.columns || 120;
@@ -2106,7 +2426,7 @@ function autoSelectColumns(schema, entries) {
2106
2426
  return selected;
2107
2427
  }
2108
2428
  function dbQueryCommand() {
2109
- return new Command11("query").description("Query database entries with optional filtering and sorting").argument("<id>", "Notion database ID or URL").option(
2429
+ return new Command12("query").description("Query database entries with optional filtering and sorting").argument("<id>", "Notion database ID or URL").option(
2110
2430
  "--filter <filter>",
2111
2431
  'Filter entries (repeatable): --filter "Status=Done"',
2112
2432
  collect,
@@ -2161,9 +2481,9 @@ function collect(value, previous) {
2161
2481
  }
2162
2482
 
2163
2483
  // src/commands/db/schema.ts
2164
- import { Command as Command12 } from "commander";
2484
+ import { Command as Command13 } from "commander";
2165
2485
  function dbSchemaCommand() {
2166
- return new Command12("schema").description("Show database schema (property names, types, and options)").argument("<id>", "Notion database ID or URL").action(
2486
+ return new Command13("schema").description("Show database schema (property names, types, and options)").argument("<id>", "Notion database ID or URL").action(
2167
2487
  withErrorHandling(async (id) => {
2168
2488
  const { token } = await resolveToken();
2169
2489
  const client = createNotionClient(token);
@@ -2187,13 +2507,13 @@ function dbSchemaCommand() {
2187
2507
  }
2188
2508
 
2189
2509
  // src/commands/edit-page.ts
2190
- import { Command as Command13 } from "commander";
2510
+ import { Command as Command14 } from "commander";
2191
2511
  function collect2(val, acc) {
2192
2512
  acc.push(val);
2193
2513
  return acc;
2194
2514
  }
2195
2515
  function editPageCommand() {
2196
- const cmd = new Command13("edit-page");
2516
+ const cmd = new Command14("edit-page");
2197
2517
  cmd.description(
2198
2518
  "replace a Notion page's content \u2014 full page or a targeted section"
2199
2519
  ).argument("<id/url>", "Notion page ID or URL").option(
@@ -2291,7 +2611,7 @@ function editPageCommand() {
2291
2611
 
2292
2612
  // src/commands/init.ts
2293
2613
  import { confirm, input as input2, password } from "@inquirer/prompts";
2294
- import { Command as Command14 } from "commander";
2614
+ import { Command as Command15 } from "commander";
2295
2615
  async function runInitFlow() {
2296
2616
  const profileName = await input2({
2297
2617
  message: "Profile name:",
@@ -2371,7 +2691,7 @@ async function runInitFlow() {
2371
2691
  stderrWrite(dim(" notion auth login"));
2372
2692
  }
2373
2693
  function initCommand() {
2374
- const cmd = new Command14("init");
2694
+ const cmd = new Command15("init");
2375
2695
  cmd.description("authenticate with Notion and save a profile").action(
2376
2696
  withErrorHandling(async () => {
2377
2697
  if (!process.stdin.isTTY) {
@@ -2389,7 +2709,7 @@ function initCommand() {
2389
2709
 
2390
2710
  // src/commands/ls.ts
2391
2711
  import { isFullPageOrDataSource } from "@notionhq/client";
2392
- import { Command as Command15 } from "commander";
2712
+ import { Command as Command16 } from "commander";
2393
2713
  function getTitle(item) {
2394
2714
  if (item.object === "data_source") {
2395
2715
  return item.title.map((t) => t.plain_text).join("") || "(untitled)";
@@ -2406,7 +2726,7 @@ function displayType(item) {
2406
2726
  return item.object === "data_source" ? "database" : item.object;
2407
2727
  }
2408
2728
  function lsCommand() {
2409
- const cmd = new Command15("ls");
2729
+ const cmd = new Command16("ls");
2410
2730
  cmd.description("list accessible Notion pages and databases").option(
2411
2731
  "--type <type>",
2412
2732
  "filter by object type (page or database)",
@@ -2480,9 +2800,9 @@ function lsCommand() {
2480
2800
  }
2481
2801
 
2482
2802
  // src/commands/move.ts
2483
- import { Command as Command16 } from "commander";
2803
+ import { Command as Command17 } from "commander";
2484
2804
  function moveCommand() {
2485
- const cmd = new Command16("move");
2805
+ const cmd = new Command17("move");
2486
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(
2487
2807
  "--to-db <id/url>",
2488
2808
  "target database parent ID or URL (resolves to data source)"
@@ -2540,10 +2860,10 @@ function moveCommand() {
2540
2860
  // src/commands/open.ts
2541
2861
  import { exec } from "child_process";
2542
2862
  import { promisify } from "util";
2543
- import { Command as Command17 } from "commander";
2863
+ import { Command as Command18 } from "commander";
2544
2864
  var execAsync = promisify(exec);
2545
2865
  function openCommand() {
2546
- const cmd = new Command17("open");
2866
+ const cmd = new Command18("open");
2547
2867
  cmd.description("open a Notion page in the default browser").argument("<id/url>", "Notion page ID or URL").action(
2548
2868
  withErrorHandling(async (idOrUrl) => {
2549
2869
  const id = parseNotionId(idOrUrl);
@@ -2559,9 +2879,9 @@ function openCommand() {
2559
2879
  }
2560
2880
 
2561
2881
  // src/commands/profile/list.ts
2562
- import { Command as Command18 } from "commander";
2882
+ import { Command as Command19 } from "commander";
2563
2883
  function profileListCommand() {
2564
- const cmd = new Command18("list");
2884
+ const cmd = new Command19("list");
2565
2885
  cmd.description("list all authentication profiles").action(
2566
2886
  withErrorHandling(async () => {
2567
2887
  const config = await readGlobalConfig();
@@ -2590,9 +2910,9 @@ function profileListCommand() {
2590
2910
  }
2591
2911
 
2592
2912
  // src/commands/profile/remove.ts
2593
- import { Command as Command19 } from "commander";
2913
+ import { Command as Command20 } from "commander";
2594
2914
  function profileRemoveCommand() {
2595
- const cmd = new Command19("remove");
2915
+ const cmd = new Command20("remove");
2596
2916
  cmd.description("remove an authentication profile").argument("<name>", "profile name to remove").action(
2597
2917
  withErrorHandling(async (name) => {
2598
2918
  const config = await readGlobalConfig();
@@ -2618,9 +2938,9 @@ function profileRemoveCommand() {
2618
2938
  }
2619
2939
 
2620
2940
  // src/commands/profile/use.ts
2621
- import { Command as Command20 } from "commander";
2941
+ import { Command as Command21 } from "commander";
2622
2942
  function profileUseCommand() {
2623
- const cmd = new Command20("use");
2943
+ const cmd = new Command21("use");
2624
2944
  cmd.description("switch the active profile").argument("<name>", "profile name to activate").action(
2625
2945
  withErrorHandling(async (name) => {
2626
2946
  const config = await readGlobalConfig();
@@ -2643,7 +2963,7 @@ function profileUseCommand() {
2643
2963
  }
2644
2964
 
2645
2965
  // src/commands/read.ts
2646
- import { Command as Command21 } from "commander";
2966
+ import { Command as Command22 } from "commander";
2647
2967
 
2648
2968
  // src/output/markdown.ts
2649
2969
  import { Chalk as Chalk2 } from "chalk";
@@ -2783,7 +3103,7 @@ async function fetchPageMarkdown(client, pageId) {
2783
3103
 
2784
3104
  // src/commands/read.ts
2785
3105
  function readCommand() {
2786
- return new Command21("read").description("Read a Notion page as markdown").argument("<id>", "Notion page ID or URL").action(
3106
+ return new Command22("read").description("Read a Notion page as markdown").argument("<id>", "Notion page ID or URL").action(
2787
3107
  withErrorHandling(async (id) => {
2788
3108
  const { token } = await resolveToken();
2789
3109
  const client = createNotionClient(token);
@@ -2809,7 +3129,7 @@ function readCommand() {
2809
3129
 
2810
3130
  // src/commands/search.ts
2811
3131
  import { isFullPageOrDataSource as isFullPageOrDataSource2 } from "@notionhq/client";
2812
- import { Command as Command22 } from "commander";
3132
+ import { Command as Command23 } from "commander";
2813
3133
  function getTitle2(item) {
2814
3134
  if (item.object === "data_source") {
2815
3135
  return item.title.map((t) => t.plain_text).join("") || "(untitled)";
@@ -2829,7 +3149,7 @@ function displayType2(item) {
2829
3149
  return item.object === "data_source" ? "database" : item.object;
2830
3150
  }
2831
3151
  function searchCommand() {
2832
- const cmd = new Command22("search");
3152
+ const cmd = new Command23("search");
2833
3153
  cmd.description("search Notion workspace by keyword").argument("<query>", "search keyword").option(
2834
3154
  "--type <type>",
2835
3155
  "filter by object type (page or database)",
@@ -2899,18 +3219,144 @@ function searchCommand() {
2899
3219
  return cmd;
2900
3220
  }
2901
3221
 
3222
+ // src/commands/skill.ts
3223
+ import {
3224
+ copyFileSync,
3225
+ existsSync as existsSync4,
3226
+ mkdirSync,
3227
+ readFileSync,
3228
+ realpathSync
3229
+ } from "fs";
3230
+ import { homedir as homedir2 } from "os";
3231
+ import { dirname, join as join3 } from "path";
3232
+ import chalk from "chalk";
3233
+ import { Command as Command24 } from "commander";
3234
+ function skillPath() {
3235
+ if (!process.argv[1]) {
3236
+ throw new Error("Cannot determine install path. Run with: notion skill");
3237
+ }
3238
+ const entryPoint = realpathSync(process.argv[1]);
3239
+ const packageRoot = dirname(dirname(entryPoint));
3240
+ const candidates = [
3241
+ join3(packageRoot, ".agents", "skills", "using-notion-cli", "SKILL.md"),
3242
+ join3(
3243
+ dirname(entryPoint),
3244
+ "..",
3245
+ ".agents",
3246
+ "skills",
3247
+ "using-notion-cli",
3248
+ "SKILL.md"
3249
+ )
3250
+ ];
3251
+ for (const c2 of candidates) {
3252
+ if (existsSync4(c2)) return c2;
3253
+ }
3254
+ throw new Error(
3255
+ "SKILL.md not found. Reinstall with: npm install -g @andrzejchm/notion-cli"
3256
+ );
3257
+ }
3258
+ function getAgentTargets() {
3259
+ const home = homedir2();
3260
+ const targets = [
3261
+ {
3262
+ name: "Claude Code",
3263
+ dir: join3(home, ".claude", "skills", "using-notion-cli")
3264
+ },
3265
+ { name: "Codex", dir: join3(home, ".agents", "skills", "using-notion-cli") },
3266
+ {
3267
+ name: "OpenCode",
3268
+ dir: join3(home, ".config", "opencode", "skills", "using-notion-cli")
3269
+ }
3270
+ ];
3271
+ return targets.map((t) => ({
3272
+ ...t,
3273
+ detected: existsSync4(dirname(t.dir))
3274
+ }));
3275
+ }
3276
+ function installTo(source, target) {
3277
+ if (!existsSync4(target.dir)) mkdirSync(target.dir, { recursive: true });
3278
+ const dest = join3(target.dir, "SKILL.md");
3279
+ copyFileSync(source, dest);
3280
+ return dest;
3281
+ }
3282
+ async function installInteractive(source, targets) {
3283
+ const { checkbox } = await import("@inquirer/prompts");
3284
+ const selected = await checkbox({
3285
+ message: "Install skill for which agents?",
3286
+ choices: targets.map((t) => ({
3287
+ name: `${t.name}${t.detected ? chalk.dim(" (detected)") : ""}`,
3288
+ value: t.name,
3289
+ checked: t.detected
3290
+ }))
3291
+ });
3292
+ if (selected.length === 0) {
3293
+ process.stderr.write("No agents selected.\n");
3294
+ return;
3295
+ }
3296
+ process.stdout.write("\n");
3297
+ for (const name of selected) {
3298
+ const target = targets.find((t) => t.name === name);
3299
+ if (!target) continue;
3300
+ const dest = installTo(source, target);
3301
+ process.stdout.write(` Installed: ${target.name}: ${dest}
3302
+ `);
3303
+ }
3304
+ }
3305
+ function installNonInteractive(source, targets) {
3306
+ const detected = targets.filter((t) => t.detected);
3307
+ if (detected.length === 0) {
3308
+ process.stderr.write(
3309
+ "No agents detected. Use --path to specify install location:\n notion skill --path ~/.claude/skills/using-notion-cli/SKILL.md\n"
3310
+ );
3311
+ return;
3312
+ }
3313
+ for (const target of detected) {
3314
+ const dest = installTo(source, target);
3315
+ process.stdout.write(`Installed: ${target.name}: ${dest}
3316
+ `);
3317
+ }
3318
+ }
3319
+ function skillCommand() {
3320
+ const cmd = new Command24("skill");
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(
3322
+ withErrorHandling(async (opts) => {
3323
+ if (opts.print) {
3324
+ process.stdout.write(readFileSync(skillPath(), "utf-8"));
3325
+ return;
3326
+ }
3327
+ const source = skillPath();
3328
+ if (opts.path) {
3329
+ const dir = dirname(opts.path);
3330
+ if (!existsSync4(dir)) mkdirSync(dir, { recursive: true });
3331
+ copyFileSync(source, opts.path);
3332
+ process.stdout.write(`Installed to ${opts.path}
3333
+ `);
3334
+ return;
3335
+ }
3336
+ const targets = getAgentTargets();
3337
+ const isTTY = process.stdout.isTTY ?? false;
3338
+ if (isTTY) {
3339
+ await installInteractive(source, targets);
3340
+ } else {
3341
+ installNonInteractive(source, targets);
3342
+ }
3343
+ })
3344
+ );
3345
+ return cmd;
3346
+ }
3347
+
2902
3348
  // src/commands/update.ts
2903
- import { Command as Command23 } from "commander";
2904
- function collectProps3(val, acc) {
3349
+ import { Command as Command25 } from "commander";
3350
+ function collectProps2(val, acc) {
2905
3351
  acc.push(val);
2906
3352
  return acc;
2907
3353
  }
2908
3354
  function updateCommand() {
2909
- const cmd = new Command23("update");
3355
+ const cmd = new Command25("update");
2910
3356
  cmd.description("update properties on a Notion page").argument("<id/url>", "Notion page ID or URL").option(
2911
3357
  "--prop <property=value>",
2912
3358
  "set a property value (repeatable)",
2913
- collectProps3,
3359
+ collectProps2,
2914
3360
  []
2915
3361
  ).option("--title <title>", "set the page title").action(
2916
3362
  withErrorHandling(async (idOrUrl, opts) => {
@@ -2964,7 +3410,7 @@ function updateCommand() {
2964
3410
  }
2965
3411
 
2966
3412
  // src/commands/users.ts
2967
- import { Command as Command24 } from "commander";
3413
+ import { Command as Command26 } from "commander";
2968
3414
  function getEmailOrWorkspace(user) {
2969
3415
  if (user.type === "person") {
2970
3416
  return user.person.email ?? "\u2014";
@@ -2976,7 +3422,7 @@ function getEmailOrWorkspace(user) {
2976
3422
  return "\u2014";
2977
3423
  }
2978
3424
  function usersCommand() {
2979
- const cmd = new Command24("users");
3425
+ const cmd = new Command26("users");
2980
3426
  cmd.description("list all users in the workspace").option("--json", "output as JSON").action(
2981
3427
  withErrorHandling(async (opts) => {
2982
3428
  if (opts.json) setOutputMode("json");
@@ -3003,11 +3449,11 @@ function usersCommand() {
3003
3449
 
3004
3450
  // src/cli.ts
3005
3451
  var __filename = fileURLToPath(import.meta.url);
3006
- var __dirname = dirname(__filename);
3452
+ var __dirname = dirname2(__filename);
3007
3453
  var pkg = JSON.parse(
3008
- readFileSync(join3(__dirname, "../package.json"), "utf-8")
3454
+ readFileSync2(join4(__dirname, "../package.json"), "utf-8")
3009
3455
  );
3010
- var program = new Command25();
3456
+ var program = new Command27();
3011
3457
  program.name("notion").description("Notion CLI \u2014 read Notion pages and databases from the terminal").version(pkg.version);
3012
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");
3013
3459
  program.configureOutput({
@@ -3028,7 +3474,7 @@ program.hook("preAction", (thisCommand) => {
3028
3474
  setOutputMode("md");
3029
3475
  }
3030
3476
  });
3031
- var authCmd = new Command25("auth").description("manage Notion authentication");
3477
+ var authCmd = new Command27("auth").description("manage Notion authentication");
3032
3478
  authCmd.action(authDefaultAction(authCmd));
3033
3479
  authCmd.addCommand(loginCommand());
3034
3480
  authCmd.addCommand(logoutCommand());
@@ -3038,7 +3484,7 @@ authCmd.addCommand(profileUseCommand());
3038
3484
  authCmd.addCommand(profileRemoveCommand());
3039
3485
  program.addCommand(authCmd);
3040
3486
  program.addCommand(initCommand(), { hidden: true });
3041
- var profileCmd = new Command25("profile").description(
3487
+ var profileCmd = new Command27("profile").description(
3042
3488
  "manage authentication profiles"
3043
3489
  );
3044
3490
  profileCmd.addCommand(profileListCommand());
@@ -3053,16 +3499,18 @@ program.addCommand(commentsCommand());
3053
3499
  program.addCommand(readCommand());
3054
3500
  program.addCommand(commentAddCommand());
3055
3501
  program.addCommand(appendCommand());
3502
+ program.addCommand(attachCommand());
3056
3503
  program.addCommand(createPageCommand());
3057
3504
  program.addCommand(editPageCommand());
3058
3505
  program.addCommand(updateCommand());
3059
3506
  program.addCommand(archiveCommand());
3060
3507
  program.addCommand(moveCommand());
3061
- var dbCmd = new Command25("db").description("Database operations");
3508
+ var dbCmd = new Command27("db").description("Database operations");
3062
3509
  dbCmd.addCommand(dbCreateCommand());
3063
3510
  dbCmd.addCommand(dbSchemaCommand());
3064
3511
  dbCmd.addCommand(dbQueryCommand());
3065
3512
  program.addCommand(dbCmd);
3066
3513
  program.addCommand(completionCommand());
3514
+ program.addCommand(skillCommand());
3067
3515
  await program.parseAsync();
3068
3516
  //# sourceMappingURL=cli.js.map