@andrzejchm/notion-cli 0.12.0 → 0.13.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/.agents/skills/using-notion-cli/SKILL.md +84 -8
- package/README.md +26 -1
- package/dist/cli.js +429 -50
- package/dist/cli.js.map +1 -1
- package/docs/FEATURE-PARITY.md +29 -20
- package/docs/README.agents.md +117 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { readFileSync as readFileSync2 } from "fs";
|
|
5
5
|
import { dirname as dirname2, join as join4 } from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
|
-
import { Command as
|
|
7
|
+
import { Command as Command30 } from "commander";
|
|
8
8
|
|
|
9
9
|
// src/commands/append.ts
|
|
10
10
|
import { Command } from "commander";
|
|
@@ -451,9 +451,24 @@ function isNotionValidationError(error2) {
|
|
|
451
451
|
}
|
|
452
452
|
|
|
453
453
|
// src/notion/client.ts
|
|
454
|
-
import {
|
|
454
|
+
import {
|
|
455
|
+
APIErrorCode,
|
|
456
|
+
Client,
|
|
457
|
+
isNotionClientError,
|
|
458
|
+
LogLevel
|
|
459
|
+
} from "@notionhq/client";
|
|
460
|
+
var stderrLogger = (level, message, extraInfo) => {
|
|
461
|
+
process.stderr.write(
|
|
462
|
+
`[notion-sdk] ${level}: ${message} ${JSON.stringify(extraInfo)}
|
|
463
|
+
`
|
|
464
|
+
);
|
|
465
|
+
};
|
|
455
466
|
async function validateToken(token) {
|
|
456
|
-
const notion = new Client({
|
|
467
|
+
const notion = new Client({
|
|
468
|
+
auth: token,
|
|
469
|
+
logLevel: LogLevel.WARN,
|
|
470
|
+
logger: stderrLogger
|
|
471
|
+
});
|
|
457
472
|
try {
|
|
458
473
|
const me = await notion.users.me({});
|
|
459
474
|
const bot = me;
|
|
@@ -473,7 +488,12 @@ async function validateToken(token) {
|
|
|
473
488
|
}
|
|
474
489
|
}
|
|
475
490
|
function createNotionClient(token) {
|
|
476
|
-
return new Client({
|
|
491
|
+
return new Client({
|
|
492
|
+
auth: token,
|
|
493
|
+
timeoutMs: 12e4,
|
|
494
|
+
logLevel: LogLevel.WARN,
|
|
495
|
+
logger: stderrLogger
|
|
496
|
+
});
|
|
477
497
|
}
|
|
478
498
|
|
|
479
499
|
// src/notion/url-parser.ts
|
|
@@ -1092,25 +1112,47 @@ function printWithPager(text) {
|
|
|
1092
1112
|
}
|
|
1093
1113
|
|
|
1094
1114
|
// src/commands/archive.ts
|
|
1115
|
+
async function archiveEntity(client, uuid) {
|
|
1116
|
+
try {
|
|
1117
|
+
const result2 = await client.pages.update({
|
|
1118
|
+
page_id: uuid,
|
|
1119
|
+
in_trash: true
|
|
1120
|
+
});
|
|
1121
|
+
return { result: result2, kind: "page" };
|
|
1122
|
+
} catch {
|
|
1123
|
+
}
|
|
1124
|
+
try {
|
|
1125
|
+
const result2 = await client.dataSources.update({
|
|
1126
|
+
data_source_id: uuid,
|
|
1127
|
+
in_trash: true
|
|
1128
|
+
});
|
|
1129
|
+
return { result: result2, kind: "data_source" };
|
|
1130
|
+
} catch {
|
|
1131
|
+
}
|
|
1132
|
+
const result = await client.databases.update({
|
|
1133
|
+
database_id: uuid,
|
|
1134
|
+
in_trash: true
|
|
1135
|
+
});
|
|
1136
|
+
return { result, kind: "database" };
|
|
1137
|
+
}
|
|
1095
1138
|
function archiveCommand() {
|
|
1096
1139
|
const cmd = new Command2("archive");
|
|
1097
|
-
cmd.description("archive (trash) a Notion page").argument("<id/url>", "Notion page
|
|
1140
|
+
cmd.description("archive (trash) a Notion page or database").argument("<id/url>", "Notion page or database ID/URL").action(
|
|
1098
1141
|
withErrorHandling(async (idOrUrl) => {
|
|
1099
1142
|
const { token, source } = await resolveToken();
|
|
1100
1143
|
reportTokenSource(source);
|
|
1101
1144
|
const client = createNotionClient(token);
|
|
1102
1145
|
const id = parseNotionId(idOrUrl);
|
|
1103
1146
|
const uuid = toUuid(id);
|
|
1104
|
-
const
|
|
1105
|
-
page_id: uuid,
|
|
1106
|
-
archived: true
|
|
1107
|
-
});
|
|
1147
|
+
const { result, kind } = await archiveEntity(client, uuid);
|
|
1108
1148
|
const mode = getOutputMode();
|
|
1109
1149
|
if (mode === "json") {
|
|
1110
|
-
process.stdout.write(`${formatJSON(
|
|
1150
|
+
process.stdout.write(`${formatJSON(result)}
|
|
1111
1151
|
`);
|
|
1112
1152
|
} else {
|
|
1113
|
-
|
|
1153
|
+
const label = kind === "page" ? "Page" : "Database";
|
|
1154
|
+
process.stdout.write(`${label} archived.
|
|
1155
|
+
`);
|
|
1114
1156
|
}
|
|
1115
1157
|
})
|
|
1116
1158
|
);
|
|
@@ -1858,8 +1900,9 @@ import { Command as Command10 } from "commander";
|
|
|
1858
1900
|
// src/services/database.service.ts
|
|
1859
1901
|
import { isFullPage } from "@notionhq/client";
|
|
1860
1902
|
async function fetchDatabaseSchema(client, dbId) {
|
|
1861
|
-
const
|
|
1862
|
-
const
|
|
1903
|
+
const resolvedId = await resolveDataSourceId(client, dbId);
|
|
1904
|
+
const ds = await client.dataSources.retrieve({ data_source_id: resolvedId });
|
|
1905
|
+
const title = "title" in ds ? ds.title.map((rt) => rt.plain_text).join("") || resolvedId : resolvedId;
|
|
1863
1906
|
const properties = {};
|
|
1864
1907
|
if ("properties" in ds) {
|
|
1865
1908
|
for (const [name, prop] of Object.entries(ds.properties)) {
|
|
@@ -1878,8 +1921,8 @@ async function fetchDatabaseSchema(client, dbId) {
|
|
|
1878
1921
|
properties[name] = config;
|
|
1879
1922
|
}
|
|
1880
1923
|
}
|
|
1881
|
-
const databaseId = "parent" in ds && ds.parent && typeof ds.parent === "object" && "database_id" in ds.parent ? ds.parent.database_id :
|
|
1882
|
-
return { id:
|
|
1924
|
+
const databaseId = "parent" in ds && ds.parent && typeof ds.parent === "object" && "database_id" in ds.parent ? ds.parent.database_id : resolvedId;
|
|
1925
|
+
return { id: resolvedId, databaseId, title, properties };
|
|
1883
1926
|
}
|
|
1884
1927
|
async function queryDatabase(client, dbId, opts = {}) {
|
|
1885
1928
|
const rawPages = await paginateResults(
|
|
@@ -2099,6 +2142,81 @@ async function resolveDataSourceId(client, idOrUrl) {
|
|
|
2099
2142
|
`Could not find database or data source: ${id}`
|
|
2100
2143
|
);
|
|
2101
2144
|
}
|
|
2145
|
+
function buildDatabaseUpdatePayload(opts, schema) {
|
|
2146
|
+
const properties = {};
|
|
2147
|
+
for (const def of opts.addProps) {
|
|
2148
|
+
const { name, config } = parsePropertyDefinition(def);
|
|
2149
|
+
properties[name] = config;
|
|
2150
|
+
}
|
|
2151
|
+
for (const name of opts.removeProps) {
|
|
2152
|
+
properties[name] = null;
|
|
2153
|
+
}
|
|
2154
|
+
for (const raw of opts.renameProps) {
|
|
2155
|
+
const colonIdx = raw.indexOf(":");
|
|
2156
|
+
if (colonIdx === -1) {
|
|
2157
|
+
throw new CliError(
|
|
2158
|
+
ErrorCodes.INVALID_ARG,
|
|
2159
|
+
`Invalid rename format: "${raw}"`,
|
|
2160
|
+
'Use format: --rename-prop "OldName:NewName"'
|
|
2161
|
+
);
|
|
2162
|
+
}
|
|
2163
|
+
const oldName = raw.slice(0, colonIdx).trim();
|
|
2164
|
+
const newName = raw.slice(colonIdx + 1).trim();
|
|
2165
|
+
const propConfig = schema.properties[oldName];
|
|
2166
|
+
if (!propConfig) {
|
|
2167
|
+
const available = Object.keys(schema.properties).join(", ");
|
|
2168
|
+
throw new CliError(
|
|
2169
|
+
ErrorCodes.INVALID_ARG,
|
|
2170
|
+
`Property "${oldName}" not found in schema`,
|
|
2171
|
+
`Available properties: ${available}`
|
|
2172
|
+
);
|
|
2173
|
+
}
|
|
2174
|
+
properties[propConfig.id] = { name: newName };
|
|
2175
|
+
}
|
|
2176
|
+
for (const raw of opts.setOptions) {
|
|
2177
|
+
const colonIdx = raw.indexOf(":");
|
|
2178
|
+
if (colonIdx === -1) {
|
|
2179
|
+
throw new CliError(
|
|
2180
|
+
ErrorCodes.INVALID_ARG,
|
|
2181
|
+
`Invalid set-options format: "${raw}"`,
|
|
2182
|
+
'Use format: --set-options "PropertyName:opt1,opt2,opt3"'
|
|
2183
|
+
);
|
|
2184
|
+
}
|
|
2185
|
+
const propName = raw.slice(0, colonIdx).trim();
|
|
2186
|
+
const optionsStr = raw.slice(colonIdx + 1).trim();
|
|
2187
|
+
const propConfig = schema.properties[propName];
|
|
2188
|
+
if (!propConfig) {
|
|
2189
|
+
const available = Object.keys(schema.properties).join(", ");
|
|
2190
|
+
throw new CliError(
|
|
2191
|
+
ErrorCodes.INVALID_ARG,
|
|
2192
|
+
`Property "${propName}" not found in schema`,
|
|
2193
|
+
`Available properties: ${available}`
|
|
2194
|
+
);
|
|
2195
|
+
}
|
|
2196
|
+
if (propConfig.type !== "select" && propConfig.type !== "multi_select") {
|
|
2197
|
+
throw new CliError(
|
|
2198
|
+
ErrorCodes.INVALID_ARG,
|
|
2199
|
+
`Property "${propName}" is of type "${propConfig.type}" \u2014 only select and multi_select properties support --set-options`
|
|
2200
|
+
);
|
|
2201
|
+
}
|
|
2202
|
+
const options = optionsStr.split(",").map((opt) => ({ name: opt.trim() }));
|
|
2203
|
+
properties[propName] = { [propConfig.type]: { options } };
|
|
2204
|
+
}
|
|
2205
|
+
const payload = {};
|
|
2206
|
+
if (opts.title !== void 0) {
|
|
2207
|
+
payload.title = [{ type: "text", text: { content: opts.title } }];
|
|
2208
|
+
}
|
|
2209
|
+
if (Object.keys(properties).length > 0) {
|
|
2210
|
+
payload.properties = properties;
|
|
2211
|
+
}
|
|
2212
|
+
return payload;
|
|
2213
|
+
}
|
|
2214
|
+
async function updateDatabaseSchema(client, dataSourceId, payload) {
|
|
2215
|
+
return client.dataSources.update({
|
|
2216
|
+
data_source_id: dataSourceId,
|
|
2217
|
+
...payload
|
|
2218
|
+
});
|
|
2219
|
+
}
|
|
2102
2220
|
function displayPropertyValue(prop) {
|
|
2103
2221
|
switch (prop.type) {
|
|
2104
2222
|
case "title":
|
|
@@ -2506,14 +2624,210 @@ function dbSchemaCommand() {
|
|
|
2506
2624
|
);
|
|
2507
2625
|
}
|
|
2508
2626
|
|
|
2509
|
-
// src/commands/
|
|
2627
|
+
// src/commands/db/update.ts
|
|
2510
2628
|
import { Command as Command14 } from "commander";
|
|
2629
|
+
function collectProps2(val, acc) {
|
|
2630
|
+
acc.push(val);
|
|
2631
|
+
return acc;
|
|
2632
|
+
}
|
|
2633
|
+
function dbUpdateCommand() {
|
|
2634
|
+
return new Command14("update").description(
|
|
2635
|
+
"Update database schema (add/remove/rename properties, manage options)"
|
|
2636
|
+
).argument("<id/url>", "database ID or URL").option(
|
|
2637
|
+
"--add-prop <definition>",
|
|
2638
|
+
'add property (repeatable): --add-prop "Name:type:options"',
|
|
2639
|
+
collectProps2,
|
|
2640
|
+
[]
|
|
2641
|
+
).option(
|
|
2642
|
+
"--remove-prop <name>",
|
|
2643
|
+
"remove property (repeatable)",
|
|
2644
|
+
collectProps2,
|
|
2645
|
+
[]
|
|
2646
|
+
).option(
|
|
2647
|
+
"--rename-prop <old:new>",
|
|
2648
|
+
'rename property (repeatable): --rename-prop "Old:New"',
|
|
2649
|
+
collectProps2,
|
|
2650
|
+
[]
|
|
2651
|
+
).option(
|
|
2652
|
+
"--set-options <prop:opts>",
|
|
2653
|
+
'set select/multi_select options (repeatable): --set-options "Status:A,B,C"',
|
|
2654
|
+
collectProps2,
|
|
2655
|
+
[]
|
|
2656
|
+
).option("--title <title>", "update database title").action(
|
|
2657
|
+
withErrorHandling(async (id, opts) => {
|
|
2658
|
+
const hasOperations = opts.addProp.length > 0 || opts.removeProp.length > 0 || opts.renameProp.length > 0 || opts.setOptions.length > 0 || opts.title !== void 0;
|
|
2659
|
+
if (!hasOperations) {
|
|
2660
|
+
throw new CliError(
|
|
2661
|
+
ErrorCodes.INVALID_ARG,
|
|
2662
|
+
"No update operations specified",
|
|
2663
|
+
"Provide at least one of: --add-prop, --remove-prop, --rename-prop, --set-options, --title"
|
|
2664
|
+
);
|
|
2665
|
+
}
|
|
2666
|
+
const { token, source } = await resolveToken();
|
|
2667
|
+
reportTokenSource(source);
|
|
2668
|
+
const client = createNotionClient(token);
|
|
2669
|
+
const dsId = await resolveDataSourceId(client, id);
|
|
2670
|
+
const needsSchema = opts.renameProp.length > 0 || opts.setOptions.length > 0;
|
|
2671
|
+
const schema = needsSchema ? await fetchDatabaseSchema(client, dsId) : { id: dsId, databaseId: dsId, title: "", properties: {} };
|
|
2672
|
+
const payload = buildDatabaseUpdatePayload(
|
|
2673
|
+
{
|
|
2674
|
+
addProps: opts.addProp,
|
|
2675
|
+
removeProps: opts.removeProp,
|
|
2676
|
+
renameProps: opts.renameProp,
|
|
2677
|
+
setOptions: opts.setOptions,
|
|
2678
|
+
title: opts.title
|
|
2679
|
+
},
|
|
2680
|
+
schema
|
|
2681
|
+
);
|
|
2682
|
+
const response = await updateDatabaseSchema(client, dsId, payload);
|
|
2683
|
+
if (getOutputMode() === "json") {
|
|
2684
|
+
process.stdout.write(`${formatJSON(response)}
|
|
2685
|
+
`);
|
|
2686
|
+
return;
|
|
2687
|
+
}
|
|
2688
|
+
if ("url" in response) {
|
|
2689
|
+
process.stdout.write(`${response.url}
|
|
2690
|
+
`);
|
|
2691
|
+
}
|
|
2692
|
+
})
|
|
2693
|
+
);
|
|
2694
|
+
}
|
|
2695
|
+
|
|
2696
|
+
// src/commands/db/update-rows.ts
|
|
2697
|
+
import { Command as Command15 } from "commander";
|
|
2698
|
+
var LARGE_BATCH_WARNING_THRESHOLD = 10;
|
|
2699
|
+
var DEFAULT_CONCURRENCY = 3;
|
|
2700
|
+
function collectProps3(val, acc) {
|
|
2701
|
+
acc.push(val);
|
|
2702
|
+
return acc;
|
|
2703
|
+
}
|
|
2704
|
+
async function batchUpdate(items, fn, concurrency = DEFAULT_CONCURRENCY) {
|
|
2705
|
+
const results = [];
|
|
2706
|
+
for (let i = 0; i < items.length; i += concurrency) {
|
|
2707
|
+
const batch = items.slice(i, i + concurrency);
|
|
2708
|
+
results.push(...await Promise.all(batch.map(fn)));
|
|
2709
|
+
}
|
|
2710
|
+
return results;
|
|
2711
|
+
}
|
|
2712
|
+
function getPageTitle(page) {
|
|
2713
|
+
for (const prop of Object.values(page.properties)) {
|
|
2714
|
+
if (prop.type === "title") {
|
|
2715
|
+
return prop.title.map((r) => r.plain_text).join("") || page.id;
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
return page.id;
|
|
2719
|
+
}
|
|
2720
|
+
function dbUpdateRowsCommand() {
|
|
2721
|
+
return new Command15("update-rows").description("Update properties on all matching rows in a database").argument("<id>", "Notion database ID or URL").option(
|
|
2722
|
+
"--filter <filter>",
|
|
2723
|
+
'Filter rows (repeatable): --filter "Status=Done"',
|
|
2724
|
+
collectProps3,
|
|
2725
|
+
[]
|
|
2726
|
+
).option(
|
|
2727
|
+
"--prop <property=value>",
|
|
2728
|
+
"Set a property value on matching rows (repeatable, required)",
|
|
2729
|
+
collectProps3,
|
|
2730
|
+
[]
|
|
2731
|
+
).option("--dry-run", "Preview matching rows without making changes", false).action(
|
|
2732
|
+
withErrorHandling(async (id, opts) => {
|
|
2733
|
+
if (opts.prop.length === 0) {
|
|
2734
|
+
throw new CliError(
|
|
2735
|
+
ErrorCodes.INVALID_ARG,
|
|
2736
|
+
"No properties to update.",
|
|
2737
|
+
'Provide at least one --prop "Name=Value"'
|
|
2738
|
+
);
|
|
2739
|
+
}
|
|
2740
|
+
const { token } = await resolveToken();
|
|
2741
|
+
const client = createNotionClient(token);
|
|
2742
|
+
const dsId = await resolveDataSourceId(client, id);
|
|
2743
|
+
const schema = await fetchDatabaseSchema(client, dsId);
|
|
2744
|
+
const filter = opts.filter.length ? buildFilter(opts.filter, schema) : void 0;
|
|
2745
|
+
const entries = await queryDatabase(client, dsId, { filter });
|
|
2746
|
+
if (opts.filter.length === 0 && entries.length > LARGE_BATCH_WARNING_THRESHOLD) {
|
|
2747
|
+
process.stderr.write(
|
|
2748
|
+
`Warning: no --filter specified \u2014 ${entries.length} rows will be updated.
|
|
2749
|
+
`
|
|
2750
|
+
);
|
|
2751
|
+
}
|
|
2752
|
+
if (opts.dryRun) {
|
|
2753
|
+
process.stderr.write(
|
|
2754
|
+
`Dry run: would update ${entries.length} row(s).
|
|
2755
|
+
`
|
|
2756
|
+
);
|
|
2757
|
+
const lines = entries.map((e) => `${e.id} ${getPageTitle(e.raw)}`).join("\n");
|
|
2758
|
+
if (entries.length > 0) {
|
|
2759
|
+
process.stdout.write(`${lines}
|
|
2760
|
+
`);
|
|
2761
|
+
}
|
|
2762
|
+
return;
|
|
2763
|
+
}
|
|
2764
|
+
const results = await batchUpdate(entries, async (entry) => {
|
|
2765
|
+
const title = getPageTitle(entry.raw);
|
|
2766
|
+
try {
|
|
2767
|
+
const properties = buildPropertiesPayload(
|
|
2768
|
+
opts.prop,
|
|
2769
|
+
entry.raw
|
|
2770
|
+
);
|
|
2771
|
+
await updatePageProperties(client, entry.id, properties);
|
|
2772
|
+
return { id: entry.id, title, success: true };
|
|
2773
|
+
} catch (err) {
|
|
2774
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2775
|
+
return { id: entry.id, title, success: false, error: message };
|
|
2776
|
+
}
|
|
2777
|
+
});
|
|
2778
|
+
const successCount = results.filter((r) => r.success).length;
|
|
2779
|
+
const failureCount = results.length - successCount;
|
|
2780
|
+
if (getOutputMode() === "json") {
|
|
2781
|
+
process.stdout.write(`${formatJSON(results)}
|
|
2782
|
+
`);
|
|
2783
|
+
return;
|
|
2784
|
+
}
|
|
2785
|
+
process.stdout.write(`Updated ${successCount} row(s).
|
|
2786
|
+
`);
|
|
2787
|
+
if (failureCount > 0) {
|
|
2788
|
+
process.stderr.write(`${failureCount} row(s) failed to update.
|
|
2789
|
+
`);
|
|
2790
|
+
for (const r of results.filter((res) => !res.success)) {
|
|
2791
|
+
process.stderr.write(` ${r.id} (${r.title}): ${r.error}
|
|
2792
|
+
`);
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
})
|
|
2796
|
+
);
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
// src/commands/delete-block.ts
|
|
2800
|
+
import { Command as Command16 } from "commander";
|
|
2801
|
+
function deleteBlockCommand() {
|
|
2802
|
+
const cmd = new Command16("delete-block");
|
|
2803
|
+
cmd.description("delete a Notion block by ID or URL").argument("<id/url>", "Notion block ID or URL").action(
|
|
2804
|
+
withErrorHandling(async (idOrUrl) => {
|
|
2805
|
+
const { token, source } = await resolveToken();
|
|
2806
|
+
reportTokenSource(source);
|
|
2807
|
+
const client = createNotionClient(token);
|
|
2808
|
+
const id = parseNotionId(idOrUrl);
|
|
2809
|
+
const uuid = toUuid(id);
|
|
2810
|
+
const deletedBlock = await client.blocks.delete({ block_id: uuid });
|
|
2811
|
+
const mode = getOutputMode();
|
|
2812
|
+
if (mode === "json") {
|
|
2813
|
+
process.stdout.write(`${formatJSON(deletedBlock)}
|
|
2814
|
+
`);
|
|
2815
|
+
} else {
|
|
2816
|
+
process.stdout.write("Block deleted.\n");
|
|
2817
|
+
}
|
|
2818
|
+
})
|
|
2819
|
+
);
|
|
2820
|
+
return cmd;
|
|
2821
|
+
}
|
|
2822
|
+
|
|
2823
|
+
// src/commands/edit-page.ts
|
|
2824
|
+
import { Command as Command17 } from "commander";
|
|
2511
2825
|
function collect2(val, acc) {
|
|
2512
2826
|
acc.push(val);
|
|
2513
2827
|
return acc;
|
|
2514
2828
|
}
|
|
2515
2829
|
function editPageCommand() {
|
|
2516
|
-
const cmd = new
|
|
2830
|
+
const cmd = new Command17("edit-page");
|
|
2517
2831
|
cmd.description(
|
|
2518
2832
|
"replace a Notion page's content \u2014 full page or a targeted section"
|
|
2519
2833
|
).argument("<id/url>", "Notion page ID or URL").option(
|
|
@@ -2611,7 +2925,7 @@ function editPageCommand() {
|
|
|
2611
2925
|
|
|
2612
2926
|
// src/commands/init.ts
|
|
2613
2927
|
import { confirm, input as input2, password } from "@inquirer/prompts";
|
|
2614
|
-
import { Command as
|
|
2928
|
+
import { Command as Command18 } from "commander";
|
|
2615
2929
|
async function runInitFlow() {
|
|
2616
2930
|
const profileName = await input2({
|
|
2617
2931
|
message: "Profile name:",
|
|
@@ -2691,7 +3005,7 @@ async function runInitFlow() {
|
|
|
2691
3005
|
stderrWrite(dim(" notion auth login"));
|
|
2692
3006
|
}
|
|
2693
3007
|
function initCommand() {
|
|
2694
|
-
const cmd = new
|
|
3008
|
+
const cmd = new Command18("init");
|
|
2695
3009
|
cmd.description("authenticate with Notion and save a profile").action(
|
|
2696
3010
|
withErrorHandling(async () => {
|
|
2697
3011
|
if (!process.stdin.isTTY) {
|
|
@@ -2709,7 +3023,7 @@ function initCommand() {
|
|
|
2709
3023
|
|
|
2710
3024
|
// src/commands/ls.ts
|
|
2711
3025
|
import { isFullPageOrDataSource } from "@notionhq/client";
|
|
2712
|
-
import { Command as
|
|
3026
|
+
import { Command as Command19 } from "commander";
|
|
2713
3027
|
function getTitle(item) {
|
|
2714
3028
|
if (item.object === "data_source") {
|
|
2715
3029
|
return item.title.map((t) => t.plain_text).join("") || "(untitled)";
|
|
@@ -2726,7 +3040,7 @@ function displayType(item) {
|
|
|
2726
3040
|
return item.object === "data_source" ? "database" : item.object;
|
|
2727
3041
|
}
|
|
2728
3042
|
function lsCommand() {
|
|
2729
|
-
const cmd = new
|
|
3043
|
+
const cmd = new Command19("ls");
|
|
2730
3044
|
cmd.description("list accessible Notion pages and databases").option(
|
|
2731
3045
|
"--type <type>",
|
|
2732
3046
|
"filter by object type (page or database)",
|
|
@@ -2775,7 +3089,11 @@ function lsCommand() {
|
|
|
2775
3089
|
);
|
|
2776
3090
|
}
|
|
2777
3091
|
if (items.length === 0) {
|
|
2778
|
-
|
|
3092
|
+
if (getOutputMode() === "json") {
|
|
3093
|
+
process.stdout.write("[]\n");
|
|
3094
|
+
} else {
|
|
3095
|
+
process.stdout.write("No accessible content found\n");
|
|
3096
|
+
}
|
|
2779
3097
|
return;
|
|
2780
3098
|
}
|
|
2781
3099
|
const headers = ["TYPE", "TITLE", "ID", "MODIFIED"];
|
|
@@ -2800,9 +3118,9 @@ function lsCommand() {
|
|
|
2800
3118
|
}
|
|
2801
3119
|
|
|
2802
3120
|
// src/commands/move.ts
|
|
2803
|
-
import { Command as
|
|
3121
|
+
import { Command as Command20 } from "commander";
|
|
2804
3122
|
function moveCommand() {
|
|
2805
|
-
const cmd = new
|
|
3123
|
+
const cmd = new Command20("move");
|
|
2806
3124
|
cmd.description("move pages to a new parent").argument("<ids/urls...>", "Notion page IDs or URLs to move").option("--to <id/url>", "target page parent ID or URL").option(
|
|
2807
3125
|
"--to-db <id/url>",
|
|
2808
3126
|
"target database parent ID or URL (resolves to data source)"
|
|
@@ -2860,10 +3178,10 @@ function moveCommand() {
|
|
|
2860
3178
|
// src/commands/open.ts
|
|
2861
3179
|
import { exec } from "child_process";
|
|
2862
3180
|
import { promisify } from "util";
|
|
2863
|
-
import { Command as
|
|
3181
|
+
import { Command as Command21 } from "commander";
|
|
2864
3182
|
var execAsync = promisify(exec);
|
|
2865
3183
|
function openCommand() {
|
|
2866
|
-
const cmd = new
|
|
3184
|
+
const cmd = new Command21("open");
|
|
2867
3185
|
cmd.description("open a Notion page in the default browser").argument("<id/url>", "Notion page ID or URL").action(
|
|
2868
3186
|
withErrorHandling(async (idOrUrl) => {
|
|
2869
3187
|
const id = parseNotionId(idOrUrl);
|
|
@@ -2879,9 +3197,9 @@ function openCommand() {
|
|
|
2879
3197
|
}
|
|
2880
3198
|
|
|
2881
3199
|
// src/commands/profile/list.ts
|
|
2882
|
-
import { Command as
|
|
3200
|
+
import { Command as Command22 } from "commander";
|
|
2883
3201
|
function profileListCommand() {
|
|
2884
|
-
const cmd = new
|
|
3202
|
+
const cmd = new Command22("list");
|
|
2885
3203
|
cmd.description("list all authentication profiles").action(
|
|
2886
3204
|
withErrorHandling(async () => {
|
|
2887
3205
|
const config = await readGlobalConfig();
|
|
@@ -2910,9 +3228,9 @@ function profileListCommand() {
|
|
|
2910
3228
|
}
|
|
2911
3229
|
|
|
2912
3230
|
// src/commands/profile/remove.ts
|
|
2913
|
-
import { Command as
|
|
3231
|
+
import { Command as Command23 } from "commander";
|
|
2914
3232
|
function profileRemoveCommand() {
|
|
2915
|
-
const cmd = new
|
|
3233
|
+
const cmd = new Command23("remove");
|
|
2916
3234
|
cmd.description("remove an authentication profile").argument("<name>", "profile name to remove").action(
|
|
2917
3235
|
withErrorHandling(async (name) => {
|
|
2918
3236
|
const config = await readGlobalConfig();
|
|
@@ -2938,9 +3256,9 @@ function profileRemoveCommand() {
|
|
|
2938
3256
|
}
|
|
2939
3257
|
|
|
2940
3258
|
// src/commands/profile/use.ts
|
|
2941
|
-
import { Command as
|
|
3259
|
+
import { Command as Command24 } from "commander";
|
|
2942
3260
|
function profileUseCommand() {
|
|
2943
|
-
const cmd = new
|
|
3261
|
+
const cmd = new Command24("use");
|
|
2944
3262
|
cmd.description("switch the active profile").argument("<name>", "profile name to activate").action(
|
|
2945
3263
|
withErrorHandling(async (name) => {
|
|
2946
3264
|
const config = await readGlobalConfig();
|
|
@@ -2963,7 +3281,7 @@ function profileUseCommand() {
|
|
|
2963
3281
|
}
|
|
2964
3282
|
|
|
2965
3283
|
// src/commands/read.ts
|
|
2966
|
-
import { Command as
|
|
3284
|
+
import { Command as Command25 } from "commander";
|
|
2967
3285
|
|
|
2968
3286
|
// src/output/markdown.ts
|
|
2969
3287
|
import { Chalk as Chalk2 } from "chalk";
|
|
@@ -3093,17 +3411,71 @@ function renderInline(text) {
|
|
|
3093
3411
|
}
|
|
3094
3412
|
|
|
3095
3413
|
// src/services/page.service.ts
|
|
3414
|
+
var UNKNOWN_BOOKMARK_REGEX = /<unknown url="([^"]*)" alt="bookmark"\/>/g;
|
|
3415
|
+
function extractBlockIdFromUrl(url) {
|
|
3416
|
+
const fragment = new URL(url).hash.slice(1);
|
|
3417
|
+
const hex = fragment.replace(/-/g, "");
|
|
3418
|
+
return /^[0-9a-f]{32}$/i.test(hex) ? hex : null;
|
|
3419
|
+
}
|
|
3420
|
+
function richTextToPlainText(richText) {
|
|
3421
|
+
return richText.map((rt) => rt.plain_text ?? "").join("");
|
|
3422
|
+
}
|
|
3423
|
+
async function resolveUnknownBookmarks(client, markdown) {
|
|
3424
|
+
const matches = [...markdown.matchAll(UNKNOWN_BOOKMARK_REGEX)];
|
|
3425
|
+
if (matches.length === 0) return markdown;
|
|
3426
|
+
const tagToBlockId = /* @__PURE__ */ new Map();
|
|
3427
|
+
for (const match of matches) {
|
|
3428
|
+
const tag = match[0];
|
|
3429
|
+
if (tagToBlockId.has(tag)) continue;
|
|
3430
|
+
try {
|
|
3431
|
+
const blockId = extractBlockIdFromUrl(match[1]);
|
|
3432
|
+
tagToBlockId.set(tag, blockId);
|
|
3433
|
+
} catch {
|
|
3434
|
+
tagToBlockId.set(tag, null);
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
const blockIdToReplacement = /* @__PURE__ */ new Map();
|
|
3438
|
+
await Promise.all(
|
|
3439
|
+
[...new Set(tagToBlockId.values())].map(async (blockId) => {
|
|
3440
|
+
if (!blockId) return;
|
|
3441
|
+
try {
|
|
3442
|
+
const block = await client.blocks.retrieve({
|
|
3443
|
+
block_id: toUuid(blockId)
|
|
3444
|
+
});
|
|
3445
|
+
if (block.type !== "bookmark" || !block.bookmark) return;
|
|
3446
|
+
const { url, caption } = block.bookmark;
|
|
3447
|
+
const captionText = richTextToPlainText(caption).trim();
|
|
3448
|
+
const replacement = captionText ? `[${captionText}](${url})` : url;
|
|
3449
|
+
blockIdToReplacement.set(blockId, replacement);
|
|
3450
|
+
} catch {
|
|
3451
|
+
}
|
|
3452
|
+
})
|
|
3453
|
+
);
|
|
3454
|
+
return markdown.replace(UNKNOWN_BOOKMARK_REGEX, (tag, notionUrl) => {
|
|
3455
|
+
try {
|
|
3456
|
+
const blockId = extractBlockIdFromUrl(notionUrl);
|
|
3457
|
+
if (!blockId) return tag;
|
|
3458
|
+
return blockIdToReplacement.get(blockId) ?? tag;
|
|
3459
|
+
} catch {
|
|
3460
|
+
return tag;
|
|
3461
|
+
}
|
|
3462
|
+
});
|
|
3463
|
+
}
|
|
3096
3464
|
async function fetchPageMarkdown(client, pageId) {
|
|
3097
3465
|
const [page, markdownResponse] = await Promise.all([
|
|
3098
3466
|
client.pages.retrieve({ page_id: pageId }),
|
|
3099
3467
|
client.pages.retrieveMarkdown({ page_id: pageId })
|
|
3100
3468
|
]);
|
|
3101
|
-
|
|
3469
|
+
const markdown = await resolveUnknownBookmarks(
|
|
3470
|
+
client,
|
|
3471
|
+
markdownResponse.markdown
|
|
3472
|
+
);
|
|
3473
|
+
return { page, markdown };
|
|
3102
3474
|
}
|
|
3103
3475
|
|
|
3104
3476
|
// src/commands/read.ts
|
|
3105
3477
|
function readCommand() {
|
|
3106
|
-
return new
|
|
3478
|
+
return new Command25("read").description("Read a Notion page as markdown").argument("<id>", "Notion page ID or URL").action(
|
|
3107
3479
|
withErrorHandling(async (id) => {
|
|
3108
3480
|
const { token } = await resolveToken();
|
|
3109
3481
|
const client = createNotionClient(token);
|
|
@@ -3129,7 +3501,7 @@ function readCommand() {
|
|
|
3129
3501
|
|
|
3130
3502
|
// src/commands/search.ts
|
|
3131
3503
|
import { isFullPageOrDataSource as isFullPageOrDataSource2 } from "@notionhq/client";
|
|
3132
|
-
import { Command as
|
|
3504
|
+
import { Command as Command26 } from "commander";
|
|
3133
3505
|
function getTitle2(item) {
|
|
3134
3506
|
if (item.object === "data_source") {
|
|
3135
3507
|
return item.title.map((t) => t.plain_text).join("") || "(untitled)";
|
|
@@ -3149,7 +3521,7 @@ function displayType2(item) {
|
|
|
3149
3521
|
return item.object === "data_source" ? "database" : item.object;
|
|
3150
3522
|
}
|
|
3151
3523
|
function searchCommand() {
|
|
3152
|
-
const cmd = new
|
|
3524
|
+
const cmd = new Command26("search");
|
|
3153
3525
|
cmd.description("search Notion workspace by keyword").argument("<query>", "search keyword").option(
|
|
3154
3526
|
"--type <type>",
|
|
3155
3527
|
"filter by object type (page or database)",
|
|
@@ -3194,8 +3566,12 @@ function searchCommand() {
|
|
|
3194
3566
|
(r) => isFullPageOrDataSource2(r)
|
|
3195
3567
|
);
|
|
3196
3568
|
if (fullResults.length === 0) {
|
|
3197
|
-
|
|
3569
|
+
if (getOutputMode() === "json") {
|
|
3570
|
+
process.stdout.write("[]\n");
|
|
3571
|
+
} else {
|
|
3572
|
+
process.stdout.write(`No results found for "${query}"
|
|
3198
3573
|
`);
|
|
3574
|
+
}
|
|
3199
3575
|
return;
|
|
3200
3576
|
}
|
|
3201
3577
|
const headers = ["TYPE", "TITLE", "ID", "MODIFIED"];
|
|
@@ -3230,7 +3606,7 @@ import {
|
|
|
3230
3606
|
import { homedir as homedir2 } from "os";
|
|
3231
3607
|
import { dirname, join as join3 } from "path";
|
|
3232
3608
|
import chalk from "chalk";
|
|
3233
|
-
import { Command as
|
|
3609
|
+
import { Command as Command27 } from "commander";
|
|
3234
3610
|
function skillPath() {
|
|
3235
3611
|
if (!process.argv[1]) {
|
|
3236
3612
|
throw new Error("Cannot determine install path. Run with: notion skill");
|
|
@@ -3317,7 +3693,7 @@ function installNonInteractive(source, targets) {
|
|
|
3317
3693
|
}
|
|
3318
3694
|
}
|
|
3319
3695
|
function skillCommand() {
|
|
3320
|
-
const cmd = new
|
|
3696
|
+
const cmd = new Command27("skill");
|
|
3321
3697
|
cmd.description("Install the agent skill file for your coding agents").option("--print", "Print the skill file content instead of installing").option("--path <path>", "Install to a specific file path").action(
|
|
3322
3698
|
withErrorHandling(async (opts) => {
|
|
3323
3699
|
if (opts.print) {
|
|
@@ -3346,17 +3722,17 @@ function skillCommand() {
|
|
|
3346
3722
|
}
|
|
3347
3723
|
|
|
3348
3724
|
// src/commands/update.ts
|
|
3349
|
-
import { Command as
|
|
3350
|
-
function
|
|
3725
|
+
import { Command as Command28 } from "commander";
|
|
3726
|
+
function collectProps4(val, acc) {
|
|
3351
3727
|
acc.push(val);
|
|
3352
3728
|
return acc;
|
|
3353
3729
|
}
|
|
3354
3730
|
function updateCommand() {
|
|
3355
|
-
const cmd = new
|
|
3731
|
+
const cmd = new Command28("update");
|
|
3356
3732
|
cmd.description("update properties on a Notion page").argument("<id/url>", "Notion page ID or URL").option(
|
|
3357
3733
|
"--prop <property=value>",
|
|
3358
3734
|
"set a property value (repeatable)",
|
|
3359
|
-
|
|
3735
|
+
collectProps4,
|
|
3360
3736
|
[]
|
|
3361
3737
|
).option("--title <title>", "set the page title").action(
|
|
3362
3738
|
withErrorHandling(async (idOrUrl, opts) => {
|
|
@@ -3410,7 +3786,7 @@ function updateCommand() {
|
|
|
3410
3786
|
}
|
|
3411
3787
|
|
|
3412
3788
|
// src/commands/users.ts
|
|
3413
|
-
import { Command as
|
|
3789
|
+
import { Command as Command29 } from "commander";
|
|
3414
3790
|
function getEmailOrWorkspace(user) {
|
|
3415
3791
|
if (user.type === "person") {
|
|
3416
3792
|
return user.person.email ?? "\u2014";
|
|
@@ -3422,7 +3798,7 @@ function getEmailOrWorkspace(user) {
|
|
|
3422
3798
|
return "\u2014";
|
|
3423
3799
|
}
|
|
3424
3800
|
function usersCommand() {
|
|
3425
|
-
const cmd = new
|
|
3801
|
+
const cmd = new Command29("users");
|
|
3426
3802
|
cmd.description("list all users in the workspace").option("--json", "output as JSON").action(
|
|
3427
3803
|
withErrorHandling(async (opts) => {
|
|
3428
3804
|
if (opts.json) setOutputMode("json");
|
|
@@ -3453,7 +3829,7 @@ var __dirname = dirname2(__filename);
|
|
|
3453
3829
|
var pkg = JSON.parse(
|
|
3454
3830
|
readFileSync2(join4(__dirname, "../package.json"), "utf-8")
|
|
3455
3831
|
);
|
|
3456
|
-
var program = new
|
|
3832
|
+
var program = new Command30();
|
|
3457
3833
|
program.name("notion").description("Notion CLI \u2014 read Notion pages and databases from the terminal").version(pkg.version);
|
|
3458
3834
|
program.option("--verbose", "show API requests/responses").option("--color", "force color output").option("--json", "force JSON output (overrides TTY detection)").option("--md", "force markdown output for page content");
|
|
3459
3835
|
program.configureOutput({
|
|
@@ -3474,7 +3850,7 @@ program.hook("preAction", (thisCommand) => {
|
|
|
3474
3850
|
setOutputMode("md");
|
|
3475
3851
|
}
|
|
3476
3852
|
});
|
|
3477
|
-
var authCmd = new
|
|
3853
|
+
var authCmd = new Command30("auth").description("manage Notion authentication");
|
|
3478
3854
|
authCmd.action(authDefaultAction(authCmd));
|
|
3479
3855
|
authCmd.addCommand(loginCommand());
|
|
3480
3856
|
authCmd.addCommand(logoutCommand());
|
|
@@ -3484,7 +3860,7 @@ authCmd.addCommand(profileUseCommand());
|
|
|
3484
3860
|
authCmd.addCommand(profileRemoveCommand());
|
|
3485
3861
|
program.addCommand(authCmd);
|
|
3486
3862
|
program.addCommand(initCommand(), { hidden: true });
|
|
3487
|
-
var profileCmd = new
|
|
3863
|
+
var profileCmd = new Command30("profile").description(
|
|
3488
3864
|
"manage authentication profiles"
|
|
3489
3865
|
);
|
|
3490
3866
|
profileCmd.addCommand(profileListCommand());
|
|
@@ -3504,11 +3880,14 @@ program.addCommand(createPageCommand());
|
|
|
3504
3880
|
program.addCommand(editPageCommand());
|
|
3505
3881
|
program.addCommand(updateCommand());
|
|
3506
3882
|
program.addCommand(archiveCommand());
|
|
3883
|
+
program.addCommand(deleteBlockCommand());
|
|
3507
3884
|
program.addCommand(moveCommand());
|
|
3508
|
-
var dbCmd = new
|
|
3885
|
+
var dbCmd = new Command30("db").description("Database operations");
|
|
3509
3886
|
dbCmd.addCommand(dbCreateCommand());
|
|
3510
3887
|
dbCmd.addCommand(dbSchemaCommand());
|
|
3511
3888
|
dbCmd.addCommand(dbQueryCommand());
|
|
3889
|
+
dbCmd.addCommand(dbUpdateCommand());
|
|
3890
|
+
dbCmd.addCommand(dbUpdateRowsCommand());
|
|
3512
3891
|
program.addCommand(dbCmd);
|
|
3513
3892
|
program.addCommand(completionCommand());
|
|
3514
3893
|
program.addCommand(skillCommand());
|