@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
package/dist/retry.js ADDED
@@ -0,0 +1,381 @@
1
+ "use strict";
2
+ /**
3
+ * Enhanced retry logic with exponential backoff and jitter
4
+ * Handles rate limiting, network errors, and transient failures
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.CircuitBreaker = void 0;
8
+ exports.isRetryableError = isRetryableError;
9
+ exports.calculateDelay = calculateDelay;
10
+ exports.fetchWithRetry = fetchWithRetry;
11
+ exports.batchWithRetry = batchWithRetry;
12
+ /**
13
+ * Default retry configuration
14
+ */
15
+ const DEFAULT_CONFIG = {
16
+ maxRetries: parseInt(process.env.NOTION_CLI_MAX_RETRIES || '3', 10),
17
+ baseDelay: parseInt(process.env.NOTION_CLI_BASE_DELAY || '1000', 10), // 1 second
18
+ maxDelay: parseInt(process.env.NOTION_CLI_MAX_DELAY || '30000', 10), // 30 seconds
19
+ exponentialBase: parseFloat(process.env.NOTION_CLI_EXP_BASE || '2'),
20
+ jitterFactor: parseFloat(process.env.NOTION_CLI_JITTER_FACTOR || '0.1'),
21
+ // HTTP status codes that should trigger a retry
22
+ retryableStatusCodes: [408, 429, 500, 502, 503, 504],
23
+ // Notion API error codes that are retryable
24
+ retryableErrorCodes: [
25
+ 'rate_limited',
26
+ 'service_unavailable',
27
+ 'internal_server_error',
28
+ 'conflict_error',
29
+ ],
30
+ };
31
+ /**
32
+ * Check if verbose logging is enabled
33
+ */
34
+ function isVerboseEnabled() {
35
+ return process.env.DEBUG === 'true' ||
36
+ process.env.NOTION_CLI_DEBUG === 'true' ||
37
+ process.env.NOTION_CLI_VERBOSE === 'true';
38
+ }
39
+ /**
40
+ * Log structured retry event to stderr
41
+ * Never pollutes stdout - safe for JSON output
42
+ */
43
+ function logRetryEvent(event) {
44
+ // Only log if verbose mode is enabled
45
+ if (!isVerboseEnabled()) {
46
+ return;
47
+ }
48
+ // Always write to stderr, never stdout
49
+ console.error(JSON.stringify(event));
50
+ }
51
+ /**
52
+ * Extract error reason from error object
53
+ */
54
+ function getErrorReason(error) {
55
+ if (error.code === 'rate_limited' || error.status === 429)
56
+ return 'RATE_LIMITED';
57
+ if (error.status === 503)
58
+ return 'SERVICE_UNAVAILABLE';
59
+ if (error.status === 502)
60
+ return 'BAD_GATEWAY';
61
+ if (error.status === 504)
62
+ return 'GATEWAY_TIMEOUT';
63
+ if (error.status === 500)
64
+ return 'INTERNAL_SERVER_ERROR';
65
+ if (error.status === 408)
66
+ return 'REQUEST_TIMEOUT';
67
+ if (error.code === 'ECONNRESET')
68
+ return 'CONNECTION_RESET';
69
+ if (error.code === 'ETIMEDOUT')
70
+ return 'TIMEOUT';
71
+ if (error.code === 'ENOTFOUND')
72
+ return 'DNS_ERROR';
73
+ if (error.code === 'EAI_AGAIN')
74
+ return 'DNS_LOOKUP_FAILED';
75
+ if (error.code === 'service_unavailable')
76
+ return 'SERVICE_UNAVAILABLE';
77
+ if (error.code === 'internal_server_error')
78
+ return 'INTERNAL_SERVER_ERROR';
79
+ if (error.code === 'conflict_error')
80
+ return 'CONFLICT';
81
+ return 'UNKNOWN';
82
+ }
83
+ /**
84
+ * Extract URL/endpoint from error object
85
+ */
86
+ function extractUrl(error, context) {
87
+ var _a, _b;
88
+ if (error.url)
89
+ return error.url;
90
+ if ((_a = error.request) === null || _a === void 0 ? void 0 : _a.url)
91
+ return error.request.url;
92
+ if ((_b = error.config) === null || _b === void 0 ? void 0 : _b.url)
93
+ return error.config.url;
94
+ return context;
95
+ }
96
+ /**
97
+ * Categorize errors into retryable and non-retryable
98
+ */
99
+ function isRetryableError(error, config = DEFAULT_CONFIG) {
100
+ // Network errors (no response)
101
+ if (!error.status && (error.code === 'ECONNRESET' || error.code === 'ETIMEDOUT' ||
102
+ error.code === 'ENOTFOUND' || error.code === 'EAI_AGAIN')) {
103
+ return true;
104
+ }
105
+ // HTTP status codes
106
+ if (error.status && config.retryableStatusCodes.includes(error.status)) {
107
+ return true;
108
+ }
109
+ // Notion API error codes
110
+ if (error.code && config.retryableErrorCodes.includes(error.code)) {
111
+ return true;
112
+ }
113
+ // Don't retry client errors (400-499, except 408 and 429)
114
+ if (error.status >= 400 && error.status < 500 && error.status !== 408 && error.status !== 429) {
115
+ return false;
116
+ }
117
+ return false;
118
+ }
119
+ /**
120
+ * Calculate delay with exponential backoff and jitter
121
+ */
122
+ function calculateDelay(attempt, config = DEFAULT_CONFIG, retryAfterHeader) {
123
+ // If we have a Retry-After header from rate limiting, use it
124
+ if (retryAfterHeader) {
125
+ const retryAfter = parseInt(retryAfterHeader, 10);
126
+ if (!isNaN(retryAfter)) {
127
+ return Math.min(retryAfter * 1000, config.maxDelay);
128
+ }
129
+ }
130
+ // Calculate exponential backoff: baseDelay * (exponentialBase ^ attempt)
131
+ const exponentialDelay = config.baseDelay * Math.pow(config.exponentialBase, attempt - 1);
132
+ // Cap at maxDelay
133
+ const cappedDelay = Math.min(exponentialDelay, config.maxDelay);
134
+ // Add jitter: random value between -jitterFactor and +jitterFactor
135
+ const jitter = cappedDelay * config.jitterFactor * (Math.random() * 2 - 1);
136
+ const finalDelay = Math.max(0, cappedDelay + jitter);
137
+ return Math.round(finalDelay);
138
+ }
139
+ /**
140
+ * Sleep for specified milliseconds
141
+ */
142
+ function sleep(ms) {
143
+ return new Promise(resolve => setTimeout(resolve, ms));
144
+ }
145
+ /**
146
+ * Enhanced retry wrapper with exponential backoff and jitter
147
+ */
148
+ async function fetchWithRetry(fn, options = {}) {
149
+ var _a, _b;
150
+ const config = { ...DEFAULT_CONFIG, ...options.config };
151
+ const { onRetry, context } = options;
152
+ let lastError;
153
+ let totalDelay = 0;
154
+ for (let attempt = 1; attempt <= config.maxRetries + 1; attempt++) {
155
+ try {
156
+ // Log attempt start (if verbose and not first attempt)
157
+ if (attempt > 1 && isVerboseEnabled()) {
158
+ logRetryEvent({
159
+ level: 'info',
160
+ event: 'retry_attempt',
161
+ attempt,
162
+ max_retries: config.maxRetries,
163
+ context,
164
+ timestamp: new Date().toISOString(),
165
+ });
166
+ }
167
+ return await fn();
168
+ }
169
+ catch (error) {
170
+ lastError = error;
171
+ // Check if we should retry
172
+ const shouldRetry = attempt <= config.maxRetries && isRetryableError(error, config);
173
+ if (!shouldRetry) {
174
+ // Log non-retryable error if verbose
175
+ if (isVerboseEnabled() && attempt > 1) {
176
+ logRetryEvent({
177
+ level: 'error',
178
+ event: 'retry_exhausted',
179
+ attempt,
180
+ max_retries: config.maxRetries,
181
+ reason: getErrorReason(error),
182
+ context,
183
+ status_code: error.status,
184
+ error_code: error.code,
185
+ timestamp: new Date().toISOString(),
186
+ });
187
+ }
188
+ throw error;
189
+ }
190
+ // Calculate delay
191
+ const retryAfter = ((_a = error.headers) === null || _a === void 0 ? void 0 : _a['retry-after']) || ((_b = error.headers) === null || _b === void 0 ? void 0 : _b['Retry-After']);
192
+ const delay = calculateDelay(attempt, config, retryAfter);
193
+ totalDelay += delay;
194
+ // Log rate limit event specifically
195
+ if (error.status === 429 || error.code === 'rate_limited') {
196
+ logRetryEvent({
197
+ level: 'warn',
198
+ event: 'rate_limited',
199
+ attempt,
200
+ max_retries: config.maxRetries,
201
+ reason: 'RATE_LIMITED',
202
+ retry_after_ms: delay,
203
+ url: extractUrl(error, context),
204
+ context,
205
+ status_code: error.status,
206
+ timestamp: new Date().toISOString(),
207
+ });
208
+ }
209
+ else {
210
+ // Log general retry event
211
+ logRetryEvent({
212
+ level: 'warn',
213
+ event: 'retry',
214
+ attempt,
215
+ max_retries: config.maxRetries,
216
+ reason: getErrorReason(error),
217
+ retry_after_ms: delay,
218
+ url: extractUrl(error, context),
219
+ context,
220
+ status_code: error.status,
221
+ error_code: error.code,
222
+ timestamp: new Date().toISOString(),
223
+ });
224
+ }
225
+ // Create retry context
226
+ const retryContext = {
227
+ attempt,
228
+ maxRetries: config.maxRetries,
229
+ lastError: error,
230
+ totalDelay,
231
+ };
232
+ // Call retry callback if provided (for custom logging/monitoring)
233
+ if (onRetry) {
234
+ onRetry(retryContext);
235
+ }
236
+ // Wait before retrying
237
+ await sleep(delay);
238
+ }
239
+ }
240
+ // Should never reach here, but TypeScript needs it
241
+ throw lastError;
242
+ }
243
+ /**
244
+ * Batch retry wrapper for multiple operations
245
+ * Executes operations with retry logic and collects results
246
+ */
247
+ async function batchWithRetry(operations, options = {}) {
248
+ const { concurrency = 5 } = options;
249
+ const results = [];
250
+ // Process operations in batches
251
+ for (let i = 0; i < operations.length; i += concurrency) {
252
+ const batch = operations.slice(i, i + concurrency);
253
+ const batchPromises = batch.map(async (op, index) => {
254
+ try {
255
+ const data = await fetchWithRetry(op, {
256
+ ...options,
257
+ context: `Operation ${i + index + 1}/${operations.length}`,
258
+ });
259
+ return { success: true, data };
260
+ }
261
+ catch (error) {
262
+ return { success: false, error };
263
+ }
264
+ });
265
+ const batchResults = await Promise.all(batchPromises);
266
+ results.push(...batchResults);
267
+ }
268
+ return results;
269
+ }
270
+ /**
271
+ * Retry wrapper with circuit breaker pattern
272
+ * Prevents cascading failures by stopping retries after too many failures
273
+ */
274
+ class CircuitBreaker {
275
+ constructor(failureThreshold = 5, successThreshold = 2, timeout = 60000 // 1 minute
276
+ ) {
277
+ this.failureThreshold = failureThreshold;
278
+ this.successThreshold = successThreshold;
279
+ this.timeout = timeout;
280
+ this.failures = 0;
281
+ this.successes = 0;
282
+ this.state = 'closed';
283
+ this.nextAttempt = 0;
284
+ }
285
+ async execute(fn, retryOptions) {
286
+ if (this.state === 'open') {
287
+ if (Date.now() < this.nextAttempt) {
288
+ // Log circuit breaker open event
289
+ if (isVerboseEnabled()) {
290
+ logRetryEvent({
291
+ level: 'error',
292
+ event: 'retry_exhausted',
293
+ attempt: 0,
294
+ max_retries: 0,
295
+ reason: 'CIRCUIT_OPEN',
296
+ context: 'Circuit breaker is open',
297
+ timestamp: new Date().toISOString(),
298
+ });
299
+ }
300
+ throw new Error('Circuit breaker is open. Too many failures.');
301
+ }
302
+ this.state = 'half-open';
303
+ // Log circuit breaker half-open event
304
+ if (isVerboseEnabled()) {
305
+ logRetryEvent({
306
+ level: 'info',
307
+ event: 'retry_attempt',
308
+ attempt: 1,
309
+ max_retries: this.successThreshold,
310
+ context: 'Circuit breaker entering half-open state',
311
+ timestamp: new Date().toISOString(),
312
+ });
313
+ }
314
+ }
315
+ try {
316
+ const result = await fetchWithRetry(fn, retryOptions);
317
+ this.onSuccess();
318
+ return result;
319
+ }
320
+ catch (error) {
321
+ this.onFailure();
322
+ throw error;
323
+ }
324
+ }
325
+ onSuccess() {
326
+ this.failures = 0;
327
+ if (this.state === 'half-open') {
328
+ this.successes++;
329
+ if (this.successes >= this.successThreshold) {
330
+ this.state = 'closed';
331
+ this.successes = 0;
332
+ // Log circuit breaker closed event
333
+ if (isVerboseEnabled()) {
334
+ logRetryEvent({
335
+ level: 'info',
336
+ event: 'retry_attempt',
337
+ attempt: this.successThreshold,
338
+ max_retries: this.successThreshold,
339
+ context: 'Circuit breaker closed - service recovered',
340
+ timestamp: new Date().toISOString(),
341
+ });
342
+ }
343
+ }
344
+ }
345
+ }
346
+ onFailure() {
347
+ this.failures++;
348
+ this.successes = 0;
349
+ if (this.failures >= this.failureThreshold) {
350
+ this.state = 'open';
351
+ this.nextAttempt = Date.now() + this.timeout;
352
+ // Log circuit breaker open event
353
+ if (isVerboseEnabled()) {
354
+ logRetryEvent({
355
+ level: 'error',
356
+ event: 'retry_exhausted',
357
+ attempt: this.failures,
358
+ max_retries: this.failureThreshold,
359
+ reason: 'CIRCUIT_OPENED',
360
+ retry_after_ms: this.timeout,
361
+ context: `Circuit breaker opened after ${this.failures} failures`,
362
+ timestamp: new Date().toISOString(),
363
+ });
364
+ }
365
+ }
366
+ }
367
+ getState() {
368
+ return {
369
+ state: this.state,
370
+ failures: this.failures,
371
+ successes: this.successes,
372
+ };
373
+ }
374
+ reset() {
375
+ this.state = 'closed';
376
+ this.failures = 0;
377
+ this.successes = 0;
378
+ this.nextAttempt = 0;
379
+ }
380
+ }
381
+ exports.CircuitBreaker = CircuitBreaker;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Bookmarks Utility
3
+ *
4
+ * User-defined shortcuts to frequently-used Notion pages and databases.
5
+ * Stored at ~/.notion-cli/bookmarks.json alongside the workspace cache.
6
+ *
7
+ * Bookmarks integrate into the resolver — once saved, a bookmark name
8
+ * works anywhere an ID or URL does (e.g. `notion-cli db query inbox`).
9
+ */
10
+ export interface Bookmark {
11
+ id: string;
12
+ type: 'database' | 'page';
13
+ }
14
+ export interface BookmarksData {
15
+ version: string;
16
+ default: string | null;
17
+ bookmarks: Record<string, Bookmark>;
18
+ }
19
+ /** Load bookmarks from disk. Returns empty structure if file doesn't exist. */
20
+ export declare function loadBookmarks(): Promise<BookmarksData>;
21
+ /** Save bookmarks to disk (atomic write via tmp + rename). */
22
+ export declare function saveBookmarks(data: BookmarksData): Promise<void>;
23
+ /** Get a single bookmark by name. Returns null if not found. */
24
+ export declare function getBookmark(name: string): Promise<Bookmark | null>;
25
+ /** Save or update a bookmark. */
26
+ export declare function setBookmark(name: string, id: string, type: 'database' | 'page'): Promise<void>;
27
+ /** Remove a bookmark. Returns true if it existed. */
28
+ export declare function removeBookmark(name: string): Promise<boolean>;
29
+ /** Get the default bookmark name (used by `quick` command). */
30
+ export declare function getDefaultBookmark(): Promise<string | null>;
31
+ /** Set the default bookmark name. Throws if bookmark doesn't exist. */
32
+ export declare function setDefaultBookmark(name: string): Promise<void>;
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ /**
3
+ * Bookmarks Utility
4
+ *
5
+ * User-defined shortcuts to frequently-used Notion pages and databases.
6
+ * Stored at ~/.notion-cli/bookmarks.json alongside the workspace cache.
7
+ *
8
+ * Bookmarks integrate into the resolver — once saved, a bookmark name
9
+ * works anywhere an ID or URL does (e.g. `notion-cli db query inbox`).
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.loadBookmarks = loadBookmarks;
13
+ exports.saveBookmarks = saveBookmarks;
14
+ exports.getBookmark = getBookmark;
15
+ exports.setBookmark = setBookmark;
16
+ exports.removeBookmark = removeBookmark;
17
+ exports.getDefaultBookmark = getDefaultBookmark;
18
+ exports.setDefaultBookmark = setDefaultBookmark;
19
+ const fs = require("fs/promises");
20
+ const path = require("path");
21
+ const workspace_cache_1 = require("./workspace-cache");
22
+ // -- Constants --
23
+ const BOOKMARKS_FILE = 'bookmarks.json';
24
+ const BOOKMARKS_VERSION = '1.0.0';
25
+ function getBookmarksPath() {
26
+ return path.join((0, workspace_cache_1.getCacheDir)(), BOOKMARKS_FILE);
27
+ }
28
+ // -- Core operations --
29
+ /** Load bookmarks from disk. Returns empty structure if file doesn't exist. */
30
+ async function loadBookmarks() {
31
+ try {
32
+ const content = await fs.readFile(getBookmarksPath(), 'utf-8');
33
+ const data = JSON.parse(content);
34
+ // Validate structure
35
+ if (!data.version || typeof data.bookmarks !== 'object') {
36
+ return createEmpty();
37
+ }
38
+ return data;
39
+ }
40
+ catch (error) {
41
+ if (error.code === 'ENOENT')
42
+ return createEmpty();
43
+ return createEmpty();
44
+ }
45
+ }
46
+ /** Save bookmarks to disk (atomic write via tmp + rename). */
47
+ async function saveBookmarks(data) {
48
+ await (0, workspace_cache_1.ensureCacheDir)();
49
+ const filePath = getBookmarksPath();
50
+ const tmpPath = `${filePath}.tmp`;
51
+ await fs.writeFile(tmpPath, JSON.stringify(data, null, 2), { encoding: 'utf-8', mode: 0o600 });
52
+ await fs.rename(tmpPath, filePath);
53
+ }
54
+ /** Get a single bookmark by name. Returns null if not found. */
55
+ async function getBookmark(name) {
56
+ var _a;
57
+ const data = await loadBookmarks();
58
+ return (_a = data.bookmarks[name.toLowerCase()]) !== null && _a !== void 0 ? _a : null;
59
+ }
60
+ /** Save or update a bookmark. */
61
+ async function setBookmark(name, id, type) {
62
+ const data = await loadBookmarks();
63
+ data.bookmarks[name.toLowerCase()] = { id, type };
64
+ await saveBookmarks(data);
65
+ }
66
+ /** Remove a bookmark. Returns true if it existed. */
67
+ async function removeBookmark(name) {
68
+ const data = await loadBookmarks();
69
+ const key = name.toLowerCase();
70
+ if (!(key in data.bookmarks))
71
+ return false;
72
+ delete data.bookmarks[key];
73
+ // Clear default if it pointed to the removed bookmark
74
+ if (data.default === key) {
75
+ data.default = null;
76
+ }
77
+ await saveBookmarks(data);
78
+ return true;
79
+ }
80
+ /** Get the default bookmark name (used by `quick` command). */
81
+ async function getDefaultBookmark() {
82
+ const data = await loadBookmarks();
83
+ return data.default;
84
+ }
85
+ /** Set the default bookmark name. Throws if bookmark doesn't exist. */
86
+ async function setDefaultBookmark(name) {
87
+ const data = await loadBookmarks();
88
+ const key = name.toLowerCase();
89
+ if (!(key in data.bookmarks)) {
90
+ throw new Error(`Bookmark "${name}" does not exist. Create it first with: bookmark set ${name} <ID>`);
91
+ }
92
+ data.default = key;
93
+ await saveBookmarks(data);
94
+ }
95
+ // -- Helpers --
96
+ function createEmpty() {
97
+ return { version: BOOKMARKS_VERSION, default: null, bookmarks: {} };
98
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Daily Config Utility
3
+ *
4
+ * Persistent configuration for the `daily` command.
5
+ * Stored at ~/.notion-cli/daily.json alongside bookmarks and workspace cache.
6
+ *
7
+ * Follows the same atomic-write pattern as bookmarks.ts:
8
+ * write to .tmp first, then rename (safe against mid-write crashes).
9
+ */
10
+ export interface DailyConfig {
11
+ version: string;
12
+ database_id: string;
13
+ title_property: string;
14
+ date_property: string;
15
+ title_format: string;
16
+ }
17
+ /** Load daily config from disk. Returns null if not yet configured. */
18
+ export declare function loadDailyConfig(): Promise<DailyConfig | null>;
19
+ /** Save daily config to disk (atomic write via tmp + rename). */
20
+ export declare function saveDailyConfig(config: DailyConfig): Promise<void>;
21
+ /** Factory: create a DailyConfig with sensible defaults. */
22
+ export declare function createDailyConfig(databaseId: string, titleProperty: string, dateProperty: string, titleFormat?: string): DailyConfig;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ /**
3
+ * Daily Config Utility
4
+ *
5
+ * Persistent configuration for the `daily` command.
6
+ * Stored at ~/.notion-cli/daily.json alongside bookmarks and workspace cache.
7
+ *
8
+ * Follows the same atomic-write pattern as bookmarks.ts:
9
+ * write to .tmp first, then rename (safe against mid-write crashes).
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.loadDailyConfig = loadDailyConfig;
13
+ exports.saveDailyConfig = saveDailyConfig;
14
+ exports.createDailyConfig = createDailyConfig;
15
+ const fs = require("fs/promises");
16
+ const path = require("path");
17
+ const workspace_cache_1 = require("./workspace-cache");
18
+ // -- Constants --
19
+ const DAILY_FILE = 'daily.json';
20
+ const DAILY_VERSION = '1.0.0';
21
+ const DEFAULT_TITLE_FORMAT = 'YYYY-MM-DD';
22
+ function getDailyConfigPath() {
23
+ return path.join((0, workspace_cache_1.getCacheDir)(), DAILY_FILE);
24
+ }
25
+ // -- Core operations --
26
+ /** Load daily config from disk. Returns null if not yet configured. */
27
+ async function loadDailyConfig() {
28
+ try {
29
+ const content = await fs.readFile(getDailyConfigPath(), 'utf-8');
30
+ const data = JSON.parse(content);
31
+ // Validate: must have the essential fields to be usable
32
+ if (!data.version || !data.database_id || !data.date_property) {
33
+ return null;
34
+ }
35
+ return data;
36
+ }
37
+ catch (error) {
38
+ if (error.code === 'ENOENT')
39
+ return null;
40
+ return null;
41
+ }
42
+ }
43
+ /** Save daily config to disk (atomic write via tmp + rename). */
44
+ async function saveDailyConfig(config) {
45
+ await (0, workspace_cache_1.ensureCacheDir)();
46
+ const filePath = getDailyConfigPath();
47
+ const tmpPath = `${filePath}.tmp`;
48
+ await fs.writeFile(tmpPath, JSON.stringify(config, null, 2), { encoding: 'utf-8', mode: 0o600 });
49
+ await fs.rename(tmpPath, filePath);
50
+ }
51
+ /** Factory: create a DailyConfig with sensible defaults. */
52
+ function createDailyConfig(databaseId, titleProperty, dateProperty, titleFormat = DEFAULT_TITLE_FORMAT) {
53
+ return {
54
+ version: DAILY_VERSION,
55
+ database_id: databaseId,
56
+ title_property: titleProperty,
57
+ date_property: dateProperty,
58
+ title_format: titleFormat,
59
+ };
60
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Disk Cache Manager
3
+ *
4
+ * Provides persistent caching to disk, maintaining cache across CLI invocations.
5
+ * Cache entries are stored in ~/.notion-cli/cache/ directory.
6
+ */
7
+ export interface DiskCacheEntry<T = any> {
8
+ key: string;
9
+ data: T;
10
+ expiresAt: number;
11
+ createdAt: number;
12
+ size: number;
13
+ }
14
+ export interface DiskCacheStats {
15
+ totalEntries: number;
16
+ totalSize: number;
17
+ oldestEntry: number | null;
18
+ newestEntry: number | null;
19
+ }
20
+ export declare class DiskCacheManager {
21
+ private cacheDir;
22
+ private maxSize;
23
+ private syncInterval;
24
+ private dirtyKeys;
25
+ private syncTimer;
26
+ private initialized;
27
+ constructor(options?: {
28
+ cacheDir?: string;
29
+ maxSize?: number;
30
+ syncInterval?: number;
31
+ });
32
+ /**
33
+ * Initialize disk cache (create directory, start sync timer)
34
+ */
35
+ initialize(): Promise<void>;
36
+ /**
37
+ * Get a cache entry from disk
38
+ */
39
+ get<T>(key: string): Promise<DiskCacheEntry<T> | null>;
40
+ /**
41
+ * Set a cache entry to disk
42
+ */
43
+ set<T>(key: string, data: T, ttl: number): Promise<void>;
44
+ /**
45
+ * Invalidate (delete) a cache entry
46
+ */
47
+ invalidate(key: string): Promise<void>;
48
+ /**
49
+ * Clear all cache entries
50
+ */
51
+ clear(): Promise<void>;
52
+ /**
53
+ * Sync dirty entries to disk
54
+ */
55
+ sync(): Promise<void>;
56
+ /**
57
+ * Shutdown (flush and cleanup)
58
+ */
59
+ shutdown(): Promise<void>;
60
+ /**
61
+ * Get cache statistics
62
+ */
63
+ getStats(): Promise<DiskCacheStats>;
64
+ /**
65
+ * Enforce maximum cache size by removing oldest entries
66
+ */
67
+ private enforceMaxSize;
68
+ /**
69
+ * Ensure cache directory exists
70
+ */
71
+ private ensureCacheDir;
72
+ /**
73
+ * Get file path for a cache key
74
+ */
75
+ private getFilePath;
76
+ }
77
+ /**
78
+ * Global singleton instance
79
+ */
80
+ export declare const diskCacheManager: DiskCacheManager;