@dinoxx/dinox-cli 2.13.1 → 2.15.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/dist/ai/ragxClient.d.ts +15 -0
- package/dist/ai/ragxClient.js +71 -0
- package/dist/cli.js +31 -4
- package/dist/commands/auth/index.d.ts +2 -1
- package/dist/commands/auth/index.js +18 -1
- package/dist/commands/batch.d.ts +35 -0
- package/dist/commands/batch.js +202 -0
- package/dist/commands/boxes/index.js +21 -21
- package/dist/commands/doctor.d.ts +4 -1
- package/dist/commands/doctor.js +20 -1
- package/dist/commands/graph/index.js +2 -1
- package/dist/commands/mcp.d.ts +6 -0
- package/dist/commands/mcp.js +47 -0
- package/dist/commands/notes/actions.js +18 -2
- package/dist/commands/notes/contentPatch.js +2 -1
- package/dist/commands/notes/descriptors.js +111 -5
- package/dist/commands/notes/detailContent.d.ts +19 -0
- package/dist/commands/notes/detailContent.js +62 -0
- package/dist/commands/notes/export.js +7 -2
- package/dist/commands/notes/repo/search.d.ts +2 -1
- package/dist/commands/notes/repo/search.js +1 -1
- package/dist/commands/notes/repo/semantic.d.ts +14 -0
- package/dist/commands/notes/repo/semantic.js +31 -0
- package/dist/commands/notes/searchResponse.d.ts +3 -0
- package/dist/commands/notes/searchResponse.js +1 -0
- package/dist/commands/notes/semantic.d.ts +116 -0
- package/dist/commands/notes/semantic.js +332 -0
- package/dist/commands/prompt/index.js +7 -5
- package/dist/commands/runtime.d.ts +8 -0
- package/dist/commands/runtime.js +49 -1
- package/dist/commands/savedViews/index.js +12 -3
- package/dist/commands/schema/globalOptions.d.ts +7 -0
- package/dist/commands/schema/globalOptions.js +7 -0
- package/dist/commands/schema/renderSkills.js +6 -2
- package/dist/commands/schema/spec.js +20 -1
- package/dist/commands/storage/index.js +7 -5
- package/dist/commands/storage/repo/operations.js +3 -1
- package/dist/commands/sync.d.ts +2 -1
- package/dist/commands/sync.js +27 -8
- package/dist/commands/tags/index.js +21 -21
- package/dist/commands/todo/index.js +22 -2
- package/dist/commands/todo/repo.d.ts +5 -0
- package/dist/commands/todo/repo.js +7 -2
- package/dist/commands/writeReceipt.d.ts +13 -0
- package/dist/commands/writeReceipt.js +12 -0
- package/dist/config/serviceEndpoints.d.ts +2 -0
- package/dist/config/serviceEndpoints.js +2 -0
- package/dist/daemon/client.d.ts +2 -0
- package/dist/daemon/client.js +53 -3
- package/dist/daemon/connectivity.d.ts +20 -0
- package/dist/daemon/connectivity.js +42 -0
- package/dist/daemon/ensure.js +48 -12
- package/dist/daemon/forward.d.ts +37 -0
- package/dist/daemon/forward.js +155 -0
- package/dist/daemon/fresh.d.ts +4 -0
- package/dist/daemon/fresh.js +10 -0
- package/dist/daemon/index.d.ts +11 -6
- package/dist/daemon/index.js +90 -25
- package/dist/daemon/launch.d.ts +13 -1
- package/dist/daemon/launch.js +31 -2
- package/dist/daemon/lifecycle.d.ts +3 -1
- package/dist/daemon/lifecycle.js +27 -3
- package/dist/daemon/pid.d.ts +8 -0
- package/dist/daemon/pid.js +20 -0
- package/dist/daemon/route.js +2 -1
- package/dist/daemon/server.d.ts +45 -1
- package/dist/daemon/server.js +192 -4
- package/dist/daemon/supervise.d.ts +6 -6
- package/dist/daemon/supervise.js +12 -10
- package/dist/daemon/supervisor.d.ts +21 -5
- package/dist/daemon/supervisor.js +125 -6
- package/dist/daemon/types.d.ts +22 -0
- package/dist/dinox.js +43 -64
- package/dist/gateway/deviceHost.js +2 -1
- package/dist/gateway/mcpCommandTools.d.ts +24 -5
- package/dist/gateway/mcpCommandTools.js +70 -29
- package/dist/gateway/mcpEnv.d.ts +2 -0
- package/dist/gateway/mcpEnv.js +2 -0
- package/dist/gateway/mcpGroups.d.ts +3 -0
- package/dist/gateway/mcpGroups.js +4 -0
- package/dist/gateway/mcpServer.d.ts +10 -1
- package/dist/gateway/mcpServer.js +25 -5
- package/dist/powersync/runtime.d.ts +2 -0
- package/dist/powersync/runtime.js +3 -1
- package/dist/powersync/syncPass.js +13 -1
- package/dist/powersync/tokenIndex.d.ts +2 -0
- package/dist/powersync/tokenIndex.js +1 -0
- package/dist/requests/execute.d.ts +11 -0
- package/dist/requests/execute.js +26 -0
- package/dist/requests/store.d.ts +10 -0
- package/dist/requests/store.js +190 -0
- package/dist/runCli.d.ts +2 -0
- package/dist/runCli.js +64 -0
- package/dist/skills/commandRefs.js +2 -10
- package/dist/utils/argValue.js +2 -1
- package/dist/utils/cursor.d.ts +9 -0
- package/dist/utils/cursor.js +61 -0
- package/dist/utils/defaultFormat.d.ts +8 -0
- package/dist/utils/defaultFormat.js +38 -0
- package/dist/utils/errorCodes.d.ts +24 -0
- package/dist/utils/errorCodes.js +6 -0
- package/dist/utils/output.d.ts +4 -0
- package/dist/utils/output.js +72 -10
- package/dist/utils/progress.d.ts +25 -0
- package/dist/utils/progress.js +59 -0
- package/dist/utils/requestContext.d.ts +32 -0
- package/dist/utils/requestContext.js +13 -0
- package/dist/utils/select.d.ts +20 -0
- package/dist/utils/select.js +92 -0
- package/dist/utils/usageError.d.ts +37 -0
- package/dist/utils/usageError.js +127 -0
- package/package.json +2 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { WRITE_DURABILITY_OPTION } from '../writeDurability.js';
|
|
2
|
+
import { completeWrite, WRITE_RECEIPT_FIELDS } from '../writeReceipt.js';
|
|
1
3
|
import { resolveAuthIdentity } from '../../auth/userInfo.js';
|
|
2
4
|
import { registerCommandGroup } from '../descriptor/register.js';
|
|
3
5
|
import { FORMAT_OPTIONS, OFFLINE_OPTIONS, REQUIRE_SYNC_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
|
|
@@ -5,7 +7,7 @@ import { buildBoxAddDryRunPreview, buildBoxMutationDryRunPreview } from '../writ
|
|
|
5
7
|
import { printDryRunPreview } from '../../utils/dryRun.js';
|
|
6
8
|
import { DinoxError } from '../../utils/errors.js';
|
|
7
9
|
import { getRequestedStructuredOutputFormat, printHumanReadableLine, printSuccess, } from '../../utils/output.js';
|
|
8
|
-
import { commandStale, printStaleCacheWarning, syncBeforeCommand,
|
|
10
|
+
import { commandStale, printStaleCacheWarning, syncBeforeCommand, resolveWriteDurabilityForRuntime, withCommandRuntime, } from '../runtime.js';
|
|
9
11
|
import { assertWriteConfirmationProvided, parseExpectedCount } from '../writeConfirmation.js';
|
|
10
12
|
import { addBox, inspectAddBox, listBoxes } from './repo.js';
|
|
11
13
|
import { getBoxStats, getBoxTree, inspectBoxCleanup, mergeBox, moveBox, renameBox, } from './operations.js';
|
|
@@ -54,6 +56,7 @@ async function runBoxAdd(context) {
|
|
|
54
56
|
}
|
|
55
57
|
const structuredOutput = getRequestedStructuredOutputFormat() !== null;
|
|
56
58
|
await withCommandRuntime(context, async (runtime) => {
|
|
59
|
+
const durability = resolveWriteDurabilityForRuntime(runtime, context.options.durability);
|
|
57
60
|
const identity = await resolveAuthIdentity(runtime.config);
|
|
58
61
|
await syncBeforeCommand(runtime, { syncTokenIndex: true });
|
|
59
62
|
const description = typeof context.options.description === 'string' ? context.options.description : undefined;
|
|
@@ -77,8 +80,7 @@ async function runBoxAdd(context) {
|
|
|
77
80
|
description,
|
|
78
81
|
color,
|
|
79
82
|
});
|
|
80
|
-
await
|
|
81
|
-
const payload = { ok: true, stale: commandStale(runtime), ...result };
|
|
83
|
+
const payload = { ok: true, ...(await completeWrite(runtime, durability)), ...result };
|
|
82
84
|
if (structuredOutput) {
|
|
83
85
|
printSuccess(payload);
|
|
84
86
|
return;
|
|
@@ -89,9 +91,10 @@ async function runBoxAdd(context) {
|
|
|
89
91
|
}
|
|
90
92
|
async function withBoxWriteRuntime(context, run) {
|
|
91
93
|
return withCommandRuntime(context, async (runtime) => {
|
|
94
|
+
const durability = resolveWriteDurabilityForRuntime(runtime, context.options.durability);
|
|
92
95
|
const identity = await resolveAuthIdentity(runtime.config);
|
|
93
96
|
await syncBeforeCommand(runtime, { syncTokenIndex: true });
|
|
94
|
-
return run({ runtime, userId: identity.userId });
|
|
97
|
+
return run({ runtime, userId: identity.userId, durability });
|
|
95
98
|
});
|
|
96
99
|
}
|
|
97
100
|
function requireArg(value, message) {
|
|
@@ -104,7 +107,7 @@ function requireArg(value, message) {
|
|
|
104
107
|
async function runBoxRename(context) {
|
|
105
108
|
const path = requireArg(context.args.path, 'Box path is required');
|
|
106
109
|
const newName = requireArg(context.args.newName, 'New box name is required');
|
|
107
|
-
await withBoxWriteRuntime(context, async ({ runtime, userId }) => {
|
|
110
|
+
await withBoxWriteRuntime(context, async ({ runtime, userId, durability }) => {
|
|
108
111
|
const result = await renameBox(runtime.db, { path, newName, userId }, { dryRun: Boolean(context.options.dryRun) });
|
|
109
112
|
if (Boolean(context.options.dryRun)) {
|
|
110
113
|
printDryRunPreview(buildBoxMutationDryRunPreview({
|
|
@@ -119,8 +122,7 @@ async function runBoxRename(context) {
|
|
|
119
122
|
}), runtime);
|
|
120
123
|
return;
|
|
121
124
|
}
|
|
122
|
-
await
|
|
123
|
-
printSuccess({ ok: true, stale: commandStale(runtime), ...result });
|
|
125
|
+
printSuccess({ ok: true, ...(await completeWrite(runtime, durability)), ...result });
|
|
124
126
|
});
|
|
125
127
|
}
|
|
126
128
|
async function runBoxMove(context) {
|
|
@@ -129,7 +131,7 @@ async function runBoxMove(context) {
|
|
|
129
131
|
if (!to) {
|
|
130
132
|
throw new DinoxError('--to is required');
|
|
131
133
|
}
|
|
132
|
-
await withBoxWriteRuntime(context, async ({ runtime, userId }) => {
|
|
134
|
+
await withBoxWriteRuntime(context, async ({ runtime, userId, durability }) => {
|
|
133
135
|
const result = await moveBox(runtime.db, { path, to, userId }, { dryRun: Boolean(context.options.dryRun) });
|
|
134
136
|
if (Boolean(context.options.dryRun)) {
|
|
135
137
|
printDryRunPreview(buildBoxMutationDryRunPreview({
|
|
@@ -144,8 +146,7 @@ async function runBoxMove(context) {
|
|
|
144
146
|
}), runtime);
|
|
145
147
|
return;
|
|
146
148
|
}
|
|
147
|
-
await
|
|
148
|
-
printSuccess({ ok: true, stale: commandStale(runtime), ...result });
|
|
149
|
+
printSuccess({ ok: true, ...(await completeWrite(runtime, durability)), ...result });
|
|
149
150
|
});
|
|
150
151
|
}
|
|
151
152
|
async function runBoxMerge(context) {
|
|
@@ -159,7 +160,7 @@ async function runBoxMerge(context) {
|
|
|
159
160
|
expectedCount,
|
|
160
161
|
};
|
|
161
162
|
assertWriteConfirmationProvided('Box merge', writeOptions);
|
|
162
|
-
await withBoxWriteRuntime(context, async ({ runtime, userId }) => {
|
|
163
|
+
await withBoxWriteRuntime(context, async ({ runtime, userId, durability }) => {
|
|
163
164
|
const result = await mergeBox(runtime.db, { from, to, userId }, writeOptions);
|
|
164
165
|
if (dryRun) {
|
|
165
166
|
printDryRunPreview(buildBoxMutationDryRunPreview({
|
|
@@ -180,8 +181,7 @@ async function runBoxMerge(context) {
|
|
|
180
181
|
}), runtime);
|
|
181
182
|
return;
|
|
182
183
|
}
|
|
183
|
-
await
|
|
184
|
-
printSuccess({ ok: true, stale: commandStale(runtime), ...result });
|
|
184
|
+
printSuccess({ ok: true, ...(await completeWrite(runtime, durability)), ...result });
|
|
185
185
|
});
|
|
186
186
|
}
|
|
187
187
|
async function runBoxCleanup(context) {
|
|
@@ -234,7 +234,7 @@ export const boxCommandGroup = {
|
|
|
234
234
|
{ flags: '--name <string>', description: 'Box path (alternative to positional path)' },
|
|
235
235
|
{ flags: '--description <string>', description: 'Box purpose/usage description' },
|
|
236
236
|
{ flags: '--color <string>', description: 'Box color' },
|
|
237
|
-
{ flags: '--dry-run', description: 'Preview the write without executing it' },
|
|
237
|
+
WRITE_DURABILITY_OPTION, { flags: '--dry-run', description: 'Preview the write without executing it' },
|
|
238
238
|
],
|
|
239
239
|
schema: {
|
|
240
240
|
path: 'box.add',
|
|
@@ -244,7 +244,7 @@ export const boxCommandGroup = {
|
|
|
244
244
|
output: {
|
|
245
245
|
type: 'structured',
|
|
246
246
|
summary: 'Box creation acknowledgement',
|
|
247
|
-
fields: ['id', 'name', 'path', 'parentId', 'description', 'color', 'restored', 'createdNodes', 'restoredNodes',
|
|
247
|
+
fields: ['id', 'name', 'path', 'parentId', 'description', 'color', 'restored', 'createdNodes', 'restoredNodes', ...WRITE_RECEIPT_FIELDS],
|
|
248
248
|
},
|
|
249
249
|
examples: ['dino box add "Work/Inbox" --description "用于存放待整理的想法和资料" --format json --dry-run'],
|
|
250
250
|
},
|
|
@@ -291,7 +291,7 @@ export const boxCommandGroup = {
|
|
|
291
291
|
{ syntax: '<path>', description: 'Existing box path' },
|
|
292
292
|
{ syntax: '<new-name>', description: 'New leaf name' },
|
|
293
293
|
],
|
|
294
|
-
options: [{ flags: '--dry-run', description: 'Preview the write without executing it' }],
|
|
294
|
+
options: [WRITE_DURABILITY_OPTION, { flags: '--dry-run', description: 'Preview the write without executing it' }],
|
|
295
295
|
schema: {
|
|
296
296
|
path: 'box.rename',
|
|
297
297
|
risk: 'write',
|
|
@@ -300,7 +300,7 @@ export const boxCommandGroup = {
|
|
|
300
300
|
output: {
|
|
301
301
|
type: 'structured',
|
|
302
302
|
summary: 'Zettel box rename result',
|
|
303
|
-
fields: ['id', 'fromPath', 'toPath', 'updatedBoxes',
|
|
303
|
+
fields: ['id', 'fromPath', 'toPath', 'updatedBoxes', ...WRITE_RECEIPT_FIELDS],
|
|
304
304
|
},
|
|
305
305
|
examples: ['dino box rename "Work/Inbox" "Ideas" --format json --dry-run'],
|
|
306
306
|
},
|
|
@@ -312,7 +312,7 @@ export const boxCommandGroup = {
|
|
|
312
312
|
arguments: [{ syntax: '<path>', description: 'Existing box path' }],
|
|
313
313
|
options: [
|
|
314
314
|
{ flags: '--to <parent-path>', description: 'Target parent box path', required: true, type: 'string' },
|
|
315
|
-
{ flags: '--dry-run', description: 'Preview the write without executing it' },
|
|
315
|
+
WRITE_DURABILITY_OPTION, { flags: '--dry-run', description: 'Preview the write without executing it' },
|
|
316
316
|
],
|
|
317
317
|
schema: {
|
|
318
318
|
path: 'box.move',
|
|
@@ -322,7 +322,7 @@ export const boxCommandGroup = {
|
|
|
322
322
|
output: {
|
|
323
323
|
type: 'structured',
|
|
324
324
|
summary: 'Zettel box move result',
|
|
325
|
-
fields: ['id', 'fromPath', 'toPath', 'updatedBoxes',
|
|
325
|
+
fields: ['id', 'fromPath', 'toPath', 'updatedBoxes', ...WRITE_RECEIPT_FIELDS],
|
|
326
326
|
},
|
|
327
327
|
examples: ['dino box move "Inbox" --to "Work" --format json --dry-run'],
|
|
328
328
|
},
|
|
@@ -338,7 +338,7 @@ export const boxCommandGroup = {
|
|
|
338
338
|
options: [
|
|
339
339
|
{ flags: '--expected-count <n>', description: 'Required for real writes; must equal source boxes plus notes affected', type: 'number' },
|
|
340
340
|
{ flags: '--confirm', description: 'Required for real writes after reviewing a dry run', type: 'boolean' },
|
|
341
|
-
{ flags: '--dry-run', description: 'Preview the write without executing it', type: 'boolean' },
|
|
341
|
+
WRITE_DURABILITY_OPTION, { flags: '--dry-run', description: 'Preview the write without executing it', type: 'boolean' },
|
|
342
342
|
],
|
|
343
343
|
schema: {
|
|
344
344
|
path: 'box.merge',
|
|
@@ -348,7 +348,7 @@ export const boxCommandGroup = {
|
|
|
348
348
|
output: {
|
|
349
349
|
type: 'structured',
|
|
350
350
|
summary: 'Zettel box merge result',
|
|
351
|
-
fields: ['fromId', 'toId', 'fromPath', 'toPath', 'softDeletedBoxIds', 'updatedNotes', 'affectedCount',
|
|
351
|
+
fields: ['fromId', 'toId', 'fromPath', 'toPath', 'softDeletedBoxIds', 'updatedNotes', 'affectedCount', ...WRITE_RECEIPT_FIELDS],
|
|
352
352
|
},
|
|
353
353
|
examples: [
|
|
354
354
|
'dino box merge "Work/Inbox" "Work/Archive" --format json --dry-run',
|
|
@@ -5,7 +5,7 @@ import { resolveConfig } from '../config/resolve.js';
|
|
|
5
5
|
import type { ResolvedDinoxConfig } from '../config/types.js';
|
|
6
6
|
import { shutdownDaemonForExclusiveAccess } from '../daemon/client.js';
|
|
7
7
|
import { restartDaemonLocked } from '../daemon/index.js';
|
|
8
|
-
import { acquireDaemonStartLock } from '../daemon/pid.js';
|
|
8
|
+
import { acquireDaemonExclusiveIntentLock, acquireDaemonStartLock } from '../daemon/pid.js';
|
|
9
9
|
import type { DaemonPidInfo } from '../daemon/pid.js';
|
|
10
10
|
import { type DaemonPingResult } from '../daemon/types.js';
|
|
11
11
|
import { connectPowerSync } from '../powersync/runtime.js';
|
|
@@ -89,6 +89,8 @@ type DoctorRuntime = {
|
|
|
89
89
|
daemonBefore: DoctorDaemonSnapshot;
|
|
90
90
|
daemonWasStopped: boolean;
|
|
91
91
|
releaseDaemonStartLock: () => Promise<void>;
|
|
92
|
+
/** Held while doctor owns the database; see acquireDaemonExclusiveIntentLock. */
|
|
93
|
+
releaseIntentLock?: () => Promise<void>;
|
|
92
94
|
daemonStartLockReleased: boolean;
|
|
93
95
|
released: boolean;
|
|
94
96
|
};
|
|
@@ -102,6 +104,7 @@ type DoctorPreparationDependencies = {
|
|
|
102
104
|
resolveConfig: typeof resolveConfig;
|
|
103
105
|
hydrateAuthIdentity: typeof hydrateResolvedAuthIdentity;
|
|
104
106
|
acquireDaemonStartLock: typeof acquireDaemonStartLock;
|
|
107
|
+
acquireDaemonIntentLock: typeof acquireDaemonExclusiveIntentLock;
|
|
105
108
|
captureDaemonSnapshot: typeof captureDaemonSnapshot;
|
|
106
109
|
shutdownDaemon: typeof shutdownDaemonForExclusiveAccess;
|
|
107
110
|
connectPowerSync: typeof connectPowerSync;
|
package/dist/commands/doctor.js
CHANGED
|
@@ -5,7 +5,7 @@ import { loadConfig } from '../config/store.js';
|
|
|
5
5
|
import { fingerprintDaemonRuntimeConfig, resolveConfig } from '../config/resolve.js';
|
|
6
6
|
import { getDaemonInfo, isDaemonFresh, shutdownDaemonForExclusiveAccess } from '../daemon/client.js';
|
|
7
7
|
import { restartDaemonLocked } from '../daemon/index.js';
|
|
8
|
-
import { acquireDaemonStartLock, getDaemonSocketPath, isProcessRunning, readPid, } from '../daemon/pid.js';
|
|
8
|
+
import { acquireDaemonExclusiveIntentLock, acquireDaemonStartLock, getDaemonSocketPath, isProcessRunning, readPid, } from '../daemon/pid.js';
|
|
9
9
|
import { DEFAULT_DAEMON_PORT } from '../daemon/types.js';
|
|
10
10
|
import { closePowerSyncAndReleaseOwner, connectPowerSync, getStatusSnapshot, waitForUploadQueueEmpty, } from '../powersync/runtime.js';
|
|
11
11
|
import { inspectNoteTokenIndex, rebuildNoteTokenIndex, } from '../powersync/tokenIndex.js';
|
|
@@ -20,6 +20,7 @@ const doctorPreparationDependencies = {
|
|
|
20
20
|
resolveConfig,
|
|
21
21
|
hydrateAuthIdentity: hydrateResolvedAuthIdentity,
|
|
22
22
|
acquireDaemonStartLock,
|
|
23
|
+
acquireDaemonIntentLock: acquireDaemonExclusiveIntentLock,
|
|
23
24
|
captureDaemonSnapshot,
|
|
24
25
|
shutdownDaemon: shutdownDaemonForExclusiveAccess,
|
|
25
26
|
connectPowerSync,
|
|
@@ -39,6 +40,14 @@ function doctorTimeoutMs(globals, config) {
|
|
|
39
40
|
export async function prepareDoctorRuntime(context, overrides = {}) {
|
|
40
41
|
const dependencies = { ...doctorPreparationDependencies, ...overrides };
|
|
41
42
|
const releaseDaemonStartLock = await dependencies.acquireDaemonStartLock(parseSyncTimeoutMs(context.globals.syncTimeout) ?? DEFAULT_DOCTOR_TIMEOUT_MS);
|
|
43
|
+
let releaseIntentLock;
|
|
44
|
+
const releaseIntent = async () => {
|
|
45
|
+
const release = releaseIntentLock;
|
|
46
|
+
releaseIntentLock = undefined;
|
|
47
|
+
await release?.().catch((error) => {
|
|
48
|
+
printHumanReadableWarning(`warning: failed to release daemon intent lock: ${errorMessage(error)}`);
|
|
49
|
+
});
|
|
50
|
+
};
|
|
42
51
|
try {
|
|
43
52
|
const rawConfig = await dependencies.loadConfig();
|
|
44
53
|
const baseConfig = dependencies.resolveConfig(rawConfig);
|
|
@@ -56,6 +65,7 @@ export async function prepareDoctorRuntime(context, overrides = {}) {
|
|
|
56
65
|
authError = errorMessage(error);
|
|
57
66
|
}
|
|
58
67
|
}
|
|
68
|
+
releaseIntentLock = await dependencies.acquireDaemonIntentLock(timeoutMs);
|
|
59
69
|
const daemonBefore = await dependencies.captureDaemonSnapshot();
|
|
60
70
|
const daemonDbPath = daemonBefore.info ? getPowerSyncDbPath(daemonBefore.info.userId) : null;
|
|
61
71
|
const targetDbPath = getPowerSyncDbPath(config.auth.userId);
|
|
@@ -83,6 +93,7 @@ export async function prepareDoctorRuntime(context, overrides = {}) {
|
|
|
83
93
|
daemonBefore,
|
|
84
94
|
daemonWasStopped,
|
|
85
95
|
releaseDaemonStartLock,
|
|
96
|
+
releaseIntentLock,
|
|
86
97
|
daemonStartLockReleased: false,
|
|
87
98
|
released: false,
|
|
88
99
|
};
|
|
@@ -109,16 +120,19 @@ export async function prepareDoctorRuntime(context, overrides = {}) {
|
|
|
109
120
|
daemonBefore,
|
|
110
121
|
daemonWasStopped,
|
|
111
122
|
releaseDaemonStartLock,
|
|
123
|
+
releaseIntentLock,
|
|
112
124
|
daemonStartLockReleased: false,
|
|
113
125
|
released: false,
|
|
114
126
|
};
|
|
115
127
|
}
|
|
116
128
|
catch (error) {
|
|
129
|
+
await releaseIntent();
|
|
117
130
|
await restoreStoppedDoctorDaemon(config, daemonBefore, daemonWasStopped, dependencies.restartDaemon);
|
|
118
131
|
throw error;
|
|
119
132
|
}
|
|
120
133
|
}
|
|
121
134
|
catch (error) {
|
|
135
|
+
await releaseIntent();
|
|
122
136
|
await releaseDaemonStartLock().catch((releaseError) => {
|
|
123
137
|
printHumanReadableWarning(`warning: failed to release daemon lifecycle lock: ${errorMessage(releaseError)}`);
|
|
124
138
|
});
|
|
@@ -357,6 +371,11 @@ export async function releaseDoctorRuntime(runtime) {
|
|
|
357
371
|
}
|
|
358
372
|
await closePowerSyncAndReleaseOwner(runtime.db, runtime.releaseOwner);
|
|
359
373
|
runtime.released = true;
|
|
374
|
+
const releaseIntentLock = runtime.releaseIntentLock;
|
|
375
|
+
runtime.releaseIntentLock = undefined;
|
|
376
|
+
await releaseIntentLock?.().catch((error) => {
|
|
377
|
+
printHumanReadableWarning(`warning: failed to release daemon intent lock: ${errorMessage(error)}`);
|
|
378
|
+
});
|
|
360
379
|
}
|
|
361
380
|
export async function releaseDoctorDaemonStartLock(runtime) {
|
|
362
381
|
if (runtime.daemonStartLockReleased) {
|
|
@@ -4,6 +4,7 @@ import { registerCommandGroup } from '../descriptor/register.js';
|
|
|
4
4
|
import { FORMAT_OPTIONS, OFFLINE_OPTIONS, REQUIRE_SYNC_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
|
|
5
5
|
import { DinoxError } from '../../utils/errors.js';
|
|
6
6
|
import { printSuccess } from '../../utils/output.js';
|
|
7
|
+
import { localCommandExecution, requestContext } from '../../utils/requestContext.js';
|
|
7
8
|
import { commandStale, syncBeforeCommand, withCommandRuntime } from '../runtime.js';
|
|
8
9
|
import { getBacklinks, getGraphStats, getOutlinks, getRelatedNotes } from './repo.js';
|
|
9
10
|
const GRAPH_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...REQUIRE_SYNC_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
|
|
@@ -26,7 +27,7 @@ function parseDepth(value) {
|
|
|
26
27
|
}
|
|
27
28
|
async function runGraphCommand(context, onlineAction, offlineAction) {
|
|
28
29
|
const globals = context.globals;
|
|
29
|
-
if (Boolean(globals.offline) || Boolean(globals.requireSync)) {
|
|
30
|
+
if (requestContext.getStore()?.daemon || localCommandExecution.getStore() || Boolean(globals.offline) || Boolean(globals.requireSync)) {
|
|
30
31
|
await withCommandRuntime(context, async (runtime) => {
|
|
31
32
|
await syncBeforeCommand(runtime);
|
|
32
33
|
printSuccess({
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { type McpCommandGroup } from '../gateway/mcpGroups.js';
|
|
3
|
+
import type { CommandDescriptor } from './descriptor/types.js';
|
|
4
|
+
export declare function parseMcpGroups(raw: unknown): McpCommandGroup[] | undefined;
|
|
5
|
+
export declare const mcpCommandDescriptor: CommandDescriptor;
|
|
6
|
+
export declare function registerMcpCommand(program: Command): void;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { MCP_COMMAND_GROUPS } from '../gateway/mcpGroups.js';
|
|
2
|
+
import { DinoxError } from '../utils/errors.js';
|
|
3
|
+
import { registerStandaloneCommand } from './descriptor/register.js';
|
|
4
|
+
/** `--groups` names: the command group, with `stats` for meta.stats. */
|
|
5
|
+
const GROUP_NAMES = new Map(MCP_COMMAND_GROUPS.map((group) => [group === 'meta.stats' ? 'stats' : group, group]));
|
|
6
|
+
export function parseMcpGroups(raw) {
|
|
7
|
+
if (raw === undefined)
|
|
8
|
+
return undefined;
|
|
9
|
+
const names = String(raw).split(',').map((name) => name.trim().toLowerCase()).filter(Boolean);
|
|
10
|
+
const unknown = names.filter((name) => !GROUP_NAMES.has(name));
|
|
11
|
+
if (names.length === 0 || unknown.length > 0) {
|
|
12
|
+
throw new DinoxError(`--groups must list some of: ${[...GROUP_NAMES.keys()].join(', ')}`, {
|
|
13
|
+
code: 'INVALID_ARGUMENT',
|
|
14
|
+
recoverable: true,
|
|
15
|
+
details: { invalid: unknown, allowed: [...GROUP_NAMES.keys()] },
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return [...new Set(names.map((name) => GROUP_NAMES.get(name)))];
|
|
19
|
+
}
|
|
20
|
+
async function runMcp(context) {
|
|
21
|
+
const groups = parseMcpGroups(context.options.groups);
|
|
22
|
+
const { serveStandaloneMcpOverStdio } = await import('../gateway/mcpServer.js');
|
|
23
|
+
await serveStandaloneMcpOverStdio({ readOnly: context.options.readOnly === true, groups });
|
|
24
|
+
}
|
|
25
|
+
export const mcpCommandDescriptor = {
|
|
26
|
+
name: 'mcp',
|
|
27
|
+
description: 'Serve Dinox commands as MCP tools over stdio for MCP clients (Claude Desktop, Cursor, ...)',
|
|
28
|
+
options: [
|
|
29
|
+
{ flags: '--read-only', description: 'Expose only read actions', type: 'boolean' },
|
|
30
|
+
{
|
|
31
|
+
flags: '--groups <names>',
|
|
32
|
+
description: `Comma-separated tool groups to expose (${[...GROUP_NAMES.keys()].join(', ')}); default all`,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
schema: {
|
|
36
|
+
path: 'mcp',
|
|
37
|
+
risk: 'write',
|
|
38
|
+
auth: 'required',
|
|
39
|
+
globalOptions: [],
|
|
40
|
+
output: { type: 'structured', summary: 'MCP JSON-RPC over stdio until stdin closes', fields: [] },
|
|
41
|
+
examples: ['dino mcp', 'dino mcp --read-only --groups note,todo,graph'],
|
|
42
|
+
},
|
|
43
|
+
action: (context) => runMcp(context),
|
|
44
|
+
};
|
|
45
|
+
export function registerMcpCommand(program) {
|
|
46
|
+
registerStandaloneCommand(program, mcpCommandDescriptor);
|
|
47
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolveAuthIdentity } from '../../auth/userInfo.js';
|
|
2
|
+
import { encodeCursor, resolveCursorOffset } from '../../utils/cursor.js';
|
|
2
3
|
import { canonicalizeBidlinksInMarkdown } from '../../utils/bidlinkCanonicalization.js';
|
|
3
4
|
import { printDryRunPreview } from '../../utils/dryRun.js';
|
|
4
5
|
import { generateId } from '../../utils/id.js';
|
|
@@ -17,6 +18,7 @@ import { buildNoteSearchResponse, normalizeDaysForMeta } from './searchResponse.
|
|
|
17
18
|
import { parseMutationListInput, parseNoteType, parsePreviewLineCount, parseSearchLimit, parseSearchListInput, parseSearchOffset, parseStarredOption, parseStringInput, parseStringListInput, parseTargetNoteIds, syncBeforeNoteCommand, withNoteDatabase, } from './runtime.js';
|
|
18
19
|
import { readNoteProperties } from '../properties/repo.js';
|
|
19
20
|
import { createNoteWithSeed, previewNoteCreateProperties, resolveNoteCreateSeed } from './createFromTemplate.js';
|
|
21
|
+
import { parseNoteContentShape, shapeNoteContent } from './detailContent.js';
|
|
20
22
|
function toPublicNoteContext(note) {
|
|
21
23
|
if (!note) {
|
|
22
24
|
return null;
|
|
@@ -249,8 +251,20 @@ export async function runNoteSearch({ command, options, args, globals }) {
|
|
|
249
251
|
: { values: [], isExplicitEmptyArray: false };
|
|
250
252
|
const sqlExpression = typeof searchOptions.sql === 'string' ? searchOptions.sql.trim() : '';
|
|
251
253
|
const limit = parseSearchLimit(searchOptions.limit);
|
|
252
|
-
const offset = parseSearchOffset(searchOptions.offset);
|
|
253
254
|
const fields = parseNoteSearchFields(searchOptions.fields, '--fields');
|
|
255
|
+
// Raw option values, not the resolved range: `--days` must not change the hash between pages.
|
|
256
|
+
const cursorQuery = {
|
|
257
|
+
keyword,
|
|
258
|
+
includeDeleted: Boolean(searchOptions.includeDeleted),
|
|
259
|
+
tags: tagsExpression,
|
|
260
|
+
from: searchOptions.from,
|
|
261
|
+
to: searchOptions.to,
|
|
262
|
+
days: searchOptions.days,
|
|
263
|
+
starred,
|
|
264
|
+
boxes: boxInput,
|
|
265
|
+
sql: sqlExpression,
|
|
266
|
+
};
|
|
267
|
+
const offset = resolveCursorOffset(searchOptions.cursor, parseSearchOffset(searchOptions.offset), 'note.search', cursorQuery);
|
|
254
268
|
const execParams = {
|
|
255
269
|
keyword,
|
|
256
270
|
includeDeleted: Boolean(searchOptions.includeDeleted),
|
|
@@ -278,6 +292,7 @@ export async function runNoteSearch({ command, options, args, globals }) {
|
|
|
278
292
|
includeDeleted: Boolean(searchOptions.includeDeleted),
|
|
279
293
|
limit,
|
|
280
294
|
offset,
|
|
295
|
+
nextCursor: (nextOffset) => encodeCursor('note.search', nextOffset, cursorQuery),
|
|
281
296
|
fields,
|
|
282
297
|
tagsExpression: tagsExpression || undefined,
|
|
283
298
|
createdAtFrom,
|
|
@@ -349,6 +364,7 @@ export async function runNotePreview({ command, options, args }) {
|
|
|
349
364
|
}
|
|
350
365
|
export async function runNoteDetail({ command, options, args }) {
|
|
351
366
|
const detailOptions = options;
|
|
367
|
+
const contentShape = parseNoteContentShape(detailOptions.content, detailOptions.maxChars);
|
|
352
368
|
await withNoteDatabase(command, async (runtime) => {
|
|
353
369
|
const { db } = runtime;
|
|
354
370
|
await syncBeforeNoteCommand(runtime);
|
|
@@ -359,7 +375,7 @@ export async function runNoteDetail({ command, options, args }) {
|
|
|
359
375
|
if (missing.length > 0) {
|
|
360
376
|
throw new DinoxError(ids.length === 1 ? `Note not found: ${missing[0]}` : `Notes not found: ${missing.join(', ')}`);
|
|
361
377
|
}
|
|
362
|
-
const publicNotes = notes.map((note) => toPublicNoteDetail(note));
|
|
378
|
+
const publicNotes = notes.map((note) => shapeNoteContent(toPublicNoteDetail(note), contentShape));
|
|
363
379
|
printSuccess({
|
|
364
380
|
stale: commandStale(runtime),
|
|
365
381
|
count: publicNotes.length,
|
|
@@ -11,6 +11,7 @@ import { nowIsoUtc } from '../../utils/time.js';
|
|
|
11
11
|
import { deriveImageDetailFromContentJson } from '../todo/contentModel.js';
|
|
12
12
|
import { applyJsonPatchToDocument, buildNoteContentBlocks, buildNoteContentOutline, getNoteContentHash, parseStoredNoteDocument, projectNoteContent, } from './contentEngine.js';
|
|
13
13
|
import { parseStoredTags } from './repo.js';
|
|
14
|
+
import { resolveUserPath } from '../../utils/requestContext.js';
|
|
14
15
|
const READ_TOKEN_TTL_MS = 30 * 60 * 1000;
|
|
15
16
|
const MAX_STORED_READ_TOKENS = 500;
|
|
16
17
|
const TOKEN_STORE_FILE = 'note-content-read-tokens.json';
|
|
@@ -111,7 +112,7 @@ async function loadTokenStore(nowMs = Date.now()) {
|
|
|
111
112
|
const filePath = tokenStorePath();
|
|
112
113
|
let tokens = [];
|
|
113
114
|
try {
|
|
114
|
-
const raw = await fs.readFile(filePath, 'utf8');
|
|
115
|
+
const raw = await fs.readFile(resolveUserPath(filePath), 'utf8');
|
|
115
116
|
const parsed = JSON.parse(raw);
|
|
116
117
|
if (Array.isArray(parsed.tokens)) {
|
|
117
118
|
tokens = parsed.tokens.filter((token) => typeof token?.token === 'string' &&
|
|
@@ -1,11 +1,28 @@
|
|
|
1
1
|
import { registerCommandGroup } from '../descriptor/register.js';
|
|
2
|
-
import { FORMAT_OPTIONS, OFFLINE_OPTIONS, REQUIRE_SYNC_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
|
|
2
|
+
import { FORMAT_OPTIONS, OFFLINE_OPTIONS, PROGRESS_OPTIONS, REQUIRE_SYNC_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
|
|
3
3
|
import { WRITE_DURABILITY_OPTION } from '../writeDurability.js';
|
|
4
4
|
import { runNoteCreate, runNoteDelete, runNoteDetail, runNoteGet, runNoteMove, runNotePreview, runNoteSearch, runNoteStar, runNoteTag, runNoteUnstar, runNoteUpdate, } from './actions.js';
|
|
5
5
|
import { runNoteBulk as runNoteBulkAction } from './bulk.js';
|
|
6
6
|
import { runNoteContentRead, runNotePatch } from './contentPatchCommand.js';
|
|
7
7
|
import { runNoteExport } from './export.js';
|
|
8
|
+
import { runNoteContext, runNoteSemantic, runNoteSimilar, SEMANTIC_LIMITS } from './semantic.js';
|
|
8
9
|
const NOTE_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...REQUIRE_SYNC_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
|
|
10
|
+
// Semantic commands query the online index; they refuse --offline.
|
|
11
|
+
const NOTE_SEMANTIC_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...REQUIRE_SYNC_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
|
|
12
|
+
const SEMANTIC_FILTER_OPTIONS = [
|
|
13
|
+
{ flags: '--tags <string|@file>', description: 'Only notes with all these tags (JSON array or comma list; child tags included)', type: 'string' },
|
|
14
|
+
{ flags: '--boxes <string|@file>', description: 'Only notes in any of these box paths or unique names (JSON array or comma list)', type: 'string' },
|
|
15
|
+
{ flags: '--from <date>', description: 'created_at start (YYYY-MM-DD or ISO datetime)', type: 'string' },
|
|
16
|
+
{ flags: '--to <date>', description: 'created_at end (YYYY-MM-DD or ISO datetime)', type: 'string' },
|
|
17
|
+
{ flags: '--days <n>', description: 'Recent N days by created_at; mutually exclusive with --from/--to', type: 'number' },
|
|
18
|
+
];
|
|
19
|
+
const SEMANTIC_MODE_OPTION = {
|
|
20
|
+
flags: '--mode <mode>',
|
|
21
|
+
description: 'hybrid (meaning + keywords, default), semantic (meaning only), or keyword',
|
|
22
|
+
type: 'string',
|
|
23
|
+
enum: ['hybrid', 'semantic', 'keyword'],
|
|
24
|
+
};
|
|
25
|
+
const SEMANTIC_NOTE_FIELDS = ['id', 'title', 'tags', 'boxes', 'created_at', 'is_starred', 'score', 'section', 'heading_path', 'snippet', 'matched_source', 'matches', 'in_local_cache'];
|
|
9
26
|
const NOTE_WRITE_RECEIPT_FIELDS = [
|
|
10
27
|
'durability',
|
|
11
28
|
'upload_queue_remaining',
|
|
@@ -42,6 +59,7 @@ export const noteCommandGroup = {
|
|
|
42
59
|
{ flags: '--sql <expr>', description: 'SQL-like expression over id/content_md/summary/tags/zettel_boxes/created_at/type/is_starred', type: 'string' },
|
|
43
60
|
{ flags: '--limit <n>', description: 'Maximum returned notes', type: 'number' },
|
|
44
61
|
{ flags: '--offset <n>', description: 'Result offset for pagination', type: 'number', defaultValue: 0 },
|
|
62
|
+
{ flags: '--cursor <token>', description: 'Continue from meta.pagination.next_cursor of the previous page (same query)' },
|
|
45
63
|
{ flags: '--fields <list>', description: 'Comma-separated fields: id,title,summary,tags,created_at,starred_at,boxes,is_starred', type: 'string[]' },
|
|
46
64
|
{ flags: '--include-deleted', description: 'Include soft-deleted notes', type: 'boolean' },
|
|
47
65
|
],
|
|
@@ -60,10 +78,90 @@ export const noteCommandGroup = {
|
|
|
60
78
|
examples: [
|
|
61
79
|
'dino note search "AI" --days 7 --format json',
|
|
62
80
|
'dino note search --tags "(work OR life) AND NOT archived" --format json',
|
|
81
|
+
'dino note search "AI" --cursor <meta.pagination.next_cursor> --format json',
|
|
63
82
|
],
|
|
64
83
|
},
|
|
65
84
|
action: runNoteSearch,
|
|
66
85
|
},
|
|
86
|
+
{
|
|
87
|
+
name: 'semantic',
|
|
88
|
+
description: 'Find notes by meaning through the Dinox semantic index (online)',
|
|
89
|
+
arguments: [{ syntax: '<query>', description: 'Natural-language query' }],
|
|
90
|
+
options: [
|
|
91
|
+
SEMANTIC_MODE_OPTION,
|
|
92
|
+
{ flags: '--limit <n>', description: `Maximum returned notes (1-${SEMANTIC_LIMITS.search.max}, default ${SEMANTIC_LIMITS.search.default})`, type: 'number' },
|
|
93
|
+
...SEMANTIC_FILTER_OPTIONS,
|
|
94
|
+
{ flags: '--include-resources', description: 'Also match attachments (PDFs, images, web clips); hits report their parent note', type: 'boolean' },
|
|
95
|
+
],
|
|
96
|
+
schema: {
|
|
97
|
+
path: 'note.semantic',
|
|
98
|
+
risk: 'read',
|
|
99
|
+
auth: 'required',
|
|
100
|
+
globalOptions: NOTE_SEMANTIC_GLOBAL_OPTIONS,
|
|
101
|
+
output: {
|
|
102
|
+
type: 'structured',
|
|
103
|
+
summary: 'Notes ranked by semantic relevance, one entry per note with its best-matching snippet',
|
|
104
|
+
entity: 'semantic_note',
|
|
105
|
+
fields: SEMANTIC_NOTE_FIELDS,
|
|
106
|
+
metaFields: ['query', 'mode', 'returned_count', 'limit', 'include_resources', 'dropped_deleted_count', 'applied_filters'],
|
|
107
|
+
},
|
|
108
|
+
examples: [
|
|
109
|
+
'dino note semantic "how I planned the Q3 launch" --format json',
|
|
110
|
+
'dino note semantic "sleep and productivity" --tags health --days 90 --limit 20 --format json',
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
action: runNoteSemantic,
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: 'similar',
|
|
117
|
+
description: 'Find notes semantically similar to a note (online)',
|
|
118
|
+
arguments: [{ syntax: '<id>', description: 'Note id' }],
|
|
119
|
+
options: [
|
|
120
|
+
{ flags: '--limit <n>', description: `Maximum returned notes (1-${SEMANTIC_LIMITS.similar.max}, default ${SEMANTIC_LIMITS.similar.default})`, type: 'number' },
|
|
121
|
+
],
|
|
122
|
+
schema: {
|
|
123
|
+
path: 'note.similar',
|
|
124
|
+
risk: 'read',
|
|
125
|
+
auth: 'required',
|
|
126
|
+
globalOptions: NOTE_SEMANTIC_GLOBAL_OPTIONS,
|
|
127
|
+
output: {
|
|
128
|
+
type: 'structured',
|
|
129
|
+
summary: 'Semantically similar notes, excluding the note itself',
|
|
130
|
+
entity: 'semantic_note',
|
|
131
|
+
fields: SEMANTIC_NOTE_FIELDS,
|
|
132
|
+
metaFields: ['note_id', 'returned_count', 'limit', 'dropped_deleted_count'],
|
|
133
|
+
},
|
|
134
|
+
examples: ['dino note similar <id> --limit 10 --format json'],
|
|
135
|
+
},
|
|
136
|
+
action: runNoteSimilar,
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: 'context',
|
|
140
|
+
description: 'Assemble cited, token-budgeted context from notes to answer a question (online)',
|
|
141
|
+
arguments: [{ syntax: '<query>', description: 'Question to gather context for' }],
|
|
142
|
+
options: [
|
|
143
|
+
SEMANTIC_MODE_OPTION,
|
|
144
|
+
{ flags: '--max-tokens <n>', description: `Context token budget (1-${SEMANTIC_LIMITS.contextTokens.max}, default ${SEMANTIC_LIMITS.contextTokens.default})`, type: 'number' },
|
|
145
|
+
{ flags: '--max-notes <n>', description: `Maximum notes contributing blocks (1-${SEMANTIC_LIMITS.contextGroups.max}, default ${SEMANTIC_LIMITS.contextGroups.default})`, type: 'number' },
|
|
146
|
+
{ flags: '--limit <n>', description: `Retrieved chunks before assembly (1-${SEMANTIC_LIMITS.contextTopK.max}, default ${SEMANTIC_LIMITS.contextTopK.default})`, type: 'number' },
|
|
147
|
+
...SEMANTIC_FILTER_OPTIONS,
|
|
148
|
+
{ flags: '--notes-only', description: 'Leave attachments out of the context', type: 'boolean' },
|
|
149
|
+
],
|
|
150
|
+
schema: {
|
|
151
|
+
path: 'note.context',
|
|
152
|
+
risk: 'read',
|
|
153
|
+
auth: 'required',
|
|
154
|
+
globalOptions: NOTE_SEMANTIC_GLOBAL_OPTIONS,
|
|
155
|
+
output: {
|
|
156
|
+
type: 'structured',
|
|
157
|
+
summary: 'Context blocks (content with note id and citations) ready to ground an answer',
|
|
158
|
+
fields: ['blocks.ref', 'blocks.note_id', 'blocks.title', 'blocks.role', 'blocks.score', 'blocks.token_count', 'blocks.content', 'blocks.citations'],
|
|
159
|
+
metaFields: ['query', 'mode', 'returned_count', 'context_tokens', 'max_tokens', 'dropped_deleted_count'],
|
|
160
|
+
},
|
|
161
|
+
examples: ['dino note context "what did I decide about pricing?" --max-tokens 4000 --format json'],
|
|
162
|
+
},
|
|
163
|
+
action: runNoteContext,
|
|
164
|
+
},
|
|
67
165
|
{
|
|
68
166
|
name: 'get',
|
|
69
167
|
description: 'Get lightweight note context by id without exposing full note content',
|
|
@@ -107,7 +205,11 @@ export const noteCommandGroup = {
|
|
|
107
205
|
name: 'detail',
|
|
108
206
|
description: 'Get full note details for one or more ids',
|
|
109
207
|
arguments: [{ syntax: '[id]', description: 'Optional note id when reading a single note' }],
|
|
110
|
-
options: [
|
|
208
|
+
options: [
|
|
209
|
+
{ flags: '--ids <string|@file>', description: 'Batch note ids (JSON array or comma/newline-separated)', type: 'string' },
|
|
210
|
+
{ flags: '--content <formats>', description: 'Content fields to return: comma-separated md,text,json, or none (default: all three)', type: 'string' },
|
|
211
|
+
{ flags: '--max-chars <n>', description: 'Shorten content_md and content_text to n characters; sets content_truncated', type: 'number' },
|
|
212
|
+
],
|
|
111
213
|
schema: {
|
|
112
214
|
path: 'note.detail',
|
|
113
215
|
risk: 'read',
|
|
@@ -116,9 +218,13 @@ export const noteCommandGroup = {
|
|
|
116
218
|
output: {
|
|
117
219
|
type: 'structured',
|
|
118
220
|
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'],
|
|
221
|
+
fields: ['stale', 'count', 'notes.id', 'notes.title', 'notes.content_md', 'notes.content_text', 'notes.content_json', 'notes.content_truncated', '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
222
|
},
|
|
121
|
-
examples: [
|
|
223
|
+
examples: [
|
|
224
|
+
'dino note detail <id> --format json',
|
|
225
|
+
'dino note detail <id> --content md --max-chars 4000 --format json',
|
|
226
|
+
'dino note detail --ids @/tmp/note-ids.txt --format json',
|
|
227
|
+
],
|
|
122
228
|
},
|
|
123
229
|
action: runNoteDetail,
|
|
124
230
|
},
|
|
@@ -137,7 +243,7 @@ export const noteCommandGroup = {
|
|
|
137
243
|
path: 'note.export',
|
|
138
244
|
risk: 'read',
|
|
139
245
|
auth: 'required',
|
|
140
|
-
globalOptions: NOTE_GLOBAL_OPTIONS,
|
|
246
|
+
globalOptions: [...NOTE_GLOBAL_OPTIONS, ...PROGRESS_OPTIONS],
|
|
141
247
|
output: {
|
|
142
248
|
type: 'structured',
|
|
143
249
|
summary: 'Export payload or file write acknowledgement for note backup/migration',
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
declare const CONTENT_FIELDS: {
|
|
2
|
+
readonly md: "content_md";
|
|
3
|
+
readonly text: "content_text";
|
|
4
|
+
readonly json: "content_json";
|
|
5
|
+
};
|
|
6
|
+
type NoteContentFormat = keyof typeof CONTENT_FIELDS;
|
|
7
|
+
export type NoteContentShape = {
|
|
8
|
+
formats: ReadonlySet<NoteContentFormat>;
|
|
9
|
+
maxChars: number | null;
|
|
10
|
+
};
|
|
11
|
+
/** Parses `--content md,text,json|none` (default: all three, as before). */
|
|
12
|
+
export declare function parseNoteContentShape(content: unknown, maxChars: unknown): NoteContentShape;
|
|
13
|
+
/**
|
|
14
|
+
* Keeps the requested content fields and shortens Markdown / plain text to
|
|
15
|
+
* `maxChars` characters (whole code points). `content_truncated` reports
|
|
16
|
+
* whether anything was cut, so an agent knows to fetch more.
|
|
17
|
+
*/
|
|
18
|
+
export declare function shapeNoteContent(note: Record<string, unknown>, shape: NoteContentShape): Record<string, unknown>;
|
|
19
|
+
export {};
|