@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.
- package/LICENSE +21 -0
- package/README.md +552 -0
- package/bin/dev +17 -0
- package/bin/dev.cmd +3 -0
- package/bin/run +14 -0
- package/bin/run.cmd +3 -0
- package/dist/base-command.d.ts +73 -0
- package/dist/base-command.js +179 -0
- package/dist/base-flags.d.ts +14 -0
- package/dist/base-flags.js +59 -0
- package/dist/cache.d.ts +84 -0
- package/dist/cache.js +351 -0
- package/dist/commands/append.d.ts +37 -0
- package/dist/commands/append.js +120 -0
- package/dist/commands/batch/delete.d.ts +42 -0
- package/dist/commands/batch/delete.js +199 -0
- package/dist/commands/batch/retrieve.d.ts +43 -0
- package/dist/commands/batch/retrieve.js +272 -0
- package/dist/commands/block/append.d.ts +42 -0
- package/dist/commands/block/append.js +219 -0
- package/dist/commands/block/delete.d.ts +30 -0
- package/dist/commands/block/delete.js +97 -0
- package/dist/commands/block/retrieve/children.d.ts +31 -0
- package/dist/commands/block/retrieve/children.js +177 -0
- package/dist/commands/block/retrieve.d.ts +30 -0
- package/dist/commands/block/retrieve.js +101 -0
- package/dist/commands/block/update.d.ts +45 -0
- package/dist/commands/block/update.js +242 -0
- package/dist/commands/bookmark/list.d.ts +30 -0
- package/dist/commands/bookmark/list.js +60 -0
- package/dist/commands/bookmark/remove.d.ts +26 -0
- package/dist/commands/bookmark/remove.js +47 -0
- package/dist/commands/bookmark/set.d.ts +29 -0
- package/dist/commands/bookmark/set.js +96 -0
- package/dist/commands/browse.d.ts +13 -0
- package/dist/commands/browse.js +44 -0
- package/dist/commands/cache/info.d.ts +19 -0
- package/dist/commands/cache/info.js +145 -0
- package/dist/commands/config/set-token.d.ts +22 -0
- package/dist/commands/config/set-token.js +137 -0
- package/dist/commands/daily/index.d.ts +32 -0
- package/dist/commands/daily/index.js +135 -0
- package/dist/commands/daily/setup.d.ts +42 -0
- package/dist/commands/daily/setup.js +149 -0
- package/dist/commands/db/create.d.ts +31 -0
- package/dist/commands/db/create.js +124 -0
- package/dist/commands/db/query.d.ts +41 -0
- package/dist/commands/db/query.js +360 -0
- package/dist/commands/db/retrieve.d.ts +33 -0
- package/dist/commands/db/retrieve.js +134 -0
- package/dist/commands/db/schema.d.ts +32 -0
- package/dist/commands/db/schema.js +308 -0
- package/dist/commands/db/update.d.ts +31 -0
- package/dist/commands/db/update.js +117 -0
- package/dist/commands/doctor.d.ts +50 -0
- package/dist/commands/doctor.js +420 -0
- package/dist/commands/init.d.ts +65 -0
- package/dist/commands/init.js +479 -0
- package/dist/commands/list.d.ts +29 -0
- package/dist/commands/list.js +219 -0
- package/dist/commands/open.d.ts +29 -0
- package/dist/commands/open.js +100 -0
- package/dist/commands/page/create.d.ts +33 -0
- package/dist/commands/page/create.js +261 -0
- package/dist/commands/page/delete.d.ts +36 -0
- package/dist/commands/page/delete.js +107 -0
- package/dist/commands/page/export.d.ts +38 -0
- package/dist/commands/page/export.js +120 -0
- package/dist/commands/page/retrieve/property_item.d.ts +24 -0
- package/dist/commands/page/retrieve/property_item.js +75 -0
- package/dist/commands/page/retrieve.d.ts +36 -0
- package/dist/commands/page/retrieve.js +244 -0
- package/dist/commands/page/update.d.ts +34 -0
- package/dist/commands/page/update.js +184 -0
- package/dist/commands/quick.d.ts +35 -0
- package/dist/commands/quick.js +168 -0
- package/dist/commands/search.d.ts +43 -0
- package/dist/commands/search.js +361 -0
- package/dist/commands/stats.d.ts +35 -0
- package/dist/commands/stats.js +274 -0
- package/dist/commands/sync.d.ts +24 -0
- package/dist/commands/sync.js +183 -0
- package/dist/commands/template/get.d.ts +28 -0
- package/dist/commands/template/get.js +59 -0
- package/dist/commands/template/list.d.ts +32 -0
- package/dist/commands/template/list.js +62 -0
- package/dist/commands/template/remove.d.ts +27 -0
- package/dist/commands/template/remove.js +48 -0
- package/dist/commands/template/save.d.ts +32 -0
- package/dist/commands/template/save.js +92 -0
- package/dist/commands/template/use.d.ts +34 -0
- package/dist/commands/template/use.js +142 -0
- package/dist/commands/user/list.d.ts +27 -0
- package/dist/commands/user/list.js +99 -0
- package/dist/commands/user/retrieve/bot.d.ts +28 -0
- package/dist/commands/user/retrieve/bot.js +96 -0
- package/dist/commands/user/retrieve.d.ts +30 -0
- package/dist/commands/user/retrieve.js +103 -0
- package/dist/commands/whoami.d.ts +19 -0
- package/dist/commands/whoami.js +175 -0
- package/dist/deduplication.d.ts +41 -0
- package/dist/deduplication.js +71 -0
- package/dist/envelope.d.ts +169 -0
- package/dist/envelope.js +257 -0
- package/dist/errors/enhanced-errors.d.ts +168 -0
- package/dist/errors/enhanced-errors.js +567 -0
- package/dist/errors/index.d.ts +18 -0
- package/dist/errors/index.js +33 -0
- package/dist/examples/cache-retry-examples.d.ts +64 -0
- package/dist/examples/cache-retry-examples.js +375 -0
- package/dist/helper.d.ts +102 -0
- package/dist/helper.js +885 -0
- package/dist/http-agent.d.ts +38 -0
- package/dist/http-agent.js +60 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -0
- package/dist/interface.d.ts +4 -0
- package/dist/interface.js +2 -0
- package/dist/notion.d.ts +144 -0
- package/dist/notion.js +547 -0
- package/dist/retry.d.ts +72 -0
- package/dist/retry.js +381 -0
- package/dist/utils/bookmarks.d.ts +32 -0
- package/dist/utils/bookmarks.js +98 -0
- package/dist/utils/daily-config.d.ts +22 -0
- package/dist/utils/daily-config.js +60 -0
- package/dist/utils/disk-cache.d.ts +80 -0
- package/dist/utils/disk-cache.js +291 -0
- package/dist/utils/fuzzy.d.ts +36 -0
- package/dist/utils/fuzzy.js +69 -0
- package/dist/utils/interactive-navigator.d.ts +63 -0
- package/dist/utils/interactive-navigator.js +123 -0
- package/dist/utils/markdown-to-blocks.d.ts +21 -0
- package/dist/utils/markdown-to-blocks.js +333 -0
- package/dist/utils/notion-resolver.d.ts +49 -0
- package/dist/utils/notion-resolver.js +278 -0
- package/dist/utils/notion-url-parser.d.ts +48 -0
- package/dist/utils/notion-url-parser.js +121 -0
- package/dist/utils/property-expander.d.ts +45 -0
- package/dist/utils/property-expander.js +323 -0
- package/dist/utils/schema-examples.d.ts +40 -0
- package/dist/utils/schema-examples.js +359 -0
- package/dist/utils/schema-extractor.d.ts +65 -0
- package/dist/utils/schema-extractor.js +235 -0
- package/dist/utils/shell-config.d.ts +30 -0
- package/dist/utils/shell-config.js +84 -0
- package/dist/utils/table-formatter.d.ts +36 -0
- package/dist/utils/table-formatter.js +125 -0
- package/dist/utils/templates.d.ts +30 -0
- package/dist/utils/templates.js +82 -0
- package/dist/utils/terminal-banner.d.ts +24 -0
- package/dist/utils/terminal-banner.js +34 -0
- package/dist/utils/token-validator.d.ts +42 -0
- package/dist/utils/token-validator.js +66 -0
- package/dist/utils/update-notifier.d.ts +26 -0
- package/dist/utils/update-notifier.js +54 -0
- package/dist/utils/workspace-cache.d.ts +58 -0
- package/dist/utils/workspace-cache.js +185 -0
- package/oclif.manifest.json +6471 -0
- package/package.json +118 -0
- package/scripts/banner.js +38 -0
- 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;
|