@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.
- package/README.md +72 -34
- package/dist/cli.js +15 -1
- package/dist/commands/auth/index.d.ts +2 -0
- package/dist/commands/auth/index.js +209 -163
- package/dist/commands/boxes/index.d.ts +2 -0
- package/dist/commands/boxes/index.js +119 -75
- package/dist/commands/boxes/repo.d.ts +8 -0
- package/dist/commands/boxes/repo.js +41 -0
- package/dist/commands/config/index.d.ts +2 -0
- package/dist/commands/config/index.js +85 -36
- package/dist/commands/descriptor/register.d.ts +8 -0
- package/dist/commands/descriptor/register.js +113 -0
- package/dist/commands/descriptor/types.d.ts +37 -0
- package/dist/commands/descriptor/types.js +17 -0
- package/dist/commands/graph/index.d.ts +2 -0
- package/dist/commands/graph/index.js +143 -73
- package/dist/commands/info/index.d.ts +2 -0
- package/dist/commands/info/index.js +41 -14
- package/dist/commands/mcp/index.js +11 -10
- package/dist/commands/mcp/server.js +6 -3
- package/dist/commands/mcp/tools.js +30 -104
- package/dist/commands/mcp/types.d.ts +8 -1
- package/dist/commands/meta/index.d.ts +2 -0
- package/dist/commands/meta/index.js +64 -33
- package/dist/commands/meta/repo.d.ts +6 -0
- package/dist/commands/meta/repo.js +6 -0
- package/dist/commands/notes/actions.d.ts +10 -0
- package/dist/commands/notes/actions.js +403 -0
- package/dist/commands/notes/descriptors.d.ts +4 -0
- package/dist/commands/notes/descriptors.js +250 -0
- package/dist/commands/notes/dryRun.d.ts +31 -0
- package/dist/commands/notes/dryRun.js +130 -0
- package/dist/commands/notes/index.d.ts +2 -51
- package/dist/commands/notes/index.js +2 -581
- package/dist/commands/notes/repo.d.ts +9 -0
- package/dist/commands/notes/repo.js +56 -10
- package/dist/commands/notes/runtime.d.ts +28 -0
- package/dist/commands/notes/runtime.js +185 -0
- package/dist/commands/notes/searchOutput.d.ts +10 -2
- package/dist/commands/notes/searchOutput.js +14 -2
- package/dist/commands/notes/searchResponse.d.ts +51 -0
- package/dist/commands/notes/searchResponse.js +56 -0
- package/dist/commands/notes/searchSql.js +2 -0
- package/dist/commands/prompt/index.d.ts +2 -0
- package/dist/commands/prompt/index.js +122 -72
- package/dist/commands/prompt/repo.d.ts +7 -0
- package/dist/commands/prompt/repo.js +46 -0
- package/dist/commands/schema/commandMetadata.d.ts +2 -0
- package/dist/commands/schema/commandMetadata.js +18 -0
- package/dist/commands/schema/globalOptions.d.ts +3 -0
- package/dist/commands/schema/globalOptions.js +3 -0
- package/dist/commands/schema/index.d.ts +4 -0
- package/dist/commands/schema/index.js +109 -0
- package/dist/commands/schema/renderSkills.d.ts +6 -0
- package/dist/commands/schema/renderSkills.js +216 -0
- package/dist/commands/schema/spec.d.ts +6 -0
- package/dist/commands/schema/spec.js +40 -0
- package/dist/commands/schema/types.d.ts +47 -0
- package/dist/commands/schema/types.js +1 -0
- package/dist/commands/storage/index.d.ts +4 -0
- package/dist/commands/storage/index.js +292 -0
- package/dist/commands/storage/repo.d.ts +162 -0
- package/dist/commands/storage/repo.js +751 -0
- package/dist/commands/sync.d.ts +2 -0
- package/dist/commands/sync.js +41 -27
- package/dist/commands/tags/index.d.ts +2 -0
- package/dist/commands/tags/index.js +121 -79
- package/dist/commands/tags/repo.d.ts +7 -0
- package/dist/commands/tags/repo.js +52 -0
- package/dist/commands/todo/dryRun.d.ts +23 -0
- package/dist/commands/todo/dryRun.js +133 -0
- package/dist/commands/todo/index.d.ts +2 -0
- package/dist/commands/todo/index.js +285 -135
- package/dist/commands/todo/repo.d.ts +11 -0
- package/dist/commands/todo/repo.js +37 -9
- package/dist/commands/update.d.ts +3 -0
- package/dist/commands/update.js +35 -13
- package/dist/commands/writeDryRun.d.ts +62 -0
- package/dist/commands/writeDryRun.js +131 -0
- package/dist/constants/links.d.ts +1 -1
- package/dist/constants/links.js +1 -1
- package/dist/daemon/ensure.js +3 -2
- package/dist/daemon/index.js +17 -15
- package/dist/dinox.js +9 -13
- package/dist/powersync/schema/index.d.ts +27 -0
- package/dist/powersync/schema/index.js +2 -0
- package/dist/powersync/schema/note.d.ts +1 -0
- package/dist/powersync/schema/note.js +1 -0
- package/dist/powersync/schema/projects.d.ts +3 -0
- package/dist/powersync/schema/projects.js +3 -0
- package/dist/powersync/schema/storage.d.ts +24 -0
- package/dist/powersync/schema/storage.js +24 -0
- package/dist/skills/check.d.ts +6 -0
- package/dist/skills/check.js +254 -0
- package/dist/utils/bidlinkCanonicalization.d.ts +2 -0
- package/dist/utils/bidlinkCanonicalization.js +161 -0
- package/dist/utils/dryRun.d.ts +13 -0
- package/dist/utils/dryRun.js +69 -0
- package/dist/utils/output.d.ts +19 -0
- package/dist/utils/output.js +155 -1
- package/dist/utils/terminalSanitization.d.ts +2 -0
- package/dist/utils/terminalSanitization.js +33 -0
- package/dist/utils/tiptapMarkdown.d.ts +4 -1
- package/dist/utils/tiptapMarkdown.js +285 -113
- package/dist/utils/tiptapMarkdownSchema.d.ts +37 -0
- package/dist/utils/tiptapMarkdownSchema.js +577 -0
- package/dist/utils/tiptapMarkdownSchemaUtils.d.ts +74 -0
- package/dist/utils/tiptapMarkdownSchemaUtils.js +394 -0
- package/dist/utils/updateNotice.d.ts +7 -0
- package/dist/utils/updateNotice.js +64 -0
- package/package.json +25 -3
|
@@ -5,10 +5,20 @@ import { listBoxes } from '../boxes/repo.js';
|
|
|
5
5
|
import { listTags } from '../tags/repo.js';
|
|
6
6
|
import { buildNoteSearchSqlFilter } from './searchSql.js';
|
|
7
7
|
const MAX_SEARCH_SUMMARY_CHARS = 200;
|
|
8
|
+
function normalizeStoredStarred(value) {
|
|
9
|
+
if (typeof value === 'number') {
|
|
10
|
+
return value === 1;
|
|
11
|
+
}
|
|
12
|
+
if (typeof value === 'string') {
|
|
13
|
+
return value.trim() === '1';
|
|
14
|
+
}
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
8
17
|
export async function searchNotes(db, query, options) {
|
|
9
18
|
const normalizedQuery = query.trim();
|
|
10
19
|
const tagFilter = buildTagFilter(options.tagsExpression, 'n');
|
|
11
20
|
const createdAtFilter = buildCreatedAtFilter(options.createdAtFrom, options.createdAtTo, 'n');
|
|
21
|
+
const starredFilter = buildStarredFilter(options.starred, 'n');
|
|
12
22
|
const zettelBoxFilter = buildZettelBoxFilter(options.zettelBoxIds, options.matchEmptyZettelBoxes, 'n');
|
|
13
23
|
const sqlFilter = await buildNoteSearchSqlFilter({
|
|
14
24
|
expression: options.sqlExpression,
|
|
@@ -20,7 +30,7 @@ export async function searchNotes(db, query, options) {
|
|
|
20
30
|
const ftsMatch = buildFtsMatchQuery(normalizedQuery);
|
|
21
31
|
if (ftsMatch) {
|
|
22
32
|
try {
|
|
23
|
-
rows = await searchNotesWithFts(db, ftsMatch, tagFilter, createdAtFilter, zettelBoxFilter, sqlFilter);
|
|
33
|
+
rows = await searchNotesWithFts(db, ftsMatch, tagFilter, createdAtFilter, starredFilter, zettelBoxFilter, sqlFilter);
|
|
24
34
|
return enrichSearchRows(db, rows);
|
|
25
35
|
}
|
|
26
36
|
catch (error) {
|
|
@@ -61,6 +71,10 @@ export async function searchNotes(db, query, options) {
|
|
|
61
71
|
whereClauses.push(createdAtFilter.sql);
|
|
62
72
|
params.push(...createdAtFilter.params);
|
|
63
73
|
}
|
|
74
|
+
if (starredFilter) {
|
|
75
|
+
whereClauses.push(starredFilter.sql);
|
|
76
|
+
params.push(...starredFilter.params);
|
|
77
|
+
}
|
|
64
78
|
if (zettelBoxFilter) {
|
|
65
79
|
whereClauses.push(zettelBoxFilter.sql);
|
|
66
80
|
params.push(...zettelBoxFilter.params);
|
|
@@ -77,14 +91,15 @@ export async function searchNotes(db, query, options) {
|
|
|
77
91
|
n.content_text,
|
|
78
92
|
n.tags,
|
|
79
93
|
n.zettel_boxes,
|
|
80
|
-
n.created_at
|
|
94
|
+
n.created_at,
|
|
95
|
+
n.is_starred
|
|
81
96
|
FROM c_note n
|
|
82
97
|
WHERE ${whereClauses.join('\n AND ')}
|
|
83
98
|
ORDER BY COALESCE(n.updated_at, n.created_at) DESC
|
|
84
99
|
`, params);
|
|
85
100
|
return enrichSearchRows(db, rows);
|
|
86
101
|
}
|
|
87
|
-
async function searchNotesWithFts(db, matchQuery, tagFilter, createdAtFilter, zettelBoxFilter, sqlFilter) {
|
|
102
|
+
async function searchNotesWithFts(db, matchQuery, tagFilter, createdAtFilter, starredFilter, zettelBoxFilter, sqlFilter) {
|
|
88
103
|
const whereClauses = ['(n.is_del IS NULL OR n.is_del = 0)', 'note_local_fts MATCH ?'];
|
|
89
104
|
const params = [matchQuery];
|
|
90
105
|
if (tagFilter) {
|
|
@@ -95,6 +110,10 @@ async function searchNotesWithFts(db, matchQuery, tagFilter, createdAtFilter, ze
|
|
|
95
110
|
whereClauses.push(createdAtFilter.sql);
|
|
96
111
|
params.push(...createdAtFilter.params);
|
|
97
112
|
}
|
|
113
|
+
if (starredFilter) {
|
|
114
|
+
whereClauses.push(starredFilter.sql);
|
|
115
|
+
params.push(...starredFilter.params);
|
|
116
|
+
}
|
|
98
117
|
if (zettelBoxFilter) {
|
|
99
118
|
whereClauses.push(zettelBoxFilter.sql);
|
|
100
119
|
params.push(...zettelBoxFilter.params);
|
|
@@ -111,7 +130,8 @@ async function searchNotesWithFts(db, matchQuery, tagFilter, createdAtFilter, ze
|
|
|
111
130
|
n.content_text,
|
|
112
131
|
n.tags,
|
|
113
132
|
n.zettel_boxes,
|
|
114
|
-
n.created_at
|
|
133
|
+
n.created_at,
|
|
134
|
+
n.is_starred
|
|
115
135
|
FROM note_local_fts f
|
|
116
136
|
JOIN c_note n ON n.id = f.note_id
|
|
117
137
|
WHERE ${whereClauses.join('\n AND ')}
|
|
@@ -132,6 +152,7 @@ async function enrichSearchRows(db, rows) {
|
|
|
132
152
|
summary,
|
|
133
153
|
tags: parseStoredTags(row.tags),
|
|
134
154
|
created_at: row.created_at,
|
|
155
|
+
is_starred: normalizeStoredStarred(row.is_starred),
|
|
135
156
|
zettel_boxes: zettelBoxes,
|
|
136
157
|
};
|
|
137
158
|
});
|
|
@@ -150,7 +171,7 @@ function truncateSearchSummary(value) {
|
|
|
150
171
|
}
|
|
151
172
|
return chars.slice(0, MAX_SEARCH_SUMMARY_CHARS).join('');
|
|
152
173
|
}
|
|
153
|
-
function parseZettelBoxIds(raw) {
|
|
174
|
+
export function parseZettelBoxIds(raw) {
|
|
154
175
|
const value = raw?.trim();
|
|
155
176
|
if (!value) {
|
|
156
177
|
return [];
|
|
@@ -248,6 +269,15 @@ function buildCreatedAtFilter(createdAtFrom, createdAtTo, noteAlias) {
|
|
|
248
269
|
params,
|
|
249
270
|
};
|
|
250
271
|
}
|
|
272
|
+
function buildStarredFilter(starred, noteAlias) {
|
|
273
|
+
if (typeof starred !== 'boolean') {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
sql: `(${noteAlias}.is_starred = ?)`,
|
|
278
|
+
params: [starred ? '1' : '0'],
|
|
279
|
+
};
|
|
280
|
+
}
|
|
251
281
|
function buildZettelBoxFilter(zettelBoxIds, matchEmptyZettelBoxes, noteAlias) {
|
|
252
282
|
const normalizedIds = Array.from(new Set((zettelBoxIds ?? [])
|
|
253
283
|
.map((id) => id.trim())
|
|
@@ -490,6 +520,7 @@ export async function getNote(db, id) {
|
|
|
490
520
|
created_at,
|
|
491
521
|
updated_at,
|
|
492
522
|
is_del,
|
|
523
|
+
is_starred,
|
|
493
524
|
version
|
|
494
525
|
FROM c_note
|
|
495
526
|
WHERE id = ?
|
|
@@ -539,6 +570,7 @@ export async function getNoteContext(db, id) {
|
|
|
539
570
|
n.created_at,
|
|
540
571
|
n.updated_at,
|
|
541
572
|
n.is_del,
|
|
573
|
+
n.is_starred,
|
|
542
574
|
n.zettel_boxes AS zettel_boxes_raw
|
|
543
575
|
FROM c_note n
|
|
544
576
|
WHERE n.id = ?
|
|
@@ -560,6 +592,7 @@ export async function getNoteContext(db, id) {
|
|
|
560
592
|
created_at: row.created_at,
|
|
561
593
|
updated_at: row.updated_at,
|
|
562
594
|
is_del: row.is_del,
|
|
595
|
+
is_starred: normalizeStoredStarred(row.is_starred),
|
|
563
596
|
zettel_boxes: boxIds
|
|
564
597
|
.map((boxId) => boxNameMap.get(boxId))
|
|
565
598
|
.filter((name) => typeof name === 'string' && name.length > 0),
|
|
@@ -607,6 +640,7 @@ function mapNoteDetailRow(note, boxNameMap) {
|
|
|
607
640
|
source: note.source,
|
|
608
641
|
tags: note.tags,
|
|
609
642
|
is_del: note.is_del,
|
|
643
|
+
is_starred: normalizeStoredStarred(note.is_starred),
|
|
610
644
|
content_md: note.content_md,
|
|
611
645
|
zettel_boxes: zettelBoxes,
|
|
612
646
|
};
|
|
@@ -632,6 +666,7 @@ export async function getNoteDetails(db, ids) {
|
|
|
632
666
|
source,
|
|
633
667
|
tags,
|
|
634
668
|
is_del,
|
|
669
|
+
is_starred,
|
|
635
670
|
content_md,
|
|
636
671
|
zettel_boxes AS zettel_boxes_raw
|
|
637
672
|
FROM c_note
|
|
@@ -735,6 +770,7 @@ export async function createNote(db, input) {
|
|
|
735
770
|
['extra_data', '{}'],
|
|
736
771
|
['format_type', ''],
|
|
737
772
|
['comments', '[]'],
|
|
773
|
+
['is_starred', 0],
|
|
738
774
|
['is_top', 0],
|
|
739
775
|
['pinned_at', '1997-01-01T00:00:00.000Z'],
|
|
740
776
|
['chat_context', '{}'],
|
|
@@ -813,7 +849,7 @@ async function validateTagsForWrite(db, requestedTags, retryCommand) {
|
|
|
813
849
|
}
|
|
814
850
|
return resolved;
|
|
815
851
|
}
|
|
816
|
-
function parseStoredTags(raw) {
|
|
852
|
+
export function parseStoredTags(raw) {
|
|
817
853
|
if (!raw) {
|
|
818
854
|
return [];
|
|
819
855
|
}
|
|
@@ -867,11 +903,12 @@ export async function updateNote(db, input) {
|
|
|
867
903
|
}
|
|
868
904
|
const shouldUpdateTags = Array.isArray(input.tags);
|
|
869
905
|
const shouldUpdateBoxes = Array.isArray(input.zettelBoxIds);
|
|
870
|
-
|
|
871
|
-
|
|
906
|
+
const shouldUpdateStarred = typeof input.starred === 'boolean';
|
|
907
|
+
if (!shouldUpdateTags && !shouldUpdateBoxes && !shouldUpdateStarred) {
|
|
908
|
+
throw new DinoxError('At least one field is required: tags, boxes, or starred');
|
|
872
909
|
}
|
|
873
910
|
const existing = await db.getOptional(`
|
|
874
|
-
SELECT id, tags, zettel_boxes, is_del
|
|
911
|
+
SELECT id, tags, zettel_boxes, is_del, is_starred
|
|
875
912
|
FROM c_note
|
|
876
913
|
WHERE id = ?
|
|
877
914
|
`, [id]);
|
|
@@ -883,16 +920,20 @@ export async function updateNote(db, input) {
|
|
|
883
920
|
}
|
|
884
921
|
const currentTags = parseStoredTags(existing.tags);
|
|
885
922
|
const currentZettelBoxIds = parseZettelBoxIds(existing.zettel_boxes);
|
|
923
|
+
const currentStarred = normalizeStoredStarred(existing.is_starred);
|
|
886
924
|
const nextTags = shouldUpdateTags ? normalizeDistinctStrings(input.tags) : currentTags;
|
|
887
925
|
const nextZettelBoxIds = shouldUpdateBoxes ? normalizeDistinctStrings(input.zettelBoxIds) : currentZettelBoxIds;
|
|
926
|
+
const nextStarred = shouldUpdateStarred ? input.starred : currentStarred;
|
|
888
927
|
const tagsChanged = shouldUpdateTags && !sameTagSet(currentTags, nextTags);
|
|
889
928
|
const boxesChanged = shouldUpdateBoxes && !sameStringSet(currentZettelBoxIds, nextZettelBoxIds);
|
|
890
|
-
|
|
929
|
+
const starredChanged = shouldUpdateStarred && currentStarred !== nextStarred;
|
|
930
|
+
if (!tagsChanged && !boxesChanged && !starredChanged) {
|
|
891
931
|
return {
|
|
892
932
|
id,
|
|
893
933
|
updated: false,
|
|
894
934
|
tags: currentTags,
|
|
895
935
|
zettelBoxIds: currentZettelBoxIds,
|
|
936
|
+
is_starred: currentStarred,
|
|
896
937
|
};
|
|
897
938
|
}
|
|
898
939
|
const setParts = [];
|
|
@@ -905,6 +946,10 @@ export async function updateNote(db, input) {
|
|
|
905
946
|
setParts.push('zettel_boxes = ?');
|
|
906
947
|
params.push(JSON.stringify(nextZettelBoxIds));
|
|
907
948
|
}
|
|
949
|
+
if (starredChanged) {
|
|
950
|
+
setParts.push('is_starred = ?');
|
|
951
|
+
params.push(nextStarred ? 1 : 0);
|
|
952
|
+
}
|
|
908
953
|
setParts.push('updated_at = ?');
|
|
909
954
|
params.push(nowIsoUtc());
|
|
910
955
|
setParts.push('version = COALESCE(version, 0) + 1');
|
|
@@ -919,6 +964,7 @@ export async function updateNote(db, input) {
|
|
|
919
964
|
updated: true,
|
|
920
965
|
tags: nextTags,
|
|
921
966
|
zettelBoxIds: nextZettelBoxIds,
|
|
967
|
+
is_starred: nextStarred,
|
|
922
968
|
};
|
|
923
969
|
}
|
|
924
970
|
export async function assertNotesUpdatable(db, noteIds) {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import type { AbstractPowerSyncDatabase } from '@powersync/common';
|
|
3
|
+
import { loadConfig } from '../../config/store.js';
|
|
4
|
+
import { resolveConfig } from '../../config/resolve.js';
|
|
5
|
+
export type NoteCommandRuntime = {
|
|
6
|
+
db: AbstractPowerSyncDatabase;
|
|
7
|
+
config: ReturnType<typeof resolveConfig>;
|
|
8
|
+
rawConfig: Awaited<ReturnType<typeof loadConfig>>;
|
|
9
|
+
offline: boolean;
|
|
10
|
+
timeoutMs: number;
|
|
11
|
+
stale: boolean;
|
|
12
|
+
};
|
|
13
|
+
export declare function parseNoteType(value: unknown): 'note' | 'crawl';
|
|
14
|
+
export declare function parseSearchLimit(value: unknown): number;
|
|
15
|
+
export declare function parseSearchOffset(value: unknown): number;
|
|
16
|
+
export declare function parsePreviewLineCount(value: unknown): number;
|
|
17
|
+
export declare function parseStarredOption(value: unknown, optionName: string): boolean;
|
|
18
|
+
export declare function parseStringInput(value: string): Promise<string>;
|
|
19
|
+
export declare function parseListInput(raw: string, optionName: string): string[];
|
|
20
|
+
export declare function parseStringListInput(value: string, optionName: string): Promise<string[]>;
|
|
21
|
+
export type ParsedSearchListInput = {
|
|
22
|
+
values: string[];
|
|
23
|
+
isExplicitEmptyArray: boolean;
|
|
24
|
+
};
|
|
25
|
+
export declare function parseSearchListInput(value: string, optionName: string): Promise<ParsedSearchListInput>;
|
|
26
|
+
export declare function parseTargetNoteIds(idArg: string | undefined, idsOption: string | undefined, commandName: 'detail' | 'update'): Promise<string[]>;
|
|
27
|
+
export declare function syncBeforeNoteCommand(db: AbstractPowerSyncDatabase, offline: boolean, timeoutMs: number): Promise<void>;
|
|
28
|
+
export declare function withNoteDatabase(command: Command, run: (runtime: NoteCommandRuntime) => Promise<void>): Promise<void>;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { loadConfig } from '../../config/store.js';
|
|
2
|
+
import { resolveConfig } from '../../config/resolve.js';
|
|
3
|
+
import { connectPowerSync, waitForIdleDataFlow } from '../../powersync/runtime.js';
|
|
4
|
+
import { syncNoteTokenIndex } from '../../powersync/tokenIndex.js';
|
|
5
|
+
import { readStringOrFile } from '../../utils/argValue.js';
|
|
6
|
+
import { DinoxError } from '../../utils/errors.js';
|
|
7
|
+
import { parseSyncTimeoutMs } from '../../utils/syncTimeout.js';
|
|
8
|
+
const DEFAULT_NOTE_SEARCH_LIMIT = 50;
|
|
9
|
+
const MAX_NOTE_SEARCH_LIMIT = 500;
|
|
10
|
+
export function parseNoteType(value) {
|
|
11
|
+
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
12
|
+
if (normalized === 'note' || normalized === 'crawl') {
|
|
13
|
+
return normalized;
|
|
14
|
+
}
|
|
15
|
+
throw new DinoxError('--type must be one of: note, crawl');
|
|
16
|
+
}
|
|
17
|
+
export function parseSearchLimit(value) {
|
|
18
|
+
if (value === undefined || value === null || value === '') {
|
|
19
|
+
return DEFAULT_NOTE_SEARCH_LIMIT;
|
|
20
|
+
}
|
|
21
|
+
if (typeof value !== 'string') {
|
|
22
|
+
throw new DinoxError('--limit must be a positive integer');
|
|
23
|
+
}
|
|
24
|
+
const trimmed = value.trim();
|
|
25
|
+
if (!/^\d+$/.test(trimmed)) {
|
|
26
|
+
throw new DinoxError('--limit must be a positive integer');
|
|
27
|
+
}
|
|
28
|
+
const parsed = Number(trimmed);
|
|
29
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
30
|
+
throw new DinoxError('--limit must be a positive integer');
|
|
31
|
+
}
|
|
32
|
+
if (parsed > MAX_NOTE_SEARCH_LIMIT) {
|
|
33
|
+
throw new DinoxError(`--limit cannot be greater than ${MAX_NOTE_SEARCH_LIMIT}`);
|
|
34
|
+
}
|
|
35
|
+
return Math.trunc(parsed);
|
|
36
|
+
}
|
|
37
|
+
export function parseSearchOffset(value) {
|
|
38
|
+
if (value === undefined || value === null || value === '') {
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
if (typeof value !== 'string') {
|
|
42
|
+
throw new DinoxError('--offset must be a non-negative integer');
|
|
43
|
+
}
|
|
44
|
+
const trimmed = value.trim();
|
|
45
|
+
if (!/^\d+$/.test(trimmed)) {
|
|
46
|
+
throw new DinoxError('--offset must be a non-negative integer');
|
|
47
|
+
}
|
|
48
|
+
const parsed = Number(trimmed);
|
|
49
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
50
|
+
throw new DinoxError('--offset must be a non-negative integer');
|
|
51
|
+
}
|
|
52
|
+
return Math.trunc(parsed);
|
|
53
|
+
}
|
|
54
|
+
export function parsePreviewLineCount(value) {
|
|
55
|
+
if (value === undefined || value === null || value === '') {
|
|
56
|
+
return 10;
|
|
57
|
+
}
|
|
58
|
+
if (typeof value !== 'string') {
|
|
59
|
+
throw new DinoxError('--lines must be a positive integer');
|
|
60
|
+
}
|
|
61
|
+
const trimmed = value.trim();
|
|
62
|
+
if (!/^\d+$/.test(trimmed)) {
|
|
63
|
+
throw new DinoxError('--lines must be a positive integer');
|
|
64
|
+
}
|
|
65
|
+
const parsed = Number(trimmed);
|
|
66
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
67
|
+
throw new DinoxError('--lines must be a positive integer');
|
|
68
|
+
}
|
|
69
|
+
if (parsed > 500) {
|
|
70
|
+
throw new DinoxError('--lines cannot be greater than 500');
|
|
71
|
+
}
|
|
72
|
+
return Math.trunc(parsed);
|
|
73
|
+
}
|
|
74
|
+
export function parseStarredOption(value, optionName) {
|
|
75
|
+
if (typeof value !== 'string') {
|
|
76
|
+
throw new DinoxError(`${optionName} must be one of: true, false, 1, 0, yes, no`);
|
|
77
|
+
}
|
|
78
|
+
const normalized = value.trim().toLowerCase();
|
|
79
|
+
if (normalized === 'true' || normalized === '1' || normalized === 'yes') {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
if (normalized === 'false' || normalized === '0' || normalized === 'no') {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
throw new DinoxError(`${optionName} must be one of: true, false, 1, 0, yes, no`);
|
|
86
|
+
}
|
|
87
|
+
export async function parseStringInput(value) {
|
|
88
|
+
return readStringOrFile(value);
|
|
89
|
+
}
|
|
90
|
+
export function parseListInput(raw, optionName) {
|
|
91
|
+
const value = raw.trim();
|
|
92
|
+
if (!value) {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
if (value.startsWith('[')) {
|
|
96
|
+
try {
|
|
97
|
+
const parsed = JSON.parse(value);
|
|
98
|
+
if (!Array.isArray(parsed)) {
|
|
99
|
+
throw new DinoxError(`${optionName} must be a JSON array`);
|
|
100
|
+
}
|
|
101
|
+
return Array.from(new Set(parsed
|
|
102
|
+
.map((entry) => String(entry).trim())
|
|
103
|
+
.filter((entry) => entry.length > 0)));
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
if (error instanceof DinoxError) {
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
throw new DinoxError(`Invalid list for ${optionName}`, { cause: error });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return Array.from(new Set(value
|
|
113
|
+
.split(/[\n,,]/g)
|
|
114
|
+
.map((entry) => entry.trim())
|
|
115
|
+
.filter((entry) => entry.length > 0)));
|
|
116
|
+
}
|
|
117
|
+
export async function parseStringListInput(value, optionName) {
|
|
118
|
+
const raw = await readStringOrFile(value);
|
|
119
|
+
return parseListInput(raw, optionName);
|
|
120
|
+
}
|
|
121
|
+
export async function parseSearchListInput(value, optionName) {
|
|
122
|
+
const raw = await readStringOrFile(value);
|
|
123
|
+
return {
|
|
124
|
+
values: parseListInput(raw, optionName),
|
|
125
|
+
isExplicitEmptyArray: isExplicitEmptyArrayLiteral(raw),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function isExplicitEmptyArrayLiteral(value) {
|
|
129
|
+
const normalized = value.trim();
|
|
130
|
+
if (!normalized.startsWith('[')) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
const parsed = JSON.parse(normalized);
|
|
135
|
+
return Array.isArray(parsed) && parsed.length === 0;
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
export async function parseTargetNoteIds(idArg, idsOption, commandName) {
|
|
142
|
+
const merged = [];
|
|
143
|
+
const idFromArg = typeof idArg === 'string' ? idArg.trim() : '';
|
|
144
|
+
if (idFromArg) {
|
|
145
|
+
merged.push(idFromArg);
|
|
146
|
+
}
|
|
147
|
+
if (typeof idsOption === 'string') {
|
|
148
|
+
merged.push(...(await parseStringListInput(idsOption, '--ids')));
|
|
149
|
+
}
|
|
150
|
+
const ids = Array.from(new Set(merged
|
|
151
|
+
.map((id) => id.trim())
|
|
152
|
+
.filter((id) => id.length > 0)));
|
|
153
|
+
if (ids.length === 0) {
|
|
154
|
+
throw new DinoxError(`Provide <id> or --ids for ${commandName}`);
|
|
155
|
+
}
|
|
156
|
+
return ids;
|
|
157
|
+
}
|
|
158
|
+
export async function syncBeforeNoteCommand(db, offline, timeoutMs) {
|
|
159
|
+
if (offline) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
await waitForIdleDataFlow(db, Math.min(timeoutMs, 10_000));
|
|
163
|
+
await syncNoteTokenIndex(db);
|
|
164
|
+
}
|
|
165
|
+
export async function withNoteDatabase(command, run) {
|
|
166
|
+
const globals = command.optsWithGlobals?.() ?? {};
|
|
167
|
+
const rawConfig = await loadConfig();
|
|
168
|
+
const config = resolveConfig(rawConfig);
|
|
169
|
+
const timeoutMs = parseSyncTimeoutMs(globals.syncTimeout) ?? config.sync.timeoutMs;
|
|
170
|
+
const offline = Boolean(globals.offline);
|
|
171
|
+
const { db, stale } = await connectPowerSync({ config, offline, timeoutMs });
|
|
172
|
+
try {
|
|
173
|
+
await run({
|
|
174
|
+
db,
|
|
175
|
+
config,
|
|
176
|
+
rawConfig,
|
|
177
|
+
offline,
|
|
178
|
+
timeoutMs,
|
|
179
|
+
stale,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
finally {
|
|
183
|
+
await db.close().catch(() => undefined);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import type { SearchedNote } from './repo.js';
|
|
2
|
-
export declare const NOTE_SEARCH_FIELDS: readonly ["id", "title", "summary", "tags", "created_at", "
|
|
2
|
+
export declare const NOTE_SEARCH_FIELDS: readonly ["id", "title", "summary", "tags", "created_at", "boxes", "is_starred"];
|
|
3
3
|
export type NoteSearchField = (typeof NOTE_SEARCH_FIELDS)[number];
|
|
4
|
-
export type NoteSearchResultItem =
|
|
4
|
+
export type NoteSearchResultItem = {
|
|
5
|
+
id?: string;
|
|
6
|
+
title?: string;
|
|
7
|
+
summary?: string;
|
|
8
|
+
tags?: string[];
|
|
9
|
+
created_at?: string | null;
|
|
10
|
+
is_starred?: boolean;
|
|
11
|
+
boxes?: string[];
|
|
12
|
+
};
|
|
5
13
|
export declare function parseNoteSearchFields(value: unknown, optionName: string): NoteSearchField[] | undefined;
|
|
6
14
|
export declare function projectNoteSearchFields(notes: SearchedNote[], fields: NoteSearchField[] | undefined): NoteSearchResultItem[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DinoxError } from '../../utils/errors.js';
|
|
2
|
-
export const NOTE_SEARCH_FIELDS = ['id', 'title', 'summary', 'tags', 'created_at', '
|
|
2
|
+
export const NOTE_SEARCH_FIELDS = ['id', 'title', 'summary', 'tags', 'created_at', 'boxes', 'is_starred'];
|
|
3
3
|
const NOTE_SEARCH_FIELD_SET = new Set(NOTE_SEARCH_FIELDS);
|
|
4
4
|
export function parseNoteSearchFields(value, optionName) {
|
|
5
5
|
if (value === undefined || value === null || value === '') {
|
|
@@ -29,11 +29,23 @@ export function parseNoteSearchFields(value, optionName) {
|
|
|
29
29
|
}
|
|
30
30
|
export function projectNoteSearchFields(notes, fields) {
|
|
31
31
|
if (!fields || fields.length === 0) {
|
|
32
|
-
return notes.map((note) => ({
|
|
32
|
+
return notes.map((note) => ({
|
|
33
|
+
id: note.id,
|
|
34
|
+
title: note.title,
|
|
35
|
+
summary: note.summary,
|
|
36
|
+
tags: note.tags,
|
|
37
|
+
created_at: note.created_at,
|
|
38
|
+
is_starred: note.is_starred,
|
|
39
|
+
boxes: note.zettel_boxes,
|
|
40
|
+
}));
|
|
33
41
|
}
|
|
34
42
|
return notes.map((note) => {
|
|
35
43
|
const item = {};
|
|
36
44
|
for (const field of fields) {
|
|
45
|
+
if (field === 'boxes') {
|
|
46
|
+
item.boxes = note.zettel_boxes;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
37
49
|
item[field] = note[field];
|
|
38
50
|
}
|
|
39
51
|
return item;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { NoteSearchField, NoteSearchResultItem } from './searchOutput.js';
|
|
2
|
+
export type NoteSearchResponse = {
|
|
3
|
+
meta: {
|
|
4
|
+
query: string;
|
|
5
|
+
returned_count: number;
|
|
6
|
+
total_count: number;
|
|
7
|
+
stale: boolean;
|
|
8
|
+
pagination: {
|
|
9
|
+
limit: number | null;
|
|
10
|
+
offset: number;
|
|
11
|
+
has_more: boolean;
|
|
12
|
+
};
|
|
13
|
+
fields: NoteSearchField[] | null;
|
|
14
|
+
applied_filters: {
|
|
15
|
+
include_deleted: boolean;
|
|
16
|
+
tags_expression: string | null;
|
|
17
|
+
created_at_range: {
|
|
18
|
+
from: string | null;
|
|
19
|
+
to: string | null;
|
|
20
|
+
} | null;
|
|
21
|
+
days: number | null;
|
|
22
|
+
starred: boolean | null;
|
|
23
|
+
box_names: string[];
|
|
24
|
+
box_ids: string[];
|
|
25
|
+
match_empty_boxes: boolean;
|
|
26
|
+
sql_expression: string | null;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
notes: NoteSearchResultItem[];
|
|
30
|
+
};
|
|
31
|
+
export type BuildNoteSearchResponseInput = {
|
|
32
|
+
query: string;
|
|
33
|
+
notes: NoteSearchResultItem[];
|
|
34
|
+
totalCount: number;
|
|
35
|
+
stale: boolean;
|
|
36
|
+
includeDeleted: boolean;
|
|
37
|
+
limit?: number;
|
|
38
|
+
offset?: number;
|
|
39
|
+
fields?: NoteSearchField[];
|
|
40
|
+
tagsExpression?: string;
|
|
41
|
+
createdAtFrom?: string;
|
|
42
|
+
createdAtTo?: string;
|
|
43
|
+
days?: number;
|
|
44
|
+
starred?: boolean;
|
|
45
|
+
boxNames: string[];
|
|
46
|
+
boxIds: string[];
|
|
47
|
+
matchEmptyBoxes: boolean;
|
|
48
|
+
sqlExpression?: string;
|
|
49
|
+
};
|
|
50
|
+
export declare function normalizeDaysForMeta(value: unknown): number | undefined;
|
|
51
|
+
export declare function buildNoteSearchResponse(input: BuildNoteSearchResponseInput): NoteSearchResponse;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export function normalizeDaysForMeta(value) {
|
|
2
|
+
if (typeof value !== 'string') {
|
|
3
|
+
return undefined;
|
|
4
|
+
}
|
|
5
|
+
const trimmed = value.trim();
|
|
6
|
+
if (!/^\d+$/.test(trimmed)) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
const parsed = Number(trimmed);
|
|
10
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
return Math.trunc(parsed);
|
|
14
|
+
}
|
|
15
|
+
export function buildNoteSearchResponse(input) {
|
|
16
|
+
const query = input.query.trim();
|
|
17
|
+
const tagsExpression = input.tagsExpression?.trim() ?? '';
|
|
18
|
+
const sqlExpression = input.sqlExpression?.trim() ?? '';
|
|
19
|
+
const hasCreatedAtRange = Boolean(input.createdAtFrom || input.createdAtTo);
|
|
20
|
+
const offset = input.offset ?? 0;
|
|
21
|
+
const limit = input.limit ?? null;
|
|
22
|
+
const returnedCount = input.notes.length;
|
|
23
|
+
const totalCount = input.totalCount;
|
|
24
|
+
const hasMore = offset + returnedCount < totalCount;
|
|
25
|
+
return {
|
|
26
|
+
meta: {
|
|
27
|
+
query,
|
|
28
|
+
returned_count: returnedCount,
|
|
29
|
+
total_count: totalCount,
|
|
30
|
+
stale: input.stale,
|
|
31
|
+
pagination: {
|
|
32
|
+
limit,
|
|
33
|
+
offset,
|
|
34
|
+
has_more: hasMore,
|
|
35
|
+
},
|
|
36
|
+
fields: input.fields && input.fields.length > 0 ? [...input.fields] : null,
|
|
37
|
+
applied_filters: {
|
|
38
|
+
include_deleted: input.includeDeleted,
|
|
39
|
+
tags_expression: tagsExpression || null,
|
|
40
|
+
created_at_range: hasCreatedAtRange
|
|
41
|
+
? {
|
|
42
|
+
from: input.createdAtFrom ?? null,
|
|
43
|
+
to: input.createdAtTo ?? null,
|
|
44
|
+
}
|
|
45
|
+
: null,
|
|
46
|
+
days: input.days ?? null,
|
|
47
|
+
box_names: [...input.boxNames],
|
|
48
|
+
box_ids: [...input.boxIds],
|
|
49
|
+
match_empty_boxes: input.matchEmptyBoxes,
|
|
50
|
+
starred: typeof input.starred === 'boolean' ? input.starred : null,
|
|
51
|
+
sql_expression: sqlExpression || null,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
notes: input.notes,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -7,6 +7,7 @@ const SEARCH_FIELDS = new Set([
|
|
|
7
7
|
'zettel_boxes',
|
|
8
8
|
'created_at',
|
|
9
9
|
'type',
|
|
10
|
+
'is_starred',
|
|
10
11
|
]);
|
|
11
12
|
const FORBIDDEN_SQL_KEYWORDS = [
|
|
12
13
|
'insert',
|
|
@@ -31,6 +32,7 @@ const SCALAR_FIELD_COLUMNS = {
|
|
|
31
32
|
summary: 'summary',
|
|
32
33
|
created_at: 'created_at',
|
|
33
34
|
type: 'type',
|
|
35
|
+
is_starred: 'is_starred',
|
|
34
36
|
};
|
|
35
37
|
export async function buildNoteSearchSqlFilter(options) {
|
|
36
38
|
const expression = options.expression?.trim() ?? '';
|