@dinoxx/dinox-cli 1.1.1 → 2.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 +44 -3
- package/dist/cli.js +4 -0
- package/dist/commands/auth/index.js +1 -2
- package/dist/commands/boxes/index.js +37 -13
- package/dist/commands/boxes/operations.d.ts +3 -3
- package/dist/commands/boxes/operations.js +51 -28
- package/dist/commands/info/index.js +30 -5
- package/dist/commands/meta/repo.js +2 -1
- package/dist/commands/notes/actions.js +20 -37
- package/dist/commands/notes/bulk.d.ts +2 -2
- package/dist/commands/notes/bulk.js +3 -18
- package/dist/commands/notes/contentPatch.d.ts +5 -1
- package/dist/commands/notes/contentPatch.js +12 -1
- package/dist/commands/notes/contentPatchCommand.d.ts +3 -0
- package/dist/commands/notes/contentPatchCommand.js +29 -8
- package/dist/commands/notes/descriptors.js +19 -16
- package/dist/commands/notes/export.js +3 -0
- package/dist/commands/notes/repo/read.d.ts +8 -0
- package/dist/commands/notes/repo/read.js +321 -0
- package/dist/commands/notes/repo/resolve.d.ts +8 -0
- package/dist/commands/notes/repo/resolve.js +187 -0
- package/dist/commands/notes/repo/search.d.ts +42 -0
- package/dist/commands/notes/repo/search.js +513 -0
- package/dist/commands/notes/repo/shared.d.ts +190 -0
- package/dist/commands/notes/repo/shared.js +102 -0
- package/dist/commands/notes/repo/write.d.ts +24 -0
- package/dist/commands/notes/repo/write.js +251 -0
- package/dist/commands/notes/repo.d.ts +7 -177
- package/dist/commands/notes/repo.js +7 -1310
- package/dist/commands/notes/searchOutput.d.ts +2 -1
- package/dist/commands/notes/searchOutput.js +2 -1
- package/dist/commands/notes/searchSql.js +9 -3
- package/dist/commands/prompt/index.d.ts +1 -0
- package/dist/commands/prompt/index.js +26 -11
- package/dist/commands/savedViews/index.d.ts +7 -0
- package/dist/commands/savedViews/index.js +458 -0
- package/dist/commands/savedViews/model.d.ts +87 -0
- package/dist/commands/savedViews/model.js +306 -0
- package/dist/commands/savedViews/repo.d.ts +123 -0
- package/dist/commands/savedViews/repo.js +512 -0
- package/dist/commands/savedViews/semantics.d.ts +46 -0
- package/dist/commands/savedViews/semantics.js +738 -0
- package/dist/commands/schema/commandMetadata.js +4 -4
- package/dist/commands/schema/index.d.ts +1 -0
- package/dist/commands/schema/index.js +23 -2
- package/dist/commands/schema/renderSkills.d.ts +6 -0
- package/dist/commands/schema/renderSkills.js +109 -11
- package/dist/commands/schema/spec.js +4 -0
- package/dist/commands/skills/index.d.ts +80 -0
- package/dist/commands/skills/index.js +572 -0
- package/dist/commands/storage/index.js +2 -2
- package/dist/commands/storage/repo/configs.d.ts +30 -0
- package/dist/commands/storage/repo/configs.js +259 -0
- package/dist/commands/storage/repo/operations.d.ts +25 -0
- package/dist/commands/storage/repo/operations.js +337 -0
- package/dist/commands/storage/repo/remote.d.ts +32 -0
- package/dist/commands/storage/repo/remote.js +132 -0
- package/dist/commands/storage/repo/resources.d.ts +12 -0
- package/dist/commands/storage/repo/resources.js +134 -0
- package/dist/commands/storage/repo/shared.d.ts +181 -0
- package/dist/commands/storage/repo/shared.js +36 -0
- package/dist/commands/storage/repo/targets.d.ts +12 -0
- package/dist/commands/storage/repo/targets.js +178 -0
- package/dist/commands/storage/repo.d.ts +3 -166
- package/dist/commands/storage/repo.js +4 -1034
- package/dist/commands/sync.js +2 -1
- package/dist/commands/tags/index.js +30 -11
- package/dist/commands/tags/operations.d.ts +3 -3
- package/dist/commands/tags/operations.js +45 -20
- package/dist/commands/templates/model.d.ts +50 -0
- package/dist/commands/templates/model.js +239 -0
- package/dist/commands/todo/index.js +3 -3
- package/dist/commands/update.d.ts +1 -0
- package/dist/commands/update.js +46 -9
- package/dist/commands/writeConfirmation.d.ts +13 -0
- package/dist/commands/writeConfirmation.js +54 -0
- package/dist/config/resolve.js +5 -10
- package/dist/config/types.d.ts +3 -6
- package/dist/constants/links.d.ts +18 -1
- package/dist/constants/links.js +26 -1
- package/dist/contract/snapshot.d.ts +30 -0
- package/dist/contract/snapshot.js +114 -0
- package/dist/daemon/client.d.ts +2 -2
- package/dist/daemon/client.js +108 -54
- package/dist/daemon/index.d.ts +5 -3
- package/dist/daemon/index.js +28 -5
- package/dist/daemon/lifecycle.d.ts +7 -2
- package/dist/daemon/lifecycle.js +53 -13
- package/dist/daemon/pid.d.ts +1 -1
- package/dist/daemon/pid.js +6 -3
- package/dist/dinox.js +9 -1
- package/dist/powersync/exclusiveRuntime.d.ts +1 -0
- package/dist/powersync/exclusiveRuntime.js +26 -8
- package/dist/powersync/runtime.d.ts +3 -2
- package/dist/powersync/runtime.js +19 -3
- package/dist/powersync/schema/content.d.ts +1 -78
- package/dist/powersync/schema/content.js +1 -78
- package/dist/powersync/schema/electronCanonical.d.ts +1561 -0
- package/dist/powersync/schema/electronCanonical.js +1154 -0
- package/dist/powersync/schema/index.d.ts +573 -129
- package/dist/powersync/schema/index.js +3 -32
- package/dist/powersync/schema/local.d.ts +1 -56
- package/dist/powersync/schema/local.js +1 -60
- package/dist/powersync/schema/note.d.ts +1 -35
- package/dist/powersync/schema/note.js +1 -35
- package/dist/powersync/schema/notesExtras.d.ts +1 -62
- package/dist/powersync/schema/notesExtras.js +1 -71
- package/dist/powersync/schema/projects.d.ts +1 -104
- package/dist/powersync/schema/projects.js +1 -104
- package/dist/powersync/schema/savedViews.d.ts +1 -0
- package/dist/powersync/schema/savedViews.js +1 -0
- package/dist/powersync/schema/storage.d.ts +1 -24
- package/dist/powersync/schema/storage.js +1 -24
- package/dist/powersync/schema/tags.d.ts +1 -37
- package/dist/powersync/schema/tags.js +1 -37
- package/dist/powersync/syncGate.d.ts +20 -2
- package/dist/powersync/uploader.d.ts +16 -2
- package/dist/powersync/uploader.js +152 -158
- package/dist/skills/check.d.ts +1 -0
- package/dist/skills/check.js +538 -70
- package/dist/skills/commandRefs.d.ts +21 -0
- package/dist/skills/commandRefs.js +132 -0
- package/dist/skills/generate.d.ts +13 -0
- package/dist/skills/generate.js +80 -0
- package/dist/skills/registry.d.ts +13 -0
- package/dist/skills/registry.js +58 -0
- package/dist/skills/retired.d.ts +60 -0
- package/dist/skills/retired.js +58 -0
- package/dist/utils/deadline.d.ts +8 -0
- package/dist/utils/deadline.js +31 -0
- package/dist/utils/dryRun.d.ts +20 -1
- package/dist/utils/dryRun.js +22 -3
- package/dist/utils/errorCodes.d.ts +311 -0
- package/dist/utils/errorCodes.js +92 -0
- package/dist/utils/errors.d.ts +15 -2
- package/dist/utils/errors.js +13 -0
- package/dist/utils/processExit.d.ts +1 -0
- package/dist/utils/processExit.js +33 -0
- package/dist/utils/updateNotice.d.ts +1 -0
- package/dist/utils/updateNotice.js +1 -1
- package/package.json +18 -8
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AbstractPowerSyncDatabase } from '@powersync/common';
|
|
2
2
|
import { type NoteContentBlockDescriptor, type NoteContentOutlineItem } from './contentEngine.js';
|
|
3
|
+
export type NoteContentReadScope = 'structural-metadata' | 'full-content';
|
|
3
4
|
export type NoteContentPatchOperation = {
|
|
4
5
|
kind: 'appendSection';
|
|
5
6
|
heading: string;
|
|
@@ -24,6 +25,7 @@ export type NoteContentReadResult = {
|
|
|
24
25
|
title: string;
|
|
25
26
|
readToken: string;
|
|
26
27
|
readTokenExpiresAt: string;
|
|
28
|
+
readTokenScope: NoteContentReadScope;
|
|
27
29
|
base: {
|
|
28
30
|
contentHash: string;
|
|
29
31
|
updatedAt: string | null;
|
|
@@ -72,7 +74,9 @@ export declare function planMarkdownPatch(input: {
|
|
|
72
74
|
contentMd: string;
|
|
73
75
|
operation: NoteContentPatchOperation;
|
|
74
76
|
}): NoteContentPatchPlan;
|
|
75
|
-
export declare function readNoteContentForPatch(db: AbstractPowerSyncDatabase, id: string, databaseScope: string, userId: string
|
|
77
|
+
export declare function readNoteContentForPatch(db: AbstractPowerSyncDatabase, id: string, databaseScope: string, userId: string, options?: {
|
|
78
|
+
includeContent?: boolean;
|
|
79
|
+
}): Promise<NoteContentReadResult>;
|
|
76
80
|
export declare function planNoteContentPatch(db: AbstractPowerSyncDatabase, input: {
|
|
77
81
|
id: string;
|
|
78
82
|
userId: string;
|
|
@@ -154,6 +154,7 @@ async function rememberReadToken(input) {
|
|
|
154
154
|
contentHash: input.contentHash,
|
|
155
155
|
updatedAt: input.updatedAt,
|
|
156
156
|
version: input.version,
|
|
157
|
+
reviewScope: input.reviewScope,
|
|
157
158
|
createdAt: new Date(nowMs).toISOString(),
|
|
158
159
|
expiresAt,
|
|
159
160
|
};
|
|
@@ -296,7 +297,7 @@ export function planMarkdownPatch(input) {
|
|
|
296
297
|
});
|
|
297
298
|
return plan;
|
|
298
299
|
}
|
|
299
|
-
export async function readNoteContentForPatch(db, id, databaseScope, userId) {
|
|
300
|
+
export async function readNoteContentForPatch(db, id, databaseScope, userId, options = {}) {
|
|
300
301
|
const row = await readNoteContentRow(db, id);
|
|
301
302
|
const tagLookup = await loadTagReferenceLookup(db, userId);
|
|
302
303
|
const document = parseStoredNoteDocument({
|
|
@@ -308,18 +309,23 @@ export async function readNoteContentForPatch(db, id, databaseScope, userId) {
|
|
|
308
309
|
});
|
|
309
310
|
const contentHash = getNoteContentHash(document);
|
|
310
311
|
const version = normalizeVersion(row.version);
|
|
312
|
+
const readTokenScope = options.includeContent === true
|
|
313
|
+
? 'full-content'
|
|
314
|
+
: 'structural-metadata';
|
|
311
315
|
const token = await rememberReadToken({
|
|
312
316
|
databaseScope,
|
|
313
317
|
noteId: row.id,
|
|
314
318
|
contentHash,
|
|
315
319
|
updatedAt: row.updated_at ?? null,
|
|
316
320
|
version,
|
|
321
|
+
reviewScope: readTokenScope,
|
|
317
322
|
});
|
|
318
323
|
return {
|
|
319
324
|
noteId: row.id,
|
|
320
325
|
title: row.title?.trim() ?? '',
|
|
321
326
|
readToken: token.token,
|
|
322
327
|
readTokenExpiresAt: token.expiresAt,
|
|
328
|
+
readTokenScope,
|
|
323
329
|
base: {
|
|
324
330
|
contentHash,
|
|
325
331
|
updatedAt: row.updated_at ?? null,
|
|
@@ -353,6 +359,11 @@ export async function applyNoteContentPatch(db, input) {
|
|
|
353
359
|
if (token.databaseScopeFingerprint !== fingerprintDatabaseScope(input.databaseScope)) {
|
|
354
360
|
throw new DinoxError('read token was issued for a different local account database. Run `dino note content-read <id>` again.');
|
|
355
361
|
}
|
|
362
|
+
if ('allowProtectedReplace' in input.operation
|
|
363
|
+
&& input.operation.allowProtectedReplace === true
|
|
364
|
+
&& token.reviewScope !== 'full-content') {
|
|
365
|
+
throw new DinoxError('Protected content replacement requires a full-content read token. Run `dino note content-read <id> --include-content --format json`, review the affected protected blocks, and retry with the new token.');
|
|
366
|
+
}
|
|
356
367
|
const result = await withPowerSyncWriteTransaction(db, async (tx) => {
|
|
357
368
|
const tagLookup = await loadTagReferenceLookup(tx, input.userId);
|
|
358
369
|
const row = await readNoteContentRow(tx, input.id);
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
import type { CommandExecutionContext } from '../descriptor/types.js';
|
|
2
|
+
import type { NoteContentBlockDescriptor } from './contentEngine.js';
|
|
3
|
+
export type PublicNoteContentBlockDescriptor = Pick<NoteContentBlockDescriptor, 'anchorId' | 'hasStableAnchor' | 'nodeIndex' | 'nodeRange' | 'type' | 'editMode' | 'level' | 'textHash' | 'nodeHash' | 'charCount'>;
|
|
4
|
+
export declare function toPublicNoteContentBlocks(blocks: NoteContentBlockDescriptor[]): PublicNoteContentBlockDescriptor[];
|
|
2
5
|
export declare function runNoteContentRead({ command, options, args }: CommandExecutionContext): Promise<void>;
|
|
3
6
|
export declare function runNotePatch({ command, options, args }: CommandExecutionContext): Promise<void>;
|
|
@@ -7,6 +7,20 @@ import { commandStale, printStaleCacheWarning, resolveWriteDurabilityForRuntime,
|
|
|
7
7
|
import { readNoteWriteReceipt } from './writeReceipt.js';
|
|
8
8
|
import { applyNoteContentPatch, planNoteContentPatch, readNoteContentForPatch, } from './contentPatch.js';
|
|
9
9
|
import { parseStringInput, syncBeforeNoteCommand, withNoteDatabase, } from './runtime.js';
|
|
10
|
+
export function toPublicNoteContentBlocks(blocks) {
|
|
11
|
+
return blocks.map((block) => ({
|
|
12
|
+
anchorId: block.anchorId,
|
|
13
|
+
hasStableAnchor: block.hasStableAnchor,
|
|
14
|
+
nodeIndex: block.nodeIndex,
|
|
15
|
+
nodeRange: block.nodeRange,
|
|
16
|
+
type: block.type,
|
|
17
|
+
editMode: block.editMode,
|
|
18
|
+
level: block.level,
|
|
19
|
+
textHash: block.textHash,
|
|
20
|
+
nodeHash: block.nodeHash,
|
|
21
|
+
charCount: block.charCount,
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
10
24
|
function requireNoteId(id) {
|
|
11
25
|
const normalized = id?.trim() ?? '';
|
|
12
26
|
if (!normalized) {
|
|
@@ -60,11 +74,11 @@ export async function runNoteContentRead({ command, options, args }) {
|
|
|
60
74
|
await withNoteDatabase(command, async (runtime) => {
|
|
61
75
|
await syncBeforeNoteCommand(runtime);
|
|
62
76
|
const identity = await resolveAuthIdentity(runtime.config);
|
|
63
|
-
const result = await readNoteContentForPatch(runtime.db, id, getPowerSyncDbPath(identity.userId), identity.userId);
|
|
64
|
-
const { content, ...rest } = result;
|
|
77
|
+
const result = await readNoteContentForPatch(runtime.db, id, getPowerSyncDbPath(identity.userId), identity.userId, { includeContent: readOptions.includeContent === true });
|
|
78
|
+
const { content, blocks, ...rest } = result;
|
|
65
79
|
const payload = readOptions.includeContent
|
|
66
|
-
? { ...rest, content, stale: commandStale(runtime) }
|
|
67
|
-
: { ...rest, stale: commandStale(runtime) };
|
|
80
|
+
? { ...rest, blocks, content, stale: commandStale(runtime) }
|
|
81
|
+
: { ...rest, blocks: toPublicNoteContentBlocks(blocks), stale: commandStale(runtime) };
|
|
68
82
|
printSuccess(payload);
|
|
69
83
|
});
|
|
70
84
|
}
|
|
@@ -88,6 +102,9 @@ export async function runNotePatch({ command, options, args }) {
|
|
|
88
102
|
summary: `Preview ${plan.operation} for note "${plan.title}" (${plan.noteId}); content ${plan.changed ? 'would change' : 'is unchanged'}.`,
|
|
89
103
|
warnings: [
|
|
90
104
|
'Run `dino note content-read <id> --format json` before the real edit and pass the returned readToken as --read-token.',
|
|
105
|
+
...('allowProtectedReplace' in operation && operation.allowProtectedReplace === true
|
|
106
|
+
? ['Protected replacement requires `content-read --include-content`; review the affected protected blocks before using its full-content token.']
|
|
107
|
+
: []),
|
|
91
108
|
...(offline ? ['Execution would run in offline mode and only use the local cache.'] : []),
|
|
92
109
|
],
|
|
93
110
|
inputs: {
|
|
@@ -102,12 +119,14 @@ export async function runNotePatch({ command, options, args }) {
|
|
|
102
119
|
after_chars: plan.afterContent.length,
|
|
103
120
|
changed: plan.changed,
|
|
104
121
|
},
|
|
105
|
-
});
|
|
122
|
+
}, runtime);
|
|
106
123
|
return;
|
|
107
124
|
}
|
|
108
|
-
const readToken = patchOptions.readToken
|
|
125
|
+
const readToken = typeof patchOptions.readToken === 'string'
|
|
126
|
+
? (await parseStringInput(patchOptions.readToken)).trim()
|
|
127
|
+
: '';
|
|
109
128
|
if (!readToken) {
|
|
110
|
-
throw new DinoxError('Real note content edits require --read-token from `dino note content-read <id>`.');
|
|
129
|
+
throw new DinoxError('Real note content edits require --read-token <token|@file> from `dino note content-read <id>`.');
|
|
111
130
|
}
|
|
112
131
|
const result = await applyNoteContentPatch(db, {
|
|
113
132
|
id,
|
|
@@ -124,10 +143,12 @@ export async function runNotePatch({ command, options, args }) {
|
|
|
124
143
|
upload,
|
|
125
144
|
stale: commandStale(runtime),
|
|
126
145
|
});
|
|
127
|
-
const { changed: contentChanged, ...publicResult } = result;
|
|
146
|
+
const { changed: contentChanged, beforeBlocks, afterBlocks, ...publicResult } = result;
|
|
128
147
|
const payload = {
|
|
129
148
|
ok: true,
|
|
130
149
|
...publicResult,
|
|
150
|
+
beforeBlocks: toPublicNoteContentBlocks(beforeBlocks),
|
|
151
|
+
afterBlocks: toPublicNoteContentBlocks(afterBlocks),
|
|
131
152
|
content_changed: contentChanged,
|
|
132
153
|
...receipt,
|
|
133
154
|
};
|
|
@@ -42,7 +42,7 @@ export const noteCommandGroup = {
|
|
|
42
42
|
{ flags: '--sql <expr>', description: 'SQL-like expression over id/content_md/summary/tags/zettel_boxes/created_at/type/is_starred', type: 'string' },
|
|
43
43
|
{ flags: '--limit <n>', description: 'Maximum returned notes', type: 'number' },
|
|
44
44
|
{ flags: '--offset <n>', description: 'Result offset for pagination', type: 'number', defaultValue: 0 },
|
|
45
|
-
{ flags: '--fields <list>', description: 'Comma-separated fields: id,title,summary,tags,created_at,boxes,is_starred', type: 'string[]' },
|
|
45
|
+
{ flags: '--fields <list>', description: 'Comma-separated fields: id,title,summary,tags,created_at,starred_at,boxes,is_starred', type: 'string[]' },
|
|
46
46
|
{ flags: '--include-deleted', description: 'Include soft-deleted notes', type: 'boolean' },
|
|
47
47
|
],
|
|
48
48
|
schema: {
|
|
@@ -54,7 +54,7 @@ export const noteCommandGroup = {
|
|
|
54
54
|
type: 'structured',
|
|
55
55
|
summary: 'AI-friendly note search payload',
|
|
56
56
|
entity: 'note_search_result',
|
|
57
|
-
fields: ['id', 'title', 'summary', 'tags', 'created_at', 'boxes', 'is_starred'],
|
|
57
|
+
fields: ['id', 'title', 'summary', 'tags', 'created_at', 'starred_at', 'boxes', 'is_starred'],
|
|
58
58
|
metaFields: ['query', 'returned_count', 'total_count', 'stale', 'pagination', 'fields', 'applied_filters'],
|
|
59
59
|
},
|
|
60
60
|
examples: [
|
|
@@ -66,9 +66,9 @@ export const noteCommandGroup = {
|
|
|
66
66
|
},
|
|
67
67
|
{
|
|
68
68
|
name: 'get',
|
|
69
|
-
description: 'Get
|
|
69
|
+
description: 'Get lightweight note context by id without exposing full note content',
|
|
70
70
|
arguments: [{ syntax: '<id>', description: 'Note id' }],
|
|
71
|
-
options: [{ flags: '--context-only', description: '
|
|
71
|
+
options: [{ flags: '--context-only', description: 'Compatibility flag; note get is context-only by default', type: 'boolean' }],
|
|
72
72
|
schema: {
|
|
73
73
|
path: 'note.get',
|
|
74
74
|
risk: 'read',
|
|
@@ -76,11 +76,11 @@ export const noteCommandGroup = {
|
|
|
76
76
|
globalOptions: NOTE_GLOBAL_OPTIONS,
|
|
77
77
|
output: {
|
|
78
78
|
type: 'structured',
|
|
79
|
-
summary: '
|
|
79
|
+
summary: 'Lightweight note context payload; use note detail or content-read --include-content for full content',
|
|
80
80
|
entity: 'note_context',
|
|
81
|
-
fields: ['id', 'title', 'summary', 'tags', 'created_at', 'updated_at', 'is_del', 'boxes', 'links'],
|
|
81
|
+
fields: ['stale', 'note.id', 'note.title', 'note.summary', 'note.tags', 'note.created_at', 'note.updated_at', 'note.is_del', 'note.starred_at', 'note.is_starred', 'note.boxes', 'note.links'],
|
|
82
82
|
},
|
|
83
|
-
examples: ['dino note get <id> --
|
|
83
|
+
examples: ['dino note get <id> --format json'],
|
|
84
84
|
},
|
|
85
85
|
action: runNoteGet,
|
|
86
86
|
},
|
|
@@ -115,8 +115,8 @@ export const noteCommandGroup = {
|
|
|
115
115
|
globalOptions: NOTE_GLOBAL_OPTIONS,
|
|
116
116
|
output: {
|
|
117
117
|
type: 'structured',
|
|
118
|
-
summary: '
|
|
119
|
-
fields: ['id', 'title', 'content_md', 'content_text', 'content_json', 'tags', 'boxes', 'created_at', 'updated_at', 'is_del'],
|
|
118
|
+
summary: 'Ordered full-note collection with freshness metadata',
|
|
119
|
+
fields: ['stale', 'count', 'notes.id', 'notes.title', 'notes.content_md', 'notes.content_text', 'notes.content_json', 'notes.properties', 'notes.is_pub', 'notes.starred_at', 'notes.is_starred', 'notes.tags', 'notes.boxes', 'notes.created_at', 'notes.updated_at', 'notes.is_del'],
|
|
120
120
|
},
|
|
121
121
|
examples: ['dino note detail <id> --format json', 'dino note detail --ids @/tmp/note-ids.txt --format json'],
|
|
122
122
|
},
|
|
@@ -155,7 +155,7 @@ export const noteCommandGroup = {
|
|
|
155
155
|
description: 'Read note content context and issue a short-lived token required before content patching',
|
|
156
156
|
arguments: [{ syntax: '<id>', description: 'Note id' }],
|
|
157
157
|
options: [
|
|
158
|
-
{ flags: '--include-content', description: 'Include full markdown
|
|
158
|
+
{ flags: '--include-content', description: 'Include full markdown plus detailed block text and attributes', type: 'boolean' },
|
|
159
159
|
],
|
|
160
160
|
schema: {
|
|
161
161
|
path: 'note.content-read',
|
|
@@ -164,10 +164,13 @@ export const noteCommandGroup = {
|
|
|
164
164
|
globalOptions: NOTE_GLOBAL_OPTIONS,
|
|
165
165
|
output: {
|
|
166
166
|
type: 'structured',
|
|
167
|
-
summary: '
|
|
168
|
-
fields: ['noteId', 'title', 'readToken', 'readTokenExpiresAt', 'base', 'outline', 'blocks', 'derived', 'content', 'stale'],
|
|
167
|
+
summary: 'Privacy-preserving note edit context; full content and detailed blocks require --include-content',
|
|
168
|
+
fields: ['noteId', 'title', 'readToken', 'readTokenExpiresAt', 'readTokenScope', 'base', 'outline', 'blocks.type', 'blocks.editMode', 'blocks.nodeRange', 'blocks.textHash', 'blocks.nodeHash', 'blocks.charCount', 'derived', 'content', 'stale'],
|
|
169
169
|
},
|
|
170
|
-
examples: [
|
|
170
|
+
examples: [
|
|
171
|
+
'dino note content-read <id> --format json',
|
|
172
|
+
'dino note content-read <id> --include-content --format json',
|
|
173
|
+
],
|
|
171
174
|
},
|
|
172
175
|
action: runNoteContentRead,
|
|
173
176
|
},
|
|
@@ -295,8 +298,8 @@ export const noteCommandGroup = {
|
|
|
295
298
|
{ flags: '--replace-block', description: 'Replace one exact markdown block matched by --match', type: 'boolean' },
|
|
296
299
|
{ flags: '--match <string|@file>', description: 'Exact markdown to replace when using --replace-block', type: 'string' },
|
|
297
300
|
{ flags: '--content <string|@file>', description: 'Markdown content to insert', type: 'string' },
|
|
298
|
-
{ flags: '--read-token <token>', description: '
|
|
299
|
-
{ flags: '--allow-protected-replace', description: 'Allow
|
|
301
|
+
{ flags: '--read-token <token|@file>', description: 'Single-use patch capability returned by note content-read; prefer @file to avoid exposing it in argv', type: 'string' },
|
|
302
|
+
{ flags: '--allow-protected-replace', description: 'Allow replacement of media, table, container, or unknown blocks only with a reviewed full-content read token', type: 'boolean' },
|
|
300
303
|
WRITE_DURABILITY_OPTION,
|
|
301
304
|
{ flags: '--dry-run', description: 'Preview the patch without writing', type: 'boolean' },
|
|
302
305
|
],
|
|
@@ -312,7 +315,7 @@ export const noteCommandGroup = {
|
|
|
312
315
|
},
|
|
313
316
|
examples: [
|
|
314
317
|
'dino note content-read <id> --format json',
|
|
315
|
-
'dino note patch <id> --append-section "复盘" --content @/tmp/section.md --read-token
|
|
318
|
+
'dino note patch <id> --append-section "复盘" --content @/tmp/section.md --read-token @/tmp/dinox-read-token --format json',
|
|
316
319
|
],
|
|
317
320
|
},
|
|
318
321
|
action: runNotePatch,
|
|
@@ -59,6 +59,9 @@ function serializeMarkdownExport(note) {
|
|
|
59
59
|
tags: note.tags,
|
|
60
60
|
boxes: note.boxes,
|
|
61
61
|
box_ids: note.box_ids,
|
|
62
|
+
properties: note.properties,
|
|
63
|
+
is_pub: note.is_pub,
|
|
64
|
+
starred_at: note.starred_at,
|
|
62
65
|
is_starred: note.is_starred,
|
|
63
66
|
is_del: note.is_del ?? 0,
|
|
64
67
|
}, {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AbstractPowerSyncDatabase } from '@powersync/common';
|
|
2
|
+
import { type NoteContext, type NoteDetail, type NoteExportItem, type NotePreview, type NoteRow } from './shared.js';
|
|
3
|
+
export declare function getNote(db: AbstractPowerSyncDatabase, id: string): Promise<NoteRow | null>;
|
|
4
|
+
export declare function getNoteContext(db: AbstractPowerSyncDatabase, id: string): Promise<NoteContext | null>;
|
|
5
|
+
export declare function getNotePreview(db: AbstractPowerSyncDatabase, id: string, requestedLines: number): Promise<NotePreview | null>;
|
|
6
|
+
export declare function getNoteDetails(db: AbstractPowerSyncDatabase, ids: string[]): Promise<NoteDetail[]>;
|
|
7
|
+
export declare function getNoteDetail(db: AbstractPowerSyncDatabase, id: string): Promise<NoteDetail | null>;
|
|
8
|
+
export declare function getNoteExportItems(db: AbstractPowerSyncDatabase, ids: string[]): Promise<NoteExportItem[]>;
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
// Note reads: single-note lookup, context, preview, detail, and export rows.
|
|
2
|
+
import { DinoxError } from '../../../utils/errors.js';
|
|
3
|
+
import { loadZettelBoxNameMap, normalizeStoredFlag, normalizeStoredStarred, parseStoredTags, parseZettelBoxIds, resolveSummary, } from './shared.js';
|
|
4
|
+
export async function getNote(db, id) {
|
|
5
|
+
return db.getOptional(`
|
|
6
|
+
SELECT
|
|
7
|
+
id,
|
|
8
|
+
title,
|
|
9
|
+
content_md,
|
|
10
|
+
content_json,
|
|
11
|
+
content_text,
|
|
12
|
+
created_at,
|
|
13
|
+
updated_at,
|
|
14
|
+
is_del,
|
|
15
|
+
starred_at,
|
|
16
|
+
CASE WHEN starred_at IS NOT NULL THEN 1 ELSE 0 END AS is_starred,
|
|
17
|
+
version
|
|
18
|
+
FROM c_note
|
|
19
|
+
WHERE id = ?
|
|
20
|
+
`, [id]);
|
|
21
|
+
}
|
|
22
|
+
async function getLinkCount(db, direction, noteId) {
|
|
23
|
+
const [column, boundColumn] = direction === 'in'
|
|
24
|
+
? ['src_note_id', 'dst_note_id']
|
|
25
|
+
: ['dst_note_id', 'src_note_id'];
|
|
26
|
+
const row = await db.getOptional(`
|
|
27
|
+
SELECT COUNT(1) AS count
|
|
28
|
+
FROM c_note_link l
|
|
29
|
+
JOIN c_note n ON n.id = l.${column}
|
|
30
|
+
WHERE l.${boundColumn} = ?
|
|
31
|
+
AND (l.is_del IS NULL OR l.is_del = 0)
|
|
32
|
+
AND (n.is_del IS NULL OR n.is_del = 0)
|
|
33
|
+
`, [noteId]);
|
|
34
|
+
return Number(row?.count ?? 0);
|
|
35
|
+
}
|
|
36
|
+
function buildPreviewText(contentMd, requestedLines) {
|
|
37
|
+
const normalized = (contentMd ?? '').replace(/\r\n/g, '\n');
|
|
38
|
+
if (!normalized) {
|
|
39
|
+
return {
|
|
40
|
+
preview: '',
|
|
41
|
+
totalLines: 0,
|
|
42
|
+
returnedLines: 0,
|
|
43
|
+
truncated: false,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const lines = normalized.split('\n');
|
|
47
|
+
const selected = lines.slice(0, requestedLines);
|
|
48
|
+
return {
|
|
49
|
+
preview: selected.join('\n'),
|
|
50
|
+
totalLines: lines.length,
|
|
51
|
+
returnedLines: selected.length,
|
|
52
|
+
truncated: selected.length < lines.length,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export async function getNoteContext(db, id) {
|
|
56
|
+
const row = await db.getOptional(`
|
|
57
|
+
SELECT
|
|
58
|
+
n.id,
|
|
59
|
+
n.title,
|
|
60
|
+
n.summary,
|
|
61
|
+
n.content_text,
|
|
62
|
+
n.tags,
|
|
63
|
+
n.created_at,
|
|
64
|
+
n.updated_at,
|
|
65
|
+
n.is_del,
|
|
66
|
+
n.starred_at,
|
|
67
|
+
CASE WHEN n.starred_at IS NOT NULL THEN 1 ELSE 0 END AS is_starred,
|
|
68
|
+
n.zettel_boxes AS zettel_boxes_raw
|
|
69
|
+
FROM c_note n
|
|
70
|
+
WHERE n.id = ?
|
|
71
|
+
`, [id]);
|
|
72
|
+
if (!row) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const boxIds = parseZettelBoxIds(row.zettel_boxes_raw);
|
|
76
|
+
const boxNameMap = await loadZettelBoxNameMap(db, boxIds);
|
|
77
|
+
const [backlinks, outlinks] = await Promise.all([
|
|
78
|
+
getLinkCount(db, 'in', id),
|
|
79
|
+
getLinkCount(db, 'out', id),
|
|
80
|
+
]);
|
|
81
|
+
return {
|
|
82
|
+
id: row.id,
|
|
83
|
+
title: row.title?.trim() ?? '',
|
|
84
|
+
summary: resolveSummary(row.summary, row.content_text),
|
|
85
|
+
tags: parseStoredTags(row.tags),
|
|
86
|
+
created_at: row.created_at,
|
|
87
|
+
updated_at: row.updated_at,
|
|
88
|
+
is_del: row.is_del,
|
|
89
|
+
starred_at: row.starred_at ?? null,
|
|
90
|
+
is_starred: normalizeStoredStarred(row.is_starred),
|
|
91
|
+
zettel_boxes: boxIds
|
|
92
|
+
.map((boxId) => boxNameMap.get(boxId))
|
|
93
|
+
.filter((name) => typeof name === 'string' && name.length > 0),
|
|
94
|
+
links: {
|
|
95
|
+
backlinks,
|
|
96
|
+
outlinks,
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export async function getNotePreview(db, id, requestedLines) {
|
|
101
|
+
const row = await db.getOptional(`
|
|
102
|
+
SELECT
|
|
103
|
+
n.id,
|
|
104
|
+
n.title,
|
|
105
|
+
n.content_md,
|
|
106
|
+
n.updated_at
|
|
107
|
+
FROM c_note n
|
|
108
|
+
WHERE n.id = ?
|
|
109
|
+
`, [id]);
|
|
110
|
+
if (!row) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
const preview = buildPreviewText(row.content_md, requestedLines);
|
|
114
|
+
return {
|
|
115
|
+
id: row.id,
|
|
116
|
+
title: row.title?.trim() ?? '',
|
|
117
|
+
requested_lines: requestedLines,
|
|
118
|
+
total_lines: preview.totalLines,
|
|
119
|
+
returned_lines: preview.returnedLines,
|
|
120
|
+
truncated: preview.truncated,
|
|
121
|
+
preview: preview.preview,
|
|
122
|
+
updated_at: row.updated_at ?? null,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function mapNoteDetailRow(note, boxNameMap) {
|
|
126
|
+
const boxIds = parseZettelBoxIds(note.zettel_boxes_raw);
|
|
127
|
+
const zettelBoxes = boxIds
|
|
128
|
+
.map((boxId) => boxNameMap.get(boxId))
|
|
129
|
+
.filter((name) => typeof name === 'string' && name.length > 0);
|
|
130
|
+
return {
|
|
131
|
+
id: note.id,
|
|
132
|
+
created_at: note.created_at,
|
|
133
|
+
updated_at: note.updated_at,
|
|
134
|
+
title: note.title,
|
|
135
|
+
type: note.type,
|
|
136
|
+
source: note.source,
|
|
137
|
+
tags: parseStoredTags(note.tags),
|
|
138
|
+
is_del: note.is_del,
|
|
139
|
+
is_pub: normalizeStoredFlag(note.is_pub),
|
|
140
|
+
properties: parseStoredNoteProperties(note.properties),
|
|
141
|
+
starred_at: note.starred_at ?? null,
|
|
142
|
+
is_starred: normalizeStoredStarred(note.is_starred),
|
|
143
|
+
content_md: note.content_md,
|
|
144
|
+
content_text: note.content_text,
|
|
145
|
+
content_json: parseStoredJsonValue(note.content_json),
|
|
146
|
+
zettel_boxes: zettelBoxes,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function parseStoredJsonValue(raw) {
|
|
150
|
+
const value = raw?.trim() ?? '';
|
|
151
|
+
if (!value) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
return JSON.parse(value);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return raw;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function parseStoredNoteProperties(raw) {
|
|
162
|
+
const value = raw?.trim() ?? '';
|
|
163
|
+
if (!value) {
|
|
164
|
+
return {};
|
|
165
|
+
}
|
|
166
|
+
let parsed;
|
|
167
|
+
try {
|
|
168
|
+
parsed = JSON.parse(value);
|
|
169
|
+
}
|
|
170
|
+
catch (cause) {
|
|
171
|
+
throw new DinoxError('c_note.properties must be valid JSON', {
|
|
172
|
+
code: 'NOTE_INVALID_PROPERTIES',
|
|
173
|
+
details: { cause: cause instanceof Error ? cause.message : String(cause) },
|
|
174
|
+
recoverable: false,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
178
|
+
throw new DinoxError('c_note.properties must be a JSON object', {
|
|
179
|
+
code: 'NOTE_INVALID_PROPERTIES',
|
|
180
|
+
recoverable: false,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return Object.fromEntries(Object.entries(parsed));
|
|
184
|
+
}
|
|
185
|
+
function mapNoteExportRow(note, boxNameMap) {
|
|
186
|
+
const boxIds = parseZettelBoxIds(note.zettel_boxes_raw);
|
|
187
|
+
const boxes = boxIds
|
|
188
|
+
.map((boxId) => boxNameMap.get(boxId))
|
|
189
|
+
.filter((name) => typeof name === 'string' && name.length > 0);
|
|
190
|
+
return {
|
|
191
|
+
id: note.id,
|
|
192
|
+
created_at: note.created_at,
|
|
193
|
+
updated_at: note.updated_at,
|
|
194
|
+
title: note.title,
|
|
195
|
+
type: note.type,
|
|
196
|
+
source: note.source,
|
|
197
|
+
summary: note.summary,
|
|
198
|
+
tags: parseStoredTags(note.tags),
|
|
199
|
+
is_del: note.is_del,
|
|
200
|
+
is_pub: normalizeStoredFlag(note.is_pub),
|
|
201
|
+
properties: parseStoredNoteProperties(note.properties),
|
|
202
|
+
starred_at: note.starred_at ?? null,
|
|
203
|
+
is_starred: normalizeStoredStarred(note.is_starred),
|
|
204
|
+
content_md: note.content_md ?? '',
|
|
205
|
+
content_text: note.content_text,
|
|
206
|
+
content_json: parseStoredJsonValue(note.content_json),
|
|
207
|
+
box_ids: boxIds,
|
|
208
|
+
boxes,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
export async function getNoteDetails(db, ids) {
|
|
212
|
+
const targetIds = Array.from(new Set(ids
|
|
213
|
+
.map((id) => id.trim())
|
|
214
|
+
.filter((id) => id.length > 0)));
|
|
215
|
+
if (targetIds.length === 0) {
|
|
216
|
+
return [];
|
|
217
|
+
}
|
|
218
|
+
const noteRowsById = new Map();
|
|
219
|
+
const chunkSize = 500;
|
|
220
|
+
for (let index = 0; index < targetIds.length; index += chunkSize) {
|
|
221
|
+
const batch = targetIds.slice(index, index + chunkSize);
|
|
222
|
+
const placeholders = batch.map(() => '?').join(', ');
|
|
223
|
+
const rows = await db.getAll(`
|
|
224
|
+
SELECT
|
|
225
|
+
id,
|
|
226
|
+
created_at,
|
|
227
|
+
updated_at,
|
|
228
|
+
title,
|
|
229
|
+
type,
|
|
230
|
+
source,
|
|
231
|
+
tags,
|
|
232
|
+
is_del,
|
|
233
|
+
is_pub,
|
|
234
|
+
properties,
|
|
235
|
+
starred_at,
|
|
236
|
+
CASE WHEN starred_at IS NOT NULL THEN 1 ELSE 0 END AS is_starred,
|
|
237
|
+
content_md,
|
|
238
|
+
content_text,
|
|
239
|
+
content_json,
|
|
240
|
+
zettel_boxes AS zettel_boxes_raw
|
|
241
|
+
FROM c_note
|
|
242
|
+
WHERE id IN (${placeholders})
|
|
243
|
+
`, batch);
|
|
244
|
+
for (const row of rows) {
|
|
245
|
+
const id = row.id?.trim() ?? '';
|
|
246
|
+
if (!id || noteRowsById.has(id)) {
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
noteRowsById.set(id, row);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
const allBoxIds = Array.from(new Set(Array.from(noteRowsById.values()).flatMap((row) => parseZettelBoxIds(row.zettel_boxes_raw))));
|
|
253
|
+
const boxNameMap = await loadZettelBoxNameMap(db, allBoxIds);
|
|
254
|
+
const details = [];
|
|
255
|
+
for (const id of targetIds) {
|
|
256
|
+
const row = noteRowsById.get(id);
|
|
257
|
+
if (!row) {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
details.push(mapNoteDetailRow(row, boxNameMap));
|
|
261
|
+
}
|
|
262
|
+
return details;
|
|
263
|
+
}
|
|
264
|
+
export async function getNoteDetail(db, id) {
|
|
265
|
+
const details = await getNoteDetails(db, [id]);
|
|
266
|
+
return details[0] ?? null;
|
|
267
|
+
}
|
|
268
|
+
export async function getNoteExportItems(db, ids) {
|
|
269
|
+
const targetIds = Array.from(new Set(ids
|
|
270
|
+
.map((id) => id.trim())
|
|
271
|
+
.filter((id) => id.length > 0)));
|
|
272
|
+
if (targetIds.length === 0) {
|
|
273
|
+
return [];
|
|
274
|
+
}
|
|
275
|
+
const noteRowsById = new Map();
|
|
276
|
+
const chunkSize = 500;
|
|
277
|
+
for (let index = 0; index < targetIds.length; index += chunkSize) {
|
|
278
|
+
const batch = targetIds.slice(index, index + chunkSize);
|
|
279
|
+
const placeholders = batch.map(() => '?').join(', ');
|
|
280
|
+
const rows = await db.getAll(`
|
|
281
|
+
SELECT
|
|
282
|
+
id,
|
|
283
|
+
created_at,
|
|
284
|
+
updated_at,
|
|
285
|
+
title,
|
|
286
|
+
type,
|
|
287
|
+
source,
|
|
288
|
+
tags,
|
|
289
|
+
summary,
|
|
290
|
+
is_del,
|
|
291
|
+
is_pub,
|
|
292
|
+
properties,
|
|
293
|
+
starred_at,
|
|
294
|
+
CASE WHEN starred_at IS NOT NULL THEN 1 ELSE 0 END AS is_starred,
|
|
295
|
+
content_md,
|
|
296
|
+
content_text,
|
|
297
|
+
content_json,
|
|
298
|
+
zettel_boxes AS zettel_boxes_raw
|
|
299
|
+
FROM c_note
|
|
300
|
+
WHERE id IN (${placeholders})
|
|
301
|
+
`, batch);
|
|
302
|
+
for (const row of rows) {
|
|
303
|
+
const id = row.id?.trim() ?? '';
|
|
304
|
+
if (!id || noteRowsById.has(id)) {
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
noteRowsById.set(id, row);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
const allBoxIds = Array.from(new Set(Array.from(noteRowsById.values()).flatMap((row) => parseZettelBoxIds(row.zettel_boxes_raw))));
|
|
311
|
+
const boxNameMap = await loadZettelBoxNameMap(db, allBoxIds);
|
|
312
|
+
const items = [];
|
|
313
|
+
for (const id of targetIds) {
|
|
314
|
+
const row = noteRowsById.get(id);
|
|
315
|
+
if (!row) {
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
items.push(mapNoteExportRow(row, boxNameMap));
|
|
319
|
+
}
|
|
320
|
+
return items;
|
|
321
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AbstractPowerSyncDatabase } from '@powersync/common';
|
|
2
|
+
export declare function validateTagsForCreate(db: AbstractPowerSyncDatabase, requestedTags: string[], userId: string): Promise<string[]>;
|
|
3
|
+
export declare function validateTagsForUpdate(db: AbstractPowerSyncDatabase, requestedTags: string[], userId: string): Promise<string[]>;
|
|
4
|
+
export declare function validateTagsForBulk(db: AbstractPowerSyncDatabase, requestedTags: string[], userId: string): Promise<string[]>;
|
|
5
|
+
export declare function resolveZettelBoxIdsForCreate(db: AbstractPowerSyncDatabase, requestedNames: string[]): Promise<string[]>;
|
|
6
|
+
export declare function resolveZettelBoxIdsForSearch(db: AbstractPowerSyncDatabase, requestedNames: string[]): Promise<string[]>;
|
|
7
|
+
export declare function resolveZettelBoxIdsForUpdate(db: AbstractPowerSyncDatabase, requestedNames: string[]): Promise<string[]>;
|
|
8
|
+
export declare function resolveZettelBoxIdsForBulk(db: AbstractPowerSyncDatabase, requestedNames: string[]): Promise<string[]>;
|