@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,274 @@
1
+ "use strict";
2
+ /**
3
+ * Stats Dashboard Command
4
+ *
5
+ * Shows a bird's-eye view of the Notion workspace: database count, user count,
6
+ * property type breakdown, and per-database details. Data comes primarily from
7
+ * the workspace cache (fast), with optional --live flag for page counts (slower).
8
+ *
9
+ * notion-cli stats # fast dashboard from cache
10
+ * notion-cli stats --live # also fetch page counts per DB
11
+ * notion-cli stats --relations # show database relation graph
12
+ * notion-cli stats --json # JSON output for automation
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const core_1 = require("@oclif/core");
16
+ const workspace_cache_1 = require("../utils/workspace-cache");
17
+ const base_flags_1 = require("../base-flags");
18
+ const errors_1 = require("../errors");
19
+ const notion = require("../notion");
20
+ class Stats extends core_1.Command {
21
+ async run() {
22
+ var _a, _b;
23
+ const { flags } = await this.parse(Stats);
24
+ try {
25
+ // 1. Load workspace cache — the primary data source (free, no API calls)
26
+ const cache = await (0, workspace_cache_1.loadCache)();
27
+ if (!cache || cache.databases.length === 0) {
28
+ throw errors_1.NotionCLIErrorFactory.workspaceNotSynced('');
29
+ }
30
+ // 2. Fetch workspace info + users (cached API calls, fast)
31
+ // Both calls can fail if the token lacks permissions — handle gracefully.
32
+ let workspaceName = 'Unknown';
33
+ let userCount = null;
34
+ try {
35
+ const botInfo = await notion.botUser();
36
+ workspaceName = ((_a = botInfo === null || botInfo === void 0 ? void 0 : botInfo.bot) === null || _a === void 0 ? void 0 : _a.workspace_name) || 'Unknown';
37
+ }
38
+ catch { /* bot info unavailable — non-critical */ }
39
+ try {
40
+ const usersResponse = await notion.listUser();
41
+ userCount = usersResponse.results.length;
42
+ }
43
+ catch { /* users endpoint needs extra permissions — skip gracefully */ }
44
+ // 3. Aggregate property types across all databases (from cache, no API)
45
+ const propertyTypeCounts = {};
46
+ const dbDetails = [];
47
+ for (const db of cache.databases) {
48
+ const propCount = db.properties ? Object.keys(db.properties).length : 0;
49
+ dbDetails.push({ title: db.title, shortId: db.id.slice(0, 8), propertyCount: propCount });
50
+ // Count each property type across the workspace
51
+ if (db.properties) {
52
+ for (const prop of Object.values(db.properties)) {
53
+ const type = prop.type || 'unknown';
54
+ propertyTypeCounts[type] = (propertyTypeCounts[type] || 0) + 1;
55
+ }
56
+ }
57
+ }
58
+ // Sort databases by property count (most complex first)
59
+ dbDetails.sort((a, b) => b.propertyCount - a.propertyCount);
60
+ // 3b. Extract database relation edges (from cache, no API calls).
61
+ // Each relation property points to a target database via database_id or data_source_id.
62
+ // We resolve the target name by looking it up in the cache.
63
+ const idToTitle = new Map(cache.databases.flatMap(db => [
64
+ [db.id, db.title],
65
+ // Also index by database_id (relation properties may use either ID format)
66
+ ...(db.url ? [[db.url, db.title]] : []),
67
+ ]));
68
+ const relationEdges = [];
69
+ for (const db of cache.databases) {
70
+ if (!db.properties)
71
+ continue;
72
+ for (const prop of Object.values(db.properties)) {
73
+ const p = prop;
74
+ if (p.type !== 'relation' || !p.relation)
75
+ continue;
76
+ // Resolve target name — try data_source_id first, then database_id
77
+ const targetId = p.relation.data_source_id || p.relation.database_id || '';
78
+ const target = idToTitle.get(targetId) || `Unknown (${targetId.slice(0, 8)})`;
79
+ relationEdges.push({
80
+ source: db.title,
81
+ sourceId: db.id.slice(0, 8),
82
+ property: p.name || Object.keys(db.properties).find(k => db.properties[k] === prop) || '?',
83
+ target,
84
+ targetId: targetId.slice(0, 8),
85
+ });
86
+ }
87
+ }
88
+ // 4. Optional: fetch page counts per database (expensive, behind --live flag)
89
+ let totalPages;
90
+ if (flags.live) {
91
+ totalPages = 0;
92
+ for (const db of cache.databases) {
93
+ try {
94
+ const pages = await notion.fetchAllPagesInDS(db.id);
95
+ const count = pages.length;
96
+ totalPages += count;
97
+ // Update the matching entry in dbDetails
98
+ const detail = dbDetails.find(d => d.title === db.title);
99
+ if (detail)
100
+ detail.pages = count;
101
+ }
102
+ catch {
103
+ // If a DB query fails (permissions, deleted), skip it gracefully
104
+ const detail = dbDetails.find(d => d.title === db.title);
105
+ if (detail)
106
+ detail.pages = -1;
107
+ }
108
+ }
109
+ // Re-sort by page count when live data is available
110
+ dbDetails.sort((a, b) => { var _a, _b; return ((_a = b.pages) !== null && _a !== void 0 ? _a : 0) - ((_b = a.pages) !== null && _b !== void 0 ? _b : 0); });
111
+ }
112
+ // 5. Calculate cache age for display
113
+ const cacheAgeMs = Date.now() - new Date(cache.lastSync).getTime();
114
+ const cacheAgeLabel = this.formatAge(cacheAgeMs);
115
+ // 6. Sort property types by count (most used first)
116
+ const sortedTypes = Object.entries(propertyTypeCounts)
117
+ .sort(([, a], [, b]) => b - a);
118
+ // --- Output ---
119
+ if (flags.json) {
120
+ this.log(JSON.stringify({
121
+ success: true,
122
+ data: {
123
+ workspace: workspaceName,
124
+ databases: {
125
+ count: cache.databases.length,
126
+ items: dbDetails,
127
+ },
128
+ ...(userCount !== null && { users: { count: userCount } }),
129
+ property_types: propertyTypeCounts,
130
+ ...(totalPages !== undefined && { pages: { total: totalPages } }),
131
+ ...(flags.relations && { relations: relationEdges }),
132
+ cache: {
133
+ last_sync: cache.lastSync,
134
+ age_ms: cacheAgeMs,
135
+ },
136
+ },
137
+ timestamp: new Date().toISOString(),
138
+ }, null, 2));
139
+ process.exit(0);
140
+ return;
141
+ }
142
+ // Human-readable dashboard
143
+ this.log('');
144
+ const userLabel = userCount !== null ? ` | Users: ${userCount}` : '';
145
+ const summary = totalPages !== undefined
146
+ ? `Databases: ${cache.databases.length}${userLabel} | Total Pages: ${totalPages}`
147
+ : `Databases: ${cache.databases.length}${userLabel}`;
148
+ this.log(`Workspace: ${workspaceName}`);
149
+ this.log(summary);
150
+ // Database table — includes short ID to disambiguate duplicate names
151
+ this.log('\nDatabases');
152
+ this.log('─'.repeat(65));
153
+ const nameWidth = 36;
154
+ const header = flags.live
155
+ ? `${'Name'.padEnd(nameWidth)} ${'ID'.padEnd(8)} ${'Props'.padStart(5)} ${'Pages'.padStart(5)}`
156
+ : `${'Name'.padEnd(nameWidth)} ${'ID'.padEnd(8)} ${'Props'.padStart(5)}`;
157
+ this.log(header);
158
+ for (const db of dbDetails) {
159
+ const name = db.title.length > nameWidth
160
+ ? db.title.slice(0, nameWidth - 1) + '…'
161
+ : db.title.padEnd(nameWidth);
162
+ const props = String(db.propertyCount).padStart(5);
163
+ if (flags.live) {
164
+ const pages = db.pages === -1 ? ' err' : String((_b = db.pages) !== null && _b !== void 0 ? _b : '-').padStart(5);
165
+ this.log(`${name} ${db.shortId} ${props} ${pages}`);
166
+ }
167
+ else {
168
+ this.log(`${name} ${db.shortId} ${props}`);
169
+ }
170
+ }
171
+ // Property type breakdown
172
+ this.log('\nProperty Types');
173
+ this.log('─'.repeat(30));
174
+ for (const [type, count] of sortedTypes) {
175
+ this.log(`${type.padEnd(20)} ${String(count).padStart(4)}`);
176
+ }
177
+ // Relation graph — shows which databases connect to which
178
+ if (flags.relations) {
179
+ this.log('\nRelation Graph');
180
+ this.log('─'.repeat(55));
181
+ // Group edges by source database
182
+ const bySource = new Map();
183
+ for (const edge of relationEdges) {
184
+ const key = edge.source;
185
+ if (!bySource.has(key))
186
+ bySource.set(key, []);
187
+ bySource.get(key).push(edge);
188
+ }
189
+ // Databases with relations
190
+ for (const [source, edges] of bySource) {
191
+ const srcId = edges[0].sourceId;
192
+ this.log(`${source} ${srcId}`);
193
+ for (const edge of edges) {
194
+ this.log(` → ${edge.target} (via "${edge.property}")`);
195
+ }
196
+ }
197
+ // Databases without any relations
198
+ const sourcesWithRelations = new Set(bySource.keys());
199
+ const isolated = cache.databases
200
+ .filter(db => !sourcesWithRelations.has(db.title))
201
+ .map(db => db.title);
202
+ if (isolated.length > 0) {
203
+ this.log(`\nIsolated (no relations): ${isolated.join(', ')}`);
204
+ }
205
+ }
206
+ // Footer
207
+ this.log(`\nCache: synced ${cacheAgeLabel}`);
208
+ if (!flags.live) {
209
+ this.log('Tip: Run with --live to fetch page counts per database.');
210
+ }
211
+ process.exit(0);
212
+ }
213
+ catch (error) {
214
+ const cliError = error instanceof errors_1.NotionCLIError
215
+ ? error
216
+ : (0, errors_1.wrapNotionError)(error, {
217
+ endpoint: 'workspace.stats',
218
+ resourceType: 'workspace',
219
+ });
220
+ if (flags.json) {
221
+ this.log(JSON.stringify(cliError.toJSON(), null, 2));
222
+ }
223
+ else {
224
+ this.error(cliError.toHumanString());
225
+ }
226
+ process.exit(1);
227
+ }
228
+ }
229
+ /** Convert milliseconds to a human-friendly age string (e.g. "2h ago"). */
230
+ formatAge(ms) {
231
+ const minutes = Math.floor(ms / 60000);
232
+ if (minutes < 1)
233
+ return 'just now';
234
+ if (minutes < 60)
235
+ return `${minutes}m ago`;
236
+ const hours = Math.floor(minutes / 60);
237
+ if (hours < 24)
238
+ return `${hours}h ago`;
239
+ const days = Math.floor(hours / 24);
240
+ return `${days}d ago`;
241
+ }
242
+ }
243
+ Stats.description = 'Show workspace statistics dashboard';
244
+ Stats.aliases = ['dashboard'];
245
+ Stats.examples = [
246
+ {
247
+ description: 'Show workspace statistics',
248
+ command: '$ notion-cli stats',
249
+ },
250
+ {
251
+ description: 'Include page counts per database (slower)',
252
+ command: '$ notion-cli stats --live',
253
+ },
254
+ {
255
+ description: 'Show database relation graph',
256
+ command: '$ notion-cli stats --relations',
257
+ },
258
+ {
259
+ description: 'JSON output for automation',
260
+ command: '$ notion-cli stats --json',
261
+ },
262
+ ];
263
+ Stats.flags = {
264
+ ...base_flags_1.AutomationFlags,
265
+ live: core_1.Flags.boolean({
266
+ description: 'Fetch page counts per database (requires API calls)',
267
+ default: false,
268
+ }),
269
+ relations: core_1.Flags.boolean({
270
+ description: 'Show database relation graph (from cache, no API calls)',
271
+ default: false,
272
+ }),
273
+ };
274
+ exports.default = Stats;
@@ -0,0 +1,24 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Sync 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
+ force: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
18
+ };
19
+ run(): Promise<void>;
20
+ /**
21
+ * Fetch all databases from Notion API with pagination
22
+ */
23
+ private fetchAllDatabases;
24
+ }
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const notion_1 = require("../notion");
5
+ const retry_1 = require("../retry");
6
+ const workspace_cache_1 = require("../utils/workspace-cache");
7
+ const base_flags_1 = require("../base-flags");
8
+ const errors_1 = require("../errors");
9
+ const token_validator_1 = require("../utils/token-validator");
10
+ class Sync extends core_1.Command {
11
+ async run() {
12
+ const { flags } = await this.parse(Sync);
13
+ const startTime = Date.now();
14
+ try {
15
+ // Verify NOTION_TOKEN is set (throws if not)
16
+ (0, token_validator_1.validateNotionToken)();
17
+ if (!flags.json) {
18
+ core_1.ux.action.start('Syncing workspace databases');
19
+ }
20
+ // Fetch all databases from Notion API with progress updates
21
+ const databases = await this.fetchAllDatabases(flags.json);
22
+ // const _fetchTime = Date.now() - startTime
23
+ if (!flags.json) {
24
+ core_1.ux.action.stop(`Found ${databases.length} database${databases.length === 1 ? '' : 's'}`);
25
+ core_1.ux.action.start('Generating search aliases');
26
+ }
27
+ // Build cache entries
28
+ const cacheEntries = databases.map(db => (0, workspace_cache_1.buildCacheEntry)(db));
29
+ if (!flags.json) {
30
+ core_1.ux.action.stop();
31
+ core_1.ux.action.start('Saving cache');
32
+ }
33
+ // Save to cache
34
+ const cache = {
35
+ version: '1.0.0',
36
+ lastSync: new Date().toISOString(),
37
+ databases: cacheEntries,
38
+ };
39
+ await (0, workspace_cache_1.saveCache)(cache);
40
+ const cachePath = await (0, workspace_cache_1.getCachePath)();
41
+ const executionTime = Date.now() - startTime;
42
+ // Build comprehensive metadata
43
+ const metadata = {
44
+ sync_time: new Date().toISOString(),
45
+ execution_time_ms: executionTime,
46
+ databases_found: databases.length,
47
+ cache_ttls: {
48
+ in_memory: {
49
+ data_source_ms: parseInt(process.env.NOTION_CLI_CACHE_DS_TTL || '600000', 10),
50
+ page_ms: parseInt(process.env.NOTION_CLI_CACHE_PAGE_TTL || '60000', 10),
51
+ user_ms: parseInt(process.env.NOTION_CLI_CACHE_USER_TTL || '3600000', 10),
52
+ block_ms: parseInt(process.env.NOTION_CLI_CACHE_BLOCK_TTL || '30000', 10),
53
+ },
54
+ workspace: {
55
+ persistence: 'until next sync',
56
+ recommended_sync_interval_hours: 24,
57
+ },
58
+ },
59
+ next_recommended_sync: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
60
+ cache_location: cachePath,
61
+ };
62
+ if (flags.json) {
63
+ this.log(JSON.stringify({
64
+ success: true,
65
+ data: {
66
+ databases: cacheEntries.map(db => ({
67
+ id: db.id,
68
+ title: db.title,
69
+ aliases: db.aliases,
70
+ url: db.url,
71
+ })),
72
+ summary: {
73
+ total: databases.length,
74
+ cached_at: cache.lastSync,
75
+ cache_version: cache.version,
76
+ },
77
+ },
78
+ metadata,
79
+ }, null, 2));
80
+ }
81
+ else {
82
+ core_1.ux.action.stop();
83
+ // Enhanced completion summary
84
+ const elapsedSeconds = (executionTime / 1000).toFixed(2);
85
+ this.log(`\n✓ Synced ${databases.length} database${databases.length === 1 ? '' : 's'} in ${elapsedSeconds}s`);
86
+ this.log('');
87
+ this.log(`📁 Cache: ${cachePath}`);
88
+ this.log(`🕐 Last updated: ${new Date(cache.lastSync).toLocaleString()}`);
89
+ this.log(`📊 Databases: ${databases.length} total`);
90
+ this.log('');
91
+ this.log(`Next sync recommended: ${new Date(metadata.next_recommended_sync).toLocaleString()}`);
92
+ if (databases.length > 0) {
93
+ this.log('\nIndexed databases:');
94
+ cacheEntries.slice(0, 10).forEach(db => {
95
+ const aliasesStr = db.aliases.slice(0, 3).join(', ');
96
+ this.log(` • ${db.title} (aliases: ${aliasesStr})`);
97
+ });
98
+ if (databases.length > 10) {
99
+ this.log(` ... and ${databases.length - 10} more`);
100
+ }
101
+ this.log('\nTry: notion-cli list');
102
+ }
103
+ else {
104
+ this.log('\nNo databases found in workspace.');
105
+ this.log('Make sure your integration has access to databases.');
106
+ }
107
+ }
108
+ process.exit(0);
109
+ }
110
+ catch (error) {
111
+ const cliError = error instanceof errors_1.NotionCLIError
112
+ ? error
113
+ : (0, errors_1.wrapNotionError)(error, {
114
+ endpoint: 'search',
115
+ resourceType: 'database'
116
+ });
117
+ if (flags.json) {
118
+ this.log(JSON.stringify(cliError.toJSON(), null, 2));
119
+ }
120
+ else {
121
+ core_1.ux.action.stop('failed');
122
+ this.error(cliError.toHumanString());
123
+ }
124
+ process.exit(1);
125
+ }
126
+ }
127
+ /**
128
+ * Fetch all databases from Notion API with pagination
129
+ */
130
+ async fetchAllDatabases(isJsonMode) {
131
+ const databases = [];
132
+ let cursor = undefined;
133
+ while (true) {
134
+ const response = await (0, retry_1.fetchWithRetry)(() => notion_1.client.search({
135
+ filter: {
136
+ value: 'data_source',
137
+ property: 'object',
138
+ },
139
+ start_cursor: cursor,
140
+ page_size: 100, // Max allowed by API
141
+ }), {
142
+ context: 'sync:fetchAllDatabases',
143
+ config: { maxRetries: 5 }, // Higher retries for sync
144
+ });
145
+ databases.push(...response.results);
146
+ // Show progress update (only in non-JSON mode)
147
+ if (!isJsonMode && response.has_more) {
148
+ // Update the spinner text to show current count
149
+ core_1.ux.action.start(`Syncing workspace databases (found ${databases.length} so far)`);
150
+ }
151
+ if (!response.has_more || !response.next_cursor) {
152
+ break;
153
+ }
154
+ cursor = response.next_cursor;
155
+ }
156
+ return databases;
157
+ }
158
+ }
159
+ Sync.description = 'Sync workspace databases to local cache for fast lookups';
160
+ Sync.aliases = ['db:sync'];
161
+ Sync.examples = [
162
+ {
163
+ description: 'Sync all workspace databases',
164
+ command: 'notion-cli sync',
165
+ },
166
+ {
167
+ description: 'Force resync even if cache exists',
168
+ command: 'notion-cli sync --force',
169
+ },
170
+ {
171
+ description: 'Sync and output as JSON',
172
+ command: 'notion-cli sync --json',
173
+ },
174
+ ];
175
+ Sync.flags = {
176
+ force: core_1.Flags.boolean({
177
+ char: 'f',
178
+ description: 'Force resync even if cache is fresh',
179
+ default: false,
180
+ }),
181
+ ...base_flags_1.AutomationFlags,
182
+ };
183
+ exports.default = Sync;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Template Get Command
3
+ *
4
+ * Displays the full contents of a saved template — properties, body content,
5
+ * and icon. Useful for inspecting a template before using it.
6
+ */
7
+ import { Command } from '@oclif/core';
8
+ export default class TemplateGet extends Command {
9
+ static description: string;
10
+ static aliases: string[];
11
+ static examples: {
12
+ description: string;
13
+ command: string;
14
+ }[];
15
+ static args: {
16
+ name: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
17
+ };
18
+ static flags: {
19
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
20
+ 'page-size': import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
21
+ retry: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
22
+ timeout: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
23
+ 'no-cache': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
24
+ verbose: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
25
+ minimal: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
26
+ };
27
+ run(): Promise<void>;
28
+ }
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ /**
3
+ * Template Get Command
4
+ *
5
+ * Displays the full contents of a saved template — properties, body content,
6
+ * and icon. Useful for inspecting a template before using it.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ const core_1 = require("@oclif/core");
10
+ const templates_1 = require("../../utils/templates");
11
+ const base_flags_1 = require("../../base-flags");
12
+ class TemplateGet extends core_1.Command {
13
+ async run() {
14
+ const { args, flags } = await this.parse(TemplateGet);
15
+ const template = await (0, templates_1.getTemplate)(args.name);
16
+ if (!template) {
17
+ this.error(`Template "${args.name}" not found.\nRun \`notion-cli template list\` to see saved templates.`);
18
+ process.exit(1);
19
+ return;
20
+ }
21
+ if (flags.json) {
22
+ this.log(JSON.stringify({
23
+ success: true,
24
+ data: { name: args.name, ...template },
25
+ timestamp: new Date().toISOString()
26
+ }, null, 2));
27
+ process.exit(0);
28
+ return;
29
+ }
30
+ // Human-readable output
31
+ this.log(`Template: ${args.name}`);
32
+ if (template.icon)
33
+ this.log(`Icon: ${template.icon}`);
34
+ if (template.properties) {
35
+ this.log(`Properties:`);
36
+ this.log(JSON.stringify(template.properties, null, 2));
37
+ }
38
+ if (template.content) {
39
+ this.log(`Content:`);
40
+ this.log(template.content);
41
+ }
42
+ process.exit(0);
43
+ }
44
+ }
45
+ TemplateGet.description = 'View a saved template';
46
+ TemplateGet.aliases = ['tpl:get'];
47
+ TemplateGet.examples = [
48
+ {
49
+ description: 'View a template',
50
+ command: '$ notion-cli template get "meeting"',
51
+ },
52
+ ];
53
+ TemplateGet.args = {
54
+ name: core_1.Args.string({ required: true, description: 'Template name' }),
55
+ };
56
+ TemplateGet.flags = {
57
+ ...base_flags_1.AutomationFlags,
58
+ };
59
+ exports.default = TemplateGet;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Template List Command
3
+ *
4
+ * Shows all saved templates in a table with name, property count,
5
+ * content indicator, and icon.
6
+ */
7
+ import { Command } from '@oclif/core';
8
+ export default class TemplateList extends Command {
9
+ static description: string;
10
+ static aliases: string[];
11
+ static examples: {
12
+ description: string;
13
+ command: string;
14
+ }[];
15
+ static flags: {
16
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
17
+ 'page-size': import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
18
+ retry: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
19
+ timeout: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
20
+ 'no-cache': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
21
+ verbose: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
22
+ minimal: 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
+ };
31
+ run(): Promise<void>;
32
+ }
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ /**
3
+ * Template List Command
4
+ *
5
+ * Shows all saved templates in a table with name, property count,
6
+ * content indicator, and icon.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ const core_1 = require("@oclif/core");
10
+ const templates_1 = require("../../utils/templates");
11
+ const table_formatter_1 = require("../../utils/table-formatter");
12
+ const base_flags_1 = require("../../base-flags");
13
+ class TemplateList extends core_1.Command {
14
+ async run() {
15
+ const { flags } = await this.parse(TemplateList);
16
+ const data = await (0, templates_1.loadTemplates)();
17
+ const entries = Object.entries(data.templates);
18
+ if (flags.json) {
19
+ this.log(JSON.stringify({
20
+ success: true,
21
+ data: { templates: data.templates },
22
+ timestamp: new Date().toISOString()
23
+ }, null, 2));
24
+ process.exit(0);
25
+ return;
26
+ }
27
+ if (entries.length === 0) {
28
+ this.log('No templates saved yet.');
29
+ this.log('Create one with: notion-cli template save <name> --properties \'{"Status": "To Do"}\'');
30
+ process.exit(0);
31
+ return;
32
+ }
33
+ // Build rows for table display
34
+ const rows = entries.map(([name, tmpl]) => ({
35
+ name,
36
+ properties: tmpl.properties ? Object.keys(tmpl.properties).length : 0,
37
+ content: tmpl.content ? 'yes' : '',
38
+ icon: tmpl.icon || '',
39
+ }));
40
+ const columns = {
41
+ name: {},
42
+ properties: { header: '# props' },
43
+ content: {},
44
+ icon: {},
45
+ };
46
+ (0, table_formatter_1.formatTable)(rows, columns, { printLine: this.log.bind(this), ...flags });
47
+ process.exit(0);
48
+ }
49
+ }
50
+ TemplateList.description = 'List all saved templates';
51
+ TemplateList.aliases = ['tpl:ls'];
52
+ TemplateList.examples = [
53
+ {
54
+ description: 'List all templates',
55
+ command: '$ notion-cli template list',
56
+ },
57
+ ];
58
+ TemplateList.flags = {
59
+ ...table_formatter_1.tableFlags,
60
+ ...base_flags_1.AutomationFlags,
61
+ };
62
+ exports.default = TemplateList;