@infograb/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 (114) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1386 -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/batch/retrieve.d.ts +43 -0
  14. package/dist/commands/batch/retrieve.js +265 -0
  15. package/dist/commands/block/append.d.ts +42 -0
  16. package/dist/commands/block/append.js +219 -0
  17. package/dist/commands/block/delete.d.ts +30 -0
  18. package/dist/commands/block/delete.js +94 -0
  19. package/dist/commands/block/retrieve/children.d.ts +31 -0
  20. package/dist/commands/block/retrieve/children.js +174 -0
  21. package/dist/commands/block/retrieve.d.ts +30 -0
  22. package/dist/commands/block/retrieve.js +98 -0
  23. package/dist/commands/block/update.d.ts +45 -0
  24. package/dist/commands/block/update.js +241 -0
  25. package/dist/commands/cache/info.d.ts +19 -0
  26. package/dist/commands/cache/info.js +145 -0
  27. package/dist/commands/config/set-token.d.ts +30 -0
  28. package/dist/commands/config/set-token.js +201 -0
  29. package/dist/commands/db/create.d.ts +31 -0
  30. package/dist/commands/db/create.js +124 -0
  31. package/dist/commands/db/query.d.ts +41 -0
  32. package/dist/commands/db/query.js +355 -0
  33. package/dist/commands/db/retrieve.d.ts +33 -0
  34. package/dist/commands/db/retrieve.js +134 -0
  35. package/dist/commands/db/schema.d.ts +32 -0
  36. package/dist/commands/db/schema.js +308 -0
  37. package/dist/commands/db/update.d.ts +31 -0
  38. package/dist/commands/db/update.js +117 -0
  39. package/dist/commands/doctor.d.ts +50 -0
  40. package/dist/commands/doctor.js +420 -0
  41. package/dist/commands/init.d.ts +57 -0
  42. package/dist/commands/init.js +471 -0
  43. package/dist/commands/list.d.ts +29 -0
  44. package/dist/commands/list.js +184 -0
  45. package/dist/commands/page/create.d.ts +33 -0
  46. package/dist/commands/page/create.js +240 -0
  47. package/dist/commands/page/retrieve/property_item.d.ts +24 -0
  48. package/dist/commands/page/retrieve/property_item.js +72 -0
  49. package/dist/commands/page/retrieve.d.ts +36 -0
  50. package/dist/commands/page/retrieve.js +244 -0
  51. package/dist/commands/page/update.d.ts +34 -0
  52. package/dist/commands/page/update.js +184 -0
  53. package/dist/commands/search.d.ts +40 -0
  54. package/dist/commands/search.js +348 -0
  55. package/dist/commands/sync.d.ts +24 -0
  56. package/dist/commands/sync.js +183 -0
  57. package/dist/commands/user/list.d.ts +27 -0
  58. package/dist/commands/user/list.js +99 -0
  59. package/dist/commands/user/retrieve/bot.d.ts +28 -0
  60. package/dist/commands/user/retrieve/bot.js +96 -0
  61. package/dist/commands/user/retrieve.d.ts +30 -0
  62. package/dist/commands/user/retrieve.js +103 -0
  63. package/dist/commands/whoami.d.ts +19 -0
  64. package/dist/commands/whoami.js +175 -0
  65. package/dist/deduplication.d.ts +41 -0
  66. package/dist/deduplication.js +71 -0
  67. package/dist/envelope.d.ts +169 -0
  68. package/dist/envelope.js +257 -0
  69. package/dist/errors/enhanced-errors.d.ts +168 -0
  70. package/dist/errors/enhanced-errors.js +570 -0
  71. package/dist/errors/index.d.ts +18 -0
  72. package/dist/errors/index.js +33 -0
  73. package/dist/examples/cache-retry-examples.d.ts +64 -0
  74. package/dist/examples/cache-retry-examples.js +375 -0
  75. package/dist/helper.d.ts +102 -0
  76. package/dist/helper.js +885 -0
  77. package/dist/http-agent.d.ts +38 -0
  78. package/dist/http-agent.js +60 -0
  79. package/dist/index.d.ts +1 -0
  80. package/dist/index.js +4 -0
  81. package/dist/interface.d.ts +4 -0
  82. package/dist/interface.js +2 -0
  83. package/dist/notion.d.ts +144 -0
  84. package/dist/notion.js +547 -0
  85. package/dist/retry.d.ts +72 -0
  86. package/dist/retry.js +381 -0
  87. package/dist/utils/disk-cache.d.ts +80 -0
  88. package/dist/utils/disk-cache.js +291 -0
  89. package/dist/utils/markdown-to-blocks.d.ts +19 -0
  90. package/dist/utils/markdown-to-blocks.js +259 -0
  91. package/dist/utils/notion-resolver.d.ts +48 -0
  92. package/dist/utils/notion-resolver.js +262 -0
  93. package/dist/utils/notion-url-parser.d.ts +46 -0
  94. package/dist/utils/notion-url-parser.js +111 -0
  95. package/dist/utils/property-expander.d.ts +45 -0
  96. package/dist/utils/property-expander.js +323 -0
  97. package/dist/utils/schema-examples.d.ts +40 -0
  98. package/dist/utils/schema-examples.js +359 -0
  99. package/dist/utils/schema-extractor.d.ts +65 -0
  100. package/dist/utils/schema-extractor.js +235 -0
  101. package/dist/utils/table-formatter.d.ts +36 -0
  102. package/dist/utils/table-formatter.js +122 -0
  103. package/dist/utils/terminal-banner.d.ts +24 -0
  104. package/dist/utils/terminal-banner.js +34 -0
  105. package/dist/utils/token-validator.d.ts +55 -0
  106. package/dist/utils/token-validator.js +85 -0
  107. package/dist/utils/update-notifier.d.ts +26 -0
  108. package/dist/utils/update-notifier.js +54 -0
  109. package/dist/utils/workspace-cache.d.ts +58 -0
  110. package/dist/utils/workspace-cache.js +185 -0
  111. package/oclif.manifest.json +4497 -0
  112. package/package.json +115 -0
  113. package/scripts/banner.js +38 -0
  114. package/scripts/postinstall.js +56 -0
@@ -0,0 +1,291 @@
1
+ "use strict";
2
+ /**
3
+ * Disk Cache Manager
4
+ *
5
+ * Provides persistent caching to disk, maintaining cache across CLI invocations.
6
+ * Cache entries are stored in ~/.notion-cli/cache/ directory.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.diskCacheManager = exports.DiskCacheManager = void 0;
10
+ const fs = require("fs/promises");
11
+ const path = require("path");
12
+ const os = require("os");
13
+ const crypto = require("crypto");
14
+ const CACHE_DIR_NAME = '.notion-cli';
15
+ const CACHE_SUBDIR = 'cache';
16
+ const DEFAULT_MAX_SIZE = 100 * 1024 * 1024; // 100MB
17
+ const DEFAULT_SYNC_INTERVAL = 5000; // 5 seconds
18
+ class DiskCacheManager {
19
+ constructor(options = {}) {
20
+ this.dirtyKeys = new Set();
21
+ this.syncTimer = null;
22
+ this.initialized = false;
23
+ this.cacheDir = options.cacheDir || path.join(os.homedir(), CACHE_DIR_NAME, CACHE_SUBDIR);
24
+ this.maxSize = options.maxSize || parseInt(process.env.NOTION_CLI_DISK_CACHE_MAX_SIZE || String(DEFAULT_MAX_SIZE), 10);
25
+ this.syncInterval = options.syncInterval || parseInt(process.env.NOTION_CLI_DISK_CACHE_SYNC_INTERVAL || String(DEFAULT_SYNC_INTERVAL), 10);
26
+ }
27
+ /**
28
+ * Initialize disk cache (create directory, start sync timer)
29
+ */
30
+ async initialize() {
31
+ if (this.initialized) {
32
+ return;
33
+ }
34
+ await this.ensureCacheDir();
35
+ await this.enforceMaxSize();
36
+ // Start periodic sync timer
37
+ if (this.syncInterval > 0) {
38
+ this.syncTimer = setInterval(() => {
39
+ this.sync().catch(error => {
40
+ if (process.env.DEBUG) {
41
+ console.warn('Disk cache sync error:', error);
42
+ }
43
+ });
44
+ }, this.syncInterval);
45
+ // Don't keep the process alive
46
+ if (this.syncTimer.unref) {
47
+ this.syncTimer.unref();
48
+ }
49
+ }
50
+ this.initialized = true;
51
+ }
52
+ /**
53
+ * Get a cache entry from disk
54
+ */
55
+ async get(key) {
56
+ try {
57
+ const filePath = this.getFilePath(key);
58
+ const content = await fs.readFile(filePath, 'utf-8');
59
+ const entry = JSON.parse(content);
60
+ // Check if expired
61
+ if (Date.now() > entry.expiresAt) {
62
+ // Delete expired entry
63
+ await this.invalidate(key);
64
+ return null;
65
+ }
66
+ return entry;
67
+ }
68
+ catch (error) {
69
+ if (error.code === 'ENOENT') {
70
+ return null;
71
+ }
72
+ if (process.env.DEBUG) {
73
+ console.warn(`Failed to read cache entry ${key}:`, error.message);
74
+ }
75
+ return null;
76
+ }
77
+ }
78
+ /**
79
+ * Set a cache entry to disk
80
+ */
81
+ async set(key, data, ttl) {
82
+ const entry = {
83
+ key,
84
+ data,
85
+ expiresAt: Date.now() + ttl,
86
+ createdAt: Date.now(),
87
+ size: JSON.stringify(data).length,
88
+ };
89
+ const filePath = this.getFilePath(key);
90
+ const tmpPath = `${filePath}.tmp`;
91
+ try {
92
+ // Write to temporary file
93
+ await fs.writeFile(tmpPath, JSON.stringify(entry), 'utf-8');
94
+ // Atomic rename
95
+ await fs.rename(tmpPath, filePath);
96
+ this.dirtyKeys.delete(key);
97
+ }
98
+ catch (error) {
99
+ // Clean up temp file if it exists
100
+ try {
101
+ await fs.unlink(tmpPath);
102
+ }
103
+ catch {
104
+ // Ignore cleanup errors
105
+ }
106
+ if (process.env.DEBUG) {
107
+ console.warn(`Failed to write cache entry ${key}:`, error.message);
108
+ }
109
+ }
110
+ // Check if we need to enforce size limits
111
+ const stats = await this.getStats();
112
+ if (stats.totalSize > this.maxSize) {
113
+ await this.enforceMaxSize();
114
+ }
115
+ }
116
+ /**
117
+ * Invalidate (delete) a cache entry
118
+ */
119
+ async invalidate(key) {
120
+ try {
121
+ const filePath = this.getFilePath(key);
122
+ await fs.unlink(filePath);
123
+ this.dirtyKeys.delete(key);
124
+ }
125
+ catch (error) {
126
+ if (error.code !== 'ENOENT') {
127
+ if (process.env.DEBUG) {
128
+ console.warn(`Failed to delete cache entry ${key}:`, error.message);
129
+ }
130
+ }
131
+ }
132
+ }
133
+ /**
134
+ * Clear all cache entries
135
+ */
136
+ async clear() {
137
+ try {
138
+ const files = await fs.readdir(this.cacheDir);
139
+ await Promise.all(files
140
+ .filter(file => !file.endsWith('.tmp'))
141
+ .map(file => fs.unlink(path.join(this.cacheDir, file)).catch(() => { })));
142
+ this.dirtyKeys.clear();
143
+ }
144
+ catch (error) {
145
+ if (error.code !== 'ENOENT') {
146
+ if (process.env.DEBUG) {
147
+ console.warn('Failed to clear cache:', error.message);
148
+ }
149
+ }
150
+ }
151
+ }
152
+ /**
153
+ * Sync dirty entries to disk
154
+ */
155
+ async sync() {
156
+ // In our implementation, writes are immediate (no write buffering)
157
+ // This method is here for API compatibility
158
+ this.dirtyKeys.clear();
159
+ }
160
+ /**
161
+ * Shutdown (flush and cleanup)
162
+ */
163
+ async shutdown() {
164
+ if (this.syncTimer) {
165
+ clearInterval(this.syncTimer);
166
+ this.syncTimer = null;
167
+ }
168
+ await this.sync();
169
+ this.initialized = false;
170
+ }
171
+ /**
172
+ * Get cache statistics
173
+ */
174
+ async getStats() {
175
+ try {
176
+ const files = await fs.readdir(this.cacheDir);
177
+ const entries = [];
178
+ for (const file of files) {
179
+ if (file.endsWith('.tmp')) {
180
+ continue;
181
+ }
182
+ try {
183
+ const content = await fs.readFile(path.join(this.cacheDir, file), 'utf-8');
184
+ const entry = JSON.parse(content);
185
+ entries.push(entry);
186
+ }
187
+ catch {
188
+ // Skip corrupted entries
189
+ }
190
+ }
191
+ const totalSize = entries.reduce((sum, entry) => sum + entry.size, 0);
192
+ const timestamps = entries.map(e => e.createdAt);
193
+ return {
194
+ totalEntries: entries.length,
195
+ totalSize,
196
+ oldestEntry: timestamps.length > 0 ? Math.min(...timestamps) : null,
197
+ newestEntry: timestamps.length > 0 ? Math.max(...timestamps) : null,
198
+ };
199
+ }
200
+ catch (error) {
201
+ return {
202
+ totalEntries: 0,
203
+ totalSize: 0,
204
+ oldestEntry: null,
205
+ newestEntry: null,
206
+ };
207
+ }
208
+ }
209
+ /**
210
+ * Enforce maximum cache size by removing oldest entries
211
+ */
212
+ async enforceMaxSize() {
213
+ try {
214
+ const files = await fs.readdir(this.cacheDir);
215
+ const entries = [];
216
+ // Load all entries
217
+ for (const file of files) {
218
+ if (file.endsWith('.tmp')) {
219
+ continue;
220
+ }
221
+ try {
222
+ const filePath = path.join(this.cacheDir, file);
223
+ const content = await fs.readFile(filePath, 'utf-8');
224
+ const entry = JSON.parse(content);
225
+ // Remove expired entries
226
+ if (Date.now() > entry.expiresAt) {
227
+ await fs.unlink(filePath);
228
+ continue;
229
+ }
230
+ entries.push({ file, entry });
231
+ }
232
+ catch {
233
+ // Skip corrupted entries
234
+ }
235
+ }
236
+ // Calculate total size
237
+ const totalSize = entries.reduce((sum, { entry }) => sum + entry.size, 0);
238
+ // If under limit, we're done
239
+ if (totalSize <= this.maxSize) {
240
+ return;
241
+ }
242
+ // Sort by creation time (oldest first)
243
+ entries.sort((a, b) => a.entry.createdAt - b.entry.createdAt);
244
+ // Remove oldest entries until under limit
245
+ let currentSize = totalSize;
246
+ for (const { file, entry } of entries) {
247
+ if (currentSize <= this.maxSize) {
248
+ break;
249
+ }
250
+ try {
251
+ await fs.unlink(path.join(this.cacheDir, file));
252
+ currentSize -= entry.size;
253
+ }
254
+ catch {
255
+ // Skip deletion errors
256
+ }
257
+ }
258
+ }
259
+ catch (error) {
260
+ if (process.env.DEBUG) {
261
+ console.warn('Failed to enforce max size:', error.message);
262
+ }
263
+ }
264
+ }
265
+ /**
266
+ * Ensure cache directory exists
267
+ */
268
+ async ensureCacheDir() {
269
+ try {
270
+ await fs.mkdir(this.cacheDir, { recursive: true });
271
+ }
272
+ catch (error) {
273
+ if (error.code !== 'EEXIST') {
274
+ throw new Error(`Failed to create cache directory: ${error.message}`);
275
+ }
276
+ }
277
+ }
278
+ /**
279
+ * Get file path for a cache key
280
+ */
281
+ getFilePath(key) {
282
+ // Hash the key to create a safe filename
283
+ const hash = crypto.createHash('sha256').update(key).digest('hex');
284
+ return path.join(this.cacheDir, `${hash}.json`);
285
+ }
286
+ }
287
+ exports.DiskCacheManager = DiskCacheManager;
288
+ /**
289
+ * Global singleton instance
290
+ */
291
+ exports.diskCacheManager = new DiskCacheManager();
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Converts markdown text to Notion block objects
3
+ *
4
+ * This is a simple, secure replacement for @tryfabric/martian's markdownToBlocks
5
+ * to eliminate security vulnerabilities from the katex dependency chain.
6
+ *
7
+ * Supports:
8
+ * - Headings (h1, h2, h3)
9
+ * - Paragraphs
10
+ * - Bulleted lists
11
+ * - Numbered lists
12
+ * - Code blocks
13
+ * - Quotes
14
+ * - Bold, italic, and inline code formatting
15
+ *
16
+ * @param markdown - The markdown string to convert
17
+ * @returns Array of Notion block objects
18
+ */
19
+ export declare function markdownToBlocks(markdown: string): any[];
@@ -0,0 +1,259 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.markdownToBlocks = markdownToBlocks;
4
+ /**
5
+ * Converts markdown text to Notion block objects
6
+ *
7
+ * This is a simple, secure replacement for @tryfabric/martian's markdownToBlocks
8
+ * to eliminate security vulnerabilities from the katex dependency chain.
9
+ *
10
+ * Supports:
11
+ * - Headings (h1, h2, h3)
12
+ * - Paragraphs
13
+ * - Bulleted lists
14
+ * - Numbered lists
15
+ * - Code blocks
16
+ * - Quotes
17
+ * - Bold, italic, and inline code formatting
18
+ *
19
+ * @param markdown - The markdown string to convert
20
+ * @returns Array of Notion block objects
21
+ */
22
+ function markdownToBlocks(markdown) {
23
+ const blocks = [];
24
+ const lines = markdown.split('\n');
25
+ let i = 0;
26
+ while (i < lines.length) {
27
+ const line = lines[i];
28
+ const trimmedLine = line.trim();
29
+ // Skip empty lines at the top level
30
+ if (!trimmedLine) {
31
+ i++;
32
+ continue;
33
+ }
34
+ // Headings
35
+ const headingMatch = trimmedLine.match(/^(#{1,3})\s+(.+)$/);
36
+ if (headingMatch) {
37
+ const level = headingMatch[1].length;
38
+ const text = headingMatch[2];
39
+ const headingType = level === 1 ? 'heading_1' : level === 2 ? 'heading_2' : 'heading_3';
40
+ blocks.push({
41
+ object: 'block',
42
+ type: headingType,
43
+ [headingType]: {
44
+ rich_text: parseRichText(text),
45
+ },
46
+ });
47
+ i++;
48
+ continue;
49
+ }
50
+ // Code blocks
51
+ if (trimmedLine.startsWith('```')) {
52
+ const language = trimmedLine.slice(3).trim() || 'plain text';
53
+ const codeLines = [];
54
+ i++;
55
+ while (i < lines.length && !lines[i].trim().startsWith('```')) {
56
+ codeLines.push(lines[i]);
57
+ i++;
58
+ }
59
+ blocks.push({
60
+ object: 'block',
61
+ type: 'code',
62
+ code: {
63
+ rich_text: [{
64
+ type: 'text',
65
+ text: { content: codeLines.join('\n') },
66
+ }],
67
+ language: language,
68
+ },
69
+ });
70
+ i++; // Skip closing ```
71
+ continue;
72
+ }
73
+ // Block quotes
74
+ if (trimmedLine.startsWith('>')) {
75
+ const quoteText = trimmedLine.slice(1).trim();
76
+ blocks.push({
77
+ object: 'block',
78
+ type: 'quote',
79
+ quote: {
80
+ rich_text: parseRichText(quoteText),
81
+ },
82
+ });
83
+ i++;
84
+ continue;
85
+ }
86
+ // Bulleted lists
87
+ if (trimmedLine.match(/^[-*]\s+/)) {
88
+ const text = trimmedLine.replace(/^[-*]\s+/, '');
89
+ blocks.push({
90
+ object: 'block',
91
+ type: 'bulleted_list_item',
92
+ bulleted_list_item: {
93
+ rich_text: parseRichText(text),
94
+ },
95
+ });
96
+ i++;
97
+ continue;
98
+ }
99
+ // Numbered lists
100
+ if (trimmedLine.match(/^\d+\.\s+/)) {
101
+ const text = trimmedLine.replace(/^\d+\.\s+/, '');
102
+ blocks.push({
103
+ object: 'block',
104
+ type: 'numbered_list_item',
105
+ numbered_list_item: {
106
+ rich_text: parseRichText(text),
107
+ },
108
+ });
109
+ i++;
110
+ continue;
111
+ }
112
+ // Horizontal rule
113
+ if (trimmedLine.match(/^(-{3,}|\*{3,}|_{3,})$/)) {
114
+ blocks.push({
115
+ object: 'block',
116
+ type: 'divider',
117
+ divider: {},
118
+ });
119
+ i++;
120
+ continue;
121
+ }
122
+ // Regular paragraph
123
+ blocks.push({
124
+ object: 'block',
125
+ type: 'paragraph',
126
+ paragraph: {
127
+ rich_text: parseRichText(trimmedLine),
128
+ },
129
+ });
130
+ i++;
131
+ }
132
+ return blocks;
133
+ }
134
+ /**
135
+ * Parse markdown text into Notion rich text format
136
+ * Supports: **bold**, *italic*, `code`, and [links](url)
137
+ */
138
+ function parseRichText(text) {
139
+ if (!text) {
140
+ return [{ type: 'text', text: { content: '' } }];
141
+ }
142
+ const richText = [];
143
+ let currentText = '';
144
+ let i = 0;
145
+ while (i < text.length) {
146
+ // Bold: **text**
147
+ if (text[i] === '*' && text[i + 1] === '*') {
148
+ // Save any accumulated plain text
149
+ if (currentText) {
150
+ richText.push({ type: 'text', text: { content: currentText } });
151
+ currentText = '';
152
+ }
153
+ // Find closing **
154
+ i += 2;
155
+ let boldText = '';
156
+ while (i < text.length && !(text[i] === '*' && text[i + 1] === '*')) {
157
+ boldText += text[i];
158
+ i++;
159
+ }
160
+ richText.push({
161
+ type: 'text',
162
+ text: { content: boldText },
163
+ annotations: { bold: true },
164
+ });
165
+ i += 2; // Skip closing **
166
+ continue;
167
+ }
168
+ // Italic: *text* or _text_
169
+ if ((text[i] === '*' || text[i] === '_') && text[i + 1] !== text[i]) {
170
+ const marker = text[i];
171
+ // Save any accumulated plain text
172
+ if (currentText) {
173
+ richText.push({ type: 'text', text: { content: currentText } });
174
+ currentText = '';
175
+ }
176
+ // Find closing marker
177
+ i++;
178
+ let italicText = '';
179
+ while (i < text.length && text[i] !== marker) {
180
+ italicText += text[i];
181
+ i++;
182
+ }
183
+ richText.push({
184
+ type: 'text',
185
+ text: { content: italicText },
186
+ annotations: { italic: true },
187
+ });
188
+ i++; // Skip closing marker
189
+ continue;
190
+ }
191
+ // Inline code: `text`
192
+ if (text[i] === '`') {
193
+ // Save any accumulated plain text
194
+ if (currentText) {
195
+ richText.push({ type: 'text', text: { content: currentText } });
196
+ currentText = '';
197
+ }
198
+ // Find closing `
199
+ i++;
200
+ let codeText = '';
201
+ while (i < text.length && text[i] !== '`') {
202
+ codeText += text[i];
203
+ i++;
204
+ }
205
+ richText.push({
206
+ type: 'text',
207
+ text: { content: codeText },
208
+ annotations: { code: true },
209
+ });
210
+ i++; // Skip closing `
211
+ continue;
212
+ }
213
+ // Links: [text](url)
214
+ if (text[i] === '[') {
215
+ const linkStart = i;
216
+ let linkText = '';
217
+ i++;
218
+ // Find closing ]
219
+ while (i < text.length && text[i] !== ']') {
220
+ linkText += text[i];
221
+ i++;
222
+ }
223
+ // Check if followed by (url)
224
+ if (i < text.length && text[i] === ']' && text[i + 1] === '(') {
225
+ i += 2; // Skip ](
226
+ let url = '';
227
+ while (i < text.length && text[i] !== ')') {
228
+ url += text[i];
229
+ i++;
230
+ }
231
+ // Save any accumulated plain text
232
+ if (currentText) {
233
+ richText.push({ type: 'text', text: { content: currentText } });
234
+ currentText = '';
235
+ }
236
+ richText.push({
237
+ type: 'text',
238
+ text: { content: linkText, link: { url } },
239
+ });
240
+ i++; // Skip closing )
241
+ continue;
242
+ }
243
+ else {
244
+ // Not a link, treat as plain text
245
+ currentText += text.slice(linkStart, i + 1);
246
+ i++;
247
+ continue;
248
+ }
249
+ }
250
+ // Regular character
251
+ currentText += text[i];
252
+ i++;
253
+ }
254
+ // Add any remaining text
255
+ if (currentText) {
256
+ richText.push({ type: 'text', text: { content: currentText } });
257
+ }
258
+ return richText.length > 0 ? richText : [{ type: 'text', text: { content: '' } }];
259
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Notion ID Resolver
3
+ *
4
+ * Hybrid resolution system that supports:
5
+ * - URLs: https://www.notion.so/database-id
6
+ * - Direct IDs: database-id
7
+ * - Names: "Tasks Database" (via cache lookup and API fallback)
8
+ * - Smart database_id → data_source_id conversion
9
+ *
10
+ * Resolution stages:
11
+ * 1. URL extraction
12
+ * 2. Direct ID validation
13
+ * 3. Cache lookup (exact + aliases)
14
+ * 4. API search fallback
15
+ * 5. Smart database_id → data_source_id resolution (for databases)
16
+ */
17
+ /**
18
+ * Resolve Notion input (URL, ID, or name) to a clean Notion ID
19
+ *
20
+ * Supports URLs, IDs, and name-based lookups via cache and API search.
21
+ * For databases, automatically detects and converts database_id to data_source_id.
22
+ *
23
+ * @param input - Database/page name, ID, or URL
24
+ * @param type - Resource type (for better error messages)
25
+ * @returns Clean Notion ID (32 hex characters without dashes)
26
+ * @throws NotionCLIError if input cannot be resolved
27
+ *
28
+ * @example
29
+ * // URL
30
+ * await resolveNotionId('https://notion.so/1fb79d4c71bb8032b722c82305b63a00')
31
+ * // Returns: '1fb79d4c71bb8032b722c82305b63a00'
32
+ *
33
+ * @example
34
+ * // Direct ID
35
+ * await resolveNotionId('1fb79d4c71bb8032b722c82305b63a00')
36
+ * // Returns: '1fb79d4c71bb8032b722c82305b63a00'
37
+ *
38
+ * @example
39
+ * // Name (via cache or API)
40
+ * await resolveNotionId('Tasks Database', 'database')
41
+ * // Returns: '1fb79d4c71bb8032b722c82305b63a00'
42
+ *
43
+ * @example
44
+ * // database_id auto-conversion
45
+ * await resolveNotionId('abc123...', 'database')
46
+ * // If abc123 is a database_id, auto-resolves to data_source_id
47
+ */
48
+ export declare function resolveNotionId(input: string, type?: 'database' | 'page'): Promise<string>;