@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
@@ -0,0 +1,403 @@
1
+ import { resolveAuthIdentity } from '../../auth/userInfo.js';
2
+ import { canonicalizeBidlinksInMarkdown } from '../../utils/bidlinkCanonicalization.js';
3
+ import { printDryRunPreview } from '../../utils/dryRun.js';
4
+ import { generateId } from '../../utils/id.js';
5
+ import { convertMarkdownToNoteContent } from '../../utils/tiptapMarkdown.js';
6
+ import { getRequestedStructuredOutputFormat, printHumanReadableLine, printSuccess, } from '../../utils/output.js';
7
+ import { DinoxError } from '../../utils/errors.js';
8
+ import { waitForIdleDataFlow } from '../../powersync/runtime.js';
9
+ import { createNote, getNote, getNoteContext, getNoteDetails, getNotePreview, assertNotesUpdatable, resolveZettelBoxIdsForCreate, resolveZettelBoxIdsForSearch, resolveZettelBoxIdsForUpdate, searchNotes, softDeleteNote, updateNote, validateTagsForCreate, validateTagsForUpdate, } from './repo.js';
10
+ import { buildNoteCreateDryRunPreview, buildNoteDeleteDryRunPreview, buildNoteUpdateDryRunPreview, } from './dryRun.js';
11
+ import { parseNoteSearchFields, projectNoteSearchFields } from './searchOutput.js';
12
+ import { parseCreatedAtRange } from './searchTime.js';
13
+ import { buildNoteSearchResponse, normalizeDaysForMeta } from './searchResponse.js';
14
+ import { parseNoteType, parsePreviewLineCount, parseSearchLimit, parseSearchListInput, parseSearchOffset, parseStarredOption, parseStringInput, parseStringListInput, parseTargetNoteIds, syncBeforeNoteCommand, withNoteDatabase, } from './runtime.js';
15
+ function toPublicNoteContext(note) {
16
+ if (!note) {
17
+ return null;
18
+ }
19
+ const { zettel_boxes, ...rest } = note;
20
+ return {
21
+ ...rest,
22
+ boxes: zettel_boxes,
23
+ };
24
+ }
25
+ function toPublicNoteDetail(note) {
26
+ const { zettel_boxes, ...rest } = note;
27
+ return {
28
+ ...rest,
29
+ boxes: zettel_boxes,
30
+ };
31
+ }
32
+ function toPublicNote(note) {
33
+ if (!note) {
34
+ return null;
35
+ }
36
+ const { zettel_boxes, ...rest } = note;
37
+ return {
38
+ ...rest,
39
+ ...(zettel_boxes !== undefined ? { boxes: zettel_boxes } : {}),
40
+ };
41
+ }
42
+ async function settleMutation(db, timeoutMs) {
43
+ await waitForIdleDataFlow(db, Math.min(timeoutMs, 5_000));
44
+ }
45
+ async function executeNoteUpdateOperation(args) {
46
+ const structuredOutput = getRequestedStructuredOutputFormat() !== null;
47
+ await withNoteDatabase(args.command, async ({ db, config, offline, timeoutMs, stale }) => {
48
+ await syncBeforeNoteCommand(db, offline, timeoutMs);
49
+ const ids = await parseTargetNoteIds(args.id, args.idsOption, 'update');
50
+ const hasTags = typeof args.tagsOption === 'string';
51
+ const hasBoxes = typeof args.boxOption === 'string';
52
+ const hasStarred = typeof args.starred === 'boolean';
53
+ if (!hasTags && !hasBoxes && !hasStarred) {
54
+ throw new DinoxError('At least one option is required: --tags, --boxes, or --starred');
55
+ }
56
+ const tags = hasTags
57
+ ? await validateTagsForUpdate(db, await parseStringListInput(args.tagsOption, '--tags'))
58
+ : undefined;
59
+ const zettelBoxIds = hasBoxes
60
+ ? await resolveZettelBoxIdsForUpdate(db, await parseStringListInput(args.boxOption, '--boxes'))
61
+ : undefined;
62
+ await assertNotesUpdatable(db, ids);
63
+ if (args.dryRun) {
64
+ const preview = await buildNoteUpdateDryRunPreview({
65
+ db,
66
+ config,
67
+ offline,
68
+ ids,
69
+ tagsInput: tags,
70
+ zettelBoxNames: hasBoxes ? await parseStringListInput(args.boxOption, '--boxes') : undefined,
71
+ starred: hasStarred ? args.starred : undefined,
72
+ commandName: args.commandName ?? 'dino note update',
73
+ action: args.actionName ?? 'update_note',
74
+ });
75
+ printDryRunPreview(preview);
76
+ return;
77
+ }
78
+ const results = [];
79
+ for (const targetId of ids) {
80
+ results.push(await updateNote(db, {
81
+ id: targetId,
82
+ tags,
83
+ zettelBoxIds,
84
+ starred: hasStarred ? args.starred : undefined,
85
+ }));
86
+ }
87
+ const updatedCount = results.filter((item) => item.updated).length;
88
+ if (!offline && !config.powersync.uploadBaseUrl) {
89
+ printHumanReadableLine('warning: upload disabled (powersync.uploadBaseUrl is unset); changes are local-only for now');
90
+ }
91
+ if (!offline) {
92
+ await settleMutation(db, timeoutMs);
93
+ }
94
+ const payload = {
95
+ ok: true,
96
+ total: results.length,
97
+ updatedCount,
98
+ results: results.map((item) => ({
99
+ id: item.id,
100
+ updated: item.updated,
101
+ tags: item.tags,
102
+ boxes: item.zettelBoxIds,
103
+ is_starred: item.is_starred,
104
+ })),
105
+ stale: offline ? true : stale,
106
+ };
107
+ if (structuredOutput) {
108
+ printSuccess(payload);
109
+ return;
110
+ }
111
+ if (results.length === 1) {
112
+ if (!args.actionLabel) {
113
+ printHumanReadableLine(results[0].updated ? 'OK' : 'No changes');
114
+ }
115
+ else if (results[0].updated) {
116
+ printHumanReadableLine(args.actionLabel);
117
+ }
118
+ else {
119
+ printHumanReadableLine(`Already ${args.staleVerb ?? args.actionLabel.toLowerCase()}`);
120
+ }
121
+ }
122
+ else if (args.actionLabel) {
123
+ printHumanReadableLine(`${args.actionLabel} ${updatedCount}/${results.length} notes`);
124
+ }
125
+ else {
126
+ printHumanReadableLine(`Updated ${updatedCount}/${results.length} notes`);
127
+ }
128
+ if (stale && !offline) {
129
+ printHumanReadableLine('warning: sync timed out; local cache may be stale');
130
+ }
131
+ });
132
+ }
133
+ export async function runNoteSearch({ command, options, args }) {
134
+ const searchOptions = options;
135
+ await withNoteDatabase(command, async ({ db, offline, timeoutMs, stale }) => {
136
+ await syncBeforeNoteCommand(db, offline, timeoutMs);
137
+ const keyword = args.query?.trim() ?? '';
138
+ const tagsExpression = typeof searchOptions.tags === 'string' ? searchOptions.tags.trim() : '';
139
+ const { createdAtFrom, createdAtTo } = parseCreatedAtRange({
140
+ from: searchOptions.from,
141
+ to: searchOptions.to,
142
+ days: searchOptions.days,
143
+ });
144
+ const starred = typeof searchOptions.starred === 'string'
145
+ ? parseStarredOption(searchOptions.starred, '--starred')
146
+ : undefined;
147
+ const boxInput = typeof searchOptions.boxes === 'string' ? searchOptions.boxes : '';
148
+ const parsedBoxInput = boxInput
149
+ ? await parseSearchListInput(boxInput, '--boxes')
150
+ : { values: [], isExplicitEmptyArray: false };
151
+ const zettelBoxIds = await resolveZettelBoxIdsForSearch(db, parsedBoxInput.values);
152
+ const sqlExpression = typeof searchOptions.sql === 'string' ? searchOptions.sql.trim() : '';
153
+ const limit = parseSearchLimit(searchOptions.limit);
154
+ const offset = parseSearchOffset(searchOptions.offset);
155
+ const fields = parseNoteSearchFields(searchOptions.fields, '--fields');
156
+ const rows = await searchNotes(db, keyword, {
157
+ includeDeleted: Boolean(searchOptions.includeDeleted),
158
+ tagsExpression: tagsExpression || undefined,
159
+ createdAtFrom,
160
+ createdAtTo,
161
+ starred,
162
+ zettelBoxIds,
163
+ matchEmptyZettelBoxes: parsedBoxInput.isExplicitEmptyArray,
164
+ sqlExpression: sqlExpression || undefined,
165
+ });
166
+ printSuccess(buildNoteSearchResponse({
167
+ query: keyword,
168
+ notes: projectNoteSearchFields(rows.slice(offset, offset + limit), fields),
169
+ totalCount: rows.length,
170
+ stale: offline ? true : stale,
171
+ includeDeleted: Boolean(searchOptions.includeDeleted),
172
+ limit,
173
+ offset,
174
+ fields,
175
+ tagsExpression: tagsExpression || undefined,
176
+ createdAtFrom,
177
+ createdAtTo,
178
+ days: normalizeDaysForMeta(searchOptions.days),
179
+ starred,
180
+ boxNames: parsedBoxInput.values,
181
+ boxIds: zettelBoxIds,
182
+ matchEmptyBoxes: parsedBoxInput.isExplicitEmptyArray,
183
+ sqlExpression: sqlExpression || undefined,
184
+ }));
185
+ });
186
+ }
187
+ export async function runNoteGet({ command, options, args }) {
188
+ const getOptions = options;
189
+ const id = args.id;
190
+ if (!id) {
191
+ throw new DinoxError('Note id is required');
192
+ }
193
+ const structuredOutput = getRequestedStructuredOutputFormat() !== null;
194
+ await withNoteDatabase(command, async ({ db, offline, timeoutMs, stale }) => {
195
+ await syncBeforeNoteCommand(db, offline, timeoutMs);
196
+ if (getOptions.contextOnly) {
197
+ const context = await getNoteContext(db, id);
198
+ if (!context) {
199
+ throw new DinoxError(`Note not found: ${id}`, {
200
+ code: 'NOTE_NOT_FOUND',
201
+ suggestion: "Run 'dino note search <keyword>' to locate the note id.",
202
+ recoverable: true,
203
+ });
204
+ }
205
+ printSuccess({ stale: offline ? true : stale, note: toPublicNoteContext(context) });
206
+ return;
207
+ }
208
+ const note = await getNote(db, id);
209
+ if (!note) {
210
+ throw new DinoxError(`Note not found: ${id}`, {
211
+ code: 'NOTE_NOT_FOUND',
212
+ suggestion: "Run 'dino note search <keyword>' to locate the note id.",
213
+ recoverable: true,
214
+ });
215
+ }
216
+ if (structuredOutput) {
217
+ printSuccess({ stale, note: toPublicNote(note) });
218
+ return;
219
+ }
220
+ if (stale && !offline) {
221
+ printHumanReadableLine('warning: sync timed out; result may be stale');
222
+ }
223
+ printHumanReadableLine(`${note.id}`);
224
+ printHumanReadableLine(`title: ${note.title ?? ''}`);
225
+ if (note.updated_at) {
226
+ printHumanReadableLine(`updated_at: ${note.updated_at}`);
227
+ }
228
+ if (note.is_starred) {
229
+ printHumanReadableLine('is_starred: 1');
230
+ }
231
+ if (note.is_del) {
232
+ printHumanReadableLine('is_del: 1');
233
+ }
234
+ });
235
+ }
236
+ export async function runNotePreview({ command, options, args }) {
237
+ const previewOptions = options;
238
+ const id = args.id;
239
+ if (!id) {
240
+ throw new DinoxError('Note id is required');
241
+ }
242
+ await withNoteDatabase(command, async ({ db, offline, timeoutMs, stale }) => {
243
+ await syncBeforeNoteCommand(db, offline, timeoutMs);
244
+ const preview = await getNotePreview(db, id, parsePreviewLineCount(previewOptions.lines));
245
+ if (!preview) {
246
+ throw new DinoxError(`Note not found: ${id}`, {
247
+ code: 'NOTE_NOT_FOUND',
248
+ suggestion: "Run 'dino note search <keyword>' to locate the note id.",
249
+ recoverable: true,
250
+ });
251
+ }
252
+ printSuccess({ stale: offline ? true : stale, note: preview });
253
+ });
254
+ }
255
+ export async function runNoteDetail({ command, options, args }) {
256
+ const detailOptions = options;
257
+ await withNoteDatabase(command, async ({ db, offline, timeoutMs }) => {
258
+ await syncBeforeNoteCommand(db, offline, timeoutMs);
259
+ const ids = await parseTargetNoteIds(args.id, detailOptions.ids, 'detail');
260
+ const notes = await getNoteDetails(db, ids);
261
+ const foundIds = new Set(notes.map((note) => note.id));
262
+ const missing = ids.filter((noteId) => !foundIds.has(noteId));
263
+ if (missing.length > 0) {
264
+ throw new DinoxError(ids.length === 1 ? `Note not found: ${missing[0]}` : `Notes not found: ${missing.join(', ')}`);
265
+ }
266
+ const publicNotes = notes.map((note) => toPublicNoteDetail(note));
267
+ printSuccess(publicNotes.length === 1 ? publicNotes[0] : publicNotes);
268
+ });
269
+ }
270
+ export async function runNoteCreate({ command, options }) {
271
+ const createOptions = options;
272
+ const structuredOutput = getRequestedStructuredOutputFormat() !== null;
273
+ await withNoteDatabase(command, async ({ db, rawConfig, config, offline, timeoutMs, stale }) => {
274
+ await syncBeforeNoteCommand(db, offline, timeoutMs);
275
+ const identity = await resolveAuthIdentity(rawConfig, config);
276
+ const contentMd = await parseStringInput(String(createOptions.content));
277
+ const canonicalContentMd = await canonicalizeBidlinksInMarkdown(db, contentMd, identity.userId);
278
+ const { contentJson, contentText } = convertMarkdownToNoteContent(canonicalContentMd);
279
+ const tagsInput = typeof createOptions.tags === 'string'
280
+ ? await parseStringListInput(createOptions.tags, '--tags')
281
+ : [];
282
+ const boxNames = typeof createOptions.boxes === 'string'
283
+ ? await parseStringListInput(createOptions.boxes, '--boxes')
284
+ : [];
285
+ const noteType = parseNoteType(createOptions.type);
286
+ if (Boolean(createOptions.dryRun)) {
287
+ printDryRunPreview(await buildNoteCreateDryRunPreview({
288
+ db,
289
+ config,
290
+ offline,
291
+ userId: identity.userId,
292
+ title: String(createOptions.title),
293
+ noteType,
294
+ contentMd,
295
+ tagsInput,
296
+ zettelBoxNames: boxNames,
297
+ }));
298
+ return;
299
+ }
300
+ const id = generateId();
301
+ await createNote(db, {
302
+ id,
303
+ title: String(createOptions.title),
304
+ noteType,
305
+ contentMd: canonicalContentMd,
306
+ contentJson,
307
+ contentText,
308
+ tags: await validateTagsForCreate(db, tagsInput),
309
+ zettelBoxIds: await resolveZettelBoxIdsForCreate(db, boxNames),
310
+ userId: identity.userId,
311
+ });
312
+ if (!offline && !config.powersync.uploadBaseUrl) {
313
+ printHumanReadableLine('warning: upload disabled (powersync.uploadBaseUrl is unset); changes are local-only for now');
314
+ }
315
+ if (!offline) {
316
+ await settleMutation(db, timeoutMs);
317
+ }
318
+ const payload = { ok: true, id, stale: offline ? true : stale };
319
+ if (structuredOutput) {
320
+ printSuccess(payload);
321
+ return;
322
+ }
323
+ printHumanReadableLine(id);
324
+ if (stale && !offline) {
325
+ printHumanReadableLine('warning: sync timed out; local cache may be stale');
326
+ }
327
+ });
328
+ }
329
+ export async function runNoteDelete({ command, options, args }) {
330
+ const deleteOptions = options;
331
+ const id = args.id;
332
+ if (!id) {
333
+ throw new DinoxError('Note id is required');
334
+ }
335
+ const structuredOutput = getRequestedStructuredOutputFormat() !== null;
336
+ await withNoteDatabase(command, async ({ db, config, offline, timeoutMs, stale }) => {
337
+ await syncBeforeNoteCommand(db, offline, timeoutMs);
338
+ if (Boolean(deleteOptions.dryRun)) {
339
+ printDryRunPreview(await buildNoteDeleteDryRunPreview({ db, config, offline, id }));
340
+ return;
341
+ }
342
+ await softDeleteNote(db, id);
343
+ if (!offline && !config.powersync.uploadBaseUrl) {
344
+ printHumanReadableLine('warning: upload disabled (powersync.uploadBaseUrl is unset); changes are local-only for now');
345
+ }
346
+ if (!offline) {
347
+ await settleMutation(db, timeoutMs);
348
+ }
349
+ const payload = { ok: true, id, stale: offline ? true : stale };
350
+ if (structuredOutput) {
351
+ printSuccess(payload);
352
+ return;
353
+ }
354
+ printHumanReadableLine('OK');
355
+ if (stale && !offline) {
356
+ printHumanReadableLine('warning: sync timed out; local cache may be stale');
357
+ }
358
+ });
359
+ }
360
+ export async function runNoteUpdate({ command, options, args }) {
361
+ const updateOptions = options;
362
+ await executeNoteUpdateOperation({
363
+ boxOption: updateOptions.boxes,
364
+ command,
365
+ dryRun: Boolean(updateOptions.dryRun),
366
+ id: args.id,
367
+ idsOption: updateOptions.ids,
368
+ starred: typeof updateOptions.starred === 'string'
369
+ ? parseStarredOption(updateOptions.starred, '--starred')
370
+ : undefined,
371
+ tagsOption: updateOptions.tags,
372
+ commandName: 'dino note update',
373
+ actionName: 'update_note',
374
+ });
375
+ }
376
+ export async function runNoteStar({ command, options, args }) {
377
+ const starOptions = options;
378
+ await executeNoteUpdateOperation({
379
+ actionLabel: 'Starred',
380
+ command,
381
+ dryRun: Boolean(starOptions.dryRun),
382
+ id: args.id,
383
+ idsOption: starOptions.ids,
384
+ starred: true,
385
+ staleVerb: 'starred',
386
+ commandName: 'dino note star',
387
+ actionName: 'star_note',
388
+ });
389
+ }
390
+ export async function runNoteUnstar({ command, options, args }) {
391
+ const unstarOptions = options;
392
+ await executeNoteUpdateOperation({
393
+ actionLabel: 'Unstarred',
394
+ command,
395
+ dryRun: Boolean(unstarOptions.dryRun),
396
+ id: args.id,
397
+ idsOption: unstarOptions.ids,
398
+ starred: false,
399
+ staleVerb: 'unstarred',
400
+ commandName: 'dino note unstar',
401
+ actionName: 'unstar_note',
402
+ });
403
+ }
@@ -0,0 +1,4 @@
1
+ import type { Command } from 'commander';
2
+ import type { CommandGroupDescriptor } from '../descriptor/types.js';
3
+ export declare const noteCommandGroup: CommandGroupDescriptor;
4
+ export declare function registerNoteCommands(program: Command): void;
@@ -0,0 +1,250 @@
1
+ import { registerCommandGroup } from '../descriptor/register.js';
2
+ import { FORMAT_OPTIONS, OFFLINE_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
3
+ import { runNoteCreate, runNoteDelete, runNoteDetail, runNoteGet, runNotePreview, runNoteSearch, runNoteStar, runNoteUnstar, runNoteUpdate, } from './actions.js';
4
+ const NOTE_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
5
+ export const noteCommandGroup = {
6
+ name: 'note',
7
+ description: 'Manage notes',
8
+ commands: [
9
+ {
10
+ name: 'search',
11
+ description: 'Search notes by keyword, tags, date range, boxes, or SQL-like filter',
12
+ arguments: [{ syntax: '[query]', description: 'Search keyword; may be omitted for filter-only search' }],
13
+ options: [
14
+ { flags: '--tags <expr>', description: 'Tag expression with AND/OR/NOT, or [] for empty tags', type: 'string' },
15
+ { flags: '--from <date>', description: 'created_at start (YYYY-MM-DD or ISO datetime)', type: 'string' },
16
+ { flags: '--to <date>', description: 'created_at end (YYYY-MM-DD or ISO datetime)', type: 'string' },
17
+ { flags: '--days <n>', description: 'Recent N days by created_at; mutually exclusive with --from/--to', type: 'number' },
18
+ { flags: '--starred <true|false>', description: 'Filter by starred status', type: 'string', enum: ['true', 'false'] },
19
+ { flags: '--boxes <string|@file>', description: 'Box names (JSON array/comma list), or [] for empty boxes', type: 'string' },
20
+ { flags: '--sql <expr>', description: 'SQL-like expression over id/content_md/summary/tags/zettel_boxes/created_at/type/is_starred', type: 'string' },
21
+ { flags: '--limit <n>', description: 'Maximum returned notes', type: 'number' },
22
+ { flags: '--offset <n>', description: 'Result offset for pagination', type: 'number', defaultValue: 0 },
23
+ { flags: '--fields <list>', description: 'Comma-separated fields: id,title,summary,tags,created_at,boxes,is_starred', type: 'string[]' },
24
+ { flags: '--include-deleted', description: 'Include soft-deleted notes', type: 'boolean' },
25
+ ],
26
+ schema: {
27
+ path: 'note.search',
28
+ risk: 'read',
29
+ auth: 'required',
30
+ globalOptions: NOTE_GLOBAL_OPTIONS,
31
+ output: {
32
+ type: 'structured',
33
+ summary: 'AI-friendly note search payload',
34
+ entity: 'note_search_result',
35
+ fields: ['id', 'title', 'summary', 'tags', 'created_at', 'boxes', 'is_starred'],
36
+ metaFields: ['query', 'returned_count', 'total_count', 'stale', 'pagination', 'fields', 'applied_filters'],
37
+ },
38
+ mcp: {
39
+ toolName: 'dinox_search_notes',
40
+ description: 'Search notes by keyword, tags, date range, or SQL expression',
41
+ properties: [
42
+ { name: 'query', type: 'string', description: 'Search keyword' },
43
+ { name: 'tags', type: 'string', description: 'Tag expression (AND/OR/NOT)' },
44
+ { name: 'from', type: 'string', description: 'Start date (YYYY-MM-DD)' },
45
+ { name: 'to', type: 'string', description: 'End date (YYYY-MM-DD)' },
46
+ { name: 'days', type: 'number', description: 'Recent N days' },
47
+ { name: 'boxes', type: 'string[]', description: 'Box names' },
48
+ { name: 'limit', type: 'number', description: 'Max results (default: 50)' },
49
+ { name: 'offset', type: 'number', description: 'Result offset (default: 0)' },
50
+ { name: 'fields', type: 'string[]', description: 'Returned fields: id,title,summary,tags,created_at,boxes,is_starred' },
51
+ ],
52
+ },
53
+ examples: [
54
+ 'dino note search "AI" --days 7 --format json',
55
+ 'dino note search --tags "(work OR life) AND NOT archived" --format json',
56
+ ],
57
+ },
58
+ action: runNoteSearch,
59
+ },
60
+ {
61
+ name: 'get',
62
+ description: 'Get one note by id, optionally in lightweight context mode',
63
+ arguments: [{ syntax: '<id>', description: 'Note id' }],
64
+ options: [{ flags: '--context-only', description: 'Return lightweight note context (title/tags/summary/links)', type: 'boolean' }],
65
+ schema: {
66
+ path: 'note.get',
67
+ risk: 'read',
68
+ auth: 'required',
69
+ globalOptions: NOTE_GLOBAL_OPTIONS,
70
+ output: {
71
+ type: 'structured',
72
+ summary: 'Single note or note context payload',
73
+ entity: 'note_context',
74
+ fields: ['id', 'title', 'summary', 'tags', 'created_at', 'updated_at', 'is_del', 'boxes', 'links'],
75
+ },
76
+ examples: ['dino note get <id> --context-only --format json', 'dino note get <id> --format json'],
77
+ },
78
+ action: runNoteGet,
79
+ },
80
+ {
81
+ name: 'preview',
82
+ description: 'Preview the first N lines of note markdown',
83
+ arguments: [{ syntax: '<id>', description: 'Note id' }],
84
+ options: [{ flags: '--lines <n>', description: 'Number of lines to return', type: 'number', defaultValue: 10 }],
85
+ schema: {
86
+ path: 'note.preview',
87
+ risk: 'read',
88
+ auth: 'required',
89
+ globalOptions: NOTE_GLOBAL_OPTIONS,
90
+ output: {
91
+ type: 'structured',
92
+ summary: 'Markdown preview payload',
93
+ fields: ['note.id', 'note.title', 'note.preview', 'note.lines', 'note.truncated', 'stale'],
94
+ },
95
+ examples: ['dino note preview <id> --lines 30 --format json'],
96
+ },
97
+ action: runNotePreview,
98
+ },
99
+ {
100
+ name: 'detail',
101
+ description: 'Get full note details for one or more ids',
102
+ arguments: [{ syntax: '[id]', description: 'Optional note id when reading a single note' }],
103
+ options: [{ flags: '--ids <string|@file>', description: 'Batch note ids (JSON array or comma/newline-separated)', type: 'string' }],
104
+ schema: {
105
+ path: 'note.detail',
106
+ risk: 'read',
107
+ auth: 'required',
108
+ globalOptions: NOTE_GLOBAL_OPTIONS,
109
+ output: {
110
+ type: 'structured',
111
+ summary: 'One full note object or an ordered array of note objects',
112
+ fields: ['id', 'title', 'content_md', 'content_text', 'content_json', 'tags', 'boxes', 'created_at', 'updated_at', 'is_del'],
113
+ },
114
+ mcp: {
115
+ toolName: 'dinox_get_note',
116
+ description: 'Get note details by ID',
117
+ properties: [{ name: 'id', type: 'string', description: 'Note ID', required: true }],
118
+ },
119
+ examples: ['dino note detail <id> --format json', 'dino note detail --ids @/tmp/note-ids.txt --format json'],
120
+ },
121
+ action: runNoteDetail,
122
+ },
123
+ {
124
+ name: 'create',
125
+ description: 'Create a new note from markdown content',
126
+ options: [
127
+ { flags: '--title <string>', description: 'Note title', required: true, type: 'string' },
128
+ { flags: '--content <string|@file>', description: 'Markdown content', required: true, type: 'string' },
129
+ { flags: '--type <note|crawl>', description: 'Note type: note or crawl', type: 'string', defaultValue: 'crawl', enum: ['note', 'crawl'] },
130
+ { flags: '--tags <string|@file>', description: 'Tag list (JSON array or comma/newline-separated)', type: 'string' },
131
+ { flags: '--boxes <string|@file>', description: 'Box names (JSON array or comma/newline-separated)', type: 'string' },
132
+ { flags: '--dry-run', description: 'Preview the write without executing it', type: 'boolean' },
133
+ ],
134
+ schema: {
135
+ path: 'note.create',
136
+ risk: 'write',
137
+ auth: 'required',
138
+ globalOptions: NOTE_GLOBAL_OPTIONS,
139
+ output: {
140
+ type: 'structured',
141
+ summary: 'Creation acknowledgement with the new note id',
142
+ fields: ['id', 'stale'],
143
+ },
144
+ mcp: {
145
+ toolName: 'dinox_create_note',
146
+ description: 'Create a new note',
147
+ properties: [
148
+ { name: 'title', type: 'string', description: 'Note title', required: true },
149
+ { name: 'content', type: 'string', description: 'Markdown content', required: true },
150
+ { name: 'tags', type: 'string[]', description: 'Tags' },
151
+ { name: 'boxes', type: 'string[]', description: 'Zettel boxes' },
152
+ ],
153
+ },
154
+ examples: ['dino note create --title "Weekly note" --content @/tmp/note.md --type note --format json'],
155
+ },
156
+ action: runNoteCreate,
157
+ },
158
+ {
159
+ name: 'update',
160
+ description: 'Full-replace note tags, boxes, and/or starred state',
161
+ arguments: [{ syntax: '[id]', description: 'Optional note id for single-note update' }],
162
+ options: [
163
+ { flags: '--ids <string|@file>', description: 'Batch note ids (JSON array or comma/newline-separated)', type: 'string' },
164
+ { flags: '--tags <string|@file>', description: 'Full-replace tag list; use [] to clear', type: 'string' },
165
+ { flags: '--boxes <string|@file>', description: 'Full-replace box names; use [] to clear', type: 'string' },
166
+ { flags: '--starred <true|false>', description: 'Set starred status', type: 'string', enum: ['true', 'false'] },
167
+ { flags: '--dry-run', description: 'Preview the write without executing it', type: 'boolean' },
168
+ ],
169
+ schema: {
170
+ path: 'note.update',
171
+ risk: 'write',
172
+ auth: 'required',
173
+ globalOptions: NOTE_GLOBAL_OPTIONS,
174
+ output: {
175
+ type: 'structured',
176
+ summary: 'Mutation result with per-note update status',
177
+ fields: ['total', 'updatedCount', 'results', 'stale'],
178
+ },
179
+ examples: ['dino note update <id> --tags "work,ai" --format json', 'dino note update --ids @/tmp/note-ids.txt --boxes "Inbox" --format json'],
180
+ },
181
+ action: runNoteUpdate,
182
+ },
183
+ {
184
+ name: 'delete',
185
+ description: 'Soft-delete a note by setting is_del=1',
186
+ arguments: [{ syntax: '<id>', description: 'Note id' }],
187
+ options: [{ flags: '--dry-run', description: 'Preview the write without executing it', type: 'boolean' }],
188
+ schema: {
189
+ path: 'note.delete',
190
+ risk: 'high-risk-write',
191
+ auth: 'required',
192
+ globalOptions: NOTE_GLOBAL_OPTIONS,
193
+ output: {
194
+ type: 'structured',
195
+ summary: 'Deletion acknowledgement',
196
+ fields: ['id', 'stale'],
197
+ },
198
+ examples: ['dino note delete <id> --format json'],
199
+ },
200
+ action: runNoteDelete,
201
+ },
202
+ {
203
+ name: 'star',
204
+ description: 'Mark one or more notes as starred',
205
+ arguments: [{ syntax: '[id]', description: 'Optional note id for single-note update' }],
206
+ options: [
207
+ { flags: '--ids <string|@file>', description: 'Batch note ids (JSON array or comma/newline-separated)', type: 'string' },
208
+ { flags: '--dry-run', description: 'Preview the write without executing it', type: 'boolean' },
209
+ ],
210
+ schema: {
211
+ path: 'note.star',
212
+ risk: 'write',
213
+ auth: 'required',
214
+ globalOptions: NOTE_GLOBAL_OPTIONS,
215
+ output: {
216
+ type: 'structured',
217
+ summary: 'Mutation result with per-note starred status',
218
+ fields: ['total', 'updatedCount', 'results', 'stale'],
219
+ },
220
+ examples: ['dino note star <id> --format json --dry-run'],
221
+ },
222
+ action: runNoteStar,
223
+ },
224
+ {
225
+ name: 'unstar',
226
+ description: 'Mark one or more notes as not starred',
227
+ arguments: [{ syntax: '[id]', description: 'Optional note id for single-note update' }],
228
+ options: [
229
+ { flags: '--ids <string|@file>', description: 'Batch note ids (JSON array or comma/newline-separated)', type: 'string' },
230
+ { flags: '--dry-run', description: 'Preview the write without executing it', type: 'boolean' },
231
+ ],
232
+ schema: {
233
+ path: 'note.unstar',
234
+ risk: 'write',
235
+ auth: 'required',
236
+ globalOptions: NOTE_GLOBAL_OPTIONS,
237
+ output: {
238
+ type: 'structured',
239
+ summary: 'Mutation result with per-note starred status',
240
+ fields: ['total', 'updatedCount', 'results', 'stale'],
241
+ },
242
+ examples: ['dino note unstar <id> --format json --dry-run'],
243
+ },
244
+ action: runNoteUnstar,
245
+ },
246
+ ],
247
+ };
248
+ export function registerNoteCommands(program) {
249
+ registerCommandGroup(program, noteCommandGroup);
250
+ }