@andrzejchm/notion-cli 0.12.0 → 0.13.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
@@ -4,7 +4,7 @@
4
4
  import { readFileSync as readFileSync2 } from "fs";
5
5
  import { dirname as dirname2, join as join4 } from "path";
6
6
  import { fileURLToPath } from "url";
7
- import { Command as Command27 } from "commander";
7
+ import { Command as Command30 } from "commander";
8
8
 
9
9
  // src/commands/append.ts
10
10
  import { Command } from "commander";
@@ -451,9 +451,24 @@ function isNotionValidationError(error2) {
451
451
  }
452
452
 
453
453
  // src/notion/client.ts
454
- import { APIErrorCode, Client, isNotionClientError } from "@notionhq/client";
454
+ import {
455
+ APIErrorCode,
456
+ Client,
457
+ isNotionClientError,
458
+ LogLevel
459
+ } from "@notionhq/client";
460
+ var stderrLogger = (level, message, extraInfo) => {
461
+ process.stderr.write(
462
+ `[notion-sdk] ${level}: ${message} ${JSON.stringify(extraInfo)}
463
+ `
464
+ );
465
+ };
455
466
  async function validateToken(token) {
456
- const notion = new Client({ auth: token });
467
+ const notion = new Client({
468
+ auth: token,
469
+ logLevel: LogLevel.WARN,
470
+ logger: stderrLogger
471
+ });
457
472
  try {
458
473
  const me = await notion.users.me({});
459
474
  const bot = me;
@@ -473,7 +488,12 @@ async function validateToken(token) {
473
488
  }
474
489
  }
475
490
  function createNotionClient(token) {
476
- return new Client({ auth: token, timeoutMs: 12e4 });
491
+ return new Client({
492
+ auth: token,
493
+ timeoutMs: 12e4,
494
+ logLevel: LogLevel.WARN,
495
+ logger: stderrLogger
496
+ });
477
497
  }
478
498
 
479
499
  // src/notion/url-parser.ts
@@ -1092,25 +1112,47 @@ function printWithPager(text) {
1092
1112
  }
1093
1113
 
1094
1114
  // src/commands/archive.ts
1115
+ async function archiveEntity(client, uuid) {
1116
+ try {
1117
+ const result2 = await client.pages.update({
1118
+ page_id: uuid,
1119
+ in_trash: true
1120
+ });
1121
+ return { result: result2, kind: "page" };
1122
+ } catch {
1123
+ }
1124
+ try {
1125
+ const result2 = await client.dataSources.update({
1126
+ data_source_id: uuid,
1127
+ in_trash: true
1128
+ });
1129
+ return { result: result2, kind: "data_source" };
1130
+ } catch {
1131
+ }
1132
+ const result = await client.databases.update({
1133
+ database_id: uuid,
1134
+ in_trash: true
1135
+ });
1136
+ return { result, kind: "database" };
1137
+ }
1095
1138
  function archiveCommand() {
1096
1139
  const cmd = new Command2("archive");
1097
- cmd.description("archive (trash) a Notion page").argument("<id/url>", "Notion page ID or URL").action(
1140
+ cmd.description("archive (trash) a Notion page or database").argument("<id/url>", "Notion page or database ID/URL").action(
1098
1141
  withErrorHandling(async (idOrUrl) => {
1099
1142
  const { token, source } = await resolveToken();
1100
1143
  reportTokenSource(source);
1101
1144
  const client = createNotionClient(token);
1102
1145
  const id = parseNotionId(idOrUrl);
1103
1146
  const uuid = toUuid(id);
1104
- const updatedPage = await client.pages.update({
1105
- page_id: uuid,
1106
- archived: true
1107
- });
1147
+ const { result, kind } = await archiveEntity(client, uuid);
1108
1148
  const mode = getOutputMode();
1109
1149
  if (mode === "json") {
1110
- process.stdout.write(`${formatJSON(updatedPage)}
1150
+ process.stdout.write(`${formatJSON(result)}
1111
1151
  `);
1112
1152
  } else {
1113
- process.stdout.write("Page archived.\n");
1153
+ const label = kind === "page" ? "Page" : "Database";
1154
+ process.stdout.write(`${label} archived.
1155
+ `);
1114
1156
  }
1115
1157
  })
1116
1158
  );
@@ -1858,8 +1900,9 @@ import { Command as Command10 } from "commander";
1858
1900
  // src/services/database.service.ts
1859
1901
  import { isFullPage } from "@notionhq/client";
1860
1902
  async function fetchDatabaseSchema(client, dbId) {
1861
- const ds = await client.dataSources.retrieve({ data_source_id: dbId });
1862
- const title = "title" in ds ? ds.title.map((rt) => rt.plain_text).join("") || dbId : dbId;
1903
+ const resolvedId = await resolveDataSourceId(client, dbId);
1904
+ const ds = await client.dataSources.retrieve({ data_source_id: resolvedId });
1905
+ const title = "title" in ds ? ds.title.map((rt) => rt.plain_text).join("") || resolvedId : resolvedId;
1863
1906
  const properties = {};
1864
1907
  if ("properties" in ds) {
1865
1908
  for (const [name, prop] of Object.entries(ds.properties)) {
@@ -1878,8 +1921,8 @@ async function fetchDatabaseSchema(client, dbId) {
1878
1921
  properties[name] = config;
1879
1922
  }
1880
1923
  }
1881
- const databaseId = "parent" in ds && ds.parent && typeof ds.parent === "object" && "database_id" in ds.parent ? ds.parent.database_id : dbId;
1882
- return { id: dbId, databaseId, title, properties };
1924
+ const databaseId = "parent" in ds && ds.parent && typeof ds.parent === "object" && "database_id" in ds.parent ? ds.parent.database_id : resolvedId;
1925
+ return { id: resolvedId, databaseId, title, properties };
1883
1926
  }
1884
1927
  async function queryDatabase(client, dbId, opts = {}) {
1885
1928
  const rawPages = await paginateResults(
@@ -2099,6 +2142,81 @@ async function resolveDataSourceId(client, idOrUrl) {
2099
2142
  `Could not find database or data source: ${id}`
2100
2143
  );
2101
2144
  }
2145
+ function buildDatabaseUpdatePayload(opts, schema) {
2146
+ const properties = {};
2147
+ for (const def of opts.addProps) {
2148
+ const { name, config } = parsePropertyDefinition(def);
2149
+ properties[name] = config;
2150
+ }
2151
+ for (const name of opts.removeProps) {
2152
+ properties[name] = null;
2153
+ }
2154
+ for (const raw of opts.renameProps) {
2155
+ const colonIdx = raw.indexOf(":");
2156
+ if (colonIdx === -1) {
2157
+ throw new CliError(
2158
+ ErrorCodes.INVALID_ARG,
2159
+ `Invalid rename format: "${raw}"`,
2160
+ 'Use format: --rename-prop "OldName:NewName"'
2161
+ );
2162
+ }
2163
+ const oldName = raw.slice(0, colonIdx).trim();
2164
+ const newName = raw.slice(colonIdx + 1).trim();
2165
+ const propConfig = schema.properties[oldName];
2166
+ if (!propConfig) {
2167
+ const available = Object.keys(schema.properties).join(", ");
2168
+ throw new CliError(
2169
+ ErrorCodes.INVALID_ARG,
2170
+ `Property "${oldName}" not found in schema`,
2171
+ `Available properties: ${available}`
2172
+ );
2173
+ }
2174
+ properties[propConfig.id] = { name: newName };
2175
+ }
2176
+ for (const raw of opts.setOptions) {
2177
+ const colonIdx = raw.indexOf(":");
2178
+ if (colonIdx === -1) {
2179
+ throw new CliError(
2180
+ ErrorCodes.INVALID_ARG,
2181
+ `Invalid set-options format: "${raw}"`,
2182
+ 'Use format: --set-options "PropertyName:opt1,opt2,opt3"'
2183
+ );
2184
+ }
2185
+ const propName = raw.slice(0, colonIdx).trim();
2186
+ const optionsStr = raw.slice(colonIdx + 1).trim();
2187
+ const propConfig = schema.properties[propName];
2188
+ if (!propConfig) {
2189
+ const available = Object.keys(schema.properties).join(", ");
2190
+ throw new CliError(
2191
+ ErrorCodes.INVALID_ARG,
2192
+ `Property "${propName}" not found in schema`,
2193
+ `Available properties: ${available}`
2194
+ );
2195
+ }
2196
+ if (propConfig.type !== "select" && propConfig.type !== "multi_select") {
2197
+ throw new CliError(
2198
+ ErrorCodes.INVALID_ARG,
2199
+ `Property "${propName}" is of type "${propConfig.type}" \u2014 only select and multi_select properties support --set-options`
2200
+ );
2201
+ }
2202
+ const options = optionsStr.split(",").map((opt) => ({ name: opt.trim() }));
2203
+ properties[propName] = { [propConfig.type]: { options } };
2204
+ }
2205
+ const payload = {};
2206
+ if (opts.title !== void 0) {
2207
+ payload.title = [{ type: "text", text: { content: opts.title } }];
2208
+ }
2209
+ if (Object.keys(properties).length > 0) {
2210
+ payload.properties = properties;
2211
+ }
2212
+ return payload;
2213
+ }
2214
+ async function updateDatabaseSchema(client, dataSourceId, payload) {
2215
+ return client.dataSources.update({
2216
+ data_source_id: dataSourceId,
2217
+ ...payload
2218
+ });
2219
+ }
2102
2220
  function displayPropertyValue(prop) {
2103
2221
  switch (prop.type) {
2104
2222
  case "title":
@@ -2506,14 +2624,210 @@ function dbSchemaCommand() {
2506
2624
  );
2507
2625
  }
2508
2626
 
2509
- // src/commands/edit-page.ts
2627
+ // src/commands/db/update.ts
2510
2628
  import { Command as Command14 } from "commander";
2629
+ function collectProps2(val, acc) {
2630
+ acc.push(val);
2631
+ return acc;
2632
+ }
2633
+ function dbUpdateCommand() {
2634
+ return new Command14("update").description(
2635
+ "Update database schema (add/remove/rename properties, manage options)"
2636
+ ).argument("<id/url>", "database ID or URL").option(
2637
+ "--add-prop <definition>",
2638
+ 'add property (repeatable): --add-prop "Name:type:options"',
2639
+ collectProps2,
2640
+ []
2641
+ ).option(
2642
+ "--remove-prop <name>",
2643
+ "remove property (repeatable)",
2644
+ collectProps2,
2645
+ []
2646
+ ).option(
2647
+ "--rename-prop <old:new>",
2648
+ 'rename property (repeatable): --rename-prop "Old:New"',
2649
+ collectProps2,
2650
+ []
2651
+ ).option(
2652
+ "--set-options <prop:opts>",
2653
+ 'set select/multi_select options (repeatable): --set-options "Status:A,B,C"',
2654
+ collectProps2,
2655
+ []
2656
+ ).option("--title <title>", "update database title").action(
2657
+ withErrorHandling(async (id, opts) => {
2658
+ const hasOperations = opts.addProp.length > 0 || opts.removeProp.length > 0 || opts.renameProp.length > 0 || opts.setOptions.length > 0 || opts.title !== void 0;
2659
+ if (!hasOperations) {
2660
+ throw new CliError(
2661
+ ErrorCodes.INVALID_ARG,
2662
+ "No update operations specified",
2663
+ "Provide at least one of: --add-prop, --remove-prop, --rename-prop, --set-options, --title"
2664
+ );
2665
+ }
2666
+ const { token, source } = await resolveToken();
2667
+ reportTokenSource(source);
2668
+ const client = createNotionClient(token);
2669
+ const dsId = await resolveDataSourceId(client, id);
2670
+ const needsSchema = opts.renameProp.length > 0 || opts.setOptions.length > 0;
2671
+ const schema = needsSchema ? await fetchDatabaseSchema(client, dsId) : { id: dsId, databaseId: dsId, title: "", properties: {} };
2672
+ const payload = buildDatabaseUpdatePayload(
2673
+ {
2674
+ addProps: opts.addProp,
2675
+ removeProps: opts.removeProp,
2676
+ renameProps: opts.renameProp,
2677
+ setOptions: opts.setOptions,
2678
+ title: opts.title
2679
+ },
2680
+ schema
2681
+ );
2682
+ const response = await updateDatabaseSchema(client, dsId, payload);
2683
+ if (getOutputMode() === "json") {
2684
+ process.stdout.write(`${formatJSON(response)}
2685
+ `);
2686
+ return;
2687
+ }
2688
+ if ("url" in response) {
2689
+ process.stdout.write(`${response.url}
2690
+ `);
2691
+ }
2692
+ })
2693
+ );
2694
+ }
2695
+
2696
+ // src/commands/db/update-rows.ts
2697
+ import { Command as Command15 } from "commander";
2698
+ var LARGE_BATCH_WARNING_THRESHOLD = 10;
2699
+ var DEFAULT_CONCURRENCY = 3;
2700
+ function collectProps3(val, acc) {
2701
+ acc.push(val);
2702
+ return acc;
2703
+ }
2704
+ async function batchUpdate(items, fn, concurrency = DEFAULT_CONCURRENCY) {
2705
+ const results = [];
2706
+ for (let i = 0; i < items.length; i += concurrency) {
2707
+ const batch = items.slice(i, i + concurrency);
2708
+ results.push(...await Promise.all(batch.map(fn)));
2709
+ }
2710
+ return results;
2711
+ }
2712
+ function getPageTitle(page) {
2713
+ for (const prop of Object.values(page.properties)) {
2714
+ if (prop.type === "title") {
2715
+ return prop.title.map((r) => r.plain_text).join("") || page.id;
2716
+ }
2717
+ }
2718
+ return page.id;
2719
+ }
2720
+ function dbUpdateRowsCommand() {
2721
+ return new Command15("update-rows").description("Update properties on all matching rows in a database").argument("<id>", "Notion database ID or URL").option(
2722
+ "--filter <filter>",
2723
+ 'Filter rows (repeatable): --filter "Status=Done"',
2724
+ collectProps3,
2725
+ []
2726
+ ).option(
2727
+ "--prop <property=value>",
2728
+ "Set a property value on matching rows (repeatable, required)",
2729
+ collectProps3,
2730
+ []
2731
+ ).option("--dry-run", "Preview matching rows without making changes", false).action(
2732
+ withErrorHandling(async (id, opts) => {
2733
+ if (opts.prop.length === 0) {
2734
+ throw new CliError(
2735
+ ErrorCodes.INVALID_ARG,
2736
+ "No properties to update.",
2737
+ 'Provide at least one --prop "Name=Value"'
2738
+ );
2739
+ }
2740
+ const { token } = await resolveToken();
2741
+ const client = createNotionClient(token);
2742
+ const dsId = await resolveDataSourceId(client, id);
2743
+ const schema = await fetchDatabaseSchema(client, dsId);
2744
+ const filter = opts.filter.length ? buildFilter(opts.filter, schema) : void 0;
2745
+ const entries = await queryDatabase(client, dsId, { filter });
2746
+ if (opts.filter.length === 0 && entries.length > LARGE_BATCH_WARNING_THRESHOLD) {
2747
+ process.stderr.write(
2748
+ `Warning: no --filter specified \u2014 ${entries.length} rows will be updated.
2749
+ `
2750
+ );
2751
+ }
2752
+ if (opts.dryRun) {
2753
+ process.stderr.write(
2754
+ `Dry run: would update ${entries.length} row(s).
2755
+ `
2756
+ );
2757
+ const lines = entries.map((e) => `${e.id} ${getPageTitle(e.raw)}`).join("\n");
2758
+ if (entries.length > 0) {
2759
+ process.stdout.write(`${lines}
2760
+ `);
2761
+ }
2762
+ return;
2763
+ }
2764
+ const results = await batchUpdate(entries, async (entry) => {
2765
+ const title = getPageTitle(entry.raw);
2766
+ try {
2767
+ const properties = buildPropertiesPayload(
2768
+ opts.prop,
2769
+ entry.raw
2770
+ );
2771
+ await updatePageProperties(client, entry.id, properties);
2772
+ return { id: entry.id, title, success: true };
2773
+ } catch (err) {
2774
+ const message = err instanceof Error ? err.message : String(err);
2775
+ return { id: entry.id, title, success: false, error: message };
2776
+ }
2777
+ });
2778
+ const successCount = results.filter((r) => r.success).length;
2779
+ const failureCount = results.length - successCount;
2780
+ if (getOutputMode() === "json") {
2781
+ process.stdout.write(`${formatJSON(results)}
2782
+ `);
2783
+ return;
2784
+ }
2785
+ process.stdout.write(`Updated ${successCount} row(s).
2786
+ `);
2787
+ if (failureCount > 0) {
2788
+ process.stderr.write(`${failureCount} row(s) failed to update.
2789
+ `);
2790
+ for (const r of results.filter((res) => !res.success)) {
2791
+ process.stderr.write(` ${r.id} (${r.title}): ${r.error}
2792
+ `);
2793
+ }
2794
+ }
2795
+ })
2796
+ );
2797
+ }
2798
+
2799
+ // src/commands/delete-block.ts
2800
+ import { Command as Command16 } from "commander";
2801
+ function deleteBlockCommand() {
2802
+ const cmd = new Command16("delete-block");
2803
+ cmd.description("delete a Notion block by ID or URL").argument("<id/url>", "Notion block ID or URL").action(
2804
+ withErrorHandling(async (idOrUrl) => {
2805
+ const { token, source } = await resolveToken();
2806
+ reportTokenSource(source);
2807
+ const client = createNotionClient(token);
2808
+ const id = parseNotionId(idOrUrl);
2809
+ const uuid = toUuid(id);
2810
+ const deletedBlock = await client.blocks.delete({ block_id: uuid });
2811
+ const mode = getOutputMode();
2812
+ if (mode === "json") {
2813
+ process.stdout.write(`${formatJSON(deletedBlock)}
2814
+ `);
2815
+ } else {
2816
+ process.stdout.write("Block deleted.\n");
2817
+ }
2818
+ })
2819
+ );
2820
+ return cmd;
2821
+ }
2822
+
2823
+ // src/commands/edit-page.ts
2824
+ import { Command as Command17 } from "commander";
2511
2825
  function collect2(val, acc) {
2512
2826
  acc.push(val);
2513
2827
  return acc;
2514
2828
  }
2515
2829
  function editPageCommand() {
2516
- const cmd = new Command14("edit-page");
2830
+ const cmd = new Command17("edit-page");
2517
2831
  cmd.description(
2518
2832
  "replace a Notion page's content \u2014 full page or a targeted section"
2519
2833
  ).argument("<id/url>", "Notion page ID or URL").option(
@@ -2611,7 +2925,7 @@ function editPageCommand() {
2611
2925
 
2612
2926
  // src/commands/init.ts
2613
2927
  import { confirm, input as input2, password } from "@inquirer/prompts";
2614
- import { Command as Command15 } from "commander";
2928
+ import { Command as Command18 } from "commander";
2615
2929
  async function runInitFlow() {
2616
2930
  const profileName = await input2({
2617
2931
  message: "Profile name:",
@@ -2691,7 +3005,7 @@ async function runInitFlow() {
2691
3005
  stderrWrite(dim(" notion auth login"));
2692
3006
  }
2693
3007
  function initCommand() {
2694
- const cmd = new Command15("init");
3008
+ const cmd = new Command18("init");
2695
3009
  cmd.description("authenticate with Notion and save a profile").action(
2696
3010
  withErrorHandling(async () => {
2697
3011
  if (!process.stdin.isTTY) {
@@ -2709,7 +3023,7 @@ function initCommand() {
2709
3023
 
2710
3024
  // src/commands/ls.ts
2711
3025
  import { isFullPageOrDataSource } from "@notionhq/client";
2712
- import { Command as Command16 } from "commander";
3026
+ import { Command as Command19 } from "commander";
2713
3027
  function getTitle(item) {
2714
3028
  if (item.object === "data_source") {
2715
3029
  return item.title.map((t) => t.plain_text).join("") || "(untitled)";
@@ -2726,7 +3040,7 @@ function displayType(item) {
2726
3040
  return item.object === "data_source" ? "database" : item.object;
2727
3041
  }
2728
3042
  function lsCommand() {
2729
- const cmd = new Command16("ls");
3043
+ const cmd = new Command19("ls");
2730
3044
  cmd.description("list accessible Notion pages and databases").option(
2731
3045
  "--type <type>",
2732
3046
  "filter by object type (page or database)",
@@ -2775,7 +3089,11 @@ function lsCommand() {
2775
3089
  );
2776
3090
  }
2777
3091
  if (items.length === 0) {
2778
- process.stdout.write("No accessible content found\n");
3092
+ if (getOutputMode() === "json") {
3093
+ process.stdout.write("[]\n");
3094
+ } else {
3095
+ process.stdout.write("No accessible content found\n");
3096
+ }
2779
3097
  return;
2780
3098
  }
2781
3099
  const headers = ["TYPE", "TITLE", "ID", "MODIFIED"];
@@ -2800,9 +3118,9 @@ function lsCommand() {
2800
3118
  }
2801
3119
 
2802
3120
  // src/commands/move.ts
2803
- import { Command as Command17 } from "commander";
3121
+ import { Command as Command20 } from "commander";
2804
3122
  function moveCommand() {
2805
- const cmd = new Command17("move");
3123
+ const cmd = new Command20("move");
2806
3124
  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(
2807
3125
  "--to-db <id/url>",
2808
3126
  "target database parent ID or URL (resolves to data source)"
@@ -2860,10 +3178,10 @@ function moveCommand() {
2860
3178
  // src/commands/open.ts
2861
3179
  import { exec } from "child_process";
2862
3180
  import { promisify } from "util";
2863
- import { Command as Command18 } from "commander";
3181
+ import { Command as Command21 } from "commander";
2864
3182
  var execAsync = promisify(exec);
2865
3183
  function openCommand() {
2866
- const cmd = new Command18("open");
3184
+ const cmd = new Command21("open");
2867
3185
  cmd.description("open a Notion page in the default browser").argument("<id/url>", "Notion page ID or URL").action(
2868
3186
  withErrorHandling(async (idOrUrl) => {
2869
3187
  const id = parseNotionId(idOrUrl);
@@ -2879,9 +3197,9 @@ function openCommand() {
2879
3197
  }
2880
3198
 
2881
3199
  // src/commands/profile/list.ts
2882
- import { Command as Command19 } from "commander";
3200
+ import { Command as Command22 } from "commander";
2883
3201
  function profileListCommand() {
2884
- const cmd = new Command19("list");
3202
+ const cmd = new Command22("list");
2885
3203
  cmd.description("list all authentication profiles").action(
2886
3204
  withErrorHandling(async () => {
2887
3205
  const config = await readGlobalConfig();
@@ -2910,9 +3228,9 @@ function profileListCommand() {
2910
3228
  }
2911
3229
 
2912
3230
  // src/commands/profile/remove.ts
2913
- import { Command as Command20 } from "commander";
3231
+ import { Command as Command23 } from "commander";
2914
3232
  function profileRemoveCommand() {
2915
- const cmd = new Command20("remove");
3233
+ const cmd = new Command23("remove");
2916
3234
  cmd.description("remove an authentication profile").argument("<name>", "profile name to remove").action(
2917
3235
  withErrorHandling(async (name) => {
2918
3236
  const config = await readGlobalConfig();
@@ -2938,9 +3256,9 @@ function profileRemoveCommand() {
2938
3256
  }
2939
3257
 
2940
3258
  // src/commands/profile/use.ts
2941
- import { Command as Command21 } from "commander";
3259
+ import { Command as Command24 } from "commander";
2942
3260
  function profileUseCommand() {
2943
- const cmd = new Command21("use");
3261
+ const cmd = new Command24("use");
2944
3262
  cmd.description("switch the active profile").argument("<name>", "profile name to activate").action(
2945
3263
  withErrorHandling(async (name) => {
2946
3264
  const config = await readGlobalConfig();
@@ -2963,7 +3281,7 @@ function profileUseCommand() {
2963
3281
  }
2964
3282
 
2965
3283
  // src/commands/read.ts
2966
- import { Command as Command22 } from "commander";
3284
+ import { Command as Command25 } from "commander";
2967
3285
 
2968
3286
  // src/output/markdown.ts
2969
3287
  import { Chalk as Chalk2 } from "chalk";
@@ -3093,17 +3411,71 @@ function renderInline(text) {
3093
3411
  }
3094
3412
 
3095
3413
  // src/services/page.service.ts
3414
+ var UNKNOWN_BOOKMARK_REGEX = /<unknown url="([^"]*)" alt="bookmark"\/>/g;
3415
+ function extractBlockIdFromUrl(url) {
3416
+ const fragment = new URL(url).hash.slice(1);
3417
+ const hex = fragment.replace(/-/g, "");
3418
+ return /^[0-9a-f]{32}$/i.test(hex) ? hex : null;
3419
+ }
3420
+ function richTextToPlainText(richText) {
3421
+ return richText.map((rt) => rt.plain_text ?? "").join("");
3422
+ }
3423
+ async function resolveUnknownBookmarks(client, markdown) {
3424
+ const matches = [...markdown.matchAll(UNKNOWN_BOOKMARK_REGEX)];
3425
+ if (matches.length === 0) return markdown;
3426
+ const tagToBlockId = /* @__PURE__ */ new Map();
3427
+ for (const match of matches) {
3428
+ const tag = match[0];
3429
+ if (tagToBlockId.has(tag)) continue;
3430
+ try {
3431
+ const blockId = extractBlockIdFromUrl(match[1]);
3432
+ tagToBlockId.set(tag, blockId);
3433
+ } catch {
3434
+ tagToBlockId.set(tag, null);
3435
+ }
3436
+ }
3437
+ const blockIdToReplacement = /* @__PURE__ */ new Map();
3438
+ await Promise.all(
3439
+ [...new Set(tagToBlockId.values())].map(async (blockId) => {
3440
+ if (!blockId) return;
3441
+ try {
3442
+ const block = await client.blocks.retrieve({
3443
+ block_id: toUuid(blockId)
3444
+ });
3445
+ if (block.type !== "bookmark" || !block.bookmark) return;
3446
+ const { url, caption } = block.bookmark;
3447
+ const captionText = richTextToPlainText(caption).trim();
3448
+ const replacement = captionText ? `[${captionText}](${url})` : url;
3449
+ blockIdToReplacement.set(blockId, replacement);
3450
+ } catch {
3451
+ }
3452
+ })
3453
+ );
3454
+ return markdown.replace(UNKNOWN_BOOKMARK_REGEX, (tag, notionUrl) => {
3455
+ try {
3456
+ const blockId = extractBlockIdFromUrl(notionUrl);
3457
+ if (!blockId) return tag;
3458
+ return blockIdToReplacement.get(blockId) ?? tag;
3459
+ } catch {
3460
+ return tag;
3461
+ }
3462
+ });
3463
+ }
3096
3464
  async function fetchPageMarkdown(client, pageId) {
3097
3465
  const [page, markdownResponse] = await Promise.all([
3098
3466
  client.pages.retrieve({ page_id: pageId }),
3099
3467
  client.pages.retrieveMarkdown({ page_id: pageId })
3100
3468
  ]);
3101
- return { page, markdown: markdownResponse.markdown };
3469
+ const markdown = await resolveUnknownBookmarks(
3470
+ client,
3471
+ markdownResponse.markdown
3472
+ );
3473
+ return { page, markdown };
3102
3474
  }
3103
3475
 
3104
3476
  // src/commands/read.ts
3105
3477
  function readCommand() {
3106
- return new Command22("read").description("Read a Notion page as markdown").argument("<id>", "Notion page ID or URL").action(
3478
+ return new Command25("read").description("Read a Notion page as markdown").argument("<id>", "Notion page ID or URL").action(
3107
3479
  withErrorHandling(async (id) => {
3108
3480
  const { token } = await resolveToken();
3109
3481
  const client = createNotionClient(token);
@@ -3129,7 +3501,7 @@ function readCommand() {
3129
3501
 
3130
3502
  // src/commands/search.ts
3131
3503
  import { isFullPageOrDataSource as isFullPageOrDataSource2 } from "@notionhq/client";
3132
- import { Command as Command23 } from "commander";
3504
+ import { Command as Command26 } from "commander";
3133
3505
  function getTitle2(item) {
3134
3506
  if (item.object === "data_source") {
3135
3507
  return item.title.map((t) => t.plain_text).join("") || "(untitled)";
@@ -3149,7 +3521,7 @@ function displayType2(item) {
3149
3521
  return item.object === "data_source" ? "database" : item.object;
3150
3522
  }
3151
3523
  function searchCommand() {
3152
- const cmd = new Command23("search");
3524
+ const cmd = new Command26("search");
3153
3525
  cmd.description("search Notion workspace by keyword").argument("<query>", "search keyword").option(
3154
3526
  "--type <type>",
3155
3527
  "filter by object type (page or database)",
@@ -3194,8 +3566,12 @@ function searchCommand() {
3194
3566
  (r) => isFullPageOrDataSource2(r)
3195
3567
  );
3196
3568
  if (fullResults.length === 0) {
3197
- process.stdout.write(`No results found for "${query}"
3569
+ if (getOutputMode() === "json") {
3570
+ process.stdout.write("[]\n");
3571
+ } else {
3572
+ process.stdout.write(`No results found for "${query}"
3198
3573
  `);
3574
+ }
3199
3575
  return;
3200
3576
  }
3201
3577
  const headers = ["TYPE", "TITLE", "ID", "MODIFIED"];
@@ -3230,7 +3606,7 @@ import {
3230
3606
  import { homedir as homedir2 } from "os";
3231
3607
  import { dirname, join as join3 } from "path";
3232
3608
  import chalk from "chalk";
3233
- import { Command as Command24 } from "commander";
3609
+ import { Command as Command27 } from "commander";
3234
3610
  function skillPath() {
3235
3611
  if (!process.argv[1]) {
3236
3612
  throw new Error("Cannot determine install path. Run with: notion skill");
@@ -3317,7 +3693,7 @@ function installNonInteractive(source, targets) {
3317
3693
  }
3318
3694
  }
3319
3695
  function skillCommand() {
3320
- const cmd = new Command24("skill");
3696
+ const cmd = new Command27("skill");
3321
3697
  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
3698
  withErrorHandling(async (opts) => {
3323
3699
  if (opts.print) {
@@ -3346,17 +3722,17 @@ function skillCommand() {
3346
3722
  }
3347
3723
 
3348
3724
  // src/commands/update.ts
3349
- import { Command as Command25 } from "commander";
3350
- function collectProps2(val, acc) {
3725
+ import { Command as Command28 } from "commander";
3726
+ function collectProps4(val, acc) {
3351
3727
  acc.push(val);
3352
3728
  return acc;
3353
3729
  }
3354
3730
  function updateCommand() {
3355
- const cmd = new Command25("update");
3731
+ const cmd = new Command28("update");
3356
3732
  cmd.description("update properties on a Notion page").argument("<id/url>", "Notion page ID or URL").option(
3357
3733
  "--prop <property=value>",
3358
3734
  "set a property value (repeatable)",
3359
- collectProps2,
3735
+ collectProps4,
3360
3736
  []
3361
3737
  ).option("--title <title>", "set the page title").action(
3362
3738
  withErrorHandling(async (idOrUrl, opts) => {
@@ -3410,7 +3786,7 @@ function updateCommand() {
3410
3786
  }
3411
3787
 
3412
3788
  // src/commands/users.ts
3413
- import { Command as Command26 } from "commander";
3789
+ import { Command as Command29 } from "commander";
3414
3790
  function getEmailOrWorkspace(user) {
3415
3791
  if (user.type === "person") {
3416
3792
  return user.person.email ?? "\u2014";
@@ -3422,7 +3798,7 @@ function getEmailOrWorkspace(user) {
3422
3798
  return "\u2014";
3423
3799
  }
3424
3800
  function usersCommand() {
3425
- const cmd = new Command26("users");
3801
+ const cmd = new Command29("users");
3426
3802
  cmd.description("list all users in the workspace").option("--json", "output as JSON").action(
3427
3803
  withErrorHandling(async (opts) => {
3428
3804
  if (opts.json) setOutputMode("json");
@@ -3453,7 +3829,7 @@ var __dirname = dirname2(__filename);
3453
3829
  var pkg = JSON.parse(
3454
3830
  readFileSync2(join4(__dirname, "../package.json"), "utf-8")
3455
3831
  );
3456
- var program = new Command27();
3832
+ var program = new Command30();
3457
3833
  program.name("notion").description("Notion CLI \u2014 read Notion pages and databases from the terminal").version(pkg.version);
3458
3834
  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");
3459
3835
  program.configureOutput({
@@ -3474,7 +3850,7 @@ program.hook("preAction", (thisCommand) => {
3474
3850
  setOutputMode("md");
3475
3851
  }
3476
3852
  });
3477
- var authCmd = new Command27("auth").description("manage Notion authentication");
3853
+ var authCmd = new Command30("auth").description("manage Notion authentication");
3478
3854
  authCmd.action(authDefaultAction(authCmd));
3479
3855
  authCmd.addCommand(loginCommand());
3480
3856
  authCmd.addCommand(logoutCommand());
@@ -3484,7 +3860,7 @@ authCmd.addCommand(profileUseCommand());
3484
3860
  authCmd.addCommand(profileRemoveCommand());
3485
3861
  program.addCommand(authCmd);
3486
3862
  program.addCommand(initCommand(), { hidden: true });
3487
- var profileCmd = new Command27("profile").description(
3863
+ var profileCmd = new Command30("profile").description(
3488
3864
  "manage authentication profiles"
3489
3865
  );
3490
3866
  profileCmd.addCommand(profileListCommand());
@@ -3504,11 +3880,14 @@ program.addCommand(createPageCommand());
3504
3880
  program.addCommand(editPageCommand());
3505
3881
  program.addCommand(updateCommand());
3506
3882
  program.addCommand(archiveCommand());
3883
+ program.addCommand(deleteBlockCommand());
3507
3884
  program.addCommand(moveCommand());
3508
- var dbCmd = new Command27("db").description("Database operations");
3885
+ var dbCmd = new Command30("db").description("Database operations");
3509
3886
  dbCmd.addCommand(dbCreateCommand());
3510
3887
  dbCmd.addCommand(dbSchemaCommand());
3511
3888
  dbCmd.addCommand(dbQueryCommand());
3889
+ dbCmd.addCommand(dbUpdateCommand());
3890
+ dbCmd.addCommand(dbUpdateRowsCommand());
3512
3891
  program.addCommand(dbCmd);
3513
3892
  program.addCommand(completionCommand());
3514
3893
  program.addCommand(skillCommand());