@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
|
@@ -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') {
|
|
@@ -218,7 +218,7 @@ export class MCPServer {
|
|
|
218
218
|
const tagsExpression = typeof args.tags === 'string' ? args.tags.trim() : '';
|
|
219
219
|
const rawBoxes = args.boxes ?? args.box;
|
|
220
220
|
const { names: boxNames, matchEmpty: matchEmptyBoxes } = parseBoxFilter(rawBoxes);
|
|
221
|
-
const zettelBoxIds = await resolveZettelBoxIdsForSearch(
|
|
221
|
+
const zettelBoxIds = await resolveZettelBoxIdsForSearch(db, boxNames);
|
|
222
222
|
const daysInput = typeof args.days === 'number' ? String(args.days) : args.days;
|
|
223
223
|
const { createdAtFrom, createdAtTo } = parseCreatedAtRange({
|
|
224
224
|
from: args.from,
|
|
@@ -228,7 +228,7 @@ export class MCPServer {
|
|
|
228
228
|
const limit = normalizeLimit(args.limit);
|
|
229
229
|
const offset = normalizeOffset(args.offset);
|
|
230
230
|
const fields = parseNoteSearchFields(args.fields, 'dinox_search_notes.fields');
|
|
231
|
-
const rows = await searchNotes(
|
|
231
|
+
const rows = await searchNotes(db, keyword, {
|
|
232
232
|
includeDeleted: false,
|
|
233
233
|
tagsExpression: tagsExpression || undefined,
|
|
234
234
|
createdAtFrom,
|
|
@@ -256,7 +256,7 @@ export class MCPServer {
|
|
|
256
256
|
if (typeof args.id !== 'string' || !args.id.trim()) {
|
|
257
257
|
throw new Error('dinox_get_note: id is required');
|
|
258
258
|
}
|
|
259
|
-
const notes = await getNoteDetails(
|
|
259
|
+
const notes = await getNoteDetails(db, [args.id.trim()]);
|
|
260
260
|
const text = JSON.stringify(notes[0] ?? null);
|
|
261
261
|
return buildResponse(req, { result: { content: [{ type: 'text', text }] } });
|
|
262
262
|
}
|
|
@@ -272,12 +272,12 @@ export class MCPServer {
|
|
|
272
272
|
const rawConfig = await loadConfig();
|
|
273
273
|
const config = resolveConfig(rawConfig);
|
|
274
274
|
const identity = await resolveAuthIdentity(rawConfig, config);
|
|
275
|
-
const canonicalContentMd = await canonicalizeBidlinksInMarkdown(
|
|
275
|
+
const canonicalContentMd = await canonicalizeBidlinksInMarkdown(db, contentMd, identity.userId);
|
|
276
276
|
const { contentJson, contentText } = convertMarkdownToNoteContent(canonicalContentMd);
|
|
277
|
-
const tags = await validateTagsForCreate(
|
|
278
|
-
const zettelBoxIds = await resolveZettelBoxIdsForCreate(
|
|
277
|
+
const tags = await validateTagsForCreate(db, normalizeStringArray(args.tags));
|
|
278
|
+
const zettelBoxIds = await resolveZettelBoxIdsForCreate(db, normalizeStringArray(args.boxes));
|
|
279
279
|
const id = generateId();
|
|
280
|
-
await createNote(
|
|
280
|
+
await createNote(db, {
|
|
281
281
|
id,
|
|
282
282
|
title,
|
|
283
283
|
noteType: 'crawl',
|
|
@@ -295,7 +295,7 @@ export class MCPServer {
|
|
|
295
295
|
if (typeof args.id !== 'string' || !args.id.trim()) {
|
|
296
296
|
throw new Error('dinox_graph_backlinks: id is required');
|
|
297
297
|
}
|
|
298
|
-
const result = await getBacklinks(
|
|
298
|
+
const result = await getBacklinks(db, args.id.trim());
|
|
299
299
|
const text = JSON.stringify(result);
|
|
300
300
|
return buildResponse(req, { result: { content: [{ type: 'text', text }] } });
|
|
301
301
|
}
|
|
@@ -303,7 +303,7 @@ export class MCPServer {
|
|
|
303
303
|
if (typeof args.id !== 'string' || !args.id.trim()) {
|
|
304
304
|
throw new Error('dinox_graph_outlinks: id is required');
|
|
305
305
|
}
|
|
306
|
-
const result = await getOutlinks(
|
|
306
|
+
const result = await getOutlinks(db, args.id.trim());
|
|
307
307
|
const text = JSON.stringify(result);
|
|
308
308
|
return buildResponse(req, { result: { content: [{ type: 'text', text }] } });
|
|
309
309
|
}
|
|
@@ -315,12 +315,12 @@ export class MCPServer {
|
|
|
315
315
|
? Math.trunc(args.depth)
|
|
316
316
|
: 2;
|
|
317
317
|
const depth = Math.min(Math.max(depthRaw, 1), 5);
|
|
318
|
-
const result = await getRelatedNotes(
|
|
318
|
+
const result = await getRelatedNotes(db, args.id.trim(), depth);
|
|
319
319
|
const text = JSON.stringify(result);
|
|
320
320
|
return buildResponse(req, { result: { content: [{ type: 'text', text }] } });
|
|
321
321
|
}
|
|
322
322
|
case 'dinox_graph_stats': {
|
|
323
|
-
const result = await getGraphStats(
|
|
323
|
+
const result = await getGraphStats(db);
|
|
324
324
|
const text = JSON.stringify(result);
|
|
325
325
|
return buildResponse(req, { result: { content: [{ type: 'text', text }] } });
|
|
326
326
|
}
|
|
@@ -330,7 +330,7 @@ export class MCPServer {
|
|
|
330
330
|
? args.status
|
|
331
331
|
: 'all';
|
|
332
332
|
const limit = normalizeLimit(args.limit);
|
|
333
|
-
const result = await searchTodoTasks(
|
|
333
|
+
const result = await searchTodoTasks(db, query, {
|
|
334
334
|
includeDeleted: false,
|
|
335
335
|
taskStatus: status || 'all',
|
|
336
336
|
taskTagFilter: normalizeStringArray(args.tags),
|
|
@@ -340,10 +340,38 @@ export class MCPServer {
|
|
|
340
340
|
const text = JSON.stringify(result);
|
|
341
341
|
return buildResponse(req, { result: { content: [{ type: 'text', text }] } });
|
|
342
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
|
+
}
|
|
343
359
|
default:
|
|
344
360
|
throw new Error(`Unknown tool: ${name}`);
|
|
345
361
|
}
|
|
346
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
|
+
}
|
|
347
375
|
async close() {
|
|
348
376
|
if (this.db) {
|
|
349
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 {};
|