@dinoxx/dinox-cli 2.14.0 → 2.16.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.
Files changed (97) hide show
  1. package/README.md +2 -0
  2. package/dist/ai/ragxClient.d.ts +15 -0
  3. package/dist/ai/ragxClient.js +71 -0
  4. package/dist/cli.js +27 -3
  5. package/dist/commands/batch.d.ts +35 -0
  6. package/dist/commands/batch.js +202 -0
  7. package/dist/commands/boxes/index.js +21 -21
  8. package/dist/commands/mcp.d.ts +6 -0
  9. package/dist/commands/mcp.js +47 -0
  10. package/dist/commands/notes/actions.js +18 -2
  11. package/dist/commands/notes/descriptors.js +111 -5
  12. package/dist/commands/notes/detailContent.d.ts +19 -0
  13. package/dist/commands/notes/detailContent.js +62 -0
  14. package/dist/commands/notes/export.js +4 -0
  15. package/dist/commands/notes/repo/search.d.ts +2 -1
  16. package/dist/commands/notes/repo/search.js +1 -1
  17. package/dist/commands/notes/repo/semantic.d.ts +14 -0
  18. package/dist/commands/notes/repo/semantic.js +31 -0
  19. package/dist/commands/notes/searchResponse.d.ts +3 -0
  20. package/dist/commands/notes/searchResponse.js +1 -0
  21. package/dist/commands/notes/semantic.d.ts +116 -0
  22. package/dist/commands/notes/semantic.js +332 -0
  23. package/dist/commands/prompt/index.js +7 -5
  24. package/dist/commands/savedViews/index.js +12 -3
  25. package/dist/commands/schema/globalOptions.d.ts +6 -0
  26. package/dist/commands/schema/globalOptions.js +6 -0
  27. package/dist/commands/schema/renderSkills.js +5 -2
  28. package/dist/commands/schema/spec.js +18 -7
  29. package/dist/commands/storage/index.js +7 -5
  30. package/dist/commands/sync.js +3 -3
  31. package/dist/commands/tags/index.js +21 -21
  32. package/dist/commands/todo/index.js +22 -2
  33. package/dist/commands/todo/repo.d.ts +5 -0
  34. package/dist/commands/todo/repo.js +7 -2
  35. package/dist/commands/writeReceipt.d.ts +13 -0
  36. package/dist/commands/writeReceipt.js +12 -0
  37. package/dist/config/serviceEndpoints.d.ts +2 -0
  38. package/dist/config/serviceEndpoints.js +2 -0
  39. package/dist/daemon/client.d.ts +2 -0
  40. package/dist/daemon/client.js +22 -2
  41. package/dist/daemon/forward.d.ts +4 -1
  42. package/dist/daemon/forward.js +13 -4
  43. package/dist/daemon/server.d.ts +1 -1
  44. package/dist/daemon/server.js +17 -1
  45. package/dist/daemon/types.d.ts +6 -0
  46. package/dist/dinox.js +12 -10
  47. package/dist/gateway/adapters/acp.d.ts +5 -1
  48. package/dist/gateway/adapters/acp.js +21 -9
  49. package/dist/gateway/adapters/echo.js +7 -4
  50. package/dist/gateway/adapters/index.d.ts +1 -0
  51. package/dist/gateway/adapters/index.js +5 -3
  52. package/dist/gateway/deviceHost.d.ts +3 -1
  53. package/dist/gateway/deviceHost.js +75 -5
  54. package/dist/gateway/mcpCommandTools.d.ts +15 -5
  55. package/dist/gateway/mcpCommandTools.js +30 -19
  56. package/dist/gateway/mcpEnv.d.ts +4 -0
  57. package/dist/gateway/mcpEnv.js +4 -0
  58. package/dist/gateway/mcpGroups.d.ts +3 -0
  59. package/dist/gateway/mcpGroups.js +4 -0
  60. package/dist/gateway/mcpServer.d.ts +10 -1
  61. package/dist/gateway/mcpServer.js +26 -5
  62. package/dist/gateway/protocol.d.ts +31 -2
  63. package/dist/gateway/protocol.js +15 -3
  64. package/dist/gateway/runExecutor.js +23 -12
  65. package/dist/gateway/service.d.ts +2 -0
  66. package/dist/gateway/service.js +29 -4
  67. package/dist/gateway/threadStore.d.ts +2 -0
  68. package/dist/gateway/threadStore.js +13 -0
  69. package/dist/gateway/types.d.ts +5 -4
  70. package/dist/gateway/workspace.d.ts +5 -0
  71. package/dist/gateway/workspace.js +30 -0
  72. package/dist/gateway/workspaceFolders.d.ts +5 -3
  73. package/dist/gateway/workspaceFolders.js +20 -7
  74. package/dist/gateway/workspaceListing.d.ts +5 -0
  75. package/dist/gateway/workspaceListing.js +78 -0
  76. package/dist/powersync/syncPass.js +13 -1
  77. package/dist/powersync/tokenIndex.d.ts +2 -0
  78. package/dist/powersync/tokenIndex.js +1 -0
  79. package/dist/requests/execute.d.ts +11 -0
  80. package/dist/requests/execute.js +26 -0
  81. package/dist/skills/commandRefs.js +2 -10
  82. package/dist/utils/cursor.d.ts +9 -0
  83. package/dist/utils/cursor.js +61 -0
  84. package/dist/utils/defaultFormat.d.ts +8 -0
  85. package/dist/utils/defaultFormat.js +38 -0
  86. package/dist/utils/errorCodes.d.ts +4 -0
  87. package/dist/utils/errorCodes.js +1 -0
  88. package/dist/utils/output.d.ts +2 -0
  89. package/dist/utils/output.js +41 -3
  90. package/dist/utils/progress.d.ts +25 -0
  91. package/dist/utils/progress.js +59 -0
  92. package/dist/utils/requestContext.d.ts +8 -0
  93. package/dist/utils/select.d.ts +20 -0
  94. package/dist/utils/select.js +92 -0
  95. package/dist/utils/usageError.d.ts +37 -0
  96. package/dist/utils/usageError.js +127 -0
  97. package/package.json +1 -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, waitForMutationUpload, withCommandRuntime, } from '../runtime.js';
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 waitForMutationUpload(runtime);
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 waitForMutationUpload(runtime);
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 waitForMutationUpload(runtime);
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 waitForMutationUpload(runtime);
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', 'stale'],
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', 'stale'],
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', 'stale'],
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', 'stale'],
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
- printSuccess({ stale: stale ?? false, ...response });
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 visibleTasks = filteredTasks.slice(0, options.limit);
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';
@@ -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
  /**
@@ -66,8 +66,9 @@ export async function sendDaemonRequest(method, params, requestOptions = {}) {
66
66
  };
67
67
  const req = httpRequest(options, (res) => {
68
68
  const statusCode = res.statusCode ?? 0;
69
- const chunks = [];
69
+ let chunks = [];
70
70
  let totalBytes = 0;
71
+ const { onFrame } = requestOptions;
71
72
  res.on('data', (chunk) => {
72
73
  totalBytes += chunk.length;
73
74
  if (method !== 'command.execute' && totalBytes > MAX_DAEMON_RESPONSE_BYTES) {
@@ -75,6 +76,21 @@ export async function sendDaemonRequest(method, params, requestOptions = {}) {
75
76
  return;
76
77
  }
77
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];
78
94
  });
79
95
  res.on('end', () => {
80
96
  try {
@@ -97,8 +113,12 @@ export async function sendDaemonRequest(method, params, requestOptions = {}) {
97
113
  });
98
114
  });
99
115
  req.on('socket', socket => {
100
- if (!socket.connecting)
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) {
101
119
  connected = true;
120
+ return;
121
+ }
102
122
  socket.once('connect', () => { connected = true; });
103
123
  });
104
124
  req.on('timeout', () => {
@@ -30,5 +30,8 @@ export type ForwardDependencies = typeof forwardDependencies;
30
30
  * the daemon could have received the request. A failure after that point
31
31
  * throws DAEMON_UNAVAILABLE, never falls back, so a write is not applied twice.
32
32
  */
33
- export declare function tryForwardToDaemon(argv: string[], overrides?: Partial<ForwardDependencies>): Promise<CommandExecuteResult | null>;
33
+ export declare function tryForwardToDaemon(argv: string[], overrides?: Partial<ForwardDependencies> & {
34
+ /** Receives `--progress` events live while the daemon runs the command. */
35
+ onProgress?: (text: string) => void;
36
+ }): Promise<CommandExecuteResult | null>;
34
37
  export {};
@@ -1,6 +1,7 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { loadConfig } from '../config/store.js';
3
3
  import { resolveConfig } from '../config/resolve.js';
4
+ import { GLOBAL_VALUE_OPTIONS } from '../commands/schema/globalOptions.js';
4
5
  import { DinoxError } from '../utils/errors.js';
5
6
  import { printHumanReadableWarning } from '../utils/output.js';
6
7
  import { parseSyncTimeoutMs } from '../utils/syncTimeout.js';
@@ -29,7 +30,7 @@ export function commandExecutionTarget(input) {
29
30
  words.push(...argv.slice(i + 1));
30
31
  break;
31
32
  }
32
- if (arg === '--format' || arg === '--sync-timeout' || arg === '--request-id') {
33
+ if (GLOBAL_VALUE_OPTIONS.includes(arg)) {
33
34
  i++;
34
35
  continue;
35
36
  }
@@ -66,7 +67,8 @@ const forwardDependencies = {
66
67
  * throws DAEMON_UNAVAILABLE, never falls back, so a write is not applied twice.
67
68
  */
68
69
  export async function tryForwardToDaemon(argv, overrides = {}) {
69
- const deps = { ...forwardDependencies, ...overrides };
70
+ const { onProgress, ...dependencyOverrides } = overrides;
71
+ const deps = { ...forwardDependencies, ...dependencyOverrides };
70
72
  if (commandExecutionTarget(argv) !== 'daemon' || /^(0|false|off|no)$/i.test(deps.env.DINOX_DAEMON ?? ''))
71
73
  return null;
72
74
  const fallback = (error) => {
@@ -106,8 +108,15 @@ export async function tryForwardToDaemon(argv, overrides = {}) {
106
108
  }
107
109
  const execute = async () => {
108
110
  const result = await deps.sendDaemonRequest('command.execute', {
109
- argv, cwd: deps.cwd(), clientVersion: deps.version(), requestId,
110
- }, { timeoutMs });
111
+ argv, cwd: deps.cwd(), clientVersion: deps.version(), requestId, ...(onProgress ? { stream: true } : {}),
112
+ }, {
113
+ timeoutMs,
114
+ ...(onProgress ? { onFrame: (frame) => {
115
+ const text = frame?.progress;
116
+ if (typeof text === 'string')
117
+ onProgress(text);
118
+ } } : {}),
119
+ });
111
120
  if (!result || typeof result.stdout !== 'string' || typeof result.stderr !== 'string' || !Number.isInteger(result.exitCode)) {
112
121
  throw new Error('Invalid command.execute response');
113
122
  }
@@ -37,7 +37,7 @@ export declare class DaemonServer {
37
37
  onStopRequested?: (reason: string, details?: Record<string, unknown>) => void | Promise<void>;
38
38
  });
39
39
  initialize(): Promise<void>;
40
- handleRequest(req: DaemonRequest): Promise<DaemonResponse>;
40
+ handleRequest(req: DaemonRequest, onProgress?: (text: string) => void): Promise<DaemonResponse>;
41
41
  private isCacheStale;
42
42
  start(port: number): Promise<void>;
43
43
  stop(): Promise<void>;
@@ -74,6 +74,9 @@ function validateLocalRequest(req) {
74
74
  }
75
75
  return { ok: true };
76
76
  }
77
+ function isStreamedCommand(request) {
78
+ return request?.method === 'command.execute' && request.params?.stream === true;
79
+ }
77
80
  export class DaemonServer {
78
81
  options;
79
82
  config = null;
@@ -144,7 +147,7 @@ export class DaemonServer {
144
147
  // PowerSync keeps the local cache in sync continuously while connected,
145
148
  // so no background polling loop is needed here.
146
149
  }
147
- async handleRequest(req) {
150
+ async handleRequest(req, onProgress) {
148
151
  try {
149
152
  if (!this.db) {
150
153
  throw new Error('Database not initialized');
@@ -171,6 +174,7 @@ export class DaemonServer {
171
174
  format: null,
172
175
  noticeProvider: null,
173
176
  cwd: input.cwd,
177
+ ...(onProgress ? { onProgress } : {}),
174
178
  daemon: {
175
179
  db: this.db,
176
180
  config: this.config,
@@ -316,12 +320,24 @@ export class DaemonServer {
316
320
  }
317
321
  try {
318
322
  const daemonReq = await readJsonBody(req);
323
+ if (isStreamedCommand(daemonReq)) {
324
+ res.writeHead(200, { 'Content-Type': 'application/x-ndjson' });
325
+ const daemonRes = await this.handleRequest(daemonReq, (progress) => {
326
+ res.write(`${JSON.stringify({ progress })}\n`);
327
+ });
328
+ res.end(JSON.stringify(daemonRes));
329
+ return;
330
+ }
319
331
  const daemonRes = await this.handleRequest(daemonReq);
320
332
  res.writeHead(200, { 'Content-Type': 'application/json' });
321
333
  res.end(JSON.stringify(daemonRes));
322
334
  }
323
335
  catch (error) {
324
336
  const message = error instanceof Error ? error.message : String(error);
337
+ if (res.headersSent) {
338
+ res.end(JSON.stringify({ ok: false, error: message }));
339
+ return;
340
+ }
325
341
  const statusCode = message.includes('too large') ? 413 : 400;
326
342
  res.writeHead(statusCode, { 'Content-Type': 'application/json' });
327
343
  res.end(JSON.stringify({ ok: false, error: message }));
@@ -57,6 +57,12 @@ export type CommandExecuteParams = {
57
57
  cwd: string;
58
58
  clientVersion: string;
59
59
  requestId?: string;
60
+ /** Answer as NDJSON: `{"progress": text}` frames while the command runs, then the response. */
61
+ stream?: boolean;
62
+ };
63
+ /** A frame the daemon streams ahead of a `stream: true` response. */
64
+ export type CommandExecuteFrame = {
65
+ progress: string;
60
66
  };
61
67
  export type CommandExecuteResult = {
62
68
  stdout: string;
package/dist/dinox.js CHANGED
@@ -1,17 +1,24 @@
1
1
  #!/usr/bin/env node
2
2
  import { commandExecutionTarget, tryForwardToDaemon } from './daemon/forward.js';
3
+ import { applyDefaultFormat } from './utils/defaultFormat.js';
3
4
  import { getPackageVersion } from './utils/version.js';
4
5
  import { requestIdFromArgv, runWithRequestId } from './requests/store.js';
5
6
  import { isDinoxError } from './utils/errors.js';
6
7
  import { exitProcessAfterFlushing } from './utils/processExit.js';
7
- import { localCommandExecution, requestContext } from './utils/requestContext.js';
8
- const argv = process.argv.slice(2);
8
+ import { localCommandExecution } from './utils/requestContext.js';
9
+ /** `--progress` before `--`: the daemon streams events back while it runs the command. */
10
+ function wantsProgress(args) {
11
+ const end = args.indexOf('--');
12
+ return (end === -1 ? args : args.slice(0, end)).includes('--progress');
13
+ }
14
+ let argv = process.argv.slice(2);
9
15
  let fatalError;
10
16
  try {
17
+ argv = applyDefaultFormat(argv);
11
18
  const versionOnly = argv.length === 1 && ['--version', '-V'].includes(argv[0]);
12
19
  const forwarded = versionOnly
13
20
  ? { stdout: `${getPackageVersion()}\n`, stderr: '', exitCode: 0 }
14
- : await tryForwardToDaemon(argv);
21
+ : await tryForwardToDaemon(argv, wantsProgress(argv) ? { onProgress: (text) => process.stderr.write(text) } : {});
15
22
  if (forwarded) {
16
23
  process.stdout.write(forwarded.stdout);
17
24
  process.stderr.write(forwarded.stderr);
@@ -21,13 +28,8 @@ try {
21
28
  const { runCli } = await import('./runCli.js');
22
29
  const key = requestIdFromArgv(argv);
23
30
  if (key && commandExecutionTarget(argv) === 'daemon') {
24
- const result = await runWithRequestId(key, argv, async () => {
25
- const context = {
26
- stdout: [], stderr: [], format: null, noticeProvider: null, cwd: process.cwd(),
27
- };
28
- const exitCode = await requestContext.run(context, () => localCommandExecution.run(true, () => runCli(argv, error => { fatalError = error; })));
29
- return { stdout: context.stdout.join(''), stderr: context.stderr.join(''), exitCode };
30
- });
31
+ const { runCapturedInProcess } = await import('./requests/execute.js');
32
+ const result = await runWithRequestId(key, argv, () => runCapturedInProcess(argv, error => { fatalError = error; }));
31
33
  process.stdout.write(result.stdout);
32
34
  process.stderr.write(result.stderr);
33
35
  process.exitCode = result.exitCode;
@@ -37,11 +37,15 @@ export declare class AcpGatewayAdapter implements GatewayAdapter {
37
37
  * MCP servers offered to sessions as they are created or loaded, e.g. the
38
38
  * Dinox tools; `sessionToken` lets their `send_file` reach this session.
39
39
  */
40
- mcpServers?: (sessionToken: string) => McpServer[]);
40
+ mcpServers?: (sessionToken: string, cwd: string) => McpServer[]);
41
41
  info(): GatewayAdapterInfo;
42
42
  runTurn(input: GatewayTurnInput): Promise<GatewayTurnResult>;
43
43
  sendFile(token: string, file: GatewayFileSource): Promise<GatewaySentFile> | null;
44
+ private catalogExpiresAt;
45
+ private catalogRevision;
46
+ private probing;
44
47
  describeConfig(): Promise<GatewayConfigOption[]>;
48
+ private probeConfig;
45
49
  dispose(): Promise<void>;
46
50
  /** Codex leaves generated images on disk; send the ones this turn made when it sent nothing itself. */
47
51
  private sendGeneratedImages;