@catafal/notion-cli 5.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.
Files changed (162) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +552 -0
  3. package/bin/dev +17 -0
  4. package/bin/dev.cmd +3 -0
  5. package/bin/run +14 -0
  6. package/bin/run.cmd +3 -0
  7. package/dist/base-command.d.ts +73 -0
  8. package/dist/base-command.js +179 -0
  9. package/dist/base-flags.d.ts +14 -0
  10. package/dist/base-flags.js +59 -0
  11. package/dist/cache.d.ts +84 -0
  12. package/dist/cache.js +351 -0
  13. package/dist/commands/append.d.ts +37 -0
  14. package/dist/commands/append.js +120 -0
  15. package/dist/commands/batch/delete.d.ts +42 -0
  16. package/dist/commands/batch/delete.js +199 -0
  17. package/dist/commands/batch/retrieve.d.ts +43 -0
  18. package/dist/commands/batch/retrieve.js +272 -0
  19. package/dist/commands/block/append.d.ts +42 -0
  20. package/dist/commands/block/append.js +219 -0
  21. package/dist/commands/block/delete.d.ts +30 -0
  22. package/dist/commands/block/delete.js +97 -0
  23. package/dist/commands/block/retrieve/children.d.ts +31 -0
  24. package/dist/commands/block/retrieve/children.js +177 -0
  25. package/dist/commands/block/retrieve.d.ts +30 -0
  26. package/dist/commands/block/retrieve.js +101 -0
  27. package/dist/commands/block/update.d.ts +45 -0
  28. package/dist/commands/block/update.js +242 -0
  29. package/dist/commands/bookmark/list.d.ts +30 -0
  30. package/dist/commands/bookmark/list.js +60 -0
  31. package/dist/commands/bookmark/remove.d.ts +26 -0
  32. package/dist/commands/bookmark/remove.js +47 -0
  33. package/dist/commands/bookmark/set.d.ts +29 -0
  34. package/dist/commands/bookmark/set.js +96 -0
  35. package/dist/commands/browse.d.ts +13 -0
  36. package/dist/commands/browse.js +44 -0
  37. package/dist/commands/cache/info.d.ts +19 -0
  38. package/dist/commands/cache/info.js +145 -0
  39. package/dist/commands/config/set-token.d.ts +22 -0
  40. package/dist/commands/config/set-token.js +137 -0
  41. package/dist/commands/daily/index.d.ts +32 -0
  42. package/dist/commands/daily/index.js +135 -0
  43. package/dist/commands/daily/setup.d.ts +42 -0
  44. package/dist/commands/daily/setup.js +149 -0
  45. package/dist/commands/db/create.d.ts +31 -0
  46. package/dist/commands/db/create.js +124 -0
  47. package/dist/commands/db/query.d.ts +41 -0
  48. package/dist/commands/db/query.js +360 -0
  49. package/dist/commands/db/retrieve.d.ts +33 -0
  50. package/dist/commands/db/retrieve.js +134 -0
  51. package/dist/commands/db/schema.d.ts +32 -0
  52. package/dist/commands/db/schema.js +308 -0
  53. package/dist/commands/db/update.d.ts +31 -0
  54. package/dist/commands/db/update.js +117 -0
  55. package/dist/commands/doctor.d.ts +50 -0
  56. package/dist/commands/doctor.js +420 -0
  57. package/dist/commands/init.d.ts +65 -0
  58. package/dist/commands/init.js +479 -0
  59. package/dist/commands/list.d.ts +29 -0
  60. package/dist/commands/list.js +219 -0
  61. package/dist/commands/open.d.ts +29 -0
  62. package/dist/commands/open.js +100 -0
  63. package/dist/commands/page/create.d.ts +33 -0
  64. package/dist/commands/page/create.js +261 -0
  65. package/dist/commands/page/delete.d.ts +36 -0
  66. package/dist/commands/page/delete.js +107 -0
  67. package/dist/commands/page/export.d.ts +38 -0
  68. package/dist/commands/page/export.js +120 -0
  69. package/dist/commands/page/retrieve/property_item.d.ts +24 -0
  70. package/dist/commands/page/retrieve/property_item.js +75 -0
  71. package/dist/commands/page/retrieve.d.ts +36 -0
  72. package/dist/commands/page/retrieve.js +244 -0
  73. package/dist/commands/page/update.d.ts +34 -0
  74. package/dist/commands/page/update.js +184 -0
  75. package/dist/commands/quick.d.ts +35 -0
  76. package/dist/commands/quick.js +168 -0
  77. package/dist/commands/search.d.ts +43 -0
  78. package/dist/commands/search.js +361 -0
  79. package/dist/commands/stats.d.ts +35 -0
  80. package/dist/commands/stats.js +274 -0
  81. package/dist/commands/sync.d.ts +24 -0
  82. package/dist/commands/sync.js +183 -0
  83. package/dist/commands/template/get.d.ts +28 -0
  84. package/dist/commands/template/get.js +59 -0
  85. package/dist/commands/template/list.d.ts +32 -0
  86. package/dist/commands/template/list.js +62 -0
  87. package/dist/commands/template/remove.d.ts +27 -0
  88. package/dist/commands/template/remove.js +48 -0
  89. package/dist/commands/template/save.d.ts +32 -0
  90. package/dist/commands/template/save.js +92 -0
  91. package/dist/commands/template/use.d.ts +34 -0
  92. package/dist/commands/template/use.js +142 -0
  93. package/dist/commands/user/list.d.ts +27 -0
  94. package/dist/commands/user/list.js +99 -0
  95. package/dist/commands/user/retrieve/bot.d.ts +28 -0
  96. package/dist/commands/user/retrieve/bot.js +96 -0
  97. package/dist/commands/user/retrieve.d.ts +30 -0
  98. package/dist/commands/user/retrieve.js +103 -0
  99. package/dist/commands/whoami.d.ts +19 -0
  100. package/dist/commands/whoami.js +175 -0
  101. package/dist/deduplication.d.ts +41 -0
  102. package/dist/deduplication.js +71 -0
  103. package/dist/envelope.d.ts +169 -0
  104. package/dist/envelope.js +257 -0
  105. package/dist/errors/enhanced-errors.d.ts +168 -0
  106. package/dist/errors/enhanced-errors.js +567 -0
  107. package/dist/errors/index.d.ts +18 -0
  108. package/dist/errors/index.js +33 -0
  109. package/dist/examples/cache-retry-examples.d.ts +64 -0
  110. package/dist/examples/cache-retry-examples.js +375 -0
  111. package/dist/helper.d.ts +102 -0
  112. package/dist/helper.js +885 -0
  113. package/dist/http-agent.d.ts +38 -0
  114. package/dist/http-agent.js +60 -0
  115. package/dist/index.d.ts +1 -0
  116. package/dist/index.js +4 -0
  117. package/dist/interface.d.ts +4 -0
  118. package/dist/interface.js +2 -0
  119. package/dist/notion.d.ts +144 -0
  120. package/dist/notion.js +547 -0
  121. package/dist/retry.d.ts +72 -0
  122. package/dist/retry.js +381 -0
  123. package/dist/utils/bookmarks.d.ts +32 -0
  124. package/dist/utils/bookmarks.js +98 -0
  125. package/dist/utils/daily-config.d.ts +22 -0
  126. package/dist/utils/daily-config.js +60 -0
  127. package/dist/utils/disk-cache.d.ts +80 -0
  128. package/dist/utils/disk-cache.js +291 -0
  129. package/dist/utils/fuzzy.d.ts +36 -0
  130. package/dist/utils/fuzzy.js +69 -0
  131. package/dist/utils/interactive-navigator.d.ts +63 -0
  132. package/dist/utils/interactive-navigator.js +123 -0
  133. package/dist/utils/markdown-to-blocks.d.ts +21 -0
  134. package/dist/utils/markdown-to-blocks.js +333 -0
  135. package/dist/utils/notion-resolver.d.ts +49 -0
  136. package/dist/utils/notion-resolver.js +278 -0
  137. package/dist/utils/notion-url-parser.d.ts +48 -0
  138. package/dist/utils/notion-url-parser.js +121 -0
  139. package/dist/utils/property-expander.d.ts +45 -0
  140. package/dist/utils/property-expander.js +323 -0
  141. package/dist/utils/schema-examples.d.ts +40 -0
  142. package/dist/utils/schema-examples.js +359 -0
  143. package/dist/utils/schema-extractor.d.ts +65 -0
  144. package/dist/utils/schema-extractor.js +235 -0
  145. package/dist/utils/shell-config.d.ts +30 -0
  146. package/dist/utils/shell-config.js +84 -0
  147. package/dist/utils/table-formatter.d.ts +36 -0
  148. package/dist/utils/table-formatter.js +125 -0
  149. package/dist/utils/templates.d.ts +30 -0
  150. package/dist/utils/templates.js +82 -0
  151. package/dist/utils/terminal-banner.d.ts +24 -0
  152. package/dist/utils/terminal-banner.js +34 -0
  153. package/dist/utils/token-validator.d.ts +42 -0
  154. package/dist/utils/token-validator.js +66 -0
  155. package/dist/utils/update-notifier.d.ts +26 -0
  156. package/dist/utils/update-notifier.js +54 -0
  157. package/dist/utils/workspace-cache.d.ts +58 -0
  158. package/dist/utils/workspace-cache.js +185 -0
  159. package/oclif.manifest.json +6471 -0
  160. package/package.json +118 -0
  161. package/scripts/banner.js +38 -0
  162. package/scripts/postinstall.js +44 -0
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Notion URL Parser
3
+ *
4
+ * Extracts clean Notion IDs from various input formats:
5
+ * - Title-slug URLs: https://www.notion.so/My-Page-Title-1fb79d4c71bb8032b722c82305b63a00
6
+ * - Workspace URLs: https://www.notion.so/workspace/Page-Title-1fb79d4c71bb8032b722c82305b63a00
7
+ * - Bare ID URLs: https://www.notion.so/1fb79d4c71bb8032b722c82305b63a00?v=...
8
+ * - Short URLs: notion.so/1fb79d4c71bb8032b722c82305b63a00
9
+ * - Raw IDs with dashes: 1fb79d4c-71bb-8032-b722-c82305b63a00
10
+ * - Raw IDs without dashes: 1fb79d4c71bb8032b722c82305b63a00
11
+ */
12
+ /**
13
+ * Extract Notion ID from URL or raw ID
14
+ *
15
+ * @param input - Full Notion URL, partial URL, or raw ID
16
+ * @returns Clean Notion ID (32 hex characters without dashes)
17
+ * @throws Error if input is invalid
18
+ *
19
+ * @example
20
+ * // URL with page title slug (most common format)
21
+ * extractNotionId('https://www.notion.so/My-Page-1fb79d4c71bb8032b722c82305b63a00')
22
+ * // Returns: '1fb79d4c71bb8032b722c82305b63a00'
23
+ *
24
+ * @example
25
+ * // Bare ID URL
26
+ * extractNotionId('https://www.notion.so/1fb79d4c71bb8032b722c82305b63a00?v=...')
27
+ * // Returns: '1fb79d4c71bb8032b722c82305b63a00'
28
+ *
29
+ * @example
30
+ * // Raw ID with dashes
31
+ * extractNotionId('1fb79d4c-71bb-8032-b722-c82305b63a00')
32
+ * // Returns: '1fb79d4c71bb8032b722c82305b63a00'
33
+ */
34
+ export declare function extractNotionId(input: string): string;
35
+ /**
36
+ * Check if a string looks like a Notion URL
37
+ *
38
+ * @param input - String to check
39
+ * @returns True if input appears to be a Notion URL
40
+ */
41
+ export declare function isNotionUrl(input: string): boolean;
42
+ /**
43
+ * Check if a string looks like a valid Notion ID
44
+ *
45
+ * @param input - String to check
46
+ * @returns True if input appears to be a valid Notion ID
47
+ */
48
+ export declare function isValidNotionId(input: string): boolean;
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ /**
3
+ * Notion URL Parser
4
+ *
5
+ * Extracts clean Notion IDs from various input formats:
6
+ * - Title-slug URLs: https://www.notion.so/My-Page-Title-1fb79d4c71bb8032b722c82305b63a00
7
+ * - Workspace URLs: https://www.notion.so/workspace/Page-Title-1fb79d4c71bb8032b722c82305b63a00
8
+ * - Bare ID URLs: https://www.notion.so/1fb79d4c71bb8032b722c82305b63a00?v=...
9
+ * - Short URLs: notion.so/1fb79d4c71bb8032b722c82305b63a00
10
+ * - Raw IDs with dashes: 1fb79d4c-71bb-8032-b722-c82305b63a00
11
+ * - Raw IDs without dashes: 1fb79d4c71bb8032b722c82305b63a00
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.extractNotionId = extractNotionId;
15
+ exports.isNotionUrl = isNotionUrl;
16
+ exports.isValidNotionId = isValidNotionId;
17
+ /**
18
+ * Extract Notion ID from URL or raw ID
19
+ *
20
+ * @param input - Full Notion URL, partial URL, or raw ID
21
+ * @returns Clean Notion ID (32 hex characters without dashes)
22
+ * @throws Error if input is invalid
23
+ *
24
+ * @example
25
+ * // URL with page title slug (most common format)
26
+ * extractNotionId('https://www.notion.so/My-Page-1fb79d4c71bb8032b722c82305b63a00')
27
+ * // Returns: '1fb79d4c71bb8032b722c82305b63a00'
28
+ *
29
+ * @example
30
+ * // Bare ID URL
31
+ * extractNotionId('https://www.notion.so/1fb79d4c71bb8032b722c82305b63a00?v=...')
32
+ * // Returns: '1fb79d4c71bb8032b722c82305b63a00'
33
+ *
34
+ * @example
35
+ * // Raw ID with dashes
36
+ * extractNotionId('1fb79d4c-71bb-8032-b722-c82305b63a00')
37
+ * // Returns: '1fb79d4c71bb8032b722c82305b63a00'
38
+ */
39
+ function extractNotionId(input) {
40
+ if (!input || typeof input !== 'string') {
41
+ throw new Error('Input must be a non-empty string');
42
+ }
43
+ const trimmed = input.trim();
44
+ // Check if it's a URL (contains notion.so or http)
45
+ if (trimmed.includes('notion.so') || trimmed.includes('http')) {
46
+ return extractIdFromUrl(trimmed);
47
+ }
48
+ // Not a URL, treat as raw ID
49
+ return cleanRawId(trimmed);
50
+ }
51
+ /**
52
+ * Extract ID from Notion URL
53
+ */
54
+ function extractIdFromUrl(url) {
55
+ // Notion URL formats (the ID is always the last 32 hex chars in the path):
56
+ // https://www.notion.so/1fb79d4c71bb8032b722c82305b63a00
57
+ // https://www.notion.so/1fb79d4c71bb8032b722c82305b63a00?v=view_id
58
+ // https://www.notion.so/My-Page-Title-1fb79d4c71bb8032b722c82305b63a00
59
+ // https://www.notion.so/workspace/Page-Title-1fb79d4c71bb8032b722c82305b63a00
60
+ // Strip query params and hash before matching
61
+ const pathOnly = url.split(/[?#]/)[0];
62
+ // Extract the last 32 hex characters from the URL path.
63
+ // Notion always appends the ID at the end of the slug (after the last dash).
64
+ const match = pathOnly.match(/([a-f0-9]{32})$/i);
65
+ if (match) {
66
+ return match[1].toLowerCase();
67
+ }
68
+ // Fallback: try matching a dashed UUID anywhere in the path
69
+ const dashedMatch = pathOnly.match(/([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})/i);
70
+ if (dashedMatch) {
71
+ return cleanRawId(dashedMatch[1]);
72
+ }
73
+ throw new Error(`Could not extract Notion ID from URL: ${url}\n\n` +
74
+ `Expected format: https://www.notion.so/Page-Title-{id}\n` +
75
+ `Example: https://www.notion.so/My-Page-1fb79d4c71bb8032b722c82305b63a00`);
76
+ }
77
+ /**
78
+ * Clean raw ID by removing dashes and validating format
79
+ */
80
+ function cleanRawId(id) {
81
+ // Remove all dashes
82
+ const cleaned = id.replace(/-/g, '');
83
+ // Validate: must be exactly 32 hex characters
84
+ if (!/^[a-f0-9]{32}$/i.test(cleaned)) {
85
+ throw new Error(`Invalid Notion ID format: ${id}\n\n` +
86
+ `Expected: 32 hexadecimal characters (with or without dashes)\n` +
87
+ `Example: 1fb79d4c71bb8032b722c82305b63a00\n` +
88
+ `Example: 1fb79d4c-71bb-8032-b722-c82305b63a00`);
89
+ }
90
+ return cleaned.toLowerCase();
91
+ }
92
+ /**
93
+ * Check if a string looks like a Notion URL
94
+ *
95
+ * @param input - String to check
96
+ * @returns True if input appears to be a Notion URL
97
+ */
98
+ function isNotionUrl(input) {
99
+ if (!input || typeof input !== 'string') {
100
+ return false;
101
+ }
102
+ return input.includes('notion.so');
103
+ }
104
+ /**
105
+ * Check if a string looks like a valid Notion ID
106
+ *
107
+ * @param input - String to check
108
+ * @returns True if input appears to be a valid Notion ID
109
+ */
110
+ function isValidNotionId(input) {
111
+ if (!input || typeof input !== 'string') {
112
+ return false;
113
+ }
114
+ try {
115
+ extractNotionId(input);
116
+ return true;
117
+ }
118
+ catch {
119
+ return false;
120
+ }
121
+ }
@@ -0,0 +1,45 @@
1
+ import { GetDataSourceResponse } from '@notionhq/client/build/src/api-endpoints';
2
+ /**
3
+ * Simple flat property format for AI agents
4
+ * Instead of complex Notion nested structures, use simple key-value pairs:
5
+ * { "Name": "Task", "Status": "Done", "Tags": ["urgent", "bug"] }
6
+ */
7
+ export interface SimpleProperties {
8
+ [key: string]: string | number | boolean | string[] | null;
9
+ }
10
+ /**
11
+ * Notion API property format (deeply nested)
12
+ */
13
+ export interface NotionProperties {
14
+ [key: string]: any;
15
+ }
16
+ /**
17
+ * Expand simple flat properties to Notion API format
18
+ *
19
+ * This function takes simplified property values and automatically expands them
20
+ * to the correct Notion API structure based on the database schema.
21
+ *
22
+ * @param simple - Flat key-value property object
23
+ * @param schema - Database properties schema from data source
24
+ * @returns Properly formatted Notion properties object
25
+ *
26
+ * @example
27
+ * // Input (simple):
28
+ * { "Name": "My Task", "Status": "In Progress", "Priority": 5 }
29
+ *
30
+ * // Output (Notion format):
31
+ * {
32
+ * "Name": { "title": [{ "text": { "content": "My Task" } }] },
33
+ * "Status": { "select": { "name": "In Progress" } },
34
+ * "Priority": { "number": 5 }
35
+ * }
36
+ */
37
+ export declare function expandSimpleProperties(simple: SimpleProperties, schema: GetDataSourceResponse['properties']): Promise<NotionProperties>;
38
+ /**
39
+ * Validate simple properties against schema before expansion
40
+ * This can be called optionally before expandSimpleProperties to get detailed errors
41
+ */
42
+ export declare function validateSimpleProperties(simple: SimpleProperties, schema: GetDataSourceResponse['properties']): {
43
+ valid: boolean;
44
+ errors: string[];
45
+ };
@@ -0,0 +1,323 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.expandSimpleProperties = expandSimpleProperties;
4
+ exports.validateSimpleProperties = validateSimpleProperties;
5
+ /**
6
+ * Expand simple flat properties to Notion API format
7
+ *
8
+ * This function takes simplified property values and automatically expands them
9
+ * to the correct Notion API structure based on the database schema.
10
+ *
11
+ * @param simple - Flat key-value property object
12
+ * @param schema - Database properties schema from data source
13
+ * @returns Properly formatted Notion properties object
14
+ *
15
+ * @example
16
+ * // Input (simple):
17
+ * { "Name": "My Task", "Status": "In Progress", "Priority": 5 }
18
+ *
19
+ * // Output (Notion format):
20
+ * {
21
+ * "Name": { "title": [{ "text": { "content": "My Task" } }] },
22
+ * "Status": { "select": { "name": "In Progress" } },
23
+ * "Priority": { "number": 5 }
24
+ * }
25
+ */
26
+ async function expandSimpleProperties(simple, schema) {
27
+ const expanded = {};
28
+ for (const [propName, value] of Object.entries(simple)) {
29
+ // Find property in schema (case-insensitive)
30
+ const propDef = findProperty(schema, propName);
31
+ if (!propDef) {
32
+ throw new Error(`Property "${propName}" not found in database schema.\n` +
33
+ `Available properties: ${Object.keys(schema).join(', ')}`);
34
+ }
35
+ // Expand based on type
36
+ try {
37
+ expanded[propDef.actualName] = expandProperty(value, propDef.type, propDef);
38
+ }
39
+ catch (error) {
40
+ throw new Error(`Error expanding property "${propName}": ${error.message}`);
41
+ }
42
+ }
43
+ return expanded;
44
+ }
45
+ /**
46
+ * Find property in schema with case-insensitive matching
47
+ */
48
+ function findProperty(schema, name) {
49
+ const normalized = name.toLowerCase();
50
+ for (const [key, value] of Object.entries(schema)) {
51
+ if (key.toLowerCase() === normalized) {
52
+ // Cast value to object type to allow spreading
53
+ const propConfig = value;
54
+ return { actualName: key, ...propConfig };
55
+ }
56
+ }
57
+ return null;
58
+ }
59
+ /**
60
+ * Expand a single property value to Notion format based on type
61
+ */
62
+ function expandProperty(value, type, propDef) {
63
+ // Handle null values
64
+ if (value === null) {
65
+ return null;
66
+ }
67
+ switch (type) {
68
+ case 'title':
69
+ return {
70
+ title: [{ text: { content: String(value) } }]
71
+ };
72
+ case 'rich_text':
73
+ return {
74
+ rich_text: [{ text: { content: String(value) } }]
75
+ };
76
+ case 'number': {
77
+ const num = Number(value);
78
+ if (isNaN(num)) {
79
+ throw new Error(`Invalid number value: "${value}"`);
80
+ }
81
+ return { number: num };
82
+ }
83
+ case 'checkbox': {
84
+ // Handle boolean or string representations
85
+ let boolValue;
86
+ if (typeof value === 'boolean') {
87
+ boolValue = value;
88
+ }
89
+ else if (typeof value === 'string') {
90
+ const lower = value.toLowerCase();
91
+ if (lower === 'true' || lower === 'yes' || lower === '1') {
92
+ boolValue = true;
93
+ }
94
+ else if (lower === 'false' || lower === 'no' || lower === '0') {
95
+ boolValue = false;
96
+ }
97
+ else {
98
+ throw new Error(`Invalid checkbox value: "${value}". Use true/false, yes/no, or 1/0`);
99
+ }
100
+ }
101
+ else {
102
+ boolValue = Boolean(value);
103
+ }
104
+ return { checkbox: boolValue };
105
+ }
106
+ case 'select':
107
+ return expandSelectProperty(value, propDef);
108
+ case 'multi_select':
109
+ return expandMultiSelectProperty(value, propDef);
110
+ case 'status':
111
+ return expandStatusProperty(value, propDef);
112
+ case 'date':
113
+ return expandDateProperty(value);
114
+ case 'url': {
115
+ const urlStr = String(value);
116
+ // Basic URL validation
117
+ if (!urlStr.match(/^https?:\/\/.+/)) {
118
+ throw new Error(`Invalid URL: "${value}". Must start with http:// or https://`);
119
+ }
120
+ return { url: urlStr };
121
+ }
122
+ case 'email': {
123
+ const emailStr = String(value);
124
+ // Basic email validation
125
+ if (!emailStr.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
126
+ throw new Error(`Invalid email: "${value}"`);
127
+ }
128
+ return { email: emailStr };
129
+ }
130
+ case 'phone_number':
131
+ return { phone_number: String(value) };
132
+ case 'people':
133
+ return expandPeopleProperty(value);
134
+ case 'files': {
135
+ // Files need external URLs
136
+ const files = Array.isArray(value) ? value : [value];
137
+ return {
138
+ files: files.map(f => {
139
+ if (typeof f === 'string') {
140
+ return { name: f, external: { url: f } };
141
+ }
142
+ return f;
143
+ })
144
+ };
145
+ }
146
+ case 'relation': {
147
+ // Relations need page IDs
148
+ const relations = Array.isArray(value) ? value : [value];
149
+ return {
150
+ relation: relations.map(id => ({ id: String(id) }))
151
+ };
152
+ }
153
+ default:
154
+ throw new Error(`Unsupported property type: ${type}. ` +
155
+ `Supported types: title, rich_text, number, checkbox, select, multi_select, ` +
156
+ `status, date, url, email, phone_number, people, files, relation`);
157
+ }
158
+ }
159
+ /**
160
+ * Expand select property with validation
161
+ */
162
+ function expandSelectProperty(value, propDef) {
163
+ var _a;
164
+ const selectOptions = ((_a = propDef.select) === null || _a === void 0 ? void 0 : _a.options) || [];
165
+ const strValue = String(value);
166
+ // Case-insensitive matching
167
+ const validOption = selectOptions.find((opt) => opt.name.toLowerCase() === strValue.toLowerCase());
168
+ if (!validOption && selectOptions.length > 0) {
169
+ const optionNames = selectOptions.map((o) => o.name).join(', ');
170
+ throw new Error(`Invalid select value: "${value}"\n` +
171
+ `Valid options: ${optionNames}\n` +
172
+ `Tip: Values are case-insensitive`);
173
+ }
174
+ // Use the exact option name from schema (preserving case)
175
+ const exactName = validOption ? validOption.name : strValue;
176
+ return { select: { name: exactName } };
177
+ }
178
+ /**
179
+ * Expand multi-select property with validation
180
+ */
181
+ function expandMultiSelectProperty(value, propDef) {
182
+ var _a;
183
+ const values = Array.isArray(value) ? value : [value];
184
+ const multiOptions = ((_a = propDef.multi_select) === null || _a === void 0 ? void 0 : _a.options) || [];
185
+ const validated = values.map(v => {
186
+ const strValue = String(v);
187
+ // Case-insensitive matching
188
+ const validOption = multiOptions.find((opt) => opt.name.toLowerCase() === strValue.toLowerCase());
189
+ if (!validOption && multiOptions.length > 0) {
190
+ const optionNames = multiOptions.map((o) => o.name).join(', ');
191
+ throw new Error(`Invalid multi-select value: "${v}"\n` +
192
+ `Valid options: ${optionNames}`);
193
+ }
194
+ // Use exact option name from schema
195
+ const exactName = validOption ? validOption.name : strValue;
196
+ return { name: exactName };
197
+ });
198
+ return { multi_select: validated };
199
+ }
200
+ /**
201
+ * Expand status property with validation
202
+ */
203
+ function expandStatusProperty(value, propDef) {
204
+ var _a;
205
+ const statusOptions = ((_a = propDef.status) === null || _a === void 0 ? void 0 : _a.options) || [];
206
+ const strValue = String(value);
207
+ // Case-insensitive matching
208
+ const validStatus = statusOptions.find((opt) => opt.name.toLowerCase() === strValue.toLowerCase());
209
+ if (!validStatus && statusOptions.length > 0) {
210
+ const optionNames = statusOptions.map((o) => o.name).join(', ');
211
+ throw new Error(`Invalid status value: "${value}"\n` +
212
+ `Valid options: ${optionNames}`);
213
+ }
214
+ // Use exact status name from schema
215
+ const exactName = validStatus ? validStatus.name : strValue;
216
+ return { status: { name: exactName } };
217
+ }
218
+ /**
219
+ * Expand date property with support for ISO dates and relative dates
220
+ */
221
+ function expandDateProperty(value) {
222
+ const dateStr = parseRelativeDate(String(value));
223
+ // Check if it includes time (ISO 8601 with time component)
224
+ if (dateStr.includes('T')) {
225
+ return { date: { start: dateStr } };
226
+ }
227
+ return { date: { start: dateStr } };
228
+ }
229
+ /**
230
+ * Parse relative date strings like "today", "tomorrow", "+7 days"
231
+ */
232
+ function parseRelativeDate(value) {
233
+ // Handle ISO dates (YYYY-MM-DD or full ISO 8601)
234
+ if (/^\d{4}-\d{2}-\d{2}/.test(value)) {
235
+ return value;
236
+ }
237
+ // Handle relative dates
238
+ const today = new Date();
239
+ today.setHours(0, 0, 0, 0); // Reset to start of day
240
+ if (value.toLowerCase() === 'today') {
241
+ return today.toISOString().split('T')[0];
242
+ }
243
+ if (value.toLowerCase() === 'tomorrow') {
244
+ today.setDate(today.getDate() + 1);
245
+ return today.toISOString().split('T')[0];
246
+ }
247
+ if (value.toLowerCase() === 'yesterday') {
248
+ today.setDate(today.getDate() - 1);
249
+ return today.toISOString().split('T')[0];
250
+ }
251
+ // Parse "+N days/weeks/months/years" format
252
+ const match = value.match(/^([+-]?\d+)\s*(day|week|month|year)s?$/i);
253
+ if (match) {
254
+ const amount = parseInt(match[1]);
255
+ const unit = match[2].toLowerCase();
256
+ switch (unit) {
257
+ case 'day':
258
+ today.setDate(today.getDate() + amount);
259
+ break;
260
+ case 'week':
261
+ today.setDate(today.getDate() + amount * 7);
262
+ break;
263
+ case 'month':
264
+ today.setMonth(today.getMonth() + amount);
265
+ break;
266
+ case 'year':
267
+ today.setFullYear(today.getFullYear() + amount);
268
+ break;
269
+ }
270
+ return today.toISOString().split('T')[0];
271
+ }
272
+ // If none of the above, assume it's already a valid date string
273
+ return value;
274
+ }
275
+ /**
276
+ * Expand people property
277
+ */
278
+ function expandPeopleProperty(value) {
279
+ const users = Array.isArray(value) ? value : [value];
280
+ return {
281
+ people: users.map(u => {
282
+ // Support user ID or email
283
+ if (typeof u === 'string') {
284
+ // Check if it's a UUID (user ID) or email
285
+ if (u.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)) {
286
+ return { id: u };
287
+ }
288
+ // For email, we can only use ID - throw helpful error
289
+ if (u.includes('@')) {
290
+ throw new Error(`Cannot use email addresses for people property. ` +
291
+ `Use Notion user IDs instead. You can get user IDs with: notion-cli user list`);
292
+ }
293
+ return { id: u };
294
+ }
295
+ return { id: String(u) };
296
+ })
297
+ };
298
+ }
299
+ /**
300
+ * Validate simple properties against schema before expansion
301
+ * This can be called optionally before expandSimpleProperties to get detailed errors
302
+ */
303
+ function validateSimpleProperties(simple, schema) {
304
+ const errors = [];
305
+ for (const [propName, value] of Object.entries(simple)) {
306
+ const propDef = findProperty(schema, propName);
307
+ if (!propDef) {
308
+ errors.push(`Property "${propName}" not found in schema`);
309
+ continue;
310
+ }
311
+ // Type-specific validation
312
+ try {
313
+ expandProperty(value, propDef.type, propDef);
314
+ }
315
+ catch (error) {
316
+ errors.push(`${propName}: ${error.message}`);
317
+ }
318
+ }
319
+ return {
320
+ valid: errors.length === 0,
321
+ errors
322
+ };
323
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Property Example Generator for Notion API
3
+ *
4
+ * Generates copy-pastable property payload examples based on database schema.
5
+ * Helps AI agents understand the correct format for create/update operations.
6
+ */
7
+ /**
8
+ * Property example with simple value and Notion API payload
9
+ */
10
+ export interface PropertyExample {
11
+ property_name: string;
12
+ property_type: string;
13
+ simple_value: string | number | boolean | string[] | null;
14
+ notion_payload: Record<string, any>;
15
+ description: string;
16
+ }
17
+ /**
18
+ * Generate property examples for all properties in a data source schema
19
+ *
20
+ * @param properties - Properties object from GetDataSourceResponse
21
+ * @returns Array of property examples
22
+ */
23
+ export declare function generatePropertyExamples(properties: Record<string, any>): PropertyExample[];
24
+ /**
25
+ * Format examples for human-readable console output
26
+ *
27
+ * @param examples - Array of property examples
28
+ * @returns Formatted string
29
+ */
30
+ export declare function formatExamplesForConsole(examples: PropertyExample[]): string;
31
+ /**
32
+ * Group examples by writability (writable vs read-only)
33
+ *
34
+ * @param examples - Array of property examples
35
+ * @returns Grouped examples
36
+ */
37
+ export declare function groupExamplesByWritability(examples: PropertyExample[]): {
38
+ writable: PropertyExample[];
39
+ readOnly: PropertyExample[];
40
+ };