@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
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import { resolveAuthIdentity } from '../../auth/userInfo.js';
|
|
2
2
|
import { registerCommandGroup } from '../descriptor/register.js';
|
|
3
3
|
import { FORMAT_OPTIONS, OFFLINE_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
|
|
4
|
-
import { loadConfig } from '../../config/store.js';
|
|
5
|
-
import { resolveConfig } from '../../config/resolve.js';
|
|
6
|
-
import { connectPowerSync, waitForIdleDataFlow } from '../../powersync/runtime.js';
|
|
7
4
|
import { readStringOrFile } from '../../utils/argValue.js';
|
|
8
5
|
import { printDryRunPreview } from '../../utils/dryRun.js';
|
|
9
6
|
import { DinoxError } from '../../utils/errors.js';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
7
|
+
import { printSuccess } from '../../utils/output.js';
|
|
8
|
+
import { commandStale, printUploadDisabledWarning, syncBeforeCommand, waitForMutationUpload, withCommandRuntime, } from '../runtime.js';
|
|
12
9
|
import { buildTodoAppendDryRunPreview, buildTodoCreateDryRunPreview, buildTodoUpdateDryRunPreview, } from './dryRun.js';
|
|
13
10
|
import { appendTodoTasks, createTodoNote, normalizeTaskTexts, searchTodoTasks, updateTodoTaskStatus, } from './repo.js';
|
|
14
11
|
const DATE_ONLY_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
@@ -192,35 +189,14 @@ async function parseTaskInput(args) {
|
|
|
192
189
|
}
|
|
193
190
|
return normalizeTaskTexts(merged);
|
|
194
191
|
}
|
|
195
|
-
async function syncBeforeTodoCommand(db, offline, timeoutMs) {
|
|
196
|
-
if (offline) {
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
await waitForIdleDataFlow(db, Math.min(timeoutMs, 10_000));
|
|
200
|
-
}
|
|
201
192
|
async function withTodoDatabase(globals, run) {
|
|
202
|
-
|
|
203
|
-
const config = resolveConfig(rawConfig);
|
|
204
|
-
const timeoutMs = parseSyncTimeoutMs(globals.syncTimeout) ?? config.sync.timeoutMs;
|
|
205
|
-
const offline = Boolean(globals.offline);
|
|
206
|
-
const { db } = await connectPowerSync({ config, offline, timeoutMs });
|
|
207
|
-
try {
|
|
208
|
-
await run({
|
|
209
|
-
db,
|
|
210
|
-
rawConfig,
|
|
211
|
-
config,
|
|
212
|
-
offline,
|
|
213
|
-
timeoutMs,
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
finally {
|
|
217
|
-
await db.close().catch(() => undefined);
|
|
218
|
-
}
|
|
193
|
+
await withCommandRuntime(globals, run);
|
|
219
194
|
}
|
|
220
195
|
async function runTodoSearch({ args, globals, options }) {
|
|
221
196
|
const searchOptions = options;
|
|
222
|
-
await withTodoDatabase(globals, async (
|
|
223
|
-
|
|
197
|
+
await withTodoDatabase(globals, async (runtime) => {
|
|
198
|
+
const { db } = runtime;
|
|
199
|
+
await syncBeforeCommand(runtime);
|
|
224
200
|
const keyword = args.query?.trim() ?? '';
|
|
225
201
|
const taskStatus = parseTaskStatus(searchOptions.status);
|
|
226
202
|
const taskTagFilter = typeof searchOptions.tags === 'string'
|
|
@@ -241,13 +217,14 @@ async function runTodoSearch({ args, globals, options }) {
|
|
|
241
217
|
limit,
|
|
242
218
|
scanLimit,
|
|
243
219
|
});
|
|
244
|
-
printSuccess(result);
|
|
220
|
+
printSuccess({ stale: commandStale(runtime), ...result });
|
|
245
221
|
});
|
|
246
222
|
}
|
|
247
223
|
async function runTodoAppend({ args, globals, options }) {
|
|
248
224
|
const appendOptions = options;
|
|
249
|
-
await withTodoDatabase(globals, async (
|
|
250
|
-
|
|
225
|
+
await withTodoDatabase(globals, async (runtime) => {
|
|
226
|
+
const { db, config, offline } = runtime;
|
|
227
|
+
await syncBeforeCommand(runtime);
|
|
251
228
|
const taskTexts = await parseTaskInput({
|
|
252
229
|
positionalTask: args.task,
|
|
253
230
|
tasksOption: typeof appendOptions.tasks === 'string' ? appendOptions.tasks : undefined,
|
|
@@ -268,13 +245,16 @@ async function runTodoAppend({ args, globals, options }) {
|
|
|
268
245
|
noteId: typeof appendOptions.noteId === 'string' ? appendOptions.noteId : undefined,
|
|
269
246
|
taskTexts,
|
|
270
247
|
});
|
|
271
|
-
|
|
248
|
+
printUploadDisabledWarning(runtime);
|
|
249
|
+
await waitForMutationUpload(runtime);
|
|
250
|
+
printSuccess({ ok: true, stale: commandStale(runtime), ...result });
|
|
272
251
|
});
|
|
273
252
|
}
|
|
274
253
|
async function runTodoCreate({ args, globals, options }) {
|
|
275
254
|
const createOptions = options;
|
|
276
|
-
await withTodoDatabase(globals, async (
|
|
277
|
-
|
|
255
|
+
await withTodoDatabase(globals, async (runtime) => {
|
|
256
|
+
const { db, config, offline } = runtime;
|
|
257
|
+
await syncBeforeCommand(runtime);
|
|
278
258
|
const taskTexts = await parseTaskInput({
|
|
279
259
|
positionalTask: args.task,
|
|
280
260
|
tasksOption: typeof createOptions.tasks === 'string' ? createOptions.tasks : undefined,
|
|
@@ -290,25 +270,22 @@ async function runTodoCreate({ args, globals, options }) {
|
|
|
290
270
|
printDryRunPreview(preview);
|
|
291
271
|
return;
|
|
292
272
|
}
|
|
293
|
-
const identity = await resolveAuthIdentity(
|
|
273
|
+
const identity = await resolveAuthIdentity(config);
|
|
294
274
|
const result = await createTodoNote(db, {
|
|
295
275
|
taskTexts,
|
|
296
276
|
title: typeof createOptions.title === 'string' ? createOptions.title : undefined,
|
|
297
277
|
userId: identity.userId,
|
|
298
278
|
});
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
302
|
-
if (!offline) {
|
|
303
|
-
await waitForIdleDataFlow(db, Math.min(timeoutMs, 5_000));
|
|
304
|
-
}
|
|
305
|
-
printSuccess({ ok: true, ...result });
|
|
279
|
+
printUploadDisabledWarning(runtime);
|
|
280
|
+
await waitForMutationUpload(runtime);
|
|
281
|
+
printSuccess({ ok: true, stale: commandStale(runtime), ...result });
|
|
306
282
|
});
|
|
307
283
|
}
|
|
308
284
|
async function runTodoUpdate({ args, globals, options }) {
|
|
309
285
|
const updateOptions = options;
|
|
310
|
-
await withTodoDatabase(globals, async (
|
|
311
|
-
|
|
286
|
+
await withTodoDatabase(globals, async (runtime) => {
|
|
287
|
+
const { db, config, offline } = runtime;
|
|
288
|
+
await syncBeforeCommand(runtime);
|
|
312
289
|
const taskId = args.taskId;
|
|
313
290
|
if (!taskId) {
|
|
314
291
|
throw new DinoxError('taskId is required');
|
|
@@ -329,7 +306,9 @@ async function runTodoUpdate({ args, globals, options }) {
|
|
|
329
306
|
taskId,
|
|
330
307
|
checked,
|
|
331
308
|
});
|
|
332
|
-
|
|
309
|
+
printUploadDisabledWarning(runtime);
|
|
310
|
+
await waitForMutationUpload(runtime);
|
|
311
|
+
printSuccess({ ok: true, stale: commandStale(runtime), ...result });
|
|
333
312
|
});
|
|
334
313
|
}
|
|
335
314
|
const todoSearchCommand = {
|
|
@@ -360,17 +339,7 @@ const todoSearchCommand = {
|
|
|
360
339
|
type: 'structured',
|
|
361
340
|
summary: 'Todo search payload with metadata and task list',
|
|
362
341
|
fields: ['tasks.task_id', 'tasks.task_key', 'tasks.note_id', 'tasks.note_title', 'tasks.status', 'tasks.depth', 'tasks.parent_id', 'tasks.tags'],
|
|
363
|
-
metaFields: ['meta.query', 'meta.returned_count', 'meta.limit', 'meta.task_status', 'meta.task_tag_filter', 'meta.task_date_range', 'meta.scanned_note_count'],
|
|
364
|
-
},
|
|
365
|
-
mcp: {
|
|
366
|
-
toolName: 'dinox_search_todos',
|
|
367
|
-
description: 'Search todo tasks',
|
|
368
|
-
properties: [
|
|
369
|
-
{ name: 'query', type: 'string', description: 'Task text query' },
|
|
370
|
-
{ name: 'status', type: 'string', description: 'Task status', enum: ['all', 'completed', 'uncompleted'] },
|
|
371
|
-
{ name: 'tags', type: 'string[]', description: 'Task tags' },
|
|
372
|
-
{ name: 'limit', type: 'number', description: 'Max results (default: 50)' },
|
|
373
|
-
],
|
|
342
|
+
metaFields: ['stale', 'meta.query', 'meta.returned_count', 'meta.limit', 'meta.task_status', 'meta.task_tag_filter', 'meta.task_date_range', 'meta.scanned_note_count'],
|
|
374
343
|
},
|
|
375
344
|
examples: ['dino todo search "报销" --status uncompleted --format json', 'dino todo search --days 7 --format json'],
|
|
376
345
|
},
|
|
@@ -404,7 +373,7 @@ const todoAppendCommand = {
|
|
|
404
373
|
output: {
|
|
405
374
|
type: 'structured',
|
|
406
375
|
summary: 'Append result with target note and appended task ids',
|
|
407
|
-
fields: ['noteId', 'appendedCount', 'taskIds'],
|
|
376
|
+
fields: ['noteId', 'appendedCount', 'taskIds', 'stale'],
|
|
408
377
|
},
|
|
409
378
|
examples: ['dino todo append --note-id <id> --task "补交报销单" --format json'],
|
|
410
379
|
},
|
|
@@ -438,7 +407,7 @@ const todoCreateCommand = {
|
|
|
438
407
|
output: {
|
|
439
408
|
type: 'structured',
|
|
440
409
|
summary: 'Creation result with new note id and task ids',
|
|
441
|
-
fields: ['noteId', 'taskIds', 'title'],
|
|
410
|
+
fields: ['noteId', 'taskIds', 'title', 'stale'],
|
|
442
411
|
},
|
|
443
412
|
examples: ['dino todo create --task "补交报销单" --task "整理发票" --format json'],
|
|
444
413
|
},
|
|
@@ -469,7 +438,7 @@ const todoUpdateCommand = {
|
|
|
469
438
|
output: {
|
|
470
439
|
type: 'structured',
|
|
471
440
|
summary: 'Mutation result with updated status and target note id',
|
|
472
|
-
fields: ['taskId', 'checked', 'updated', 'noteId'],
|
|
441
|
+
fields: ['taskId', 'checked', 'updated', 'noteId', 'stale'],
|
|
473
442
|
},
|
|
474
443
|
examples: ['dino todo update <taskId> --status completed --format json'],
|
|
475
444
|
},
|
|
@@ -8,15 +8,40 @@ export declare function buildTagAddDryRunPreview(args: {
|
|
|
8
8
|
createdPaths: string[];
|
|
9
9
|
restoredPaths: string[];
|
|
10
10
|
}): DryRunPreview;
|
|
11
|
+
export declare function buildTagMutationDryRunPreview(args: {
|
|
12
|
+
config: ResolvedDinoxConfig;
|
|
13
|
+
offline: boolean;
|
|
14
|
+
command: string;
|
|
15
|
+
action: 'rename_tag' | 'move_tag' | 'merge_tag' | 'cleanup_tags';
|
|
16
|
+
summary: string;
|
|
17
|
+
risk: 'write' | 'high-risk-write';
|
|
18
|
+
inputs: Record<string, unknown>;
|
|
19
|
+
changes?: Record<string, unknown> | Array<Record<string, unknown>>;
|
|
20
|
+
targets?: Array<Record<string, unknown>>;
|
|
21
|
+
}): DryRunPreview;
|
|
11
22
|
export declare function buildBoxAddDryRunPreview(args: {
|
|
12
23
|
config: ResolvedDinoxConfig;
|
|
13
24
|
offline: boolean;
|
|
14
25
|
name: string;
|
|
26
|
+
path: string;
|
|
15
27
|
description: string;
|
|
16
28
|
color: string;
|
|
17
29
|
restored: boolean;
|
|
30
|
+
createdPaths: string[];
|
|
31
|
+
restoredPaths: string[];
|
|
18
32
|
existingId?: string;
|
|
19
33
|
}): DryRunPreview;
|
|
34
|
+
export declare function buildBoxMutationDryRunPreview(args: {
|
|
35
|
+
config: ResolvedDinoxConfig;
|
|
36
|
+
offline: boolean;
|
|
37
|
+
command: string;
|
|
38
|
+
action: 'rename_box' | 'move_box' | 'merge_box' | 'cleanup_boxes';
|
|
39
|
+
summary: string;
|
|
40
|
+
risk: 'write' | 'high-risk-write';
|
|
41
|
+
inputs: Record<string, unknown>;
|
|
42
|
+
changes?: Record<string, unknown> | Array<Record<string, unknown>>;
|
|
43
|
+
targets?: Array<Record<string, unknown>>;
|
|
44
|
+
}): DryRunPreview;
|
|
20
45
|
export declare function buildPromptAddDryRunPreview(args: {
|
|
21
46
|
config: ResolvedDinoxConfig;
|
|
22
47
|
offline: boolean;
|
|
@@ -28,28 +28,58 @@ export function buildTagAddDryRunPreview(args) {
|
|
|
28
28
|
},
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
|
+
export function buildTagMutationDryRunPreview(args) {
|
|
32
|
+
return {
|
|
33
|
+
dry_run: true,
|
|
34
|
+
command: args.command,
|
|
35
|
+
action: args.action,
|
|
36
|
+
risk: args.risk,
|
|
37
|
+
summary: args.summary,
|
|
38
|
+
warnings: buildExecutionWarnings(args.config, args.offline),
|
|
39
|
+
inputs: args.inputs,
|
|
40
|
+
...(args.targets ? { targets: args.targets } : {}),
|
|
41
|
+
...(args.changes ? { changes: args.changes } : {}),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
31
44
|
export function buildBoxAddDryRunPreview(args) {
|
|
32
45
|
return {
|
|
33
46
|
dry_run: true,
|
|
34
47
|
command: 'dino box add',
|
|
35
48
|
action: 'add_box',
|
|
36
49
|
risk: 'write',
|
|
37
|
-
summary: `${args.restored ? 'Restore' : 'Create'} zettel box "${args.
|
|
50
|
+
summary: `${args.restored ? 'Restore' : 'Create'} zettel box "${args.path}".`,
|
|
38
51
|
warnings: buildExecutionWarnings(args.config, args.offline),
|
|
39
52
|
inputs: {
|
|
40
53
|
name: args.name,
|
|
54
|
+
path: args.path,
|
|
41
55
|
...(args.description ? { description: args.description } : {}),
|
|
42
56
|
...(args.color ? { color: args.color } : {}),
|
|
43
57
|
},
|
|
44
58
|
changes: {
|
|
45
59
|
name: args.name,
|
|
60
|
+
path: args.path,
|
|
46
61
|
description: args.description,
|
|
47
62
|
color: args.color,
|
|
48
63
|
restored: args.restored,
|
|
64
|
+
created_paths: args.createdPaths,
|
|
65
|
+
restored_paths: args.restoredPaths,
|
|
49
66
|
...(args.existingId ? { existing_id: args.existingId } : {}),
|
|
50
67
|
},
|
|
51
68
|
};
|
|
52
69
|
}
|
|
70
|
+
export function buildBoxMutationDryRunPreview(args) {
|
|
71
|
+
return {
|
|
72
|
+
dry_run: true,
|
|
73
|
+
command: args.command,
|
|
74
|
+
action: args.action,
|
|
75
|
+
risk: args.risk,
|
|
76
|
+
summary: args.summary,
|
|
77
|
+
warnings: buildExecutionWarnings(args.config, args.offline),
|
|
78
|
+
inputs: args.inputs,
|
|
79
|
+
...(args.targets ? { targets: args.targets } : {}),
|
|
80
|
+
...(args.changes ? { changes: args.changes } : {}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
53
83
|
export function buildPromptAddDryRunPreview(args) {
|
|
54
84
|
return {
|
|
55
85
|
dry_run: true,
|
package/dist/config/resolve.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { POWERSYNC_DEFAULT_ENDPOINT, POWERSYNC_DEFAULT_TOKEN_ENDPOINT, POWERSYNC_DEFAULT_UPLOAD_BASE_URL, } from './serviceEndpoints.js';
|
|
2
|
-
import { normalizeAuthToken } from '../utils/authToken.js';
|
|
2
|
+
import { normalizeAuthToken, readEnvAuthToken } from '../utils/authToken.js';
|
|
3
3
|
const DEFAULT_UPLOAD_V4_PATH = '/powersync/v4/uploadData';
|
|
4
4
|
const DEFAULT_UPLOAD_V2_PATH = '/powersync/v2/uploadData';
|
|
5
5
|
const DEFAULT_SYNC_TIMEOUT_MS = 300_000;
|
|
@@ -30,7 +30,7 @@ export function resolveConfig(raw) {
|
|
|
30
30
|
const fileUploadV4Path = nonEmptyString(raw.powersync?.uploadV4Path);
|
|
31
31
|
const fileUploadV2Path = nonEmptyString(raw.powersync?.uploadV2Path);
|
|
32
32
|
const fileTimeoutMs = parsePositiveInt(raw.sync?.timeoutMs);
|
|
33
|
-
const envAuth =
|
|
33
|
+
const envAuth = readEnvAuthToken();
|
|
34
34
|
const envTokenEndpoint = nonEmptyString(process.env.DINOX_POWERSYNC_TOKEN_ENDPOINT) ?? nonEmptyString(process.env.POWERSYNC_TOKEN_ENDPOINT);
|
|
35
35
|
const envUploadV4Path = nonEmptyString(process.env.DINOX_POWERSYNC_UPLOAD_V4_PATH);
|
|
36
36
|
const envUploadV2Path = nonEmptyString(process.env.DINOX_POWERSYNC_UPLOAD_V2_PATH);
|
package/dist/daemon/index.d.ts
CHANGED
package/dist/daemon/index.js
CHANGED
|
@@ -5,17 +5,19 @@ import { readPid, isProcessRunning, removePid } from './pid.js';
|
|
|
5
5
|
import { isDaemonRunning } from './client.js';
|
|
6
6
|
import { getPackageVersion } from '../utils/version.js';
|
|
7
7
|
import { DinoxError } from '../utils/errors.js';
|
|
8
|
-
import { printHumanReadableError, printHumanReadableLine } from '../utils/output.js';
|
|
8
|
+
import { getRequestedStructuredOutputFormat, printHumanReadableError, printHumanReadableLine, printSuccess, } from '../utils/output.js';
|
|
9
|
+
import { registerCommandGroup } from '../commands/descriptor/register.js';
|
|
10
|
+
import { FORMAT_OPTIONS } from '../commands/schema/globalOptions.js';
|
|
9
11
|
const DAEMON_STOP_POLL_INTERVAL_MS = 100;
|
|
10
12
|
const DAEMON_STOP_TIMEOUT_MS = 5_000;
|
|
11
13
|
function parsePort(value) {
|
|
12
14
|
if (value === undefined || value === null || value === '') {
|
|
13
15
|
return DEFAULT_DAEMON_PORT;
|
|
14
16
|
}
|
|
15
|
-
if (typeof value !== 'string') {
|
|
17
|
+
if (typeof value !== 'string' && typeof value !== 'number') {
|
|
16
18
|
throw new DinoxError('--port must be a valid port number');
|
|
17
19
|
}
|
|
18
|
-
const trimmed = value.trim();
|
|
20
|
+
const trimmed = String(value).trim();
|
|
19
21
|
if (!/^\d+$/.test(trimmed)) {
|
|
20
22
|
throw new DinoxError('--port must be a valid port number');
|
|
21
23
|
}
|
|
@@ -35,10 +37,9 @@ async function waitForProcessExit(pid, timeoutMs) {
|
|
|
35
37
|
}
|
|
36
38
|
return !isProcessRunning(pid);
|
|
37
39
|
}
|
|
38
|
-
async function startDaemon(port, detached = true) {
|
|
40
|
+
async function startDaemon(port, detached = true, options = {}) {
|
|
39
41
|
if (await isDaemonRunning()) {
|
|
40
|
-
|
|
41
|
-
return;
|
|
42
|
+
return { running: true, port, detached, alreadyRunning: true };
|
|
42
43
|
}
|
|
43
44
|
if (detached) {
|
|
44
45
|
const args = ['daemon', 'start', '--port', String(port), '--no-detach'];
|
|
@@ -49,21 +50,27 @@ async function startDaemon(port, detached = true) {
|
|
|
49
50
|
child.unref();
|
|
50
51
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
51
52
|
if (await isDaemonRunning()) {
|
|
52
|
-
|
|
53
|
+
return { running: true, port, detached, alreadyRunning: false };
|
|
53
54
|
}
|
|
54
55
|
else {
|
|
55
|
-
|
|
56
|
+
return { running: false, port, detached, alreadyRunning: false };
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
59
|
else {
|
|
59
|
-
|
|
60
|
+
if (!options.quiet) {
|
|
61
|
+
printHumanReadableLine(`Starting daemon on port ${port}...`);
|
|
62
|
+
}
|
|
60
63
|
const server = new DaemonServer();
|
|
61
64
|
await server.initialize();
|
|
62
65
|
await server.start(port);
|
|
63
|
-
|
|
66
|
+
if (!options.quiet) {
|
|
67
|
+
printHumanReadableLine(`Daemon running on port ${port} (PID: ${process.pid})`);
|
|
68
|
+
}
|
|
64
69
|
process.on('SIGINT', async () => {
|
|
65
|
-
|
|
66
|
-
|
|
70
|
+
if (!options.quiet) {
|
|
71
|
+
printHumanReadableLine('');
|
|
72
|
+
printHumanReadableLine('Stopping daemon...');
|
|
73
|
+
}
|
|
67
74
|
await server.stop();
|
|
68
75
|
process.exit(0);
|
|
69
76
|
});
|
|
@@ -72,17 +79,16 @@ async function startDaemon(port, detached = true) {
|
|
|
72
79
|
process.exit(0);
|
|
73
80
|
});
|
|
74
81
|
}
|
|
82
|
+
return { running: true, port, detached, alreadyRunning: false };
|
|
75
83
|
}
|
|
76
84
|
async function stopDaemon() {
|
|
77
85
|
const pidInfo = readPid();
|
|
78
86
|
if (!pidInfo) {
|
|
79
|
-
|
|
80
|
-
return;
|
|
87
|
+
return { stopped: false, wasRunning: false, stalePidFile: false };
|
|
81
88
|
}
|
|
82
89
|
if (!isProcessRunning(pidInfo.pid)) {
|
|
83
|
-
printHumanReadableLine('Daemon is not running (stale PID file)');
|
|
84
90
|
removePid();
|
|
85
|
-
return;
|
|
91
|
+
return { stopped: false, wasRunning: false, stalePidFile: true };
|
|
86
92
|
}
|
|
87
93
|
try {
|
|
88
94
|
process.kill(pidInfo.pid, 'SIGTERM');
|
|
@@ -95,59 +101,171 @@ async function stopDaemon() {
|
|
|
95
101
|
throw new DinoxError(`Daemon did not stop within ${DAEMON_STOP_TIMEOUT_MS}ms. Try stopping it manually and rerun \`dino daemon status\`.`);
|
|
96
102
|
}
|
|
97
103
|
removePid();
|
|
98
|
-
|
|
104
|
+
return { stopped: true, wasRunning: true, stalePidFile: false };
|
|
99
105
|
}
|
|
100
106
|
async function daemonStatus() {
|
|
101
107
|
const pidInfo = readPid();
|
|
102
108
|
if (!pidInfo) {
|
|
103
|
-
|
|
104
|
-
return;
|
|
109
|
+
return { running: false, pid: null, port: null, version: null, stalePidFile: false };
|
|
105
110
|
}
|
|
106
111
|
const running = await isDaemonRunning();
|
|
107
112
|
if (running) {
|
|
108
|
-
|
|
109
|
-
printHumanReadableLine(`PID: ${pidInfo.pid}`);
|
|
110
|
-
printHumanReadableLine(`Port: ${pidInfo.port}`);
|
|
111
|
-
printHumanReadableLine(`Version: ${getPackageVersion()}`);
|
|
113
|
+
return { running: true, pid: pidInfo.pid, port: pidInfo.port, version: getPackageVersion(), stalePidFile: false };
|
|
112
114
|
}
|
|
113
115
|
else {
|
|
114
|
-
printHumanReadableLine('Status: Not running (stale PID file)');
|
|
115
116
|
removePid();
|
|
117
|
+
return { running: false, pid: null, port: null, version: null, stalePidFile: true };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function printDaemonStartResult(result) {
|
|
121
|
+
if (result.alreadyRunning) {
|
|
122
|
+
printHumanReadableLine('Daemon is already running');
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (result.running) {
|
|
126
|
+
printHumanReadableLine(`Daemon started on port ${result.port}`);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
printHumanReadableError('Failed to start daemon');
|
|
130
|
+
}
|
|
131
|
+
function printDaemonStopResult(result) {
|
|
132
|
+
if (result.stopped) {
|
|
133
|
+
printHumanReadableLine('Daemon stopped');
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (result.stalePidFile) {
|
|
137
|
+
printHumanReadableLine('Daemon is not running (stale PID file)');
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
printHumanReadableLine('Daemon is not running');
|
|
141
|
+
}
|
|
142
|
+
function printDaemonStatusResult(result) {
|
|
143
|
+
if (!result.running) {
|
|
144
|
+
printHumanReadableLine(result.stalePidFile ? 'Status: Not running (stale PID file)' : 'Status: Not running');
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
printHumanReadableLine('Status: Running');
|
|
148
|
+
printHumanReadableLine(`PID: ${result.pid}`);
|
|
149
|
+
printHumanReadableLine(`Port: ${result.port}`);
|
|
150
|
+
printHumanReadableLine(`Version: ${result.version}`);
|
|
151
|
+
}
|
|
152
|
+
async function runDaemonStart(context) {
|
|
153
|
+
const port = parsePort(context.options.port);
|
|
154
|
+
const structured = Boolean(getRequestedStructuredOutputFormat());
|
|
155
|
+
const result = await startDaemon(port, context.options.detach !== false, { quiet: structured });
|
|
156
|
+
if (structured) {
|
|
157
|
+
printSuccess(result);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
printDaemonStartResult(result);
|
|
161
|
+
}
|
|
162
|
+
async function runDaemonStop() {
|
|
163
|
+
const result = await stopDaemon();
|
|
164
|
+
if (getRequestedStructuredOutputFormat()) {
|
|
165
|
+
printSuccess(result);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
printDaemonStopResult(result);
|
|
169
|
+
}
|
|
170
|
+
async function runDaemonStatus() {
|
|
171
|
+
const result = await daemonStatus();
|
|
172
|
+
if (getRequestedStructuredOutputFormat()) {
|
|
173
|
+
printSuccess(result);
|
|
174
|
+
return;
|
|
116
175
|
}
|
|
176
|
+
printDaemonStatusResult(result);
|
|
117
177
|
}
|
|
178
|
+
async function runDaemonRestart(context) {
|
|
179
|
+
const structured = Boolean(getRequestedStructuredOutputFormat());
|
|
180
|
+
const stopResult = await stopDaemon();
|
|
181
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
182
|
+
const startResult = await startDaemon(parsePort(context.options.port), true, { quiet: structured });
|
|
183
|
+
if (structured) {
|
|
184
|
+
printSuccess({ stopped: stopResult.stopped, ...startResult });
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
printDaemonStopResult(stopResult);
|
|
188
|
+
printDaemonStartResult(startResult);
|
|
189
|
+
}
|
|
190
|
+
export const daemonCommandGroup = {
|
|
191
|
+
name: 'daemon',
|
|
192
|
+
description: 'Manage daemon process',
|
|
193
|
+
commands: [
|
|
194
|
+
{
|
|
195
|
+
name: 'start',
|
|
196
|
+
description: 'Start daemon process',
|
|
197
|
+
options: [
|
|
198
|
+
{ flags: '--port <number>', description: 'Daemon port', type: 'number', defaultValue: DEFAULT_DAEMON_PORT },
|
|
199
|
+
{ flags: '--no-detach', description: 'Run in foreground for debugging', type: 'boolean' },
|
|
200
|
+
],
|
|
201
|
+
schema: {
|
|
202
|
+
path: 'daemon.start',
|
|
203
|
+
risk: 'write',
|
|
204
|
+
auth: 'none',
|
|
205
|
+
globalOptions: FORMAT_OPTIONS,
|
|
206
|
+
output: {
|
|
207
|
+
type: 'structured',
|
|
208
|
+
summary: 'Daemon start acknowledgement',
|
|
209
|
+
fields: ['running', 'port', 'detached', 'alreadyRunning'],
|
|
210
|
+
},
|
|
211
|
+
examples: ['dino daemon start', 'dino daemon start --port 17321', 'dino daemon start --no-detach'],
|
|
212
|
+
},
|
|
213
|
+
action: runDaemonStart,
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: 'stop',
|
|
217
|
+
description: 'Stop daemon process',
|
|
218
|
+
schema: {
|
|
219
|
+
path: 'daemon.stop',
|
|
220
|
+
risk: 'write',
|
|
221
|
+
auth: 'none',
|
|
222
|
+
globalOptions: FORMAT_OPTIONS,
|
|
223
|
+
output: {
|
|
224
|
+
type: 'structured',
|
|
225
|
+
summary: 'Daemon stop acknowledgement',
|
|
226
|
+
fields: ['stopped', 'wasRunning', 'stalePidFile'],
|
|
227
|
+
},
|
|
228
|
+
examples: ['dino daemon stop'],
|
|
229
|
+
},
|
|
230
|
+
action: runDaemonStop,
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
name: 'status',
|
|
234
|
+
description: 'Show daemon status',
|
|
235
|
+
schema: {
|
|
236
|
+
path: 'daemon.status',
|
|
237
|
+
risk: 'read',
|
|
238
|
+
auth: 'none',
|
|
239
|
+
globalOptions: FORMAT_OPTIONS,
|
|
240
|
+
output: {
|
|
241
|
+
type: 'structured',
|
|
242
|
+
summary: 'Daemon process status',
|
|
243
|
+
fields: ['running', 'pid', 'port', 'version', 'stalePidFile'],
|
|
244
|
+
},
|
|
245
|
+
examples: ['dino daemon status'],
|
|
246
|
+
},
|
|
247
|
+
action: runDaemonStatus,
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
name: 'restart',
|
|
251
|
+
description: 'Restart daemon process',
|
|
252
|
+
options: [{ flags: '--port <number>', description: 'Daemon port', type: 'number', defaultValue: DEFAULT_DAEMON_PORT }],
|
|
253
|
+
schema: {
|
|
254
|
+
path: 'daemon.restart',
|
|
255
|
+
risk: 'write',
|
|
256
|
+
auth: 'none',
|
|
257
|
+
globalOptions: FORMAT_OPTIONS,
|
|
258
|
+
output: {
|
|
259
|
+
type: 'structured',
|
|
260
|
+
summary: 'Daemon restart acknowledgement',
|
|
261
|
+
fields: ['stopped', 'running', 'port', 'detached', 'alreadyRunning'],
|
|
262
|
+
},
|
|
263
|
+
examples: ['dino daemon restart', 'dino daemon restart --port 17321'],
|
|
264
|
+
},
|
|
265
|
+
action: runDaemonRestart,
|
|
266
|
+
},
|
|
267
|
+
],
|
|
268
|
+
};
|
|
118
269
|
export function registerDaemonCommands(program) {
|
|
119
|
-
|
|
120
|
-
daemon
|
|
121
|
-
.command('start')
|
|
122
|
-
.description('Start daemon process')
|
|
123
|
-
.option('--port <number>', 'daemon port', String(DEFAULT_DAEMON_PORT))
|
|
124
|
-
.option('--no-detach', 'run in foreground (for debugging)')
|
|
125
|
-
.action(async (options) => {
|
|
126
|
-
const port = parsePort(options.port);
|
|
127
|
-
await startDaemon(port, options.detach !== false);
|
|
128
|
-
});
|
|
129
|
-
daemon
|
|
130
|
-
.command('stop')
|
|
131
|
-
.description('Stop daemon process')
|
|
132
|
-
.action(async () => {
|
|
133
|
-
await stopDaemon();
|
|
134
|
-
});
|
|
135
|
-
daemon
|
|
136
|
-
.command('status')
|
|
137
|
-
.description('Show daemon status')
|
|
138
|
-
.action(async () => {
|
|
139
|
-
await daemonStatus();
|
|
140
|
-
});
|
|
141
|
-
daemon
|
|
142
|
-
.command('restart')
|
|
143
|
-
.description('Restart daemon process')
|
|
144
|
-
.option('--port <number>', 'daemon port', String(DEFAULT_DAEMON_PORT))
|
|
145
|
-
.action(async (options) => {
|
|
146
|
-
await stopDaemon();
|
|
147
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
148
|
-
await startDaemon(parsePort(options.port));
|
|
149
|
-
});
|
|
150
|
-
daemon.action((_options, command) => {
|
|
151
|
-
command.outputHelp();
|
|
152
|
-
});
|
|
270
|
+
registerCommandGroup(program, daemonCommandGroup);
|
|
153
271
|
}
|
|
@@ -35,3 +35,4 @@ export declare function connectPowerSync(args: {
|
|
|
35
35
|
timeoutMs: number;
|
|
36
36
|
}): Promise<ConnectResult>;
|
|
37
37
|
export declare function waitForIdleDataFlow(db: AbstractPowerSyncDatabase, timeoutMs: number): Promise<boolean>;
|
|
38
|
+
export declare function withPowerSyncWriteTransaction<T>(db: AbstractPowerSyncDatabase, run: (tx: AbstractPowerSyncDatabase) => Promise<T>): Promise<T>;
|
|
@@ -102,3 +102,13 @@ export async function waitForIdleDataFlow(db, timeoutMs) {
|
|
|
102
102
|
cancel();
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
+
export async function withPowerSyncWriteTransaction(db, run) {
|
|
106
|
+
const txDb = db;
|
|
107
|
+
if (typeof txDb.writeTransaction === 'function') {
|
|
108
|
+
return txDb.writeTransaction(run);
|
|
109
|
+
}
|
|
110
|
+
if (typeof txDb.database?.writeTransaction === 'function') {
|
|
111
|
+
return txDb.database.writeTransaction(run);
|
|
112
|
+
}
|
|
113
|
+
throw new DinoxError('PowerSync write transactions are not supported by this database runtime');
|
|
114
|
+
}
|
|
@@ -63,6 +63,8 @@ export declare const c_note_template: Table<{
|
|
|
63
63
|
export declare const c_zettel_box: Table<{
|
|
64
64
|
id: import("@powersync/common").BaseColumnType<string | null>;
|
|
65
65
|
name: import("@powersync/common").BaseColumnType<string | null>;
|
|
66
|
+
path: import("@powersync/common").BaseColumnType<string | null>;
|
|
67
|
+
parent_id: import("@powersync/common").BaseColumnType<string | null>;
|
|
66
68
|
description: import("@powersync/common").BaseColumnType<string | null>;
|
|
67
69
|
color: import("@powersync/common").BaseColumnType<string | null>;
|
|
68
70
|
priority: import("@powersync/common").BaseColumnType<number | null>;
|
|
@@ -232,6 +232,8 @@ export declare const AppSchema: Schema<{
|
|
|
232
232
|
c_zettel_box: import("@powersync/common").Table<{
|
|
233
233
|
id: import("@powersync/common").BaseColumnType<string | null>;
|
|
234
234
|
name: import("@powersync/common").BaseColumnType<string | null>;
|
|
235
|
+
path: import("@powersync/common").BaseColumnType<string | null>;
|
|
236
|
+
parent_id: import("@powersync/common").BaseColumnType<string | null>;
|
|
235
237
|
description: import("@powersync/common").BaseColumnType<string | null>;
|
|
236
238
|
color: import("@powersync/common").BaseColumnType<string | null>;
|
|
237
239
|
priority: import("@powersync/common").BaseColumnType<number | null>;
|