@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
package/dist/cache.js ADDED
@@ -0,0 +1,351 @@
1
+ "use strict";
2
+ /**
3
+ * Simple in-memory caching layer for Notion API responses
4
+ * Supports TTL (time-to-live) and cache invalidation
5
+ * Integrated with disk cache for persistence across CLI invocations
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.cacheManager = exports.CacheManager = void 0;
9
+ const disk_cache_1 = require("./utils/disk-cache");
10
+ /**
11
+ * Check if verbose logging is enabled
12
+ */
13
+ function isVerboseEnabled() {
14
+ return process.env.DEBUG === 'true' ||
15
+ process.env.NOTION_CLI_DEBUG === 'true' ||
16
+ process.env.NOTION_CLI_VERBOSE === 'true';
17
+ }
18
+ /**
19
+ * Log structured cache event to stderr
20
+ * Never pollutes stdout - safe for JSON output
21
+ */
22
+ function logCacheEvent(event) {
23
+ // Only log if verbose mode is enabled
24
+ if (!isVerboseEnabled()) {
25
+ return;
26
+ }
27
+ // Always write to stderr, never stdout
28
+ console.error(JSON.stringify(event));
29
+ }
30
+ class CacheManager {
31
+ constructor(config) {
32
+ this.cache = new Map();
33
+ this.stats = {
34
+ hits: 0,
35
+ misses: 0,
36
+ sets: 0,
37
+ evictions: 0,
38
+ size: 0,
39
+ };
40
+ // Default configuration
41
+ this.config = {
42
+ enabled: process.env.NOTION_CLI_CACHE_ENABLED !== 'false',
43
+ defaultTtl: parseInt(process.env.NOTION_CLI_CACHE_TTL || '300000', 10), // 5 minutes default
44
+ maxSize: parseInt(process.env.NOTION_CLI_CACHE_MAX_SIZE || '1000', 10),
45
+ ttlByType: {
46
+ dataSource: parseInt(process.env.NOTION_CLI_CACHE_DS_TTL || '600000', 10), // 10 min
47
+ database: parseInt(process.env.NOTION_CLI_CACHE_DB_TTL || '600000', 10), // 10 min
48
+ user: parseInt(process.env.NOTION_CLI_CACHE_USER_TTL || '3600000', 10), // 1 hour
49
+ page: parseInt(process.env.NOTION_CLI_CACHE_PAGE_TTL || '60000', 10), // 1 min
50
+ block: parseInt(process.env.NOTION_CLI_CACHE_BLOCK_TTL || '30000', 10), // 30 sec
51
+ },
52
+ ...config,
53
+ };
54
+ }
55
+ /**
56
+ * Generate a cache key from resource type and identifiers
57
+ */
58
+ generateKey(type, ...identifiers) {
59
+ return `${type}:${identifiers.map(id => typeof id === 'object' ? JSON.stringify(id) : String(id)).join(':')}`;
60
+ }
61
+ /**
62
+ * Check if a cache entry is still valid
63
+ */
64
+ isValid(entry) {
65
+ const now = Date.now();
66
+ return now - entry.timestamp < entry.ttl;
67
+ }
68
+ /**
69
+ * Evict expired entries
70
+ */
71
+ evictExpired() {
72
+ let evictedCount = 0;
73
+ for (const [key, entry] of this.cache.entries()) {
74
+ if (!this.isValid(entry)) {
75
+ this.cache.delete(key);
76
+ this.stats.evictions++;
77
+ evictedCount++;
78
+ }
79
+ }
80
+ this.stats.size = this.cache.size;
81
+ // Log eviction event if any entries were evicted
82
+ if (evictedCount > 0 && isVerboseEnabled()) {
83
+ logCacheEvent({
84
+ level: 'debug',
85
+ event: 'cache_evict',
86
+ namespace: 'expired',
87
+ cache_size: this.cache.size,
88
+ timestamp: new Date().toISOString(),
89
+ });
90
+ }
91
+ }
92
+ /**
93
+ * Evict oldest entries if cache is full
94
+ */
95
+ evictOldest() {
96
+ if (this.cache.size >= this.config.maxSize) {
97
+ // Find and remove oldest entry
98
+ let oldestKey = null;
99
+ let oldestTime = Infinity;
100
+ for (const [key, entry] of this.cache.entries()) {
101
+ if (entry.timestamp < oldestTime) {
102
+ oldestTime = entry.timestamp;
103
+ oldestKey = key;
104
+ }
105
+ }
106
+ if (oldestKey) {
107
+ this.cache.delete(oldestKey);
108
+ this.stats.evictions++;
109
+ // Log LRU eviction
110
+ logCacheEvent({
111
+ level: 'debug',
112
+ event: 'cache_evict',
113
+ namespace: 'lru',
114
+ key: oldestKey,
115
+ cache_size: this.cache.size,
116
+ timestamp: new Date().toISOString(),
117
+ });
118
+ }
119
+ }
120
+ }
121
+ /**
122
+ * Get a value from cache (checks memory, then disk)
123
+ */
124
+ async get(type, ...identifiers) {
125
+ if (!this.config.enabled) {
126
+ return null;
127
+ }
128
+ const key = this.generateKey(type, ...identifiers);
129
+ const entry = this.cache.get(key);
130
+ // Check memory cache first
131
+ if (entry && this.isValid(entry)) {
132
+ this.stats.hits++;
133
+ // Log cache hit
134
+ logCacheEvent({
135
+ level: 'debug',
136
+ event: 'cache_hit',
137
+ namespace: type,
138
+ key: identifiers.join(':'),
139
+ age_ms: Date.now() - entry.timestamp,
140
+ ttl_ms: entry.ttl,
141
+ timestamp: new Date().toISOString(),
142
+ });
143
+ return entry.data;
144
+ }
145
+ // Remove invalid memory entry
146
+ if (entry) {
147
+ this.cache.delete(key);
148
+ this.stats.evictions++;
149
+ // Log eviction event
150
+ logCacheEvent({
151
+ level: 'debug',
152
+ event: 'cache_evict',
153
+ namespace: type,
154
+ key: identifiers.join(':'),
155
+ cache_size: this.cache.size,
156
+ timestamp: new Date().toISOString(),
157
+ });
158
+ }
159
+ // Check disk cache (only if enabled)
160
+ const diskEnabled = process.env.NOTION_CLI_DISK_CACHE_ENABLED !== 'false';
161
+ if (diskEnabled) {
162
+ try {
163
+ const diskEntry = await disk_cache_1.diskCacheManager.get(key);
164
+ if (diskEntry && diskEntry.data) {
165
+ const entry = diskEntry.data;
166
+ // Validate disk entry
167
+ if (this.isValid(entry)) {
168
+ // Promote to memory cache
169
+ this.cache.set(key, entry);
170
+ this.stats.hits++;
171
+ // Log cache hit (from disk)
172
+ logCacheEvent({
173
+ level: 'debug',
174
+ event: 'cache_hit',
175
+ namespace: type,
176
+ key: identifiers.join(':'),
177
+ age_ms: Date.now() - entry.timestamp,
178
+ ttl_ms: entry.ttl,
179
+ timestamp: new Date().toISOString(),
180
+ });
181
+ return entry.data;
182
+ }
183
+ else {
184
+ // Remove expired disk entry
185
+ disk_cache_1.diskCacheManager.invalidate(key).catch(() => { });
186
+ }
187
+ }
188
+ }
189
+ catch (error) {
190
+ // Silently ignore disk cache errors
191
+ }
192
+ }
193
+ // Cache miss
194
+ this.stats.misses++;
195
+ // Log cache miss
196
+ logCacheEvent({
197
+ level: 'debug',
198
+ event: 'cache_miss',
199
+ namespace: type,
200
+ key: identifiers.join(':'),
201
+ timestamp: new Date().toISOString(),
202
+ });
203
+ return null;
204
+ }
205
+ /**
206
+ * Set a value in cache with optional custom TTL (writes to memory and disk)
207
+ */
208
+ set(type, data, customTtl, ...identifiers) {
209
+ if (!this.config.enabled) {
210
+ return;
211
+ }
212
+ // Evict expired entries periodically
213
+ if (this.cache.size > 0 && Math.random() < 0.1) {
214
+ this.evictExpired();
215
+ }
216
+ // Evict oldest if at capacity
217
+ this.evictOldest();
218
+ const key = this.generateKey(type, ...identifiers);
219
+ const ttl = customTtl || this.config.ttlByType[type] || this.config.defaultTtl;
220
+ const entry = {
221
+ data,
222
+ timestamp: Date.now(),
223
+ ttl,
224
+ };
225
+ this.cache.set(key, entry);
226
+ this.stats.sets++;
227
+ this.stats.size = this.cache.size;
228
+ // Log cache set
229
+ logCacheEvent({
230
+ level: 'debug',
231
+ event: 'cache_set',
232
+ namespace: type,
233
+ key: identifiers.join(':'),
234
+ ttl_ms: ttl,
235
+ cache_size: this.cache.size,
236
+ timestamp: new Date().toISOString(),
237
+ });
238
+ // Async write to disk cache (fire-and-forget)
239
+ const diskEnabled = process.env.NOTION_CLI_DISK_CACHE_ENABLED !== 'false';
240
+ if (diskEnabled) {
241
+ disk_cache_1.diskCacheManager.set(key, entry, ttl).catch(() => {
242
+ // Silently ignore disk cache errors
243
+ });
244
+ }
245
+ }
246
+ /**
247
+ * Invalidate specific cache entries by type and optional identifiers
248
+ */
249
+ invalidate(type, ...identifiers) {
250
+ const diskEnabled = process.env.NOTION_CLI_DISK_CACHE_ENABLED !== 'false';
251
+ if (identifiers.length === 0) {
252
+ // Invalidate all entries of this type
253
+ const pattern = `${type}:`;
254
+ let invalidatedCount = 0;
255
+ for (const key of this.cache.keys()) {
256
+ if (key.startsWith(pattern)) {
257
+ this.cache.delete(key);
258
+ this.stats.evictions++;
259
+ invalidatedCount++;
260
+ // Also invalidate from disk (fire-and-forget)
261
+ if (diskEnabled) {
262
+ disk_cache_1.diskCacheManager.invalidate(key).catch(() => { });
263
+ }
264
+ }
265
+ }
266
+ // Log bulk invalidation
267
+ if (invalidatedCount > 0) {
268
+ logCacheEvent({
269
+ level: 'debug',
270
+ event: 'cache_invalidate',
271
+ namespace: type,
272
+ cache_size: this.cache.size,
273
+ timestamp: new Date().toISOString(),
274
+ });
275
+ }
276
+ }
277
+ else {
278
+ // Invalidate specific entry
279
+ const key = this.generateKey(type, ...identifiers);
280
+ if (this.cache.delete(key)) {
281
+ this.stats.evictions++;
282
+ // Also invalidate from disk (fire-and-forget)
283
+ if (diskEnabled) {
284
+ disk_cache_1.diskCacheManager.invalidate(key).catch(() => { });
285
+ }
286
+ // Log specific invalidation
287
+ logCacheEvent({
288
+ level: 'debug',
289
+ event: 'cache_invalidate',
290
+ namespace: type,
291
+ key: identifiers.join(':'),
292
+ cache_size: this.cache.size,
293
+ timestamp: new Date().toISOString(),
294
+ });
295
+ }
296
+ }
297
+ this.stats.size = this.cache.size;
298
+ }
299
+ /**
300
+ * Clear all cache entries (memory and disk)
301
+ */
302
+ clear() {
303
+ const previousSize = this.cache.size;
304
+ this.cache.clear();
305
+ this.stats.evictions += this.stats.size;
306
+ this.stats.size = 0;
307
+ // Also clear disk cache (fire-and-forget)
308
+ const diskEnabled = process.env.NOTION_CLI_DISK_CACHE_ENABLED !== 'false';
309
+ if (diskEnabled) {
310
+ disk_cache_1.diskCacheManager.clear().catch(() => { });
311
+ }
312
+ // Log cache clear
313
+ if (previousSize > 0) {
314
+ logCacheEvent({
315
+ level: 'info',
316
+ event: 'cache_invalidate',
317
+ namespace: 'all',
318
+ cache_size: 0,
319
+ timestamp: new Date().toISOString(),
320
+ });
321
+ }
322
+ }
323
+ /**
324
+ * Get cache statistics
325
+ */
326
+ getStats() {
327
+ return { ...this.stats };
328
+ }
329
+ /**
330
+ * Get cache hit rate
331
+ */
332
+ getHitRate() {
333
+ const total = this.stats.hits + this.stats.misses;
334
+ return total > 0 ? this.stats.hits / total : 0;
335
+ }
336
+ /**
337
+ * Check if cache is enabled
338
+ */
339
+ isEnabled() {
340
+ return this.config.enabled;
341
+ }
342
+ /**
343
+ * Get current configuration
344
+ */
345
+ getConfig() {
346
+ return { ...this.config };
347
+ }
348
+ }
349
+ exports.CacheManager = CacheManager;
350
+ // Singleton instance
351
+ exports.cacheManager = new CacheManager();
@@ -0,0 +1,43 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class BatchRetrieve extends Command {
3
+ static description: string;
4
+ static aliases: string[];
5
+ static examples: {
6
+ description: string;
7
+ command: string;
8
+ }[];
9
+ static args: {
10
+ ids: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
11
+ };
12
+ static flags: {
13
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
14
+ 'page-size': import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
15
+ retry: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
16
+ timeout: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
17
+ 'no-cache': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
18
+ verbose: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
19
+ minimal: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
20
+ markdown: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
21
+ 'compact-json': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
22
+ pretty: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
23
+ columns: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
24
+ sort: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
25
+ filter: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
26
+ csv: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
27
+ extended: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
28
+ 'no-truncate': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
29
+ 'no-header': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
30
+ ids: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
31
+ type: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
32
+ raw: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
33
+ };
34
+ /**
35
+ * Read IDs from stdin
36
+ */
37
+ private readStdin;
38
+ /**
39
+ * Retrieve a single resource and handle errors
40
+ */
41
+ private retrieveResource;
42
+ run(): Promise<void>;
43
+ }
@@ -0,0 +1,265 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const table_formatter_1 = require("../../utils/table-formatter");
5
+ const notion = require("../../notion");
6
+ const helper_1 = require("../../helper");
7
+ const base_flags_1 = require("../../base-flags");
8
+ const errors_1 = require("../../errors");
9
+ const client_1 = require("@notionhq/client");
10
+ const readline = require("readline");
11
+ class BatchRetrieve extends core_1.Command {
12
+ /**
13
+ * Read IDs from stdin
14
+ */
15
+ async readStdin() {
16
+ return new Promise((resolve, reject) => {
17
+ const ids = [];
18
+ const rl = readline.createInterface({
19
+ input: process.stdin,
20
+ output: process.stdout,
21
+ terminal: false,
22
+ });
23
+ rl.on('line', (line) => {
24
+ const trimmed = line.trim();
25
+ if (trimmed) {
26
+ ids.push(trimmed);
27
+ }
28
+ });
29
+ rl.on('close', () => {
30
+ resolve(ids);
31
+ });
32
+ rl.on('error', (err) => {
33
+ reject(err);
34
+ });
35
+ // Timeout after 5 seconds if no input
36
+ setTimeout(() => {
37
+ rl.close();
38
+ resolve(ids);
39
+ }, 5000);
40
+ });
41
+ }
42
+ /**
43
+ * Retrieve a single resource and handle errors
44
+ */
45
+ async retrieveResource(id, type) {
46
+ try {
47
+ let data;
48
+ switch (type) {
49
+ case 'page': {
50
+ const pageResponse = await notion.retrievePage({ page_id: id });
51
+ if (!(0, client_1.isFullPage)(pageResponse)) {
52
+ throw new errors_1.NotionCLIError(errors_1.NotionCLIErrorCode.API_ERROR, 'Received partial page response instead of full page', [], { attemptedId: id });
53
+ }
54
+ data = pageResponse;
55
+ break;
56
+ }
57
+ case 'block': {
58
+ const blockResponse = await notion.retrieveBlock(id);
59
+ if (!(0, client_1.isFullBlock)(blockResponse)) {
60
+ throw new errors_1.NotionCLIError(errors_1.NotionCLIErrorCode.API_ERROR, 'Received partial block response instead of full block', [], { attemptedId: id });
61
+ }
62
+ data = blockResponse;
63
+ break;
64
+ }
65
+ case 'database':
66
+ data = await notion.retrieveDataSource(id);
67
+ break;
68
+ default:
69
+ throw new errors_1.NotionCLIError(errors_1.NotionCLIErrorCode.VALIDATION_ERROR, `Invalid resource type: ${type}`, [], { userInput: type, resourceType: type });
70
+ }
71
+ return {
72
+ id,
73
+ success: true,
74
+ data,
75
+ };
76
+ }
77
+ catch (error) {
78
+ const cliError = error instanceof errors_1.NotionCLIError
79
+ ? error
80
+ : (0, errors_1.wrapNotionError)(error, {
81
+ attemptedId: id,
82
+ userInput: id
83
+ });
84
+ return {
85
+ id,
86
+ success: false,
87
+ error: cliError.code,
88
+ message: cliError.userMessage,
89
+ };
90
+ }
91
+ }
92
+ async run() {
93
+ const { args, flags } = await this.parse(BatchRetrieve);
94
+ try {
95
+ // Get IDs from args, flags, or stdin
96
+ let ids = [];
97
+ if (args.ids) {
98
+ // From positional argument
99
+ ids = args.ids.split(',').map(id => id.trim()).filter(id => id);
100
+ }
101
+ else if (flags.ids) {
102
+ // From --ids flag
103
+ ids = flags.ids.split(',').map(id => id.trim()).filter(id => id);
104
+ }
105
+ else if (!process.stdin.isTTY) {
106
+ // From stdin
107
+ ids = await this.readStdin();
108
+ }
109
+ if (ids.length === 0) {
110
+ throw new errors_1.NotionCLIError(errors_1.NotionCLIErrorCode.VALIDATION_ERROR, 'No IDs provided. Use --ids flag, positional argument, or pipe IDs via stdin', [
111
+ {
112
+ description: 'Provide IDs via --ids flag',
113
+ command: 'notion-cli batch retrieve --ids ID1,ID2,ID3'
114
+ },
115
+ {
116
+ description: 'Or pipe IDs from a file',
117
+ command: 'cat ids.txt | notion-cli batch retrieve'
118
+ }
119
+ ]);
120
+ }
121
+ // Fetch all resources in parallel
122
+ const results = await Promise.all(ids.map(id => this.retrieveResource(id, flags.type)));
123
+ // Count successes and failures
124
+ const successCount = results.filter(r => r.success).length;
125
+ const failureCount = results.filter(r => !r.success).length;
126
+ // Handle JSON output for automation (takes precedence)
127
+ if (flags.json) {
128
+ this.log(JSON.stringify({
129
+ success: successCount > 0,
130
+ total: results.length,
131
+ succeeded: successCount,
132
+ failed: failureCount,
133
+ results: results,
134
+ timestamp: new Date().toISOString(),
135
+ }, null, 2));
136
+ process.exit(failureCount === 0 ? 0 : 1);
137
+ return;
138
+ }
139
+ // Handle compact JSON output
140
+ if (flags['compact-json']) {
141
+ (0, helper_1.outputCompactJson)({
142
+ total: results.length,
143
+ succeeded: successCount,
144
+ failed: failureCount,
145
+ results: results,
146
+ });
147
+ process.exit(failureCount === 0 ? 0 : 1);
148
+ return;
149
+ }
150
+ // Handle raw JSON output
151
+ if (flags.raw) {
152
+ (0, helper_1.outputRawJson)(results);
153
+ process.exit(failureCount === 0 ? 0 : 1);
154
+ return;
155
+ }
156
+ // Handle table output (default)
157
+ const tableData = results.map(result => {
158
+ if (result.success && result.data) {
159
+ let title = '';
160
+ if ('object' in result.data) {
161
+ if (result.data.object === 'page') {
162
+ title = (0, helper_1.getPageTitle)(result.data);
163
+ }
164
+ else if (result.data.object === 'data_source') {
165
+ title = (0, helper_1.getDataSourceTitle)(result.data);
166
+ }
167
+ else if (result.data.object === 'block') {
168
+ title = (0, helper_1.getBlockPlainText)(result.data);
169
+ }
170
+ }
171
+ return {
172
+ id: result.id,
173
+ status: 'success',
174
+ type: result.data.object || flags.type,
175
+ title: title || '-',
176
+ };
177
+ }
178
+ else {
179
+ return {
180
+ id: result.id,
181
+ status: 'failed',
182
+ type: flags.type,
183
+ title: result.message || result.error || 'Unknown error',
184
+ };
185
+ }
186
+ });
187
+ const columns = {
188
+ id: {},
189
+ status: {},
190
+ type: {},
191
+ title: {},
192
+ };
193
+ const options = {
194
+ printLine: this.log.bind(this),
195
+ ...flags,
196
+ };
197
+ (0, table_formatter_1.formatTable)(tableData, columns, options);
198
+ // Print summary
199
+ this.log(`\nTotal: ${results.length} | Succeeded: ${successCount} | Failed: ${failureCount}`);
200
+ process.exit(failureCount === 0 ? 0 : 1);
201
+ }
202
+ catch (error) {
203
+ const cliError = error instanceof errors_1.NotionCLIError
204
+ ? error
205
+ : (0, errors_1.wrapNotionError)(error, {
206
+ endpoint: 'batch.retrieve'
207
+ });
208
+ if (flags.json) {
209
+ this.log(JSON.stringify(cliError.toJSON(), null, 2));
210
+ }
211
+ else {
212
+ this.error(cliError.toHumanString());
213
+ }
214
+ process.exit(1);
215
+ }
216
+ }
217
+ }
218
+ BatchRetrieve.description = 'Batch retrieve multiple pages, blocks, or data sources';
219
+ BatchRetrieve.aliases = ['batch:r'];
220
+ BatchRetrieve.examples = [
221
+ {
222
+ description: 'Retrieve multiple pages via --ids flag',
223
+ command: '$ notion-cli batch retrieve --ids PAGE_ID_1,PAGE_ID_2,PAGE_ID_3 --compact-json',
224
+ },
225
+ {
226
+ description: 'Retrieve multiple pages from stdin (one ID per line)',
227
+ command: '$ cat page_ids.txt | notion-cli batch retrieve --compact-json',
228
+ },
229
+ {
230
+ description: 'Retrieve multiple blocks',
231
+ command: '$ notion-cli batch retrieve --ids BLOCK_ID_1,BLOCK_ID_2 --type block --json',
232
+ },
233
+ {
234
+ description: 'Retrieve multiple data sources',
235
+ command: '$ notion-cli batch retrieve --ids DS_ID_1,DS_ID_2 --type database --json',
236
+ },
237
+ {
238
+ description: 'Retrieve with raw output',
239
+ command: '$ notion-cli batch retrieve --ids ID1,ID2,ID3 -r',
240
+ },
241
+ ];
242
+ BatchRetrieve.args = {
243
+ ids: core_1.Args.string({
244
+ required: false,
245
+ description: 'Comma-separated list of IDs to retrieve (or use --ids flag or stdin)',
246
+ }),
247
+ };
248
+ BatchRetrieve.flags = {
249
+ ids: core_1.Flags.string({
250
+ description: 'Comma-separated list of IDs to retrieve',
251
+ }),
252
+ type: core_1.Flags.string({
253
+ description: 'Resource type to retrieve (page, block, database)',
254
+ options: ['page', 'block', 'database'],
255
+ default: 'page',
256
+ }),
257
+ raw: core_1.Flags.boolean({
258
+ char: 'r',
259
+ description: 'output raw json (recommended for AI assistants - returns all fields)',
260
+ }),
261
+ ...table_formatter_1.tableFlags,
262
+ ...base_flags_1.OutputFormatFlags,
263
+ ...base_flags_1.AutomationFlags,
264
+ };
265
+ exports.default = BatchRetrieve;