@dinoxx/dinox-cli 1.0.19 → 1.0.21
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 +756 -472
- package/dist/auth/userInfo.d.ts +10 -1
- package/dist/auth/userInfo.js +66 -17
- package/dist/cli.js +0 -2
- package/dist/commands/auth/index.js +29 -14
- package/dist/commands/boxes/index.js +272 -52
- package/dist/commands/boxes/model.d.ts +34 -0
- package/dist/commands/boxes/model.js +78 -0
- package/dist/commands/boxes/operations.d.ts +86 -0
- package/dist/commands/boxes/operations.js +415 -0
- package/dist/commands/boxes/repo.d.ts +13 -6
- package/dist/commands/boxes/repo.js +272 -149
- package/dist/commands/boxes/store.d.ts +22 -0
- package/dist/commands/boxes/store.js +143 -0
- package/dist/commands/graph/index.js +2 -36
- package/dist/commands/mcp/noteContentTools.d.ts +222 -0
- package/dist/commands/mcp/noteContentTools.js +390 -0
- package/dist/commands/mcp/server.d.ts +3 -0
- package/dist/commands/mcp/server.js +45 -16
- package/dist/commands/mcp/tools.js +3 -1
- package/dist/commands/mcp/types.d.ts +6 -2
- package/dist/commands/meta/index.js +6 -22
- package/dist/commands/meta/repo.d.ts +0 -3
- package/dist/commands/meta/repo.js +1 -4
- package/dist/commands/notes/actions.d.ts +2 -0
- package/dist/commands/notes/actions.js +149 -55
- package/dist/commands/notes/bulk.d.ts +34 -0
- package/dist/commands/notes/bulk.js +204 -0
- package/dist/commands/notes/contentEngine.d.ts +95 -0
- package/dist/commands/notes/contentEngine.js +652 -0
- package/dist/commands/notes/contentPatch.d.ts +84 -0
- package/dist/commands/notes/contentPatch.js +337 -0
- package/dist/commands/notes/contentPatchCommand.d.ts +3 -0
- package/dist/commands/notes/contentPatchCommand.js +126 -0
- package/dist/commands/notes/descriptors.js +192 -40
- package/dist/commands/notes/export.d.ts +21 -0
- package/dist/commands/notes/export.js +224 -0
- package/dist/commands/notes/metadataMutation.d.ts +28 -0
- package/dist/commands/notes/metadataMutation.js +127 -0
- package/dist/commands/notes/repo.d.ts +20 -0
- package/dist/commands/notes/repo.js +135 -18
- package/dist/commands/notes/runtime.d.ts +3 -12
- package/dist/commands/notes/runtime.js +4 -30
- package/dist/commands/notes/searchSql.js +1 -1
- package/dist/commands/prompt/index.js +15 -45
- package/dist/commands/prompt/repo.js +2 -2
- package/dist/commands/runtime.d.ts +26 -0
- package/dist/commands/runtime.js +76 -0
- package/dist/commands/schema/renderSkills.js +20 -7
- package/dist/commands/schema/spec.d.ts +1 -2
- package/dist/commands/schema/spec.js +2 -3
- package/dist/commands/schema/types.d.ts +0 -13
- package/dist/commands/storage/index.js +30 -60
- package/dist/commands/storage/repo.d.ts +1 -1
- package/dist/commands/storage/repo.js +20 -1
- package/dist/commands/sync.js +4 -2
- package/dist/commands/tags/content.d.ts +32 -0
- package/dist/commands/tags/content.js +243 -0
- package/dist/commands/tags/index.js +285 -49
- package/dist/commands/tags/model.d.ts +38 -0
- package/dist/commands/tags/model.js +100 -0
- package/dist/commands/tags/operations.d.ts +105 -0
- package/dist/commands/tags/operations.js +438 -0
- package/dist/commands/tags/repo.d.ts +5 -1
- package/dist/commands/tags/repo.js +142 -218
- package/dist/commands/tags/store.d.ts +28 -0
- package/dist/commands/tags/store.js +166 -0
- package/dist/commands/todo/index.js +30 -61
- package/dist/commands/writeDryRun.d.ts +25 -0
- package/dist/commands/writeDryRun.js +31 -1
- package/dist/config/paths.d.ts +2 -1
- package/dist/config/paths.js +11 -1
- package/dist/config/resolve.js +9 -7
- package/dist/daemon/index.d.ts +2 -0
- package/dist/daemon/index.js +176 -58
- package/dist/powersync/runtime.d.ts +2 -1
- package/dist/powersync/runtime.js +15 -3
- package/dist/powersync/schema/content.d.ts +2 -0
- package/dist/powersync/schema/content.js +2 -0
- package/dist/powersync/schema/index.d.ts +2 -0
- package/dist/skills/check.d.ts +1 -1
- package/dist/skills/check.js +27 -2
- package/dist/utils/authToken.d.ts +2 -0
- package/dist/utils/authToken.js +7 -2
- package/dist/utils/noteContentEngine.d.ts +333 -0
- package/dist/utils/noteContentEngine.js +740 -0
- package/dist/utils/tiptapMarkdownSchema.js +31 -0
- package/package.json +4 -8
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { withPowerSyncWriteTransaction } from '../../powersync/runtime.js';
|
|
2
|
+
import { DinoxError } from '../../utils/errors.js';
|
|
3
|
+
import { nowIsoUtc } from '../../utils/time.js';
|
|
4
|
+
import { parseStoredTags, parseZettelBoxIds } from './repo.js';
|
|
5
|
+
function normalizeDistinct(values) {
|
|
6
|
+
const result = [];
|
|
7
|
+
const seen = new Set();
|
|
8
|
+
for (const value of values) {
|
|
9
|
+
const normalized = value.trim();
|
|
10
|
+
const key = normalized.toLowerCase();
|
|
11
|
+
if (!normalized || seen.has(key)) {
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
seen.add(key);
|
|
15
|
+
result.push(normalized);
|
|
16
|
+
}
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
function sameOrdered(left, right) {
|
|
20
|
+
if (left.length !== right.length) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
return left.every((value, index) => value === right[index]);
|
|
24
|
+
}
|
|
25
|
+
function keySet(values) {
|
|
26
|
+
return new Set(values.map((value) => value.trim().toLowerCase()).filter(Boolean));
|
|
27
|
+
}
|
|
28
|
+
function applyListOperation(current, values, operation) {
|
|
29
|
+
const normalizedValues = normalizeDistinct(values);
|
|
30
|
+
if (operation === 'replace') {
|
|
31
|
+
return normalizedValues;
|
|
32
|
+
}
|
|
33
|
+
if (operation === 'add') {
|
|
34
|
+
return normalizeDistinct([...current, ...normalizedValues]);
|
|
35
|
+
}
|
|
36
|
+
const removeKeys = keySet(normalizedValues);
|
|
37
|
+
return current.filter((value) => !removeKeys.has(value.trim().toLowerCase()));
|
|
38
|
+
}
|
|
39
|
+
function assertMutationInput(input) {
|
|
40
|
+
const ids = normalizeDistinct(input.ids);
|
|
41
|
+
if (ids.length === 0) {
|
|
42
|
+
throw new DinoxError('At least one note id is required');
|
|
43
|
+
}
|
|
44
|
+
const values = normalizeDistinct(input.values);
|
|
45
|
+
if (values.length === 0 && input.operation !== 'replace') {
|
|
46
|
+
const option = input.operation === 'add' ? '--add' : '--remove';
|
|
47
|
+
throw new DinoxError(`${option} requires at least one ${input.field === 'tags' ? 'tag' : 'box'}`);
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
ids,
|
|
51
|
+
values,
|
|
52
|
+
displayValues: normalizeDistinct(input.displayValues ?? input.values),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
async function loadNoteMetadataState(db, ids) {
|
|
56
|
+
const rowsById = new Map();
|
|
57
|
+
const chunkSize = 500;
|
|
58
|
+
for (let index = 0; index < ids.length; index += chunkSize) {
|
|
59
|
+
const batch = ids.slice(index, index + chunkSize);
|
|
60
|
+
const placeholders = batch.map(() => '?').join(', ');
|
|
61
|
+
const rows = await db.getAll(`
|
|
62
|
+
SELECT id, title, tags, zettel_boxes, is_del
|
|
63
|
+
FROM c_note
|
|
64
|
+
WHERE id IN (${placeholders})
|
|
65
|
+
`, batch);
|
|
66
|
+
for (const row of rows) {
|
|
67
|
+
const id = row.id?.trim() ?? '';
|
|
68
|
+
if (!id || rowsById.has(id)) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
rowsById.set(id, row);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return rowsById;
|
|
75
|
+
}
|
|
76
|
+
export async function planNoteMetadataMutation(db, input) {
|
|
77
|
+
const { ids, values, displayValues } = assertMutationInput(input);
|
|
78
|
+
const stateById = await loadNoteMetadataState(db, ids);
|
|
79
|
+
const missing = ids.filter((id) => !stateById.has(id));
|
|
80
|
+
if (missing.length > 0) {
|
|
81
|
+
throw new DinoxError(`Notes not found: ${missing.join(', ')}`);
|
|
82
|
+
}
|
|
83
|
+
const results = ids.map((id) => {
|
|
84
|
+
const note = stateById.get(id);
|
|
85
|
+
if (typeof note.is_del === 'number' && note.is_del !== 0) {
|
|
86
|
+
throw new DinoxError(`Note has been deleted: ${id}`);
|
|
87
|
+
}
|
|
88
|
+
const before = input.field === 'tags'
|
|
89
|
+
? parseStoredTags(note.tags)
|
|
90
|
+
: parseZettelBoxIds(note.zettel_boxes);
|
|
91
|
+
const after = applyListOperation(before, values, input.operation);
|
|
92
|
+
return {
|
|
93
|
+
id,
|
|
94
|
+
title: note.title ?? '',
|
|
95
|
+
before,
|
|
96
|
+
after,
|
|
97
|
+
changed: !sameOrdered(before, after),
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
return {
|
|
101
|
+
field: input.field,
|
|
102
|
+
operation: input.operation,
|
|
103
|
+
values,
|
|
104
|
+
displayValues,
|
|
105
|
+
total: results.length,
|
|
106
|
+
changedCount: results.filter((item) => item.changed).length,
|
|
107
|
+
results,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
export async function applyNoteMetadataMutation(db, plan) {
|
|
111
|
+
const changed = plan.results.filter((item) => item.changed);
|
|
112
|
+
if (changed.length === 0) {
|
|
113
|
+
return plan;
|
|
114
|
+
}
|
|
115
|
+
await withPowerSyncWriteTransaction(db, async (tx) => {
|
|
116
|
+
const timestamp = nowIsoUtc();
|
|
117
|
+
for (const item of changed) {
|
|
118
|
+
const column = plan.field === 'tags' ? 'tags' : 'zettel_boxes';
|
|
119
|
+
await tx.execute(`
|
|
120
|
+
UPDATE c_note
|
|
121
|
+
SET ${column} = ?, updated_at = ?, version = COALESCE(version, 0) + 1
|
|
122
|
+
WHERE id = ?
|
|
123
|
+
`, [JSON.stringify(item.after), timestamp, item.id]);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
return plan;
|
|
127
|
+
}
|
|
@@ -45,6 +45,23 @@ export type NoteDetail = {
|
|
|
45
45
|
content_md: string | null;
|
|
46
46
|
zettel_boxes: string[];
|
|
47
47
|
};
|
|
48
|
+
export type NoteExportItem = {
|
|
49
|
+
id: string;
|
|
50
|
+
created_at: string | null;
|
|
51
|
+
updated_at: string | null;
|
|
52
|
+
title: string | null;
|
|
53
|
+
type: string | null;
|
|
54
|
+
source: string | null;
|
|
55
|
+
summary: string | null;
|
|
56
|
+
tags: string[];
|
|
57
|
+
is_del: number | null;
|
|
58
|
+
is_starred: boolean;
|
|
59
|
+
content_md: string;
|
|
60
|
+
content_text: string | null;
|
|
61
|
+
content_json: unknown;
|
|
62
|
+
box_ids: string[];
|
|
63
|
+
boxes: string[];
|
|
64
|
+
};
|
|
48
65
|
export type NoteContext = {
|
|
49
66
|
id: string;
|
|
50
67
|
title: string;
|
|
@@ -84,6 +101,7 @@ export declare function getNoteContext(db: AbstractPowerSyncDatabase, id: string
|
|
|
84
101
|
export declare function getNotePreview(db: AbstractPowerSyncDatabase, id: string, requestedLines: number): Promise<NotePreview | null>;
|
|
85
102
|
export declare function getNoteDetails(db: AbstractPowerSyncDatabase, ids: string[]): Promise<NoteDetail[]>;
|
|
86
103
|
export declare function getNoteDetail(db: AbstractPowerSyncDatabase, id: string): Promise<NoteDetail | null>;
|
|
104
|
+
export declare function getNoteExportItems(db: AbstractPowerSyncDatabase, ids: string[]): Promise<NoteExportItem[]>;
|
|
87
105
|
export declare function createNote(db: AbstractPowerSyncDatabase, input: {
|
|
88
106
|
id: string;
|
|
89
107
|
title: string;
|
|
@@ -100,6 +118,7 @@ export declare function createNote(db: AbstractPowerSyncDatabase, input: {
|
|
|
100
118
|
}>;
|
|
101
119
|
export declare function validateTagsForCreate(db: AbstractPowerSyncDatabase, requestedTags: string[]): Promise<string[]>;
|
|
102
120
|
export declare function validateTagsForUpdate(db: AbstractPowerSyncDatabase, requestedTags: string[]): Promise<string[]>;
|
|
121
|
+
export declare function validateTagsForBulk(db: AbstractPowerSyncDatabase, requestedTags: string[]): Promise<string[]>;
|
|
103
122
|
export declare function parseStoredTags(raw: string | null): string[];
|
|
104
123
|
export declare function updateNote(db: AbstractPowerSyncDatabase, input: {
|
|
105
124
|
id: string;
|
|
@@ -111,5 +130,6 @@ export declare function assertNotesUpdatable(db: AbstractPowerSyncDatabase, note
|
|
|
111
130
|
export declare function resolveZettelBoxIdsForCreate(db: AbstractPowerSyncDatabase, requestedNames: string[]): Promise<string[]>;
|
|
112
131
|
export declare function resolveZettelBoxIdsForSearch(db: AbstractPowerSyncDatabase, requestedNames: string[]): Promise<string[]>;
|
|
113
132
|
export declare function resolveZettelBoxIdsForUpdate(db: AbstractPowerSyncDatabase, requestedNames: string[]): Promise<string[]>;
|
|
133
|
+
export declare function resolveZettelBoxIdsForBulk(db: AbstractPowerSyncDatabase, requestedNames: string[]): Promise<string[]>;
|
|
114
134
|
export declare function softDeleteNote(db: AbstractPowerSyncDatabase, id: string): Promise<void>;
|
|
115
135
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DinoxError } from '../../utils/errors.js';
|
|
2
2
|
import { nowIsoUtc } from '../../utils/time.js';
|
|
3
3
|
import { tokenizeWithJieba } from '../../utils/tokenize.js';
|
|
4
|
-
import { listBoxes } from '../boxes/repo.js';
|
|
4
|
+
import { canonicalizeBoxPath, 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;
|
|
@@ -199,7 +199,7 @@ async function loadZettelBoxNameMap(db, ids) {
|
|
|
199
199
|
const batch = ids.slice(i, i + chunkSize);
|
|
200
200
|
const placeholders = batch.map(() => '?').join(', ');
|
|
201
201
|
const rows = await db.getAll(`
|
|
202
|
-
SELECT id, name
|
|
202
|
+
SELECT id, name, path
|
|
203
203
|
FROM c_zettel_box
|
|
204
204
|
WHERE (is_del IS NULL OR is_del = 0)
|
|
205
205
|
AND id IN (${placeholders})
|
|
@@ -209,7 +209,8 @@ async function loadZettelBoxNameMap(db, ids) {
|
|
|
209
209
|
if (!id) {
|
|
210
210
|
continue;
|
|
211
211
|
}
|
|
212
|
-
|
|
212
|
+
const path = canonicalizeBoxPath(row.path ?? row.name ?? '');
|
|
213
|
+
map.set(id, path || row.name?.trim() || '');
|
|
213
214
|
}
|
|
214
215
|
}
|
|
215
216
|
return map;
|
|
@@ -645,6 +646,41 @@ function mapNoteDetailRow(note, boxNameMap) {
|
|
|
645
646
|
zettel_boxes: zettelBoxes,
|
|
646
647
|
};
|
|
647
648
|
}
|
|
649
|
+
function parseStoredJsonValue(raw) {
|
|
650
|
+
const value = raw?.trim() ?? '';
|
|
651
|
+
if (!value) {
|
|
652
|
+
return null;
|
|
653
|
+
}
|
|
654
|
+
try {
|
|
655
|
+
return JSON.parse(value);
|
|
656
|
+
}
|
|
657
|
+
catch {
|
|
658
|
+
return raw;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
function mapNoteExportRow(note, boxNameMap) {
|
|
662
|
+
const boxIds = parseZettelBoxIds(note.zettel_boxes_raw);
|
|
663
|
+
const boxes = boxIds
|
|
664
|
+
.map((boxId) => boxNameMap.get(boxId))
|
|
665
|
+
.filter((name) => typeof name === 'string' && name.length > 0);
|
|
666
|
+
return {
|
|
667
|
+
id: note.id,
|
|
668
|
+
created_at: note.created_at,
|
|
669
|
+
updated_at: note.updated_at,
|
|
670
|
+
title: note.title,
|
|
671
|
+
type: note.type,
|
|
672
|
+
source: note.source,
|
|
673
|
+
summary: note.summary,
|
|
674
|
+
tags: parseStoredTags(note.tags),
|
|
675
|
+
is_del: note.is_del,
|
|
676
|
+
is_starred: normalizeStoredStarred(note.is_starred),
|
|
677
|
+
content_md: note.content_md ?? '',
|
|
678
|
+
content_text: note.content_text,
|
|
679
|
+
content_json: parseStoredJsonValue(note.content_json),
|
|
680
|
+
box_ids: boxIds,
|
|
681
|
+
boxes,
|
|
682
|
+
};
|
|
683
|
+
}
|
|
648
684
|
export async function getNoteDetails(db, ids) {
|
|
649
685
|
const targetIds = Array.from(new Set(ids
|
|
650
686
|
.map((id) => id.trim())
|
|
@@ -696,6 +732,57 @@ export async function getNoteDetail(db, id) {
|
|
|
696
732
|
const details = await getNoteDetails(db, [id]);
|
|
697
733
|
return details[0] ?? null;
|
|
698
734
|
}
|
|
735
|
+
export async function getNoteExportItems(db, ids) {
|
|
736
|
+
const targetIds = Array.from(new Set(ids
|
|
737
|
+
.map((id) => id.trim())
|
|
738
|
+
.filter((id) => id.length > 0)));
|
|
739
|
+
if (targetIds.length === 0) {
|
|
740
|
+
return [];
|
|
741
|
+
}
|
|
742
|
+
const noteRowsById = new Map();
|
|
743
|
+
const chunkSize = 500;
|
|
744
|
+
for (let index = 0; index < targetIds.length; index += chunkSize) {
|
|
745
|
+
const batch = targetIds.slice(index, index + chunkSize);
|
|
746
|
+
const placeholders = batch.map(() => '?').join(', ');
|
|
747
|
+
const rows = await db.getAll(`
|
|
748
|
+
SELECT
|
|
749
|
+
id,
|
|
750
|
+
created_at,
|
|
751
|
+
updated_at,
|
|
752
|
+
title,
|
|
753
|
+
type,
|
|
754
|
+
source,
|
|
755
|
+
tags,
|
|
756
|
+
summary,
|
|
757
|
+
is_del,
|
|
758
|
+
is_starred,
|
|
759
|
+
content_md,
|
|
760
|
+
content_text,
|
|
761
|
+
content_json,
|
|
762
|
+
zettel_boxes AS zettel_boxes_raw
|
|
763
|
+
FROM c_note
|
|
764
|
+
WHERE id IN (${placeholders})
|
|
765
|
+
`, batch);
|
|
766
|
+
for (const row of rows) {
|
|
767
|
+
const id = row.id?.trim() ?? '';
|
|
768
|
+
if (!id || noteRowsById.has(id)) {
|
|
769
|
+
continue;
|
|
770
|
+
}
|
|
771
|
+
noteRowsById.set(id, row);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
const allBoxIds = Array.from(new Set(Array.from(noteRowsById.values()).flatMap((row) => parseZettelBoxIds(row.zettel_boxes_raw))));
|
|
775
|
+
const boxNameMap = await loadZettelBoxNameMap(db, allBoxIds);
|
|
776
|
+
const items = [];
|
|
777
|
+
for (const id of targetIds) {
|
|
778
|
+
const row = noteRowsById.get(id);
|
|
779
|
+
if (!row) {
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
items.push(mapNoteExportRow(row, boxNameMap));
|
|
783
|
+
}
|
|
784
|
+
return items;
|
|
785
|
+
}
|
|
699
786
|
export async function createNote(db, input) {
|
|
700
787
|
const userId = input.userId.trim();
|
|
701
788
|
if (!userId) {
|
|
@@ -790,14 +877,19 @@ export async function createNote(db, input) {
|
|
|
790
877
|
function normalizeLookupKey(value) {
|
|
791
878
|
return value.trim().toLowerCase();
|
|
792
879
|
}
|
|
880
|
+
function normalizeZettelBoxLookupKey(value) {
|
|
881
|
+
return canonicalizeBoxPath(value).toLowerCase();
|
|
882
|
+
}
|
|
793
883
|
function buildZettelBoxRetryQuestion(intent, type) {
|
|
794
884
|
const command = intent === 'search'
|
|
795
885
|
? 'dino note search'
|
|
796
886
|
: intent === 'update'
|
|
797
887
|
? 'dino note update'
|
|
798
|
-
:
|
|
888
|
+
: intent === 'bulk'
|
|
889
|
+
? 'dino note bulk'
|
|
890
|
+
: 'dino note create';
|
|
799
891
|
if (type === 'ambiguous') {
|
|
800
|
-
return `Do you want to specify
|
|
892
|
+
return `Do you want to specify box paths and retry \`${command}\`?`;
|
|
801
893
|
}
|
|
802
894
|
return `Do you want to add these missing boxes and retry \`${command}\`?`;
|
|
803
895
|
}
|
|
@@ -807,6 +899,9 @@ export async function validateTagsForCreate(db, requestedTags) {
|
|
|
807
899
|
export async function validateTagsForUpdate(db, requestedTags) {
|
|
808
900
|
return validateTagsForWrite(db, requestedTags, 'dino note update');
|
|
809
901
|
}
|
|
902
|
+
export async function validateTagsForBulk(db, requestedTags) {
|
|
903
|
+
return validateTagsForWrite(db, requestedTags, 'dino note bulk');
|
|
904
|
+
}
|
|
810
905
|
async function validateTagsForWrite(db, requestedTags, retryCommand) {
|
|
811
906
|
if (requestedTags.length === 0) {
|
|
812
907
|
return [];
|
|
@@ -1013,30 +1108,43 @@ export async function resolveZettelBoxIdsForSearch(db, requestedNames) {
|
|
|
1013
1108
|
export async function resolveZettelBoxIdsForUpdate(db, requestedNames) {
|
|
1014
1109
|
return resolveZettelBoxIdsByName(db, requestedNames, 'update');
|
|
1015
1110
|
}
|
|
1111
|
+
export async function resolveZettelBoxIdsForBulk(db, requestedNames) {
|
|
1112
|
+
return resolveZettelBoxIdsByName(db, requestedNames, 'bulk');
|
|
1113
|
+
}
|
|
1016
1114
|
async function resolveZettelBoxIdsByName(db, requestedNames, intent) {
|
|
1017
1115
|
if (requestedNames.length === 0) {
|
|
1018
1116
|
return [];
|
|
1019
1117
|
}
|
|
1020
1118
|
const boxes = await listBoxes(db);
|
|
1021
|
-
const
|
|
1119
|
+
const availablePaths = Array.from(new Set(boxes.map((box) => box.path.trim()).filter(Boolean)));
|
|
1120
|
+
const boxByPath = new Map();
|
|
1022
1121
|
const boxByName = new Map();
|
|
1023
1122
|
for (const box of boxes) {
|
|
1024
1123
|
const name = box.name.trim();
|
|
1025
|
-
const
|
|
1026
|
-
|
|
1124
|
+
const path = canonicalizeBoxPath(box.path || name);
|
|
1125
|
+
const pathKey = normalizeZettelBoxLookupKey(path);
|
|
1126
|
+
if (pathKey && !boxByPath.has(pathKey)) {
|
|
1127
|
+
boxByPath.set(pathKey, box.id);
|
|
1128
|
+
}
|
|
1129
|
+
const nameKey = normalizeLookupKey(name);
|
|
1130
|
+
if (!nameKey) {
|
|
1027
1131
|
continue;
|
|
1028
1132
|
}
|
|
1029
|
-
const current = boxByName.get(
|
|
1133
|
+
const current = boxByName.get(nameKey);
|
|
1030
1134
|
if (!current) {
|
|
1031
|
-
boxByName.set(
|
|
1135
|
+
boxByName.set(nameKey, {
|
|
1032
1136
|
name,
|
|
1033
1137
|
ids: [box.id],
|
|
1138
|
+
paths: path ? [path] : [],
|
|
1034
1139
|
});
|
|
1035
1140
|
continue;
|
|
1036
1141
|
}
|
|
1037
1142
|
if (!current.ids.includes(box.id)) {
|
|
1038
1143
|
current.ids.push(box.id);
|
|
1039
1144
|
}
|
|
1145
|
+
if (path && !current.paths.includes(path)) {
|
|
1146
|
+
current.paths.push(path);
|
|
1147
|
+
}
|
|
1040
1148
|
}
|
|
1041
1149
|
const resolvedIds = [];
|
|
1042
1150
|
const seenIds = new Set();
|
|
@@ -1044,11 +1152,19 @@ async function resolveZettelBoxIdsByName(db, requestedNames, intent) {
|
|
|
1044
1152
|
const ambiguous = [];
|
|
1045
1153
|
const seenAmbiguous = new Set();
|
|
1046
1154
|
for (const requested of requestedNames) {
|
|
1047
|
-
const
|
|
1048
|
-
if (!
|
|
1155
|
+
const pathKey = normalizeZettelBoxLookupKey(requested);
|
|
1156
|
+
if (!pathKey) {
|
|
1157
|
+
continue;
|
|
1158
|
+
}
|
|
1159
|
+
const pathMatchId = boxByPath.get(pathKey);
|
|
1160
|
+
if (pathMatchId) {
|
|
1161
|
+
if (!seenIds.has(pathMatchId)) {
|
|
1162
|
+
seenIds.add(pathMatchId);
|
|
1163
|
+
resolvedIds.push(pathMatchId);
|
|
1164
|
+
}
|
|
1049
1165
|
continue;
|
|
1050
1166
|
}
|
|
1051
|
-
const lookup = boxByName.get(
|
|
1167
|
+
const lookup = boxByName.get(normalizeLookupKey(requested));
|
|
1052
1168
|
if (!lookup) {
|
|
1053
1169
|
missing.push(requested);
|
|
1054
1170
|
continue;
|
|
@@ -1060,6 +1176,7 @@ async function resolveZettelBoxIdsByName(db, requestedNames, intent) {
|
|
|
1060
1176
|
ambiguous.push({
|
|
1061
1177
|
name: requested,
|
|
1062
1178
|
ids: lookup.ids,
|
|
1179
|
+
paths: lookup.paths,
|
|
1063
1180
|
});
|
|
1064
1181
|
}
|
|
1065
1182
|
continue;
|
|
@@ -1071,22 +1188,22 @@ async function resolveZettelBoxIdsByName(db, requestedNames, intent) {
|
|
|
1071
1188
|
}
|
|
1072
1189
|
}
|
|
1073
1190
|
if (ambiguous.length > 0) {
|
|
1074
|
-
throw new DinoxError(`Ambiguous zettel box names: ${ambiguous.map((item) => `${item.name} -> [${item.
|
|
1191
|
+
throw new DinoxError(`Ambiguous zettel box names: ${ambiguous.map((item) => `${item.name} -> [${item.paths.join(', ')}]`).join('; ')}`, {
|
|
1075
1192
|
details: {
|
|
1076
1193
|
type: 'ambiguous_zettel_boxes',
|
|
1077
1194
|
ambiguous,
|
|
1078
|
-
|
|
1195
|
+
availablePathsSample: availablePaths.slice(0, 20),
|
|
1079
1196
|
question: buildZettelBoxRetryQuestion(intent, 'ambiguous'),
|
|
1080
1197
|
},
|
|
1081
1198
|
});
|
|
1082
1199
|
}
|
|
1083
1200
|
if (missing.length > 0) {
|
|
1084
|
-
throw new DinoxError(`Unknown zettel box names: ${missing.join(', ')}`, {
|
|
1201
|
+
throw new DinoxError(`Unknown zettel box paths or names: ${missing.join(', ')}`, {
|
|
1085
1202
|
details: {
|
|
1086
1203
|
type: 'missing_zettel_boxes',
|
|
1087
1204
|
missing,
|
|
1088
|
-
availableCount:
|
|
1089
|
-
|
|
1205
|
+
availableCount: availablePaths.length,
|
|
1206
|
+
availablePathsSample: availablePaths.slice(0, 20),
|
|
1090
1207
|
question: buildZettelBoxRetryQuestion(intent, 'missing'),
|
|
1091
1208
|
},
|
|
1092
1209
|
});
|
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
|
-
import type
|
|
3
|
-
|
|
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
|
-
};
|
|
2
|
+
import { type CommandRuntime } from '../runtime.js';
|
|
3
|
+
export type NoteCommandRuntime = CommandRuntime;
|
|
13
4
|
export declare function parseNoteType(value: unknown): 'note' | 'crawl';
|
|
14
5
|
export declare function parseSearchLimit(value: unknown): number;
|
|
15
6
|
export declare function parseSearchOffset(value: unknown): number;
|
|
@@ -24,5 +15,5 @@ export type ParsedSearchListInput = {
|
|
|
24
15
|
};
|
|
25
16
|
export declare function parseSearchListInput(value: string, optionName: string): Promise<ParsedSearchListInput>;
|
|
26
17
|
export declare function parseTargetNoteIds(idArg: string | undefined, idsOption: string | undefined, commandName: 'detail' | 'update'): Promise<string[]>;
|
|
27
|
-
export declare function syncBeforeNoteCommand(
|
|
18
|
+
export declare function syncBeforeNoteCommand(runtime: CommandRuntime): Promise<void>;
|
|
28
19
|
export declare function withNoteDatabase(command: Command, run: (runtime: NoteCommandRuntime) => Promise<void>): Promise<void>;
|
|
@@ -1,10 +1,6 @@
|
|
|
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
1
|
import { readStringOrFile } from '../../utils/argValue.js';
|
|
6
2
|
import { DinoxError } from '../../utils/errors.js';
|
|
7
|
-
import {
|
|
3
|
+
import { syncBeforeCommand, withCommandRuntime, } from '../runtime.js';
|
|
8
4
|
const DEFAULT_NOTE_SEARCH_LIMIT = 50;
|
|
9
5
|
const MAX_NOTE_SEARCH_LIMIT = 500;
|
|
10
6
|
export function parseNoteType(value) {
|
|
@@ -155,31 +151,9 @@ export async function parseTargetNoteIds(idArg, idsOption, commandName) {
|
|
|
155
151
|
}
|
|
156
152
|
return ids;
|
|
157
153
|
}
|
|
158
|
-
export async function syncBeforeNoteCommand(
|
|
159
|
-
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
await waitForIdleDataFlow(db, Math.min(timeoutMs, 10_000));
|
|
163
|
-
await syncNoteTokenIndex(db);
|
|
154
|
+
export async function syncBeforeNoteCommand(runtime) {
|
|
155
|
+
await syncBeforeCommand(runtime, { syncTokenIndex: true });
|
|
164
156
|
}
|
|
165
157
|
export async function withNoteDatabase(command, run) {
|
|
166
|
-
|
|
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
|
-
}
|
|
158
|
+
await withCommandRuntime(command, run);
|
|
185
159
|
}
|
|
@@ -432,7 +432,7 @@ async function buildZettelBoxIdLookup(ast, resolveZettelBoxNames) {
|
|
|
432
432
|
}
|
|
433
433
|
const ids = await resolveZettelBoxNames(uniqueNames);
|
|
434
434
|
if (ids.length !== uniqueNames.length) {
|
|
435
|
-
throw new DinoxError('Failed to resolve zettel box names in --sql expression');
|
|
435
|
+
throw new DinoxError('Failed to resolve zettel box paths or names in --sql expression');
|
|
436
436
|
}
|
|
437
437
|
const lookup = new Map();
|
|
438
438
|
for (let i = 0; i < uniqueNames.length; i += 1) {
|
|
@@ -1,41 +1,17 @@
|
|
|
1
1
|
import { resolveAuthIdentity } from '../../auth/userInfo.js';
|
|
2
|
-
import { loadConfig } from '../../config/store.js';
|
|
3
|
-
import { resolveConfig } from '../../config/resolve.js';
|
|
4
|
-
import { connectPowerSync, waitForIdleDataFlow } from '../../powersync/runtime.js';
|
|
5
|
-
import { syncNoteTokenIndex } from '../../powersync/tokenIndex.js';
|
|
6
2
|
import { registerCommandGroup } from '../descriptor/register.js';
|
|
7
3
|
import { FORMAT_OPTIONS, OFFLINE_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
|
|
8
4
|
import { buildPromptAddDryRunPreview } from '../writeDryRun.js';
|
|
9
5
|
import { printDryRunPreview } from '../../utils/dryRun.js';
|
|
10
6
|
import { DinoxError } from '../../utils/errors.js';
|
|
11
7
|
import { getRequestedStructuredOutputFormat, printHumanReadableLine, printSuccess, } from '../../utils/output.js';
|
|
12
|
-
import {
|
|
8
|
+
import { commandStale, printStaleCacheWarning, printUploadDisabledWarning, syncBeforeCommand, waitForMutationUpload, withCommandRuntime, } from '../runtime.js';
|
|
13
9
|
import { addPrompt, inspectAddPrompt, listPrompts } from './repo.js';
|
|
14
10
|
const PROMPT_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
|
|
15
|
-
async function syncBeforePromptCommand(db, offline, timeoutMs) {
|
|
16
|
-
if (offline) {
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
await waitForIdleDataFlow(db, Math.min(timeoutMs, 10_000));
|
|
20
|
-
await syncNoteTokenIndex(db);
|
|
21
|
-
}
|
|
22
|
-
async function withPromptDb(context, run) {
|
|
23
|
-
const rawConfig = await loadConfig();
|
|
24
|
-
const config = resolveConfig(rawConfig);
|
|
25
|
-
const timeoutMs = parseSyncTimeoutMs(context.globals.syncTimeout) ?? config.sync.timeoutMs;
|
|
26
|
-
const offline = Boolean(context.globals.offline);
|
|
27
|
-
const { db, stale } = await connectPowerSync({ config, offline, timeoutMs });
|
|
28
|
-
try {
|
|
29
|
-
await run({ db, config, rawConfig, offline, timeoutMs, stale });
|
|
30
|
-
}
|
|
31
|
-
finally {
|
|
32
|
-
await db.close().catch(() => undefined);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
11
|
async function runPromptList(context) {
|
|
36
|
-
await
|
|
37
|
-
await
|
|
38
|
-
printSuccess((await listPrompts(db)).map((prompt) => ({
|
|
12
|
+
await withCommandRuntime(context, async (runtime) => {
|
|
13
|
+
await syncBeforeCommand(runtime, { syncTokenIndex: true });
|
|
14
|
+
printSuccess((await listPrompts(runtime.db)).map((prompt) => ({
|
|
39
15
|
name: prompt.name,
|
|
40
16
|
prompt: prompt.cmd,
|
|
41
17
|
})));
|
|
@@ -51,14 +27,14 @@ async function runPromptAdd(context) {
|
|
|
51
27
|
throw new DinoxError('Prompt text is required. Use `dino prompt add --name <name> --prompt <text>`.');
|
|
52
28
|
}
|
|
53
29
|
const structuredOutput = getRequestedStructuredOutputFormat() !== null;
|
|
54
|
-
await
|
|
55
|
-
const identity = await resolveAuthIdentity(
|
|
56
|
-
await
|
|
30
|
+
await withCommandRuntime(context, async (runtime) => {
|
|
31
|
+
const identity = await resolveAuthIdentity(runtime.config);
|
|
32
|
+
await syncBeforeCommand(runtime, { syncTokenIndex: true });
|
|
57
33
|
if (Boolean(context.options.dryRun)) {
|
|
58
34
|
printDryRunPreview(buildPromptAddDryRunPreview({
|
|
59
|
-
config,
|
|
60
|
-
offline,
|
|
61
|
-
...(await inspectAddPrompt(db, {
|
|
35
|
+
config: runtime.config,
|
|
36
|
+
offline: runtime.offline,
|
|
37
|
+
...(await inspectAddPrompt(runtime.db, {
|
|
62
38
|
name,
|
|
63
39
|
cmd,
|
|
64
40
|
userId: identity.userId,
|
|
@@ -66,20 +42,16 @@ async function runPromptAdd(context) {
|
|
|
66
42
|
}));
|
|
67
43
|
return;
|
|
68
44
|
}
|
|
69
|
-
const result = await addPrompt(db, {
|
|
45
|
+
const result = await addPrompt(runtime.db, {
|
|
70
46
|
name,
|
|
71
47
|
cmd,
|
|
72
48
|
userId: identity.userId,
|
|
73
49
|
});
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
if (!offline) {
|
|
78
|
-
await waitForIdleDataFlow(db, Math.min(timeoutMs, 5_000));
|
|
79
|
-
}
|
|
50
|
+
printUploadDisabledWarning(runtime);
|
|
51
|
+
await waitForMutationUpload(runtime);
|
|
80
52
|
const payload = {
|
|
81
53
|
ok: true,
|
|
82
|
-
stale:
|
|
54
|
+
stale: commandStale(runtime),
|
|
83
55
|
id: result.id,
|
|
84
56
|
name: result.name,
|
|
85
57
|
prompt: result.cmd,
|
|
@@ -90,9 +62,7 @@ async function runPromptAdd(context) {
|
|
|
90
62
|
return;
|
|
91
63
|
}
|
|
92
64
|
printHumanReadableLine(result.id);
|
|
93
|
-
|
|
94
|
-
printHumanReadableLine('warning: sync timed out; results may be stale');
|
|
95
|
-
}
|
|
65
|
+
printStaleCacheWarning(runtime, 'warning: sync timed out; results may be stale');
|
|
96
66
|
});
|
|
97
67
|
}
|
|
98
68
|
export const promptCommandGroup = {
|
|
@@ -71,7 +71,7 @@ export async function addPrompt(db, input) {
|
|
|
71
71
|
throw new DinoxError(`c_cmd table is missing required columns: ${missingRequired.join(', ')}`);
|
|
72
72
|
}
|
|
73
73
|
if (columnNames.has('user_id') && !userId) {
|
|
74
|
-
throw new DinoxError('c_cmd insert requires user_id. Missing
|
|
74
|
+
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
75
|
}
|
|
76
76
|
const rows = await listPromptRowsForWrite(db, columnNames);
|
|
77
77
|
const samePromptRows = rows.filter((row) => {
|
|
@@ -181,7 +181,7 @@ export async function inspectAddPrompt(db, input) {
|
|
|
181
181
|
throw new DinoxError(`c_cmd table is missing required columns: ${missingRequired.join(', ')}`);
|
|
182
182
|
}
|
|
183
183
|
if (columnNames.has('user_id') && !userId) {
|
|
184
|
-
throw new DinoxError('c_cmd insert requires user_id. Missing
|
|
184
|
+
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
185
|
}
|
|
186
186
|
const rows = await listPromptRowsForWrite(db, columnNames);
|
|
187
187
|
const samePromptRows = rows.filter((row) => {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AbstractPowerSyncDatabase } from '@powersync/common';
|
|
2
|
+
import type { Command } from 'commander';
|
|
3
|
+
import { loadConfig } from '../config/store.js';
|
|
4
|
+
import type { ResolvedDinoxConfig } from '../config/types.js';
|
|
5
|
+
import type { CommandExecutionContext } from './descriptor/types.js';
|
|
6
|
+
export type CommandRuntime = {
|
|
7
|
+
db: AbstractPowerSyncDatabase;
|
|
8
|
+
config: ResolvedDinoxConfig;
|
|
9
|
+
rawConfig: Awaited<ReturnType<typeof loadConfig>>;
|
|
10
|
+
offline: boolean;
|
|
11
|
+
timeoutMs: number;
|
|
12
|
+
stale: boolean;
|
|
13
|
+
};
|
|
14
|
+
type RuntimeSource = CommandExecutionContext | Command | Record<string, unknown>;
|
|
15
|
+
export declare function commandStale(runtime: Pick<CommandRuntime, 'offline' | 'stale'>): boolean;
|
|
16
|
+
export declare function withCommandRuntime<T>(source: RuntimeSource, run: (runtime: CommandRuntime) => Promise<T>): Promise<T>;
|
|
17
|
+
export declare function syncBeforeCommand(runtime: CommandRuntime, options?: {
|
|
18
|
+
syncTokenIndex?: boolean;
|
|
19
|
+
timeoutMs?: number;
|
|
20
|
+
}): Promise<void>;
|
|
21
|
+
export declare function waitForMutationUpload(runtime: CommandRuntime, options?: {
|
|
22
|
+
timeoutMs?: number;
|
|
23
|
+
}): Promise<void>;
|
|
24
|
+
export declare function printUploadDisabledWarning(runtime: CommandRuntime, message?: string): void;
|
|
25
|
+
export declare function printStaleCacheWarning(runtime: CommandRuntime, message?: string): void;
|
|
26
|
+
export {};
|