@dinoxx/dinox-cli 1.0.20 → 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 +72 -139
- package/dist/auth/userInfo.d.ts +8 -1
- package/dist/auth/userInfo.js +38 -6
- package/dist/cli.js +0 -2
- package/dist/commands/auth/index.js +11 -4
- 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 +43 -15
- 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/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/resolve.js +2 -2
- package/dist/daemon/index.d.ts +2 -0
- package/dist/daemon/index.js +176 -58
- package/dist/powersync/runtime.d.ts +1 -0
- package/dist/powersync/runtime.js +10 -0
- 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,204 @@
|
|
|
1
|
+
import { printDryRunPreview } from '../../utils/dryRun.js';
|
|
2
|
+
import { getRequestedStructuredOutputFormat, printHumanReadableLine, printSuccess, } from '../../utils/output.js';
|
|
3
|
+
import { DinoxError } from '../../utils/errors.js';
|
|
4
|
+
import { commandStale, printStaleCacheWarning, printUploadDisabledWarning, waitForMutationUpload, } from '../runtime.js';
|
|
5
|
+
import { resolveZettelBoxIdsForBulk, resolveZettelBoxIdsForSearch, searchNotes, validateTagsForBulk, } from './repo.js';
|
|
6
|
+
import { applyNoteMetadataMutation, planNoteMetadataMutation, } from './metadataMutation.js';
|
|
7
|
+
import { parseCreatedAtRange } from './searchTime.js';
|
|
8
|
+
import { normalizeDaysForMeta, } from './searchResponse.js';
|
|
9
|
+
import { parseSearchListInput, parseStarredOption, parseStringListInput, syncBeforeNoteCommand, withNoteDatabase, } from './runtime.js';
|
|
10
|
+
const BULK_PREVIEW_LIMIT = 100;
|
|
11
|
+
export function selectBulkMutation(options) {
|
|
12
|
+
const candidates = [
|
|
13
|
+
{ optionName: '--tag-add', field: 'tags', operation: 'add', rawValues: options.tagAdd },
|
|
14
|
+
{ optionName: '--tag-remove', field: 'tags', operation: 'remove', rawValues: options.tagRemove },
|
|
15
|
+
{ optionName: '--tag-replace', field: 'tags', operation: 'replace', rawValues: options.tagReplace },
|
|
16
|
+
{ optionName: '--box-add', field: 'boxes', operation: 'add', rawValues: options.boxAdd },
|
|
17
|
+
{ optionName: '--box-remove', field: 'boxes', operation: 'remove', rawValues: options.boxRemove },
|
|
18
|
+
{ optionName: '--box-replace', field: 'boxes', operation: 'replace', rawValues: options.boxReplace },
|
|
19
|
+
];
|
|
20
|
+
const selected = candidates.filter((entry) => typeof entry.rawValues === 'string');
|
|
21
|
+
if (selected.length !== 1) {
|
|
22
|
+
throw new DinoxError('Provide exactly one bulk mutation option: --tag-add, --tag-remove, --tag-replace, --box-add, --box-remove, or --box-replace');
|
|
23
|
+
}
|
|
24
|
+
return selected[0];
|
|
25
|
+
}
|
|
26
|
+
export function parseExpectedCount(value) {
|
|
27
|
+
if (value === undefined || value === null || value === '') {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
if (typeof value !== 'string') {
|
|
31
|
+
throw new DinoxError('--expected-count must be a non-negative integer');
|
|
32
|
+
}
|
|
33
|
+
const trimmed = value.trim();
|
|
34
|
+
if (!/^\d+$/.test(trimmed)) {
|
|
35
|
+
throw new DinoxError('--expected-count must be a non-negative integer');
|
|
36
|
+
}
|
|
37
|
+
const parsed = Number(trimmed);
|
|
38
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0) {
|
|
39
|
+
throw new DinoxError('--expected-count must be a non-negative integer');
|
|
40
|
+
}
|
|
41
|
+
return parsed;
|
|
42
|
+
}
|
|
43
|
+
function hasConcreteTargetFilter(input) {
|
|
44
|
+
const { query, options } = input;
|
|
45
|
+
return Boolean(query ||
|
|
46
|
+
options.tags ||
|
|
47
|
+
options.from ||
|
|
48
|
+
options.to ||
|
|
49
|
+
options.days ||
|
|
50
|
+
options.starred ||
|
|
51
|
+
options.boxes);
|
|
52
|
+
}
|
|
53
|
+
export function assertBulkTargetFilter(input) {
|
|
54
|
+
const hasFilter = hasConcreteTargetFilter(input);
|
|
55
|
+
if (input.options.all && hasFilter) {
|
|
56
|
+
throw new DinoxError('--all cannot be combined with other target filters');
|
|
57
|
+
}
|
|
58
|
+
if (!input.options.all && !hasFilter) {
|
|
59
|
+
throw new DinoxError('Provide at least one target filter or --all for note bulk');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function buildExecutionWarnings(input) {
|
|
63
|
+
const warnings = [];
|
|
64
|
+
if (input.offline) {
|
|
65
|
+
warnings.push('Execution would run in offline mode and only use the local cache.');
|
|
66
|
+
}
|
|
67
|
+
if (!input.offline && !input.uploadEnabled) {
|
|
68
|
+
warnings.push('Execution would be local-only because powersync.uploadBaseUrl is unset.');
|
|
69
|
+
}
|
|
70
|
+
if (input.total > BULK_PREVIEW_LIMIT) {
|
|
71
|
+
warnings.push(`Preview is truncated to the first ${BULK_PREVIEW_LIMIT} matched notes.`);
|
|
72
|
+
}
|
|
73
|
+
return warnings;
|
|
74
|
+
}
|
|
75
|
+
function previewItems(items) {
|
|
76
|
+
return items.slice(0, BULK_PREVIEW_LIMIT);
|
|
77
|
+
}
|
|
78
|
+
export async function runNoteBulk({ command, options, args }) {
|
|
79
|
+
const bulkOptions = options;
|
|
80
|
+
const structuredOutput = getRequestedStructuredOutputFormat() !== null;
|
|
81
|
+
await withNoteDatabase(command, async (runtime) => {
|
|
82
|
+
const { db, config, offline } = runtime;
|
|
83
|
+
await syncBeforeNoteCommand(runtime);
|
|
84
|
+
const query = args.query?.trim() ?? '';
|
|
85
|
+
assertBulkTargetFilter({ query, options: bulkOptions });
|
|
86
|
+
const selected = selectBulkMutation(bulkOptions);
|
|
87
|
+
const requestedValues = await parseStringListInput(selected.rawValues, selected.optionName);
|
|
88
|
+
const values = selected.field === 'tags'
|
|
89
|
+
? await validateTagsForBulk(db, requestedValues)
|
|
90
|
+
: await resolveZettelBoxIdsForBulk(db, requestedValues);
|
|
91
|
+
const tagsExpression = typeof bulkOptions.tags === 'string' ? bulkOptions.tags.trim() : '';
|
|
92
|
+
const { createdAtFrom, createdAtTo } = parseCreatedAtRange({
|
|
93
|
+
from: bulkOptions.from,
|
|
94
|
+
to: bulkOptions.to,
|
|
95
|
+
days: bulkOptions.days,
|
|
96
|
+
});
|
|
97
|
+
const starred = typeof bulkOptions.starred === 'string'
|
|
98
|
+
? parseStarredOption(bulkOptions.starred, '--starred')
|
|
99
|
+
: undefined;
|
|
100
|
+
const boxInput = typeof bulkOptions.boxes === 'string' ? bulkOptions.boxes : '';
|
|
101
|
+
const parsedBoxInput = boxInput
|
|
102
|
+
? await parseSearchListInput(boxInput, '--boxes')
|
|
103
|
+
: { values: [], isExplicitEmptyArray: false };
|
|
104
|
+
const zettelBoxIds = await resolveZettelBoxIdsForSearch(db, parsedBoxInput.values);
|
|
105
|
+
const matches = await searchNotes(db, query, {
|
|
106
|
+
includeDeleted: false,
|
|
107
|
+
tagsExpression: tagsExpression || undefined,
|
|
108
|
+
createdAtFrom,
|
|
109
|
+
createdAtTo,
|
|
110
|
+
starred,
|
|
111
|
+
zettelBoxIds,
|
|
112
|
+
matchEmptyZettelBoxes: parsedBoxInput.isExplicitEmptyArray,
|
|
113
|
+
});
|
|
114
|
+
const ids = matches.map((note) => note.id);
|
|
115
|
+
if (ids.length === 0) {
|
|
116
|
+
throw new DinoxError('No active notes matched the bulk filters');
|
|
117
|
+
}
|
|
118
|
+
const plan = await planNoteMetadataMutation(db, {
|
|
119
|
+
ids,
|
|
120
|
+
field: selected.field,
|
|
121
|
+
operation: selected.operation,
|
|
122
|
+
values,
|
|
123
|
+
displayValues: requestedValues,
|
|
124
|
+
});
|
|
125
|
+
const expectedCount = parseExpectedCount(bulkOptions.expectedCount);
|
|
126
|
+
const filterMeta = {
|
|
127
|
+
query,
|
|
128
|
+
tags_expression: tagsExpression || null,
|
|
129
|
+
created_at_range: createdAtFrom || createdAtTo
|
|
130
|
+
? { from: createdAtFrom ?? null, to: createdAtTo ?? null }
|
|
131
|
+
: null,
|
|
132
|
+
days: normalizeDaysForMeta(bulkOptions.days) ?? null,
|
|
133
|
+
starred: typeof starred === 'boolean' ? starred : null,
|
|
134
|
+
box_names: parsedBoxInput.values,
|
|
135
|
+
box_ids: zettelBoxIds,
|
|
136
|
+
match_empty_boxes: parsedBoxInput.isExplicitEmptyArray,
|
|
137
|
+
all: Boolean(bulkOptions.all),
|
|
138
|
+
};
|
|
139
|
+
if (bulkOptions.dryRun) {
|
|
140
|
+
printDryRunPreview({
|
|
141
|
+
dry_run: true,
|
|
142
|
+
command: 'dino note bulk',
|
|
143
|
+
action: 'bulk_mutate_note_metadata',
|
|
144
|
+
risk: 'high-risk-write',
|
|
145
|
+
summary: `Preview bulk ${selected.field} ${selected.operation} for ${plan.total} matched note(s); ${plan.changedCount} note(s) would change.`,
|
|
146
|
+
warnings: buildExecutionWarnings({
|
|
147
|
+
offline,
|
|
148
|
+
uploadEnabled: Boolean(config.powersync.uploadBaseUrl),
|
|
149
|
+
total: plan.total,
|
|
150
|
+
}),
|
|
151
|
+
inputs: {
|
|
152
|
+
filters: filterMeta,
|
|
153
|
+
operation: selected.operation,
|
|
154
|
+
field: selected.field,
|
|
155
|
+
[selected.field]: selected.field === 'tags' ? values : requestedValues,
|
|
156
|
+
expected_count: expectedCount,
|
|
157
|
+
preview_limit: BULK_PREVIEW_LIMIT,
|
|
158
|
+
preview_truncated: plan.total > BULK_PREVIEW_LIMIT,
|
|
159
|
+
},
|
|
160
|
+
targets: previewItems(plan.results).map((item) => ({ id: item.id, title: item.title })),
|
|
161
|
+
changes: previewItems(plan.results).map((item) => ({
|
|
162
|
+
id: item.id,
|
|
163
|
+
title: item.title,
|
|
164
|
+
[`${selected.field}_from`]: item.before,
|
|
165
|
+
[`${selected.field}_to`]: item.after,
|
|
166
|
+
changed: item.changed,
|
|
167
|
+
})),
|
|
168
|
+
});
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (!bulkOptions.confirm) {
|
|
172
|
+
throw new DinoxError('Bulk writes require --confirm');
|
|
173
|
+
}
|
|
174
|
+
if (expectedCount === null) {
|
|
175
|
+
throw new DinoxError('Bulk writes require --expected-count <n>');
|
|
176
|
+
}
|
|
177
|
+
if (expectedCount !== plan.total) {
|
|
178
|
+
throw new DinoxError(`--expected-count ${expectedCount} does not match current target count ${plan.total}`);
|
|
179
|
+
}
|
|
180
|
+
await applyNoteMetadataMutation(db, plan);
|
|
181
|
+
printUploadDisabledWarning(runtime);
|
|
182
|
+
await waitForMutationUpload(runtime);
|
|
183
|
+
const payload = {
|
|
184
|
+
ok: true,
|
|
185
|
+
field: selected.field,
|
|
186
|
+
operation: selected.operation,
|
|
187
|
+
total: plan.total,
|
|
188
|
+
updatedCount: plan.changedCount,
|
|
189
|
+
filters: filterMeta,
|
|
190
|
+
results: plan.results.map((item) => ({
|
|
191
|
+
id: item.id,
|
|
192
|
+
updated: item.changed,
|
|
193
|
+
[selected.field]: item.after,
|
|
194
|
+
})),
|
|
195
|
+
stale: commandStale(runtime),
|
|
196
|
+
};
|
|
197
|
+
if (structuredOutput) {
|
|
198
|
+
printSuccess(payload);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
printHumanReadableLine(`Updated ${plan.changedCount}/${plan.total} notes`);
|
|
202
|
+
printStaleCacheWarning(runtime);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
export type TiptapNode = {
|
|
2
|
+
type: string;
|
|
3
|
+
attrs?: Record<string, unknown>;
|
|
4
|
+
content?: TiptapNode[];
|
|
5
|
+
marks?: Array<Record<string, unknown>>;
|
|
6
|
+
text?: string;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
};
|
|
9
|
+
export type NoteContentEditMode = 'text' | 'inlineAtom' | 'atomicBlock' | 'container' | 'unsupported';
|
|
10
|
+
export type NoteContentBlockDescriptor = {
|
|
11
|
+
anchorId: string;
|
|
12
|
+
hasStableAnchor: boolean;
|
|
13
|
+
nodeIndex: number;
|
|
14
|
+
nodeRange: string;
|
|
15
|
+
type: string;
|
|
16
|
+
editMode: NoteContentEditMode;
|
|
17
|
+
level: number | null;
|
|
18
|
+
text: string;
|
|
19
|
+
textPreview: string;
|
|
20
|
+
textHash: string;
|
|
21
|
+
nodeHash: string;
|
|
22
|
+
charCount: number;
|
|
23
|
+
attrs?: Record<string, unknown>;
|
|
24
|
+
};
|
|
25
|
+
export type NoteContentOutlineItem = {
|
|
26
|
+
path: string;
|
|
27
|
+
level: number;
|
|
28
|
+
nodeRange: string;
|
|
29
|
+
contentHash: string;
|
|
30
|
+
};
|
|
31
|
+
export type NoteContentProjection = {
|
|
32
|
+
document: TiptapNode;
|
|
33
|
+
contentJson: string;
|
|
34
|
+
contentMd: string;
|
|
35
|
+
contentText: string;
|
|
36
|
+
hashtags: string[];
|
|
37
|
+
images: string[];
|
|
38
|
+
bidLinks: Array<{
|
|
39
|
+
id: string;
|
|
40
|
+
type: string;
|
|
41
|
+
}>;
|
|
42
|
+
};
|
|
43
|
+
export type HashtagReferenceResolver = (input: {
|
|
44
|
+
id: string;
|
|
45
|
+
path: string;
|
|
46
|
+
}) => {
|
|
47
|
+
id: string;
|
|
48
|
+
path: string;
|
|
49
|
+
} | null;
|
|
50
|
+
export type NoteContentProjectionOptions = {
|
|
51
|
+
resolveHashtagReference?: HashtagReferenceResolver;
|
|
52
|
+
};
|
|
53
|
+
export type JsonPatchOperation = {
|
|
54
|
+
kind: 'appendSection';
|
|
55
|
+
heading: string;
|
|
56
|
+
content: string;
|
|
57
|
+
} | {
|
|
58
|
+
kind: 'appendToHeading';
|
|
59
|
+
headingPath: string;
|
|
60
|
+
content: string;
|
|
61
|
+
} | {
|
|
62
|
+
kind: 'replaceSection';
|
|
63
|
+
headingPath: string;
|
|
64
|
+
content: string;
|
|
65
|
+
allowProtectedReplace?: boolean;
|
|
66
|
+
} | {
|
|
67
|
+
kind: 'replaceBlock';
|
|
68
|
+
match: string;
|
|
69
|
+
content: string;
|
|
70
|
+
allowProtectedReplace?: boolean;
|
|
71
|
+
};
|
|
72
|
+
export type JsonPatchPlan = {
|
|
73
|
+
operation: JsonPatchOperation['kind'];
|
|
74
|
+
changed: boolean;
|
|
75
|
+
beforeHash: string;
|
|
76
|
+
afterHash: string;
|
|
77
|
+
beforeContent: string;
|
|
78
|
+
afterContent: string;
|
|
79
|
+
beforeBlocks: NoteContentBlockDescriptor[];
|
|
80
|
+
afterBlocks: NoteContentBlockDescriptor[];
|
|
81
|
+
beforeProjection: NoteContentProjection;
|
|
82
|
+
afterProjection: NoteContentProjection;
|
|
83
|
+
target?: Record<string, unknown>;
|
|
84
|
+
};
|
|
85
|
+
export declare function parseStoredNoteDocument(input: {
|
|
86
|
+
contentJson: string | null;
|
|
87
|
+
contentMd: string | null;
|
|
88
|
+
}): TiptapNode;
|
|
89
|
+
export declare function buildNoteContentBlocks(document: TiptapNode): NoteContentBlockDescriptor[];
|
|
90
|
+
export declare function buildNoteContentOutline(document: TiptapNode): NoteContentOutlineItem[];
|
|
91
|
+
export declare function getNoteContentHash(document: TiptapNode): string;
|
|
92
|
+
export declare function serializeNoteContentMarkdown(document: TiptapNode): string;
|
|
93
|
+
export declare function projectNoteContent(document: TiptapNode, options?: NoteContentProjectionOptions): NoteContentProjection;
|
|
94
|
+
export declare function applyJsonPatchToDocument(document: TiptapNode, operation: JsonPatchOperation, options?: NoteContentProjectionOptions): JsonPatchPlan;
|
|
95
|
+
export declare function withStableBlockAnchors(document: TiptapNode): TiptapNode;
|