@andrzejchm/notion-cli 0.9.0 → 0.9.1

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
@@ -95,6 +95,7 @@ notion ls
95
95
  | `notion ls` | List all accessible pages and databases (`--sort asc\|desc`) |
96
96
  | `notion open <id\|url>` | Open a page in your browser |
97
97
  | `notion read <id\|url>` | Read a page as markdown |
98
+ | `notion db create --parent <id\|url> --title <title>` | Create a new database with property definitions |
98
99
  | `notion db schema <id\|url>` | Show database property schema and valid values |
99
100
  | `notion db query <id\|url>` | Query database entries with filtering and sorting |
100
101
  | `notion users` | List workspace members |
@@ -117,6 +118,17 @@ notion ls
117
118
  | `--cursor` | `--cursor <cursor>` | Pagination cursor from a previous `--next` hint |
118
119
  | `--json` | `--json` | Force JSON output |
119
120
 
121
+ ### `notion db create` flags
122
+
123
+ | Flag | Example | Description |
124
+ |------|---------|-------------|
125
+ | `--parent` | `--parent <id\|url>` | Parent page ID or URL (required) |
126
+ | `--title` | `--title "Tasks"` | Database title (required) |
127
+ | `--prop` | `--prop "Status:select:To Do,Done"` | Property definition (repeatable) |
128
+ | `--json` | `--json` | Output full JSON response |
129
+
130
+ Property syntax: `Name:type[:options]`. Supported types: `title`, `rich_text`, `number`, `select`, `multi_select`, `status`, `date`, `checkbox`, `url`, `email`, `phone_number`, `people`, `files`, `created_time`, `last_edited_time`. If no title property is defined, `Name:title` is added automatically.
131
+
120
132
  ### `notion db query` flags
121
133
 
122
134
  | Flag | Example | Description |
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 Command23 } from "commander";
7
+ import { Command as Command24 } from "commander";
8
8
 
9
9
  // src/commands/append.ts
10
10
  import { Command } from "commander";
@@ -1684,6 +1684,126 @@ function displayDate(date) {
1684
1684
  if (!date) return "";
1685
1685
  return date.end ? `${date.start} \u2192 ${date.end}` : date.start;
1686
1686
  }
1687
+ var SIMPLE_PROPERTY_TYPES = /* @__PURE__ */ new Set([
1688
+ "title",
1689
+ "rich_text",
1690
+ "number",
1691
+ "date",
1692
+ "checkbox",
1693
+ "url",
1694
+ "email",
1695
+ "phone_number",
1696
+ "people",
1697
+ "files",
1698
+ "created_time",
1699
+ "last_edited_time"
1700
+ ]);
1701
+ var OPTIONS_PROPERTY_TYPES = /* @__PURE__ */ new Set(["select", "multi_select", "status"]);
1702
+ var UNSUPPORTED_PROPERTY_TYPES = {
1703
+ relation: "requires a data_source_id reference",
1704
+ rollup: "requires relation and property references",
1705
+ formula: "requires an expression string",
1706
+ unique_id: "auto-managed by Notion"
1707
+ };
1708
+ function parsePropertyDefinition(defString) {
1709
+ if (!defString) {
1710
+ throw new CliError(
1711
+ ErrorCodes.INVALID_ARG,
1712
+ "Property definition cannot be empty",
1713
+ 'Use format: --prop "Name:type" or --prop "Name:type:opt1,opt2"'
1714
+ );
1715
+ }
1716
+ const firstColon = defString.indexOf(":");
1717
+ if (firstColon === -1) {
1718
+ throw new CliError(
1719
+ ErrorCodes.INVALID_ARG,
1720
+ `Invalid property definition: "${defString}"`,
1721
+ 'Use format: --prop "Name:type" or --prop "Name:type:opt1,opt2"'
1722
+ );
1723
+ }
1724
+ const name = defString.slice(0, firstColon);
1725
+ const rest = defString.slice(firstColon + 1);
1726
+ const secondColon = rest.indexOf(":");
1727
+ const type = secondColon === -1 ? rest : rest.slice(0, secondColon);
1728
+ const optionsStr = secondColon === -1 ? "" : rest.slice(secondColon + 1);
1729
+ if (type in UNSUPPORTED_PROPERTY_TYPES) {
1730
+ throw new CliError(
1731
+ ErrorCodes.INVALID_ARG,
1732
+ `Property type "${type}" is not supported via CLI \u2014 ${UNSUPPORTED_PROPERTY_TYPES[type]}`,
1733
+ "Supported types: title, rich_text, number, select, multi_select, status, date, checkbox, url, email, phone_number, people, files, created_time, last_edited_time"
1734
+ );
1735
+ }
1736
+ if (SIMPLE_PROPERTY_TYPES.has(type)) {
1737
+ return { name, config: { [type]: {} } };
1738
+ }
1739
+ if (OPTIONS_PROPERTY_TYPES.has(type)) {
1740
+ const options = optionsStr ? optionsStr.split(",").map((opt) => ({ name: opt.trim() })) : [];
1741
+ return { name, config: { [type]: { options } } };
1742
+ }
1743
+ throw new CliError(
1744
+ ErrorCodes.INVALID_ARG,
1745
+ `Unknown property type: "${type}"`,
1746
+ "Supported types: title, rich_text, number, select, multi_select, status, date, checkbox, url, email, phone_number, people, files, created_time, last_edited_time"
1747
+ );
1748
+ }
1749
+ function parsePropertyDefinitions(defStrings) {
1750
+ const properties = {};
1751
+ let hasTitleProperty = false;
1752
+ for (const def of defStrings) {
1753
+ const { name, config } = parsePropertyDefinition(def);
1754
+ if (name in properties) {
1755
+ throw new CliError(
1756
+ ErrorCodes.INVALID_ARG,
1757
+ `Duplicate property name: "${name}"`,
1758
+ "Each property must have a unique name"
1759
+ );
1760
+ }
1761
+ const isTitle = "title" in config;
1762
+ if (isTitle && hasTitleProperty) {
1763
+ throw new CliError(
1764
+ ErrorCodes.INVALID_ARG,
1765
+ "Only one title property is allowed per database",
1766
+ "Remove one of the title property definitions"
1767
+ );
1768
+ }
1769
+ if (isTitle) hasTitleProperty = true;
1770
+ properties[name] = config;
1771
+ }
1772
+ if (!hasTitleProperty) {
1773
+ properties.Name = { title: {} };
1774
+ }
1775
+ return properties;
1776
+ }
1777
+ async function createDatabase(client, parentId, title, properties) {
1778
+ return client.databases.create({
1779
+ parent: { type: "page_id", page_id: parentId },
1780
+ title: [{ type: "text", text: { content: title } }],
1781
+ initial_data_source: {
1782
+ properties
1783
+ }
1784
+ });
1785
+ }
1786
+ async function resolveDataSourceId(client, idOrUrl) {
1787
+ const id = toUuid(parseNotionId(idOrUrl));
1788
+ try {
1789
+ await client.dataSources.retrieve({ data_source_id: id });
1790
+ return id;
1791
+ } catch {
1792
+ }
1793
+ try {
1794
+ const db = await client.databases.retrieve({
1795
+ database_id: id
1796
+ });
1797
+ if (db.data_sources && db.data_sources.length > 0) {
1798
+ return db.data_sources[0].id;
1799
+ }
1800
+ } catch {
1801
+ }
1802
+ throw new CliError(
1803
+ ErrorCodes.API_NOT_FOUND,
1804
+ `Could not find database or data source: ${id}`
1805
+ );
1806
+ }
1687
1807
  function displayPropertyValue(prop) {
1688
1808
  switch (prop.type) {
1689
1809
  case "title":
@@ -1919,8 +2039,46 @@ function createPageCommand() {
1919
2039
  return cmd;
1920
2040
  }
1921
2041
 
1922
- // src/commands/db/query.ts
2042
+ // src/commands/db/create.ts
1923
2043
  import { Command as Command10 } from "commander";
2044
+ function collectProps2(val, acc) {
2045
+ acc.push(val);
2046
+ return acc;
2047
+ }
2048
+ function dbCreateCommand() {
2049
+ return new Command10("create").description("Create a new database under a parent page").requiredOption("--parent <id/url>", "parent page ID or URL").requiredOption("--title <title>", "database title").option(
2050
+ "--prop <definition>",
2051
+ 'property definition (repeatable): --prop "Name:type:options"',
2052
+ collectProps2,
2053
+ []
2054
+ ).action(
2055
+ withErrorHandling(async (opts) => {
2056
+ const { token, source } = await resolveToken();
2057
+ reportTokenSource(source);
2058
+ const client = createNotionClient(token);
2059
+ const parentUuid = toUuid(parseNotionId(opts.parent));
2060
+ const properties = parsePropertyDefinitions(opts.prop);
2061
+ const response = await createDatabase(
2062
+ client,
2063
+ parentUuid,
2064
+ opts.title,
2065
+ properties
2066
+ );
2067
+ if (getOutputMode() === "json") {
2068
+ process.stdout.write(`${formatJSON(response)}
2069
+ `);
2070
+ return;
2071
+ }
2072
+ if ("url" in response) {
2073
+ process.stdout.write(`${response.url}
2074
+ `);
2075
+ }
2076
+ })
2077
+ );
2078
+ }
2079
+
2080
+ // src/commands/db/query.ts
2081
+ import { Command as Command11 } from "commander";
1924
2082
  var SKIP_TYPES_IN_AUTO = /* @__PURE__ */ new Set(["relation", "rich_text", "people"]);
1925
2083
  function autoSelectColumns(schema, entries) {
1926
2084
  const termWidth = process.stdout.columns || 120;
@@ -1948,7 +2106,7 @@ function autoSelectColumns(schema, entries) {
1948
2106
  return selected;
1949
2107
  }
1950
2108
  function dbQueryCommand() {
1951
- return new Command10("query").description("Query database entries with optional filtering and sorting").argument("<id>", "Notion database ID or URL").option(
2109
+ return new Command11("query").description("Query database entries with optional filtering and sorting").argument("<id>", "Notion database ID or URL").option(
1952
2110
  "--filter <filter>",
1953
2111
  'Filter entries (repeatable): --filter "Status=Done"',
1954
2112
  collect,
@@ -1961,22 +2119,22 @@ function dbQueryCommand() {
1961
2119
  ).option(
1962
2120
  "--columns <columns>",
1963
2121
  'Comma-separated list of columns to display: --columns "Title,Status"'
1964
- ).option("--json", "Output raw JSON").action(
2122
+ ).action(
1965
2123
  withErrorHandling(
1966
2124
  async (id, options) => {
1967
2125
  const { token } = await resolveToken();
1968
2126
  const client = createNotionClient(token);
1969
- const dbId = parseNotionId(id);
1970
- const schema = await fetchDatabaseSchema(client, dbId);
2127
+ const dsId = await resolveDataSourceId(client, id);
2128
+ const schema = await fetchDatabaseSchema(client, dsId);
1971
2129
  const columns = options.columns ? options.columns.split(",").map((c2) => c2.trim()) : void 0;
1972
2130
  const filter = options.filter.length ? buildFilter(options.filter, schema) : void 0;
1973
2131
  const sorts = options.sort.length ? buildSorts(options.sort) : void 0;
1974
- const entries = await queryDatabase(client, dbId, {
2132
+ const entries = await queryDatabase(client, dsId, {
1975
2133
  filter,
1976
2134
  sorts,
1977
2135
  columns
1978
2136
  });
1979
- if (options.json) {
2137
+ if (getOutputMode() === "json") {
1980
2138
  process.stdout.write(`${formatJSON(entries.map((e) => e.raw))}
1981
2139
  `);
1982
2140
  return;
@@ -2003,15 +2161,15 @@ function collect(value, previous) {
2003
2161
  }
2004
2162
 
2005
2163
  // src/commands/db/schema.ts
2006
- import { Command as Command11 } from "commander";
2164
+ import { Command as Command12 } from "commander";
2007
2165
  function dbSchemaCommand() {
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(
2009
- withErrorHandling(async (id, options) => {
2166
+ return new Command12("schema").description("Show database schema (property names, types, and options)").argument("<id>", "Notion database ID or URL").action(
2167
+ withErrorHandling(async (id) => {
2010
2168
  const { token } = await resolveToken();
2011
2169
  const client = createNotionClient(token);
2012
- const dbId = parseNotionId(id);
2013
- const schema = await fetchDatabaseSchema(client, dbId);
2014
- if (options.json) {
2170
+ const dsId = await resolveDataSourceId(client, id);
2171
+ const schema = await fetchDatabaseSchema(client, dsId);
2172
+ if (getOutputMode() === "json") {
2015
2173
  process.stdout.write(`${formatJSON(schema)}
2016
2174
  `);
2017
2175
  return;
@@ -2029,13 +2187,13 @@ function dbSchemaCommand() {
2029
2187
  }
2030
2188
 
2031
2189
  // src/commands/edit-page.ts
2032
- import { Command as Command12 } from "commander";
2190
+ import { Command as Command13 } from "commander";
2033
2191
  function collect2(val, acc) {
2034
2192
  acc.push(val);
2035
2193
  return acc;
2036
2194
  }
2037
2195
  function editPageCommand() {
2038
- const cmd = new Command12("edit-page");
2196
+ const cmd = new Command13("edit-page");
2039
2197
  cmd.description(
2040
2198
  "replace a Notion page's content \u2014 full page or a targeted section"
2041
2199
  ).argument("<id/url>", "Notion page ID or URL").option(
@@ -2133,7 +2291,7 @@ function editPageCommand() {
2133
2291
 
2134
2292
  // src/commands/init.ts
2135
2293
  import { confirm, input as input2, password } from "@inquirer/prompts";
2136
- import { Command as Command13 } from "commander";
2294
+ import { Command as Command14 } from "commander";
2137
2295
  async function runInitFlow() {
2138
2296
  const profileName = await input2({
2139
2297
  message: "Profile name:",
@@ -2213,7 +2371,7 @@ async function runInitFlow() {
2213
2371
  stderrWrite(dim(" notion auth login"));
2214
2372
  }
2215
2373
  function initCommand() {
2216
- const cmd = new Command13("init");
2374
+ const cmd = new Command14("init");
2217
2375
  cmd.description("authenticate with Notion and save a profile").action(
2218
2376
  withErrorHandling(async () => {
2219
2377
  if (!process.stdin.isTTY) {
@@ -2231,7 +2389,7 @@ function initCommand() {
2231
2389
 
2232
2390
  // src/commands/ls.ts
2233
2391
  import { isFullPageOrDataSource } from "@notionhq/client";
2234
- import { Command as Command14 } from "commander";
2392
+ import { Command as Command15 } from "commander";
2235
2393
  function getTitle(item) {
2236
2394
  if (item.object === "data_source") {
2237
2395
  return item.title.map((t) => t.plain_text).join("") || "(untitled)";
@@ -2248,7 +2406,7 @@ function displayType(item) {
2248
2406
  return item.object === "data_source" ? "database" : item.object;
2249
2407
  }
2250
2408
  function lsCommand() {
2251
- const cmd = new Command14("ls");
2409
+ const cmd = new Command15("ls");
2252
2410
  cmd.description("list accessible Notion pages and databases").option(
2253
2411
  "--type <type>",
2254
2412
  "filter by object type (page or database)",
@@ -2324,10 +2482,10 @@ function lsCommand() {
2324
2482
  // src/commands/open.ts
2325
2483
  import { exec } from "child_process";
2326
2484
  import { promisify } from "util";
2327
- import { Command as Command15 } from "commander";
2485
+ import { Command as Command16 } from "commander";
2328
2486
  var execAsync = promisify(exec);
2329
2487
  function openCommand() {
2330
- const cmd = new Command15("open");
2488
+ const cmd = new Command16("open");
2331
2489
  cmd.description("open a Notion page in the default browser").argument("<id/url>", "Notion page ID or URL").action(
2332
2490
  withErrorHandling(async (idOrUrl) => {
2333
2491
  const id = parseNotionId(idOrUrl);
@@ -2343,9 +2501,9 @@ function openCommand() {
2343
2501
  }
2344
2502
 
2345
2503
  // src/commands/profile/list.ts
2346
- import { Command as Command16 } from "commander";
2504
+ import { Command as Command17 } from "commander";
2347
2505
  function profileListCommand() {
2348
- const cmd = new Command16("list");
2506
+ const cmd = new Command17("list");
2349
2507
  cmd.description("list all authentication profiles").action(
2350
2508
  withErrorHandling(async () => {
2351
2509
  const config = await readGlobalConfig();
@@ -2374,9 +2532,9 @@ function profileListCommand() {
2374
2532
  }
2375
2533
 
2376
2534
  // src/commands/profile/remove.ts
2377
- import { Command as Command17 } from "commander";
2535
+ import { Command as Command18 } from "commander";
2378
2536
  function profileRemoveCommand() {
2379
- const cmd = new Command17("remove");
2537
+ const cmd = new Command18("remove");
2380
2538
  cmd.description("remove an authentication profile").argument("<name>", "profile name to remove").action(
2381
2539
  withErrorHandling(async (name) => {
2382
2540
  const config = await readGlobalConfig();
@@ -2402,9 +2560,9 @@ function profileRemoveCommand() {
2402
2560
  }
2403
2561
 
2404
2562
  // src/commands/profile/use.ts
2405
- import { Command as Command18 } from "commander";
2563
+ import { Command as Command19 } from "commander";
2406
2564
  function profileUseCommand() {
2407
- const cmd = new Command18("use");
2565
+ const cmd = new Command19("use");
2408
2566
  cmd.description("switch the active profile").argument("<name>", "profile name to activate").action(
2409
2567
  withErrorHandling(async (name) => {
2410
2568
  const config = await readGlobalConfig();
@@ -2427,7 +2585,7 @@ function profileUseCommand() {
2427
2585
  }
2428
2586
 
2429
2587
  // src/commands/read.ts
2430
- import { Command as Command19 } from "commander";
2588
+ import { Command as Command20 } from "commander";
2431
2589
 
2432
2590
  // src/output/markdown.ts
2433
2591
  import { Chalk as Chalk2 } from "chalk";
@@ -2567,7 +2725,7 @@ async function fetchPageMarkdown(client, pageId) {
2567
2725
 
2568
2726
  // src/commands/read.ts
2569
2727
  function readCommand() {
2570
- return new Command19("read").description("Read a Notion page as markdown").argument("<id>", "Notion page ID or URL").action(
2728
+ return new Command20("read").description("Read a Notion page as markdown").argument("<id>", "Notion page ID or URL").action(
2571
2729
  withErrorHandling(async (id) => {
2572
2730
  const { token } = await resolveToken();
2573
2731
  const client = createNotionClient(token);
@@ -2593,7 +2751,7 @@ function readCommand() {
2593
2751
 
2594
2752
  // src/commands/search.ts
2595
2753
  import { isFullPageOrDataSource as isFullPageOrDataSource2 } from "@notionhq/client";
2596
- import { Command as Command20 } from "commander";
2754
+ import { Command as Command21 } from "commander";
2597
2755
  function getTitle2(item) {
2598
2756
  if (item.object === "data_source") {
2599
2757
  return item.title.map((t) => t.plain_text).join("") || "(untitled)";
@@ -2613,7 +2771,7 @@ function displayType2(item) {
2613
2771
  return item.object === "data_source" ? "database" : item.object;
2614
2772
  }
2615
2773
  function searchCommand() {
2616
- const cmd = new Command20("search");
2774
+ const cmd = new Command21("search");
2617
2775
  cmd.description("search Notion workspace by keyword").argument("<query>", "search keyword").option(
2618
2776
  "--type <type>",
2619
2777
  "filter by object type (page or database)",
@@ -2684,17 +2842,17 @@ function searchCommand() {
2684
2842
  }
2685
2843
 
2686
2844
  // src/commands/update.ts
2687
- import { Command as Command21 } from "commander";
2688
- function collectProps2(val, acc) {
2845
+ import { Command as Command22 } from "commander";
2846
+ function collectProps3(val, acc) {
2689
2847
  acc.push(val);
2690
2848
  return acc;
2691
2849
  }
2692
2850
  function updateCommand() {
2693
- const cmd = new Command21("update");
2851
+ const cmd = new Command22("update");
2694
2852
  cmd.description("update properties on a Notion page").argument("<id/url>", "Notion page ID or URL").option(
2695
2853
  "--prop <property=value>",
2696
2854
  "set a property value (repeatable)",
2697
- collectProps2,
2855
+ collectProps3,
2698
2856
  []
2699
2857
  ).option("--title <title>", "set the page title").action(
2700
2858
  withErrorHandling(async (idOrUrl, opts) => {
@@ -2748,7 +2906,7 @@ function updateCommand() {
2748
2906
  }
2749
2907
 
2750
2908
  // src/commands/users.ts
2751
- import { Command as Command22 } from "commander";
2909
+ import { Command as Command23 } from "commander";
2752
2910
  function getEmailOrWorkspace(user) {
2753
2911
  if (user.type === "person") {
2754
2912
  return user.person.email ?? "\u2014";
@@ -2760,7 +2918,7 @@ function getEmailOrWorkspace(user) {
2760
2918
  return "\u2014";
2761
2919
  }
2762
2920
  function usersCommand() {
2763
- const cmd = new Command22("users");
2921
+ const cmd = new Command23("users");
2764
2922
  cmd.description("list all users in the workspace").option("--json", "output as JSON").action(
2765
2923
  withErrorHandling(async (opts) => {
2766
2924
  if (opts.json) setOutputMode("json");
@@ -2791,7 +2949,7 @@ var __dirname = dirname(__filename);
2791
2949
  var pkg = JSON.parse(
2792
2950
  readFileSync(join3(__dirname, "../package.json"), "utf-8")
2793
2951
  );
2794
- var program = new Command23();
2952
+ var program = new Command24();
2795
2953
  program.name("notion").description("Notion CLI \u2014 read Notion pages and databases from the terminal").version(pkg.version);
2796
2954
  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");
2797
2955
  program.configureOutput({
@@ -2812,7 +2970,7 @@ program.hook("preAction", (thisCommand) => {
2812
2970
  setOutputMode("md");
2813
2971
  }
2814
2972
  });
2815
- var authCmd = new Command23("auth").description("manage Notion authentication");
2973
+ var authCmd = new Command24("auth").description("manage Notion authentication");
2816
2974
  authCmd.action(authDefaultAction(authCmd));
2817
2975
  authCmd.addCommand(loginCommand());
2818
2976
  authCmd.addCommand(logoutCommand());
@@ -2822,7 +2980,7 @@ authCmd.addCommand(profileUseCommand());
2822
2980
  authCmd.addCommand(profileRemoveCommand());
2823
2981
  program.addCommand(authCmd);
2824
2982
  program.addCommand(initCommand(), { hidden: true });
2825
- var profileCmd = new Command23("profile").description(
2983
+ var profileCmd = new Command24("profile").description(
2826
2984
  "manage authentication profiles"
2827
2985
  );
2828
2986
  profileCmd.addCommand(profileListCommand());
@@ -2841,7 +2999,8 @@ program.addCommand(createPageCommand());
2841
2999
  program.addCommand(editPageCommand());
2842
3000
  program.addCommand(updateCommand());
2843
3001
  program.addCommand(archiveCommand());
2844
- var dbCmd = new Command23("db").description("Database operations");
3002
+ var dbCmd = new Command24("db").description("Database operations");
3003
+ dbCmd.addCommand(dbCreateCommand());
2845
3004
  dbCmd.addCommand(dbSchemaCommand());
2846
3005
  dbCmd.addCommand(dbQueryCommand());
2847
3006
  program.addCommand(dbCmd);