@dinoxx/dinox-cli 1.0.12 → 1.0.14

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.
@@ -0,0 +1,286 @@
1
+ import { createNote } from '../notes/repo.js';
2
+ import { DinoxError } from '../../utils/errors.js';
3
+ import { generateId } from '../../utils/id.js';
4
+ import { nowIsoUtc } from '../../utils/time.js';
5
+ import { applyTaskCheckedStatus, appendTasksToDoc, buildAppendMarkdown, buildContentTextPreview, buildCreateMarkdown, buildSummaryTitle, createTodoDocFromTasks, deriveImageDetailFromContentJson, normalizeTaskTexts, patchTaskMarkdownByOrder, parseTiptapDocFromDb, } from './contentModel.js';
6
+ import { buildTaskKey, extractFlattenedTasks, filterTasksForTodoList, } from './taskModel.js';
7
+ export { normalizeTaskTexts } from './contentModel.js';
8
+ export async function searchTodoTasks(db, query, options) {
9
+ const noteRows = await loadTodoNoteRows(db, options.includeDeleted, options.scanLimit);
10
+ const allTasks = extractFlattenedTasks(noteRows);
11
+ const filteredTasks = filterTasksForTodoList(allTasks, query, options.taskStatus, options.taskTagFilter, options.taskDateRange);
12
+ const visibleTasks = filteredTasks.slice(0, options.limit);
13
+ const tasks = visibleTasks.map((task) => ({
14
+ task_key: buildTaskKey(task.noteId, task.taskId),
15
+ note_id: task.noteId,
16
+ note_title: task.noteTitle,
17
+ task_id: task.taskId,
18
+ text: task.text,
19
+ checked: task.checked,
20
+ status: task.checked ? 'completed' : 'uncompleted',
21
+ created_at: task.createdAtRaw,
22
+ depth: task.depth,
23
+ has_children: task.hasChildren,
24
+ parent_id: task.parentId,
25
+ tags: task.tags ?? [],
26
+ note_tags: task.noteTags ?? [],
27
+ start_time: task.startTime,
28
+ due_time: task.dueTime,
29
+ }));
30
+ return {
31
+ meta: {
32
+ query: query.trim(),
33
+ returned_count: tasks.length,
34
+ total_count: filteredTasks.length,
35
+ matched_note_count: new Set(tasks.map((task) => task.note_id)).size,
36
+ limit: options.limit,
37
+ scan_limit: options.scanLimit,
38
+ truncated: filteredTasks.length > tasks.length,
39
+ applied_filters: {
40
+ status: options.taskStatus,
41
+ tags: [...options.taskTagFilter],
42
+ include_deleted: options.includeDeleted,
43
+ date_range: serializeTaskDateRange(options.taskDateRange),
44
+ },
45
+ },
46
+ tasks,
47
+ };
48
+ }
49
+ export async function appendTodoTasks(db, options) {
50
+ const taskTexts = normalizeTaskTexts(options.taskTexts);
51
+ const explicitNoteId = typeof options.noteId === 'string' ? options.noteId.trim() : '';
52
+ const source = explicitNoteId ? 'explicit' : 'latest_non_empty_image_detail';
53
+ const target = explicitNoteId
54
+ ? await getTodoAppendTargetById(db, explicitNoteId)
55
+ : await getLatestTodoAppendTarget(db);
56
+ if (!target) {
57
+ if (explicitNoteId) {
58
+ throw new DinoxError(`Note not found: ${explicitNoteId}`);
59
+ }
60
+ throw new DinoxError('No eligible note found for append. Try `dino todo create ...` first.');
61
+ }
62
+ if (typeof target.is_del === 'number' && target.is_del !== 0) {
63
+ throw new DinoxError(`Note has been deleted: ${target.id}`);
64
+ }
65
+ if (!target.id?.trim()) {
66
+ throw new DinoxError('Note id is required');
67
+ }
68
+ const currentDoc = parseTiptapDocFromDb(target.content_json);
69
+ const hasMarkdownSnapshot = Boolean(target.content_md?.trim());
70
+ const hasDocContent = Array.isArray(currentDoc.content) && currentDoc.content.length > 0;
71
+ if (!hasMarkdownSnapshot && hasDocContent) {
72
+ throw new DinoxError('Cannot append todo: current note has non-empty content_json but empty content_md. Please repair note markdown before append.');
73
+ }
74
+ const nextDoc = appendTasksToDoc(currentDoc, taskTexts);
75
+ const imageDetail = deriveImageDetailFromContentJson(nextDoc);
76
+ const contentMd = buildAppendMarkdown(target.content_md, taskTexts);
77
+ const contentText = buildContentTextPreview(contentMd);
78
+ const now = nowIsoUtc();
79
+ await db.execute(`
80
+ UPDATE c_note
81
+ SET
82
+ content_json = ?,
83
+ image_detail = ?,
84
+ content_md = ?,
85
+ content_text = ?,
86
+ updated_at = ?,
87
+ version = COALESCE(version, 0) + 1
88
+ WHERE id = ?
89
+ AND (is_del IS NULL OR is_del = 0)
90
+ `, [
91
+ JSON.stringify(nextDoc),
92
+ JSON.stringify(imageDetail),
93
+ contentMd,
94
+ contentText,
95
+ now,
96
+ target.id,
97
+ ]);
98
+ return {
99
+ note_id: target.id,
100
+ title: (target.title ?? '').trim(),
101
+ appended_count: taskTexts.length,
102
+ source,
103
+ };
104
+ }
105
+ export async function createTodoNote(db, options) {
106
+ const taskTexts = normalizeTaskTexts(options.taskTexts);
107
+ const title = options.title?.trim() || buildSummaryTitle(taskTexts);
108
+ const contentJson = createTodoDocFromTasks(taskTexts);
109
+ const imageDetail = deriveImageDetailFromContentJson(contentJson);
110
+ const contentMd = buildCreateMarkdown(taskTexts);
111
+ const contentText = buildContentTextPreview(contentMd);
112
+ const created = await createNote(db, {
113
+ id: generateId(),
114
+ title,
115
+ noteType: 'note',
116
+ contentMd,
117
+ contentJson,
118
+ contentText,
119
+ tags: [],
120
+ zettelBoxIds: [],
121
+ userId: options.userId,
122
+ imageDetail,
123
+ });
124
+ return {
125
+ note_id: created.id,
126
+ title,
127
+ created_count: taskTexts.length,
128
+ };
129
+ }
130
+ export async function updateTodoTaskStatus(db, options) {
131
+ const taskId = options.taskId.trim();
132
+ if (!taskId) {
133
+ throw new DinoxError('taskId is required');
134
+ }
135
+ const candidates = await loadTodoTaskCandidatesByTaskId(db, taskId);
136
+ const matches = candidates.flatMap((candidate) => {
137
+ try {
138
+ const doc = parseTiptapDocFromDb(candidate.content_json);
139
+ const applied = applyTaskCheckedStatus(doc, taskId, options.checked);
140
+ if (applied.matchedIndices.length === 0) {
141
+ return [];
142
+ }
143
+ return [{ candidate, applied }];
144
+ }
145
+ catch {
146
+ return [];
147
+ }
148
+ });
149
+ if (matches.length === 0) {
150
+ throw new DinoxError(`Task not found: ${taskId}`);
151
+ }
152
+ if (matches.length > 1) {
153
+ const noteIds = matches
154
+ .map((entry) => entry.candidate.id?.trim())
155
+ .filter((value) => Boolean(value));
156
+ throw new DinoxError(`Task id is not unique; found in multiple notes: ${noteIds.join(', ')}`);
157
+ }
158
+ const match = matches[0];
159
+ const noteId = (match.candidate.id ?? '').trim();
160
+ if (!noteId) {
161
+ throw new DinoxError('Note id is required');
162
+ }
163
+ const nextContentMd = patchTaskMarkdownByOrder(match.candidate.content_md, match.applied.taskOrder, match.applied.matchedIndices, options.checked);
164
+ const nextContentText = buildContentTextPreview(nextContentMd);
165
+ const nextImageDetail = deriveImageDetailFromContentJson(match.applied.nextDoc);
166
+ const hasMarkdownChanged = nextContentMd !== (match.candidate.content_md ?? '');
167
+ const hasContentJsonChanged = match.applied.changedCount > 0;
168
+ if (!hasContentJsonChanged && !hasMarkdownChanged) {
169
+ return {
170
+ note_id: noteId,
171
+ task_id: taskId,
172
+ checked: options.checked,
173
+ updated: false,
174
+ title: (match.candidate.title ?? '').trim(),
175
+ };
176
+ }
177
+ await db.execute(`
178
+ UPDATE c_note
179
+ SET
180
+ content_json = ?,
181
+ image_detail = ?,
182
+ content_md = ?,
183
+ content_text = ?,
184
+ updated_at = ?,
185
+ version = COALESCE(version, 0) + 1
186
+ WHERE id = ?
187
+ AND (is_del IS NULL OR is_del = 0)
188
+ `, [
189
+ JSON.stringify(match.applied.nextDoc),
190
+ JSON.stringify(nextImageDetail),
191
+ nextContentMd,
192
+ nextContentText,
193
+ nowIsoUtc(),
194
+ noteId,
195
+ ]);
196
+ return {
197
+ note_id: noteId,
198
+ task_id: taskId,
199
+ checked: options.checked,
200
+ updated: true,
201
+ title: (match.candidate.title ?? '').trim(),
202
+ };
203
+ }
204
+ async function loadTodoNoteRows(db, includeDeleted, scanLimit) {
205
+ const whereClauses = [
206
+ includeDeleted ? '1 = 1' : '(n.is_del IS NULL OR n.is_del = 0)',
207
+ 'n.image_detail IS NOT NULL',
208
+ "LENGTH(TRIM(n.image_detail)) > 0",
209
+ "TRIM(n.image_detail) <> '[]'",
210
+ ];
211
+ return db.getAll(`
212
+ SELECT
213
+ n.id,
214
+ n.title,
215
+ n.image_detail,
216
+ n.tags,
217
+ n.created_at,
218
+ n.updated_at
219
+ FROM c_note n
220
+ WHERE ${whereClauses.join('\n AND ')}
221
+ ORDER BY COALESCE(n.created_at, n.updated_at) DESC
222
+ LIMIT ?
223
+ `, [scanLimit]);
224
+ }
225
+ async function getTodoAppendTargetById(db, noteId) {
226
+ return db.getOptional(`
227
+ SELECT
228
+ n.id,
229
+ n.title,
230
+ n.content_json,
231
+ n.content_md,
232
+ n.is_del
233
+ FROM c_note n
234
+ WHERE n.id = ?
235
+ `, [noteId]);
236
+ }
237
+ async function getLatestTodoAppendTarget(db) {
238
+ return db.getOptional(`
239
+ SELECT
240
+ n.id,
241
+ n.title,
242
+ n.content_json,
243
+ n.content_md,
244
+ n.is_del
245
+ FROM c_note n
246
+ WHERE (n.is_del IS NULL OR n.is_del = 0)
247
+ AND n.image_detail IS NOT NULL
248
+ AND LENGTH(TRIM(n.image_detail)) > 0
249
+ AND TRIM(n.image_detail) <> '[]'
250
+ ORDER BY n.created_at DESC
251
+ LIMIT 1
252
+ `, []);
253
+ }
254
+ async function loadTodoTaskCandidatesByTaskId(db, taskId) {
255
+ const escapedTaskId = escapeLikePattern(taskId);
256
+ return db.getAll(`
257
+ SELECT
258
+ n.id,
259
+ n.title,
260
+ n.content_json,
261
+ n.content_md,
262
+ n.is_del,
263
+ n.created_at
264
+ FROM c_note n
265
+ WHERE (n.is_del IS NULL OR n.is_del = 0)
266
+ AND n.content_json IS NOT NULL
267
+ AND LENGTH(TRIM(n.content_json)) > 0
268
+ AND n.content_json LIKE ? ESCAPE '\\'
269
+ ORDER BY n.created_at DESC
270
+ LIMIT 500
271
+ `, [`%${escapedTaskId}%`]);
272
+ }
273
+ function escapeLikePattern(value) {
274
+ return value.replace(/[\\%_]/g, (token) => `\\${token}`);
275
+ }
276
+ function serializeTaskDateRange(value) {
277
+ if (!value) {
278
+ return null;
279
+ }
280
+ const from = value.startDate ? new Date(value.startDate).toISOString() : null;
281
+ const to = value.endDate ? new Date(value.endDate).toISOString() : null;
282
+ if (!from && !to) {
283
+ return null;
284
+ }
285
+ return { from, to };
286
+ }
@@ -0,0 +1,37 @@
1
+ export type TaskStatusFilter = 'all' | 'completed' | 'uncompleted';
2
+ export type TaskTimeDisplayFormat = 'date' | 'datetime' | 'datetime-seconds' | 'time';
3
+ export type TaskTimePoint = {
4
+ iso: string;
5
+ displayFormat: TaskTimeDisplayFormat;
6
+ };
7
+ export type TaskDateRangeFilter = {
8
+ startDate?: Date;
9
+ endDate?: Date;
10
+ };
11
+ export type TodoTaskSourceRow = {
12
+ id: string | null;
13
+ title?: string | null;
14
+ image_detail: unknown;
15
+ tags: string | null;
16
+ created_at: string | null;
17
+ updated_at: string | null;
18
+ };
19
+ export type FlattenedTask = {
20
+ noteId: string;
21
+ noteTitle: string;
22
+ taskId: string;
23
+ text: string;
24
+ checked: boolean;
25
+ createdAt: Date;
26
+ createdAtRaw: string | null;
27
+ startTime?: TaskTimePoint;
28
+ dueTime?: TaskTimePoint;
29
+ tags?: string[];
30
+ noteTags?: string[];
31
+ depth: number;
32
+ hasChildren: boolean;
33
+ parentId: string | null;
34
+ };
35
+ export declare function buildTaskKey(noteId: string, taskId: string): string;
36
+ export declare function extractFlattenedTasks(rows: TodoTaskSourceRow[]): FlattenedTask[];
37
+ export declare function filterTasksForTodoList(allTasks: FlattenedTask[], query: string, taskStatus: TaskStatusFilter, taskTagFilter: string[], taskDateRange?: TaskDateRangeFilter): FlattenedTask[];
@@ -0,0 +1,349 @@
1
+ const DEFAULT_TASK_TIME_DISPLAY_FORMAT = 'datetime';
2
+ export function buildTaskKey(noteId, taskId) {
3
+ return `${noteId}:${taskId}`;
4
+ }
5
+ export function extractFlattenedTasks(rows) {
6
+ const extracted = [];
7
+ for (const row of rows) {
8
+ const noteId = normalizeString(row.id);
9
+ if (!noteId) {
10
+ continue;
11
+ }
12
+ const taskItems = parseImageDetailTaskItems(row.image_detail);
13
+ if (taskItems.length === 0) {
14
+ continue;
15
+ }
16
+ const { createdAt, createdAtRaw } = parseCreatedAt(row.created_at, row.updated_at);
17
+ extracted.push(...extractTasksFromDetailWithHierarchy({
18
+ noteId,
19
+ noteTitle: normalizeString(row.title),
20
+ createdAt,
21
+ createdAtRaw,
22
+ noteTags: parseNoteTags(row.tags),
23
+ taskItems,
24
+ }));
25
+ }
26
+ extracted.sort((left, right) => right.createdAt.getTime() - left.createdAt.getTime());
27
+ return flattenTasks(extracted);
28
+ }
29
+ export function filterTasksForTodoList(allTasks, query, taskStatus, taskTagFilter, taskDateRange) {
30
+ let filteredTasks = allTasks;
31
+ const normalizedQuery = query.trim().toLocaleLowerCase();
32
+ if (normalizedQuery) {
33
+ filteredTasks = filteredTasks.filter((task) => task.text.toLocaleLowerCase().includes(normalizedQuery));
34
+ }
35
+ if (taskStatus === 'completed') {
36
+ filteredTasks = filteredTasks.filter((task) => task.checked);
37
+ }
38
+ else if (taskStatus === 'uncompleted') {
39
+ filteredTasks = filteredTasks.filter((task) => !task.checked);
40
+ }
41
+ if (taskTagFilter.length > 0) {
42
+ filteredTasks = filteredTasks.filter((task) => taskTagFilter.every((tag) => task.tags?.some((taskTag) => taskTag.trim() === tag)));
43
+ }
44
+ const range = resolveTaskDateRange(taskDateRange);
45
+ if (range) {
46
+ filteredTasks = filteredTasks.filter((task) => isTaskInDateRange(task, range));
47
+ }
48
+ return filteredTasks;
49
+ }
50
+ function parseImageDetailTaskItems(raw) {
51
+ if (Array.isArray(raw)) {
52
+ return pickTaskItems(raw);
53
+ }
54
+ if (typeof raw !== 'string') {
55
+ return [];
56
+ }
57
+ const trimmed = raw.trim();
58
+ if (!trimmed) {
59
+ return [];
60
+ }
61
+ try {
62
+ const parsed = JSON.parse(trimmed);
63
+ if (!Array.isArray(parsed)) {
64
+ return [];
65
+ }
66
+ return pickTaskItems(parsed);
67
+ }
68
+ catch {
69
+ return [];
70
+ }
71
+ }
72
+ function pickTaskItems(values) {
73
+ const result = [];
74
+ for (const value of values) {
75
+ if (!value || typeof value !== 'object') {
76
+ continue;
77
+ }
78
+ const node = value;
79
+ if (node.type !== 'taskItem') {
80
+ continue;
81
+ }
82
+ result.push(node);
83
+ }
84
+ return result;
85
+ }
86
+ function parseCreatedAt(createdAtRaw, updatedAtRaw) {
87
+ const createdAtValue = normalizeString(createdAtRaw) || null;
88
+ const fallback = normalizeString(updatedAtRaw) || null;
89
+ const source = createdAtValue ?? fallback;
90
+ if (!source) {
91
+ return { createdAt: new Date(0), createdAtRaw: createdAtValue };
92
+ }
93
+ const parsed = new Date(source);
94
+ if (Number.isNaN(parsed.getTime())) {
95
+ return { createdAt: new Date(0), createdAtRaw: createdAtValue };
96
+ }
97
+ return { createdAt: parsed, createdAtRaw: createdAtValue };
98
+ }
99
+ function parseNoteTags(raw) {
100
+ const value = normalizeString(raw);
101
+ if (!value) {
102
+ return undefined;
103
+ }
104
+ try {
105
+ const parsed = JSON.parse(value);
106
+ if (!Array.isArray(parsed)) {
107
+ return undefined;
108
+ }
109
+ const tags = uniqueTrimmedStrings(parsed);
110
+ return tags.length > 0 ? tags : undefined;
111
+ }
112
+ catch {
113
+ return undefined;
114
+ }
115
+ }
116
+ function uniqueTrimmedStrings(values) {
117
+ const result = [];
118
+ const seen = new Set();
119
+ for (const value of values) {
120
+ const normalized = normalizeString(String(value));
121
+ if (!normalized || seen.has(normalized)) {
122
+ continue;
123
+ }
124
+ seen.add(normalized);
125
+ result.push(normalized);
126
+ }
127
+ return result;
128
+ }
129
+ function normalizeTaskTimeDisplayFormat(value) {
130
+ if (value === 'date')
131
+ return 'date';
132
+ if (value === 'datetime')
133
+ return 'datetime';
134
+ if (value === 'datetime-seconds')
135
+ return 'datetime-seconds';
136
+ if (value === 'time')
137
+ return 'time';
138
+ return DEFAULT_TASK_TIME_DISPLAY_FORMAT;
139
+ }
140
+ function getTaskText(node) {
141
+ if (!node || typeof node !== 'object')
142
+ return '';
143
+ const taskNode = node;
144
+ if (typeof taskNode.text === 'string') {
145
+ return (taskNode.text ?? '').trim();
146
+ }
147
+ if (taskNode.type === 'taskList') {
148
+ return '';
149
+ }
150
+ if (!Array.isArray(taskNode.content)) {
151
+ return '';
152
+ }
153
+ return taskNode.content.map((child) => getTaskText(child)).join('');
154
+ }
155
+ function getTaskTags(node) {
156
+ if (!node || typeof node !== 'object')
157
+ return [];
158
+ const taskNode = node;
159
+ if (taskNode.type === 'hashtag') {
160
+ const label = normalizeString(taskNode.attrs?.label);
161
+ return label ? [label] : [];
162
+ }
163
+ if (taskNode.type === 'taskList') {
164
+ return [];
165
+ }
166
+ if (!Array.isArray(taskNode.content)) {
167
+ return [];
168
+ }
169
+ return taskNode.content.flatMap((child) => getTaskTags(child));
170
+ }
171
+ function collectTaskTimeNodes(node, out) {
172
+ if (!node || typeof node !== 'object' || out.length >= 2) {
173
+ return;
174
+ }
175
+ const taskNode = node;
176
+ if (taskNode.type === 'taskList') {
177
+ return;
178
+ }
179
+ if (taskNode.type === 'time' || taskNode.type === 'timeblock') {
180
+ const iso = normalizeString(taskNode.attrs?.iso);
181
+ if (iso) {
182
+ out.push({
183
+ iso,
184
+ displayFormat: normalizeTaskTimeDisplayFormat(taskNode.attrs?.displayFormat),
185
+ });
186
+ if (out.length >= 2) {
187
+ return;
188
+ }
189
+ }
190
+ }
191
+ if (!Array.isArray(taskNode.content)) {
192
+ return;
193
+ }
194
+ for (const child of taskNode.content) {
195
+ collectTaskTimeNodes(child, out);
196
+ if (out.length >= 2) {
197
+ return;
198
+ }
199
+ }
200
+ }
201
+ function getTaskTimeRange(node) {
202
+ const points = [];
203
+ collectTaskTimeNodes(node, points);
204
+ if (points.length === 0) {
205
+ return {};
206
+ }
207
+ if (points.length === 1) {
208
+ return { dueTime: points[0] };
209
+ }
210
+ return {
211
+ startTime: points[0],
212
+ dueTime: points[1],
213
+ };
214
+ }
215
+ function extractNestedTasks(content) {
216
+ if (!Array.isArray(content)) {
217
+ return [];
218
+ }
219
+ const nested = [];
220
+ for (const value of content) {
221
+ if (!value || typeof value !== 'object') {
222
+ continue;
223
+ }
224
+ const node = value;
225
+ if (node.type === 'taskList' && Array.isArray(node.content)) {
226
+ nested.push(...pickTaskItems(node.content));
227
+ continue;
228
+ }
229
+ if (node.type === 'taskItem') {
230
+ nested.push(node);
231
+ }
232
+ }
233
+ return nested;
234
+ }
235
+ function extractTasksFromDetailWithHierarchy(input) {
236
+ const { noteId, noteTitle, createdAt, createdAtRaw, noteTags, taskItems, depth = 0 } = input;
237
+ const result = [];
238
+ for (const item of taskItems) {
239
+ const taskId = normalizeString(item.id) ||
240
+ normalizeString(item.attrs?.id) ||
241
+ '';
242
+ if (!taskId) {
243
+ continue;
244
+ }
245
+ const checked = item.checked === true || item.checked === 'true' || item.attrs?.checked === true;
246
+ const text = getTaskText(item).trim();
247
+ const tags = getTaskTags(item);
248
+ const { startTime, dueTime } = getTaskTimeRange(item);
249
+ const children = extractTasksFromDetailWithHierarchy({
250
+ noteId,
251
+ noteTitle,
252
+ createdAt,
253
+ createdAtRaw,
254
+ noteTags,
255
+ taskItems: extractNestedTasks(item.content),
256
+ depth: depth + 1,
257
+ });
258
+ result.push({
259
+ noteId,
260
+ noteTitle,
261
+ taskId,
262
+ text,
263
+ checked,
264
+ createdAt,
265
+ createdAtRaw,
266
+ startTime,
267
+ dueTime,
268
+ tags: tags.length > 0 ? tags : undefined,
269
+ noteTags,
270
+ children,
271
+ depth,
272
+ });
273
+ }
274
+ return result;
275
+ }
276
+ function flattenTasks(tasks, parentId = null) {
277
+ const result = [];
278
+ for (const task of tasks) {
279
+ result.push({
280
+ noteId: task.noteId,
281
+ noteTitle: task.noteTitle,
282
+ taskId: task.taskId,
283
+ text: task.text,
284
+ checked: task.checked,
285
+ createdAt: task.createdAt,
286
+ createdAtRaw: task.createdAtRaw,
287
+ startTime: task.startTime,
288
+ dueTime: task.dueTime,
289
+ tags: task.tags,
290
+ noteTags: task.noteTags,
291
+ depth: task.depth,
292
+ hasChildren: task.children.length > 0,
293
+ parentId,
294
+ });
295
+ if (task.children.length > 0) {
296
+ result.push(...flattenTasks(task.children, task.taskId));
297
+ }
298
+ }
299
+ return result;
300
+ }
301
+ function resolveTaskDateRange(taskDateRange) {
302
+ if (!taskDateRange) {
303
+ return null;
304
+ }
305
+ const startSource = taskDateRange.startDate ?? taskDateRange.endDate;
306
+ const endSource = taskDateRange.endDate ?? taskDateRange.startDate;
307
+ if (!startSource || !endSource) {
308
+ return null;
309
+ }
310
+ const start = new Date(startSource);
311
+ const end = new Date(endSource);
312
+ if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) {
313
+ return null;
314
+ }
315
+ start.setHours(0, 0, 0, 0);
316
+ end.setHours(23, 59, 59, 999);
317
+ return {
318
+ startMs: Math.min(start.getTime(), end.getTime()),
319
+ endMs: Math.max(start.getTime(), end.getTime()),
320
+ };
321
+ }
322
+ function isTaskInDateRange(task, range) {
323
+ const dueMs = parseTaskPointMs(task.dueTime);
324
+ if (dueMs !== null && dueMs >= range.startMs && dueMs <= range.endMs) {
325
+ return true;
326
+ }
327
+ const startMs = parseTaskPointMs(task.startTime);
328
+ if (startMs !== null && startMs >= range.startMs && startMs <= range.endMs) {
329
+ return true;
330
+ }
331
+ const createdAtMs = task.createdAt.getTime();
332
+ if (!Number.isNaN(createdAtMs) && createdAtMs >= range.startMs && createdAtMs <= range.endMs) {
333
+ return true;
334
+ }
335
+ return false;
336
+ }
337
+ function parseTaskPointMs(point) {
338
+ if (!point?.iso) {
339
+ return null;
340
+ }
341
+ const timestamp = Date.parse(point.iso);
342
+ return Number.isNaN(timestamp) ? null : timestamp;
343
+ }
344
+ function normalizeString(value) {
345
+ if (typeof value !== 'string') {
346
+ return '';
347
+ }
348
+ return value.trim();
349
+ }
@@ -1,2 +1,11 @@
1
1
  import { Command } from 'commander';
2
- export declare function registerUpdateCommand(program: Command): void;
2
+ type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun';
3
+ type LogFn = (...parts: unknown[]) => void;
4
+ type UpdateCommandDeps = {
5
+ detectPackageManager: () => PackageManager;
6
+ runUpdate: (pm: PackageManager) => Promise<void>;
7
+ getVersion: () => string;
8
+ log: LogFn;
9
+ };
10
+ export declare function registerUpdateCommand(program: Command, overrides?: Partial<UpdateCommandDeps>): void;
11
+ export {};