@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 { buildTagAddDryRunPreview, buildTagMutationDryRunPreview } 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 { addTag, inspectAddTag, listTags } from './repo.js';
|
|
11
13
|
import { getTagStats, getTagTree, inspectTagCleanup, mergeTag, moveTag, renameTag, suggestTagMerges, } from './operations.js';
|
|
@@ -56,6 +58,7 @@ async function runTagAdd(context) {
|
|
|
56
58
|
}
|
|
57
59
|
const structuredOutput = getRequestedStructuredOutputFormat() !== null;
|
|
58
60
|
await withCommandRuntime(context, async (runtime) => {
|
|
61
|
+
const durability = resolveWriteDurabilityForRuntime(runtime, context.options.durability);
|
|
59
62
|
const identity = await resolveAuthIdentity(runtime.config);
|
|
60
63
|
await syncBeforeCommand(runtime, { syncTokenIndex: true });
|
|
61
64
|
const emoji = typeof context.options.emoji === 'string' ? context.options.emoji : undefined;
|
|
@@ -76,8 +79,7 @@ async function runTagAdd(context) {
|
|
|
76
79
|
userId: identity.userId,
|
|
77
80
|
emoji,
|
|
78
81
|
});
|
|
79
|
-
await
|
|
80
|
-
const payload = { ok: true, stale: commandStale(runtime), ...result };
|
|
82
|
+
const payload = { ok: true, ...(await completeWrite(runtime, durability)), ...result };
|
|
81
83
|
if (structuredOutput) {
|
|
82
84
|
printSuccess(payload);
|
|
83
85
|
return;
|
|
@@ -88,9 +90,10 @@ async function runTagAdd(context) {
|
|
|
88
90
|
}
|
|
89
91
|
async function withTagWriteRuntime(context, run) {
|
|
90
92
|
return withCommandRuntime(context, async (runtime) => {
|
|
93
|
+
const durability = resolveWriteDurabilityForRuntime(runtime, context.options.durability);
|
|
91
94
|
const identity = await resolveAuthIdentity(runtime.config);
|
|
92
95
|
await syncBeforeCommand(runtime, { syncTokenIndex: true });
|
|
93
|
-
return run({ runtime, userId: identity.userId });
|
|
96
|
+
return run({ runtime, userId: identity.userId, durability });
|
|
94
97
|
});
|
|
95
98
|
}
|
|
96
99
|
function requireArg(value, message) {
|
|
@@ -103,7 +106,7 @@ function requireArg(value, message) {
|
|
|
103
106
|
async function runTagRename(context) {
|
|
104
107
|
const path = requireArg(context.args.path, 'Tag path is required');
|
|
105
108
|
const newName = requireArg(context.args.newName, 'New tag name is required');
|
|
106
|
-
await withTagWriteRuntime(context, async ({ runtime, userId }) => {
|
|
109
|
+
await withTagWriteRuntime(context, async ({ runtime, userId, durability }) => {
|
|
107
110
|
const result = await renameTag(runtime.db, { path, newName, userId }, { dryRun: Boolean(context.options.dryRun) });
|
|
108
111
|
if (Boolean(context.options.dryRun)) {
|
|
109
112
|
printDryRunPreview(buildTagMutationDryRunPreview({
|
|
@@ -118,8 +121,7 @@ async function runTagRename(context) {
|
|
|
118
121
|
}), runtime);
|
|
119
122
|
return;
|
|
120
123
|
}
|
|
121
|
-
await
|
|
122
|
-
printSuccess({ ok: true, stale: commandStale(runtime), ...result });
|
|
124
|
+
printSuccess({ ok: true, ...(await completeWrite(runtime, durability)), ...result });
|
|
123
125
|
});
|
|
124
126
|
}
|
|
125
127
|
async function runTagMove(context) {
|
|
@@ -128,7 +130,7 @@ async function runTagMove(context) {
|
|
|
128
130
|
if (!to) {
|
|
129
131
|
throw new DinoxError('--to is required');
|
|
130
132
|
}
|
|
131
|
-
await withTagWriteRuntime(context, async ({ runtime, userId }) => {
|
|
133
|
+
await withTagWriteRuntime(context, async ({ runtime, userId, durability }) => {
|
|
132
134
|
const result = await moveTag(runtime.db, { path, to, userId }, { dryRun: Boolean(context.options.dryRun) });
|
|
133
135
|
if (Boolean(context.options.dryRun)) {
|
|
134
136
|
printDryRunPreview(buildTagMutationDryRunPreview({
|
|
@@ -143,8 +145,7 @@ async function runTagMove(context) {
|
|
|
143
145
|
}), runtime);
|
|
144
146
|
return;
|
|
145
147
|
}
|
|
146
|
-
await
|
|
147
|
-
printSuccess({ ok: true, stale: commandStale(runtime), ...result });
|
|
148
|
+
printSuccess({ ok: true, ...(await completeWrite(runtime, durability)), ...result });
|
|
148
149
|
});
|
|
149
150
|
}
|
|
150
151
|
async function runTagMerge(context) {
|
|
@@ -158,7 +159,7 @@ async function runTagMerge(context) {
|
|
|
158
159
|
expectedCount,
|
|
159
160
|
};
|
|
160
161
|
assertWriteConfirmationProvided('Tag merge', writeOptions);
|
|
161
|
-
await withTagWriteRuntime(context, async ({ runtime, userId }) => {
|
|
162
|
+
await withTagWriteRuntime(context, async ({ runtime, userId, durability }) => {
|
|
162
163
|
const result = await mergeTag(runtime.db, { from, to, userId }, writeOptions);
|
|
163
164
|
if (dryRun) {
|
|
164
165
|
printDryRunPreview(buildTagMutationDryRunPreview({
|
|
@@ -179,8 +180,7 @@ async function runTagMerge(context) {
|
|
|
179
180
|
}), runtime);
|
|
180
181
|
return;
|
|
181
182
|
}
|
|
182
|
-
await
|
|
183
|
-
printSuccess({ ok: true, stale: commandStale(runtime), ...result });
|
|
183
|
+
printSuccess({ ok: true, ...(await completeWrite(runtime, durability)), ...result });
|
|
184
184
|
});
|
|
185
185
|
}
|
|
186
186
|
async function runTagSuggest(context) {
|
|
@@ -273,7 +273,7 @@ export const tagCommandGroup = {
|
|
|
273
273
|
options: [
|
|
274
274
|
{ flags: '--name <string>', description: 'Tag name/path (alternative to positional name)' },
|
|
275
275
|
{ flags: '--emoji <string>', description: 'Tag emoji' },
|
|
276
|
-
{ flags: '--dry-run', description: 'Preview the write without executing it' },
|
|
276
|
+
WRITE_DURABILITY_OPTION, { flags: '--dry-run', description: 'Preview the write without executing it' },
|
|
277
277
|
],
|
|
278
278
|
schema: {
|
|
279
279
|
path: 'tag.add',
|
|
@@ -283,7 +283,7 @@ export const tagCommandGroup = {
|
|
|
283
283
|
output: {
|
|
284
284
|
type: 'structured',
|
|
285
285
|
summary: 'Tag creation acknowledgement',
|
|
286
|
-
fields: ['id', 'tag', 'createdNodes', 'restoredNodes',
|
|
286
|
+
fields: ['id', 'tag', 'createdNodes', 'restoredNodes', ...WRITE_RECEIPT_FIELDS],
|
|
287
287
|
},
|
|
288
288
|
examples: ['dino tag add "work/projects" --format json --dry-run', 'dino tag add "work/projects" --emoji "🏷️" --format json'],
|
|
289
289
|
},
|
|
@@ -296,7 +296,7 @@ export const tagCommandGroup = {
|
|
|
296
296
|
{ syntax: '<path>', description: 'Existing tag path' },
|
|
297
297
|
{ syntax: '<new-name>', description: 'New leaf name' },
|
|
298
298
|
],
|
|
299
|
-
options: [{ flags: '--dry-run', description: 'Preview the write without executing it' }],
|
|
299
|
+
options: [WRITE_DURABILITY_OPTION, { flags: '--dry-run', description: 'Preview the write without executing it' }],
|
|
300
300
|
schema: {
|
|
301
301
|
path: 'tag.rename',
|
|
302
302
|
risk: 'write',
|
|
@@ -305,7 +305,7 @@ export const tagCommandGroup = {
|
|
|
305
305
|
output: {
|
|
306
306
|
type: 'structured',
|
|
307
307
|
summary: 'Tag rename result',
|
|
308
|
-
fields: ['id', 'fromPath', 'toPath', 'updatedTags', 'updatedNotes',
|
|
308
|
+
fields: ['id', 'fromPath', 'toPath', 'updatedTags', 'updatedNotes', ...WRITE_RECEIPT_FIELDS],
|
|
309
309
|
},
|
|
310
310
|
examples: ['dino tag rename "AI/old" "new" --format json --dry-run'],
|
|
311
311
|
},
|
|
@@ -317,7 +317,7 @@ export const tagCommandGroup = {
|
|
|
317
317
|
arguments: [{ syntax: '<path>', description: 'Existing tag path' }],
|
|
318
318
|
options: [
|
|
319
319
|
{ flags: '--to <parent-path>', description: 'Target parent tag path', required: true, type: 'string' },
|
|
320
|
-
{ flags: '--dry-run', description: 'Preview the write without executing it' },
|
|
320
|
+
WRITE_DURABILITY_OPTION, { flags: '--dry-run', description: 'Preview the write without executing it' },
|
|
321
321
|
],
|
|
322
322
|
schema: {
|
|
323
323
|
path: 'tag.move',
|
|
@@ -327,7 +327,7 @@ export const tagCommandGroup = {
|
|
|
327
327
|
output: {
|
|
328
328
|
type: 'structured',
|
|
329
329
|
summary: 'Tag move result',
|
|
330
|
-
fields: ['id', 'fromPath', 'toPath', 'updatedTags', 'updatedNotes',
|
|
330
|
+
fields: ['id', 'fromPath', 'toPath', 'updatedTags', 'updatedNotes', ...WRITE_RECEIPT_FIELDS],
|
|
331
331
|
},
|
|
332
332
|
examples: ['dino tag move "AI" --to "Technology" --format json --dry-run'],
|
|
333
333
|
},
|
|
@@ -343,7 +343,7 @@ export const tagCommandGroup = {
|
|
|
343
343
|
options: [
|
|
344
344
|
{ flags: '--expected-count <n>', description: 'Required for real writes; must equal source tags plus notes affected', type: 'number' },
|
|
345
345
|
{ flags: '--confirm', description: 'Required for real writes after reviewing a dry run', type: 'boolean' },
|
|
346
|
-
{ flags: '--dry-run', description: 'Preview the write without executing it', type: 'boolean' },
|
|
346
|
+
WRITE_DURABILITY_OPTION, { flags: '--dry-run', description: 'Preview the write without executing it', type: 'boolean' },
|
|
347
347
|
],
|
|
348
348
|
schema: {
|
|
349
349
|
path: 'tag.merge',
|
|
@@ -353,7 +353,7 @@ export const tagCommandGroup = {
|
|
|
353
353
|
output: {
|
|
354
354
|
type: 'structured',
|
|
355
355
|
summary: 'Tag merge result',
|
|
356
|
-
fields: ['fromId', 'toId', 'fromPath', 'toPath', 'softDeletedTagIds', 'updatedNotes', 'affectedCount',
|
|
356
|
+
fields: ['fromId', 'toId', 'fromPath', 'toPath', 'softDeletedTagIds', 'updatedNotes', 'affectedCount', ...WRITE_RECEIPT_FIELDS],
|
|
357
357
|
},
|
|
358
358
|
examples: [
|
|
359
359
|
'dino tag merge "ai" "AI" --format json --dry-run',
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolveAuthIdentity } from '../../auth/userInfo.js';
|
|
2
|
+
import { nextCursor, resolveCursorOffset } from '../../utils/cursor.js';
|
|
2
3
|
import { registerCommandGroup } from '../descriptor/register.js';
|
|
3
4
|
import { FORMAT_OPTIONS, OFFLINE_OPTIONS, REQUIRE_SYNC_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
|
|
4
5
|
import { WRITE_DURABILITY_OPTION } from '../writeDurability.js';
|
|
@@ -147,12 +148,25 @@ async function runTodoSearch({ args, globals, options }) {
|
|
|
147
148
|
});
|
|
148
149
|
const limit = parsePositiveIntegerOption(searchOptions.limit, '--limit', DEFAULT_TASK_LIMIT, MAX_TASK_LIMIT);
|
|
149
150
|
const scanLimit = parsePositiveIntegerOption(searchOptions.scanLimit, '--scan-limit', DEFAULT_NOTE_SCAN_LIMIT, MAX_NOTE_SCAN_LIMIT);
|
|
151
|
+
// Raw option values, not the resolved range: `--days` must not change the hash between pages.
|
|
152
|
+
const cursorQuery = {
|
|
153
|
+
keyword,
|
|
154
|
+
includeDeleted: Boolean(searchOptions.includeDeleted),
|
|
155
|
+
taskStatus,
|
|
156
|
+
taskTagFilter,
|
|
157
|
+
from: searchOptions.from,
|
|
158
|
+
to: searchOptions.to,
|
|
159
|
+
days: searchOptions.days,
|
|
160
|
+
scanLimit,
|
|
161
|
+
};
|
|
162
|
+
const offset = resolveCursorOffset(searchOptions.cursor, 0, 'todo.search', cursorQuery);
|
|
150
163
|
const wire = toTodoSearchWire(keyword, {
|
|
151
164
|
includeDeleted: Boolean(searchOptions.includeDeleted),
|
|
152
165
|
taskStatus,
|
|
153
166
|
taskTagFilter,
|
|
154
167
|
taskDateRange,
|
|
155
168
|
limit,
|
|
169
|
+
offset,
|
|
156
170
|
scanLimit,
|
|
157
171
|
});
|
|
158
172
|
const result = await routeRead(globals, 'todo.search', wire, () => withTodoDatabase(globals, async (runtime) => {
|
|
@@ -161,7 +175,12 @@ async function runTodoSearch({ args, globals, options }) {
|
|
|
161
175
|
return { ...local, stale: commandStale(runtime) };
|
|
162
176
|
}));
|
|
163
177
|
const { stale, ...response } = result;
|
|
164
|
-
|
|
178
|
+
const nextOffset = response.meta.offset + response.meta.returned_count;
|
|
179
|
+
printSuccess({
|
|
180
|
+
stale: stale ?? false,
|
|
181
|
+
...response,
|
|
182
|
+
meta: { ...response.meta, next_cursor: nextCursor('todo.search', cursorQuery, nextOffset, response.meta.has_more) },
|
|
183
|
+
});
|
|
165
184
|
}
|
|
166
185
|
async function runTodoAppend({ args, globals, options }) {
|
|
167
186
|
const appendOptions = options;
|
|
@@ -290,6 +309,7 @@ const todoSearchCommand = {
|
|
|
290
309
|
{ flags: '--days <n>', description: 'Recent N days; mutually exclusive with --from/--to' },
|
|
291
310
|
{ flags: '--limit <n>', description: 'Maximum returned tasks' },
|
|
292
311
|
{ flags: '--scan-limit <n>', description: 'Maximum scanned note rows' },
|
|
312
|
+
{ flags: '--cursor <token>', description: 'Continue from meta.next_cursor of the previous page (same query)' },
|
|
293
313
|
{ flags: '--include-deleted', description: 'Include soft-deleted notes' },
|
|
294
314
|
],
|
|
295
315
|
schema: {
|
|
@@ -301,7 +321,7 @@ const todoSearchCommand = {
|
|
|
301
321
|
type: 'structured',
|
|
302
322
|
summary: 'Todo search payload with metadata and task list',
|
|
303
323
|
fields: ['tasks.task_id', 'tasks.task_key', 'tasks.note_id', 'tasks.note_title', 'tasks.status', 'tasks.depth', 'tasks.parent_id', 'tasks.tags'],
|
|
304
|
-
metaFields: ['stale', 'meta.query', 'meta.returned_count', 'meta.total_count', 'meta.total_count_is_lower_bound', 'meta.matched_note_count', 'meta.limit', 'meta.scan_limit', 'meta.scanned_note_count', 'meta.scan_truncated', 'meta.truncated', 'meta.applied_filters.status', 'meta.applied_filters.tags', 'meta.applied_filters.include_deleted', 'meta.applied_filters.date_range'],
|
|
324
|
+
metaFields: ['stale', 'meta.query', 'meta.returned_count', 'meta.total_count', 'meta.total_count_is_lower_bound', 'meta.matched_note_count', 'meta.limit', 'meta.offset', 'meta.has_more', 'meta.next_cursor', 'meta.scan_limit', 'meta.scanned_note_count', 'meta.scan_truncated', 'meta.truncated', 'meta.applied_filters.status', 'meta.applied_filters.tags', 'meta.applied_filters.include_deleted', 'meta.applied_filters.date_range'],
|
|
305
325
|
},
|
|
306
326
|
examples: ['dino todo search "报销" --status uncompleted --format json', 'dino todo search --days 7 --format json'],
|
|
307
327
|
},
|
|
@@ -7,6 +7,7 @@ export type SearchTodoOptions = {
|
|
|
7
7
|
taskTagFilter: string[];
|
|
8
8
|
taskDateRange?: TaskDateRangeFilter;
|
|
9
9
|
limit: number;
|
|
10
|
+
offset?: number;
|
|
10
11
|
scanLimit: number;
|
|
11
12
|
};
|
|
12
13
|
export type AppendTodoTasksOptions = {
|
|
@@ -65,6 +66,8 @@ export type SearchedTodoResponse = {
|
|
|
65
66
|
total_count: number;
|
|
66
67
|
matched_note_count: number;
|
|
67
68
|
limit: number;
|
|
69
|
+
offset: number;
|
|
70
|
+
has_more: boolean;
|
|
68
71
|
scan_limit: number;
|
|
69
72
|
scanned_note_count: number;
|
|
70
73
|
scan_truncated: boolean;
|
|
@@ -109,6 +112,8 @@ export type TodoSearchWire = {
|
|
|
109
112
|
taskStatus: TaskStatusFilter;
|
|
110
113
|
taskTagFilter: string[];
|
|
111
114
|
limit: number;
|
|
115
|
+
/** Index of the first returned task among the matches (from `--cursor`). */
|
|
116
|
+
offset?: number;
|
|
112
117
|
scanLimit: number;
|
|
113
118
|
startDate?: string;
|
|
114
119
|
endDate?: string;
|
|
@@ -40,6 +40,7 @@ export function toTodoSearchWire(query, options) {
|
|
|
40
40
|
taskStatus: options.taskStatus,
|
|
41
41
|
taskTagFilter: options.taskTagFilter,
|
|
42
42
|
limit: options.limit,
|
|
43
|
+
...(options.offset ? { offset: options.offset } : {}),
|
|
43
44
|
scanLimit: options.scanLimit,
|
|
44
45
|
startDate: options.taskDateRange?.startDate?.toISOString(),
|
|
45
46
|
endDate: options.taskDateRange?.endDate?.toISOString(),
|
|
@@ -62,6 +63,7 @@ export async function executeTodoSearch(db, wire) {
|
|
|
62
63
|
taskTagFilter: wire.taskTagFilter,
|
|
63
64
|
taskDateRange,
|
|
64
65
|
limit: wire.limit,
|
|
66
|
+
offset: wire.offset,
|
|
65
67
|
scanLimit: wire.scanLimit,
|
|
66
68
|
});
|
|
67
69
|
}
|
|
@@ -71,7 +73,8 @@ export async function searchTodoTasks(db, query, options) {
|
|
|
71
73
|
const noteRows = scannedRows.slice(0, options.scanLimit);
|
|
72
74
|
const allTasks = extractFlattenedTasks(noteRows);
|
|
73
75
|
const filteredTasks = filterTasksForTodoList(allTasks, query, options.taskStatus, options.taskTagFilter, options.taskDateRange);
|
|
74
|
-
const
|
|
76
|
+
const offset = options.offset ?? 0;
|
|
77
|
+
const visibleTasks = filteredTasks.slice(offset, offset + options.limit);
|
|
75
78
|
const tasks = visibleTasks.map((task) => ({
|
|
76
79
|
task_key: buildTaskKey(task.noteId, task.taskId),
|
|
77
80
|
note_id: task.noteId,
|
|
@@ -96,11 +99,13 @@ export async function searchTodoTasks(db, query, options) {
|
|
|
96
99
|
total_count: filteredTasks.length,
|
|
97
100
|
matched_note_count: new Set(filteredTasks.map((task) => task.noteId)).size,
|
|
98
101
|
limit: options.limit,
|
|
102
|
+
offset,
|
|
103
|
+
has_more: filteredTasks.length > offset + tasks.length,
|
|
99
104
|
scan_limit: options.scanLimit,
|
|
100
105
|
scanned_note_count: noteRows.length,
|
|
101
106
|
scan_truncated: scanTruncated,
|
|
102
107
|
total_count_is_lower_bound: scanTruncated,
|
|
103
|
-
truncated: scanTruncated || filteredTasks.length > tasks.length,
|
|
108
|
+
truncated: scanTruncated || filteredTasks.length > offset + tasks.length,
|
|
104
109
|
applied_filters: {
|
|
105
110
|
status: options.taskStatus,
|
|
106
111
|
tags: [...options.taskTagFilter],
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { WriteDurability } from './writeDurability.js';
|
|
2
|
+
import { type CommandRuntime, type MutationUploadReceipt } from './runtime.js';
|
|
3
|
+
/** Output fields every local-database write reports, as note and todo writes do. */
|
|
4
|
+
export declare const WRITE_RECEIPT_FIELDS: readonly ["durability", "upload_queue_remaining", "stale"];
|
|
5
|
+
export type WriteReceipt = MutationUploadReceipt & {
|
|
6
|
+
stale: boolean;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Waits for the upload `--durability` asks for and describes where the write
|
|
10
|
+
* stands: `durability` local or uploaded, the remaining upload queue, any
|
|
11
|
+
* upload error, and whether the pre-write sync was stale.
|
|
12
|
+
*/
|
|
13
|
+
export declare function completeWrite(runtime: CommandRuntime, durability: WriteDurability): Promise<WriteReceipt>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { commandStale, waitForMutationUpload } from './runtime.js';
|
|
2
|
+
/** Output fields every local-database write reports, as note and todo writes do. */
|
|
3
|
+
export const WRITE_RECEIPT_FIELDS = ['durability', 'upload_queue_remaining', 'stale'];
|
|
4
|
+
/**
|
|
5
|
+
* Waits for the upload `--durability` asks for and describes where the write
|
|
6
|
+
* stands: `durability` local or uploaded, the remaining upload queue, any
|
|
7
|
+
* upload error, and whether the pre-write sync was stale.
|
|
8
|
+
*/
|
|
9
|
+
export async function completeWrite(runtime, durability) {
|
|
10
|
+
const upload = await waitForMutationUpload(runtime, { durability });
|
|
11
|
+
return { ...upload, stale: commandStale(runtime) };
|
|
12
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export declare const POWERSYNC_DEFAULT_ENDPOINT = "https://powersync.dinoai.fun";
|
|
2
2
|
export declare const POWERSYNC_DEFAULT_TOKEN_ENDPOINT = "https://dinoai.chatgo.pro/auth/v2/token";
|
|
3
3
|
export declare const POWERSYNC_DEFAULT_UPLOAD_BASE_URL = "https://dinoai.chatgo.pro";
|
|
4
|
+
/** dinox-tool business API (semantic search, gateway credentials). */
|
|
5
|
+
export declare const AI_SERVICE_URL = "https://aisdk.chatgo.pro";
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export const POWERSYNC_DEFAULT_ENDPOINT = 'https://powersync.dinoai.fun';
|
|
2
2
|
export const POWERSYNC_DEFAULT_TOKEN_ENDPOINT = 'https://dinoai.chatgo.pro/auth/v2/token';
|
|
3
3
|
export const POWERSYNC_DEFAULT_UPLOAD_BASE_URL = 'https://dinoai.chatgo.pro';
|
|
4
|
+
/** dinox-tool business API (semantic search, gateway credentials). */
|
|
5
|
+
export const AI_SERVICE_URL = 'https://aisdk.chatgo.pro';
|
package/dist/daemon/client.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { DaemonPingResult, DaemonShutdownResult } from './types.js';
|
|
2
2
|
export declare function sendDaemonRequest(method: string, params?: unknown, requestOptions?: {
|
|
3
3
|
timeoutMs?: number;
|
|
4
|
+
/** Streamed responses: called with each NDJSON frame ahead of the final response line. */
|
|
5
|
+
onFrame?: (frame: unknown) => void;
|
|
4
6
|
}): Promise<unknown>;
|
|
5
7
|
export declare function isDaemonRunning(timeoutMs?: number): Promise<boolean>;
|
|
6
8
|
/**
|
package/dist/daemon/client.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { request as httpRequest } from 'node:http';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import { getDaemonSocketPath, isLegacyDaemonProcess, isProcessRunning, readPid, removePid, } from './pid.js';
|
|
4
|
+
import { getPackageVersion } from '../utils/version.js';
|
|
4
5
|
import { remainingDeadlineMs } from '../utils/deadline.js';
|
|
5
6
|
import { DinoxError, wireErrorCode } from '../utils/errors.js';
|
|
6
7
|
const DEFAULT_DAEMON_REQUEST_TIMEOUT_MS = 10_000;
|
|
@@ -34,6 +35,7 @@ export async function sendDaemonRequest(method, params, requestOptions = {}) {
|
|
|
34
35
|
};
|
|
35
36
|
return new Promise((resolve, reject) => {
|
|
36
37
|
let settled = false;
|
|
38
|
+
let connected = false;
|
|
37
39
|
let deadlineTimer;
|
|
38
40
|
const finalize = (error, value) => {
|
|
39
41
|
if (settled)
|
|
@@ -43,6 +45,8 @@ export async function sendDaemonRequest(method, params, requestOptions = {}) {
|
|
|
43
45
|
clearTimeout(deadlineTimer);
|
|
44
46
|
}
|
|
45
47
|
if (error) {
|
|
48
|
+
if (error instanceof Error)
|
|
49
|
+
Object.assign(error, { requestWritten: connected });
|
|
46
50
|
reject(error);
|
|
47
51
|
return;
|
|
48
52
|
}
|
|
@@ -62,15 +66,31 @@ export async function sendDaemonRequest(method, params, requestOptions = {}) {
|
|
|
62
66
|
};
|
|
63
67
|
const req = httpRequest(options, (res) => {
|
|
64
68
|
const statusCode = res.statusCode ?? 0;
|
|
65
|
-
|
|
69
|
+
let chunks = [];
|
|
66
70
|
let totalBytes = 0;
|
|
71
|
+
const { onFrame } = requestOptions;
|
|
67
72
|
res.on('data', (chunk) => {
|
|
68
73
|
totalBytes += chunk.length;
|
|
69
|
-
if (totalBytes > MAX_DAEMON_RESPONSE_BYTES) {
|
|
74
|
+
if (method !== 'command.execute' && totalBytes > MAX_DAEMON_RESPONSE_BYTES) {
|
|
70
75
|
req.destroy(new Error('Daemon response too large'));
|
|
71
76
|
return;
|
|
72
77
|
}
|
|
73
78
|
chunks.push(chunk);
|
|
79
|
+
if (!onFrame || !chunk.includes(0x0a))
|
|
80
|
+
return;
|
|
81
|
+
// Every complete line is a frame; the response itself is the unterminated last line.
|
|
82
|
+
let pending = Buffer.concat(chunks);
|
|
83
|
+
for (let newline = pending.indexOf(0x0a); newline !== -1; newline = pending.indexOf(0x0a)) {
|
|
84
|
+
const line = pending.subarray(0, newline).toString('utf-8');
|
|
85
|
+
pending = pending.subarray(newline + 1);
|
|
86
|
+
try {
|
|
87
|
+
onFrame(JSON.parse(line));
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// A malformed frame only loses that progress line.
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
chunks = [pending];
|
|
74
94
|
});
|
|
75
95
|
res.on('end', () => {
|
|
76
96
|
try {
|
|
@@ -92,10 +112,23 @@ export async function sendDaemonRequest(method, params, requestOptions = {}) {
|
|
|
92
112
|
}
|
|
93
113
|
});
|
|
94
114
|
});
|
|
115
|
+
req.on('socket', socket => {
|
|
116
|
+
// A reused keep-alive socket is already connected and never emits
|
|
117
|
+
// 'connect' again; listening anyway leaks one listener per request.
|
|
118
|
+
if (!socket.connecting) {
|
|
119
|
+
connected = true;
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
socket.once('connect', () => { connected = true; });
|
|
123
|
+
});
|
|
95
124
|
req.on('timeout', () => {
|
|
96
125
|
req.destroy(new Error('Daemon request timed out'));
|
|
97
126
|
});
|
|
98
127
|
req.on('error', (error) => finalize(error));
|
|
128
|
+
req.on('response', res => {
|
|
129
|
+
res.on('aborted', () => finalize(new Error('Daemon response interrupted')));
|
|
130
|
+
res.on('error', error => finalize(error));
|
|
131
|
+
});
|
|
99
132
|
deadlineTimer = setTimeout(() => {
|
|
100
133
|
req.destroy(new Error('Daemon request timed out'));
|
|
101
134
|
}, timeoutMs);
|
|
@@ -282,6 +315,16 @@ export async function waitForFreshDaemon(expectedVersion, expectedUserId, expect
|
|
|
282
315
|
}
|
|
283
316
|
}
|
|
284
317
|
}
|
|
318
|
+
/** Daemon log context for a stop request; never carries credentials. */
|
|
319
|
+
function redactRequesterCommand(argv) {
|
|
320
|
+
return argv.join(' ')
|
|
321
|
+
.replace(/(auth\s+login)\b[\s\S]*/i, '$1 [REDACTED]')
|
|
322
|
+
.replace(/((?:--)?[\w-]*(?:token|authorization|secret|password|api[-_]?key)[\w-]*[=\s]+)(?:Bearer\s+)?[^\s]+/gi, '$1[REDACTED]')
|
|
323
|
+
.replace(/\bBearer\s+\S+/gi, 'Bearer [REDACTED]')
|
|
324
|
+
.replace(/\beyJ[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+){1,2}\b/g, '[REDACTED]')
|
|
325
|
+
.replace(/\b[A-Za-z0-9_-]{32,}\b/g, '[REDACTED]')
|
|
326
|
+
.slice(0, 200);
|
|
327
|
+
}
|
|
285
328
|
/** Request shutdown of one exact daemon instance and reject if the socket changed owners. */
|
|
286
329
|
export async function requestDaemonShutdown(expectedInstanceId, timeoutMs = 2_000) {
|
|
287
330
|
if (!expectedInstanceId) {
|
|
@@ -290,7 +333,14 @@ export async function requestDaemonShutdown(expectedInstanceId, timeoutMs = 2_00
|
|
|
290
333
|
recoverable: true,
|
|
291
334
|
});
|
|
292
335
|
}
|
|
293
|
-
const result = await sendDaemonRequest('shutdown', {
|
|
336
|
+
const result = await sendDaemonRequest('shutdown', {
|
|
337
|
+
expectedInstanceId,
|
|
338
|
+
requester: {
|
|
339
|
+
pid: process.pid,
|
|
340
|
+
version: getPackageVersion(),
|
|
341
|
+
command: redactRequesterCommand(process.argv.slice(2)),
|
|
342
|
+
},
|
|
343
|
+
}, { timeoutMs });
|
|
294
344
|
if (!result
|
|
295
345
|
|| typeof result !== 'object'
|
|
296
346
|
|| result.stopping !== true
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** How often the daemon checks for a wake from sleep or a network change. */
|
|
2
|
+
export declare const CONNECTIVITY_CHECK_INTERVAL_MS = 10000;
|
|
3
|
+
/** A tick this late means the machine was asleep (timers do not run during sleep). */
|
|
4
|
+
export declare const WAKE_DRIFT_THRESHOLD_MS = 30000;
|
|
5
|
+
export type ConnectivityChange = 'wake' | 'network-change';
|
|
6
|
+
/** Addresses of the non-internal interfaces; changes on Wi-Fi switch, VPN, or link loss. */
|
|
7
|
+
export declare function networkSignature(): string;
|
|
8
|
+
/**
|
|
9
|
+
* Detects the events after which a long-lived sync stream may be a dead
|
|
10
|
+
* connection that still reports "connected": a wake from sleep and a change
|
|
11
|
+
* of network. Call `tick()` every CONNECTIVITY_CHECK_INTERVAL_MS.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createConnectivityWatcher(options: {
|
|
14
|
+
onChange: (change: ConnectivityChange) => void;
|
|
15
|
+
now?: () => number;
|
|
16
|
+
signature?: () => string;
|
|
17
|
+
intervalMs?: number;
|
|
18
|
+
}): {
|
|
19
|
+
tick: () => void;
|
|
20
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
/** How often the daemon checks for a wake from sleep or a network change. */
|
|
3
|
+
export const CONNECTIVITY_CHECK_INTERVAL_MS = 10_000;
|
|
4
|
+
/** A tick this late means the machine was asleep (timers do not run during sleep). */
|
|
5
|
+
export const WAKE_DRIFT_THRESHOLD_MS = 30_000;
|
|
6
|
+
/** Addresses of the non-internal interfaces; changes on Wi-Fi switch, VPN, or link loss. */
|
|
7
|
+
export function networkSignature() {
|
|
8
|
+
const addresses = [];
|
|
9
|
+
for (const [name, entries] of Object.entries(os.networkInterfaces())) {
|
|
10
|
+
for (const entry of entries ?? []) {
|
|
11
|
+
if (!entry.internal)
|
|
12
|
+
addresses.push(`${name}:${entry.address}`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return addresses.sort().join(',');
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Detects the events after which a long-lived sync stream may be a dead
|
|
19
|
+
* connection that still reports "connected": a wake from sleep and a change
|
|
20
|
+
* of network. Call `tick()` every CONNECTIVITY_CHECK_INTERVAL_MS.
|
|
21
|
+
*/
|
|
22
|
+
export function createConnectivityWatcher(options) {
|
|
23
|
+
const now = options.now ?? Date.now;
|
|
24
|
+
const signature = options.signature ?? networkSignature;
|
|
25
|
+
const intervalMs = options.intervalMs ?? CONNECTIVITY_CHECK_INTERVAL_MS;
|
|
26
|
+
let lastTickAt = now();
|
|
27
|
+
let lastSignature = signature();
|
|
28
|
+
return {
|
|
29
|
+
tick() {
|
|
30
|
+
const tickAt = now();
|
|
31
|
+
const drift = tickAt - lastTickAt - intervalMs;
|
|
32
|
+
lastTickAt = tickAt;
|
|
33
|
+
const currentSignature = signature();
|
|
34
|
+
const networkChanged = currentSignature !== lastSignature;
|
|
35
|
+
lastSignature = currentSignature;
|
|
36
|
+
if (drift >= WAKE_DRIFT_THRESHOLD_MS)
|
|
37
|
+
options.onChange('wake');
|
|
38
|
+
else if (networkChanged)
|
|
39
|
+
options.onChange('network-change');
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
package/dist/daemon/ensure.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
1
2
|
import { getDaemonInfo, isDaemonFresh, waitForFreshDaemon } from './client.js';
|
|
2
|
-
import {
|
|
3
|
+
import { matchesRuntimeTokenBinding } from './fresh.js';
|
|
4
|
+
import { restartDaemonLocked, stopDaemonLocked } from './index.js';
|
|
3
5
|
import { DEFAULT_DAEMON_PORT } from './types.js';
|
|
4
|
-
import { launchDaemon } from './launch.js';
|
|
6
|
+
import { daemonServiceSpec, foreignServiceOwner, launchDaemon, usableDaemonService } from './launch.js';
|
|
5
7
|
import { getDaemonLogPath } from './log.js';
|
|
6
|
-
import { acquireDaemonStartLock, } from './pid.js';
|
|
8
|
+
import { acquireDaemonStartLock, getDaemonSocketPath, isProcessRunning, readPid, } from './pid.js';
|
|
9
|
+
import { getDaemonService } from './supervisor.js';
|
|
7
10
|
import { loadConfig } from '../config/store.js';
|
|
8
11
|
import { fingerprintDaemonRuntimeConfig, resolveConfig } from '../config/resolve.js';
|
|
9
12
|
import { hydrateResolvedAuthIdentity } from '../auth/userInfo.js';
|
|
@@ -20,13 +23,22 @@ async function hydrateDaemonIdentity(baseConfig) {
|
|
|
20
23
|
runtimeConfigFingerprint: fingerprintDaemonRuntimeConfig(config),
|
|
21
24
|
};
|
|
22
25
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Refuses to restart or rewrite a service another dino install owns. The data
|
|
28
|
+
* command then runs in-process; `dino daemon install` (or start/restart)
|
|
29
|
+
* switches the service to this CLI.
|
|
30
|
+
*/
|
|
31
|
+
async function assertServiceNotForeign() {
|
|
32
|
+
const service = await usableDaemonService();
|
|
33
|
+
const owner = service ? foreignServiceOwner(service) : null;
|
|
34
|
+
if (!service || !owner)
|
|
35
|
+
return;
|
|
36
|
+
throw new DinoxError(`The ${service.kind} daemon service belongs to another dino install (${owner}).`, {
|
|
37
|
+
code: 'DAEMON_SERVICE_FOREIGN',
|
|
38
|
+
recoverable: true,
|
|
39
|
+
details: { service: service.kind, owner, definitionPath: service.definitionPath },
|
|
40
|
+
suggestion: 'Run `dino daemon install` with the dino you want the service to use.',
|
|
41
|
+
});
|
|
30
42
|
}
|
|
31
43
|
async function ensureDaemonRunningUnlocked() {
|
|
32
44
|
const expectedVersion = getPackageVersion();
|
|
@@ -43,15 +55,22 @@ async function ensureDaemonRunningUnlocked() {
|
|
|
43
55
|
return;
|
|
44
56
|
}
|
|
45
57
|
const expectedIdentity = await hydrateDaemonIdentity(baseConfig);
|
|
58
|
+
if (info && isDaemonFresh(info, expectedVersion, expectedIdentity.userId, expectedIdentity.tokenFingerprint, expectedIdentity.runtimeConfigFingerprint)) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
await assertServiceNotForeign();
|
|
46
62
|
if (info) {
|
|
47
63
|
// A daemon is reachable — make sure it is the *right* one. It holds a long-lived
|
|
48
64
|
// PowerSync connection bound to whatever account/version it started with, so after
|
|
49
65
|
// `auth login` (new userId) or `dino update` (new version) the running daemon would
|
|
50
66
|
// otherwise keep serving stale or wrong-account data. Restart it when it drifts.
|
|
51
|
-
|
|
67
|
+
printHumanReadableWarning('Restarting daemon (version or account changed)...');
|
|
68
|
+
const service = getDaemonService();
|
|
69
|
+
if (service?.isInstalled() && info.supervisor === service.kind
|
|
70
|
+
&& service.isCurrent(daemonServiceSpec(service, DEFAULT_DAEMON_PORT))) {
|
|
71
|
+
await restartDaemonLocked(DEFAULT_DAEMON_PORT, { quiet: true });
|
|
52
72
|
return;
|
|
53
73
|
}
|
|
54
|
-
printHumanReadableWarning('Restarting daemon (version or account changed)...');
|
|
55
74
|
await stopDaemonLocked().catch((error) => {
|
|
56
75
|
const message = error instanceof Error ? error.message : String(error);
|
|
57
76
|
printHumanReadableWarning(`warning: failed to stop stale daemon: ${message}`);
|
|
@@ -65,6 +84,23 @@ async function ensureDaemonRunningUnlocked() {
|
|
|
65
84
|
}
|
|
66
85
|
}
|
|
67
86
|
else {
|
|
87
|
+
// A live process that owns the socket but does not answer is hung; only
|
|
88
|
+
// launchd / systemd can replace it safely (they know the exact process).
|
|
89
|
+
const pid = readPid();
|
|
90
|
+
const service = getDaemonService();
|
|
91
|
+
if (pid && isProcessRunning(pid.pid) && existsSync(getDaemonSocketPath())
|
|
92
|
+
&& service?.isInstalled() && service.kind !== 'schtasks') {
|
|
93
|
+
printHumanReadableWarning('warning: daemon is not responding; restarting the service');
|
|
94
|
+
await service.restart();
|
|
95
|
+
if (await waitForFreshDaemon(expectedVersion, expectedIdentity.userId, expectedIdentity.tokenFingerprint, expectedIdentity.runtimeConfigFingerprint, DAEMON_READY_TIMEOUT_MS)) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
throw new DinoxError('The daemon service is still not responding after a restart.', {
|
|
99
|
+
code: 'DAEMON_START_FAILED',
|
|
100
|
+
details: { service: service.kind, logPath: getDaemonLogPath() },
|
|
101
|
+
suggestion: `Inspect ${getDaemonLogPath()} for the daemon's startup error.`,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
68
104
|
printHumanReadableWarning('Starting daemon...');
|
|
69
105
|
}
|
|
70
106
|
const launch = await launchDaemon(DEFAULT_DAEMON_PORT);
|