@dinoxx/dinox-cli 1.0.22 → 1.1.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 (144) hide show
  1. package/README.md +29 -9
  2. package/dist/auth/userInfo.d.ts +2 -0
  3. package/dist/auth/userInfo.js +55 -14
  4. package/dist/cli.js +36 -2
  5. package/dist/commands/auth/index.d.ts +35 -0
  6. package/dist/commands/auth/index.js +382 -83
  7. package/dist/commands/boxes/index.js +2 -6
  8. package/dist/commands/boxes/model.d.ts +3 -0
  9. package/dist/commands/boxes/model.js +16 -1
  10. package/dist/commands/boxes/operations.js +18 -8
  11. package/dist/commands/boxes/repo.js +13 -11
  12. package/dist/commands/boxes/store.js +1 -1
  13. package/dist/commands/config/index.js +7 -5
  14. package/dist/commands/descriptor/register.js +9 -0
  15. package/dist/commands/doctor.d.ts +123 -0
  16. package/dist/commands/doctor.js +539 -0
  17. package/dist/commands/graph/index.d.ts +1 -1
  18. package/dist/commands/graph/index.js +35 -21
  19. package/dist/commands/graph/repo.js +92 -17
  20. package/dist/commands/meta/index.js +4 -4
  21. package/dist/commands/meta/repo.js +4 -3
  22. package/dist/commands/notes/actions.d.ts +1 -1
  23. package/dist/commands/notes/actions.js +121 -78
  24. package/dist/commands/notes/bulk.d.ts +1 -0
  25. package/dist/commands/notes/bulk.js +23 -17
  26. package/dist/commands/notes/contentEngine.js +69 -11
  27. package/dist/commands/notes/contentPatch.d.ts +4 -1
  28. package/dist/commands/notes/contentPatch.js +105 -41
  29. package/dist/commands/notes/contentPatchCommand.js +28 -11
  30. package/dist/commands/notes/descriptors.js +50 -20
  31. package/dist/commands/notes/dryRun.d.ts +1 -0
  32. package/dist/commands/notes/dryRun.js +4 -7
  33. package/dist/commands/notes/export.d.ts +6 -0
  34. package/dist/commands/notes/export.js +26 -6
  35. package/dist/commands/notes/metadataMutation.d.ts +1 -0
  36. package/dist/commands/notes/metadataMutation.js +14 -3
  37. package/dist/commands/notes/repo.d.ts +46 -4
  38. package/dist/commands/notes/repo.js +113 -28
  39. package/dist/commands/notes/runtime.d.ts +2 -1
  40. package/dist/commands/notes/runtime.js +17 -1
  41. package/dist/commands/notes/searchSql.js +13 -6
  42. package/dist/commands/notes/writeReceipt.d.ts +20 -0
  43. package/dist/commands/notes/writeReceipt.js +82 -0
  44. package/dist/commands/prompt/index.js +3 -3
  45. package/dist/commands/prompt/repo.d.ts +4 -1
  46. package/dist/commands/prompt/repo.js +17 -7
  47. package/dist/commands/runtime.d.ts +14 -2
  48. package/dist/commands/runtime.js +74 -15
  49. package/dist/commands/schema/renderSkills.js +5 -2
  50. package/dist/commands/schema/spec.js +4 -0
  51. package/dist/commands/storage/index.js +8 -2
  52. package/dist/commands/storage/repo.d.ts +4 -0
  53. package/dist/commands/storage/repo.js +356 -92
  54. package/dist/commands/sync.js +16 -12
  55. package/dist/commands/tags/content.js +9 -0
  56. package/dist/commands/tags/index.js +2 -6
  57. package/dist/commands/tags/operations.js +15 -10
  58. package/dist/commands/tags/repo.d.ts +2 -0
  59. package/dist/commands/tags/repo.js +18 -5
  60. package/dist/commands/tags/store.js +1 -1
  61. package/dist/commands/todo/contentModel.d.ts +4 -3
  62. package/dist/commands/todo/contentModel.js +78 -28
  63. package/dist/commands/todo/dryRun.d.ts +1 -0
  64. package/dist/commands/todo/dryRun.js +17 -13
  65. package/dist/commands/todo/index.d.ts +6 -0
  66. package/dist/commands/todo/index.js +89 -123
  67. package/dist/commands/todo/repo.d.ts +36 -1
  68. package/dist/commands/todo/repo.js +146 -37
  69. package/dist/commands/todo/taskIdentity.d.ts +2 -0
  70. package/dist/commands/todo/taskIdentity.js +64 -0
  71. package/dist/commands/todo/taskModel.js +27 -17
  72. package/dist/commands/update.d.ts +13 -1
  73. package/dist/commands/update.js +135 -23
  74. package/dist/commands/writeDryRun.d.ts +3 -2
  75. package/dist/commands/writeDryRun.js +4 -3
  76. package/dist/commands/writeDurability.d.ts +5 -0
  77. package/dist/commands/writeDurability.js +22 -0
  78. package/dist/config/paths.js +6 -1
  79. package/dist/config/resolve.d.ts +2 -0
  80. package/dist/config/resolve.js +23 -9
  81. package/dist/config/store.d.ts +3 -0
  82. package/dist/config/store.js +61 -14
  83. package/dist/config/types.d.ts +4 -0
  84. package/dist/daemon/client.d.ts +26 -1
  85. package/dist/daemon/client.js +236 -12
  86. package/dist/daemon/ensure.js +74 -12
  87. package/dist/daemon/index.d.ts +42 -0
  88. package/dist/daemon/index.js +202 -52
  89. package/dist/daemon/lifecycle.d.ts +42 -0
  90. package/dist/daemon/lifecycle.js +115 -0
  91. package/dist/daemon/pid.d.ts +19 -6
  92. package/dist/daemon/pid.js +147 -13
  93. package/dist/daemon/route.d.ts +14 -0
  94. package/dist/daemon/route.js +40 -0
  95. package/dist/daemon/server.d.ts +11 -0
  96. package/dist/daemon/server.js +201 -50
  97. package/dist/daemon/types.d.ts +29 -1
  98. package/dist/daemon/types.js +1 -0
  99. package/dist/dinox.js +25 -1
  100. package/dist/powersync/connector.js +31 -16
  101. package/dist/powersync/exclusiveRuntime.d.ts +19 -0
  102. package/dist/powersync/exclusiveRuntime.js +42 -0
  103. package/dist/powersync/runtime.d.ts +24 -4
  104. package/dist/powersync/runtime.js +234 -36
  105. package/dist/powersync/syncGate.d.ts +3 -1
  106. package/dist/powersync/syncGate.js +7 -1
  107. package/dist/powersync/tokenIndex.d.ts +11 -0
  108. package/dist/powersync/tokenIndex.js +159 -22
  109. package/dist/powersync/uploader.js +107 -52
  110. package/dist/utils/abort.js +0 -4
  111. package/dist/utils/asyncRelease.d.ts +2 -0
  112. package/dist/utils/asyncRelease.js +25 -0
  113. package/dist/utils/bidlinkCanonicalization.js +3 -0
  114. package/dist/utils/dryRun.d.ts +1 -1
  115. package/dist/utils/errors.d.ts +15 -1
  116. package/dist/utils/errors.js +223 -5
  117. package/dist/utils/output.d.ts +1 -0
  118. package/dist/utils/output.js +51 -15
  119. package/dist/utils/redact.js +1 -1
  120. package/dist/utils/sqliteLease.d.ts +8 -0
  121. package/dist/utils/sqliteLease.js +59 -0
  122. package/dist/utils/syncTimeout.d.ts +7 -1
  123. package/dist/utils/syncTimeout.js +8 -7
  124. package/dist/utils/terminalSanitization.js +6 -2
  125. package/dist/utils/tiptapMarkdown.js +49 -3
  126. package/dist/utils/tiptapMarkdownSyntax.d.ts +9 -0
  127. package/dist/utils/tiptapMarkdownSyntax.js +249 -0
  128. package/dist/utils/tokenize.js +24 -2
  129. package/dist/utils/updateNotice.js +60 -12
  130. package/package.json +12 -10
  131. package/dist/commands/mcp/auth.d.ts +0 -4
  132. package/dist/commands/mcp/auth.js +0 -75
  133. package/dist/commands/mcp/index.d.ts +0 -2
  134. package/dist/commands/mcp/index.js +0 -86
  135. package/dist/commands/mcp/noteContentTools.d.ts +0 -222
  136. package/dist/commands/mcp/noteContentTools.js +0 -390
  137. package/dist/commands/mcp/server.d.ts +0 -34
  138. package/dist/commands/mcp/server.js +0 -424
  139. package/dist/commands/mcp/tools.d.ts +0 -2
  140. package/dist/commands/mcp/tools.js +0 -32
  141. package/dist/commands/mcp/types.d.ts +0 -39
  142. package/dist/commands/mcp/types.js +0 -1
  143. package/dist/utils/noteContentEngine.d.ts +0 -333
  144. package/dist/utils/noteContentEngine.js +0 -740
@@ -1,20 +1,31 @@
1
1
  import { resolveAuthIdentity } from '../../auth/userInfo.js';
2
2
  import { registerCommandGroup } from '../descriptor/register.js';
3
3
  import { FORMAT_OPTIONS, OFFLINE_OPTIONS, REQUIRE_SYNC_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
4
+ import { WRITE_DURABILITY_OPTION } from '../writeDurability.js';
4
5
  import { readStringOrFile } from '../../utils/argValue.js';
5
6
  import { printDryRunPreview } from '../../utils/dryRun.js';
6
7
  import { DinoxError } from '../../utils/errors.js';
7
8
  import { printSuccess } from '../../utils/output.js';
8
- import { commandStale, printUploadDisabledWarning, syncBeforeCommand, waitForMutationUpload, withCommandRuntime, } from '../runtime.js';
9
+ import { commandStale, resolveWriteDurabilityForRuntime, syncBeforeCommand, waitForMutationUpload, withCommandRuntime, } from '../runtime.js';
10
+ import { readNoteWriteReceipt } from '../notes/writeReceipt.js';
11
+ import { parseCreatedAtRange } from '../notes/searchTime.js';
9
12
  import { buildTodoAppendDryRunPreview, buildTodoCreateDryRunPreview, buildTodoUpdateDryRunPreview, } from './dryRun.js';
10
- import { appendTodoTasks, createTodoNote, normalizeTaskTexts, searchTodoTasks, updateTodoTaskStatus, } from './repo.js';
11
- const DATE_ONLY_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
12
- const DAY_MS = 24 * 60 * 60 * 1000;
13
+ import { appendTodoTasks, createTodoNote, executeTodoSearch, normalizeTaskTexts, toTodoSearchWire, updateTodoTaskStatus, } from './repo.js';
14
+ import { routeRead } from '../../daemon/route.js';
13
15
  const DEFAULT_TASK_LIMIT = 50;
14
16
  const DEFAULT_NOTE_SCAN_LIMIT = 200;
15
17
  const MAX_TASK_LIMIT = 500;
16
18
  const MAX_NOTE_SCAN_LIMIT = 5000;
17
19
  const TODO_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...REQUIRE_SYNC_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
20
+ const TODO_WRITE_RECEIPT_FIELDS = [
21
+ 'durability',
22
+ 'upload_queue_remaining',
23
+ 'version',
24
+ 'updated_at',
25
+ 'content_hash',
26
+ 'changed',
27
+ 'stale',
28
+ ];
18
29
  function parseTaskStatus(value) {
19
30
  const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
20
31
  if (normalized === 'all' || normalized === 'completed' || normalized === 'uncompleted') {
@@ -56,85 +67,14 @@ function parsePositiveIntegerOption(value, optionName, fallback, max) {
56
67
  }
57
68
  return Math.trunc(parsed);
58
69
  }
59
- function parseLocalDateOnly(value) {
60
- const match = value.match(DATE_ONLY_RE);
61
- if (!match) {
62
- return null;
63
- }
64
- const year = Number(match[1]);
65
- const month = Number(match[2]);
66
- const day = Number(match[3]);
67
- const parsed = new Date(year, month - 1, day, 0, 0, 0, 0);
68
- if (parsed.getFullYear() !== year ||
69
- parsed.getMonth() !== month - 1 ||
70
- parsed.getDate() !== day) {
71
- return null;
72
- }
73
- return parsed;
74
- }
75
- function parseDateInput(value, optionName) {
76
- if (typeof value !== 'string') {
77
- return undefined;
78
- }
79
- const raw = value.trim();
80
- if (!raw) {
81
- throw new DinoxError(`${optionName} requires a non-empty date value`);
82
- }
83
- const isDateOnly = DATE_ONLY_RE.test(raw);
84
- const dateOnly = parseLocalDateOnly(raw);
85
- if (dateOnly) {
86
- return dateOnly;
87
- }
88
- if (isDateOnly) {
89
- throw new DinoxError(`${optionName} must be a valid date (YYYY-MM-DD or ISO datetime)`);
90
- }
91
- const parsed = new Date(raw);
92
- if (Number.isNaN(parsed.getTime())) {
93
- throw new DinoxError(`${optionName} must be a valid date (YYYY-MM-DD or ISO datetime)`);
94
- }
95
- return parsed;
96
- }
97
- function parseDaysInput(value) {
98
- if (value === undefined || value === null || value === '') {
99
- return undefined;
100
- }
101
- if (typeof value !== 'string') {
102
- throw new DinoxError('--days must be a positive integer');
103
- }
104
- const trimmed = value.trim();
105
- if (!/^\d+$/.test(trimmed)) {
106
- throw new DinoxError('--days must be a positive integer');
107
- }
108
- const parsed = Number(trimmed);
109
- if (!Number.isFinite(parsed) || parsed <= 0) {
110
- throw new DinoxError('--days must be a positive integer');
111
- }
112
- return Math.trunc(parsed);
113
- }
114
- function parseTaskDateRange(input, now = new Date()) {
115
- const from = parseDateInput(input.from, '--from');
116
- const to = parseDateInput(input.to, '--to');
117
- const days = parseDaysInput(input.days);
118
- if (days !== undefined && (from || to)) {
119
- throw new DinoxError('--days cannot be used together with --from/--to');
120
- }
121
- if (days !== undefined) {
122
- const end = new Date(now);
123
- const start = new Date(now.getTime() - (days - 1) * DAY_MS);
124
- return {
125
- startDate: start,
126
- endDate: end,
127
- };
128
- }
129
- if (!from && !to) {
70
+ export function parseTaskDateRange(input, now = new Date()) {
71
+ const { createdAtFrom, createdAtTo } = parseCreatedAtRange(input, now);
72
+ if (!createdAtFrom && !createdAtTo) {
130
73
  return undefined;
131
74
  }
132
- if (from && to && from.getTime() > to.getTime()) {
133
- throw new DinoxError('--from must be earlier than or equal to --to');
134
- }
135
75
  return {
136
- startDate: from,
137
- endDate: to,
76
+ startDate: createdAtFrom ? new Date(createdAtFrom) : undefined,
77
+ endDate: createdAtTo ? new Date(createdAtTo) : undefined,
138
78
  };
139
79
  }
140
80
  function parseListInput(raw, optionName) {
@@ -190,41 +130,45 @@ async function parseTaskInput(args) {
190
130
  return normalizeTaskTexts(merged);
191
131
  }
192
132
  async function withTodoDatabase(globals, run) {
193
- await withCommandRuntime(globals, run);
133
+ return withCommandRuntime(globals, run);
194
134
  }
195
135
  async function runTodoSearch({ args, globals, options }) {
196
136
  const searchOptions = options;
197
- await withTodoDatabase(globals, async (runtime) => {
198
- const { db } = runtime;
199
- await syncBeforeCommand(runtime);
200
- const keyword = args.query?.trim() ?? '';
201
- const taskStatus = parseTaskStatus(searchOptions.status);
202
- const taskTagFilter = typeof searchOptions.tags === 'string'
203
- ? await parseTagFilterInput(searchOptions.tags)
204
- : [];
205
- const taskDateRange = parseTaskDateRange({
206
- from: searchOptions.from,
207
- to: searchOptions.to,
208
- days: searchOptions.days,
209
- });
210
- const limit = parsePositiveIntegerOption(searchOptions.limit, '--limit', DEFAULT_TASK_LIMIT, MAX_TASK_LIMIT);
211
- const scanLimit = parsePositiveIntegerOption(searchOptions.scanLimit, '--scan-limit', DEFAULT_NOTE_SCAN_LIMIT, MAX_NOTE_SCAN_LIMIT);
212
- const result = await searchTodoTasks(db, keyword, {
213
- includeDeleted: Boolean(searchOptions.includeDeleted),
214
- taskStatus,
215
- taskTagFilter,
216
- taskDateRange,
217
- limit,
218
- scanLimit,
219
- });
220
- printSuccess({ stale: commandStale(runtime), ...result });
137
+ // Parse here; the db-touching scan runs through executeTodoSearch — locally or on the daemon.
138
+ const keyword = args.query?.trim() ?? '';
139
+ const taskStatus = parseTaskStatus(searchOptions.status);
140
+ const taskTagFilter = typeof searchOptions.tags === 'string'
141
+ ? await parseTagFilterInput(searchOptions.tags)
142
+ : [];
143
+ const taskDateRange = parseTaskDateRange({
144
+ from: searchOptions.from,
145
+ to: searchOptions.to,
146
+ days: searchOptions.days,
147
+ });
148
+ const limit = parsePositiveIntegerOption(searchOptions.limit, '--limit', DEFAULT_TASK_LIMIT, MAX_TASK_LIMIT);
149
+ const scanLimit = parsePositiveIntegerOption(searchOptions.scanLimit, '--scan-limit', DEFAULT_NOTE_SCAN_LIMIT, MAX_NOTE_SCAN_LIMIT);
150
+ const wire = toTodoSearchWire(keyword, {
151
+ includeDeleted: Boolean(searchOptions.includeDeleted),
152
+ taskStatus,
153
+ taskTagFilter,
154
+ taskDateRange,
155
+ limit,
156
+ scanLimit,
221
157
  });
158
+ const result = await routeRead(globals, 'todo.search', wire, () => withTodoDatabase(globals, async (runtime) => {
159
+ await syncBeforeCommand(runtime);
160
+ const local = await executeTodoSearch(runtime.db, wire);
161
+ return { ...local, stale: commandStale(runtime) };
162
+ }));
163
+ const { stale, ...response } = result;
164
+ printSuccess({ stale: stale ?? false, ...response });
222
165
  }
223
166
  async function runTodoAppend({ args, globals, options }) {
224
167
  const appendOptions = options;
225
168
  await withTodoDatabase(globals, async (runtime) => {
226
169
  const { db, config, offline } = runtime;
227
170
  await syncBeforeCommand(runtime);
171
+ const durability = resolveWriteDurabilityForRuntime(runtime, appendOptions.durability);
228
172
  const taskTexts = await parseTaskInput({
229
173
  positionalTask: args.task,
230
174
  tasksOption: typeof appendOptions.tasks === 'string' ? appendOptions.tasks : undefined,
@@ -245,9 +189,13 @@ async function runTodoAppend({ args, globals, options }) {
245
189
  noteId: typeof appendOptions.noteId === 'string' ? appendOptions.noteId : undefined,
246
190
  taskTexts,
247
191
  });
248
- printUploadDisabledWarning(runtime);
249
- await waitForMutationUpload(runtime);
250
- printSuccess({ ok: true, stale: commandStale(runtime), ...result });
192
+ const upload = await waitForMutationUpload(runtime, { durability });
193
+ const receipt = await readNoteWriteReceipt(db, result.note_id, {
194
+ changed: ['content_json', 'image_detail', 'content_md', 'content_text'],
195
+ upload,
196
+ stale: commandStale(runtime),
197
+ });
198
+ printSuccess({ ok: true, ...receipt, ...result });
251
199
  });
252
200
  }
253
201
  async function runTodoCreate({ args, globals, options }) {
@@ -255,6 +203,7 @@ async function runTodoCreate({ args, globals, options }) {
255
203
  await withTodoDatabase(globals, async (runtime) => {
256
204
  const { db, config, offline } = runtime;
257
205
  await syncBeforeCommand(runtime);
206
+ const durability = resolveWriteDurabilityForRuntime(runtime, createOptions.durability);
258
207
  const taskTexts = await parseTaskInput({
259
208
  positionalTask: args.task,
260
209
  tasksOption: typeof createOptions.tasks === 'string' ? createOptions.tasks : undefined,
@@ -276,9 +225,13 @@ async function runTodoCreate({ args, globals, options }) {
276
225
  title: typeof createOptions.title === 'string' ? createOptions.title : undefined,
277
226
  userId: identity.userId,
278
227
  });
279
- printUploadDisabledWarning(runtime);
280
- await waitForMutationUpload(runtime);
281
- printSuccess({ ok: true, stale: commandStale(runtime), ...result });
228
+ const upload = await waitForMutationUpload(runtime, { durability });
229
+ const receipt = await readNoteWriteReceipt(db, result.note_id, {
230
+ changed: ['title', 'type', 'content_json', 'image_detail', 'content_md', 'content_text'],
231
+ upload,
232
+ stale: commandStale(runtime),
233
+ });
234
+ printSuccess({ ok: true, ...receipt, ...result });
282
235
  });
283
236
  }
284
237
  async function runTodoUpdate({ args, globals, options }) {
@@ -286,6 +239,7 @@ async function runTodoUpdate({ args, globals, options }) {
286
239
  await withTodoDatabase(globals, async (runtime) => {
287
240
  const { db, config, offline } = runtime;
288
241
  await syncBeforeCommand(runtime);
242
+ const durability = resolveWriteDurabilityForRuntime(runtime, updateOptions.durability);
289
243
  const taskId = args.taskId;
290
244
  if (!taskId) {
291
245
  throw new DinoxError('taskId is required');
@@ -297,6 +251,7 @@ async function runTodoUpdate({ args, globals, options }) {
297
251
  config,
298
252
  offline,
299
253
  taskId,
254
+ noteId: typeof updateOptions.noteId === 'string' ? updateOptions.noteId : undefined,
300
255
  checked,
301
256
  });
302
257
  printDryRunPreview(preview);
@@ -304,11 +259,18 @@ async function runTodoUpdate({ args, globals, options }) {
304
259
  }
305
260
  const result = await updateTodoTaskStatus(db, {
306
261
  taskId,
262
+ noteId: typeof updateOptions.noteId === 'string' ? updateOptions.noteId : undefined,
307
263
  checked,
308
264
  });
309
- printUploadDisabledWarning(runtime);
310
- await waitForMutationUpload(runtime);
311
- printSuccess({ ok: true, stale: commandStale(runtime), ...result });
265
+ const upload = await waitForMutationUpload(runtime, { durability });
266
+ const receipt = await readNoteWriteReceipt(db, result.note_id, {
267
+ changed: result.updated
268
+ ? ['content_json', 'image_detail', 'content_md', 'content_text']
269
+ : [],
270
+ upload,
271
+ stale: commandStale(runtime),
272
+ });
273
+ printSuccess({ ok: true, ...receipt, ...result });
312
274
  });
313
275
  }
314
276
  const todoSearchCommand = {
@@ -339,7 +301,7 @@ const todoSearchCommand = {
339
301
  type: 'structured',
340
302
  summary: 'Todo search payload with metadata and task list',
341
303
  fields: ['tasks.task_id', 'tasks.task_key', 'tasks.note_id', 'tasks.note_title', 'tasks.status', 'tasks.depth', 'tasks.parent_id', 'tasks.tags'],
342
- metaFields: ['stale', 'meta.query', 'meta.returned_count', 'meta.limit', 'meta.task_status', 'meta.task_tag_filter', 'meta.task_date_range', 'meta.scanned_note_count'],
304
+ metaFields: ['stale', 'meta.query', 'meta.returned_count', 'meta.total_count', 'meta.total_count_is_lower_bound', 'meta.matched_note_count', 'meta.limit', 'meta.scan_limit', 'meta.scanned_note_count', 'meta.scan_truncated', 'meta.truncated', 'meta.applied_filters.status', 'meta.applied_filters.tags', 'meta.applied_filters.include_deleted', 'meta.applied_filters.date_range'],
343
305
  },
344
306
  examples: ['dino todo search "报销" --status uncompleted --format json', 'dino todo search --days 7 --format json'],
345
307
  },
@@ -363,6 +325,7 @@ const todoAppendCommand = {
363
325
  },
364
326
  { flags: '--tasks <string|@file>', description: 'Task list (JSON array or comma/newline-separated)' },
365
327
  { flags: '--note-id <id>', description: 'Target note id; omitted means latest eligible note' },
328
+ WRITE_DURABILITY_OPTION,
366
329
  { flags: '--dry-run', description: 'Preview the write without executing it' },
367
330
  ],
368
331
  schema: {
@@ -372,10 +335,10 @@ const todoAppendCommand = {
372
335
  globalOptions: TODO_GLOBAL_OPTIONS,
373
336
  output: {
374
337
  type: 'structured',
375
- summary: 'Append result with target note and appended task ids',
376
- fields: ['noteId', 'appendedCount', 'taskIds', 'stale'],
338
+ summary: 'Append result with target note, appended task count, and write receipt',
339
+ fields: ['note_id', 'appended_count', 'source', ...TODO_WRITE_RECEIPT_FIELDS],
377
340
  },
378
- examples: ['dino todo append --note-id <id> --task "补交报销单" --format json'],
341
+ examples: ['dino todo append --note-id <id> --task "补交报销单" --durability uploaded --format json'],
379
342
  },
380
343
  action: runTodoAppend,
381
344
  };
@@ -397,6 +360,7 @@ const todoCreateCommand = {
397
360
  },
398
361
  { flags: '--tasks <string|@file>', description: 'Task list (JSON array or comma/newline-separated)' },
399
362
  { flags: '--title <string>', description: 'Optional note title' },
363
+ WRITE_DURABILITY_OPTION,
400
364
  { flags: '--dry-run', description: 'Preview the write without executing it' },
401
365
  ],
402
366
  schema: {
@@ -406,8 +370,8 @@ const todoCreateCommand = {
406
370
  globalOptions: TODO_GLOBAL_OPTIONS,
407
371
  output: {
408
372
  type: 'structured',
409
- summary: 'Creation result with new note id and task ids',
410
- fields: ['noteId', 'taskIds', 'title', 'stale'],
373
+ summary: 'Creation result with new note id, created task count, and write receipt',
374
+ fields: ['note_id', 'created_count', 'title', ...TODO_WRITE_RECEIPT_FIELDS],
411
375
  },
412
376
  examples: ['dino todo create --task "补交报销单" --task "整理发票" --format json'],
413
377
  },
@@ -428,6 +392,8 @@ const todoUpdateCommand = {
428
392
  description: 'Target status: completed|uncompleted|done|undone|true|false|1|0',
429
393
  required: true,
430
394
  },
395
+ { flags: '--note-id <id>', description: 'Restrict task lookup to one exact note id' },
396
+ WRITE_DURABILITY_OPTION,
431
397
  { flags: '--dry-run', description: 'Preview the write without executing it' },
432
398
  ],
433
399
  schema: {
@@ -437,10 +403,10 @@ const todoUpdateCommand = {
437
403
  globalOptions: TODO_GLOBAL_OPTIONS,
438
404
  output: {
439
405
  type: 'structured',
440
- summary: 'Mutation result with updated status and target note id',
441
- fields: ['taskId', 'checked', 'updated', 'noteId', 'stale'],
406
+ summary: 'Mutation result with updated status, target note id, and write receipt',
407
+ fields: ['task_id', 'checked', 'updated', 'note_id', ...TODO_WRITE_RECEIPT_FIELDS],
442
408
  },
443
- examples: ['dino todo update <taskId> --status completed --format json'],
409
+ examples: ['dino todo update <taskId> --note-id <noteId> --status completed --format json'],
444
410
  },
445
411
  action: runTodoUpdate,
446
412
  };
@@ -1,4 +1,5 @@
1
1
  import type { AbstractPowerSyncDatabase } from '@powersync/common';
2
+ import { parseTiptapDocFromDb } from './contentModel.js';
2
3
  import { type TaskDateRangeFilter, type TaskStatusFilter, type TaskTimePoint } from './taskModel.js';
3
4
  export type SearchTodoOptions = {
4
5
  includeDeleted: boolean;
@@ -31,6 +32,7 @@ export type CreateTodoNoteResult = {
31
32
  export type UpdateTodoTaskStatusOptions = {
32
33
  taskId: string;
33
34
  checked: boolean;
35
+ noteId?: string;
34
36
  };
35
37
  export type UpdateTodoTaskStatusResult = {
36
38
  note_id: string;
@@ -64,6 +66,9 @@ export type SearchedTodoResponse = {
64
66
  matched_note_count: number;
65
67
  limit: number;
66
68
  scan_limit: number;
69
+ scanned_note_count: number;
70
+ scan_truncated: boolean;
71
+ total_count_is_lower_bound: boolean;
67
72
  truncated: boolean;
68
73
  applied_filters: {
69
74
  status: TaskStatusFilter;
@@ -86,11 +91,41 @@ type TodoMutableNoteRow = {
86
91
  content_md: string | null;
87
92
  is_del: number | null;
88
93
  created_at?: string | null;
94
+ version: number | string | null;
95
+ };
96
+ export declare function validateTodoAppendTarget(target: TodoMutableNoteRow): {
97
+ currentDoc: ReturnType<typeof parseTiptapDocFromDb>;
98
+ noteId: string;
99
+ expectedVersion: number;
89
100
  };
101
+ /**
102
+ * JSON-safe todo-search request. {@link SearchTodoOptions.taskDateRange} carries `Date`
103
+ * objects, which cannot cross the daemon wire intact, so the range is transported as ISO
104
+ * strings here and reconstructed inside {@link executeTodoSearch}.
105
+ */
106
+ export type TodoSearchWire = {
107
+ query: string;
108
+ includeDeleted: boolean;
109
+ taskStatus: TaskStatusFilter;
110
+ taskTagFilter: string[];
111
+ limit: number;
112
+ scanLimit: number;
113
+ startDate?: string;
114
+ endDate?: string;
115
+ };
116
+ /** Serialize parsed todo-search options into a JSON-safe wire payload. */
117
+ export declare function toTodoSearchWire(query: string, options: SearchTodoOptions): TodoSearchWire;
118
+ /**
119
+ * Run a todo search from a wire payload. Shared by the local command path and the daemon
120
+ * RPC handler so both produce identical results from one implementation.
121
+ */
122
+ export declare function executeTodoSearch(db: AbstractPowerSyncDatabase, wire: TodoSearchWire): Promise<SearchedTodoResponse>;
90
123
  export declare function searchTodoTasks(db: AbstractPowerSyncDatabase, query: string, options: SearchTodoOptions): Promise<SearchedTodoResponse>;
91
124
  export declare function appendTodoTasks(db: AbstractPowerSyncDatabase, options: AppendTodoTasksOptions): Promise<AppendTodoTasksResult>;
92
125
  export declare function createTodoNote(db: AbstractPowerSyncDatabase, options: CreateTodoNoteOptions): Promise<CreateTodoNoteResult>;
93
126
  export declare function updateTodoTaskStatus(db: AbstractPowerSyncDatabase, options: UpdateTodoTaskStatusOptions): Promise<UpdateTodoTaskStatusResult>;
94
127
  export declare function getTodoAppendTargetById(db: AbstractPowerSyncDatabase, noteId: string): Promise<TodoMutableNoteRow | null>;
95
128
  export declare function getLatestTodoAppendTarget(db: AbstractPowerSyncDatabase): Promise<TodoMutableNoteRow | null>;
96
- export declare function loadTodoTaskCandidatesByTaskId(db: AbstractPowerSyncDatabase, taskId: string): Promise<TodoMutableNoteRow[]>;
129
+ export declare function loadTodoTaskCandidatesByTaskId(db: AbstractPowerSyncDatabase, taskId: string, options?: {
130
+ noteId?: string;
131
+ }): Promise<TodoMutableNoteRow[]>;