@dinoxx/dinox-cli 1.0.17 → 1.0.19

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 (111) hide show
  1. package/README.md +72 -34
  2. package/dist/cli.js +15 -1
  3. package/dist/commands/auth/index.d.ts +2 -0
  4. package/dist/commands/auth/index.js +209 -163
  5. package/dist/commands/boxes/index.d.ts +2 -0
  6. package/dist/commands/boxes/index.js +119 -75
  7. package/dist/commands/boxes/repo.d.ts +8 -0
  8. package/dist/commands/boxes/repo.js +41 -0
  9. package/dist/commands/config/index.d.ts +2 -0
  10. package/dist/commands/config/index.js +85 -36
  11. package/dist/commands/descriptor/register.d.ts +8 -0
  12. package/dist/commands/descriptor/register.js +113 -0
  13. package/dist/commands/descriptor/types.d.ts +37 -0
  14. package/dist/commands/descriptor/types.js +17 -0
  15. package/dist/commands/graph/index.d.ts +2 -0
  16. package/dist/commands/graph/index.js +143 -73
  17. package/dist/commands/info/index.d.ts +2 -0
  18. package/dist/commands/info/index.js +41 -14
  19. package/dist/commands/mcp/index.js +11 -10
  20. package/dist/commands/mcp/server.js +6 -3
  21. package/dist/commands/mcp/tools.js +30 -104
  22. package/dist/commands/mcp/types.d.ts +8 -1
  23. package/dist/commands/meta/index.d.ts +2 -0
  24. package/dist/commands/meta/index.js +64 -33
  25. package/dist/commands/meta/repo.d.ts +6 -0
  26. package/dist/commands/meta/repo.js +6 -0
  27. package/dist/commands/notes/actions.d.ts +10 -0
  28. package/dist/commands/notes/actions.js +403 -0
  29. package/dist/commands/notes/descriptors.d.ts +4 -0
  30. package/dist/commands/notes/descriptors.js +250 -0
  31. package/dist/commands/notes/dryRun.d.ts +31 -0
  32. package/dist/commands/notes/dryRun.js +130 -0
  33. package/dist/commands/notes/index.d.ts +2 -51
  34. package/dist/commands/notes/index.js +2 -581
  35. package/dist/commands/notes/repo.d.ts +9 -0
  36. package/dist/commands/notes/repo.js +56 -10
  37. package/dist/commands/notes/runtime.d.ts +28 -0
  38. package/dist/commands/notes/runtime.js +185 -0
  39. package/dist/commands/notes/searchOutput.d.ts +10 -2
  40. package/dist/commands/notes/searchOutput.js +14 -2
  41. package/dist/commands/notes/searchResponse.d.ts +51 -0
  42. package/dist/commands/notes/searchResponse.js +56 -0
  43. package/dist/commands/notes/searchSql.js +2 -0
  44. package/dist/commands/prompt/index.d.ts +2 -0
  45. package/dist/commands/prompt/index.js +122 -72
  46. package/dist/commands/prompt/repo.d.ts +7 -0
  47. package/dist/commands/prompt/repo.js +46 -0
  48. package/dist/commands/schema/commandMetadata.d.ts +2 -0
  49. package/dist/commands/schema/commandMetadata.js +18 -0
  50. package/dist/commands/schema/globalOptions.d.ts +3 -0
  51. package/dist/commands/schema/globalOptions.js +3 -0
  52. package/dist/commands/schema/index.d.ts +4 -0
  53. package/dist/commands/schema/index.js +109 -0
  54. package/dist/commands/schema/renderSkills.d.ts +6 -0
  55. package/dist/commands/schema/renderSkills.js +216 -0
  56. package/dist/commands/schema/spec.d.ts +6 -0
  57. package/dist/commands/schema/spec.js +40 -0
  58. package/dist/commands/schema/types.d.ts +47 -0
  59. package/dist/commands/schema/types.js +1 -0
  60. package/dist/commands/storage/index.d.ts +4 -0
  61. package/dist/commands/storage/index.js +292 -0
  62. package/dist/commands/storage/repo.d.ts +162 -0
  63. package/dist/commands/storage/repo.js +751 -0
  64. package/dist/commands/sync.d.ts +2 -0
  65. package/dist/commands/sync.js +41 -27
  66. package/dist/commands/tags/index.d.ts +2 -0
  67. package/dist/commands/tags/index.js +121 -79
  68. package/dist/commands/tags/repo.d.ts +7 -0
  69. package/dist/commands/tags/repo.js +52 -0
  70. package/dist/commands/todo/dryRun.d.ts +23 -0
  71. package/dist/commands/todo/dryRun.js +133 -0
  72. package/dist/commands/todo/index.d.ts +2 -0
  73. package/dist/commands/todo/index.js +285 -135
  74. package/dist/commands/todo/repo.d.ts +11 -0
  75. package/dist/commands/todo/repo.js +37 -9
  76. package/dist/commands/update.d.ts +3 -0
  77. package/dist/commands/update.js +35 -13
  78. package/dist/commands/writeDryRun.d.ts +62 -0
  79. package/dist/commands/writeDryRun.js +131 -0
  80. package/dist/constants/links.d.ts +1 -1
  81. package/dist/constants/links.js +1 -1
  82. package/dist/daemon/ensure.js +3 -2
  83. package/dist/daemon/index.js +17 -15
  84. package/dist/dinox.js +9 -13
  85. package/dist/powersync/schema/index.d.ts +27 -0
  86. package/dist/powersync/schema/index.js +2 -0
  87. package/dist/powersync/schema/note.d.ts +1 -0
  88. package/dist/powersync/schema/note.js +1 -0
  89. package/dist/powersync/schema/projects.d.ts +3 -0
  90. package/dist/powersync/schema/projects.js +3 -0
  91. package/dist/powersync/schema/storage.d.ts +24 -0
  92. package/dist/powersync/schema/storage.js +24 -0
  93. package/dist/skills/check.d.ts +6 -0
  94. package/dist/skills/check.js +254 -0
  95. package/dist/utils/bidlinkCanonicalization.d.ts +2 -0
  96. package/dist/utils/bidlinkCanonicalization.js +161 -0
  97. package/dist/utils/dryRun.d.ts +13 -0
  98. package/dist/utils/dryRun.js +69 -0
  99. package/dist/utils/output.d.ts +19 -0
  100. package/dist/utils/output.js +155 -1
  101. package/dist/utils/terminalSanitization.d.ts +2 -0
  102. package/dist/utils/terminalSanitization.js +33 -0
  103. package/dist/utils/tiptapMarkdown.d.ts +4 -1
  104. package/dist/utils/tiptapMarkdown.js +285 -113
  105. package/dist/utils/tiptapMarkdownSchema.d.ts +37 -0
  106. package/dist/utils/tiptapMarkdownSchema.js +577 -0
  107. package/dist/utils/tiptapMarkdownSchemaUtils.d.ts +74 -0
  108. package/dist/utils/tiptapMarkdownSchemaUtils.js +394 -0
  109. package/dist/utils/updateNotice.d.ts +7 -0
  110. package/dist/utils/updateNotice.js +64 -0
  111. package/package.json +25 -3
@@ -1,581 +1,2 @@
1
- import { loadConfig } from '../../config/store.js';
2
- import { resolveConfig } from '../../config/resolve.js';
3
- import { DinoxError } from '../../utils/errors.js';
4
- import { readStringOrFile } from '../../utils/argValue.js';
5
- import { generateId } from '../../utils/id.js';
6
- import { convertMarkdownToNoteContent } from '../../utils/tiptapMarkdown.js';
7
- import { printYaml } from '../../utils/output.js';
8
- import { parseSyncTimeoutMs } from '../../utils/syncTimeout.js';
9
- import { resolveAuthIdentity } from '../../auth/userInfo.js';
10
- import { connectPowerSync, waitForIdleDataFlow } from '../../powersync/runtime.js';
11
- import { syncNoteTokenIndex } from '../../powersync/tokenIndex.js';
12
- import { createNote, getNote, getNoteContext, getNoteDetails, getNotePreview, assertNotesUpdatable, resolveZettelBoxIdsForCreate, resolveZettelBoxIdsForSearch, resolveZettelBoxIdsForUpdate, searchNotes, softDeleteNote, updateNote, validateTagsForCreate, validateTagsForUpdate, } from './repo.js';
13
- import { parseNoteSearchFields, projectNoteSearchFields, } from './searchOutput.js';
14
- import { parseCreatedAtRange } from './searchTime.js';
15
- const DEFAULT_NOTE_SEARCH_LIMIT = 50;
16
- const MAX_NOTE_SEARCH_LIMIT = 500;
17
- function parseNoteType(value) {
18
- const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
19
- if (normalized === 'note' || normalized === 'crawl') {
20
- return normalized;
21
- }
22
- throw new DinoxError('--type must be one of: note, crawl');
23
- }
24
- function parseSearchLimit(value) {
25
- if (value === undefined || value === null || value === '') {
26
- return DEFAULT_NOTE_SEARCH_LIMIT;
27
- }
28
- if (typeof value !== 'string') {
29
- throw new DinoxError('--limit must be a positive integer');
30
- }
31
- const trimmed = value.trim();
32
- if (!/^\d+$/.test(trimmed)) {
33
- throw new DinoxError('--limit must be a positive integer');
34
- }
35
- const parsed = Number(trimmed);
36
- if (!Number.isFinite(parsed) || parsed <= 0) {
37
- throw new DinoxError('--limit must be a positive integer');
38
- }
39
- if (parsed > MAX_NOTE_SEARCH_LIMIT) {
40
- throw new DinoxError(`--limit cannot be greater than ${MAX_NOTE_SEARCH_LIMIT}`);
41
- }
42
- return Math.trunc(parsed);
43
- }
44
- function parseSearchOffset(value) {
45
- if (value === undefined || value === null || value === '') {
46
- return 0;
47
- }
48
- if (typeof value !== 'string') {
49
- throw new DinoxError('--offset must be a non-negative integer');
50
- }
51
- const trimmed = value.trim();
52
- if (!/^\d+$/.test(trimmed)) {
53
- throw new DinoxError('--offset must be a non-negative integer');
54
- }
55
- const parsed = Number(trimmed);
56
- if (!Number.isFinite(parsed) || parsed < 0) {
57
- throw new DinoxError('--offset must be a non-negative integer');
58
- }
59
- return Math.trunc(parsed);
60
- }
61
- function parsePreviewLineCount(value) {
62
- if (value === undefined || value === null || value === '') {
63
- return 10;
64
- }
65
- if (typeof value !== 'string') {
66
- throw new DinoxError('--lines must be a positive integer');
67
- }
68
- const trimmed = value.trim();
69
- if (!/^\d+$/.test(trimmed)) {
70
- throw new DinoxError('--lines must be a positive integer');
71
- }
72
- const parsed = Number(trimmed);
73
- if (!Number.isFinite(parsed) || parsed <= 0) {
74
- throw new DinoxError('--lines must be a positive integer');
75
- }
76
- if (parsed > 500) {
77
- throw new DinoxError('--lines cannot be greater than 500');
78
- }
79
- return Math.trunc(parsed);
80
- }
81
- async function parseStringInput(value) {
82
- return readStringOrFile(value);
83
- }
84
- function parseListInput(raw, optionName) {
85
- const value = raw.trim();
86
- if (!value) {
87
- return [];
88
- }
89
- if (value.startsWith('[')) {
90
- try {
91
- const parsed = JSON.parse(value);
92
- if (!Array.isArray(parsed)) {
93
- throw new DinoxError(`${optionName} must be a JSON array`);
94
- }
95
- return Array.from(new Set(parsed
96
- .map((entry) => String(entry).trim())
97
- .filter((entry) => entry.length > 0)));
98
- }
99
- catch (error) {
100
- if (error instanceof DinoxError) {
101
- throw error;
102
- }
103
- throw new DinoxError(`Invalid list for ${optionName}`, { cause: error });
104
- }
105
- }
106
- return Array.from(new Set(value
107
- .split(/[\n,,]/g)
108
- .map((entry) => entry.trim())
109
- .filter((entry) => entry.length > 0)));
110
- }
111
- async function parseStringListInput(value, optionName) {
112
- const raw = await readStringOrFile(value);
113
- return parseListInput(raw, optionName);
114
- }
115
- async function parseSearchListInput(value, optionName) {
116
- const raw = await readStringOrFile(value);
117
- return {
118
- values: parseListInput(raw, optionName),
119
- isExplicitEmptyArray: isExplicitEmptyArrayLiteral(raw),
120
- };
121
- }
122
- function isExplicitEmptyArrayLiteral(value) {
123
- const normalized = value.trim();
124
- if (!normalized.startsWith('[')) {
125
- return false;
126
- }
127
- try {
128
- const parsed = JSON.parse(normalized);
129
- return Array.isArray(parsed) && parsed.length === 0;
130
- }
131
- catch {
132
- return false;
133
- }
134
- }
135
- async function parseTargetNoteIds(idArg, idsOption, commandName) {
136
- const merged = [];
137
- const idFromArg = typeof idArg === 'string' ? idArg.trim() : '';
138
- if (idFromArg) {
139
- merged.push(idFromArg);
140
- }
141
- if (typeof idsOption === 'string') {
142
- merged.push(...(await parseStringListInput(idsOption, '--ids')));
143
- }
144
- const ids = Array.from(new Set(merged
145
- .map((id) => id.trim())
146
- .filter((id) => id.length > 0)));
147
- if (ids.length === 0) {
148
- throw new DinoxError(`Provide <id> or --ids for ${commandName}`);
149
- }
150
- return ids;
151
- }
152
- async function syncBeforeNoteCommand(db, offline, timeoutMs) {
153
- if (offline) {
154
- return;
155
- }
156
- await waitForIdleDataFlow(db, Math.min(timeoutMs, 10_000));
157
- await syncNoteTokenIndex(db);
158
- }
159
- function normalizeDaysForMeta(value) {
160
- if (typeof value !== 'string') {
161
- return undefined;
162
- }
163
- const trimmed = value.trim();
164
- if (!/^\d+$/.test(trimmed)) {
165
- return undefined;
166
- }
167
- const parsed = Number(trimmed);
168
- if (!Number.isFinite(parsed) || parsed <= 0) {
169
- return undefined;
170
- }
171
- return Math.trunc(parsed);
172
- }
173
- export function buildNoteSearchResponse(input) {
174
- const query = input.query.trim();
175
- const tagsExpression = input.tagsExpression?.trim() ?? '';
176
- const sqlExpression = input.sqlExpression?.trim() ?? '';
177
- const hasCreatedAtRange = Boolean(input.createdAtFrom || input.createdAtTo);
178
- const offset = input.offset ?? 0;
179
- const limit = input.limit ?? null;
180
- const returnedCount = input.notes.length;
181
- const totalCount = input.totalCount;
182
- const hasMore = offset + returnedCount < totalCount;
183
- return {
184
- meta: {
185
- query,
186
- returned_count: returnedCount,
187
- total_count: totalCount,
188
- stale: input.stale,
189
- pagination: {
190
- limit,
191
- offset,
192
- has_more: hasMore,
193
- },
194
- fields: input.fields && input.fields.length > 0 ? [...input.fields] : null,
195
- applied_filters: {
196
- include_deleted: input.includeDeleted,
197
- tags_expression: tagsExpression || null,
198
- created_at_range: hasCreatedAtRange
199
- ? {
200
- from: input.createdAtFrom ?? null,
201
- to: input.createdAtTo ?? null,
202
- }
203
- : null,
204
- days: input.days ?? null,
205
- box_names: [...input.boxNames],
206
- box_ids: [...input.boxIds],
207
- match_empty_boxes: input.matchEmptyBoxes,
208
- sql_expression: sqlExpression || null,
209
- },
210
- },
211
- notes: input.notes,
212
- };
213
- }
214
- export function registerNoteCommands(program) {
215
- const note = program.command('note').description('Manage notes');
216
- note
217
- .command('search')
218
- .description('Search notes and return note list for AI agents')
219
- .argument('[query]', 'search query')
220
- .option('--tags <expr>', 'tag expression with AND/OR/NOT, or [] for empty tags')
221
- .option('--from <date>', 'created_at start (YYYY-MM-DD or ISO datetime)')
222
- .option('--to <date>', 'created_at end (YYYY-MM-DD or ISO datetime)')
223
- .option('--days <n>', 'recent N days by created_at (cannot be used with --from/--to)')
224
- .option('--box <string|@file>', 'zettel box names (JSON array/comma list), or [] for empty boxes')
225
- .option('--sql <expr>', 'SQL-like expression over id/content_md/summary/tags/zettel_boxes/created_at/type')
226
- .option('--limit <n>', `maximum returned notes (default: ${DEFAULT_NOTE_SEARCH_LIMIT}, max: ${MAX_NOTE_SEARCH_LIMIT})`)
227
- .option('--offset <n>', 'result offset for pagination (default: 0)')
228
- .option('--fields <list>', 'comma-separated fields: id,title,summary,tags,created_at,zettel_boxes')
229
- .option('--include-deleted', 'include soft-deleted notes')
230
- .action(async (query, options, command) => {
231
- const globals = command.optsWithGlobals?.() ?? {};
232
- const offline = Boolean(globals.offline);
233
- const config = resolveConfig(await loadConfig());
234
- const timeoutMs = parseSyncTimeoutMs(globals.syncTimeout) ?? config.sync.timeoutMs;
235
- const { db, stale } = await connectPowerSync({ config, offline, timeoutMs });
236
- try {
237
- await syncBeforeNoteCommand(db, offline, timeoutMs);
238
- const keyword = query?.trim() ?? '';
239
- const tagsExpression = typeof options.tags === 'string' ? options.tags.trim() : '';
240
- const { createdAtFrom, createdAtTo } = parseCreatedAtRange({
241
- from: options.from,
242
- to: options.to,
243
- days: options.days,
244
- });
245
- const boxInput = typeof options.box === 'string' ? options.box : '';
246
- const parsedBoxInput = boxInput
247
- ? await parseSearchListInput(boxInput, '--box')
248
- : { values: [], isExplicitEmptyArray: false };
249
- const zettelBoxIds = await resolveZettelBoxIdsForSearch(db, parsedBoxInput.values);
250
- const sqlExpression = typeof options.sql === 'string' ? options.sql.trim() : '';
251
- const limit = parseSearchLimit(options.limit);
252
- const offset = parseSearchOffset(options.offset);
253
- const fields = parseNoteSearchFields(options.fields, '--fields');
254
- const rows = await searchNotes(db, keyword, {
255
- includeDeleted: Boolean(options.includeDeleted),
256
- tagsExpression: tagsExpression || undefined,
257
- createdAtFrom,
258
- createdAtTo,
259
- zettelBoxIds,
260
- matchEmptyZettelBoxes: parsedBoxInput.isExplicitEmptyArray,
261
- sqlExpression: sqlExpression || undefined,
262
- });
263
- const totalCount = rows.length;
264
- const pagedRows = rows.slice(offset, offset + limit);
265
- const projectedRows = projectNoteSearchFields(pagedRows, fields);
266
- const response = buildNoteSearchResponse({
267
- query: keyword,
268
- notes: projectedRows,
269
- totalCount,
270
- stale: offline ? true : stale,
271
- includeDeleted: Boolean(options.includeDeleted),
272
- limit,
273
- offset,
274
- fields,
275
- tagsExpression: tagsExpression || undefined,
276
- createdAtFrom,
277
- createdAtTo,
278
- days: normalizeDaysForMeta(options.days),
279
- boxNames: parsedBoxInput.values,
280
- boxIds: zettelBoxIds,
281
- matchEmptyBoxes: parsedBoxInput.isExplicitEmptyArray,
282
- sqlExpression: sqlExpression || undefined,
283
- });
284
- printYaml(response);
285
- }
286
- finally {
287
- await db.close().catch(() => undefined);
288
- }
289
- });
290
- note
291
- .command('get')
292
- .description('Get note by id')
293
- .argument('<id>', 'note id')
294
- .option('--context-only', 'return lightweight note context (title/tags/summary/links)')
295
- .action(async (id, options, command) => {
296
- const globals = command.optsWithGlobals?.() ?? {};
297
- const offline = Boolean(globals.offline);
298
- const jsonOutput = Boolean(globals.json);
299
- const config = resolveConfig(await loadConfig());
300
- const timeoutMs = parseSyncTimeoutMs(globals.syncTimeout) ?? config.sync.timeoutMs;
301
- const { db, stale } = await connectPowerSync({ config, offline, timeoutMs });
302
- try {
303
- await syncBeforeNoteCommand(db, offline, timeoutMs);
304
- if (options.contextOnly) {
305
- const context = await getNoteContext(db, id);
306
- if (!context) {
307
- throw new DinoxError(`Note not found: ${id}`, {
308
- code: 'NOTE_NOT_FOUND',
309
- suggestion: "Run 'dino note search <keyword>' to locate the note id.",
310
- recoverable: true,
311
- });
312
- }
313
- printYaml({ stale: offline ? true : stale, note: context });
314
- return;
315
- }
316
- const note = await getNote(db, id);
317
- if (!note) {
318
- throw new DinoxError(`Note not found: ${id}`, {
319
- code: 'NOTE_NOT_FOUND',
320
- suggestion: "Run 'dino note search <keyword>' to locate the note id.",
321
- recoverable: true,
322
- });
323
- }
324
- if (jsonOutput) {
325
- printYaml({ stale, note });
326
- return;
327
- }
328
- if (stale && !offline) {
329
- console.log('warning: sync timed out; result may be stale');
330
- }
331
- console.log(`${note.id}`);
332
- console.log(`title: ${note.title ?? ''}`);
333
- if (note.updated_at) {
334
- console.log(`updated_at: ${note.updated_at}`);
335
- }
336
- if (note.is_del) {
337
- console.log('is_del: 1');
338
- }
339
- }
340
- finally {
341
- await db.close().catch(() => undefined);
342
- }
343
- });
344
- note
345
- .command('preview')
346
- .description('Preview the first N lines of note markdown')
347
- .argument('<id>', 'note id')
348
- .option('--lines <n>', 'number of lines to return (default: 10)')
349
- .action(async (id, options, command) => {
350
- const globals = command.optsWithGlobals?.() ?? {};
351
- const offline = Boolean(globals.offline);
352
- const config = resolveConfig(await loadConfig());
353
- const timeoutMs = parseSyncTimeoutMs(globals.syncTimeout) ?? config.sync.timeoutMs;
354
- const lines = parsePreviewLineCount(options.lines);
355
- const { db, stale } = await connectPowerSync({ config, offline, timeoutMs });
356
- try {
357
- await syncBeforeNoteCommand(db, offline, timeoutMs);
358
- const preview = await getNotePreview(db, id, lines);
359
- if (!preview) {
360
- throw new DinoxError(`Note not found: ${id}`, {
361
- code: 'NOTE_NOT_FOUND',
362
- suggestion: "Run 'dino note search <keyword>' to locate the note id.",
363
- recoverable: true,
364
- });
365
- }
366
- printYaml({ stale: offline ? true : stale, note: preview });
367
- }
368
- finally {
369
- await db.close().catch(() => undefined);
370
- }
371
- });
372
- note
373
- .command('detail')
374
- .description('Get full note details by id')
375
- .argument('[id]', 'note id')
376
- .option('--ids <string|@file>', 'note ids (JSON array or comma/newline-separated) for batch read')
377
- .action(async (id, options, command) => {
378
- const globals = command.optsWithGlobals?.() ?? {};
379
- const offline = Boolean(globals.offline);
380
- const config = resolveConfig(await loadConfig());
381
- const timeoutMs = parseSyncTimeoutMs(globals.syncTimeout) ?? config.sync.timeoutMs;
382
- const { db } = await connectPowerSync({ config, offline, timeoutMs });
383
- try {
384
- await syncBeforeNoteCommand(db, offline, timeoutMs);
385
- const ids = await parseTargetNoteIds(id, options.ids, 'detail');
386
- const notes = await getNoteDetails(db, ids);
387
- const foundIds = new Set(notes.map((note) => note.id));
388
- const missing = ids.filter((noteId) => !foundIds.has(noteId));
389
- if (missing.length > 0) {
390
- if (ids.length === 1) {
391
- throw new DinoxError(`Note not found: ${missing[0]}`);
392
- }
393
- throw new DinoxError(`Notes not found: ${missing.join(', ')}`);
394
- }
395
- if (notes.length === 1) {
396
- printYaml(notes[0]);
397
- }
398
- else {
399
- printYaml(notes);
400
- }
401
- }
402
- finally {
403
- await db.close().catch(() => undefined);
404
- }
405
- });
406
- note
407
- .command('create')
408
- .description('Create a new note')
409
- .requiredOption('--title <string>', 'note title')
410
- .requiredOption('--content <string|@file>', 'markdown content')
411
- .option('--type <note|crawl>', 'note type: note or crawl', 'crawl')
412
- .option('--tags <string|@file>', 'tag list (JSON array or comma/newline-separated)')
413
- .option('--zettel_boxes <string|@file>', 'zettel box names (JSON array or comma/newline-separated)')
414
- .action(async (options, command) => {
415
- const globals = command.optsWithGlobals?.() ?? {};
416
- const offline = Boolean(globals.offline);
417
- const jsonOutput = Boolean(globals.json);
418
- const rawConfig = await loadConfig();
419
- const config = resolveConfig(rawConfig);
420
- const timeoutMs = parseSyncTimeoutMs(globals.syncTimeout) ?? config.sync.timeoutMs;
421
- const identity = await resolveAuthIdentity(rawConfig, config);
422
- const { db, stale } = await connectPowerSync({ config, offline, timeoutMs });
423
- try {
424
- await syncBeforeNoteCommand(db, offline, timeoutMs);
425
- const contentMd = await parseStringInput(options.content);
426
- const { contentJson, contentText } = convertMarkdownToNoteContent(contentMd);
427
- const tagsInput = typeof options.tags === 'string'
428
- ? await parseStringListInput(options.tags, '--tags')
429
- : [];
430
- const tags = await validateTagsForCreate(db, tagsInput);
431
- const zettelBoxesOption = typeof options.zettel_boxes === 'string'
432
- ? options.zettel_boxes
433
- : typeof options.zettelBoxes === 'string'
434
- ? options.zettelBoxes
435
- : undefined;
436
- const zettelBoxNames = zettelBoxesOption
437
- ? await parseStringListInput(zettelBoxesOption, '--zettel_boxes')
438
- : [];
439
- const zettelBoxIds = await resolveZettelBoxIdsForCreate(db, zettelBoxNames);
440
- const noteType = parseNoteType(options.type);
441
- const id = generateId();
442
- await createNote(db, {
443
- id,
444
- title: String(options.title),
445
- noteType,
446
- contentMd,
447
- contentJson,
448
- contentText,
449
- tags,
450
- zettelBoxIds,
451
- userId: identity.userId,
452
- });
453
- if (!offline && !config.powersync.uploadBaseUrl) {
454
- console.log('warning: upload disabled (powersync.uploadBaseUrl is unset); changes are local-only for now');
455
- }
456
- if (!offline) {
457
- await waitForIdleDataFlow(db, Math.min(timeoutMs, 5_000));
458
- }
459
- const payload = { ok: true, id, stale: offline ? true : stale };
460
- if (jsonOutput) {
461
- printYaml(payload);
462
- }
463
- else {
464
- console.log(id);
465
- if (stale && !offline) {
466
- console.log('warning: sync timed out; local cache may be stale');
467
- }
468
- }
469
- }
470
- finally {
471
- await db.close().catch(() => undefined);
472
- }
473
- });
474
- note
475
- .command('delete')
476
- .description('Soft-delete a note (is_del=1)')
477
- .argument('<id>', 'note id')
478
- .action(async (id, _options, command) => {
479
- const globals = command.optsWithGlobals?.() ?? {};
480
- const offline = Boolean(globals.offline);
481
- const jsonOutput = Boolean(globals.json);
482
- const config = resolveConfig(await loadConfig());
483
- const timeoutMs = parseSyncTimeoutMs(globals.syncTimeout) ?? config.sync.timeoutMs;
484
- const { db, stale } = await connectPowerSync({ config, offline, timeoutMs });
485
- try {
486
- await syncBeforeNoteCommand(db, offline, timeoutMs);
487
- await softDeleteNote(db, id);
488
- if (!offline && !config.powersync.uploadBaseUrl) {
489
- console.log('warning: upload disabled (powersync.uploadBaseUrl is unset); changes are local-only for now');
490
- }
491
- if (!offline) {
492
- await waitForIdleDataFlow(db, Math.min(timeoutMs, 5_000));
493
- }
494
- const payload = { ok: true, id, stale: offline ? true : stale };
495
- if (jsonOutput) {
496
- printYaml(payload);
497
- }
498
- else {
499
- console.log('OK');
500
- if (stale && !offline) {
501
- console.log('warning: sync timed out; local cache may be stale');
502
- }
503
- }
504
- }
505
- finally {
506
- await db.close().catch(() => undefined);
507
- }
508
- });
509
- note
510
- .command('update')
511
- .description('Update note tags/boxes by id')
512
- .argument('[id]', 'note id')
513
- .option('--ids <string|@file>', 'note ids (JSON array or comma/newline-separated) for batch update')
514
- .option('--tags <string|@file>', 'tag list (JSON array or comma/newline-separated)')
515
- .option('--boxes <string|@file>', 'zettel box names (JSON array or comma/newline-separated)')
516
- .action(async (id, options, command) => {
517
- const globals = command.optsWithGlobals?.() ?? {};
518
- const offline = Boolean(globals.offline);
519
- const jsonOutput = Boolean(globals.json);
520
- const config = resolveConfig(await loadConfig());
521
- const timeoutMs = parseSyncTimeoutMs(globals.syncTimeout) ?? config.sync.timeoutMs;
522
- const { db, stale } = await connectPowerSync({ config, offline, timeoutMs });
523
- try {
524
- await syncBeforeNoteCommand(db, offline, timeoutMs);
525
- const ids = await parseTargetNoteIds(id, options.ids, 'update');
526
- const hasTags = typeof options.tags === 'string';
527
- const hasBoxes = typeof options.boxes === 'string';
528
- if (!hasTags && !hasBoxes) {
529
- throw new DinoxError('At least one option is required: --tags or --boxes');
530
- }
531
- const tags = hasTags
532
- ? await validateTagsForUpdate(db, await parseStringListInput(options.tags, '--tags'))
533
- : undefined;
534
- const zettelBoxIds = hasBoxes
535
- ? await resolveZettelBoxIdsForUpdate(db, await parseStringListInput(options.boxes, '--boxes'))
536
- : undefined;
537
- await assertNotesUpdatable(db, ids);
538
- const results = [];
539
- for (const targetId of ids) {
540
- const result = await updateNote(db, { id: targetId, tags, zettelBoxIds });
541
- results.push(result);
542
- }
543
- const updatedCount = results.filter((item) => item.updated).length;
544
- if (!offline && !config.powersync.uploadBaseUrl) {
545
- console.log('warning: upload disabled (powersync.uploadBaseUrl is unset); changes are local-only for now');
546
- }
547
- if (!offline) {
548
- await waitForIdleDataFlow(db, Math.min(timeoutMs, 5_000));
549
- }
550
- const payload = {
551
- ok: true,
552
- total: results.length,
553
- updatedCount,
554
- results: results.map((item) => ({
555
- id: item.id,
556
- updated: item.updated,
557
- tags: item.tags,
558
- boxes: item.zettelBoxIds,
559
- })),
560
- stale: offline ? true : stale,
561
- };
562
- if (jsonOutput) {
563
- printYaml(payload);
564
- }
565
- else {
566
- if (results.length === 1) {
567
- console.log(results[0].updated ? 'OK' : 'No changes');
568
- }
569
- else {
570
- console.log(`Updated ${updatedCount}/${results.length} notes`);
571
- }
572
- if (stale && !offline) {
573
- console.log('warning: sync timed out; local cache may be stale');
574
- }
575
- }
576
- }
577
- finally {
578
- await db.close().catch(() => undefined);
579
- }
580
- });
581
- }
1
+ export { buildNoteSearchResponse, } from './searchResponse.js';
2
+ export { noteCommandGroup, registerNoteCommands } from './descriptors.js';
@@ -11,10 +11,12 @@ export type NoteRow = {
11
11
  created_at: string | null;
12
12
  updated_at: string | null;
13
13
  is_del: number | null;
14
+ is_starred: number | null;
14
15
  version: number | null;
15
16
  };
16
17
  type SearchNotesOptions = {
17
18
  includeDeleted: boolean;
19
+ starred?: boolean;
18
20
  tagsExpression?: string;
19
21
  createdAtFrom?: string;
20
22
  createdAtTo?: string;
@@ -28,6 +30,7 @@ export type SearchedNote = {
28
30
  summary: string;
29
31
  tags: string[];
30
32
  created_at: string | null;
33
+ is_starred: boolean;
31
34
  zettel_boxes: string[];
32
35
  };
33
36
  export type NoteDetail = {
@@ -38,6 +41,7 @@ export type NoteDetail = {
38
41
  source: string | null;
39
42
  tags: string | null;
40
43
  is_del: number | null;
44
+ is_starred: boolean;
41
45
  content_md: string | null;
42
46
  zettel_boxes: string[];
43
47
  };
@@ -49,6 +53,7 @@ export type NoteContext = {
49
53
  created_at: string | null;
50
54
  updated_at: string | null;
51
55
  is_del: number | null;
56
+ is_starred: boolean;
52
57
  zettel_boxes: string[];
53
58
  links: {
54
59
  backlinks: number;
@@ -70,8 +75,10 @@ export type UpdateNoteResult = {
70
75
  updated: boolean;
71
76
  tags: string[];
72
77
  zettelBoxIds: string[];
78
+ is_starred: boolean;
73
79
  };
74
80
  export declare function searchNotes(db: AbstractPowerSyncDatabase, query: string, options: SearchNotesOptions): Promise<SearchedNote[]>;
81
+ export declare function parseZettelBoxIds(raw: string | null): string[];
75
82
  export declare function getNote(db: AbstractPowerSyncDatabase, id: string): Promise<NoteRow | null>;
76
83
  export declare function getNoteContext(db: AbstractPowerSyncDatabase, id: string): Promise<NoteContext | null>;
77
84
  export declare function getNotePreview(db: AbstractPowerSyncDatabase, id: string, requestedLines: number): Promise<NotePreview | null>;
@@ -93,10 +100,12 @@ export declare function createNote(db: AbstractPowerSyncDatabase, input: {
93
100
  }>;
94
101
  export declare function validateTagsForCreate(db: AbstractPowerSyncDatabase, requestedTags: string[]): Promise<string[]>;
95
102
  export declare function validateTagsForUpdate(db: AbstractPowerSyncDatabase, requestedTags: string[]): Promise<string[]>;
103
+ export declare function parseStoredTags(raw: string | null): string[];
96
104
  export declare function updateNote(db: AbstractPowerSyncDatabase, input: {
97
105
  id: string;
98
106
  tags?: string[];
99
107
  zettelBoxIds?: string[];
108
+ starred?: boolean;
100
109
  }): Promise<UpdateNoteResult>;
101
110
  export declare function assertNotesUpdatable(db: AbstractPowerSyncDatabase, noteIds: string[]): Promise<void>;
102
111
  export declare function resolveZettelBoxIdsForCreate(db: AbstractPowerSyncDatabase, requestedNames: string[]): Promise<string[]>;