@dinoxx/dinox-cli 1.0.18 → 1.0.19

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 (111) hide show
  1. package/README.md +72 -34
  2. package/dist/cli.js +15 -1
  3. package/dist/commands/auth/index.d.ts +2 -0
  4. package/dist/commands/auth/index.js +209 -163
  5. package/dist/commands/boxes/index.d.ts +2 -0
  6. package/dist/commands/boxes/index.js +119 -75
  7. package/dist/commands/boxes/repo.d.ts +8 -0
  8. package/dist/commands/boxes/repo.js +41 -0
  9. package/dist/commands/config/index.d.ts +2 -0
  10. package/dist/commands/config/index.js +85 -36
  11. package/dist/commands/descriptor/register.d.ts +8 -0
  12. package/dist/commands/descriptor/register.js +113 -0
  13. package/dist/commands/descriptor/types.d.ts +37 -0
  14. package/dist/commands/descriptor/types.js +17 -0
  15. package/dist/commands/graph/index.d.ts +2 -0
  16. package/dist/commands/graph/index.js +143 -73
  17. package/dist/commands/info/index.d.ts +2 -0
  18. package/dist/commands/info/index.js +41 -14
  19. package/dist/commands/mcp/index.js +11 -10
  20. package/dist/commands/mcp/server.js +6 -3
  21. package/dist/commands/mcp/tools.js +30 -104
  22. package/dist/commands/mcp/types.d.ts +8 -1
  23. package/dist/commands/meta/index.d.ts +2 -0
  24. package/dist/commands/meta/index.js +64 -33
  25. package/dist/commands/meta/repo.d.ts +6 -0
  26. package/dist/commands/meta/repo.js +6 -0
  27. package/dist/commands/notes/actions.d.ts +10 -0
  28. package/dist/commands/notes/actions.js +403 -0
  29. package/dist/commands/notes/descriptors.d.ts +4 -0
  30. package/dist/commands/notes/descriptors.js +250 -0
  31. package/dist/commands/notes/dryRun.d.ts +31 -0
  32. package/dist/commands/notes/dryRun.js +130 -0
  33. package/dist/commands/notes/index.d.ts +2 -51
  34. package/dist/commands/notes/index.js +2 -581
  35. package/dist/commands/notes/repo.d.ts +9 -0
  36. package/dist/commands/notes/repo.js +56 -10
  37. package/dist/commands/notes/runtime.d.ts +28 -0
  38. package/dist/commands/notes/runtime.js +185 -0
  39. package/dist/commands/notes/searchOutput.d.ts +10 -2
  40. package/dist/commands/notes/searchOutput.js +14 -2
  41. package/dist/commands/notes/searchResponse.d.ts +51 -0
  42. package/dist/commands/notes/searchResponse.js +56 -0
  43. package/dist/commands/notes/searchSql.js +2 -0
  44. package/dist/commands/prompt/index.d.ts +2 -0
  45. package/dist/commands/prompt/index.js +122 -72
  46. package/dist/commands/prompt/repo.d.ts +7 -0
  47. package/dist/commands/prompt/repo.js +46 -0
  48. package/dist/commands/schema/commandMetadata.d.ts +2 -0
  49. package/dist/commands/schema/commandMetadata.js +18 -0
  50. package/dist/commands/schema/globalOptions.d.ts +3 -0
  51. package/dist/commands/schema/globalOptions.js +3 -0
  52. package/dist/commands/schema/index.d.ts +4 -0
  53. package/dist/commands/schema/index.js +109 -0
  54. package/dist/commands/schema/renderSkills.d.ts +6 -0
  55. package/dist/commands/schema/renderSkills.js +216 -0
  56. package/dist/commands/schema/spec.d.ts +6 -0
  57. package/dist/commands/schema/spec.js +40 -0
  58. package/dist/commands/schema/types.d.ts +47 -0
  59. package/dist/commands/schema/types.js +1 -0
  60. package/dist/commands/storage/index.d.ts +4 -0
  61. package/dist/commands/storage/index.js +292 -0
  62. package/dist/commands/storage/repo.d.ts +162 -0
  63. package/dist/commands/storage/repo.js +751 -0
  64. package/dist/commands/sync.d.ts +2 -0
  65. package/dist/commands/sync.js +41 -27
  66. package/dist/commands/tags/index.d.ts +2 -0
  67. package/dist/commands/tags/index.js +121 -79
  68. package/dist/commands/tags/repo.d.ts +7 -0
  69. package/dist/commands/tags/repo.js +52 -0
  70. package/dist/commands/todo/dryRun.d.ts +23 -0
  71. package/dist/commands/todo/dryRun.js +133 -0
  72. package/dist/commands/todo/index.d.ts +2 -0
  73. package/dist/commands/todo/index.js +285 -135
  74. package/dist/commands/todo/repo.d.ts +11 -0
  75. package/dist/commands/todo/repo.js +37 -9
  76. package/dist/commands/update.d.ts +3 -0
  77. package/dist/commands/update.js +35 -13
  78. package/dist/commands/writeDryRun.d.ts +62 -0
  79. package/dist/commands/writeDryRun.js +131 -0
  80. package/dist/constants/links.d.ts +1 -1
  81. package/dist/constants/links.js +1 -1
  82. package/dist/daemon/ensure.js +3 -2
  83. package/dist/daemon/index.js +17 -15
  84. package/dist/dinox.js +9 -13
  85. package/dist/powersync/schema/index.d.ts +27 -0
  86. package/dist/powersync/schema/index.js +2 -0
  87. package/dist/powersync/schema/note.d.ts +1 -0
  88. package/dist/powersync/schema/note.js +1 -0
  89. package/dist/powersync/schema/projects.d.ts +3 -0
  90. package/dist/powersync/schema/projects.js +3 -0
  91. package/dist/powersync/schema/storage.d.ts +24 -0
  92. package/dist/powersync/schema/storage.js +24 -0
  93. package/dist/skills/check.d.ts +6 -0
  94. package/dist/skills/check.js +254 -0
  95. package/dist/utils/bidlinkCanonicalization.d.ts +2 -0
  96. package/dist/utils/bidlinkCanonicalization.js +161 -0
  97. package/dist/utils/dryRun.d.ts +13 -0
  98. package/dist/utils/dryRun.js +69 -0
  99. package/dist/utils/output.d.ts +19 -0
  100. package/dist/utils/output.js +155 -1
  101. package/dist/utils/terminalSanitization.d.ts +2 -0
  102. package/dist/utils/terminalSanitization.js +33 -0
  103. package/dist/utils/tiptapMarkdown.d.ts +4 -1
  104. package/dist/utils/tiptapMarkdown.js +285 -113
  105. package/dist/utils/tiptapMarkdownSchema.d.ts +37 -0
  106. package/dist/utils/tiptapMarkdownSchema.js +577 -0
  107. package/dist/utils/tiptapMarkdownSchemaUtils.d.ts +74 -0
  108. package/dist/utils/tiptapMarkdownSchemaUtils.js +394 -0
  109. package/dist/utils/updateNotice.d.ts +7 -0
  110. package/dist/utils/updateNotice.js +64 -0
  111. package/package.json +25 -3
@@ -5,10 +5,20 @@ import { listBoxes } from '../boxes/repo.js';
5
5
  import { listTags } from '../tags/repo.js';
6
6
  import { buildNoteSearchSqlFilter } from './searchSql.js';
7
7
  const MAX_SEARCH_SUMMARY_CHARS = 200;
8
+ function normalizeStoredStarred(value) {
9
+ if (typeof value === 'number') {
10
+ return value === 1;
11
+ }
12
+ if (typeof value === 'string') {
13
+ return value.trim() === '1';
14
+ }
15
+ return false;
16
+ }
8
17
  export async function searchNotes(db, query, options) {
9
18
  const normalizedQuery = query.trim();
10
19
  const tagFilter = buildTagFilter(options.tagsExpression, 'n');
11
20
  const createdAtFilter = buildCreatedAtFilter(options.createdAtFrom, options.createdAtTo, 'n');
21
+ const starredFilter = buildStarredFilter(options.starred, 'n');
12
22
  const zettelBoxFilter = buildZettelBoxFilter(options.zettelBoxIds, options.matchEmptyZettelBoxes, 'n');
13
23
  const sqlFilter = await buildNoteSearchSqlFilter({
14
24
  expression: options.sqlExpression,
@@ -20,7 +30,7 @@ export async function searchNotes(db, query, options) {
20
30
  const ftsMatch = buildFtsMatchQuery(normalizedQuery);
21
31
  if (ftsMatch) {
22
32
  try {
23
- rows = await searchNotesWithFts(db, ftsMatch, tagFilter, createdAtFilter, zettelBoxFilter, sqlFilter);
33
+ rows = await searchNotesWithFts(db, ftsMatch, tagFilter, createdAtFilter, starredFilter, zettelBoxFilter, sqlFilter);
24
34
  return enrichSearchRows(db, rows);
25
35
  }
26
36
  catch (error) {
@@ -61,6 +71,10 @@ export async function searchNotes(db, query, options) {
61
71
  whereClauses.push(createdAtFilter.sql);
62
72
  params.push(...createdAtFilter.params);
63
73
  }
74
+ if (starredFilter) {
75
+ whereClauses.push(starredFilter.sql);
76
+ params.push(...starredFilter.params);
77
+ }
64
78
  if (zettelBoxFilter) {
65
79
  whereClauses.push(zettelBoxFilter.sql);
66
80
  params.push(...zettelBoxFilter.params);
@@ -77,14 +91,15 @@ export async function searchNotes(db, query, options) {
77
91
  n.content_text,
78
92
  n.tags,
79
93
  n.zettel_boxes,
80
- n.created_at
94
+ n.created_at,
95
+ n.is_starred
81
96
  FROM c_note n
82
97
  WHERE ${whereClauses.join('\n AND ')}
83
98
  ORDER BY COALESCE(n.updated_at, n.created_at) DESC
84
99
  `, params);
85
100
  return enrichSearchRows(db, rows);
86
101
  }
87
- async function searchNotesWithFts(db, matchQuery, tagFilter, createdAtFilter, zettelBoxFilter, sqlFilter) {
102
+ async function searchNotesWithFts(db, matchQuery, tagFilter, createdAtFilter, starredFilter, zettelBoxFilter, sqlFilter) {
88
103
  const whereClauses = ['(n.is_del IS NULL OR n.is_del = 0)', 'note_local_fts MATCH ?'];
89
104
  const params = [matchQuery];
90
105
  if (tagFilter) {
@@ -95,6 +110,10 @@ async function searchNotesWithFts(db, matchQuery, tagFilter, createdAtFilter, ze
95
110
  whereClauses.push(createdAtFilter.sql);
96
111
  params.push(...createdAtFilter.params);
97
112
  }
113
+ if (starredFilter) {
114
+ whereClauses.push(starredFilter.sql);
115
+ params.push(...starredFilter.params);
116
+ }
98
117
  if (zettelBoxFilter) {
99
118
  whereClauses.push(zettelBoxFilter.sql);
100
119
  params.push(...zettelBoxFilter.params);
@@ -111,7 +130,8 @@ async function searchNotesWithFts(db, matchQuery, tagFilter, createdAtFilter, ze
111
130
  n.content_text,
112
131
  n.tags,
113
132
  n.zettel_boxes,
114
- n.created_at
133
+ n.created_at,
134
+ n.is_starred
115
135
  FROM note_local_fts f
116
136
  JOIN c_note n ON n.id = f.note_id
117
137
  WHERE ${whereClauses.join('\n AND ')}
@@ -132,6 +152,7 @@ async function enrichSearchRows(db, rows) {
132
152
  summary,
133
153
  tags: parseStoredTags(row.tags),
134
154
  created_at: row.created_at,
155
+ is_starred: normalizeStoredStarred(row.is_starred),
135
156
  zettel_boxes: zettelBoxes,
136
157
  };
137
158
  });
@@ -150,7 +171,7 @@ function truncateSearchSummary(value) {
150
171
  }
151
172
  return chars.slice(0, MAX_SEARCH_SUMMARY_CHARS).join('');
152
173
  }
153
- function parseZettelBoxIds(raw) {
174
+ export function parseZettelBoxIds(raw) {
154
175
  const value = raw?.trim();
155
176
  if (!value) {
156
177
  return [];
@@ -248,6 +269,15 @@ function buildCreatedAtFilter(createdAtFrom, createdAtTo, noteAlias) {
248
269
  params,
249
270
  };
250
271
  }
272
+ function buildStarredFilter(starred, noteAlias) {
273
+ if (typeof starred !== 'boolean') {
274
+ return null;
275
+ }
276
+ return {
277
+ sql: `(${noteAlias}.is_starred = ?)`,
278
+ params: [starred ? '1' : '0'],
279
+ };
280
+ }
251
281
  function buildZettelBoxFilter(zettelBoxIds, matchEmptyZettelBoxes, noteAlias) {
252
282
  const normalizedIds = Array.from(new Set((zettelBoxIds ?? [])
253
283
  .map((id) => id.trim())
@@ -490,6 +520,7 @@ export async function getNote(db, id) {
490
520
  created_at,
491
521
  updated_at,
492
522
  is_del,
523
+ is_starred,
493
524
  version
494
525
  FROM c_note
495
526
  WHERE id = ?
@@ -539,6 +570,7 @@ export async function getNoteContext(db, id) {
539
570
  n.created_at,
540
571
  n.updated_at,
541
572
  n.is_del,
573
+ n.is_starred,
542
574
  n.zettel_boxes AS zettel_boxes_raw
543
575
  FROM c_note n
544
576
  WHERE n.id = ?
@@ -560,6 +592,7 @@ export async function getNoteContext(db, id) {
560
592
  created_at: row.created_at,
561
593
  updated_at: row.updated_at,
562
594
  is_del: row.is_del,
595
+ is_starred: normalizeStoredStarred(row.is_starred),
563
596
  zettel_boxes: boxIds
564
597
  .map((boxId) => boxNameMap.get(boxId))
565
598
  .filter((name) => typeof name === 'string' && name.length > 0),
@@ -607,6 +640,7 @@ function mapNoteDetailRow(note, boxNameMap) {
607
640
  source: note.source,
608
641
  tags: note.tags,
609
642
  is_del: note.is_del,
643
+ is_starred: normalizeStoredStarred(note.is_starred),
610
644
  content_md: note.content_md,
611
645
  zettel_boxes: zettelBoxes,
612
646
  };
@@ -632,6 +666,7 @@ export async function getNoteDetails(db, ids) {
632
666
  source,
633
667
  tags,
634
668
  is_del,
669
+ is_starred,
635
670
  content_md,
636
671
  zettel_boxes AS zettel_boxes_raw
637
672
  FROM c_note
@@ -735,6 +770,7 @@ export async function createNote(db, input) {
735
770
  ['extra_data', '{}'],
736
771
  ['format_type', ''],
737
772
  ['comments', '[]'],
773
+ ['is_starred', 0],
738
774
  ['is_top', 0],
739
775
  ['pinned_at', '1997-01-01T00:00:00.000Z'],
740
776
  ['chat_context', '{}'],
@@ -813,7 +849,7 @@ async function validateTagsForWrite(db, requestedTags, retryCommand) {
813
849
  }
814
850
  return resolved;
815
851
  }
816
- function parseStoredTags(raw) {
852
+ export function parseStoredTags(raw) {
817
853
  if (!raw) {
818
854
  return [];
819
855
  }
@@ -867,11 +903,12 @@ export async function updateNote(db, input) {
867
903
  }
868
904
  const shouldUpdateTags = Array.isArray(input.tags);
869
905
  const shouldUpdateBoxes = Array.isArray(input.zettelBoxIds);
870
- if (!shouldUpdateTags && !shouldUpdateBoxes) {
871
- throw new DinoxError('At least one field is required: tags or boxes');
906
+ const shouldUpdateStarred = typeof input.starred === 'boolean';
907
+ if (!shouldUpdateTags && !shouldUpdateBoxes && !shouldUpdateStarred) {
908
+ throw new DinoxError('At least one field is required: tags, boxes, or starred');
872
909
  }
873
910
  const existing = await db.getOptional(`
874
- SELECT id, tags, zettel_boxes, is_del
911
+ SELECT id, tags, zettel_boxes, is_del, is_starred
875
912
  FROM c_note
876
913
  WHERE id = ?
877
914
  `, [id]);
@@ -883,16 +920,20 @@ export async function updateNote(db, input) {
883
920
  }
884
921
  const currentTags = parseStoredTags(existing.tags);
885
922
  const currentZettelBoxIds = parseZettelBoxIds(existing.zettel_boxes);
923
+ const currentStarred = normalizeStoredStarred(existing.is_starred);
886
924
  const nextTags = shouldUpdateTags ? normalizeDistinctStrings(input.tags) : currentTags;
887
925
  const nextZettelBoxIds = shouldUpdateBoxes ? normalizeDistinctStrings(input.zettelBoxIds) : currentZettelBoxIds;
926
+ const nextStarred = shouldUpdateStarred ? input.starred : currentStarred;
888
927
  const tagsChanged = shouldUpdateTags && !sameTagSet(currentTags, nextTags);
889
928
  const boxesChanged = shouldUpdateBoxes && !sameStringSet(currentZettelBoxIds, nextZettelBoxIds);
890
- if (!tagsChanged && !boxesChanged) {
929
+ const starredChanged = shouldUpdateStarred && currentStarred !== nextStarred;
930
+ if (!tagsChanged && !boxesChanged && !starredChanged) {
891
931
  return {
892
932
  id,
893
933
  updated: false,
894
934
  tags: currentTags,
895
935
  zettelBoxIds: currentZettelBoxIds,
936
+ is_starred: currentStarred,
896
937
  };
897
938
  }
898
939
  const setParts = [];
@@ -905,6 +946,10 @@ export async function updateNote(db, input) {
905
946
  setParts.push('zettel_boxes = ?');
906
947
  params.push(JSON.stringify(nextZettelBoxIds));
907
948
  }
949
+ if (starredChanged) {
950
+ setParts.push('is_starred = ?');
951
+ params.push(nextStarred ? 1 : 0);
952
+ }
908
953
  setParts.push('updated_at = ?');
909
954
  params.push(nowIsoUtc());
910
955
  setParts.push('version = COALESCE(version, 0) + 1');
@@ -919,6 +964,7 @@ export async function updateNote(db, input) {
919
964
  updated: true,
920
965
  tags: nextTags,
921
966
  zettelBoxIds: nextZettelBoxIds,
967
+ is_starred: nextStarred,
922
968
  };
923
969
  }
924
970
  export async function assertNotesUpdatable(db, noteIds) {
@@ -0,0 +1,28 @@
1
+ import type { Command } from 'commander';
2
+ import type { AbstractPowerSyncDatabase } from '@powersync/common';
3
+ import { loadConfig } from '../../config/store.js';
4
+ import { resolveConfig } from '../../config/resolve.js';
5
+ export type NoteCommandRuntime = {
6
+ db: AbstractPowerSyncDatabase;
7
+ config: ReturnType<typeof resolveConfig>;
8
+ rawConfig: Awaited<ReturnType<typeof loadConfig>>;
9
+ offline: boolean;
10
+ timeoutMs: number;
11
+ stale: boolean;
12
+ };
13
+ export declare function parseNoteType(value: unknown): 'note' | 'crawl';
14
+ export declare function parseSearchLimit(value: unknown): number;
15
+ export declare function parseSearchOffset(value: unknown): number;
16
+ export declare function parsePreviewLineCount(value: unknown): number;
17
+ export declare function parseStarredOption(value: unknown, optionName: string): boolean;
18
+ export declare function parseStringInput(value: string): Promise<string>;
19
+ export declare function parseListInput(raw: string, optionName: string): string[];
20
+ export declare function parseStringListInput(value: string, optionName: string): Promise<string[]>;
21
+ export type ParsedSearchListInput = {
22
+ values: string[];
23
+ isExplicitEmptyArray: boolean;
24
+ };
25
+ export declare function parseSearchListInput(value: string, optionName: string): Promise<ParsedSearchListInput>;
26
+ export declare function parseTargetNoteIds(idArg: string | undefined, idsOption: string | undefined, commandName: 'detail' | 'update'): Promise<string[]>;
27
+ export declare function syncBeforeNoteCommand(db: AbstractPowerSyncDatabase, offline: boolean, timeoutMs: number): Promise<void>;
28
+ export declare function withNoteDatabase(command: Command, run: (runtime: NoteCommandRuntime) => Promise<void>): Promise<void>;
@@ -0,0 +1,185 @@
1
+ import { loadConfig } from '../../config/store.js';
2
+ import { resolveConfig } from '../../config/resolve.js';
3
+ import { connectPowerSync, waitForIdleDataFlow } from '../../powersync/runtime.js';
4
+ import { syncNoteTokenIndex } from '../../powersync/tokenIndex.js';
5
+ import { readStringOrFile } from '../../utils/argValue.js';
6
+ import { DinoxError } from '../../utils/errors.js';
7
+ import { parseSyncTimeoutMs } from '../../utils/syncTimeout.js';
8
+ const DEFAULT_NOTE_SEARCH_LIMIT = 50;
9
+ const MAX_NOTE_SEARCH_LIMIT = 500;
10
+ export function parseNoteType(value) {
11
+ const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
12
+ if (normalized === 'note' || normalized === 'crawl') {
13
+ return normalized;
14
+ }
15
+ throw new DinoxError('--type must be one of: note, crawl');
16
+ }
17
+ export function parseSearchLimit(value) {
18
+ if (value === undefined || value === null || value === '') {
19
+ return DEFAULT_NOTE_SEARCH_LIMIT;
20
+ }
21
+ if (typeof value !== 'string') {
22
+ throw new DinoxError('--limit must be a positive integer');
23
+ }
24
+ const trimmed = value.trim();
25
+ if (!/^\d+$/.test(trimmed)) {
26
+ throw new DinoxError('--limit must be a positive integer');
27
+ }
28
+ const parsed = Number(trimmed);
29
+ if (!Number.isFinite(parsed) || parsed <= 0) {
30
+ throw new DinoxError('--limit must be a positive integer');
31
+ }
32
+ if (parsed > MAX_NOTE_SEARCH_LIMIT) {
33
+ throw new DinoxError(`--limit cannot be greater than ${MAX_NOTE_SEARCH_LIMIT}`);
34
+ }
35
+ return Math.trunc(parsed);
36
+ }
37
+ export function parseSearchOffset(value) {
38
+ if (value === undefined || value === null || value === '') {
39
+ return 0;
40
+ }
41
+ if (typeof value !== 'string') {
42
+ throw new DinoxError('--offset must be a non-negative integer');
43
+ }
44
+ const trimmed = value.trim();
45
+ if (!/^\d+$/.test(trimmed)) {
46
+ throw new DinoxError('--offset must be a non-negative integer');
47
+ }
48
+ const parsed = Number(trimmed);
49
+ if (!Number.isFinite(parsed) || parsed < 0) {
50
+ throw new DinoxError('--offset must be a non-negative integer');
51
+ }
52
+ return Math.trunc(parsed);
53
+ }
54
+ export function parsePreviewLineCount(value) {
55
+ if (value === undefined || value === null || value === '') {
56
+ return 10;
57
+ }
58
+ if (typeof value !== 'string') {
59
+ throw new DinoxError('--lines must be a positive integer');
60
+ }
61
+ const trimmed = value.trim();
62
+ if (!/^\d+$/.test(trimmed)) {
63
+ throw new DinoxError('--lines must be a positive integer');
64
+ }
65
+ const parsed = Number(trimmed);
66
+ if (!Number.isFinite(parsed) || parsed <= 0) {
67
+ throw new DinoxError('--lines must be a positive integer');
68
+ }
69
+ if (parsed > 500) {
70
+ throw new DinoxError('--lines cannot be greater than 500');
71
+ }
72
+ return Math.trunc(parsed);
73
+ }
74
+ export function parseStarredOption(value, optionName) {
75
+ if (typeof value !== 'string') {
76
+ throw new DinoxError(`${optionName} must be one of: true, false, 1, 0, yes, no`);
77
+ }
78
+ const normalized = value.trim().toLowerCase();
79
+ if (normalized === 'true' || normalized === '1' || normalized === 'yes') {
80
+ return true;
81
+ }
82
+ if (normalized === 'false' || normalized === '0' || normalized === 'no') {
83
+ return false;
84
+ }
85
+ throw new DinoxError(`${optionName} must be one of: true, false, 1, 0, yes, no`);
86
+ }
87
+ export async function parseStringInput(value) {
88
+ return readStringOrFile(value);
89
+ }
90
+ export function parseListInput(raw, optionName) {
91
+ const value = raw.trim();
92
+ if (!value) {
93
+ return [];
94
+ }
95
+ if (value.startsWith('[')) {
96
+ try {
97
+ const parsed = JSON.parse(value);
98
+ if (!Array.isArray(parsed)) {
99
+ throw new DinoxError(`${optionName} must be a JSON array`);
100
+ }
101
+ return Array.from(new Set(parsed
102
+ .map((entry) => String(entry).trim())
103
+ .filter((entry) => entry.length > 0)));
104
+ }
105
+ catch (error) {
106
+ if (error instanceof DinoxError) {
107
+ throw error;
108
+ }
109
+ throw new DinoxError(`Invalid list for ${optionName}`, { cause: error });
110
+ }
111
+ }
112
+ return Array.from(new Set(value
113
+ .split(/[\n,,]/g)
114
+ .map((entry) => entry.trim())
115
+ .filter((entry) => entry.length > 0)));
116
+ }
117
+ export async function parseStringListInput(value, optionName) {
118
+ const raw = await readStringOrFile(value);
119
+ return parseListInput(raw, optionName);
120
+ }
121
+ export async function parseSearchListInput(value, optionName) {
122
+ const raw = await readStringOrFile(value);
123
+ return {
124
+ values: parseListInput(raw, optionName),
125
+ isExplicitEmptyArray: isExplicitEmptyArrayLiteral(raw),
126
+ };
127
+ }
128
+ function isExplicitEmptyArrayLiteral(value) {
129
+ const normalized = value.trim();
130
+ if (!normalized.startsWith('[')) {
131
+ return false;
132
+ }
133
+ try {
134
+ const parsed = JSON.parse(normalized);
135
+ return Array.isArray(parsed) && parsed.length === 0;
136
+ }
137
+ catch {
138
+ return false;
139
+ }
140
+ }
141
+ export async function parseTargetNoteIds(idArg, idsOption, commandName) {
142
+ const merged = [];
143
+ const idFromArg = typeof idArg === 'string' ? idArg.trim() : '';
144
+ if (idFromArg) {
145
+ merged.push(idFromArg);
146
+ }
147
+ if (typeof idsOption === 'string') {
148
+ merged.push(...(await parseStringListInput(idsOption, '--ids')));
149
+ }
150
+ const ids = Array.from(new Set(merged
151
+ .map((id) => id.trim())
152
+ .filter((id) => id.length > 0)));
153
+ if (ids.length === 0) {
154
+ throw new DinoxError(`Provide <id> or --ids for ${commandName}`);
155
+ }
156
+ return ids;
157
+ }
158
+ export async function syncBeforeNoteCommand(db, offline, timeoutMs) {
159
+ if (offline) {
160
+ return;
161
+ }
162
+ await waitForIdleDataFlow(db, Math.min(timeoutMs, 10_000));
163
+ await syncNoteTokenIndex(db);
164
+ }
165
+ export async function withNoteDatabase(command, run) {
166
+ const globals = command.optsWithGlobals?.() ?? {};
167
+ const rawConfig = await loadConfig();
168
+ const config = resolveConfig(rawConfig);
169
+ const timeoutMs = parseSyncTimeoutMs(globals.syncTimeout) ?? config.sync.timeoutMs;
170
+ const offline = Boolean(globals.offline);
171
+ const { db, stale } = await connectPowerSync({ config, offline, timeoutMs });
172
+ try {
173
+ await run({
174
+ db,
175
+ config,
176
+ rawConfig,
177
+ offline,
178
+ timeoutMs,
179
+ stale,
180
+ });
181
+ }
182
+ finally {
183
+ await db.close().catch(() => undefined);
184
+ }
185
+ }
@@ -1,6 +1,14 @@
1
1
  import type { SearchedNote } from './repo.js';
2
- export declare const NOTE_SEARCH_FIELDS: readonly ["id", "title", "summary", "tags", "created_at", "zettel_boxes"];
2
+ export declare const NOTE_SEARCH_FIELDS: readonly ["id", "title", "summary", "tags", "created_at", "boxes", "is_starred"];
3
3
  export type NoteSearchField = (typeof NOTE_SEARCH_FIELDS)[number];
4
- export type NoteSearchResultItem = Partial<SearchedNote>;
4
+ export type NoteSearchResultItem = {
5
+ id?: string;
6
+ title?: string;
7
+ summary?: string;
8
+ tags?: string[];
9
+ created_at?: string | null;
10
+ is_starred?: boolean;
11
+ boxes?: string[];
12
+ };
5
13
  export declare function parseNoteSearchFields(value: unknown, optionName: string): NoteSearchField[] | undefined;
6
14
  export declare function projectNoteSearchFields(notes: SearchedNote[], fields: NoteSearchField[] | undefined): NoteSearchResultItem[];
@@ -1,5 +1,5 @@
1
1
  import { DinoxError } from '../../utils/errors.js';
2
- export const NOTE_SEARCH_FIELDS = ['id', 'title', 'summary', 'tags', 'created_at', 'zettel_boxes'];
2
+ export const NOTE_SEARCH_FIELDS = ['id', 'title', 'summary', 'tags', 'created_at', 'boxes', 'is_starred'];
3
3
  const NOTE_SEARCH_FIELD_SET = new Set(NOTE_SEARCH_FIELDS);
4
4
  export function parseNoteSearchFields(value, optionName) {
5
5
  if (value === undefined || value === null || value === '') {
@@ -29,11 +29,23 @@ export function parseNoteSearchFields(value, optionName) {
29
29
  }
30
30
  export function projectNoteSearchFields(notes, fields) {
31
31
  if (!fields || fields.length === 0) {
32
- return notes.map((note) => ({ ...note }));
32
+ return notes.map((note) => ({
33
+ id: note.id,
34
+ title: note.title,
35
+ summary: note.summary,
36
+ tags: note.tags,
37
+ created_at: note.created_at,
38
+ is_starred: note.is_starred,
39
+ boxes: note.zettel_boxes,
40
+ }));
33
41
  }
34
42
  return notes.map((note) => {
35
43
  const item = {};
36
44
  for (const field of fields) {
45
+ if (field === 'boxes') {
46
+ item.boxes = note.zettel_boxes;
47
+ continue;
48
+ }
37
49
  item[field] = note[field];
38
50
  }
39
51
  return item;
@@ -0,0 +1,51 @@
1
+ import type { NoteSearchField, NoteSearchResultItem } from './searchOutput.js';
2
+ export type NoteSearchResponse = {
3
+ meta: {
4
+ query: string;
5
+ returned_count: number;
6
+ total_count: number;
7
+ stale: boolean;
8
+ pagination: {
9
+ limit: number | null;
10
+ offset: number;
11
+ has_more: boolean;
12
+ };
13
+ fields: NoteSearchField[] | null;
14
+ applied_filters: {
15
+ include_deleted: boolean;
16
+ tags_expression: string | null;
17
+ created_at_range: {
18
+ from: string | null;
19
+ to: string | null;
20
+ } | null;
21
+ days: number | null;
22
+ starred: boolean | null;
23
+ box_names: string[];
24
+ box_ids: string[];
25
+ match_empty_boxes: boolean;
26
+ sql_expression: string | null;
27
+ };
28
+ };
29
+ notes: NoteSearchResultItem[];
30
+ };
31
+ export type BuildNoteSearchResponseInput = {
32
+ query: string;
33
+ notes: NoteSearchResultItem[];
34
+ totalCount: number;
35
+ stale: boolean;
36
+ includeDeleted: boolean;
37
+ limit?: number;
38
+ offset?: number;
39
+ fields?: NoteSearchField[];
40
+ tagsExpression?: string;
41
+ createdAtFrom?: string;
42
+ createdAtTo?: string;
43
+ days?: number;
44
+ starred?: boolean;
45
+ boxNames: string[];
46
+ boxIds: string[];
47
+ matchEmptyBoxes: boolean;
48
+ sqlExpression?: string;
49
+ };
50
+ export declare function normalizeDaysForMeta(value: unknown): number | undefined;
51
+ export declare function buildNoteSearchResponse(input: BuildNoteSearchResponseInput): NoteSearchResponse;
@@ -0,0 +1,56 @@
1
+ export function normalizeDaysForMeta(value) {
2
+ if (typeof value !== 'string') {
3
+ return undefined;
4
+ }
5
+ const trimmed = value.trim();
6
+ if (!/^\d+$/.test(trimmed)) {
7
+ return undefined;
8
+ }
9
+ const parsed = Number(trimmed);
10
+ if (!Number.isFinite(parsed) || parsed <= 0) {
11
+ return undefined;
12
+ }
13
+ return Math.trunc(parsed);
14
+ }
15
+ export function buildNoteSearchResponse(input) {
16
+ const query = input.query.trim();
17
+ const tagsExpression = input.tagsExpression?.trim() ?? '';
18
+ const sqlExpression = input.sqlExpression?.trim() ?? '';
19
+ const hasCreatedAtRange = Boolean(input.createdAtFrom || input.createdAtTo);
20
+ const offset = input.offset ?? 0;
21
+ const limit = input.limit ?? null;
22
+ const returnedCount = input.notes.length;
23
+ const totalCount = input.totalCount;
24
+ const hasMore = offset + returnedCount < totalCount;
25
+ return {
26
+ meta: {
27
+ query,
28
+ returned_count: returnedCount,
29
+ total_count: totalCount,
30
+ stale: input.stale,
31
+ pagination: {
32
+ limit,
33
+ offset,
34
+ has_more: hasMore,
35
+ },
36
+ fields: input.fields && input.fields.length > 0 ? [...input.fields] : null,
37
+ applied_filters: {
38
+ include_deleted: input.includeDeleted,
39
+ tags_expression: tagsExpression || null,
40
+ created_at_range: hasCreatedAtRange
41
+ ? {
42
+ from: input.createdAtFrom ?? null,
43
+ to: input.createdAtTo ?? null,
44
+ }
45
+ : null,
46
+ days: input.days ?? null,
47
+ box_names: [...input.boxNames],
48
+ box_ids: [...input.boxIds],
49
+ match_empty_boxes: input.matchEmptyBoxes,
50
+ starred: typeof input.starred === 'boolean' ? input.starred : null,
51
+ sql_expression: sqlExpression || null,
52
+ },
53
+ },
54
+ notes: input.notes,
55
+ };
56
+ }
@@ -7,6 +7,7 @@ const SEARCH_FIELDS = new Set([
7
7
  'zettel_boxes',
8
8
  'created_at',
9
9
  'type',
10
+ 'is_starred',
10
11
  ]);
11
12
  const FORBIDDEN_SQL_KEYWORDS = [
12
13
  'insert',
@@ -31,6 +32,7 @@ const SCALAR_FIELD_COLUMNS = {
31
32
  summary: 'summary',
32
33
  created_at: 'created_at',
33
34
  type: 'type',
35
+ is_starred: 'is_starred',
34
36
  };
35
37
  export async function buildNoteSearchSqlFilter(options) {
36
38
  const expression = options.expression?.trim() ?? '';
@@ -1,2 +1,4 @@
1
1
  import { Command } from 'commander';
2
+ import type { CommandGroupDescriptor } from '../descriptor/types.js';
3
+ export declare const promptCommandGroup: CommandGroupDescriptor;
2
4
  export declare function registerPromptCommands(program: Command): void;