@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,103 @@
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
+ class UserRetrieve extends core_1.Command {
10
+ async run() {
11
+ const { args, flags } = await this.parse(UserRetrieve);
12
+ try {
13
+ let res = await notion.retrieveUser(args.user_id);
14
+ // Apply minimal flag to strip metadata
15
+ if (flags.minimal) {
16
+ res = (0, helper_1.stripMetadata)(res);
17
+ }
18
+ // Handle JSON output for automation
19
+ if (flags.json) {
20
+ this.log(JSON.stringify({
21
+ success: true,
22
+ data: res,
23
+ timestamp: new Date().toISOString()
24
+ }, null, 2));
25
+ process.exit(0);
26
+ return;
27
+ }
28
+ // Handle raw JSON output (legacy)
29
+ if (flags.raw) {
30
+ (0, helper_1.outputRawJson)(res);
31
+ process.exit(0);
32
+ return;
33
+ }
34
+ // Handle table output
35
+ const columns = {
36
+ id: {},
37
+ name: {},
38
+ object: {},
39
+ type: {},
40
+ person_or_bot: {
41
+ header: 'person/bot',
42
+ get: (row) => {
43
+ if (row.type === 'person') {
44
+ return row.person;
45
+ }
46
+ return row.bot;
47
+ },
48
+ },
49
+ avatar_url: {},
50
+ };
51
+ const options = {
52
+ printLine: this.log.bind(this),
53
+ ...flags,
54
+ };
55
+ (0, table_formatter_1.formatTable)([res], columns, options);
56
+ process.exit(0);
57
+ }
58
+ catch (error) {
59
+ const cliError = error instanceof errors_1.NotionCLIError
60
+ ? error
61
+ : (0, errors_1.wrapNotionError)(error, {
62
+ resourceType: 'user',
63
+ attemptedId: args.user_id,
64
+ endpoint: 'users.retrieve'
65
+ });
66
+ if (flags.json) {
67
+ this.log(JSON.stringify(cliError.toJSON(), null, 2));
68
+ }
69
+ else {
70
+ this.error(cliError.toHumanString());
71
+ }
72
+ process.exit(1);
73
+ }
74
+ }
75
+ }
76
+ UserRetrieve.description = 'Retrieve a user';
77
+ UserRetrieve.aliases = ['user:r'];
78
+ UserRetrieve.examples = [
79
+ {
80
+ description: 'Retrieve a user',
81
+ command: `$ notion-cli user retrieve USER_ID`,
82
+ },
83
+ {
84
+ description: 'Retrieve a user and output raw json',
85
+ command: `$ notion-cli user retrieve USER_ID -r`,
86
+ },
87
+ {
88
+ description: 'Retrieve a user and output JSON for automation',
89
+ command: `$ notion-cli user retrieve USER_ID --json`,
90
+ },
91
+ ];
92
+ UserRetrieve.args = {
93
+ user_id: core_1.Args.string(),
94
+ };
95
+ UserRetrieve.flags = {
96
+ raw: core_1.Flags.boolean({
97
+ char: 'r',
98
+ description: 'output raw json',
99
+ }),
100
+ ...table_formatter_1.tableFlags,
101
+ ...base_flags_1.AutomationFlags,
102
+ };
103
+ exports.default = UserRetrieve;
@@ -0,0 +1,19 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Whoami extends Command {
3
+ static description: string;
4
+ static aliases: string[];
5
+ static examples: {
6
+ description: string;
7
+ command: string;
8
+ }[];
9
+ static flags: {
10
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
11
+ 'page-size': import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
12
+ retry: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
13
+ timeout: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
14
+ 'no-cache': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
15
+ verbose: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
16
+ minimal: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
17
+ };
18
+ run(): Promise<void>;
19
+ }
@@ -0,0 +1,175 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const base_flags_1 = require("../base-flags");
5
+ const notion = require("../notion");
6
+ const cache_1 = require("../cache");
7
+ const errors_1 = require("../errors");
8
+ const workspace_cache_1 = require("../utils/workspace-cache");
9
+ const token_validator_1 = require("../utils/token-validator");
10
+ class Whoami extends core_1.Command {
11
+ async run() {
12
+ var _a;
13
+ const { flags } = await this.parse(Whoami);
14
+ const startTime = Date.now();
15
+ try {
16
+ // Verify NOTION_TOKEN is set (throws if not)
17
+ (0, token_validator_1.validateNotionToken)();
18
+ // Get bot user info (with retry and caching)
19
+ const user = await notion.botUser();
20
+ // Get cache stats from in-memory cache
21
+ const cacheStats = cache_1.cacheManager.getStats();
22
+ const cacheHitRate = cache_1.cacheManager.getHitRate();
23
+ // Load workspace cache (databases.json)
24
+ const cache = await (0, workspace_cache_1.loadCache)();
25
+ // Calculate connection latency
26
+ const latencyMs = Date.now() - startTime;
27
+ // Extract bot info safely
28
+ let botInfo = null;
29
+ let workspaceInfo = null;
30
+ if (user.type === 'bot') {
31
+ const botUser = user;
32
+ if (botUser.bot && typeof botUser.bot === 'object' && 'owner' in botUser.bot) {
33
+ botInfo = {
34
+ owner: botUser.bot.owner,
35
+ workspace_name: botUser.bot.workspace_name,
36
+ workspace_id: botUser.bot.workspace_id,
37
+ };
38
+ // Build workspace info if available
39
+ if (botUser.bot.workspace_name) {
40
+ workspaceInfo = {
41
+ name: botUser.bot.workspace_name,
42
+ id: botUser.bot.workspace_id,
43
+ };
44
+ }
45
+ }
46
+ }
47
+ // Build response data
48
+ const data = {
49
+ bot: {
50
+ id: user.id,
51
+ name: user.name || 'Unnamed Bot',
52
+ type: user.type,
53
+ ...(botInfo && { bot_info: botInfo })
54
+ },
55
+ workspace: workspaceInfo,
56
+ api_version: '2022-06-28',
57
+ cli_version: this.config.version,
58
+ cache_status: {
59
+ enabled: !flags['no-cache'] && cache_1.cacheManager.isEnabled(),
60
+ in_memory: {
61
+ size: cacheStats.size,
62
+ hits: cacheStats.hits,
63
+ misses: cacheStats.misses,
64
+ hit_rate: cacheHitRate,
65
+ evictions: cacheStats.evictions,
66
+ },
67
+ workspace: {
68
+ databases_cached: ((_a = cache === null || cache === void 0 ? void 0 : cache.databases) === null || _a === void 0 ? void 0 : _a.length) || 0,
69
+ last_sync: (cache === null || cache === void 0 ? void 0 : cache.lastSync) || null,
70
+ cache_version: (cache === null || cache === void 0 ? void 0 : cache.version) || null,
71
+ }
72
+ },
73
+ connection: {
74
+ status: 'connected',
75
+ latency_ms: latencyMs
76
+ }
77
+ };
78
+ // Output JSON envelope
79
+ if (flags.json) {
80
+ this.log(JSON.stringify({
81
+ success: true,
82
+ data,
83
+ metadata: {
84
+ timestamp: new Date().toISOString(),
85
+ command: 'whoami',
86
+ execution_time_ms: latencyMs
87
+ }
88
+ }, null, 2));
89
+ process.exit(0);
90
+ }
91
+ // Human-readable output
92
+ this.log('\nConnection Status');
93
+ this.log('='.repeat(60));
94
+ this.log(`Status: Connected`);
95
+ this.log(`Latency: ${data.connection.latency_ms}ms`);
96
+ this.log('\nBot Information');
97
+ this.log('='.repeat(60));
98
+ this.log(`Name: ${data.bot.name}`);
99
+ this.log(`ID: ${data.bot.id}`);
100
+ this.log(`Type: ${data.bot.type}`);
101
+ if (data.workspace) {
102
+ this.log('\nWorkspace Information');
103
+ this.log('='.repeat(60));
104
+ this.log(`Name: ${data.workspace.name || 'N/A'}`);
105
+ if (data.workspace.id) {
106
+ this.log(`ID: ${data.workspace.id}`);
107
+ }
108
+ }
109
+ this.log('\nAPI & CLI Version');
110
+ this.log('='.repeat(60));
111
+ this.log(`CLI: ${data.cli_version}`);
112
+ this.log(`API: ${data.api_version}`);
113
+ this.log('\nCache Status');
114
+ this.log('='.repeat(60));
115
+ this.log(`Enabled: ${data.cache_status.enabled ? 'Yes' : 'No'}`);
116
+ if (data.cache_status.enabled) {
117
+ this.log('\nIn-Memory Cache:');
118
+ this.log(` Size: ${data.cache_status.in_memory.size} entries`);
119
+ this.log(` Hits: ${data.cache_status.in_memory.hits}`);
120
+ this.log(` Misses: ${data.cache_status.in_memory.misses}`);
121
+ this.log(` Hit Rate: ${(data.cache_status.in_memory.hit_rate * 100).toFixed(1)}%`);
122
+ this.log(` Evictions: ${data.cache_status.in_memory.evictions}`);
123
+ this.log('\nWorkspace Cache:');
124
+ this.log(` Databases: ${data.cache_status.workspace.databases_cached}`);
125
+ if (data.cache_status.workspace.last_sync) {
126
+ const syncDate = new Date(data.cache_status.workspace.last_sync);
127
+ this.log(` Last Sync: ${syncDate.toLocaleString()}`);
128
+ }
129
+ else {
130
+ this.log(` Last Sync: Never (run 'notion-cli sync' to initialize)`);
131
+ }
132
+ }
133
+ this.log('\n' + '='.repeat(60));
134
+ this.log('\nConnection verified successfully!');
135
+ // Provide helpful tips
136
+ if (!cache || cache.databases.length === 0) {
137
+ this.log('\nTip: Run "notion-cli sync" to cache workspace databases for faster lookups');
138
+ }
139
+ process.exit(0);
140
+ }
141
+ catch (error) {
142
+ const cliError = (0, errors_1.wrapNotionError)(error, {
143
+ endpoint: 'users.botUser',
144
+ resourceType: 'user'
145
+ });
146
+ if (flags.json) {
147
+ this.log(JSON.stringify(cliError.toJSON(), null, 2));
148
+ }
149
+ else {
150
+ this.error(cliError.toHumanString());
151
+ }
152
+ process.exit(1);
153
+ }
154
+ }
155
+ }
156
+ Whoami.description = 'Verify API connectivity and show workspace context';
157
+ Whoami.aliases = ['test', 'health', 'connectivity'];
158
+ Whoami.examples = [
159
+ {
160
+ description: 'Check connection and show bot info',
161
+ command: '$ notion-cli whoami',
162
+ },
163
+ {
164
+ description: 'Check connection and output as JSON',
165
+ command: '$ notion-cli whoami --json',
166
+ },
167
+ {
168
+ description: 'Bypass cache for fresh connectivity test',
169
+ command: '$ notion-cli whoami --no-cache',
170
+ },
171
+ ];
172
+ Whoami.flags = {
173
+ ...base_flags_1.AutomationFlags,
174
+ };
175
+ exports.default = Whoami;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Request deduplication manager
3
+ * Ensures only one in-flight request per unique key
4
+ */
5
+ export interface DeduplicationStats {
6
+ hits: number;
7
+ misses: number;
8
+ pending: number;
9
+ }
10
+ export declare class DeduplicationManager {
11
+ private pending;
12
+ private stats;
13
+ constructor();
14
+ /**
15
+ * Execute a function with deduplication
16
+ * If the same key is already in-flight, returns the existing promise
17
+ * @param key Unique identifier for the request
18
+ * @param fn Function to execute if no in-flight request exists
19
+ * @returns Promise resolving to the function result
20
+ */
21
+ execute<T>(key: string, fn: () => Promise<T>): Promise<T>;
22
+ /**
23
+ * Get deduplication statistics
24
+ * @returns Object containing hits, misses, and pending count
25
+ */
26
+ getStats(): DeduplicationStats;
27
+ /**
28
+ * Clear all pending requests and reset statistics
29
+ */
30
+ clear(): void;
31
+ /**
32
+ * Safety cleanup for stale entries
33
+ * This should rarely be needed as promises clean themselves up
34
+ * @param _maxAge Maximum age in milliseconds (default: 30000)
35
+ */
36
+ cleanup(_maxAge?: number): void;
37
+ }
38
+ /**
39
+ * Global singleton instance for use across the application
40
+ */
41
+ export declare const deduplicationManager: DeduplicationManager;
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ /**
3
+ * Request deduplication manager
4
+ * Ensures only one in-flight request per unique key
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.deduplicationManager = exports.DeduplicationManager = void 0;
8
+ class DeduplicationManager {
9
+ constructor() {
10
+ this.pending = new Map();
11
+ this.stats = { hits: 0, misses: 0 };
12
+ }
13
+ /**
14
+ * Execute a function with deduplication
15
+ * If the same key is already in-flight, returns the existing promise
16
+ * @param key Unique identifier for the request
17
+ * @param fn Function to execute if no in-flight request exists
18
+ * @returns Promise resolving to the function result
19
+ */
20
+ async execute(key, fn) {
21
+ // Check for in-flight request
22
+ const existing = this.pending.get(key);
23
+ if (existing) {
24
+ this.stats.hits++;
25
+ return existing;
26
+ }
27
+ // Create new request
28
+ this.stats.misses++;
29
+ const promise = fn().finally(() => {
30
+ this.pending.delete(key);
31
+ });
32
+ this.pending.set(key, promise);
33
+ return promise;
34
+ }
35
+ /**
36
+ * Get deduplication statistics
37
+ * @returns Object containing hits, misses, and pending count
38
+ */
39
+ getStats() {
40
+ return {
41
+ ...this.stats,
42
+ pending: this.pending.size,
43
+ };
44
+ }
45
+ /**
46
+ * Clear all pending requests and reset statistics
47
+ */
48
+ clear() {
49
+ this.pending.clear();
50
+ this.stats = { hits: 0, misses: 0 };
51
+ }
52
+ /**
53
+ * Safety cleanup for stale entries
54
+ * This should rarely be needed as promises clean themselves up
55
+ * @param _maxAge Maximum age in milliseconds (default: 30000)
56
+ */
57
+ cleanup(_maxAge = 30000) {
58
+ // Note: In practice, promises clean themselves up via finally()
59
+ // This is a safety mechanism for edge cases
60
+ const currentSize = this.pending.size;
61
+ if (currentSize > 0) {
62
+ // Log warning if cleanup is needed
63
+ console.warn(`DeduplicationManager cleanup called with ${currentSize} pending requests`);
64
+ }
65
+ }
66
+ }
67
+ exports.DeduplicationManager = DeduplicationManager;
68
+ /**
69
+ * Global singleton instance for use across the application
70
+ */
71
+ exports.deduplicationManager = new DeduplicationManager();
@@ -0,0 +1,169 @@
1
+ /**
2
+ * JSON Envelope Standardization System for Notion CLI
3
+ *
4
+ * Provides consistent machine-readable output across all commands with:
5
+ * - Standard success/error envelopes
6
+ * - Metadata tracking (command, timestamp, execution time)
7
+ * - Exit code standardization (0=success, 1=API error, 2=CLI error)
8
+ * - Proper stdout/stderr separation
9
+ */
10
+ import { NotionCLIErrorCode } from './errors/index';
11
+ /**
12
+ * Standard metadata included in all envelopes
13
+ */
14
+ export interface EnvelopeMetadata {
15
+ /** ISO 8601 timestamp when the command was executed */
16
+ timestamp: string;
17
+ /** Full command name (e.g., "page retrieve", "db query") */
18
+ command: string;
19
+ /** Execution time in milliseconds */
20
+ execution_time_ms: number;
21
+ /** CLI version for debugging and compatibility */
22
+ version: string;
23
+ }
24
+ /**
25
+ * Success envelope structure
26
+ * Used when a command completes successfully
27
+ */
28
+ export interface SuccessEnvelope<T = any> {
29
+ success: true;
30
+ data: T;
31
+ metadata: EnvelopeMetadata;
32
+ }
33
+ /**
34
+ * Error details structure
35
+ */
36
+ export interface ErrorDetails {
37
+ /** Semantic error code (e.g., "DATABASE_NOT_FOUND", "RATE_LIMITED") */
38
+ code: NotionCLIErrorCode | string;
39
+ /** Human-readable error message */
40
+ message: string;
41
+ /** Additional context about the error */
42
+ details?: any;
43
+ /** Actionable suggestions for the user */
44
+ suggestions?: string[];
45
+ /** Original Notion API error (if applicable) */
46
+ notionError?: any;
47
+ }
48
+ /**
49
+ * Error envelope structure
50
+ * Used when a command fails
51
+ */
52
+ export interface ErrorEnvelope {
53
+ success: false;
54
+ error: ErrorDetails;
55
+ metadata: Omit<EnvelopeMetadata, 'execution_time_ms'> & {
56
+ execution_time_ms?: number;
57
+ };
58
+ }
59
+ /**
60
+ * Union type for all envelope responses
61
+ */
62
+ export type Envelope<T = any> = SuccessEnvelope<T> | ErrorEnvelope;
63
+ /**
64
+ * Exit codes for consistent process termination
65
+ */
66
+ export declare enum ExitCode {
67
+ /** Command completed successfully */
68
+ SUCCESS = 0,
69
+ /** API/Notion error (auth, not found, rate limit, network, etc.) */
70
+ API_ERROR = 1,
71
+ /** CLI/validation error (invalid args, syntax, config issues) */
72
+ CLI_ERROR = 2
73
+ }
74
+ /**
75
+ * Output flags that determine envelope formatting
76
+ */
77
+ export interface OutputFlags {
78
+ json?: boolean;
79
+ 'compact-json'?: boolean;
80
+ raw?: boolean;
81
+ markdown?: boolean;
82
+ pretty?: boolean;
83
+ csv?: boolean;
84
+ }
85
+ /**
86
+ * EnvelopeFormatter - Core utility for creating and outputting envelopes
87
+ */
88
+ export declare class EnvelopeFormatter {
89
+ private startTime;
90
+ private commandName;
91
+ private version;
92
+ /**
93
+ * Initialize formatter with command metadata
94
+ *
95
+ * @param commandName - Full command name (e.g., "page retrieve")
96
+ * @param version - CLI version from package.json
97
+ */
98
+ constructor(commandName: string, version: string);
99
+ /**
100
+ * Create success envelope with data and metadata
101
+ *
102
+ * @param data - The actual response data
103
+ * @param additionalMetadata - Optional additional metadata fields
104
+ * @returns Success envelope ready for output
105
+ */
106
+ wrapSuccess<T>(data: T, additionalMetadata?: Record<string, any>): SuccessEnvelope<T>;
107
+ /**
108
+ * Create error envelope from Error, NotionCLIError, or raw error object
109
+ *
110
+ * @param error - Error instance or error object
111
+ * @param additionalContext - Optional additional error context
112
+ * @returns Error envelope ready for output
113
+ */
114
+ wrapError(error: any, additionalContext?: Record<string, any>): ErrorEnvelope;
115
+ /**
116
+ * Output envelope to stdout with proper formatting
117
+ * Handles flag-based format selection and stdout/stderr separation
118
+ *
119
+ * @param envelope - Success or error envelope
120
+ * @param flags - Output format flags
121
+ * @param logFn - Logging function (typically this.log from Command)
122
+ */
123
+ outputEnvelope(envelope: Envelope, flags: OutputFlags, logFn?: (message: string) => void): void;
124
+ /**
125
+ * Get appropriate exit code for the envelope
126
+ *
127
+ * @param envelope - Success or error envelope
128
+ * @returns Exit code (0, 1, or 2)
129
+ */
130
+ getExitCode(envelope: Envelope): ExitCode;
131
+ /**
132
+ * Write diagnostic messages to stderr (won't pollute JSON on stdout)
133
+ * Useful for retry messages, cache hits, debug info, etc.
134
+ *
135
+ * @param message - Diagnostic message
136
+ * @param level - Message level (info, warn, error)
137
+ */
138
+ static writeDiagnostic(message: string, level?: 'info' | 'warn' | 'error'): void;
139
+ /**
140
+ * Helper to log retry attempts to stderr (doesn't pollute JSON output)
141
+ *
142
+ * @param attempt - Retry attempt number
143
+ * @param maxRetries - Maximum retry attempts
144
+ * @param delay - Delay before next retry in milliseconds
145
+ */
146
+ static logRetry(attempt: number, maxRetries: number, delay: number): void;
147
+ /**
148
+ * Helper to log cache hits to stderr (for debugging)
149
+ *
150
+ * @param cacheKey - Cache key that was hit
151
+ */
152
+ static logCacheHit(cacheKey: string): void;
153
+ }
154
+ /**
155
+ * Convenience function to create an envelope formatter
156
+ *
157
+ * @param commandName - Full command name
158
+ * @param version - CLI version
159
+ * @returns New EnvelopeFormatter instance
160
+ */
161
+ export declare function createEnvelopeFormatter(commandName: string, version: string): EnvelopeFormatter;
162
+ /**
163
+ * Type guard to check if envelope is a success envelope
164
+ */
165
+ export declare function isSuccessEnvelope<T>(envelope: Envelope<T>): envelope is SuccessEnvelope<T>;
166
+ /**
167
+ * Type guard to check if envelope is an error envelope
168
+ */
169
+ export declare function isErrorEnvelope(envelope: Envelope): envelope is ErrorEnvelope;