@eightstate/escli 0.5.0 → 0.7.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 (45) hide show
  1. package/dist/commands/notion/block/trash.js +71 -0
  2. package/dist/commands/notion/comments/add.js +48 -0
  3. package/dist/commands/notion/comments/get.js +42 -0
  4. package/dist/commands/notion/comments/list.js +55 -0
  5. package/dist/commands/notion/comments/reply.js +45 -0
  6. package/dist/commands/notion/comments/thread.js +87 -0
  7. package/dist/commands/notion/db/create.js +555 -0
  8. package/dist/commands/notion/db/query.js +451 -0
  9. package/dist/commands/notion/db/row/create.js +74 -0
  10. package/dist/commands/notion/db/row/update.js +165 -0
  11. package/dist/commands/notion/ds/create.js +73 -0
  12. package/dist/commands/notion/enroll.js +302 -0
  13. package/dist/commands/notion/index.js +24 -0
  14. package/dist/commands/notion/page/edit.js +73 -0
  15. package/dist/commands/notion/page/move.js +59 -0
  16. package/dist/commands/notion/page/read.js +60 -0
  17. package/dist/commands/notion/page/replace-content.js +80 -0
  18. package/dist/commands/notion/page/replace-text.js +80 -0
  19. package/dist/commands/notion/page/replace.js +63 -0
  20. package/dist/commands/notion/page/trash.js +79 -0
  21. package/dist/commands/notion/search.js +207 -0
  22. package/dist/commands/notion/upload/attach.js +105 -0
  23. package/dist/commands/notion/upload/index.js +129 -0
  24. package/dist/commands/notion/upload/list.js +78 -0
  25. package/dist/commands/notion/upload/status.js +76 -0
  26. package/dist/commands/notion/view/create.js +78 -0
  27. package/dist/commands/notion/whoami.js +96 -0
  28. package/dist/commands/research.js +11 -0
  29. package/dist/entry.js +12 -6
  30. package/dist/io/render-kv.js +12 -0
  31. package/dist/io/render-labeled.js +16 -0
  32. package/dist/io/render-table.js +19 -0
  33. package/dist/io/render-trailer.js +7 -0
  34. package/dist/lib/manifest.js +1 -1
  35. package/dist/lib/notion/comments/shared.js +366 -0
  36. package/dist/lib/notion/db-row/common.js +367 -0
  37. package/dist/lib/notion/manifest-pass.js +4 -0
  38. package/dist/lib/notion/page/content-common.js +473 -0
  39. package/dist/lib/notion/trash-move/support.js +300 -0
  40. package/dist/lib/notion/upload/shared.js +372 -0
  41. package/dist/lib/registry.js +118 -25
  42. package/dist/services/notion.js +274 -0
  43. package/dist/services/research.js +31 -10
  44. package/oclif.manifest.json +4084 -1
  45. package/package.json +22 -17
@@ -0,0 +1,451 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { z } from 'zod';
4
+ import { ErrorCode } from '@eightstate/contracts/errors';
5
+ import { ExitCodes } from '@eightstate/contracts/exit-codes';
6
+ import { BaseCommand } from '../../../base-command.js';
7
+ import { EscliError } from '../../../lib/escli-error.js';
8
+ import { renderTable } from '../../../io/render-table.js';
9
+ import { renderTrailer } from '../../../io/render-trailer.js';
10
+ import { isPlainOutput, writeStdout, writeStderr } from '../../../io/io.js';
11
+ import { NOTION_VERSION, notionDataSourceQuery } from '../../../services/notion.js';
12
+ import { hasManifestFlag } from '../../../lib/notion/manifest-pass.js';
13
+ const SortDirectionSchema = z.enum(['ascending', 'descending']);
14
+ const SortSchema = z.object({ property: z.string(), direction: SortDirectionSchema });
15
+ const DbQueryRowSchema = z.object({
16
+ id: z.string(),
17
+ title: z.string().optional(),
18
+ properties: z.record(z.string(), z.unknown()).default({}),
19
+ });
20
+ const DbQueryDataSchema = z.object({
21
+ source: z.object({
22
+ database_id: z.string().optional(),
23
+ data_source_id: z.string(),
24
+ name: z.string().optional(),
25
+ }),
26
+ query: z.object({
27
+ filter: z.string().optional(),
28
+ sorts: z.array(SortSchema).default([]),
29
+ selected_properties: z.array(z.string()).default([]),
30
+ }),
31
+ rows: z.array(DbQueryRowSchema),
32
+ has_more: z.boolean(),
33
+ next: z.string().nullable(),
34
+ page_size: z.number().int().optional(),
35
+ start_index: z.number().int().optional(),
36
+ more_count: z.number().int().nullable().optional(),
37
+ request_status: z.unknown().optional(),
38
+ warnings: z.array(z.string()).optional(),
39
+ });
40
+ export default class NotionDbQuery extends BaseCommand {
41
+ static errors = [
42
+ ErrorCode.UsageRequired,
43
+ ErrorCode.UsageInvalid,
44
+ ErrorCode.UsageUnknownFlag,
45
+ ErrorCode.JsonInvalid,
46
+ ErrorCode.AuthRequired,
47
+ ErrorCode.NotionEnrollmentRequired,
48
+ ErrorCode.NotionUnauthorized,
49
+ ErrorCode.NotionRestricted,
50
+ ErrorCode.NotionNotFound,
51
+ ErrorCode.NotionValidation,
52
+ ErrorCode.NotionRateLimited,
53
+ ErrorCode.GateInvalidResponse,
54
+ ErrorCode.NetworkError,
55
+ ErrorCode.NetworkTimeout,
56
+ ErrorCode.ServiceUnavailable,
57
+ ];
58
+ static summary = 'Query a Notion data source';
59
+ static description = 'Read rows from a Notion data source with pass-through filters, sorts, selected properties, and stateless pagination.';
60
+ static examples = [
61
+ '<%= config.bin %> notion db query ds_362ef7b0c9a44f0aa0b3d174fd12004a --select Name,Status,Due',
62
+ '<%= config.bin %> notion db query ds_362ef7b0c9a44f0aa0b3d174fd12004a --filter \'{"property":"Status","status":{"equals":"In Progress"}}\' --sort Due:asc',
63
+ '<%= config.bin %> notion db query ds_362ef7b0c9a44f0aa0b3d174fd12004a --next eyJzZXNzaW9uIjoiNGNmNyJ9',
64
+ ];
65
+ static aliases = ['notion database query', 'notion ds query'];
66
+ static flags = {
67
+ filter: Flags.string({ description: 'Notion filter object as JSON or @file.' }),
68
+ sort: Flags.string({ description: 'Repeatable sort shorthand: prop:asc or prop:desc.', multiple: true, exclusive: ['sort-json'] }),
69
+ 'sort-json': Flags.string({ description: 'Exact Notion sorts array as JSON or @file.', exclusive: ['sort'] }),
70
+ source: Flags.string({ description: 'Select a data source when the target is a multi-source database.' }),
71
+ 'result-type': Flags.option({ description: 'Wiki-only result-type filter.', options: ['page', 'data_source', 'any'], default: 'page' })(),
72
+ select: Flags.string({ description: 'Comma-separated properties to display.' }),
73
+ 'page-size': Flags.integer({ description: 'Rows to request, 1-100.' }),
74
+ 'title-prop': Flags.string({ description: 'Override title property detection.' }),
75
+ next: Flags.string({ description: 'Continue from Notion pagination token.' }),
76
+ raw: Flags.boolean({ description: 'Return verbatim Notion response body.' }),
77
+ };
78
+ static args = {
79
+ target: Args.string({ description: 'Notion data-source UUID, database UUID, or Notion URL.', required: true }),
80
+ };
81
+ static enableJsonFlag = true;
82
+ static strict = true;
83
+ async run() {
84
+ if (hasManifestFlag(this.argv)) {
85
+ await super.run();
86
+ return;
87
+ }
88
+ const start = Date.now();
89
+ let json = false;
90
+ try {
91
+ const { args, flags } = await this.parse(NotionDbQuery);
92
+ const queryFlags = flags;
93
+ json = Boolean(queryFlags.json);
94
+ const pageSize = queryFlags['page-size'] ?? defaultPageSize();
95
+ if (pageSize < 1 || pageSize > 100) {
96
+ throw new EscliError('notion db query: --page-size max is 100 for Notion data source queries.', {
97
+ code: ErrorCode.UsageInvalid,
98
+ exitCode: ExitCodes.Usage,
99
+ });
100
+ }
101
+ const selectedProperties = parseSelect(queryFlags.select);
102
+ const filter = await parseJsonFlag(queryFlags.filter, '--filter');
103
+ validateTimestampFilter(filter);
104
+ const sorts = await parseSorts(queryFlags);
105
+ const dataSourceId = parseDataSourceTarget(args.target);
106
+ const result = await notionDataSourceQuery(dataSourceId, filter, sorts.length > 0 ? sorts : undefined, pageSize, queryFlags.next);
107
+ if (queryFlags.raw) {
108
+ await writeStdout(`${JSON.stringify(result.raw)}\n`);
109
+ return;
110
+ }
111
+ const data = shapeData(result, { dataSourceId, filter, sorts, selectedProperties, pageSize, titleProp: queryFlags['title-prop'] });
112
+ if (json)
113
+ await emitSuccessEnvelope(this.id ?? 'notion db query', this.config.version, data, Date.now() - start);
114
+ else
115
+ await writeStdout(`${renderDbQuery(data)}\n`);
116
+ }
117
+ catch (error) {
118
+ await emitErrorEnvelope(this.id ?? 'notion db query', this.config.version, toEscliError(error), Date.now() - start, json);
119
+ }
120
+ }
121
+ async execute() {
122
+ throw new Error('run handles notion db query');
123
+ }
124
+ }
125
+ function notionEnvelopeMeta(command, version, durationMs, next) {
126
+ return {
127
+ schema_version: '1',
128
+ escli_version: version,
129
+ command,
130
+ warnings: [],
131
+ duration_ms: durationMs,
132
+ next,
133
+ notion_version: NOTION_VERSION,
134
+ };
135
+ }
136
+ async function emitSuccessEnvelope(command, version, data, durationMs) {
137
+ await writeStdout(`${JSON.stringify({ ok: true, data, error: null, meta: notionEnvelopeMeta(command, version, durationMs, data.next) })}\n`);
138
+ }
139
+ async function emitErrorEnvelope(command, version, error, durationMs, json) {
140
+ process.exitCode = error.exitCode;
141
+ if (json) {
142
+ await writeStdout(`${JSON.stringify({
143
+ ok: false,
144
+ data: null,
145
+ error: {
146
+ code: error.code,
147
+ message: error.message,
148
+ remediation: error.remediation,
149
+ details: error.details,
150
+ cause_code: error.causeCode,
151
+ },
152
+ meta: notionEnvelopeMeta(command, version, durationMs, null),
153
+ })}\n`);
154
+ return;
155
+ }
156
+ await writeStderr(`${error.message}\n`);
157
+ }
158
+ function toEscliError(error) {
159
+ if (error instanceof EscliError)
160
+ return error;
161
+ if (error instanceof z.ZodError) {
162
+ return new EscliError('input validation failed', { code: ErrorCode.ValidationFailed, exitCode: ExitCodes.Usage, details: error.issues });
163
+ }
164
+ if (error instanceof Error)
165
+ return new EscliError(error.message, { code: ErrorCode.Internal });
166
+ return new EscliError('unknown error', { code: ErrorCode.Unknown, details: error });
167
+ }
168
+ function defaultPageSize() {
169
+ return isPlainOutput() ? 25 : 10;
170
+ }
171
+ async function parseJsonFlag(value, flagName) {
172
+ if (!value)
173
+ return undefined;
174
+ const text = value.startsWith('@') ? await readFile(value.slice(1), 'utf8') : value;
175
+ try {
176
+ return JSON.parse(text);
177
+ }
178
+ catch (error) {
179
+ throw new EscliError(`notion db query: ${flagName} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`, {
180
+ code: ErrorCode.JsonInvalid,
181
+ exitCode: ExitCodes.Usage,
182
+ });
183
+ }
184
+ }
185
+ async function parseSorts(flags) {
186
+ if (flags['sort-json']) {
187
+ const parsed = await parseJsonFlag(flags['sort-json'], '--sort-json');
188
+ const result = z.array(SortSchema).safeParse(parsed);
189
+ if (!result.success) {
190
+ throw new EscliError('notion db query: --sort-json must be a Notion sorts array.', {
191
+ code: ErrorCode.UsageInvalid,
192
+ exitCode: ExitCodes.Usage,
193
+ details: result.error.issues,
194
+ });
195
+ }
196
+ return result.data;
197
+ }
198
+ return (flags.sort ?? []).map((value) => {
199
+ const separator = value.lastIndexOf(':');
200
+ const property = separator > 0 ? value.slice(0, separator).trim() : '';
201
+ const direction = separator > 0 ? value.slice(separator + 1).trim().toLowerCase() : '';
202
+ if (!property || !['asc', 'desc'].includes(direction)) {
203
+ throw new EscliError(`notion db query: malformed --sort ${value}; use prop:asc or prop:desc.`, {
204
+ code: ErrorCode.UsageInvalid,
205
+ exitCode: ExitCodes.Usage,
206
+ });
207
+ }
208
+ return { property, direction: direction === 'asc' ? 'ascending' : 'descending' };
209
+ });
210
+ }
211
+ function parseSelect(value) {
212
+ return value?.split(',').map((part) => part.trim()).filter(Boolean) ?? [];
213
+ }
214
+ function validateTimestampFilter(filter) {
215
+ if (!filter || typeof filter !== 'object' || Array.isArray(filter))
216
+ return;
217
+ const record = filter;
218
+ if (typeof record.timestamp === 'string' && typeof record.property === 'string') {
219
+ throw new EscliError('notion db query: timestamp filters must not include property. Use {"timestamp":"created_time","created_time":{...}}.', {
220
+ code: ErrorCode.UsageInvalid,
221
+ exitCode: ExitCodes.Usage,
222
+ });
223
+ }
224
+ }
225
+ function parseDataSourceTarget(target) {
226
+ const normalized = target.trim();
227
+ if (!normalized)
228
+ throwInvalidTarget(target);
229
+ const fromUrl = extractNotionIdFromUrl(normalized);
230
+ const candidate = fromUrl ?? normalized;
231
+ const compact = candidate.replaceAll('-', '');
232
+ if (/^(ds_)?[0-9a-f]{32}$/iu.test(compact))
233
+ return candidate;
234
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu.test(candidate))
235
+ return candidate;
236
+ if (/^ds_[A-Za-z0-9_-]+$/u.test(candidate))
237
+ return candidate;
238
+ throwInvalidTarget(target);
239
+ }
240
+ function extractNotionIdFromUrl(value) {
241
+ let url;
242
+ try {
243
+ url = new URL(value);
244
+ }
245
+ catch {
246
+ return undefined;
247
+ }
248
+ const sourceParam = url.searchParams.get('source') ?? url.searchParams.get('data_source_id') ?? url.searchParams.get('dataSourceId');
249
+ if (sourceParam)
250
+ return sourceParam;
251
+ const match = /([0-9a-f]{32})(?:[/?#-]|$)/iu.exec(url.pathname);
252
+ return match?.[1];
253
+ }
254
+ function throwInvalidTarget(target) {
255
+ throw new EscliError(`notion db query: not a Notion database/data-source ID or URL: ${target}`, {
256
+ code: ErrorCode.UsageInvalid,
257
+ exitCode: ExitCodes.Error,
258
+ });
259
+ }
260
+ function shapeData(result, context) {
261
+ const list = asQueryList(result.data);
262
+ const rows = (list.results ?? []).map((item) => shapeRow(item, context.titleProp));
263
+ return DbQueryDataSchema.parse({
264
+ source: {
265
+ database_id: extractDatabaseId(list.results),
266
+ data_source_id: context.dataSourceId,
267
+ name: extractSourceName(result.data),
268
+ },
269
+ query: {
270
+ filter: summarizeFilter(context.filter),
271
+ sorts: context.sorts,
272
+ selected_properties: context.selectedProperties.length > 0 ? context.selectedProperties : inferSelectedProperties(rows),
273
+ },
274
+ rows,
275
+ has_more: Boolean(list.has_more ?? result.meta.next),
276
+ next: result.meta.next,
277
+ page_size: context.pageSize,
278
+ start_index: undefined,
279
+ more_count: undefined,
280
+ request_status: list.request_status,
281
+ warnings: warningsFor(list),
282
+ });
283
+ }
284
+ function asQueryList(value) {
285
+ if (!value || typeof value !== 'object' || Array.isArray(value))
286
+ return {};
287
+ return value;
288
+ }
289
+ function shapeRow(value, titleProp) {
290
+ const record = recordValue(value) ?? {};
291
+ const properties = recordValue(record.properties) ?? {};
292
+ const title = extractTitle(properties, titleProp) ?? stringValue(record.title) ?? stringValue(record.name) ?? '';
293
+ return {
294
+ id: stringValue(record.id) ?? '',
295
+ title,
296
+ properties: Object.fromEntries(Object.entries(properties).map(([key, property]) => [key, plainPropertyValue(property)])),
297
+ };
298
+ }
299
+ function extractTitle(properties, titleProp) {
300
+ if (titleProp && properties[titleProp] !== undefined)
301
+ return stringifyValue(plainPropertyValue(properties[titleProp]));
302
+ for (const [key, value] of Object.entries(properties)) {
303
+ const record = recordValue(value);
304
+ if (record?.type === 'title' || key.toLowerCase() === 'name')
305
+ return stringifyValue(plainPropertyValue(value));
306
+ }
307
+ return undefined;
308
+ }
309
+ function plainPropertyValue(property) {
310
+ const record = recordValue(property);
311
+ if (!record)
312
+ return property;
313
+ const type = stringValue(record.type);
314
+ if (type && record[type] !== undefined)
315
+ return plainTypedValue(record[type]);
316
+ return property;
317
+ }
318
+ function plainTypedValue(value) {
319
+ if (Array.isArray(value))
320
+ return value.map((item) => stringifyValue(plainTypedValue(item))).filter(Boolean).join(', ');
321
+ const record = recordValue(value);
322
+ if (!record)
323
+ return value;
324
+ if (typeof record.plain_text === 'string')
325
+ return record.plain_text;
326
+ if (typeof record.name === 'string')
327
+ return record.name;
328
+ if (typeof record.start === 'string')
329
+ return record.end ? `${record.start} → ${String(record.end)}` : record.start;
330
+ if (typeof record.number === 'number')
331
+ return record.number;
332
+ if (typeof record.checkbox === 'boolean')
333
+ return record.checkbox;
334
+ if (typeof record.email === 'string')
335
+ return record.email;
336
+ if (typeof record.phone_number === 'string')
337
+ return record.phone_number;
338
+ if (typeof record.url === 'string')
339
+ return record.url;
340
+ if (typeof record.content === 'string')
341
+ return record.content;
342
+ return JSON.stringify(record);
343
+ }
344
+ function renderDbQuery(data) {
345
+ const lines = [
346
+ `${data.source.name ? `${data.source.name} — ` : ''}data_source ${shortId(data.source.data_source_id)}`,
347
+ `query: ${data.query.filter ?? 'all'}${data.query.sorts.length > 0 ? ` · sort ${renderSortSummary(data.query.sorts)}` : ''}${data.query.selected_properties.length > 0 ? ` · props ${data.query.selected_properties.join(',')}` : ''}`,
348
+ `returned: ${data.rows.length} · page_size ${data.page_size ?? data.rows.length} · more ${data.has_more ? 'yes' : 'no'}`,
349
+ '',
350
+ ];
351
+ if (data.warnings && data.warnings.length > 0)
352
+ lines.push(...data.warnings.map((warning) => `warning: ${warning}`), '');
353
+ if (data.rows.length === 0)
354
+ lines.push('No matching rows.');
355
+ else
356
+ lines.push(renderRowsTable(data));
357
+ const trailer = renderTrailer({ next: data.next, remaining: data.more_count });
358
+ if (trailer)
359
+ lines.push('', trailer);
360
+ return lines.join('\n');
361
+ }
362
+ function renderRowsTable(data) {
363
+ const selected = data.query.selected_properties.length > 0 ? data.query.selected_properties : inferSelectedProperties(data.rows);
364
+ const titleHeader = selected[0] ?? 'Name';
365
+ const propertyHeaders = selected.slice(1);
366
+ const header = ['#', 'id', titleHeader, ...propertyHeaders];
367
+ const start = data.start_index ?? 1;
368
+ const rows = data.rows.map((row, index) => [
369
+ start + index,
370
+ shortId(row.id),
371
+ row.title ?? '',
372
+ ...propertyHeaders.map((property) => stringifyValue(row.properties[property])),
373
+ ]);
374
+ return renderTable({ header, rows });
375
+ }
376
+ function inferSelectedProperties(rows) {
377
+ const keys = new Set();
378
+ for (const row of rows) {
379
+ for (const key of Object.keys(row.properties))
380
+ keys.add(key);
381
+ }
382
+ return [...keys];
383
+ }
384
+ function renderSortSummary(sorts) {
385
+ return sorts.map((sort) => `${sort.property} ${sort.direction === 'ascending' ? '↑' : '↓'}`).join(', ');
386
+ }
387
+ function summarizeFilter(filter) {
388
+ if (filter === undefined)
389
+ return undefined;
390
+ const record = recordValue(filter);
391
+ if (!record)
392
+ return JSON.stringify(filter);
393
+ const property = stringValue(record.property) ?? stringValue(record.timestamp);
394
+ if (!property)
395
+ return JSON.stringify(filter);
396
+ const condition = Object.entries(record).find(([key]) => key !== 'property' && key !== 'timestamp');
397
+ const conditionRecord = recordValue(condition?.[1]);
398
+ if (!condition || !conditionRecord)
399
+ return JSON.stringify(filter);
400
+ const [operator, operand] = Object.entries(conditionRecord)[0] ?? [];
401
+ if (!operator)
402
+ return JSON.stringify(filter);
403
+ const renderedOperand = Array.isArray(operand) ? `[${operand.map(String).join(', ')}]` : stringifyValue(operand);
404
+ return `${property} ${operator.replaceAll('_', ' ')} ${renderedOperand}`;
405
+ }
406
+ function extractDatabaseId(results) {
407
+ for (const result of results ?? []) {
408
+ const parent = recordValue(recordValue(result)?.parent);
409
+ const databaseId = stringValue(parent?.database_id);
410
+ if (databaseId)
411
+ return databaseId;
412
+ }
413
+ return undefined;
414
+ }
415
+ function extractSourceName(value) {
416
+ const record = recordValue(value);
417
+ return stringValue(record?.source_name) ?? stringValue(record?.name) ?? stringValue(recordValue(record?.data_source)?.name);
418
+ }
419
+ function warningsFor(list) {
420
+ const status = recordValue(list.request_status);
421
+ if (status?.type === 'incomplete')
422
+ return ['query may be incomplete; narrow with date/status filters for complete coverage.'];
423
+ return undefined;
424
+ }
425
+ function shortId(value) {
426
+ if (value.length <= 13)
427
+ return value;
428
+ const compact = value.replaceAll('-', '');
429
+ if (compact.length >= 12)
430
+ return `${compact.slice(0, 7)}…${compact.slice(-4)}`;
431
+ return value;
432
+ }
433
+ function stringifyValue(value) {
434
+ if (value === null || value === undefined)
435
+ return '';
436
+ if (typeof value === 'string')
437
+ return value;
438
+ if (typeof value === 'number' || typeof value === 'boolean')
439
+ return String(value);
440
+ if (Array.isArray(value))
441
+ return value.map((item) => stringifyValue(item)).filter(Boolean).join(', ');
442
+ return JSON.stringify(value);
443
+ }
444
+ function recordValue(value) {
445
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
446
+ }
447
+ function stringValue(value) {
448
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
449
+ }
450
+ export { DbQueryDataSchema, renderDbQuery, parseDataSourceTarget, parseSelect, parseSorts, summarizeFilter, NOTION_VERSION };
451
+ //# sourceMappingURL=query.js.map
@@ -0,0 +1,74 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import { hasManifestFlag } from '../../../../lib/notion/manifest-pass.js';
3
+ import { z } from 'zod';
4
+ import { ErrorCode } from '@eightstate/contracts/errors';
5
+ import { BaseCommand } from '../../../../base-command.js';
6
+ import { writeStdout } from '../../../../io/io.js';
7
+ import { notionPagesCreate } from '../../../../services/notion.js';
8
+ import { dbWriteErrors, emitErrorEnvelope, emitSuccessEnvelope, normalizeCreate, rawOutput, readJsonObjectInput, renderCreate, stripCommandFields, toEscliError, } from '../../../../lib/notion/db-row/common.js';
9
+ export const NotionDbRowCreateDataSchema = z.object({
10
+ action: z.literal('created'),
11
+ page: z.object({ id: z.string(), title: z.string(), url: z.string() }),
12
+ parent: z.object({ data_source_id: z.string(), database_id: z.string().nullable() }),
13
+ written: z.record(z.string(), z.unknown()),
14
+ bot: z.string(),
15
+ last_edited_time: z.string(),
16
+ }).passthrough();
17
+ export default class NotionDbRowCreate extends BaseCommand {
18
+ static errors = dbWriteErrors;
19
+ static summary = 'Create a Notion data-source row';
20
+ static description = 'Create one new page/row in a Notion data source with typed properties.';
21
+ static examples = [
22
+ '<%= config.bin %> notion db row create 248104cd-477e-80af-bc30-000bd28de8f9 --properties \'{"Name":{"title":[{"text":{"content":"Launch checklist"}}]}}\'',
23
+ '<%= config.bin %> notion db row create 248104cd-477e-80af-bc30-000bd28de8f9 --properties @row.json --json',
24
+ '<%= config.bin %> notion db row create 248104cd-477e-80af-bc30-000bd28de8f9 --properties @row.json --raw',
25
+ ];
26
+ static aliases = ['notion db create row', 'notion ds row create'];
27
+ static flags = {
28
+ properties: Flags.string({ description: 'Typed Notion properties as JSON, @file, or - for stdin.', required: true }),
29
+ raw: Flags.boolean({ description: 'Emit verbatim Notion response body.', default: false }),
30
+ };
31
+ static args = {
32
+ data_source_id: Args.string({ description: 'Target Notion data source ID.', required: true }),
33
+ };
34
+ static enableJsonFlag = true;
35
+ static strict = true;
36
+ async run() {
37
+ if (hasManifestFlag(this.argv)) {
38
+ await super.run();
39
+ return;
40
+ }
41
+ const start = Date.now();
42
+ let json = this.argv.includes('--json');
43
+ try {
44
+ const { args, flags } = await this.parse(NotionDbRowCreate);
45
+ json = Boolean(flags.json);
46
+ const properties = await readJsonObjectInput(flags.properties, 'properties');
47
+ const result = await notionPagesCreate({ data_source_id: args.data_source_id }, properties);
48
+ if (flags.raw) {
49
+ await writeStdout(`${JSON.stringify(rawOutput(result))}\n`);
50
+ return;
51
+ }
52
+ const data = normalizeCreate(result, properties);
53
+ const cleanData = stripCommandFields(data);
54
+ if (json)
55
+ await emitSuccessEnvelope(this.id ?? 'notion db row create', this.config.version, cleanData, Date.now() - start);
56
+ else
57
+ await writeStdout(`${renderCreate(data)}\n`);
58
+ }
59
+ catch (error) {
60
+ await emitErrorEnvelope(this.id ?? 'notion db row create', this.config.version, toEscliError(error), Date.now() - start, json);
61
+ }
62
+ }
63
+ async execute() {
64
+ const { args, flags } = await this.parse(NotionDbRowCreate);
65
+ const properties = await readJsonObjectInput(flags.properties, 'properties');
66
+ const result = await notionPagesCreate({ data_source_id: args.data_source_id }, properties);
67
+ if (flags.raw)
68
+ return rawOutput(result);
69
+ return stripCommandFields(normalizeCreate(result, properties));
70
+ }
71
+ }
72
+ export { NotionDbRowCreate };
73
+ export { ErrorCode };
74
+ //# sourceMappingURL=create.js.map