@andrzejchm/notion-cli 0.7.0 → 0.9.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/README.md +20 -4
- package/dist/cli.js +485 -300
- package/dist/cli.js.map +1 -1
- package/docs/FEATURE-PARITY.md +24 -28
- package/docs/README.agents.md +63 -0
- package/docs/skills/using-notion-cli/SKILL.md +35 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { readFileSync } from "fs";
|
|
5
5
|
import { dirname, join as join3 } from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
|
-
import { Command as
|
|
7
|
+
import { Command as Command23 } from "commander";
|
|
8
8
|
|
|
9
9
|
// src/commands/append.ts
|
|
10
10
|
import { Command } from "commander";
|
|
@@ -543,23 +543,25 @@ function reportTokenSource(source) {
|
|
|
543
543
|
}
|
|
544
544
|
|
|
545
545
|
// src/services/write.service.ts
|
|
546
|
-
async function addComment(client,
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
{
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
code: false,
|
|
559
|
-
color: "default"
|
|
560
|
-
}
|
|
546
|
+
async function addComment(client, target, text, options = {}) {
|
|
547
|
+
const richText = [
|
|
548
|
+
{
|
|
549
|
+
type: "text",
|
|
550
|
+
text: { content: text, link: null },
|
|
551
|
+
annotations: {
|
|
552
|
+
bold: false,
|
|
553
|
+
italic: false,
|
|
554
|
+
strikethrough: false,
|
|
555
|
+
underline: false,
|
|
556
|
+
code: false,
|
|
557
|
+
color: "default"
|
|
561
558
|
}
|
|
562
|
-
|
|
559
|
+
}
|
|
560
|
+
];
|
|
561
|
+
const targetPayload = target.type === "reply" ? { discussion_id: target.discussionId } : target.type === "block" ? { parent: { block_id: target.blockId } } : { parent: { page_id: target.pageId } };
|
|
562
|
+
await client.comments.create({
|
|
563
|
+
...targetPayload,
|
|
564
|
+
rich_text: richText,
|
|
563
565
|
...options.asUser && { display_name: { type: "user" } }
|
|
564
566
|
});
|
|
565
567
|
}
|
|
@@ -659,7 +661,22 @@ async function replaceMarkdown(client, pageId, newMarkdown, options) {
|
|
|
659
661
|
}
|
|
660
662
|
});
|
|
661
663
|
}
|
|
662
|
-
|
|
664
|
+
function buildIconCover(options) {
|
|
665
|
+
const result = {};
|
|
666
|
+
if (options?.icon) {
|
|
667
|
+
const isUrl = /^https?:\/\//i.test(options.icon);
|
|
668
|
+
if (isUrl) {
|
|
669
|
+
result.icon = { type: "external", external: { url: options.icon } };
|
|
670
|
+
} else {
|
|
671
|
+
result.icon = { type: "emoji", emoji: options.icon };
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
if (options?.cover) {
|
|
675
|
+
result.cover = { type: "external", external: { url: options.cover } };
|
|
676
|
+
}
|
|
677
|
+
return result;
|
|
678
|
+
}
|
|
679
|
+
async function createPage(client, parentId, title, markdown, options) {
|
|
663
680
|
const response = await client.pages.create({
|
|
664
681
|
parent: { type: "page_id", page_id: parentId },
|
|
665
682
|
properties: {
|
|
@@ -667,7 +684,24 @@ async function createPage(client, parentId, title, markdown) {
|
|
|
667
684
|
title: [{ type: "text", text: { content: title, link: null } }]
|
|
668
685
|
}
|
|
669
686
|
},
|
|
670
|
-
...markdown.trim() ? { markdown } : {}
|
|
687
|
+
...markdown.trim() ? { markdown } : {},
|
|
688
|
+
...buildIconCover(options)
|
|
689
|
+
});
|
|
690
|
+
const url = "url" in response ? response.url : response.id;
|
|
691
|
+
return url;
|
|
692
|
+
}
|
|
693
|
+
async function createPageInDatabase(client, databaseId, titlePropName, title, extraProperties, markdown, options) {
|
|
694
|
+
const properties = {
|
|
695
|
+
...extraProperties,
|
|
696
|
+
[titlePropName]: {
|
|
697
|
+
title: [{ type: "text", text: { content: title, link: null } }]
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
const response = await client.pages.create({
|
|
701
|
+
parent: { type: "database_id", database_id: databaseId },
|
|
702
|
+
properties,
|
|
703
|
+
...markdown.trim() ? { markdown } : {},
|
|
704
|
+
...buildIconCover(options)
|
|
671
705
|
});
|
|
672
706
|
const url = "url" in response ? response.url : response.id;
|
|
673
707
|
return url;
|
|
@@ -737,6 +771,102 @@ function appendCommand() {
|
|
|
737
771
|
return cmd;
|
|
738
772
|
}
|
|
739
773
|
|
|
774
|
+
// src/commands/archive.ts
|
|
775
|
+
import { Command as Command2 } from "commander";
|
|
776
|
+
|
|
777
|
+
// src/output/format.ts
|
|
778
|
+
var _mode = "auto";
|
|
779
|
+
function setOutputMode(mode) {
|
|
780
|
+
_mode = mode;
|
|
781
|
+
}
|
|
782
|
+
function getOutputMode() {
|
|
783
|
+
return _mode;
|
|
784
|
+
}
|
|
785
|
+
function isatty() {
|
|
786
|
+
return Boolean(process.stdout.isTTY);
|
|
787
|
+
}
|
|
788
|
+
function isHumanMode() {
|
|
789
|
+
if (_mode === "json") return false;
|
|
790
|
+
if (_mode === "md") return false;
|
|
791
|
+
return true;
|
|
792
|
+
}
|
|
793
|
+
function formatJSON(data) {
|
|
794
|
+
return JSON.stringify(data, null, 2);
|
|
795
|
+
}
|
|
796
|
+
var COLUMN_CAPS = {
|
|
797
|
+
TITLE: 50,
|
|
798
|
+
ID: 36
|
|
799
|
+
};
|
|
800
|
+
var DEFAULT_MAX_COL_WIDTH = 40;
|
|
801
|
+
function getColumnCap(header) {
|
|
802
|
+
return COLUMN_CAPS[header.toUpperCase()] ?? DEFAULT_MAX_COL_WIDTH;
|
|
803
|
+
}
|
|
804
|
+
function truncate(str, maxLen) {
|
|
805
|
+
if (str.length <= maxLen) return str;
|
|
806
|
+
return `${str.slice(0, maxLen - 1)}\u2026`;
|
|
807
|
+
}
|
|
808
|
+
function formatTable(rows, headers) {
|
|
809
|
+
const colWidths = headers.map((header, colIdx) => {
|
|
810
|
+
const cap = getColumnCap(header);
|
|
811
|
+
const headerLen = header.length;
|
|
812
|
+
const maxRowLen = rows.reduce((max, row) => {
|
|
813
|
+
const cell = row[colIdx] ?? "";
|
|
814
|
+
return Math.max(max, cell.length);
|
|
815
|
+
}, 0);
|
|
816
|
+
return Math.min(Math.max(headerLen, maxRowLen), cap);
|
|
817
|
+
});
|
|
818
|
+
const sep = "\u2500";
|
|
819
|
+
const colSep = " ";
|
|
820
|
+
const headerRow = headers.map((h, i) => h.padEnd(colWidths[i])).join(colSep);
|
|
821
|
+
const separatorRow = colWidths.map((w) => sep.repeat(w)).join(colSep);
|
|
822
|
+
const dataRows = rows.map(
|
|
823
|
+
(row) => headers.map((_, i) => {
|
|
824
|
+
const cell = row[i] ?? "";
|
|
825
|
+
return truncate(cell, colWidths[i]).padEnd(colWidths[i]);
|
|
826
|
+
}).join(colSep)
|
|
827
|
+
);
|
|
828
|
+
return [headerRow, separatorRow, ...dataRows].join("\n");
|
|
829
|
+
}
|
|
830
|
+
function printOutput(data, tableHeaders, tableRows) {
|
|
831
|
+
const mode = getOutputMode();
|
|
832
|
+
if (mode === "json") {
|
|
833
|
+
process.stdout.write(`${formatJSON(data)}
|
|
834
|
+
`);
|
|
835
|
+
} else if (isHumanMode() && tableHeaders && tableRows) {
|
|
836
|
+
printWithPager(`${formatTable(tableRows, tableHeaders)}
|
|
837
|
+
`);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
function printWithPager(text) {
|
|
841
|
+
process.stdout.write(text);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// src/commands/archive.ts
|
|
845
|
+
function archiveCommand() {
|
|
846
|
+
const cmd = new Command2("archive");
|
|
847
|
+
cmd.description("archive (trash) a Notion page").argument("<id/url>", "Notion page ID or URL").action(
|
|
848
|
+
withErrorHandling(async (idOrUrl) => {
|
|
849
|
+
const { token, source } = await resolveToken();
|
|
850
|
+
reportTokenSource(source);
|
|
851
|
+
const client = createNotionClient(token);
|
|
852
|
+
const id = parseNotionId(idOrUrl);
|
|
853
|
+
const uuid = toUuid(id);
|
|
854
|
+
const updatedPage = await client.pages.update({
|
|
855
|
+
page_id: uuid,
|
|
856
|
+
archived: true
|
|
857
|
+
});
|
|
858
|
+
const mode = getOutputMode();
|
|
859
|
+
if (mode === "json") {
|
|
860
|
+
process.stdout.write(`${formatJSON(updatedPage)}
|
|
861
|
+
`);
|
|
862
|
+
} else {
|
|
863
|
+
process.stdout.write("Page archived.\n");
|
|
864
|
+
}
|
|
865
|
+
})
|
|
866
|
+
);
|
|
867
|
+
return cmd;
|
|
868
|
+
}
|
|
869
|
+
|
|
740
870
|
// src/commands/auth/index.ts
|
|
741
871
|
function authDefaultAction(authCmd2) {
|
|
742
872
|
return async () => {
|
|
@@ -746,7 +876,7 @@ function authDefaultAction(authCmd2) {
|
|
|
746
876
|
|
|
747
877
|
// src/commands/auth/login.ts
|
|
748
878
|
import { input } from "@inquirer/prompts";
|
|
749
|
-
import { Command as
|
|
879
|
+
import { Command as Command3 } from "commander";
|
|
750
880
|
|
|
751
881
|
// src/oauth/oauth-flow.ts
|
|
752
882
|
import { spawn } from "child_process";
|
|
@@ -993,7 +1123,7 @@ Waiting for callback (up to 120 seconds)...
|
|
|
993
1123
|
|
|
994
1124
|
// src/commands/auth/login.ts
|
|
995
1125
|
function loginCommand() {
|
|
996
|
-
const cmd = new
|
|
1126
|
+
const cmd = new Command3("login");
|
|
997
1127
|
cmd.description("authenticate with Notion via OAuth").option("--profile <name>", "profile name to store credentials in").option(
|
|
998
1128
|
"--manual",
|
|
999
1129
|
"print auth URL instead of opening browser (for headless OAuth)"
|
|
@@ -1056,7 +1186,7 @@ function loginCommand() {
|
|
|
1056
1186
|
|
|
1057
1187
|
// src/commands/auth/logout.ts
|
|
1058
1188
|
import { select } from "@inquirer/prompts";
|
|
1059
|
-
import { Command as
|
|
1189
|
+
import { Command as Command4 } from "commander";
|
|
1060
1190
|
function profileLabel(name, profile) {
|
|
1061
1191
|
const parts = [];
|
|
1062
1192
|
if (profile.oauth_access_token)
|
|
@@ -1069,7 +1199,7 @@ function profileLabel(name, profile) {
|
|
|
1069
1199
|
return `${bold(name)} ${dim(authDesc)}${workspace}`;
|
|
1070
1200
|
}
|
|
1071
1201
|
function logoutCommand() {
|
|
1072
|
-
const cmd = new
|
|
1202
|
+
const cmd = new Command4("logout");
|
|
1073
1203
|
cmd.description("remove a profile and its credentials").option(
|
|
1074
1204
|
"--profile <name>",
|
|
1075
1205
|
"profile name to remove (skips interactive selector)"
|
|
@@ -1127,9 +1257,9 @@ function logoutCommand() {
|
|
|
1127
1257
|
}
|
|
1128
1258
|
|
|
1129
1259
|
// src/commands/auth/status.ts
|
|
1130
|
-
import { Command as
|
|
1260
|
+
import { Command as Command5 } from "commander";
|
|
1131
1261
|
function statusCommand() {
|
|
1132
|
-
const cmd = new
|
|
1262
|
+
const cmd = new Command5("status");
|
|
1133
1263
|
cmd.description("show authentication status for the active profile").option("--profile <name>", "profile name to check").action(
|
|
1134
1264
|
withErrorHandling(async (opts) => {
|
|
1135
1265
|
let profileName = opts.profile;
|
|
@@ -1187,94 +1317,57 @@ function statusCommand() {
|
|
|
1187
1317
|
}
|
|
1188
1318
|
|
|
1189
1319
|
// src/commands/comment-add.ts
|
|
1190
|
-
import { Command as
|
|
1320
|
+
import { Command as Command6 } from "commander";
|
|
1321
|
+
function resolveTarget(idOrUrl, opts) {
|
|
1322
|
+
const targetCount = [idOrUrl, opts.replyTo, opts.block].filter(
|
|
1323
|
+
Boolean
|
|
1324
|
+
).length;
|
|
1325
|
+
if (targetCount > 1) {
|
|
1326
|
+
throw new CliError(
|
|
1327
|
+
ErrorCodes.INVALID_ARG,
|
|
1328
|
+
"Provide only one target: a page ID/URL, --reply-to, or --block.",
|
|
1329
|
+
"These options are mutually exclusive"
|
|
1330
|
+
);
|
|
1331
|
+
}
|
|
1332
|
+
if (opts.replyTo) {
|
|
1333
|
+
return { type: "reply", discussionId: opts.replyTo };
|
|
1334
|
+
}
|
|
1335
|
+
if (opts.block) {
|
|
1336
|
+
return { type: "block", blockId: opts.block };
|
|
1337
|
+
}
|
|
1338
|
+
if (idOrUrl) {
|
|
1339
|
+
const id = parseNotionId(idOrUrl);
|
|
1340
|
+
return { type: "page", pageId: toUuid(id) };
|
|
1341
|
+
}
|
|
1342
|
+
throw new CliError(
|
|
1343
|
+
ErrorCodes.INVALID_ARG,
|
|
1344
|
+
"Provide a page ID/URL, --reply-to <discussion-id>, or --block <block-id>."
|
|
1345
|
+
);
|
|
1346
|
+
}
|
|
1191
1347
|
function commentAddCommand() {
|
|
1192
|
-
const cmd = new
|
|
1193
|
-
cmd.description("add a comment to a Notion page").argument("
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1348
|
+
const cmd = new Command6("comment");
|
|
1349
|
+
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
|
+
"--reply-to <discussion-id>",
|
|
1351
|
+
"reply to an existing discussion thread"
|
|
1352
|
+
).option("--block <block-id>", "comment on a specific block").action(
|
|
1353
|
+
withErrorHandling(
|
|
1354
|
+
async (idOrUrl, opts) => {
|
|
1355
|
+
const target = resolveTarget(idOrUrl, opts);
|
|
1356
|
+
const { token, source } = await resolveToken();
|
|
1357
|
+
reportTokenSource(source);
|
|
1358
|
+
const client = createNotionClient(token);
|
|
1359
|
+
await addComment(client, target, opts.message, {
|
|
1360
|
+
asUser: source === "oauth"
|
|
1361
|
+
});
|
|
1362
|
+
process.stdout.write("Comment added.\n");
|
|
1363
|
+
}
|
|
1364
|
+
)
|
|
1205
1365
|
);
|
|
1206
1366
|
return cmd;
|
|
1207
1367
|
}
|
|
1208
1368
|
|
|
1209
1369
|
// src/commands/comments.ts
|
|
1210
|
-
import { Command as
|
|
1211
|
-
|
|
1212
|
-
// src/output/format.ts
|
|
1213
|
-
var _mode = "auto";
|
|
1214
|
-
function setOutputMode(mode) {
|
|
1215
|
-
_mode = mode;
|
|
1216
|
-
}
|
|
1217
|
-
function getOutputMode() {
|
|
1218
|
-
return _mode;
|
|
1219
|
-
}
|
|
1220
|
-
function isatty() {
|
|
1221
|
-
return Boolean(process.stdout.isTTY);
|
|
1222
|
-
}
|
|
1223
|
-
function isHumanMode() {
|
|
1224
|
-
if (_mode === "json") return false;
|
|
1225
|
-
if (_mode === "md") return false;
|
|
1226
|
-
return true;
|
|
1227
|
-
}
|
|
1228
|
-
function formatJSON(data) {
|
|
1229
|
-
return JSON.stringify(data, null, 2);
|
|
1230
|
-
}
|
|
1231
|
-
var COLUMN_CAPS = {
|
|
1232
|
-
TITLE: 50,
|
|
1233
|
-
ID: 36
|
|
1234
|
-
};
|
|
1235
|
-
var DEFAULT_MAX_COL_WIDTH = 40;
|
|
1236
|
-
function getColumnCap(header) {
|
|
1237
|
-
return COLUMN_CAPS[header.toUpperCase()] ?? DEFAULT_MAX_COL_WIDTH;
|
|
1238
|
-
}
|
|
1239
|
-
function truncate(str, maxLen) {
|
|
1240
|
-
if (str.length <= maxLen) return str;
|
|
1241
|
-
return `${str.slice(0, maxLen - 1)}\u2026`;
|
|
1242
|
-
}
|
|
1243
|
-
function formatTable(rows, headers) {
|
|
1244
|
-
const colWidths = headers.map((header, colIdx) => {
|
|
1245
|
-
const cap = getColumnCap(header);
|
|
1246
|
-
const headerLen = header.length;
|
|
1247
|
-
const maxRowLen = rows.reduce((max, row) => {
|
|
1248
|
-
const cell = row[colIdx] ?? "";
|
|
1249
|
-
return Math.max(max, cell.length);
|
|
1250
|
-
}, 0);
|
|
1251
|
-
return Math.min(Math.max(headerLen, maxRowLen), cap);
|
|
1252
|
-
});
|
|
1253
|
-
const sep = "\u2500";
|
|
1254
|
-
const colSep = " ";
|
|
1255
|
-
const headerRow = headers.map((h, i) => h.padEnd(colWidths[i])).join(colSep);
|
|
1256
|
-
const separatorRow = colWidths.map((w) => sep.repeat(w)).join(colSep);
|
|
1257
|
-
const dataRows = rows.map(
|
|
1258
|
-
(row) => headers.map((_, i) => {
|
|
1259
|
-
const cell = row[i] ?? "";
|
|
1260
|
-
return truncate(cell, colWidths[i]).padEnd(colWidths[i]);
|
|
1261
|
-
}).join(colSep)
|
|
1262
|
-
);
|
|
1263
|
-
return [headerRow, separatorRow, ...dataRows].join("\n");
|
|
1264
|
-
}
|
|
1265
|
-
function printOutput(data, tableHeaders, tableRows) {
|
|
1266
|
-
const mode = getOutputMode();
|
|
1267
|
-
if (mode === "json") {
|
|
1268
|
-
process.stdout.write(`${formatJSON(data)}
|
|
1269
|
-
`);
|
|
1270
|
-
} else if (isHumanMode() && tableHeaders && tableRows) {
|
|
1271
|
-
printWithPager(`${formatTable(tableRows, tableHeaders)}
|
|
1272
|
-
`);
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
function printWithPager(text) {
|
|
1276
|
-
process.stdout.write(text);
|
|
1277
|
-
}
|
|
1370
|
+
import { Command as Command7 } from "commander";
|
|
1278
1371
|
|
|
1279
1372
|
// src/output/paginate.ts
|
|
1280
1373
|
async function paginateResults(fetcher) {
|
|
@@ -1291,8 +1384,16 @@ async function paginateResults(fetcher) {
|
|
|
1291
1384
|
}
|
|
1292
1385
|
|
|
1293
1386
|
// src/commands/comments.ts
|
|
1387
|
+
function formatParent(parent) {
|
|
1388
|
+
switch (parent.type) {
|
|
1389
|
+
case "page_id":
|
|
1390
|
+
return "page";
|
|
1391
|
+
case "block_id":
|
|
1392
|
+
return `block:${parent.block_id.slice(0, 8)}`;
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1294
1395
|
function commentsCommand() {
|
|
1295
|
-
const cmd = new
|
|
1396
|
+
const cmd = new Command7("comments");
|
|
1296
1397
|
cmd.description("list comments on a Notion page").argument("<id/url>", "Notion page ID or URL").option("--json", "output as JSON").action(
|
|
1297
1398
|
withErrorHandling(async (idOrUrl, opts) => {
|
|
1298
1399
|
if (opts.json) setOutputMode("json");
|
|
@@ -1313,17 +1414,23 @@ function commentsCommand() {
|
|
|
1313
1414
|
return [
|
|
1314
1415
|
comment.created_time.split("T")[0],
|
|
1315
1416
|
`${comment.created_by.id.slice(0, 8)}...`,
|
|
1417
|
+
comment.discussion_id.slice(0, 8),
|
|
1418
|
+
formatParent(comment.parent),
|
|
1316
1419
|
text.slice(0, 80) + (text.length > 80 ? "\u2026" : "")
|
|
1317
1420
|
];
|
|
1318
1421
|
});
|
|
1319
|
-
printOutput(
|
|
1422
|
+
printOutput(
|
|
1423
|
+
comments,
|
|
1424
|
+
["DATE", "AUTHOR ID", "DISCUSSION", "PARENT", "COMMENT"],
|
|
1425
|
+
rows
|
|
1426
|
+
);
|
|
1320
1427
|
})
|
|
1321
1428
|
);
|
|
1322
1429
|
return cmd;
|
|
1323
1430
|
}
|
|
1324
1431
|
|
|
1325
1432
|
// src/commands/completion.ts
|
|
1326
|
-
import { Command as
|
|
1433
|
+
import { Command as Command8 } from "commander";
|
|
1327
1434
|
var BASH_COMPLETION = `# notion bash completion
|
|
1328
1435
|
_notion_completion() {
|
|
1329
1436
|
local cur prev words cword
|
|
@@ -1425,7 +1532,7 @@ complete -c notion -n '__fish_seen_subcommand_from completion' -a zsh -d 'zsh co
|
|
|
1425
1532
|
complete -c notion -n '__fish_seen_subcommand_from completion' -a fish -d 'fish completion script'
|
|
1426
1533
|
`;
|
|
1427
1534
|
function completionCommand() {
|
|
1428
|
-
const cmd = new
|
|
1535
|
+
const cmd = new Command8("completion");
|
|
1429
1536
|
cmd.description("output shell completion script").argument("<shell>", "shell type (bash, zsh, fish)").action(
|
|
1430
1537
|
withErrorHandling(async (shell) => {
|
|
1431
1538
|
switch (shell) {
|
|
@@ -1451,40 +1558,6 @@ function completionCommand() {
|
|
|
1451
1558
|
}
|
|
1452
1559
|
|
|
1453
1560
|
// src/commands/create-page.ts
|
|
1454
|
-
import { Command as Command8 } from "commander";
|
|
1455
|
-
function createPageCommand() {
|
|
1456
|
-
const cmd = new Command8("create-page");
|
|
1457
|
-
cmd.description("create a new Notion page under a parent page").requiredOption("--parent <id/url>", "parent page ID or URL").requiredOption("--title <title>", "page title").option(
|
|
1458
|
-
"-m, --message <markdown>",
|
|
1459
|
-
"inline markdown content for the page body"
|
|
1460
|
-
).action(
|
|
1461
|
-
withErrorHandling(
|
|
1462
|
-
async (opts) => {
|
|
1463
|
-
const { token, source } = await resolveToken();
|
|
1464
|
-
reportTokenSource(source);
|
|
1465
|
-
const client = createNotionClient(token);
|
|
1466
|
-
let markdown = "";
|
|
1467
|
-
if (opts.message) {
|
|
1468
|
-
markdown = opts.message;
|
|
1469
|
-
} else if (!process.stdin.isTTY) {
|
|
1470
|
-
markdown = await readStdin();
|
|
1471
|
-
}
|
|
1472
|
-
const parentUuid = toUuid(parseNotionId(opts.parent));
|
|
1473
|
-
const url = await createPage(
|
|
1474
|
-
client,
|
|
1475
|
-
parentUuid,
|
|
1476
|
-
opts.title,
|
|
1477
|
-
markdown
|
|
1478
|
-
);
|
|
1479
|
-
process.stdout.write(`${url}
|
|
1480
|
-
`);
|
|
1481
|
-
}
|
|
1482
|
-
)
|
|
1483
|
-
);
|
|
1484
|
-
return cmd;
|
|
1485
|
-
}
|
|
1486
|
-
|
|
1487
|
-
// src/commands/db/query.ts
|
|
1488
1561
|
import { Command as Command9 } from "commander";
|
|
1489
1562
|
|
|
1490
1563
|
// src/services/database.service.ts
|
|
@@ -1510,7 +1583,8 @@ async function fetchDatabaseSchema(client, dbId) {
|
|
|
1510
1583
|
properties[name] = config;
|
|
1511
1584
|
}
|
|
1512
1585
|
}
|
|
1513
|
-
|
|
1586
|
+
const databaseId = "parent" in ds && ds.parent && typeof ds.parent === "object" && "database_id" in ds.parent ? ds.parent.database_id : dbId;
|
|
1587
|
+
return { id: dbId, databaseId, title, properties };
|
|
1514
1588
|
}
|
|
1515
1589
|
async function queryDatabase(client, dbId, opts = {}) {
|
|
1516
1590
|
const rawPages = await paginateResults(
|
|
@@ -1651,18 +1725,213 @@ function displayPropertyValue(prop) {
|
|
|
1651
1725
|
}
|
|
1652
1726
|
}
|
|
1653
1727
|
|
|
1654
|
-
// src/
|
|
1655
|
-
var
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1728
|
+
// src/services/update.service.ts
|
|
1729
|
+
var UNSUPPORTED_TYPES = /* @__PURE__ */ new Set([
|
|
1730
|
+
"relation",
|
|
1731
|
+
"formula",
|
|
1732
|
+
"rollup",
|
|
1733
|
+
"created_time",
|
|
1734
|
+
"created_by",
|
|
1735
|
+
"last_edited_time",
|
|
1736
|
+
"last_edited_by",
|
|
1737
|
+
"files",
|
|
1738
|
+
"unique_id",
|
|
1739
|
+
"verification",
|
|
1740
|
+
"button"
|
|
1741
|
+
]);
|
|
1742
|
+
function buildPropertyUpdate(propName, propType, value) {
|
|
1743
|
+
if (UNSUPPORTED_TYPES.has(propType)) {
|
|
1744
|
+
throw new CliError(
|
|
1745
|
+
ErrorCodes.INVALID_ARG,
|
|
1746
|
+
`Property "${propName}" has type "${propType}" which cannot be set via the CLI.`,
|
|
1747
|
+
"Supported types: title, rich_text, select, status, multi_select, number, checkbox, url, email, phone_number, date"
|
|
1748
|
+
);
|
|
1749
|
+
}
|
|
1750
|
+
if (value === "") {
|
|
1751
|
+
return null;
|
|
1752
|
+
}
|
|
1753
|
+
switch (propType) {
|
|
1754
|
+
case "title":
|
|
1755
|
+
return { title: [{ type: "text", text: { content: value } }] };
|
|
1756
|
+
case "rich_text":
|
|
1757
|
+
return { rich_text: [{ type: "text", text: { content: value } }] };
|
|
1758
|
+
case "select":
|
|
1759
|
+
return { select: { name: value } };
|
|
1760
|
+
case "status":
|
|
1761
|
+
return { status: { name: value } };
|
|
1762
|
+
case "multi_select":
|
|
1763
|
+
return {
|
|
1764
|
+
multi_select: value.split(",").map((v) => v.trim()).filter(Boolean).map((v) => ({ name: v }))
|
|
1765
|
+
};
|
|
1766
|
+
case "number": {
|
|
1767
|
+
const n = Number(value);
|
|
1768
|
+
if (Number.isNaN(n)) {
|
|
1769
|
+
throw new CliError(
|
|
1770
|
+
ErrorCodes.INVALID_ARG,
|
|
1771
|
+
`Invalid number value "${value}" for property "${propName}".`,
|
|
1772
|
+
'Provide a numeric value, e.g. --prop "Count=42"'
|
|
1773
|
+
);
|
|
1774
|
+
}
|
|
1775
|
+
return { number: n };
|
|
1776
|
+
}
|
|
1777
|
+
case "checkbox": {
|
|
1778
|
+
const lower = value.toLowerCase();
|
|
1779
|
+
return { checkbox: lower === "true" || lower === "yes" };
|
|
1780
|
+
}
|
|
1781
|
+
case "url":
|
|
1782
|
+
return { url: value };
|
|
1783
|
+
case "email":
|
|
1784
|
+
return { email: value };
|
|
1785
|
+
case "phone_number":
|
|
1786
|
+
return { phone_number: value };
|
|
1787
|
+
case "date": {
|
|
1788
|
+
const parts = value.split(",");
|
|
1789
|
+
const start = parts[0].trim();
|
|
1790
|
+
const end = parts[1]?.trim();
|
|
1791
|
+
return { date: end ? { start, end } : { start } };
|
|
1792
|
+
}
|
|
1793
|
+
default:
|
|
1794
|
+
throw new CliError(
|
|
1795
|
+
ErrorCodes.INVALID_ARG,
|
|
1796
|
+
`Property "${propName}" has unsupported type "${propType}".`,
|
|
1797
|
+
"Supported types: title, rich_text, select, status, multi_select, number, checkbox, url, email, phone_number, date"
|
|
1798
|
+
);
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
function buildPropertiesPayload(propStrings, schemaOrPage) {
|
|
1802
|
+
const isPage = "object" in schemaOrPage && schemaOrPage.object === "page";
|
|
1803
|
+
const schema = isPage ? schemaOrPage.properties : schemaOrPage;
|
|
1804
|
+
const result = {};
|
|
1805
|
+
for (const propString of propStrings) {
|
|
1806
|
+
const eqIdx = propString.indexOf("=");
|
|
1807
|
+
if (eqIdx === -1) {
|
|
1808
|
+
throw new CliError(
|
|
1809
|
+
ErrorCodes.INVALID_ARG,
|
|
1810
|
+
`Invalid --prop value: "${propString}". Expected format: "PropertyName=Value".`,
|
|
1811
|
+
'Example: --prop "Status=Done"'
|
|
1812
|
+
);
|
|
1813
|
+
}
|
|
1814
|
+
const propName = propString.slice(0, eqIdx).trim();
|
|
1815
|
+
const value = propString.slice(eqIdx + 1);
|
|
1816
|
+
const schemaProp = schema[propName];
|
|
1817
|
+
if (!schemaProp) {
|
|
1818
|
+
const available = Object.keys(schema).join(", ");
|
|
1819
|
+
throw new CliError(
|
|
1820
|
+
ErrorCodes.INVALID_ARG,
|
|
1821
|
+
`Property "${propName}" not found on this page.`,
|
|
1822
|
+
`Available properties: ${available}`
|
|
1823
|
+
);
|
|
1824
|
+
}
|
|
1825
|
+
const propType = schemaProp.type;
|
|
1826
|
+
const payload = buildPropertyUpdate(propName, propType, value);
|
|
1827
|
+
result[propName] = payload;
|
|
1828
|
+
}
|
|
1829
|
+
return result;
|
|
1830
|
+
}
|
|
1831
|
+
async function updatePageProperties(client, pageId, properties) {
|
|
1832
|
+
const response = await client.pages.update({
|
|
1833
|
+
page_id: pageId,
|
|
1834
|
+
properties
|
|
1835
|
+
});
|
|
1836
|
+
return response;
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
// src/commands/create-page.ts
|
|
1840
|
+
function collectProps(val, acc) {
|
|
1841
|
+
acc.push(val);
|
|
1842
|
+
return acc;
|
|
1843
|
+
}
|
|
1844
|
+
async function tryGetDatabaseSchema(client, uuid) {
|
|
1845
|
+
try {
|
|
1846
|
+
return await fetchDatabaseSchema(client, uuid);
|
|
1847
|
+
} catch {
|
|
1848
|
+
return null;
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
function createPageCommand() {
|
|
1852
|
+
const cmd = new Command9("create-page");
|
|
1853
|
+
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(
|
|
1854
|
+
"-m, --message <markdown>",
|
|
1855
|
+
"inline markdown content for the page body"
|
|
1856
|
+
).option(
|
|
1857
|
+
"--prop <property=value>",
|
|
1858
|
+
"set a property value (repeatable, database parents only)",
|
|
1859
|
+
collectProps,
|
|
1860
|
+
[]
|
|
1861
|
+
).option("--icon <emoji-or-url>", "page icon \u2014 emoji character or image URL").option("--cover <url>", "page cover image URL").action(
|
|
1862
|
+
withErrorHandling(async (opts) => {
|
|
1863
|
+
const { token, source } = await resolveToken();
|
|
1864
|
+
reportTokenSource(source);
|
|
1865
|
+
const client = createNotionClient(token);
|
|
1866
|
+
let markdown = "";
|
|
1867
|
+
if (opts.message) {
|
|
1868
|
+
markdown = opts.message;
|
|
1869
|
+
} else if (!process.stdin.isTTY) {
|
|
1870
|
+
markdown = await readStdin();
|
|
1871
|
+
}
|
|
1872
|
+
const parentUuid = toUuid(parseNotionId(opts.parent));
|
|
1873
|
+
const iconCover = { icon: opts.icon, cover: opts.cover };
|
|
1874
|
+
const dbSchema = await tryGetDatabaseSchema(client, parentUuid);
|
|
1875
|
+
if (dbSchema) {
|
|
1876
|
+
const titleEntry = Object.entries(dbSchema.properties).find(
|
|
1877
|
+
([, prop]) => prop.type === "title"
|
|
1878
|
+
);
|
|
1879
|
+
if (!titleEntry) {
|
|
1880
|
+
throw new CliError(
|
|
1881
|
+
ErrorCodes.API_ERROR,
|
|
1882
|
+
"Database has no title property.",
|
|
1883
|
+
"This database cannot accept new pages."
|
|
1884
|
+
);
|
|
1885
|
+
}
|
|
1886
|
+
const [titlePropName] = titleEntry;
|
|
1887
|
+
const extraProperties = opts.prop.length > 0 ? buildPropertiesPayload(opts.prop, dbSchema.properties) : {};
|
|
1888
|
+
const url = await createPageInDatabase(
|
|
1889
|
+
client,
|
|
1890
|
+
dbSchema.databaseId,
|
|
1891
|
+
titlePropName,
|
|
1892
|
+
opts.title,
|
|
1893
|
+
extraProperties,
|
|
1894
|
+
markdown,
|
|
1895
|
+
iconCover
|
|
1896
|
+
);
|
|
1897
|
+
process.stdout.write(`${url}
|
|
1898
|
+
`);
|
|
1899
|
+
} else {
|
|
1900
|
+
if (opts.prop.length > 0) {
|
|
1901
|
+
throw new CliError(
|
|
1902
|
+
ErrorCodes.INVALID_ARG,
|
|
1903
|
+
"--prop is only supported when the parent is a database.",
|
|
1904
|
+
"To set properties, use a database ID/URL as --parent"
|
|
1905
|
+
);
|
|
1906
|
+
}
|
|
1907
|
+
const url = await createPage(
|
|
1908
|
+
client,
|
|
1909
|
+
parentUuid,
|
|
1910
|
+
opts.title,
|
|
1911
|
+
markdown,
|
|
1912
|
+
iconCover
|
|
1913
|
+
);
|
|
1914
|
+
process.stdout.write(`${url}
|
|
1915
|
+
`);
|
|
1916
|
+
}
|
|
1917
|
+
})
|
|
1918
|
+
);
|
|
1919
|
+
return cmd;
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
// src/commands/db/query.ts
|
|
1923
|
+
import { Command as Command10 } from "commander";
|
|
1924
|
+
var SKIP_TYPES_IN_AUTO = /* @__PURE__ */ new Set(["relation", "rich_text", "people"]);
|
|
1925
|
+
function autoSelectColumns(schema, entries) {
|
|
1926
|
+
const termWidth = process.stdout.columns || 120;
|
|
1927
|
+
const COL_SEP = 2;
|
|
1928
|
+
const candidates = Object.values(schema.properties).filter((p) => !SKIP_TYPES_IN_AUTO.has(p.type)).map((p) => p.name);
|
|
1929
|
+
const widths = candidates.map((col) => {
|
|
1930
|
+
const header = col.toUpperCase().length;
|
|
1931
|
+
const maxData = entries.reduce(
|
|
1932
|
+
(max, e) => Math.max(max, (e.properties[col] ?? "").length),
|
|
1933
|
+
0
|
|
1934
|
+
);
|
|
1666
1935
|
return Math.min(Math.max(header, maxData), 40);
|
|
1667
1936
|
});
|
|
1668
1937
|
const selected = [];
|
|
@@ -1679,7 +1948,7 @@ function autoSelectColumns(schema, entries) {
|
|
|
1679
1948
|
return selected;
|
|
1680
1949
|
}
|
|
1681
1950
|
function dbQueryCommand() {
|
|
1682
|
-
return new
|
|
1951
|
+
return new Command10("query").description("Query database entries with optional filtering and sorting").argument("<id>", "Notion database ID or URL").option(
|
|
1683
1952
|
"--filter <filter>",
|
|
1684
1953
|
'Filter entries (repeatable): --filter "Status=Done"',
|
|
1685
1954
|
collect,
|
|
@@ -1734,9 +2003,9 @@ function collect(value, previous) {
|
|
|
1734
2003
|
}
|
|
1735
2004
|
|
|
1736
2005
|
// src/commands/db/schema.ts
|
|
1737
|
-
import { Command as
|
|
2006
|
+
import { Command as Command11 } from "commander";
|
|
1738
2007
|
function dbSchemaCommand() {
|
|
1739
|
-
return new
|
|
2008
|
+
return new Command11("schema").description("Show database schema (property names, types, and options)").argument("<id>", "Notion database ID or URL").option("--json", "Output raw JSON").action(
|
|
1740
2009
|
withErrorHandling(async (id, options) => {
|
|
1741
2010
|
const { token } = await resolveToken();
|
|
1742
2011
|
const client = createNotionClient(token);
|
|
@@ -1760,13 +2029,13 @@ function dbSchemaCommand() {
|
|
|
1760
2029
|
}
|
|
1761
2030
|
|
|
1762
2031
|
// src/commands/edit-page.ts
|
|
1763
|
-
import { Command as
|
|
2032
|
+
import { Command as Command12 } from "commander";
|
|
1764
2033
|
function collect2(val, acc) {
|
|
1765
2034
|
acc.push(val);
|
|
1766
2035
|
return acc;
|
|
1767
2036
|
}
|
|
1768
2037
|
function editPageCommand() {
|
|
1769
|
-
const cmd = new
|
|
2038
|
+
const cmd = new Command12("edit-page");
|
|
1770
2039
|
cmd.description(
|
|
1771
2040
|
"replace a Notion page's content \u2014 full page or a targeted section"
|
|
1772
2041
|
).argument("<id/url>", "Notion page ID or URL").option(
|
|
@@ -1864,7 +2133,7 @@ function editPageCommand() {
|
|
|
1864
2133
|
|
|
1865
2134
|
// src/commands/init.ts
|
|
1866
2135
|
import { confirm, input as input2, password } from "@inquirer/prompts";
|
|
1867
|
-
import { Command as
|
|
2136
|
+
import { Command as Command13 } from "commander";
|
|
1868
2137
|
async function runInitFlow() {
|
|
1869
2138
|
const profileName = await input2({
|
|
1870
2139
|
message: "Profile name:",
|
|
@@ -1944,7 +2213,7 @@ async function runInitFlow() {
|
|
|
1944
2213
|
stderrWrite(dim(" notion auth login"));
|
|
1945
2214
|
}
|
|
1946
2215
|
function initCommand() {
|
|
1947
|
-
const cmd = new
|
|
2216
|
+
const cmd = new Command13("init");
|
|
1948
2217
|
cmd.description("authenticate with Notion and save a profile").action(
|
|
1949
2218
|
withErrorHandling(async () => {
|
|
1950
2219
|
if (!process.stdin.isTTY) {
|
|
@@ -1962,7 +2231,7 @@ function initCommand() {
|
|
|
1962
2231
|
|
|
1963
2232
|
// src/commands/ls.ts
|
|
1964
2233
|
import { isFullPageOrDataSource } from "@notionhq/client";
|
|
1965
|
-
import { Command as
|
|
2234
|
+
import { Command as Command14 } from "commander";
|
|
1966
2235
|
function getTitle(item) {
|
|
1967
2236
|
if (item.object === "data_source") {
|
|
1968
2237
|
return item.title.map((t) => t.plain_text).join("") || "(untitled)";
|
|
@@ -1979,7 +2248,7 @@ function displayType(item) {
|
|
|
1979
2248
|
return item.object === "data_source" ? "database" : item.object;
|
|
1980
2249
|
}
|
|
1981
2250
|
function lsCommand() {
|
|
1982
|
-
const cmd = new
|
|
2251
|
+
const cmd = new Command14("ls");
|
|
1983
2252
|
cmd.description("list accessible Notion pages and databases").option(
|
|
1984
2253
|
"--type <type>",
|
|
1985
2254
|
"filter by object type (page or database)",
|
|
@@ -1989,6 +2258,15 @@ function lsCommand() {
|
|
|
1989
2258
|
}
|
|
1990
2259
|
return val;
|
|
1991
2260
|
}
|
|
2261
|
+
).option(
|
|
2262
|
+
"--sort <direction>",
|
|
2263
|
+
"sort by last edited time (asc or desc)",
|
|
2264
|
+
(val) => {
|
|
2265
|
+
if (val !== "asc" && val !== "desc") {
|
|
2266
|
+
throw new Error('--sort must be "asc" or "desc"');
|
|
2267
|
+
}
|
|
2268
|
+
return val;
|
|
2269
|
+
}
|
|
1992
2270
|
).option(
|
|
1993
2271
|
"--cursor <cursor>",
|
|
1994
2272
|
"start from this pagination cursor (from a previous --next hint)"
|
|
@@ -2002,6 +2280,10 @@ function lsCommand() {
|
|
|
2002
2280
|
reportTokenSource(source);
|
|
2003
2281
|
const notion = createNotionClient(token);
|
|
2004
2282
|
const response = await notion.search({
|
|
2283
|
+
sort: opts.sort ? {
|
|
2284
|
+
timestamp: "last_edited_time",
|
|
2285
|
+
direction: opts.sort === "asc" ? "ascending" : "descending"
|
|
2286
|
+
} : void 0,
|
|
2005
2287
|
start_cursor: opts.cursor,
|
|
2006
2288
|
page_size: 20
|
|
2007
2289
|
});
|
|
@@ -2042,10 +2324,10 @@ function lsCommand() {
|
|
|
2042
2324
|
// src/commands/open.ts
|
|
2043
2325
|
import { exec } from "child_process";
|
|
2044
2326
|
import { promisify } from "util";
|
|
2045
|
-
import { Command as
|
|
2327
|
+
import { Command as Command15 } from "commander";
|
|
2046
2328
|
var execAsync = promisify(exec);
|
|
2047
2329
|
function openCommand() {
|
|
2048
|
-
const cmd = new
|
|
2330
|
+
const cmd = new Command15("open");
|
|
2049
2331
|
cmd.description("open a Notion page in the default browser").argument("<id/url>", "Notion page ID or URL").action(
|
|
2050
2332
|
withErrorHandling(async (idOrUrl) => {
|
|
2051
2333
|
const id = parseNotionId(idOrUrl);
|
|
@@ -2061,9 +2343,9 @@ function openCommand() {
|
|
|
2061
2343
|
}
|
|
2062
2344
|
|
|
2063
2345
|
// src/commands/profile/list.ts
|
|
2064
|
-
import { Command as
|
|
2346
|
+
import { Command as Command16 } from "commander";
|
|
2065
2347
|
function profileListCommand() {
|
|
2066
|
-
const cmd = new
|
|
2348
|
+
const cmd = new Command16("list");
|
|
2067
2349
|
cmd.description("list all authentication profiles").action(
|
|
2068
2350
|
withErrorHandling(async () => {
|
|
2069
2351
|
const config = await readGlobalConfig();
|
|
@@ -2092,9 +2374,9 @@ function profileListCommand() {
|
|
|
2092
2374
|
}
|
|
2093
2375
|
|
|
2094
2376
|
// src/commands/profile/remove.ts
|
|
2095
|
-
import { Command as
|
|
2377
|
+
import { Command as Command17 } from "commander";
|
|
2096
2378
|
function profileRemoveCommand() {
|
|
2097
|
-
const cmd = new
|
|
2379
|
+
const cmd = new Command17("remove");
|
|
2098
2380
|
cmd.description("remove an authentication profile").argument("<name>", "profile name to remove").action(
|
|
2099
2381
|
withErrorHandling(async (name) => {
|
|
2100
2382
|
const config = await readGlobalConfig();
|
|
@@ -2120,9 +2402,9 @@ function profileRemoveCommand() {
|
|
|
2120
2402
|
}
|
|
2121
2403
|
|
|
2122
2404
|
// src/commands/profile/use.ts
|
|
2123
|
-
import { Command as
|
|
2405
|
+
import { Command as Command18 } from "commander";
|
|
2124
2406
|
function profileUseCommand() {
|
|
2125
|
-
const cmd = new
|
|
2407
|
+
const cmd = new Command18("use");
|
|
2126
2408
|
cmd.description("switch the active profile").argument("<name>", "profile name to activate").action(
|
|
2127
2409
|
withErrorHandling(async (name) => {
|
|
2128
2410
|
const config = await readGlobalConfig();
|
|
@@ -2145,7 +2427,7 @@ function profileUseCommand() {
|
|
|
2145
2427
|
}
|
|
2146
2428
|
|
|
2147
2429
|
// src/commands/read.ts
|
|
2148
|
-
import { Command as
|
|
2430
|
+
import { Command as Command19 } from "commander";
|
|
2149
2431
|
|
|
2150
2432
|
// src/output/markdown.ts
|
|
2151
2433
|
import { Chalk as Chalk2 } from "chalk";
|
|
@@ -2285,7 +2567,7 @@ async function fetchPageMarkdown(client, pageId) {
|
|
|
2285
2567
|
|
|
2286
2568
|
// src/commands/read.ts
|
|
2287
2569
|
function readCommand() {
|
|
2288
|
-
return new
|
|
2570
|
+
return new Command19("read").description("Read a Notion page as markdown").argument("<id>", "Notion page ID or URL").action(
|
|
2289
2571
|
withErrorHandling(async (id) => {
|
|
2290
2572
|
const { token } = await resolveToken();
|
|
2291
2573
|
const client = createNotionClient(token);
|
|
@@ -2311,7 +2593,7 @@ function readCommand() {
|
|
|
2311
2593
|
|
|
2312
2594
|
// src/commands/search.ts
|
|
2313
2595
|
import { isFullPageOrDataSource as isFullPageOrDataSource2 } from "@notionhq/client";
|
|
2314
|
-
import { Command as
|
|
2596
|
+
import { Command as Command20 } from "commander";
|
|
2315
2597
|
function getTitle2(item) {
|
|
2316
2598
|
if (item.object === "data_source") {
|
|
2317
2599
|
return item.title.map((t) => t.plain_text).join("") || "(untitled)";
|
|
@@ -2331,7 +2613,7 @@ function displayType2(item) {
|
|
|
2331
2613
|
return item.object === "data_source" ? "database" : item.object;
|
|
2332
2614
|
}
|
|
2333
2615
|
function searchCommand() {
|
|
2334
|
-
const cmd = new
|
|
2616
|
+
const cmd = new Command20("search");
|
|
2335
2617
|
cmd.description("search Notion workspace by keyword").argument("<query>", "search keyword").option(
|
|
2336
2618
|
"--type <type>",
|
|
2337
2619
|
"filter by object type (page or database)",
|
|
@@ -2341,6 +2623,15 @@ function searchCommand() {
|
|
|
2341
2623
|
}
|
|
2342
2624
|
return val;
|
|
2343
2625
|
}
|
|
2626
|
+
).option(
|
|
2627
|
+
"--sort <direction>",
|
|
2628
|
+
"sort by last edited time (asc or desc)",
|
|
2629
|
+
(val) => {
|
|
2630
|
+
if (val !== "asc" && val !== "desc") {
|
|
2631
|
+
throw new Error('--sort must be "asc" or "desc"');
|
|
2632
|
+
}
|
|
2633
|
+
return val;
|
|
2634
|
+
}
|
|
2344
2635
|
).option(
|
|
2345
2636
|
"--cursor <cursor>",
|
|
2346
2637
|
"start from this pagination cursor (from a previous --next hint)"
|
|
@@ -2356,6 +2647,10 @@ function searchCommand() {
|
|
|
2356
2647
|
const response = await notion.search({
|
|
2357
2648
|
query,
|
|
2358
2649
|
filter: opts.type ? { property: "object", value: toSdkFilterValue(opts.type) } : void 0,
|
|
2650
|
+
sort: opts.sort ? {
|
|
2651
|
+
timestamp: "last_edited_time",
|
|
2652
|
+
direction: opts.sort === "asc" ? "ascending" : "descending"
|
|
2653
|
+
} : void 0,
|
|
2359
2654
|
start_cursor: opts.cursor,
|
|
2360
2655
|
page_size: 20
|
|
2361
2656
|
});
|
|
@@ -2389,128 +2684,17 @@ function searchCommand() {
|
|
|
2389
2684
|
}
|
|
2390
2685
|
|
|
2391
2686
|
// src/commands/update.ts
|
|
2392
|
-
import { Command as
|
|
2393
|
-
|
|
2394
|
-
// src/services/update.service.ts
|
|
2395
|
-
var UNSUPPORTED_TYPES = /* @__PURE__ */ new Set([
|
|
2396
|
-
"relation",
|
|
2397
|
-
"formula",
|
|
2398
|
-
"rollup",
|
|
2399
|
-
"created_time",
|
|
2400
|
-
"created_by",
|
|
2401
|
-
"last_edited_time",
|
|
2402
|
-
"last_edited_by",
|
|
2403
|
-
"files",
|
|
2404
|
-
"unique_id",
|
|
2405
|
-
"verification",
|
|
2406
|
-
"button"
|
|
2407
|
-
]);
|
|
2408
|
-
function buildPropertyUpdate(propName, propType, value) {
|
|
2409
|
-
if (UNSUPPORTED_TYPES.has(propType)) {
|
|
2410
|
-
throw new CliError(
|
|
2411
|
-
ErrorCodes.INVALID_ARG,
|
|
2412
|
-
`Property "${propName}" has type "${propType}" which cannot be set via the CLI.`,
|
|
2413
|
-
"Supported types: title, rich_text, select, status, multi_select, number, checkbox, url, email, phone_number, date"
|
|
2414
|
-
);
|
|
2415
|
-
}
|
|
2416
|
-
if (value === "") {
|
|
2417
|
-
return null;
|
|
2418
|
-
}
|
|
2419
|
-
switch (propType) {
|
|
2420
|
-
case "title":
|
|
2421
|
-
return { title: [{ type: "text", text: { content: value } }] };
|
|
2422
|
-
case "rich_text":
|
|
2423
|
-
return { rich_text: [{ type: "text", text: { content: value } }] };
|
|
2424
|
-
case "select":
|
|
2425
|
-
return { select: { name: value } };
|
|
2426
|
-
case "status":
|
|
2427
|
-
return { status: { name: value } };
|
|
2428
|
-
case "multi_select":
|
|
2429
|
-
return {
|
|
2430
|
-
multi_select: value.split(",").map((v) => v.trim()).filter(Boolean).map((v) => ({ name: v }))
|
|
2431
|
-
};
|
|
2432
|
-
case "number": {
|
|
2433
|
-
const n = Number(value);
|
|
2434
|
-
if (Number.isNaN(n)) {
|
|
2435
|
-
throw new CliError(
|
|
2436
|
-
ErrorCodes.INVALID_ARG,
|
|
2437
|
-
`Invalid number value "${value}" for property "${propName}".`,
|
|
2438
|
-
'Provide a numeric value, e.g. --prop "Count=42"'
|
|
2439
|
-
);
|
|
2440
|
-
}
|
|
2441
|
-
return { number: n };
|
|
2442
|
-
}
|
|
2443
|
-
case "checkbox": {
|
|
2444
|
-
const lower = value.toLowerCase();
|
|
2445
|
-
return { checkbox: lower === "true" || lower === "yes" };
|
|
2446
|
-
}
|
|
2447
|
-
case "url":
|
|
2448
|
-
return { url: value };
|
|
2449
|
-
case "email":
|
|
2450
|
-
return { email: value };
|
|
2451
|
-
case "phone_number":
|
|
2452
|
-
return { phone_number: value };
|
|
2453
|
-
case "date": {
|
|
2454
|
-
const parts = value.split(",");
|
|
2455
|
-
const start = parts[0].trim();
|
|
2456
|
-
const end = parts[1]?.trim();
|
|
2457
|
-
return { date: end ? { start, end } : { start } };
|
|
2458
|
-
}
|
|
2459
|
-
default:
|
|
2460
|
-
throw new CliError(
|
|
2461
|
-
ErrorCodes.INVALID_ARG,
|
|
2462
|
-
`Property "${propName}" has unsupported type "${propType}".`,
|
|
2463
|
-
"Supported types: title, rich_text, select, status, multi_select, number, checkbox, url, email, phone_number, date"
|
|
2464
|
-
);
|
|
2465
|
-
}
|
|
2466
|
-
}
|
|
2467
|
-
function buildPropertiesPayload(propStrings, page) {
|
|
2468
|
-
const result = {};
|
|
2469
|
-
for (const propString of propStrings) {
|
|
2470
|
-
const eqIdx = propString.indexOf("=");
|
|
2471
|
-
if (eqIdx === -1) {
|
|
2472
|
-
throw new CliError(
|
|
2473
|
-
ErrorCodes.INVALID_ARG,
|
|
2474
|
-
`Invalid --prop value: "${propString}". Expected format: "PropertyName=Value".`,
|
|
2475
|
-
'Example: --prop "Status=Done"'
|
|
2476
|
-
);
|
|
2477
|
-
}
|
|
2478
|
-
const propName = propString.slice(0, eqIdx).trim();
|
|
2479
|
-
const value = propString.slice(eqIdx + 1);
|
|
2480
|
-
const schemaProp = page.properties[propName];
|
|
2481
|
-
if (!schemaProp) {
|
|
2482
|
-
const available = Object.keys(page.properties).join(", ");
|
|
2483
|
-
throw new CliError(
|
|
2484
|
-
ErrorCodes.INVALID_ARG,
|
|
2485
|
-
`Property "${propName}" not found on this page.`,
|
|
2486
|
-
`Available properties: ${available}`
|
|
2487
|
-
);
|
|
2488
|
-
}
|
|
2489
|
-
const propType = schemaProp.type;
|
|
2490
|
-
const payload = buildPropertyUpdate(propName, propType, value);
|
|
2491
|
-
result[propName] = payload;
|
|
2492
|
-
}
|
|
2493
|
-
return result;
|
|
2494
|
-
}
|
|
2495
|
-
async function updatePageProperties(client, pageId, properties) {
|
|
2496
|
-
const response = await client.pages.update({
|
|
2497
|
-
page_id: pageId,
|
|
2498
|
-
properties
|
|
2499
|
-
});
|
|
2500
|
-
return response;
|
|
2501
|
-
}
|
|
2502
|
-
|
|
2503
|
-
// src/commands/update.ts
|
|
2504
|
-
function collectProps(val, acc) {
|
|
2687
|
+
import { Command as Command21 } from "commander";
|
|
2688
|
+
function collectProps2(val, acc) {
|
|
2505
2689
|
acc.push(val);
|
|
2506
2690
|
return acc;
|
|
2507
2691
|
}
|
|
2508
2692
|
function updateCommand() {
|
|
2509
|
-
const cmd = new
|
|
2693
|
+
const cmd = new Command21("update");
|
|
2510
2694
|
cmd.description("update properties on a Notion page").argument("<id/url>", "Notion page ID or URL").option(
|
|
2511
2695
|
"--prop <property=value>",
|
|
2512
2696
|
"set a property value (repeatable)",
|
|
2513
|
-
|
|
2697
|
+
collectProps2,
|
|
2514
2698
|
[]
|
|
2515
2699
|
).option("--title <title>", "set the page title").action(
|
|
2516
2700
|
withErrorHandling(async (idOrUrl, opts) => {
|
|
@@ -2564,7 +2748,7 @@ function updateCommand() {
|
|
|
2564
2748
|
}
|
|
2565
2749
|
|
|
2566
2750
|
// src/commands/users.ts
|
|
2567
|
-
import { Command as
|
|
2751
|
+
import { Command as Command22 } from "commander";
|
|
2568
2752
|
function getEmailOrWorkspace(user) {
|
|
2569
2753
|
if (user.type === "person") {
|
|
2570
2754
|
return user.person.email ?? "\u2014";
|
|
@@ -2576,7 +2760,7 @@ function getEmailOrWorkspace(user) {
|
|
|
2576
2760
|
return "\u2014";
|
|
2577
2761
|
}
|
|
2578
2762
|
function usersCommand() {
|
|
2579
|
-
const cmd = new
|
|
2763
|
+
const cmd = new Command22("users");
|
|
2580
2764
|
cmd.description("list all users in the workspace").option("--json", "output as JSON").action(
|
|
2581
2765
|
withErrorHandling(async (opts) => {
|
|
2582
2766
|
if (opts.json) setOutputMode("json");
|
|
@@ -2607,7 +2791,7 @@ var __dirname = dirname(__filename);
|
|
|
2607
2791
|
var pkg = JSON.parse(
|
|
2608
2792
|
readFileSync(join3(__dirname, "../package.json"), "utf-8")
|
|
2609
2793
|
);
|
|
2610
|
-
var program = new
|
|
2794
|
+
var program = new Command23();
|
|
2611
2795
|
program.name("notion").description("Notion CLI \u2014 read Notion pages and databases from the terminal").version(pkg.version);
|
|
2612
2796
|
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");
|
|
2613
2797
|
program.configureOutput({
|
|
@@ -2628,7 +2812,7 @@ program.hook("preAction", (thisCommand) => {
|
|
|
2628
2812
|
setOutputMode("md");
|
|
2629
2813
|
}
|
|
2630
2814
|
});
|
|
2631
|
-
var authCmd = new
|
|
2815
|
+
var authCmd = new Command23("auth").description("manage Notion authentication");
|
|
2632
2816
|
authCmd.action(authDefaultAction(authCmd));
|
|
2633
2817
|
authCmd.addCommand(loginCommand());
|
|
2634
2818
|
authCmd.addCommand(logoutCommand());
|
|
@@ -2638,7 +2822,7 @@ authCmd.addCommand(profileUseCommand());
|
|
|
2638
2822
|
authCmd.addCommand(profileRemoveCommand());
|
|
2639
2823
|
program.addCommand(authCmd);
|
|
2640
2824
|
program.addCommand(initCommand(), { hidden: true });
|
|
2641
|
-
var profileCmd = new
|
|
2825
|
+
var profileCmd = new Command23("profile").description(
|
|
2642
2826
|
"manage authentication profiles"
|
|
2643
2827
|
);
|
|
2644
2828
|
profileCmd.addCommand(profileListCommand());
|
|
@@ -2656,7 +2840,8 @@ program.addCommand(appendCommand());
|
|
|
2656
2840
|
program.addCommand(createPageCommand());
|
|
2657
2841
|
program.addCommand(editPageCommand());
|
|
2658
2842
|
program.addCommand(updateCommand());
|
|
2659
|
-
|
|
2843
|
+
program.addCommand(archiveCommand());
|
|
2844
|
+
var dbCmd = new Command23("db").description("Database operations");
|
|
2660
2845
|
dbCmd.addCommand(dbSchemaCommand());
|
|
2661
2846
|
dbCmd.addCommand(dbQueryCommand());
|
|
2662
2847
|
program.addCommand(dbCmd);
|