@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.
- package/README.md +29 -9
- package/dist/auth/userInfo.d.ts +2 -0
- package/dist/auth/userInfo.js +55 -14
- package/dist/cli.js +36 -2
- package/dist/commands/auth/index.d.ts +35 -0
- package/dist/commands/auth/index.js +382 -83
- package/dist/commands/boxes/index.js +2 -6
- package/dist/commands/boxes/model.d.ts +3 -0
- package/dist/commands/boxes/model.js +16 -1
- package/dist/commands/boxes/operations.js +18 -8
- package/dist/commands/boxes/repo.js +13 -11
- package/dist/commands/boxes/store.js +1 -1
- package/dist/commands/config/index.js +7 -5
- package/dist/commands/descriptor/register.js +9 -0
- package/dist/commands/doctor.d.ts +123 -0
- package/dist/commands/doctor.js +539 -0
- package/dist/commands/graph/index.d.ts +1 -1
- package/dist/commands/graph/index.js +35 -21
- package/dist/commands/graph/repo.js +92 -17
- package/dist/commands/meta/index.js +4 -4
- package/dist/commands/meta/repo.js +4 -3
- package/dist/commands/notes/actions.d.ts +1 -1
- package/dist/commands/notes/actions.js +121 -78
- package/dist/commands/notes/bulk.d.ts +1 -0
- package/dist/commands/notes/bulk.js +23 -17
- package/dist/commands/notes/contentEngine.js +69 -11
- package/dist/commands/notes/contentPatch.d.ts +4 -1
- package/dist/commands/notes/contentPatch.js +105 -41
- package/dist/commands/notes/contentPatchCommand.js +28 -11
- package/dist/commands/notes/descriptors.js +50 -20
- package/dist/commands/notes/dryRun.d.ts +1 -0
- package/dist/commands/notes/dryRun.js +4 -7
- package/dist/commands/notes/export.d.ts +6 -0
- package/dist/commands/notes/export.js +26 -6
- package/dist/commands/notes/metadataMutation.d.ts +1 -0
- package/dist/commands/notes/metadataMutation.js +14 -3
- package/dist/commands/notes/repo.d.ts +46 -4
- package/dist/commands/notes/repo.js +113 -28
- package/dist/commands/notes/runtime.d.ts +2 -1
- package/dist/commands/notes/runtime.js +17 -1
- package/dist/commands/notes/searchSql.js +13 -6
- package/dist/commands/notes/writeReceipt.d.ts +20 -0
- package/dist/commands/notes/writeReceipt.js +82 -0
- package/dist/commands/prompt/index.js +3 -3
- package/dist/commands/prompt/repo.d.ts +4 -1
- package/dist/commands/prompt/repo.js +17 -7
- package/dist/commands/runtime.d.ts +14 -2
- package/dist/commands/runtime.js +74 -15
- package/dist/commands/schema/renderSkills.js +5 -2
- package/dist/commands/schema/spec.js +4 -0
- package/dist/commands/storage/index.js +8 -2
- package/dist/commands/storage/repo.d.ts +4 -0
- package/dist/commands/storage/repo.js +356 -92
- package/dist/commands/sync.js +16 -12
- package/dist/commands/tags/content.js +9 -0
- package/dist/commands/tags/index.js +2 -6
- package/dist/commands/tags/operations.js +15 -10
- package/dist/commands/tags/repo.d.ts +2 -0
- package/dist/commands/tags/repo.js +18 -5
- package/dist/commands/tags/store.js +1 -1
- package/dist/commands/todo/contentModel.d.ts +4 -3
- package/dist/commands/todo/contentModel.js +78 -28
- package/dist/commands/todo/dryRun.d.ts +1 -0
- package/dist/commands/todo/dryRun.js +17 -13
- package/dist/commands/todo/index.d.ts +6 -0
- package/dist/commands/todo/index.js +89 -123
- package/dist/commands/todo/repo.d.ts +36 -1
- package/dist/commands/todo/repo.js +146 -37
- package/dist/commands/todo/taskIdentity.d.ts +2 -0
- package/dist/commands/todo/taskIdentity.js +64 -0
- package/dist/commands/todo/taskModel.js +27 -17
- package/dist/commands/update.d.ts +13 -1
- package/dist/commands/update.js +135 -23
- package/dist/commands/writeDryRun.d.ts +3 -2
- package/dist/commands/writeDryRun.js +4 -3
- package/dist/commands/writeDurability.d.ts +5 -0
- package/dist/commands/writeDurability.js +22 -0
- package/dist/config/paths.js +6 -1
- package/dist/config/resolve.d.ts +2 -0
- package/dist/config/resolve.js +23 -9
- package/dist/config/store.d.ts +3 -0
- package/dist/config/store.js +61 -14
- package/dist/config/types.d.ts +4 -0
- package/dist/daemon/client.d.ts +26 -1
- package/dist/daemon/client.js +236 -12
- package/dist/daemon/ensure.js +74 -12
- package/dist/daemon/index.d.ts +42 -0
- package/dist/daemon/index.js +202 -52
- package/dist/daemon/lifecycle.d.ts +42 -0
- package/dist/daemon/lifecycle.js +115 -0
- package/dist/daemon/pid.d.ts +19 -6
- package/dist/daemon/pid.js +147 -13
- package/dist/daemon/route.d.ts +14 -0
- package/dist/daemon/route.js +40 -0
- package/dist/daemon/server.d.ts +11 -0
- package/dist/daemon/server.js +201 -50
- package/dist/daemon/types.d.ts +29 -1
- package/dist/daemon/types.js +1 -0
- package/dist/dinox.js +25 -1
- package/dist/powersync/connector.js +31 -16
- package/dist/powersync/exclusiveRuntime.d.ts +19 -0
- package/dist/powersync/exclusiveRuntime.js +42 -0
- package/dist/powersync/runtime.d.ts +24 -4
- package/dist/powersync/runtime.js +234 -36
- package/dist/powersync/syncGate.d.ts +3 -1
- package/dist/powersync/syncGate.js +7 -1
- package/dist/powersync/tokenIndex.d.ts +11 -0
- package/dist/powersync/tokenIndex.js +159 -22
- package/dist/powersync/uploader.js +107 -52
- package/dist/utils/abort.js +0 -4
- package/dist/utils/asyncRelease.d.ts +2 -0
- package/dist/utils/asyncRelease.js +25 -0
- package/dist/utils/bidlinkCanonicalization.js +3 -0
- package/dist/utils/dryRun.d.ts +1 -1
- package/dist/utils/errors.d.ts +15 -1
- package/dist/utils/errors.js +223 -5
- package/dist/utils/output.d.ts +1 -0
- package/dist/utils/output.js +51 -15
- package/dist/utils/redact.js +1 -1
- package/dist/utils/sqliteLease.d.ts +8 -0
- package/dist/utils/sqliteLease.js +59 -0
- package/dist/utils/syncTimeout.d.ts +7 -1
- package/dist/utils/syncTimeout.js +8 -7
- package/dist/utils/terminalSanitization.js +6 -2
- package/dist/utils/tiptapMarkdown.js +49 -3
- package/dist/utils/tiptapMarkdownSyntax.d.ts +9 -0
- package/dist/utils/tiptapMarkdownSyntax.js +249 -0
- package/dist/utils/tokenize.js +24 -2
- package/dist/utils/updateNotice.js +60 -12
- package/package.json +12 -10
- package/dist/commands/mcp/auth.d.ts +0 -4
- package/dist/commands/mcp/auth.js +0 -75
- package/dist/commands/mcp/index.d.ts +0 -2
- package/dist/commands/mcp/index.js +0 -86
- package/dist/commands/mcp/noteContentTools.d.ts +0 -222
- package/dist/commands/mcp/noteContentTools.js +0 -390
- package/dist/commands/mcp/server.d.ts +0 -34
- package/dist/commands/mcp/server.js +0 -424
- package/dist/commands/mcp/tools.d.ts +0 -2
- package/dist/commands/mcp/tools.js +0 -32
- package/dist/commands/mcp/types.d.ts +0 -39
- package/dist/commands/mcp/types.js +0 -1
- package/dist/utils/noteContentEngine.d.ts +0 -333
- package/dist/utils/noteContentEngine.js +0 -740
|
@@ -3,6 +3,7 @@ import { nowIsoUtc } from '../../utils/time.js';
|
|
|
3
3
|
import { tokenizeWithJieba } from '../../utils/tokenize.js';
|
|
4
4
|
import { canonicalizeBoxPath, listBoxes } from '../boxes/repo.js';
|
|
5
5
|
import { listTags } from '../tags/repo.js';
|
|
6
|
+
import { deriveImageDetailFromContentJson } from '../todo/contentModel.js';
|
|
6
7
|
import { buildNoteSearchSqlFilter } from './searchSql.js';
|
|
7
8
|
const MAX_SEARCH_SUMMARY_CHARS = 200;
|
|
8
9
|
function normalizeStoredStarred(value) {
|
|
@@ -14,7 +15,30 @@ function normalizeStoredStarred(value) {
|
|
|
14
15
|
}
|
|
15
16
|
return false;
|
|
16
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Resolve box names to ids and run the note search. Shared by the local command path and
|
|
20
|
+
* the daemon RPC handler so both produce identical results from one implementation.
|
|
21
|
+
*/
|
|
22
|
+
export async function executeNoteSearch(db, params) {
|
|
23
|
+
const zettelBoxIds = await resolveZettelBoxIdsForSearch(db, params.boxValues);
|
|
24
|
+
const result = await searchNotesPage(db, params.keyword, {
|
|
25
|
+
includeDeleted: params.includeDeleted,
|
|
26
|
+
tagsExpression: params.tagsExpression,
|
|
27
|
+
createdAtFrom: params.createdAtFrom,
|
|
28
|
+
createdAtTo: params.createdAtTo,
|
|
29
|
+
starred: params.starred,
|
|
30
|
+
zettelBoxIds,
|
|
31
|
+
matchEmptyZettelBoxes: params.matchEmptyZettelBoxes,
|
|
32
|
+
sqlExpression: params.sqlExpression,
|
|
33
|
+
limit: params.limit,
|
|
34
|
+
offset: params.offset,
|
|
35
|
+
});
|
|
36
|
+
return { rows: result.rows, totalCount: result.totalCount, zettelBoxIds };
|
|
37
|
+
}
|
|
17
38
|
export async function searchNotes(db, query, options) {
|
|
39
|
+
return (await searchNotesPage(db, query, options)).rows;
|
|
40
|
+
}
|
|
41
|
+
export async function searchNotesPage(db, query, options) {
|
|
18
42
|
const normalizedQuery = query.trim();
|
|
19
43
|
const tagFilter = buildTagFilter(options.tagsExpression, 'n');
|
|
20
44
|
const createdAtFilter = buildCreatedAtFilter(options.createdAtFrom, options.createdAtTo, 'n');
|
|
@@ -30,8 +54,11 @@ export async function searchNotes(db, query, options) {
|
|
|
30
54
|
const ftsMatch = buildFtsMatchQuery(normalizedQuery);
|
|
31
55
|
if (ftsMatch) {
|
|
32
56
|
try {
|
|
33
|
-
|
|
34
|
-
return
|
|
57
|
+
const ftsResult = await searchNotesWithFts(db, ftsMatch, tagFilter, createdAtFilter, starredFilter, zettelBoxFilter, sqlFilter, options.limit, options.offset);
|
|
58
|
+
return {
|
|
59
|
+
rows: await enrichSearchRows(db, ftsResult.rows),
|
|
60
|
+
totalCount: ftsResult.totalCount,
|
|
61
|
+
};
|
|
35
62
|
}
|
|
36
63
|
catch (error) {
|
|
37
64
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -54,14 +81,15 @@ export async function searchNotes(db, query, options) {
|
|
|
54
81
|
];
|
|
55
82
|
const params = [];
|
|
56
83
|
if (normalizedQuery.length > 0) {
|
|
57
|
-
const like = `%${normalizedQuery}%`;
|
|
84
|
+
const like = `%${escapeLikePattern(normalizedQuery)}%`;
|
|
58
85
|
whereClauses.push(`
|
|
59
86
|
(
|
|
60
|
-
(n.title IS NOT NULL AND n.title LIKE ?)
|
|
61
|
-
OR (n.content_text IS NOT NULL AND n.content_text LIKE ?)
|
|
87
|
+
(n.title IS NOT NULL AND n.title LIKE ? ESCAPE '\\')
|
|
88
|
+
OR (n.content_text IS NOT NULL AND n.content_text LIKE ? ESCAPE '\\')
|
|
89
|
+
OR (n.content_md IS NOT NULL AND n.content_md LIKE ? ESCAPE '\\')
|
|
62
90
|
)
|
|
63
91
|
`);
|
|
64
|
-
params.push(like, like);
|
|
92
|
+
params.push(like, like, like);
|
|
65
93
|
}
|
|
66
94
|
if (tagFilter) {
|
|
67
95
|
whereClauses.push(tagFilter.sql);
|
|
@@ -83,6 +111,15 @@ export async function searchNotes(db, query, options) {
|
|
|
83
111
|
whereClauses.push(sqlFilter.sql);
|
|
84
112
|
params.push(...sqlFilter.params);
|
|
85
113
|
}
|
|
114
|
+
const baseFromWhere = `
|
|
115
|
+
FROM c_note n
|
|
116
|
+
WHERE ${whereClauses.join('\n AND ')}
|
|
117
|
+
`;
|
|
118
|
+
const paginated = typeof options.limit === 'number';
|
|
119
|
+
const totalCount = paginated ? await countSearchRows(db, baseFromWhere, params) : null;
|
|
120
|
+
if (totalCount === 0) {
|
|
121
|
+
return { rows: [], totalCount };
|
|
122
|
+
}
|
|
86
123
|
rows = await db.getAll(`
|
|
87
124
|
SELECT
|
|
88
125
|
n.id,
|
|
@@ -93,13 +130,13 @@ export async function searchNotes(db, query, options) {
|
|
|
93
130
|
n.zettel_boxes,
|
|
94
131
|
n.created_at,
|
|
95
132
|
n.is_starred
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
`, params);
|
|
100
|
-
return enrichSearchRows(db, rows);
|
|
133
|
+
${baseFromWhere}
|
|
134
|
+
ORDER BY COALESCE(n.updated_at, n.created_at) DESC, n.id DESC
|
|
135
|
+
${buildPaginationSql(options.limit, options.offset)}
|
|
136
|
+
`, appendPaginationParams(params, options.limit, options.offset));
|
|
137
|
+
return { rows: await enrichSearchRows(db, rows), totalCount: totalCount ?? rows.length };
|
|
101
138
|
}
|
|
102
|
-
async function searchNotesWithFts(db, matchQuery, tagFilter, createdAtFilter, starredFilter, zettelBoxFilter, sqlFilter) {
|
|
139
|
+
async function searchNotesWithFts(db, matchQuery, tagFilter, createdAtFilter, starredFilter, zettelBoxFilter, sqlFilter, limit, offset) {
|
|
103
140
|
const whereClauses = ['(n.is_del IS NULL OR n.is_del = 0)', 'note_local_fts MATCH ?'];
|
|
104
141
|
const params = [matchQuery];
|
|
105
142
|
if (tagFilter) {
|
|
@@ -122,7 +159,17 @@ async function searchNotesWithFts(db, matchQuery, tagFilter, createdAtFilter, st
|
|
|
122
159
|
whereClauses.push(sqlFilter.sql);
|
|
123
160
|
params.push(...sqlFilter.params);
|
|
124
161
|
}
|
|
125
|
-
|
|
162
|
+
const baseFromWhere = `
|
|
163
|
+
FROM note_local_fts f
|
|
164
|
+
JOIN c_note n ON n.id = f.note_id
|
|
165
|
+
WHERE ${whereClauses.join('\n AND ')}
|
|
166
|
+
`;
|
|
167
|
+
const paginated = typeof limit === 'number';
|
|
168
|
+
const totalCount = paginated ? await countSearchRows(db, baseFromWhere, params) : null;
|
|
169
|
+
if (totalCount === 0) {
|
|
170
|
+
return { rows: [], totalCount };
|
|
171
|
+
}
|
|
172
|
+
const rows = await db.getAll(`
|
|
126
173
|
SELECT
|
|
127
174
|
n.id,
|
|
128
175
|
n.title,
|
|
@@ -132,11 +179,31 @@ async function searchNotesWithFts(db, matchQuery, tagFilter, createdAtFilter, st
|
|
|
132
179
|
n.zettel_boxes,
|
|
133
180
|
n.created_at,
|
|
134
181
|
n.is_starred
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
182
|
+
${baseFromWhere}
|
|
183
|
+
ORDER BY bm25(note_local_fts, 0.0, 8.0, 4.0, 1.0) ASC, COALESCE(n.updated_at, n.created_at) DESC, n.id DESC
|
|
184
|
+
${buildPaginationSql(limit, offset)}
|
|
185
|
+
`, appendPaginationParams(params, limit, offset));
|
|
186
|
+
return { rows, totalCount: totalCount ?? rows.length };
|
|
187
|
+
}
|
|
188
|
+
function escapeLikePattern(value) {
|
|
189
|
+
return value.replace(/[\\%_]/g, (match) => `\\${match}`);
|
|
190
|
+
}
|
|
191
|
+
async function countSearchRows(db, fromWhereSql, params) {
|
|
192
|
+
const rows = await db.getAll(`
|
|
193
|
+
SELECT COUNT(*) AS total_count
|
|
194
|
+
${fromWhereSql}
|
|
139
195
|
`, params);
|
|
196
|
+
const value = Number(rows[0]?.total_count ?? 0);
|
|
197
|
+
return Number.isFinite(value) && value > 0 ? Math.trunc(value) : 0;
|
|
198
|
+
}
|
|
199
|
+
function buildPaginationSql(limit, offset) {
|
|
200
|
+
return typeof limit === 'number' ? 'LIMIT ? OFFSET ?' : '';
|
|
201
|
+
}
|
|
202
|
+
function appendPaginationParams(params, limit, offset) {
|
|
203
|
+
if (typeof limit !== 'number') {
|
|
204
|
+
return params;
|
|
205
|
+
}
|
|
206
|
+
return [...params, limit, offset ?? 0];
|
|
140
207
|
}
|
|
141
208
|
async function enrichSearchRows(db, rows) {
|
|
142
209
|
const allBoxIds = Array.from(new Set(rows.flatMap((row) => parseZettelBoxIds(row.zettel_boxes))));
|
|
@@ -536,7 +603,7 @@ async function getLinkCount(db, direction, noteId) {
|
|
|
536
603
|
FROM c_note_link l
|
|
537
604
|
JOIN c_note n ON n.id = l.${column}
|
|
538
605
|
WHERE l.${boundColumn} = ?
|
|
539
|
-
AND l.is_del = 0
|
|
606
|
+
AND (l.is_del IS NULL OR l.is_del = 0)
|
|
540
607
|
AND (n.is_del IS NULL OR n.is_del = 0)
|
|
541
608
|
`, [noteId]);
|
|
542
609
|
return Number(row?.count ?? 0);
|
|
@@ -636,13 +703,16 @@ function mapNoteDetailRow(note, boxNameMap) {
|
|
|
636
703
|
return {
|
|
637
704
|
id: note.id,
|
|
638
705
|
created_at: note.created_at,
|
|
706
|
+
updated_at: note.updated_at,
|
|
639
707
|
title: note.title,
|
|
640
708
|
type: note.type,
|
|
641
709
|
source: note.source,
|
|
642
|
-
tags: note.tags,
|
|
710
|
+
tags: parseStoredTags(note.tags),
|
|
643
711
|
is_del: note.is_del,
|
|
644
712
|
is_starred: normalizeStoredStarred(note.is_starred),
|
|
645
713
|
content_md: note.content_md,
|
|
714
|
+
content_text: note.content_text,
|
|
715
|
+
content_json: parseStoredJsonValue(note.content_json),
|
|
646
716
|
zettel_boxes: zettelBoxes,
|
|
647
717
|
};
|
|
648
718
|
}
|
|
@@ -697,6 +767,7 @@ export async function getNoteDetails(db, ids) {
|
|
|
697
767
|
SELECT
|
|
698
768
|
id,
|
|
699
769
|
created_at,
|
|
770
|
+
updated_at,
|
|
700
771
|
title,
|
|
701
772
|
type,
|
|
702
773
|
source,
|
|
@@ -704,6 +775,8 @@ export async function getNoteDetails(db, ids) {
|
|
|
704
775
|
is_del,
|
|
705
776
|
is_starred,
|
|
706
777
|
content_md,
|
|
778
|
+
content_text,
|
|
779
|
+
content_json,
|
|
707
780
|
zettel_boxes AS zettel_boxes_raw
|
|
708
781
|
FROM c_note
|
|
709
782
|
WHERE id IN (${placeholders})
|
|
@@ -792,7 +865,9 @@ export async function createNote(db, input) {
|
|
|
792
865
|
const contentJson = JSON.stringify(input.contentJson);
|
|
793
866
|
const tags = JSON.stringify(input.tags);
|
|
794
867
|
const zettelBoxes = JSON.stringify(input.zettelBoxIds);
|
|
795
|
-
const imageDetail = JSON.stringify(Array.isArray(input.imageDetail)
|
|
868
|
+
const imageDetail = JSON.stringify(Array.isArray(input.imageDetail)
|
|
869
|
+
? input.imageDetail
|
|
870
|
+
: deriveImageDetailFromContentJson(input.contentJson, input.id));
|
|
796
871
|
const columns = await db.getAll('PRAGMA table_info(c_note)', []);
|
|
797
872
|
const columnNames = new Set(columns
|
|
798
873
|
.map((row) => row.name?.trim().toLowerCase() ?? '')
|
|
@@ -893,20 +968,24 @@ function buildZettelBoxRetryQuestion(intent, type) {
|
|
|
893
968
|
}
|
|
894
969
|
return `Do you want to add these missing boxes and retry \`${command}\`?`;
|
|
895
970
|
}
|
|
896
|
-
export async function validateTagsForCreate(db, requestedTags) {
|
|
897
|
-
return validateTagsForWrite(db, requestedTags, 'dino note create');
|
|
971
|
+
export async function validateTagsForCreate(db, requestedTags, userId) {
|
|
972
|
+
return validateTagsForWrite(db, requestedTags, userId, 'dino note create');
|
|
898
973
|
}
|
|
899
|
-
export async function validateTagsForUpdate(db, requestedTags) {
|
|
900
|
-
return validateTagsForWrite(db, requestedTags, 'dino note update');
|
|
974
|
+
export async function validateTagsForUpdate(db, requestedTags, userId) {
|
|
975
|
+
return validateTagsForWrite(db, requestedTags, userId, 'dino note update');
|
|
901
976
|
}
|
|
902
|
-
export async function validateTagsForBulk(db, requestedTags) {
|
|
903
|
-
return validateTagsForWrite(db, requestedTags, 'dino note bulk');
|
|
977
|
+
export async function validateTagsForBulk(db, requestedTags, userId) {
|
|
978
|
+
return validateTagsForWrite(db, requestedTags, userId, 'dino note bulk');
|
|
904
979
|
}
|
|
905
|
-
async function validateTagsForWrite(db, requestedTags, retryCommand) {
|
|
980
|
+
async function validateTagsForWrite(db, requestedTags, userId, retryCommand) {
|
|
981
|
+
const normalizedUserId = userId.trim();
|
|
982
|
+
if (!normalizedUserId) {
|
|
983
|
+
throw new DinoxError('Tag validation requires the current userId. Run `dino auth login` first, or set DINOX_TOKEN and run an online command so the CLI can resolve the token identity.');
|
|
984
|
+
}
|
|
906
985
|
if (requestedTags.length === 0) {
|
|
907
986
|
return [];
|
|
908
987
|
}
|
|
909
|
-
const tags = await listTags(db);
|
|
988
|
+
const tags = await listTags(db, { userId: normalizedUserId });
|
|
910
989
|
const existingTagMap = new Map();
|
|
911
990
|
for (const tag of tags) {
|
|
912
991
|
const key = normalizeLookupKey(tag);
|
|
@@ -1026,6 +1105,7 @@ export async function updateNote(db, input) {
|
|
|
1026
1105
|
return {
|
|
1027
1106
|
id,
|
|
1028
1107
|
updated: false,
|
|
1108
|
+
changed: [],
|
|
1029
1109
|
tags: currentTags,
|
|
1030
1110
|
zettelBoxIds: currentZettelBoxIds,
|
|
1031
1111
|
is_starred: currentStarred,
|
|
@@ -1057,6 +1137,11 @@ export async function updateNote(db, input) {
|
|
|
1057
1137
|
return {
|
|
1058
1138
|
id,
|
|
1059
1139
|
updated: true,
|
|
1140
|
+
changed: [
|
|
1141
|
+
...(tagsChanged ? ['tags'] : []),
|
|
1142
|
+
...(boxesChanged ? ['boxes'] : []),
|
|
1143
|
+
...(starredChanged ? ['is_starred'] : []),
|
|
1144
|
+
],
|
|
1060
1145
|
tags: nextTags,
|
|
1061
1146
|
zettelBoxIds: nextZettelBoxIds,
|
|
1062
1147
|
is_starred: nextStarred,
|
|
@@ -9,6 +9,7 @@ export declare function parseStarredOption(value: unknown, optionName: string):
|
|
|
9
9
|
export declare function parseStringInput(value: string): Promise<string>;
|
|
10
10
|
export declare function parseListInput(raw: string, optionName: string): string[];
|
|
11
11
|
export declare function parseStringListInput(value: string, optionName: string): Promise<string[]>;
|
|
12
|
+
export declare function parseMutationListInput(value: string, optionName: string): Promise<string[]>;
|
|
12
13
|
export type ParsedSearchListInput = {
|
|
13
14
|
values: string[];
|
|
14
15
|
isExplicitEmptyArray: boolean;
|
|
@@ -16,4 +17,4 @@ export type ParsedSearchListInput = {
|
|
|
16
17
|
export declare function parseSearchListInput(value: string, optionName: string): Promise<ParsedSearchListInput>;
|
|
17
18
|
export declare function parseTargetNoteIds(idArg: string | undefined, idsOption: string | undefined, commandName: 'detail' | 'update'): Promise<string[]>;
|
|
18
19
|
export declare function syncBeforeNoteCommand(runtime: CommandRuntime): Promise<void>;
|
|
19
|
-
export declare function withNoteDatabase(command: Command, run: (runtime: NoteCommandRuntime) => Promise<
|
|
20
|
+
export declare function withNoteDatabase<T = void>(command: Command, run: (runtime: NoteCommandRuntime) => Promise<T>): Promise<T>;
|
|
@@ -34,6 +34,14 @@ export function parseSearchOffset(value) {
|
|
|
34
34
|
if (value === undefined || value === null || value === '') {
|
|
35
35
|
return 0;
|
|
36
36
|
}
|
|
37
|
+
// Commander supplies the option's numeric defaultValue (0) as a number, while an
|
|
38
|
+
// explicit `--offset N` arrives as a string; accept both.
|
|
39
|
+
if (typeof value === 'number') {
|
|
40
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
41
|
+
throw new DinoxError('--offset must be a non-negative integer');
|
|
42
|
+
}
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
37
45
|
if (typeof value !== 'string') {
|
|
38
46
|
throw new DinoxError('--offset must be a non-negative integer');
|
|
39
47
|
}
|
|
@@ -114,6 +122,14 @@ export async function parseStringListInput(value, optionName) {
|
|
|
114
122
|
const raw = await readStringOrFile(value);
|
|
115
123
|
return parseListInput(raw, optionName);
|
|
116
124
|
}
|
|
125
|
+
export async function parseMutationListInput(value, optionName) {
|
|
126
|
+
const raw = await readStringOrFile(value);
|
|
127
|
+
const values = parseListInput(raw, optionName);
|
|
128
|
+
if (values.length === 0 && !isExplicitEmptyArrayLiteral(raw)) {
|
|
129
|
+
throw new DinoxError(`${optionName} requires at least one value. Use [] to explicitly clear the list.`);
|
|
130
|
+
}
|
|
131
|
+
return values;
|
|
132
|
+
}
|
|
117
133
|
export async function parseSearchListInput(value, optionName) {
|
|
118
134
|
const raw = await readStringOrFile(value);
|
|
119
135
|
return {
|
|
@@ -155,5 +171,5 @@ export async function syncBeforeNoteCommand(runtime) {
|
|
|
155
171
|
await syncBeforeCommand(runtime, { syncTokenIndex: true });
|
|
156
172
|
}
|
|
157
173
|
export async function withNoteDatabase(command, run) {
|
|
158
|
-
|
|
174
|
+
return withCommandRuntime(command, run);
|
|
159
175
|
}
|
|
@@ -430,13 +430,20 @@ async function buildZettelBoxIdLookup(ast, resolveZettelBoxNames) {
|
|
|
430
430
|
seen.add(key);
|
|
431
431
|
uniqueNames.push(raw);
|
|
432
432
|
}
|
|
433
|
-
const
|
|
434
|
-
if (ids.length !== uniqueNames.length) {
|
|
435
|
-
throw new DinoxError('Failed to resolve zettel box paths or names in --sql expression');
|
|
436
|
-
}
|
|
433
|
+
const batchIds = await resolveZettelBoxNames(uniqueNames);
|
|
437
434
|
const lookup = new Map();
|
|
438
|
-
|
|
439
|
-
|
|
435
|
+
if (batchIds.length === uniqueNames.length) {
|
|
436
|
+
for (let i = 0; i < uniqueNames.length; i += 1) {
|
|
437
|
+
lookup.set(normalizeLookupKey(uniqueNames[i]), batchIds[i]);
|
|
438
|
+
}
|
|
439
|
+
return lookup;
|
|
440
|
+
}
|
|
441
|
+
for (const name of uniqueNames) {
|
|
442
|
+
const ids = await resolveZettelBoxNames([name]);
|
|
443
|
+
if (ids.length !== 1) {
|
|
444
|
+
throw new DinoxError('Failed to resolve zettel box paths or names in --sql expression');
|
|
445
|
+
}
|
|
446
|
+
lookup.set(normalizeLookupKey(name), ids[0]);
|
|
440
447
|
}
|
|
441
448
|
return lookup;
|
|
442
449
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AbstractPowerSyncDatabase } from '@powersync/common';
|
|
2
|
+
import type { MutationUploadReceipt } from '../runtime.js';
|
|
3
|
+
export type NoteRevisionReceipt = {
|
|
4
|
+
version: number | null;
|
|
5
|
+
updated_at: string | null;
|
|
6
|
+
content_hash: string;
|
|
7
|
+
changed: string[];
|
|
8
|
+
};
|
|
9
|
+
export type NoteWriteReceipt = MutationUploadReceipt & NoteRevisionReceipt & {
|
|
10
|
+
stale: boolean;
|
|
11
|
+
};
|
|
12
|
+
export declare function readNoteRevisionReceipts(db: AbstractPowerSyncDatabase, ids: readonly string[], changesById: ReadonlyMap<string, readonly string[]>): Promise<Map<string, NoteRevisionReceipt>>;
|
|
13
|
+
export declare function readNoteWriteReceipt(db: AbstractPowerSyncDatabase, id: string, input: {
|
|
14
|
+
changed: readonly string[];
|
|
15
|
+
upload: MutationUploadReceipt;
|
|
16
|
+
stale: boolean;
|
|
17
|
+
}): Promise<NoteWriteReceipt>;
|
|
18
|
+
export declare function buildBatchWriteReceiptFields(upload: MutationUploadReceipt, stale: boolean): MutationUploadReceipt & {
|
|
19
|
+
stale: boolean;
|
|
20
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { DinoxError } from '../../utils/errors.js';
|
|
2
|
+
import { getNoteContentHash, parseStoredNoteDocument, } from './contentEngine.js';
|
|
3
|
+
const NOTE_RECEIPT_BATCH_SIZE = 500;
|
|
4
|
+
function normalizeVersion(value) {
|
|
5
|
+
const parsed = Number(value);
|
|
6
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
7
|
+
}
|
|
8
|
+
function normalizeChangedFields(fields) {
|
|
9
|
+
const result = [];
|
|
10
|
+
const seen = new Set();
|
|
11
|
+
for (const field of fields) {
|
|
12
|
+
const normalized = field.trim();
|
|
13
|
+
if (!normalized || seen.has(normalized)) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
seen.add(normalized);
|
|
17
|
+
result.push(normalized);
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
function toRevisionReceipt(row, changed) {
|
|
22
|
+
const document = parseStoredNoteDocument({
|
|
23
|
+
contentJson: row.content_json,
|
|
24
|
+
contentMd: row.content_md,
|
|
25
|
+
});
|
|
26
|
+
return {
|
|
27
|
+
version: normalizeVersion(row.version),
|
|
28
|
+
updated_at: row.updated_at ?? null,
|
|
29
|
+
content_hash: `sha256:${getNoteContentHash(document)}`,
|
|
30
|
+
changed: normalizeChangedFields(changed),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
async function loadNoteReceiptRows(db, ids) {
|
|
34
|
+
const rowsById = new Map();
|
|
35
|
+
const uniqueIds = Array.from(new Set(ids.map((id) => id.trim()).filter(Boolean)));
|
|
36
|
+
for (let index = 0; index < uniqueIds.length; index += NOTE_RECEIPT_BATCH_SIZE) {
|
|
37
|
+
const batch = uniqueIds.slice(index, index + NOTE_RECEIPT_BATCH_SIZE);
|
|
38
|
+
const placeholders = batch.map(() => '?').join(', ');
|
|
39
|
+
const rows = await db.getAll(`
|
|
40
|
+
SELECT id, content_md, content_json, updated_at, version
|
|
41
|
+
FROM c_note
|
|
42
|
+
WHERE id IN (${placeholders})
|
|
43
|
+
`, batch);
|
|
44
|
+
for (const row of rows) {
|
|
45
|
+
const id = row.id?.trim() ?? '';
|
|
46
|
+
if (id) {
|
|
47
|
+
rowsById.set(id, row);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return rowsById;
|
|
52
|
+
}
|
|
53
|
+
export async function readNoteRevisionReceipts(db, ids, changesById) {
|
|
54
|
+
const uniqueIds = Array.from(new Set(ids.map((id) => id.trim()).filter(Boolean)));
|
|
55
|
+
const rowsById = await loadNoteReceiptRows(db, uniqueIds);
|
|
56
|
+
const missing = uniqueIds.filter((id) => !rowsById.has(id));
|
|
57
|
+
if (missing.length > 0) {
|
|
58
|
+
throw new DinoxError(`Could not read note write receipt for: ${missing.join(', ')}`, {
|
|
59
|
+
code: 'PRECONDITION_FAILED',
|
|
60
|
+
suggestion: 'Run `dino doctor --format json` to inspect the local database, then retry the write.',
|
|
61
|
+
recoverable: true,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return new Map(uniqueIds.map((id) => [
|
|
65
|
+
id,
|
|
66
|
+
toRevisionReceipt(rowsById.get(id), changesById.get(id) ?? []),
|
|
67
|
+
]));
|
|
68
|
+
}
|
|
69
|
+
export async function readNoteWriteReceipt(db, id, input) {
|
|
70
|
+
const revisions = await readNoteRevisionReceipts(db, [id], new Map([[id, input.changed]]));
|
|
71
|
+
return {
|
|
72
|
+
...input.upload,
|
|
73
|
+
...revisions.get(id),
|
|
74
|
+
stale: input.stale,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export function buildBatchWriteReceiptFields(upload, stale) {
|
|
78
|
+
return {
|
|
79
|
+
...upload,
|
|
80
|
+
stale,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -5,13 +5,14 @@ import { buildPromptAddDryRunPreview } from '../writeDryRun.js';
|
|
|
5
5
|
import { printDryRunPreview } from '../../utils/dryRun.js';
|
|
6
6
|
import { DinoxError } from '../../utils/errors.js';
|
|
7
7
|
import { getRequestedStructuredOutputFormat, printHumanReadableLine, printSuccess, } from '../../utils/output.js';
|
|
8
|
-
import { commandStale, printStaleCacheWarning,
|
|
8
|
+
import { commandStale, printStaleCacheWarning, syncBeforeCommand, waitForMutationUpload, withCommandRuntime, } from '../runtime.js';
|
|
9
9
|
import { addPrompt, inspectAddPrompt, listPrompts } from './repo.js';
|
|
10
10
|
const PROMPT_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...REQUIRE_SYNC_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
|
|
11
11
|
async function runPromptList(context) {
|
|
12
12
|
await withCommandRuntime(context, async (runtime) => {
|
|
13
|
+
const identity = await resolveAuthIdentity(runtime.config);
|
|
13
14
|
await syncBeforeCommand(runtime, { syncTokenIndex: true });
|
|
14
|
-
printSuccess((await listPrompts(runtime.db)).map((prompt) => ({
|
|
15
|
+
printSuccess((await listPrompts(runtime.db, { userId: identity.userId })).map((prompt) => ({
|
|
15
16
|
name: prompt.name,
|
|
16
17
|
prompt: prompt.cmd,
|
|
17
18
|
})));
|
|
@@ -47,7 +48,6 @@ async function runPromptAdd(context) {
|
|
|
47
48
|
cmd,
|
|
48
49
|
userId: identity.userId,
|
|
49
50
|
});
|
|
50
|
-
printUploadDisabledWarning(runtime);
|
|
51
51
|
await waitForMutationUpload(runtime);
|
|
52
52
|
const payload = {
|
|
53
53
|
ok: true,
|
|
@@ -3,6 +3,9 @@ export type PromptListItem = {
|
|
|
3
3
|
name: string;
|
|
4
4
|
cmd: string;
|
|
5
5
|
};
|
|
6
|
+
export type ListPromptsInput = {
|
|
7
|
+
userId?: string;
|
|
8
|
+
};
|
|
6
9
|
export type AddPromptInput = {
|
|
7
10
|
name: string;
|
|
8
11
|
cmd: string;
|
|
@@ -20,6 +23,6 @@ export type InspectAddPromptResult = {
|
|
|
20
23
|
restored: boolean;
|
|
21
24
|
existingId?: string;
|
|
22
25
|
};
|
|
23
|
-
export declare function listPrompts(db: AbstractPowerSyncDatabase): Promise<PromptListItem[]>;
|
|
26
|
+
export declare function listPrompts(db: AbstractPowerSyncDatabase, input?: ListPromptsInput): Promise<PromptListItem[]>;
|
|
24
27
|
export declare function addPrompt(db: AbstractPowerSyncDatabase, input: AddPromptInput): Promise<AddPromptResult>;
|
|
25
28
|
export declare function inspectAddPrompt(db: AbstractPowerSyncDatabase, input: AddPromptInput): Promise<InspectAddPromptResult>;
|
|
@@ -10,7 +10,7 @@ async function getPromptColumns(db) {
|
|
|
10
10
|
const rows = await db.getAll('PRAGMA table_info(c_cmd)', []);
|
|
11
11
|
return new Set(rows.map((row) => row.name).filter(Boolean));
|
|
12
12
|
}
|
|
13
|
-
async function listPromptRowsForWrite(db, columnNames) {
|
|
13
|
+
async function listPromptRowsForWrite(db, columnNames, userId) {
|
|
14
14
|
const isDelExpr = columnNames.has('is_del') ? 'is_del' : 'NULL';
|
|
15
15
|
const userIdExpr = columnNames.has('user_id') ? `TRIM(COALESCE(user_id, ''))` : `''`;
|
|
16
16
|
const activeFirstExpr = columnNames.has('is_del')
|
|
@@ -18,6 +18,7 @@ async function listPromptRowsForWrite(db, columnNames) {
|
|
|
18
18
|
: '0';
|
|
19
19
|
const updateOrderExpr = columnNames.has('update_at') ? 'update_at DESC,' : '';
|
|
20
20
|
const insertOrderExpr = columnNames.has('insert_at') ? 'insert_at DESC,' : '';
|
|
21
|
+
const userFilter = columnNames.has('user_id') ? `AND TRIM(COALESCE(user_id, '')) = ?` : '';
|
|
21
22
|
return db.getAll(`
|
|
22
23
|
SELECT
|
|
23
24
|
TRIM(COALESCE(id, '')) AS id,
|
|
@@ -27,14 +28,21 @@ async function listPromptRowsForWrite(db, columnNames) {
|
|
|
27
28
|
${userIdExpr} AS user_id
|
|
28
29
|
FROM c_cmd
|
|
29
30
|
WHERE TRIM(COALESCE(id, '')) <> ''
|
|
31
|
+
${userFilter}
|
|
30
32
|
ORDER BY
|
|
31
33
|
${activeFirstExpr} ASC,
|
|
32
34
|
${updateOrderExpr}
|
|
33
35
|
${insertOrderExpr}
|
|
34
36
|
id DESC
|
|
35
|
-
`, []);
|
|
37
|
+
`, columnNames.has('user_id') ? [userId] : []);
|
|
36
38
|
}
|
|
37
|
-
export async function listPrompts(db) {
|
|
39
|
+
export async function listPrompts(db, input = {}) {
|
|
40
|
+
const userId = normalizeText(input.userId ?? '');
|
|
41
|
+
const columnNames = await getPromptColumns(db);
|
|
42
|
+
if (columnNames.has('user_id') && !userId) {
|
|
43
|
+
throw new DinoxError('c_cmd query requires user_id. Missing resolved userId. Run `dino auth login` first, or set DINOX_TOKEN and run an online command so the CLI can resolve the token identity.');
|
|
44
|
+
}
|
|
45
|
+
const userFilter = columnNames.has('user_id') ? `AND TRIM(COALESCE(user_id, '')) = ?` : '';
|
|
38
46
|
const rows = await db.getAll(`
|
|
39
47
|
SELECT DISTINCT
|
|
40
48
|
TRIM(COALESCE(name, '')) AS name,
|
|
@@ -43,8 +51,9 @@ export async function listPrompts(db) {
|
|
|
43
51
|
WHERE (is_del IS NULL OR is_del = 0)
|
|
44
52
|
AND TRIM(COALESCE(name, '')) <> ''
|
|
45
53
|
AND TRIM(COALESCE(cmd, '')) <> ''
|
|
54
|
+
${userFilter}
|
|
46
55
|
ORDER BY COALESCE(priority, 0) DESC, name COLLATE NOCASE ASC, cmd COLLATE NOCASE ASC
|
|
47
|
-
`, []);
|
|
56
|
+
`, columnNames.has('user_id') ? [userId] : []);
|
|
48
57
|
return rows
|
|
49
58
|
.map((row) => ({
|
|
50
59
|
name: row.name?.trim() ?? '',
|
|
@@ -73,7 +82,7 @@ export async function addPrompt(db, input) {
|
|
|
73
82
|
if (columnNames.has('user_id') && !userId) {
|
|
74
83
|
throw new DinoxError('c_cmd insert requires user_id. Missing resolved userId. Run `dino auth login` first, or set DINOX_TOKEN and run an online command so the CLI can resolve the token identity.');
|
|
75
84
|
}
|
|
76
|
-
const rows = await listPromptRowsForWrite(db, columnNames);
|
|
85
|
+
const rows = await listPromptRowsForWrite(db, columnNames, userId);
|
|
77
86
|
const samePromptRows = rows.filter((row) => {
|
|
78
87
|
return normalizeLookup(row.name ?? '') === nameLookup && normalizeLookup(row.cmd ?? '') === cmdLookup;
|
|
79
88
|
});
|
|
@@ -113,7 +122,8 @@ export async function addPrompt(db, input) {
|
|
|
113
122
|
UPDATE c_cmd
|
|
114
123
|
SET ${setParts.join(', ')}
|
|
115
124
|
WHERE id = ?
|
|
116
|
-
|
|
125
|
+
${columnNames.has('user_id') ? `AND TRIM(COALESCE(user_id, '')) = ?` : ''}
|
|
126
|
+
`, columnNames.has('user_id') ? [...params, userId] : params);
|
|
117
127
|
}
|
|
118
128
|
return {
|
|
119
129
|
id: deletedRow.id,
|
|
@@ -183,7 +193,7 @@ export async function inspectAddPrompt(db, input) {
|
|
|
183
193
|
if (columnNames.has('user_id') && !userId) {
|
|
184
194
|
throw new DinoxError('c_cmd insert requires user_id. Missing resolved userId. Run `dino auth login` first, or set DINOX_TOKEN and run an online command so the CLI can resolve the token identity.');
|
|
185
195
|
}
|
|
186
|
-
const rows = await listPromptRowsForWrite(db, columnNames);
|
|
196
|
+
const rows = await listPromptRowsForWrite(db, columnNames, userId);
|
|
187
197
|
const samePromptRows = rows.filter((row) => {
|
|
188
198
|
return normalizeLookup(row.name ?? '') === nameLookup && normalizeLookup(row.cmd ?? '') === cmdLookup;
|
|
189
199
|
});
|
|
@@ -4,10 +4,21 @@ import { loadConfig } from '../config/store.js';
|
|
|
4
4
|
import type { ResolvedDinoxConfig } from '../config/types.js';
|
|
5
5
|
import { type SyncGateResult } from '../powersync/syncGate.js';
|
|
6
6
|
import type { CommandExecutionContext } from './descriptor/types.js';
|
|
7
|
+
import { type WriteDurability } from './writeDurability.js';
|
|
8
|
+
export type MutationUploadReceipt = {
|
|
9
|
+
durability: WriteDurability;
|
|
10
|
+
upload_queue_remaining: number | null;
|
|
11
|
+
upload_error?: {
|
|
12
|
+
name: string;
|
|
13
|
+
message: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
7
16
|
export type CommandSyncState = {
|
|
8
17
|
firstSyncStale: boolean;
|
|
9
18
|
preSyncIdle: boolean | null;
|
|
19
|
+
recentSync: boolean | null;
|
|
10
20
|
gate: SyncGateResult | null;
|
|
21
|
+
startedAtIso: string;
|
|
11
22
|
};
|
|
12
23
|
export type CommandRuntime = {
|
|
13
24
|
db: AbstractPowerSyncDatabase;
|
|
@@ -21,6 +32,7 @@ export type CommandRuntime = {
|
|
|
21
32
|
};
|
|
22
33
|
type RuntimeSource = CommandExecutionContext | Command | Record<string, unknown>;
|
|
23
34
|
export declare function commandStale(runtime: Pick<CommandRuntime, 'offline' | 'stale'>): boolean;
|
|
35
|
+
export declare function resolveWriteDurabilityForRuntime(runtime: Pick<CommandRuntime, 'offline'>, value: unknown): WriteDurability;
|
|
24
36
|
export declare function withCommandRuntime<T>(source: RuntimeSource, run: (runtime: CommandRuntime) => Promise<T>): Promise<T>;
|
|
25
37
|
export declare function syncBeforeCommand(runtime: CommandRuntime, options?: {
|
|
26
38
|
syncTokenIndex?: boolean;
|
|
@@ -28,7 +40,7 @@ export declare function syncBeforeCommand(runtime: CommandRuntime, options?: {
|
|
|
28
40
|
}): Promise<void>;
|
|
29
41
|
export declare function waitForMutationUpload(runtime: CommandRuntime, options?: {
|
|
30
42
|
timeoutMs?: number;
|
|
31
|
-
|
|
32
|
-
|
|
43
|
+
durability?: unknown;
|
|
44
|
+
}): Promise<MutationUploadReceipt>;
|
|
33
45
|
export declare function printStaleCacheWarning(runtime: CommandRuntime, message?: string): void;
|
|
34
46
|
export {};
|