@andrzejchm/notion-cli 0.8.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 CHANGED
@@ -37,7 +37,7 @@ Fetch and follow instructions from https://raw.githubusercontent.com/andrzejchm/
37
37
  - **`notion db query`** — filter, sort, and select columns from any database
38
38
  - **`notion append`** — append markdown content to an existing page
39
39
  - **`notion create-page`** — create a new page with markdown body, prints URL to stdout
40
- - **`notion comment`** — add a comment to a page
40
+ - **`notion comment`** — add a comment to a page, block, or discussion thread
41
41
  - **Agent-friendly** — plain text tables by default; `--json` for machine-readable output
42
42
  - **Flexible auth** — interactive setup or `NOTION_API_TOKEN` env var
43
43
  - **Accepts URLs** — pass full Notion URLs anywhere an ID is expected
@@ -70,6 +70,12 @@ my-summarize-command | notion create-page --parent "$PAGE_ID" --title "Auto Summ
70
70
  # Add a comment to a page
71
71
  notion comment "$PAGE_ID" -m "Reviewed and approved."
72
72
 
73
+ # Reply to an existing discussion thread
74
+ notion comment --reply-to "$DISCUSSION_ID" -m "Agreed, let's proceed."
75
+
76
+ # Comment on a specific block
77
+ notion comment --block "$BLOCK_ID" -m "This section needs revision."
78
+
73
79
  # List everything your integration can access
74
80
  notion ls
75
81
  ```
@@ -85,15 +91,15 @@ notion ls
85
91
  | `notion auth status` | Show current auth state |
86
92
  | `notion auth list` | List all saved profiles |
87
93
  | `notion auth use <name>` | Switch the active profile |
88
- | `notion search <query>` | Search pages and databases by title |
89
- | `notion ls` | List all accessible pages and databases |
94
+ | `notion search <query>` | Search pages and databases by title (`--sort asc\|desc`) |
95
+ | `notion ls` | List all accessible pages and databases (`--sort asc\|desc`) |
90
96
  | `notion open <id\|url>` | Open a page in your browser |
91
97
  | `notion read <id\|url>` | Read a page as markdown |
92
98
  | `notion db schema <id\|url>` | Show database property schema and valid values |
93
99
  | `notion db query <id\|url>` | Query database entries with filtering and sorting |
94
100
  | `notion users` | List workspace members |
95
101
  | `notion comments <id\|url>` | Read page comments |
96
- | `notion comment <id\|url> -m <text>` | Add a comment to a page |
102
+ | `notion comment [id\|url] -m <text>` | Add a comment to a page, block, or thread |
97
103
  | `notion append <id\|url> -m <markdown>` | Append markdown blocks to a page |
98
104
  | `notion edit-page <id\|url> --find <old> --replace <new>` | Search-and-replace text on a page |
99
105
  | `notion edit-page <id\|url> -m <markdown>` | Replace entire page content |
@@ -102,6 +108,15 @@ notion ls
102
108
  | `notion archive <id\|url>` | Archive (trash) a page |
103
109
  | `notion completion bash\|zsh\|fish` | Install shell tab completion |
104
110
 
111
+ ### `notion search` / `notion ls` flags
112
+
113
+ | Flag | Example | Description |
114
+ |------|---------|-------------|
115
+ | `--sort` | `--sort desc` | Sort by last edited time (`asc` or `desc`) |
116
+ | `--type` | `--type page` | Filter by object type (`page` or `database`) |
117
+ | `--cursor` | `--cursor <cursor>` | Pagination cursor from a previous `--next` hint |
118
+ | `--json` | `--json` | Force JSON output |
119
+
105
120
  ### `notion db query` flags
106
121
 
107
122
  | Flag | Example | Description |
package/dist/cli.js CHANGED
@@ -543,23 +543,25 @@ function reportTokenSource(source) {
543
543
  }
544
544
 
545
545
  // src/services/write.service.ts
546
- async function addComment(client, pageId, text, options = {}) {
547
- await client.comments.create({
548
- parent: { page_id: pageId },
549
- rich_text: [
550
- {
551
- type: "text",
552
- text: { content: text, link: null },
553
- annotations: {
554
- bold: false,
555
- italic: false,
556
- strikethrough: false,
557
- underline: false,
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
  }
@@ -1316,20 +1318,50 @@ function statusCommand() {
1316
1318
 
1317
1319
  // src/commands/comment-add.ts
1318
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
+ }
1319
1347
  function commentAddCommand() {
1320
1348
  const cmd = new Command6("comment");
1321
- cmd.description("add a comment to a Notion page").argument("<id/url>", "Notion page ID or URL").requiredOption("-m, --message <text>", "comment text to post").action(
1322
- withErrorHandling(async (idOrUrl, opts) => {
1323
- const { token, source } = await resolveToken();
1324
- reportTokenSource(source);
1325
- const client = createNotionClient(token);
1326
- const id = parseNotionId(idOrUrl);
1327
- const uuid = toUuid(id);
1328
- await addComment(client, uuid, opts.message, {
1329
- asUser: source === "oauth"
1330
- });
1331
- process.stdout.write("Comment added.\n");
1332
- })
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
+ )
1333
1365
  );
1334
1366
  return cmd;
1335
1367
  }
@@ -1352,6 +1384,14 @@ async function paginateResults(fetcher) {
1352
1384
  }
1353
1385
 
1354
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
+ }
1355
1395
  function commentsCommand() {
1356
1396
  const cmd = new Command7("comments");
1357
1397
  cmd.description("list comments on a Notion page").argument("<id/url>", "Notion page ID or URL").option("--json", "output as JSON").action(
@@ -1374,10 +1414,16 @@ function commentsCommand() {
1374
1414
  return [
1375
1415
  comment.created_time.split("T")[0],
1376
1416
  `${comment.created_by.id.slice(0, 8)}...`,
1417
+ comment.discussion_id.slice(0, 8),
1418
+ formatParent(comment.parent),
1377
1419
  text.slice(0, 80) + (text.length > 80 ? "\u2026" : "")
1378
1420
  ];
1379
1421
  });
1380
- printOutput(comments, ["DATE", "AUTHOR ID", "COMMENT"], rows);
1422
+ printOutput(
1423
+ comments,
1424
+ ["DATE", "AUTHOR ID", "DISCUSSION", "PARENT", "COMMENT"],
1425
+ rows
1426
+ );
1381
1427
  })
1382
1428
  );
1383
1429
  return cmd;
@@ -2212,6 +2258,15 @@ function lsCommand() {
2212
2258
  }
2213
2259
  return val;
2214
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
+ }
2215
2270
  ).option(
2216
2271
  "--cursor <cursor>",
2217
2272
  "start from this pagination cursor (from a previous --next hint)"
@@ -2225,6 +2280,10 @@ function lsCommand() {
2225
2280
  reportTokenSource(source);
2226
2281
  const notion = createNotionClient(token);
2227
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,
2228
2287
  start_cursor: opts.cursor,
2229
2288
  page_size: 20
2230
2289
  });
@@ -2564,6 +2623,15 @@ function searchCommand() {
2564
2623
  }
2565
2624
  return val;
2566
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
+ }
2567
2635
  ).option(
2568
2636
  "--cursor <cursor>",
2569
2637
  "start from this pagination cursor (from a previous --next hint)"
@@ -2579,6 +2647,10 @@ function searchCommand() {
2579
2647
  const response = await notion.search({
2580
2648
  query,
2581
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,
2582
2654
  start_cursor: opts.cursor,
2583
2655
  page_size: 20
2584
2656
  });