@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
|
@@ -17,11 +17,14 @@ type MCPResponse = {
|
|
|
17
17
|
};
|
|
18
18
|
export declare class MCPServer {
|
|
19
19
|
private db;
|
|
20
|
+
private readonly noteContent;
|
|
20
21
|
initialize(): Promise<void>;
|
|
21
22
|
handleRequest(req: MCPRequest): Promise<MCPResponse | null>;
|
|
22
23
|
private handleInitialize;
|
|
23
24
|
private handleToolsList;
|
|
24
25
|
private handleToolsCall;
|
|
26
|
+
private requireDb;
|
|
27
|
+
private getRuntime;
|
|
25
28
|
close(): Promise<void>;
|
|
26
29
|
}
|
|
27
30
|
export declare function startMCPServer(port?: number, host?: string, authToken?: string): Promise<{
|
|
@@ -2,6 +2,7 @@ import { createServer } from 'node:http';
|
|
|
2
2
|
import { DEFAULT_MCP_PORT } from './types.js';
|
|
3
3
|
import { mcpTools } from './tools.js';
|
|
4
4
|
import { isAuthorizedMCPRequest } from './auth.js';
|
|
5
|
+
import { NOTE_CONTENT_EDIT_TOOL_NAME, NOTE_CONTENT_READ_TOOL_NAME, NoteContentMcpService, buildNoteContentToolError, } from './noteContentTools.js';
|
|
5
6
|
import { resolveAuthIdentity } from '../../auth/userInfo.js';
|
|
6
7
|
import { loadConfig } from '../../config/store.js';
|
|
7
8
|
import { resolveConfig } from '../../config/resolve.js';
|
|
@@ -141,6 +142,7 @@ async function readJsonBody(req) {
|
|
|
141
142
|
}
|
|
142
143
|
export class MCPServer {
|
|
143
144
|
db = null;
|
|
145
|
+
noteContent = new NoteContentMcpService(() => this.requireDb());
|
|
144
146
|
async initialize() {
|
|
145
147
|
const config = resolveConfig(await loadConfig());
|
|
146
148
|
const { db } = await connectPowerSync({ config, offline: false, timeoutMs: config.sync.timeoutMs });
|
|
@@ -204,9 +206,7 @@ export class MCPServer {
|
|
|
204
206
|
return buildResponse(req, { result: { tools: mcpTools } });
|
|
205
207
|
}
|
|
206
208
|
async handleToolsCall(req) {
|
|
207
|
-
|
|
208
|
-
throw new Error('Database not initialized');
|
|
209
|
-
}
|
|
209
|
+
const db = this.requireDb();
|
|
210
210
|
const name = req.params?.name;
|
|
211
211
|
const args = req.params?.arguments ?? {};
|
|
212
212
|
if (typeof name !== 'string') {
|
|
@@ -216,8 +216,9 @@ export class MCPServer {
|
|
|
216
216
|
case 'dinox_search_notes': {
|
|
217
217
|
const keyword = typeof args.query === 'string' ? args.query : '';
|
|
218
218
|
const tagsExpression = typeof args.tags === 'string' ? args.tags.trim() : '';
|
|
219
|
-
const
|
|
220
|
-
const
|
|
219
|
+
const rawBoxes = args.boxes ?? args.box;
|
|
220
|
+
const { names: boxNames, matchEmpty: matchEmptyBoxes } = parseBoxFilter(rawBoxes);
|
|
221
|
+
const zettelBoxIds = await resolveZettelBoxIdsForSearch(db, boxNames);
|
|
221
222
|
const daysInput = typeof args.days === 'number' ? String(args.days) : args.days;
|
|
222
223
|
const { createdAtFrom, createdAtTo } = parseCreatedAtRange({
|
|
223
224
|
from: args.from,
|
|
@@ -227,7 +228,7 @@ export class MCPServer {
|
|
|
227
228
|
const limit = normalizeLimit(args.limit);
|
|
228
229
|
const offset = normalizeOffset(args.offset);
|
|
229
230
|
const fields = parseNoteSearchFields(args.fields, 'dinox_search_notes.fields');
|
|
230
|
-
const rows = await searchNotes(
|
|
231
|
+
const rows = await searchNotes(db, keyword, {
|
|
231
232
|
includeDeleted: false,
|
|
232
233
|
tagsExpression: tagsExpression || undefined,
|
|
233
234
|
createdAtFrom,
|
|
@@ -255,7 +256,7 @@ export class MCPServer {
|
|
|
255
256
|
if (typeof args.id !== 'string' || !args.id.trim()) {
|
|
256
257
|
throw new Error('dinox_get_note: id is required');
|
|
257
258
|
}
|
|
258
|
-
const notes = await getNoteDetails(
|
|
259
|
+
const notes = await getNoteDetails(db, [args.id.trim()]);
|
|
259
260
|
const text = JSON.stringify(notes[0] ?? null);
|
|
260
261
|
return buildResponse(req, { result: { content: [{ type: 'text', text }] } });
|
|
261
262
|
}
|
|
@@ -271,12 +272,12 @@ export class MCPServer {
|
|
|
271
272
|
const rawConfig = await loadConfig();
|
|
272
273
|
const config = resolveConfig(rawConfig);
|
|
273
274
|
const identity = await resolveAuthIdentity(rawConfig, config);
|
|
274
|
-
const canonicalContentMd = await canonicalizeBidlinksInMarkdown(
|
|
275
|
+
const canonicalContentMd = await canonicalizeBidlinksInMarkdown(db, contentMd, identity.userId);
|
|
275
276
|
const { contentJson, contentText } = convertMarkdownToNoteContent(canonicalContentMd);
|
|
276
|
-
const tags = await validateTagsForCreate(
|
|
277
|
-
const zettelBoxIds = await resolveZettelBoxIdsForCreate(
|
|
277
|
+
const tags = await validateTagsForCreate(db, normalizeStringArray(args.tags));
|
|
278
|
+
const zettelBoxIds = await resolveZettelBoxIdsForCreate(db, normalizeStringArray(args.boxes));
|
|
278
279
|
const id = generateId();
|
|
279
|
-
await createNote(
|
|
280
|
+
await createNote(db, {
|
|
280
281
|
id,
|
|
281
282
|
title,
|
|
282
283
|
noteType: 'crawl',
|
|
@@ -294,7 +295,7 @@ export class MCPServer {
|
|
|
294
295
|
if (typeof args.id !== 'string' || !args.id.trim()) {
|
|
295
296
|
throw new Error('dinox_graph_backlinks: id is required');
|
|
296
297
|
}
|
|
297
|
-
const result = await getBacklinks(
|
|
298
|
+
const result = await getBacklinks(db, args.id.trim());
|
|
298
299
|
const text = JSON.stringify(result);
|
|
299
300
|
return buildResponse(req, { result: { content: [{ type: 'text', text }] } });
|
|
300
301
|
}
|
|
@@ -302,7 +303,7 @@ export class MCPServer {
|
|
|
302
303
|
if (typeof args.id !== 'string' || !args.id.trim()) {
|
|
303
304
|
throw new Error('dinox_graph_outlinks: id is required');
|
|
304
305
|
}
|
|
305
|
-
const result = await getOutlinks(
|
|
306
|
+
const result = await getOutlinks(db, args.id.trim());
|
|
306
307
|
const text = JSON.stringify(result);
|
|
307
308
|
return buildResponse(req, { result: { content: [{ type: 'text', text }] } });
|
|
308
309
|
}
|
|
@@ -314,12 +315,12 @@ export class MCPServer {
|
|
|
314
315
|
? Math.trunc(args.depth)
|
|
315
316
|
: 2;
|
|
316
317
|
const depth = Math.min(Math.max(depthRaw, 1), 5);
|
|
317
|
-
const result = await getRelatedNotes(
|
|
318
|
+
const result = await getRelatedNotes(db, args.id.trim(), depth);
|
|
318
319
|
const text = JSON.stringify(result);
|
|
319
320
|
return buildResponse(req, { result: { content: [{ type: 'text', text }] } });
|
|
320
321
|
}
|
|
321
322
|
case 'dinox_graph_stats': {
|
|
322
|
-
const result = await getGraphStats(
|
|
323
|
+
const result = await getGraphStats(db);
|
|
323
324
|
const text = JSON.stringify(result);
|
|
324
325
|
return buildResponse(req, { result: { content: [{ type: 'text', text }] } });
|
|
325
326
|
}
|
|
@@ -329,7 +330,7 @@ export class MCPServer {
|
|
|
329
330
|
? args.status
|
|
330
331
|
: 'all';
|
|
331
332
|
const limit = normalizeLimit(args.limit);
|
|
332
|
-
const result = await searchTodoTasks(
|
|
333
|
+
const result = await searchTodoTasks(db, query, {
|
|
333
334
|
includeDeleted: false,
|
|
334
335
|
taskStatus: status || 'all',
|
|
335
336
|
taskTagFilter: normalizeStringArray(args.tags),
|
|
@@ -339,10 +340,38 @@ export class MCPServer {
|
|
|
339
340
|
const text = JSON.stringify(result);
|
|
340
341
|
return buildResponse(req, { result: { content: [{ type: 'text', text }] } });
|
|
341
342
|
}
|
|
343
|
+
case NOTE_CONTENT_READ_TOOL_NAME: {
|
|
344
|
+
const runtime = await this.getRuntime();
|
|
345
|
+
const result = await this.noteContent.read(args, runtime).catch((error) => buildNoteContentToolError(error));
|
|
346
|
+
const text = JSON.stringify(result);
|
|
347
|
+
return buildResponse(req, { result: { content: [{ type: 'text', text }] } });
|
|
348
|
+
}
|
|
349
|
+
case NOTE_CONTENT_EDIT_TOOL_NAME: {
|
|
350
|
+
const runtime = await this.getRuntime();
|
|
351
|
+
const operations = Array.isArray(args.operations) ? args.operations : [];
|
|
352
|
+
const result = await this.noteContent.edit({
|
|
353
|
+
readToken: args.readToken,
|
|
354
|
+
operations,
|
|
355
|
+
}, runtime).catch((error) => buildNoteContentToolError(error, operations));
|
|
356
|
+
const text = JSON.stringify(result);
|
|
357
|
+
return buildResponse(req, { result: { content: [{ type: 'text', text }] } });
|
|
358
|
+
}
|
|
342
359
|
default:
|
|
343
360
|
throw new Error(`Unknown tool: ${name}`);
|
|
344
361
|
}
|
|
345
362
|
}
|
|
363
|
+
requireDb() {
|
|
364
|
+
if (!this.db) {
|
|
365
|
+
throw new Error('Database not initialized');
|
|
366
|
+
}
|
|
367
|
+
return this.db;
|
|
368
|
+
}
|
|
369
|
+
async getRuntime() {
|
|
370
|
+
const rawConfig = await loadConfig();
|
|
371
|
+
const config = resolveConfig(rawConfig);
|
|
372
|
+
const identity = await resolveAuthIdentity(rawConfig, config);
|
|
373
|
+
return { userId: identity.userId };
|
|
374
|
+
}
|
|
346
375
|
async close() {
|
|
347
376
|
if (this.db) {
|
|
348
377
|
await this.db.close().catch(() => undefined);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { listMcpCommandSchemas } from '../schema/spec.js';
|
|
2
|
+
import { noteContentMcpTools } from './noteContentTools.js';
|
|
2
3
|
function toMcpProperty(property) {
|
|
3
4
|
if (property.type === 'string[]') {
|
|
4
5
|
return {
|
|
@@ -14,7 +15,7 @@ function toMcpProperty(property) {
|
|
|
14
15
|
...(property.enum ? { enum: property.enum } : {}),
|
|
15
16
|
};
|
|
16
17
|
}
|
|
17
|
-
|
|
18
|
+
const schemaBackedMcpTools = listMcpCommandSchemas().map((schema) => {
|
|
18
19
|
const mcp = schema.mcp;
|
|
19
20
|
const properties = Object.fromEntries(mcp.properties.map((property) => [property.name, toMcpProperty(property)]));
|
|
20
21
|
const required = mcp.properties.filter((property) => property.required).map((property) => property.name);
|
|
@@ -28,3 +29,4 @@ export const mcpTools = listMcpCommandSchemas().map((schema) => {
|
|
|
28
29
|
},
|
|
29
30
|
};
|
|
30
31
|
});
|
|
32
|
+
export const mcpTools = [...schemaBackedMcpTools, ...noteContentMcpTools];
|
|
@@ -4,12 +4,16 @@ export type MCPTool = {
|
|
|
4
4
|
inputSchema: {
|
|
5
5
|
type: 'object';
|
|
6
6
|
properties: Record<string, {
|
|
7
|
-
type: 'string' | 'number' | 'boolean' | 'array';
|
|
7
|
+
type: 'string' | 'number' | 'boolean' | 'array' | 'object';
|
|
8
8
|
description?: string;
|
|
9
9
|
enum?: string[];
|
|
10
10
|
items?: {
|
|
11
|
-
type: 'string';
|
|
11
|
+
type: 'string' | 'object';
|
|
12
12
|
};
|
|
13
|
+
properties?: Record<string, {
|
|
14
|
+
type: 'string' | 'number' | 'boolean' | 'array' | 'object';
|
|
15
|
+
description?: string;
|
|
16
|
+
}>;
|
|
13
17
|
}>;
|
|
14
18
|
required?: string[];
|
|
15
19
|
};
|
|
@@ -1,32 +1,16 @@
|
|
|
1
|
-
import { loadConfig } from '../../config/store.js';
|
|
2
|
-
import { resolveConfig } from '../../config/resolve.js';
|
|
3
|
-
import { connectPowerSync, waitForIdleDataFlow } from '../../powersync/runtime.js';
|
|
4
1
|
import { registerCommandGroup } from '../descriptor/register.js';
|
|
5
2
|
import { OFFLINE_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
|
|
6
3
|
import { printSuccess } from '../../utils/output.js';
|
|
7
|
-
import {
|
|
4
|
+
import { commandStale, syncBeforeCommand, withCommandRuntime } from '../runtime.js';
|
|
8
5
|
import { getMetaSchema, getMetaStats } from './repo.js';
|
|
9
|
-
async function syncBeforeMetaCommand(db, offline, timeoutMs) {
|
|
10
|
-
if (offline) {
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
await waitForIdleDataFlow(db, Math.min(timeoutMs, 10_000));
|
|
14
|
-
}
|
|
15
6
|
async function runMetaStats(context) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const timeoutMs = parseSyncTimeoutMs(context.globals.syncTimeout) ?? config.sync.timeoutMs;
|
|
19
|
-
const { db, stale } = await connectPowerSync({ config, offline, timeoutMs });
|
|
20
|
-
try {
|
|
21
|
-
await syncBeforeMetaCommand(db, offline, timeoutMs);
|
|
7
|
+
await withCommandRuntime(context, async (runtime) => {
|
|
8
|
+
await syncBeforeCommand(runtime);
|
|
22
9
|
printSuccess({
|
|
23
|
-
stale:
|
|
24
|
-
stats: await getMetaStats(db),
|
|
10
|
+
stale: commandStale(runtime),
|
|
11
|
+
stats: await getMetaStats(runtime.db),
|
|
25
12
|
});
|
|
26
|
-
}
|
|
27
|
-
finally {
|
|
28
|
-
await db.close().catch(() => undefined);
|
|
29
|
-
}
|
|
13
|
+
});
|
|
30
14
|
}
|
|
31
15
|
async function runMetaSchema() {
|
|
32
16
|
printSuccess(getMetaSchema());
|
|
@@ -67,10 +67,7 @@ export function getMetaSchema() {
|
|
|
67
67
|
fields: ['notes_count', 'tags_count', 'boxes_count', 'latest_note_updated_at'],
|
|
68
68
|
},
|
|
69
69
|
command_schema: {
|
|
70
|
-
fields: ['path', 'command', 'summary', 'risk', 'auth', 'globalOptions', 'arguments', 'options', 'output', 'examples'
|
|
71
|
-
},
|
|
72
|
-
mcp_tool: {
|
|
73
|
-
fields: ['name', 'description', 'inputSchema'],
|
|
70
|
+
fields: ['path', 'command', 'summary', 'risk', 'auth', 'globalOptions', 'arguments', 'options', 'output', 'examples'],
|
|
74
71
|
},
|
|
75
72
|
},
|
|
76
73
|
};
|
|
@@ -6,5 +6,7 @@ export declare function runNoteDetail({ command, options, args }: CommandExecuti
|
|
|
6
6
|
export declare function runNoteCreate({ command, options }: CommandExecutionContext): Promise<void>;
|
|
7
7
|
export declare function runNoteDelete({ command, options, args }: CommandExecutionContext): Promise<void>;
|
|
8
8
|
export declare function runNoteUpdate({ command, options, args }: CommandExecutionContext): Promise<void>;
|
|
9
|
+
export declare function runNoteTag({ command, options, args }: CommandExecutionContext): Promise<void>;
|
|
10
|
+
export declare function runNoteMove({ command, options, args }: CommandExecutionContext): Promise<void>;
|
|
9
11
|
export declare function runNoteStar({ command, options, args }: CommandExecutionContext): Promise<void>;
|
|
10
12
|
export declare function runNoteUnstar({ command, options, args }: CommandExecutionContext): Promise<void>;
|
|
@@ -5,9 +5,10 @@ import { generateId } from '../../utils/id.js';
|
|
|
5
5
|
import { convertMarkdownToNoteContent } from '../../utils/tiptapMarkdown.js';
|
|
6
6
|
import { getRequestedStructuredOutputFormat, printHumanReadableLine, printSuccess, } from '../../utils/output.js';
|
|
7
7
|
import { DinoxError } from '../../utils/errors.js';
|
|
8
|
-
import {
|
|
8
|
+
import { commandStale, printStaleCacheWarning, printUploadDisabledWarning, waitForMutationUpload, } from '../runtime.js';
|
|
9
9
|
import { createNote, getNote, getNoteContext, getNoteDetails, getNotePreview, assertNotesUpdatable, resolveZettelBoxIdsForCreate, resolveZettelBoxIdsForSearch, resolveZettelBoxIdsForUpdate, searchNotes, softDeleteNote, updateNote, validateTagsForCreate, validateTagsForUpdate, } from './repo.js';
|
|
10
10
|
import { buildNoteCreateDryRunPreview, buildNoteDeleteDryRunPreview, buildNoteUpdateDryRunPreview, } from './dryRun.js';
|
|
11
|
+
import { applyNoteMetadataMutation, planNoteMetadataMutation, } from './metadataMutation.js';
|
|
11
12
|
import { parseNoteSearchFields, projectNoteSearchFields } from './searchOutput.js';
|
|
12
13
|
import { parseCreatedAtRange } from './searchTime.js';
|
|
13
14
|
import { buildNoteSearchResponse, normalizeDaysForMeta } from './searchResponse.js';
|
|
@@ -39,13 +40,11 @@ function toPublicNote(note) {
|
|
|
39
40
|
...(zettel_boxes !== undefined ? { boxes: zettel_boxes } : {}),
|
|
40
41
|
};
|
|
41
42
|
}
|
|
42
|
-
async function settleMutation(db, timeoutMs) {
|
|
43
|
-
await waitForIdleDataFlow(db, Math.min(timeoutMs, 5_000));
|
|
44
|
-
}
|
|
45
43
|
async function executeNoteUpdateOperation(args) {
|
|
46
44
|
const structuredOutput = getRequestedStructuredOutputFormat() !== null;
|
|
47
|
-
await withNoteDatabase(args.command, async (
|
|
48
|
-
|
|
45
|
+
await withNoteDatabase(args.command, async (runtime) => {
|
|
46
|
+
const { db, config, offline } = runtime;
|
|
47
|
+
await syncBeforeNoteCommand(runtime);
|
|
49
48
|
const ids = await parseTargetNoteIds(args.id, args.idsOption, 'update');
|
|
50
49
|
const hasTags = typeof args.tagsOption === 'string';
|
|
51
50
|
const hasBoxes = typeof args.boxOption === 'string';
|
|
@@ -85,12 +84,8 @@ async function executeNoteUpdateOperation(args) {
|
|
|
85
84
|
}));
|
|
86
85
|
}
|
|
87
86
|
const updatedCount = results.filter((item) => item.updated).length;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
if (!offline) {
|
|
92
|
-
await settleMutation(db, timeoutMs);
|
|
93
|
-
}
|
|
87
|
+
printUploadDisabledWarning(runtime);
|
|
88
|
+
await waitForMutationUpload(runtime);
|
|
94
89
|
const payload = {
|
|
95
90
|
ok: true,
|
|
96
91
|
total: results.length,
|
|
@@ -102,7 +97,7 @@ async function executeNoteUpdateOperation(args) {
|
|
|
102
97
|
boxes: item.zettelBoxIds,
|
|
103
98
|
is_starred: item.is_starred,
|
|
104
99
|
})),
|
|
105
|
-
stale:
|
|
100
|
+
stale: commandStale(runtime),
|
|
106
101
|
};
|
|
107
102
|
if (structuredOutput) {
|
|
108
103
|
printSuccess(payload);
|
|
@@ -125,15 +120,101 @@ async function executeNoteUpdateOperation(args) {
|
|
|
125
120
|
else {
|
|
126
121
|
printHumanReadableLine(`Updated ${updatedCount}/${results.length} notes`);
|
|
127
122
|
}
|
|
128
|
-
|
|
129
|
-
|
|
123
|
+
printStaleCacheWarning(runtime);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function selectMetadataOperation(options) {
|
|
127
|
+
const candidates = [
|
|
128
|
+
['--add', options.add],
|
|
129
|
+
['--remove', options.remove],
|
|
130
|
+
['--replace', options.replace],
|
|
131
|
+
].filter((entry) => typeof entry[1] === 'string');
|
|
132
|
+
if (candidates.length !== 1) {
|
|
133
|
+
throw new DinoxError('Provide exactly one of --add, --remove, or --replace');
|
|
134
|
+
}
|
|
135
|
+
const [optionName, rawValues] = candidates[0];
|
|
136
|
+
return {
|
|
137
|
+
operation: optionName === '--add' ? 'add' : optionName === '--remove' ? 'remove' : 'replace',
|
|
138
|
+
rawValues,
|
|
139
|
+
optionName,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
async function executeNoteMetadataListOperation(args) {
|
|
143
|
+
const structuredOutput = getRequestedStructuredOutputFormat() !== null;
|
|
144
|
+
await withNoteDatabase(args.command, async (runtime) => {
|
|
145
|
+
const { db, config, offline } = runtime;
|
|
146
|
+
await syncBeforeNoteCommand(runtime);
|
|
147
|
+
const ids = await parseTargetNoteIds(args.id, args.idsOption, 'update');
|
|
148
|
+
const selected = selectMetadataOperation(args.options);
|
|
149
|
+
const requestedValues = await parseStringListInput(selected.rawValues, selected.optionName);
|
|
150
|
+
const values = args.field === 'tags'
|
|
151
|
+
? await validateTagsForUpdate(db, requestedValues)
|
|
152
|
+
: await resolveZettelBoxIdsForUpdate(db, requestedValues);
|
|
153
|
+
const plan = await planNoteMetadataMutation(db, {
|
|
154
|
+
ids,
|
|
155
|
+
field: args.field,
|
|
156
|
+
operation: selected.operation,
|
|
157
|
+
values,
|
|
158
|
+
displayValues: requestedValues,
|
|
159
|
+
});
|
|
160
|
+
if (args.options.dryRun) {
|
|
161
|
+
printDryRunPreview({
|
|
162
|
+
dry_run: true,
|
|
163
|
+
command: args.commandName,
|
|
164
|
+
action: args.actionName,
|
|
165
|
+
risk: 'write',
|
|
166
|
+
summary: `Preview ${args.field} ${selected.operation} for ${plan.total} note(s); ${plan.changedCount} note(s) would change.`,
|
|
167
|
+
warnings: [
|
|
168
|
+
...(offline ? ['Execution would run in offline mode and only use the local cache.'] : []),
|
|
169
|
+
...(!offline && !config.powersync.uploadBaseUrl
|
|
170
|
+
? ['Execution would be local-only because powersync.uploadBaseUrl is unset.']
|
|
171
|
+
: []),
|
|
172
|
+
],
|
|
173
|
+
inputs: {
|
|
174
|
+
ids,
|
|
175
|
+
operation: selected.operation,
|
|
176
|
+
[args.field]: args.field === 'tags' ? values : requestedValues,
|
|
177
|
+
},
|
|
178
|
+
targets: plan.results.map((item) => ({ id: item.id, title: item.title })),
|
|
179
|
+
changes: plan.results.map((item) => ({
|
|
180
|
+
id: item.id,
|
|
181
|
+
title: item.title,
|
|
182
|
+
[`${args.field}_from`]: item.before,
|
|
183
|
+
[`${args.field}_to`]: item.after,
|
|
184
|
+
changed: item.changed,
|
|
185
|
+
})),
|
|
186
|
+
});
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
await applyNoteMetadataMutation(db, plan);
|
|
190
|
+
printUploadDisabledWarning(runtime);
|
|
191
|
+
await waitForMutationUpload(runtime);
|
|
192
|
+
const payload = {
|
|
193
|
+
ok: true,
|
|
194
|
+
field: args.field,
|
|
195
|
+
operation: selected.operation,
|
|
196
|
+
total: plan.total,
|
|
197
|
+
updatedCount: plan.changedCount,
|
|
198
|
+
results: plan.results.map((item) => ({
|
|
199
|
+
id: item.id,
|
|
200
|
+
updated: item.changed,
|
|
201
|
+
[args.field]: item.after,
|
|
202
|
+
})),
|
|
203
|
+
stale: commandStale(runtime),
|
|
204
|
+
};
|
|
205
|
+
if (structuredOutput) {
|
|
206
|
+
printSuccess(payload);
|
|
207
|
+
return;
|
|
130
208
|
}
|
|
209
|
+
printHumanReadableLine(`Updated ${plan.changedCount}/${plan.total} notes`);
|
|
210
|
+
printStaleCacheWarning(runtime);
|
|
131
211
|
});
|
|
132
212
|
}
|
|
133
213
|
export async function runNoteSearch({ command, options, args }) {
|
|
134
214
|
const searchOptions = options;
|
|
135
|
-
await withNoteDatabase(command, async (
|
|
136
|
-
|
|
215
|
+
await withNoteDatabase(command, async (runtime) => {
|
|
216
|
+
const { db } = runtime;
|
|
217
|
+
await syncBeforeNoteCommand(runtime);
|
|
137
218
|
const keyword = args.query?.trim() ?? '';
|
|
138
219
|
const tagsExpression = typeof searchOptions.tags === 'string' ? searchOptions.tags.trim() : '';
|
|
139
220
|
const { createdAtFrom, createdAtTo } = parseCreatedAtRange({
|
|
@@ -167,7 +248,7 @@ export async function runNoteSearch({ command, options, args }) {
|
|
|
167
248
|
query: keyword,
|
|
168
249
|
notes: projectNoteSearchFields(rows.slice(offset, offset + limit), fields),
|
|
169
250
|
totalCount: rows.length,
|
|
170
|
-
stale:
|
|
251
|
+
stale: commandStale(runtime),
|
|
171
252
|
includeDeleted: Boolean(searchOptions.includeDeleted),
|
|
172
253
|
limit,
|
|
173
254
|
offset,
|
|
@@ -191,8 +272,9 @@ export async function runNoteGet({ command, options, args }) {
|
|
|
191
272
|
throw new DinoxError('Note id is required');
|
|
192
273
|
}
|
|
193
274
|
const structuredOutput = getRequestedStructuredOutputFormat() !== null;
|
|
194
|
-
await withNoteDatabase(command, async (
|
|
195
|
-
|
|
275
|
+
await withNoteDatabase(command, async (runtime) => {
|
|
276
|
+
const { db } = runtime;
|
|
277
|
+
await syncBeforeNoteCommand(runtime);
|
|
196
278
|
if (getOptions.contextOnly) {
|
|
197
279
|
const context = await getNoteContext(db, id);
|
|
198
280
|
if (!context) {
|
|
@@ -202,7 +284,7 @@ export async function runNoteGet({ command, options, args }) {
|
|
|
202
284
|
recoverable: true,
|
|
203
285
|
});
|
|
204
286
|
}
|
|
205
|
-
printSuccess({ stale:
|
|
287
|
+
printSuccess({ stale: commandStale(runtime), note: toPublicNoteContext(context) });
|
|
206
288
|
return;
|
|
207
289
|
}
|
|
208
290
|
const note = await getNote(db, id);
|
|
@@ -214,12 +296,10 @@ export async function runNoteGet({ command, options, args }) {
|
|
|
214
296
|
});
|
|
215
297
|
}
|
|
216
298
|
if (structuredOutput) {
|
|
217
|
-
printSuccess({ stale, note: toPublicNote(note) });
|
|
299
|
+
printSuccess({ stale: commandStale(runtime), note: toPublicNote(note) });
|
|
218
300
|
return;
|
|
219
301
|
}
|
|
220
|
-
|
|
221
|
-
printHumanReadableLine('warning: sync timed out; result may be stale');
|
|
222
|
-
}
|
|
302
|
+
printStaleCacheWarning(runtime, 'warning: sync timed out; result may be stale');
|
|
223
303
|
printHumanReadableLine(`${note.id}`);
|
|
224
304
|
printHumanReadableLine(`title: ${note.title ?? ''}`);
|
|
225
305
|
if (note.updated_at) {
|
|
@@ -239,8 +319,9 @@ export async function runNotePreview({ command, options, args }) {
|
|
|
239
319
|
if (!id) {
|
|
240
320
|
throw new DinoxError('Note id is required');
|
|
241
321
|
}
|
|
242
|
-
await withNoteDatabase(command, async (
|
|
243
|
-
|
|
322
|
+
await withNoteDatabase(command, async (runtime) => {
|
|
323
|
+
const { db } = runtime;
|
|
324
|
+
await syncBeforeNoteCommand(runtime);
|
|
244
325
|
const preview = await getNotePreview(db, id, parsePreviewLineCount(previewOptions.lines));
|
|
245
326
|
if (!preview) {
|
|
246
327
|
throw new DinoxError(`Note not found: ${id}`, {
|
|
@@ -249,13 +330,14 @@ export async function runNotePreview({ command, options, args }) {
|
|
|
249
330
|
recoverable: true,
|
|
250
331
|
});
|
|
251
332
|
}
|
|
252
|
-
printSuccess({ stale:
|
|
333
|
+
printSuccess({ stale: commandStale(runtime), note: preview });
|
|
253
334
|
});
|
|
254
335
|
}
|
|
255
336
|
export async function runNoteDetail({ command, options, args }) {
|
|
256
337
|
const detailOptions = options;
|
|
257
|
-
await withNoteDatabase(command, async (
|
|
258
|
-
|
|
338
|
+
await withNoteDatabase(command, async (runtime) => {
|
|
339
|
+
const { db } = runtime;
|
|
340
|
+
await syncBeforeNoteCommand(runtime);
|
|
259
341
|
const ids = await parseTargetNoteIds(args.id, detailOptions.ids, 'detail');
|
|
260
342
|
const notes = await getNoteDetails(db, ids);
|
|
261
343
|
const foundIds = new Set(notes.map((note) => note.id));
|
|
@@ -270,9 +352,10 @@ export async function runNoteDetail({ command, options, args }) {
|
|
|
270
352
|
export async function runNoteCreate({ command, options }) {
|
|
271
353
|
const createOptions = options;
|
|
272
354
|
const structuredOutput = getRequestedStructuredOutputFormat() !== null;
|
|
273
|
-
await withNoteDatabase(command, async (
|
|
274
|
-
|
|
275
|
-
|
|
355
|
+
await withNoteDatabase(command, async (runtime) => {
|
|
356
|
+
const { db, config, offline } = runtime;
|
|
357
|
+
await syncBeforeNoteCommand(runtime);
|
|
358
|
+
const identity = await resolveAuthIdentity(config);
|
|
276
359
|
const contentMd = await parseStringInput(String(createOptions.content));
|
|
277
360
|
const canonicalContentMd = await canonicalizeBidlinksInMarkdown(db, contentMd, identity.userId);
|
|
278
361
|
const { contentJson, contentText } = convertMarkdownToNoteContent(canonicalContentMd);
|
|
@@ -309,21 +392,15 @@ export async function runNoteCreate({ command, options }) {
|
|
|
309
392
|
zettelBoxIds: await resolveZettelBoxIdsForCreate(db, boxNames),
|
|
310
393
|
userId: identity.userId,
|
|
311
394
|
});
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
}
|
|
315
|
-
if (!offline) {
|
|
316
|
-
await settleMutation(db, timeoutMs);
|
|
317
|
-
}
|
|
318
|
-
const payload = { ok: true, id, stale: offline ? true : stale };
|
|
395
|
+
printUploadDisabledWarning(runtime);
|
|
396
|
+
await waitForMutationUpload(runtime);
|
|
397
|
+
const payload = { ok: true, id, stale: commandStale(runtime) };
|
|
319
398
|
if (structuredOutput) {
|
|
320
399
|
printSuccess(payload);
|
|
321
400
|
return;
|
|
322
401
|
}
|
|
323
402
|
printHumanReadableLine(id);
|
|
324
|
-
|
|
325
|
-
printHumanReadableLine('warning: sync timed out; local cache may be stale');
|
|
326
|
-
}
|
|
403
|
+
printStaleCacheWarning(runtime);
|
|
327
404
|
});
|
|
328
405
|
}
|
|
329
406
|
export async function runNoteDelete({ command, options, args }) {
|
|
@@ -333,28 +410,23 @@ export async function runNoteDelete({ command, options, args }) {
|
|
|
333
410
|
throw new DinoxError('Note id is required');
|
|
334
411
|
}
|
|
335
412
|
const structuredOutput = getRequestedStructuredOutputFormat() !== null;
|
|
336
|
-
await withNoteDatabase(command, async (
|
|
337
|
-
|
|
413
|
+
await withNoteDatabase(command, async (runtime) => {
|
|
414
|
+
const { db, config, offline } = runtime;
|
|
415
|
+
await syncBeforeNoteCommand(runtime);
|
|
338
416
|
if (Boolean(deleteOptions.dryRun)) {
|
|
339
417
|
printDryRunPreview(await buildNoteDeleteDryRunPreview({ db, config, offline, id }));
|
|
340
418
|
return;
|
|
341
419
|
}
|
|
342
420
|
await softDeleteNote(db, id);
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
}
|
|
346
|
-
if (!offline) {
|
|
347
|
-
await settleMutation(db, timeoutMs);
|
|
348
|
-
}
|
|
349
|
-
const payload = { ok: true, id, stale: offline ? true : stale };
|
|
421
|
+
printUploadDisabledWarning(runtime);
|
|
422
|
+
await waitForMutationUpload(runtime);
|
|
423
|
+
const payload = { ok: true, id, stale: commandStale(runtime) };
|
|
350
424
|
if (structuredOutput) {
|
|
351
425
|
printSuccess(payload);
|
|
352
426
|
return;
|
|
353
427
|
}
|
|
354
428
|
printHumanReadableLine('OK');
|
|
355
|
-
|
|
356
|
-
printHumanReadableLine('warning: sync timed out; local cache may be stale');
|
|
357
|
-
}
|
|
429
|
+
printStaleCacheWarning(runtime);
|
|
358
430
|
});
|
|
359
431
|
}
|
|
360
432
|
export async function runNoteUpdate({ command, options, args }) {
|
|
@@ -373,6 +445,28 @@ export async function runNoteUpdate({ command, options, args }) {
|
|
|
373
445
|
actionName: 'update_note',
|
|
374
446
|
});
|
|
375
447
|
}
|
|
448
|
+
export async function runNoteTag({ command, options, args }) {
|
|
449
|
+
await executeNoteMetadataListOperation({
|
|
450
|
+
command,
|
|
451
|
+
id: args.id,
|
|
452
|
+
idsOption: options.ids,
|
|
453
|
+
options: options,
|
|
454
|
+
field: 'tags',
|
|
455
|
+
commandName: 'dino note tag',
|
|
456
|
+
actionName: 'mutate_note_tags',
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
export async function runNoteMove({ command, options, args }) {
|
|
460
|
+
await executeNoteMetadataListOperation({
|
|
461
|
+
command,
|
|
462
|
+
id: args.id,
|
|
463
|
+
idsOption: options.ids,
|
|
464
|
+
options: options,
|
|
465
|
+
field: 'boxes',
|
|
466
|
+
commandName: 'dino note move',
|
|
467
|
+
actionName: 'mutate_note_boxes',
|
|
468
|
+
});
|
|
469
|
+
}
|
|
376
470
|
export async function runNoteStar({ command, options, args }) {
|
|
377
471
|
const starOptions = options;
|
|
378
472
|
await executeNoteUpdateOperation({
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { CommandExecutionContext } from '../descriptor/types.js';
|
|
2
|
+
import type { NoteMetadataListField, NoteMetadataOperation } from './metadataMutation.js';
|
|
3
|
+
type NoteBulkOptions = {
|
|
4
|
+
tags?: string;
|
|
5
|
+
from?: string;
|
|
6
|
+
to?: string;
|
|
7
|
+
days?: string;
|
|
8
|
+
starred?: string;
|
|
9
|
+
boxes?: string;
|
|
10
|
+
all?: boolean;
|
|
11
|
+
tagAdd?: string;
|
|
12
|
+
tagRemove?: string;
|
|
13
|
+
tagReplace?: string;
|
|
14
|
+
boxAdd?: string;
|
|
15
|
+
boxRemove?: string;
|
|
16
|
+
boxReplace?: string;
|
|
17
|
+
expectedCount?: string;
|
|
18
|
+
confirm?: boolean;
|
|
19
|
+
dryRun?: boolean;
|
|
20
|
+
};
|
|
21
|
+
type NoteBulkMutationSelection = {
|
|
22
|
+
field: NoteMetadataListField;
|
|
23
|
+
operation: NoteMetadataOperation;
|
|
24
|
+
rawValues: string;
|
|
25
|
+
optionName: '--tag-add' | '--tag-remove' | '--tag-replace' | '--box-add' | '--box-remove' | '--box-replace';
|
|
26
|
+
};
|
|
27
|
+
export declare function selectBulkMutation(options: NoteBulkOptions): NoteBulkMutationSelection;
|
|
28
|
+
export declare function parseExpectedCount(value: unknown): number | null;
|
|
29
|
+
export declare function assertBulkTargetFilter(input: {
|
|
30
|
+
query: string;
|
|
31
|
+
options: NoteBulkOptions;
|
|
32
|
+
}): void;
|
|
33
|
+
export declare function runNoteBulk({ command, options, args }: CommandExecutionContext): Promise<void>;
|
|
34
|
+
export {};
|