@andrzejchm/notion-cli 0.3.0 → 0.4.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 +102 -35
- package/dist/cli.js.map +1 -1
- package/docs/README.agents.md +1 -2
- 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 Command20 } from "commander";
|
|
8
8
|
|
|
9
9
|
// src/commands/append.ts
|
|
10
10
|
import { Command } from "commander";
|
|
@@ -692,26 +692,26 @@ function createNotionClient(token) {
|
|
|
692
692
|
var NOTION_ID_REGEX = /^[0-9a-f]{32}$/i;
|
|
693
693
|
var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
694
694
|
var NOTION_URL_REGEX = /https?:\/\/(?:[a-zA-Z0-9-]+\.)?notion\.(?:so|site)\/.*?([0-9a-f]{32})(?:[?#]|$)/i;
|
|
695
|
-
function throwInvalidId(
|
|
695
|
+
function throwInvalidId(input3) {
|
|
696
696
|
throw new CliError(
|
|
697
697
|
ErrorCodes.INVALID_ID,
|
|
698
|
-
`Cannot parse Notion ID from: ${
|
|
698
|
+
`Cannot parse Notion ID from: ${input3}`,
|
|
699
699
|
"Provide a valid Notion URL or page/database ID"
|
|
700
700
|
);
|
|
701
701
|
}
|
|
702
|
-
function parseNotionId(
|
|
703
|
-
if (!
|
|
704
|
-
if (NOTION_ID_REGEX.test(
|
|
705
|
-
return
|
|
702
|
+
function parseNotionId(input3) {
|
|
703
|
+
if (!input3) throwInvalidId(input3);
|
|
704
|
+
if (NOTION_ID_REGEX.test(input3)) {
|
|
705
|
+
return input3.toLowerCase();
|
|
706
706
|
}
|
|
707
|
-
if (UUID_REGEX.test(
|
|
708
|
-
return
|
|
707
|
+
if (UUID_REGEX.test(input3)) {
|
|
708
|
+
return input3.replace(/-/g, "").toLowerCase();
|
|
709
709
|
}
|
|
710
|
-
const urlMatch = NOTION_URL_REGEX.exec(
|
|
710
|
+
const urlMatch = NOTION_URL_REGEX.exec(input3);
|
|
711
711
|
if (urlMatch) {
|
|
712
712
|
return urlMatch[1].toLowerCase();
|
|
713
713
|
}
|
|
714
|
-
throwInvalidId(
|
|
714
|
+
throwInvalidId(input3);
|
|
715
715
|
}
|
|
716
716
|
function toUuid(id) {
|
|
717
717
|
return `${id.slice(0, 8)}-${id.slice(8, 12)}-${id.slice(12, 16)}-${id.slice(16, 20)}-${id.slice(20)}`;
|
|
@@ -825,7 +825,7 @@ function authDefaultAction(authCmd2) {
|
|
|
825
825
|
}
|
|
826
826
|
|
|
827
827
|
// src/commands/auth/login.ts
|
|
828
|
-
import { select } from "@inquirer/prompts";
|
|
828
|
+
import { input as input2, select } from "@inquirer/prompts";
|
|
829
829
|
import { Command as Command3 } from "commander";
|
|
830
830
|
|
|
831
831
|
// src/oauth/oauth-flow.ts
|
|
@@ -924,6 +924,13 @@ Paste the full redirect URL here (${OAUTH_REDIRECT_URI}?code=...):
|
|
|
924
924
|
});
|
|
925
925
|
});
|
|
926
926
|
}
|
|
927
|
+
function closeServer(ctx, cb) {
|
|
928
|
+
for (const socket of ctx.sockets) {
|
|
929
|
+
socket.destroy();
|
|
930
|
+
}
|
|
931
|
+
ctx.sockets.clear();
|
|
932
|
+
ctx.server.close(cb);
|
|
933
|
+
}
|
|
927
934
|
function handleCallbackRequest(req, res, ctx) {
|
|
928
935
|
if (ctx.settled) {
|
|
929
936
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
@@ -944,7 +951,7 @@ function handleCallbackRequest(req, res, ctx) {
|
|
|
944
951
|
"<html><body><h1>Access Denied</h1><p>You cancelled the Notion OAuth request. You can close this tab.</p></body></html>"
|
|
945
952
|
);
|
|
946
953
|
if (ctx.timeoutHandle) clearTimeout(ctx.timeoutHandle);
|
|
947
|
-
ctx
|
|
954
|
+
closeServer(ctx, () => {
|
|
948
955
|
ctx.reject(
|
|
949
956
|
new CliError(
|
|
950
957
|
ErrorCodes.AUTH_INVALID,
|
|
@@ -967,7 +974,7 @@ function handleCallbackRequest(req, res, ctx) {
|
|
|
967
974
|
"<html><body><h1>Security Error</h1><p>State mismatch \u2014 possible CSRF attempt. You can close this tab.</p></body></html>"
|
|
968
975
|
);
|
|
969
976
|
if (ctx.timeoutHandle) clearTimeout(ctx.timeoutHandle);
|
|
970
|
-
ctx
|
|
977
|
+
closeServer(ctx, () => {
|
|
971
978
|
ctx.reject(
|
|
972
979
|
new CliError(
|
|
973
980
|
ErrorCodes.AUTH_INVALID,
|
|
@@ -984,7 +991,7 @@ function handleCallbackRequest(req, res, ctx) {
|
|
|
984
991
|
"<html><body><h1>Authenticated!</h1><p>You can close this tab and return to the terminal.</p></body></html>"
|
|
985
992
|
);
|
|
986
993
|
if (ctx.timeoutHandle) clearTimeout(ctx.timeoutHandle);
|
|
987
|
-
ctx
|
|
994
|
+
closeServer(ctx, () => {
|
|
988
995
|
ctx.resolve({ code, state: returnedState });
|
|
989
996
|
});
|
|
990
997
|
} catch {
|
|
@@ -1006,12 +1013,17 @@ async function runOAuthFlow(options) {
|
|
|
1006
1013
|
resolve,
|
|
1007
1014
|
reject,
|
|
1008
1015
|
// assigned below after server is created
|
|
1009
|
-
server: null
|
|
1016
|
+
server: null,
|
|
1017
|
+
sockets: /* @__PURE__ */ new Set()
|
|
1010
1018
|
};
|
|
1011
1019
|
const server = createServer(
|
|
1012
1020
|
(req, res) => handleCallbackRequest(req, res, ctx)
|
|
1013
1021
|
);
|
|
1014
1022
|
ctx.server = server;
|
|
1023
|
+
server.on("connection", (socket) => {
|
|
1024
|
+
ctx.sockets.add(socket);
|
|
1025
|
+
socket.once("close", () => ctx.sockets.delete(socket));
|
|
1026
|
+
});
|
|
1015
1027
|
server.on("error", (err) => {
|
|
1016
1028
|
if (ctx.settled) return;
|
|
1017
1029
|
ctx.settled = true;
|
|
@@ -1045,7 +1057,7 @@ Waiting for callback (up to 120 seconds)...
|
|
|
1045
1057
|
ctx.timeoutHandle = setTimeout(() => {
|
|
1046
1058
|
if (ctx.settled) return;
|
|
1047
1059
|
ctx.settled = true;
|
|
1048
|
-
|
|
1060
|
+
closeServer(ctx, () => {
|
|
1049
1061
|
reject(
|
|
1050
1062
|
new CliError(
|
|
1051
1063
|
ErrorCodes.AUTH_INVALID,
|
|
@@ -1188,19 +1200,44 @@ function loginCommand() {
|
|
|
1188
1200
|
]
|
|
1189
1201
|
});
|
|
1190
1202
|
if (method === "oauth") {
|
|
1191
|
-
let profileName = opts.profile;
|
|
1192
|
-
if (!profileName) {
|
|
1193
|
-
const config = await readGlobalConfig();
|
|
1194
|
-
profileName = config.active_profile ?? "default";
|
|
1195
|
-
}
|
|
1196
1203
|
const result = await runOAuthFlow({ manual: opts.manual });
|
|
1197
1204
|
const response = await exchangeCode(result.code);
|
|
1198
|
-
await saveOAuthTokens(profileName, response);
|
|
1199
1205
|
const userName = response.owner?.user?.name ?? "unknown user";
|
|
1200
1206
|
const workspaceName = response.workspace_name ?? "unknown workspace";
|
|
1207
|
+
const config = await readGlobalConfig();
|
|
1208
|
+
const existingProfiles = config.profiles ?? {};
|
|
1209
|
+
let profileName = opts.profile;
|
|
1210
|
+
if (!profileName) {
|
|
1211
|
+
const suggested = workspaceName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "default";
|
|
1212
|
+
profileName = await input2({
|
|
1213
|
+
message: "Profile name to save this account under:",
|
|
1214
|
+
default: suggested
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
const isUpdate = Boolean(existingProfiles[profileName]);
|
|
1218
|
+
const isFirst = Object.keys(existingProfiles).length === 0;
|
|
1219
|
+
if (isUpdate) {
|
|
1220
|
+
stderrWrite(dim(`Updating existing profile "${profileName}"...`));
|
|
1221
|
+
}
|
|
1222
|
+
await saveOAuthTokens(profileName, response);
|
|
1223
|
+
if (isFirst) {
|
|
1224
|
+
const updated = await readGlobalConfig();
|
|
1225
|
+
await writeGlobalConfig({
|
|
1226
|
+
...updated,
|
|
1227
|
+
active_profile: profileName
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1201
1230
|
stderrWrite(
|
|
1202
1231
|
success(`\u2713 Logged in as ${userName} to workspace ${workspaceName}`)
|
|
1203
1232
|
);
|
|
1233
|
+
stderrWrite(dim(`Saved as profile "${profileName}".`));
|
|
1234
|
+
if (!isFirst && !isUpdate) {
|
|
1235
|
+
stderrWrite(
|
|
1236
|
+
dim(
|
|
1237
|
+
`Run "notion auth use ${profileName}" to switch to this profile.`
|
|
1238
|
+
)
|
|
1239
|
+
);
|
|
1240
|
+
}
|
|
1204
1241
|
stderrWrite(
|
|
1205
1242
|
dim(
|
|
1206
1243
|
"Your comments and pages will now be attributed to your Notion account."
|
|
@@ -2053,10 +2090,38 @@ function profileListCommand() {
|
|
|
2053
2090
|
return cmd;
|
|
2054
2091
|
}
|
|
2055
2092
|
|
|
2056
|
-
// src/commands/profile/
|
|
2093
|
+
// src/commands/profile/remove.ts
|
|
2057
2094
|
import { Command as Command15 } from "commander";
|
|
2095
|
+
function profileRemoveCommand() {
|
|
2096
|
+
const cmd = new Command15("remove");
|
|
2097
|
+
cmd.description("remove an authentication profile").argument("<name>", "profile name to remove").action(
|
|
2098
|
+
withErrorHandling(async (name) => {
|
|
2099
|
+
const config = await readGlobalConfig();
|
|
2100
|
+
const profiles = { ...config.profiles ?? {} };
|
|
2101
|
+
if (!profiles[name]) {
|
|
2102
|
+
throw new CliError(
|
|
2103
|
+
ErrorCodes.AUTH_PROFILE_NOT_FOUND,
|
|
2104
|
+
`Profile "${name}" not found.`,
|
|
2105
|
+
`Run "notion auth list" to see available profiles`
|
|
2106
|
+
);
|
|
2107
|
+
}
|
|
2108
|
+
delete profiles[name];
|
|
2109
|
+
const newActiveProfile = config.active_profile === name ? void 0 : config.active_profile;
|
|
2110
|
+
await writeGlobalConfig({
|
|
2111
|
+
...config,
|
|
2112
|
+
profiles,
|
|
2113
|
+
active_profile: newActiveProfile
|
|
2114
|
+
});
|
|
2115
|
+
stderrWrite(success(`Profile "${name}" removed.`));
|
|
2116
|
+
})
|
|
2117
|
+
);
|
|
2118
|
+
return cmd;
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
// src/commands/profile/use.ts
|
|
2122
|
+
import { Command as Command16 } from "commander";
|
|
2058
2123
|
function profileUseCommand() {
|
|
2059
|
-
const cmd = new
|
|
2124
|
+
const cmd = new Command16("use");
|
|
2060
2125
|
cmd.description("switch the active profile").argument("<name>", "profile name to activate").action(
|
|
2061
2126
|
withErrorHandling(async (name) => {
|
|
2062
2127
|
const config = await readGlobalConfig();
|
|
@@ -2079,7 +2144,7 @@ function profileUseCommand() {
|
|
|
2079
2144
|
}
|
|
2080
2145
|
|
|
2081
2146
|
// src/commands/read.ts
|
|
2082
|
-
import { Command as
|
|
2147
|
+
import { Command as Command17 } from "commander";
|
|
2083
2148
|
|
|
2084
2149
|
// src/blocks/rich-text.ts
|
|
2085
2150
|
function richTextToMd(richText) {
|
|
@@ -2501,7 +2566,7 @@ async function fetchPageWithBlocks(client, pageId) {
|
|
|
2501
2566
|
|
|
2502
2567
|
// src/commands/read.ts
|
|
2503
2568
|
function readCommand() {
|
|
2504
|
-
return new
|
|
2569
|
+
return new Command17("read").description("Read a Notion page as markdown").argument("<id>", "Notion page ID or URL").option("--json", "Output raw JSON instead of markdown").option("--md", "Output raw markdown (no terminal styling)").action(
|
|
2505
2570
|
withErrorHandling(
|
|
2506
2571
|
async (id, options) => {
|
|
2507
2572
|
const { token } = await resolveToken();
|
|
@@ -2528,7 +2593,7 @@ function readCommand() {
|
|
|
2528
2593
|
|
|
2529
2594
|
// src/commands/search.ts
|
|
2530
2595
|
import { isFullPageOrDataSource as isFullPageOrDataSource2 } from "@notionhq/client";
|
|
2531
|
-
import { Command as
|
|
2596
|
+
import { Command as Command18 } from "commander";
|
|
2532
2597
|
function getTitle2(item) {
|
|
2533
2598
|
if (item.object === "data_source") {
|
|
2534
2599
|
return item.title.map((t) => t.plain_text).join("") || "(untitled)";
|
|
@@ -2548,7 +2613,7 @@ function displayType2(item) {
|
|
|
2548
2613
|
return item.object === "data_source" ? "database" : item.object;
|
|
2549
2614
|
}
|
|
2550
2615
|
function searchCommand() {
|
|
2551
|
-
const cmd = new
|
|
2616
|
+
const cmd = new Command18("search");
|
|
2552
2617
|
cmd.description("search Notion workspace by keyword").argument("<query>", "search keyword").option(
|
|
2553
2618
|
"--type <type>",
|
|
2554
2619
|
"filter by object type (page or database)",
|
|
@@ -2606,7 +2671,7 @@ function searchCommand() {
|
|
|
2606
2671
|
}
|
|
2607
2672
|
|
|
2608
2673
|
// src/commands/users.ts
|
|
2609
|
-
import { Command as
|
|
2674
|
+
import { Command as Command19 } from "commander";
|
|
2610
2675
|
function getEmailOrWorkspace(user) {
|
|
2611
2676
|
if (user.type === "person") {
|
|
2612
2677
|
return user.person.email ?? "\u2014";
|
|
@@ -2618,7 +2683,7 @@ function getEmailOrWorkspace(user) {
|
|
|
2618
2683
|
return "\u2014";
|
|
2619
2684
|
}
|
|
2620
2685
|
function usersCommand() {
|
|
2621
|
-
const cmd = new
|
|
2686
|
+
const cmd = new Command19("users");
|
|
2622
2687
|
cmd.description("list all users in the workspace").option("--json", "output as JSON").action(
|
|
2623
2688
|
withErrorHandling(async (opts) => {
|
|
2624
2689
|
if (opts.json) setOutputMode("json");
|
|
@@ -2649,7 +2714,7 @@ var __dirname = dirname(__filename);
|
|
|
2649
2714
|
var pkg = JSON.parse(
|
|
2650
2715
|
readFileSync(join3(__dirname, "../package.json"), "utf-8")
|
|
2651
2716
|
);
|
|
2652
|
-
var program = new
|
|
2717
|
+
var program = new Command20();
|
|
2653
2718
|
program.name("notion").description("Notion CLI \u2014 read Notion pages and databases from the terminal").version(pkg.version);
|
|
2654
2719
|
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");
|
|
2655
2720
|
program.configureOutput({
|
|
@@ -2670,20 +2735,22 @@ program.hook("preAction", (thisCommand) => {
|
|
|
2670
2735
|
setOutputMode("md");
|
|
2671
2736
|
}
|
|
2672
2737
|
});
|
|
2673
|
-
var authCmd = new
|
|
2738
|
+
var authCmd = new Command20("auth").description("manage Notion authentication");
|
|
2674
2739
|
authCmd.action(authDefaultAction(authCmd));
|
|
2675
2740
|
authCmd.addCommand(loginCommand());
|
|
2676
2741
|
authCmd.addCommand(logoutCommand());
|
|
2677
2742
|
authCmd.addCommand(statusCommand());
|
|
2678
2743
|
authCmd.addCommand(profileListCommand());
|
|
2679
2744
|
authCmd.addCommand(profileUseCommand());
|
|
2745
|
+
authCmd.addCommand(profileRemoveCommand());
|
|
2680
2746
|
program.addCommand(authCmd);
|
|
2681
2747
|
program.addCommand(initCommand(), { hidden: true });
|
|
2682
|
-
var profileCmd = new
|
|
2748
|
+
var profileCmd = new Command20("profile").description(
|
|
2683
2749
|
"manage authentication profiles"
|
|
2684
2750
|
);
|
|
2685
2751
|
profileCmd.addCommand(profileListCommand());
|
|
2686
2752
|
profileCmd.addCommand(profileUseCommand());
|
|
2753
|
+
profileCmd.addCommand(profileRemoveCommand());
|
|
2687
2754
|
program.addCommand(profileCmd, { hidden: true });
|
|
2688
2755
|
program.addCommand(searchCommand());
|
|
2689
2756
|
program.addCommand(lsCommand());
|
|
@@ -2694,7 +2761,7 @@ program.addCommand(readCommand());
|
|
|
2694
2761
|
program.addCommand(commentAddCommand());
|
|
2695
2762
|
program.addCommand(appendCommand());
|
|
2696
2763
|
program.addCommand(createPageCommand());
|
|
2697
|
-
var dbCmd = new
|
|
2764
|
+
var dbCmd = new Command20("db").description("Database operations");
|
|
2698
2765
|
dbCmd.addCommand(dbSchemaCommand());
|
|
2699
2766
|
dbCmd.addCommand(dbQueryCommand());
|
|
2700
2767
|
program.addCommand(dbCmd);
|