@dinoxx/dinox-cli 1.0.22 → 1.1.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 (144) hide show
  1. package/README.md +29 -9
  2. package/dist/auth/userInfo.d.ts +2 -0
  3. package/dist/auth/userInfo.js +55 -14
  4. package/dist/cli.js +36 -2
  5. package/dist/commands/auth/index.d.ts +35 -0
  6. package/dist/commands/auth/index.js +382 -83
  7. package/dist/commands/boxes/index.js +2 -6
  8. package/dist/commands/boxes/model.d.ts +3 -0
  9. package/dist/commands/boxes/model.js +16 -1
  10. package/dist/commands/boxes/operations.js +18 -8
  11. package/dist/commands/boxes/repo.js +13 -11
  12. package/dist/commands/boxes/store.js +1 -1
  13. package/dist/commands/config/index.js +7 -5
  14. package/dist/commands/descriptor/register.js +9 -0
  15. package/dist/commands/doctor.d.ts +123 -0
  16. package/dist/commands/doctor.js +539 -0
  17. package/dist/commands/graph/index.d.ts +1 -1
  18. package/dist/commands/graph/index.js +35 -21
  19. package/dist/commands/graph/repo.js +92 -17
  20. package/dist/commands/meta/index.js +4 -4
  21. package/dist/commands/meta/repo.js +4 -3
  22. package/dist/commands/notes/actions.d.ts +1 -1
  23. package/dist/commands/notes/actions.js +121 -78
  24. package/dist/commands/notes/bulk.d.ts +1 -0
  25. package/dist/commands/notes/bulk.js +23 -17
  26. package/dist/commands/notes/contentEngine.js +69 -11
  27. package/dist/commands/notes/contentPatch.d.ts +4 -1
  28. package/dist/commands/notes/contentPatch.js +105 -41
  29. package/dist/commands/notes/contentPatchCommand.js +28 -11
  30. package/dist/commands/notes/descriptors.js +50 -20
  31. package/dist/commands/notes/dryRun.d.ts +1 -0
  32. package/dist/commands/notes/dryRun.js +4 -7
  33. package/dist/commands/notes/export.d.ts +6 -0
  34. package/dist/commands/notes/export.js +26 -6
  35. package/dist/commands/notes/metadataMutation.d.ts +1 -0
  36. package/dist/commands/notes/metadataMutation.js +14 -3
  37. package/dist/commands/notes/repo.d.ts +46 -4
  38. package/dist/commands/notes/repo.js +113 -28
  39. package/dist/commands/notes/runtime.d.ts +2 -1
  40. package/dist/commands/notes/runtime.js +17 -1
  41. package/dist/commands/notes/searchSql.js +13 -6
  42. package/dist/commands/notes/writeReceipt.d.ts +20 -0
  43. package/dist/commands/notes/writeReceipt.js +82 -0
  44. package/dist/commands/prompt/index.js +3 -3
  45. package/dist/commands/prompt/repo.d.ts +4 -1
  46. package/dist/commands/prompt/repo.js +17 -7
  47. package/dist/commands/runtime.d.ts +14 -2
  48. package/dist/commands/runtime.js +74 -15
  49. package/dist/commands/schema/renderSkills.js +5 -2
  50. package/dist/commands/schema/spec.js +4 -0
  51. package/dist/commands/storage/index.js +8 -2
  52. package/dist/commands/storage/repo.d.ts +4 -0
  53. package/dist/commands/storage/repo.js +356 -92
  54. package/dist/commands/sync.js +16 -12
  55. package/dist/commands/tags/content.js +9 -0
  56. package/dist/commands/tags/index.js +2 -6
  57. package/dist/commands/tags/operations.js +15 -10
  58. package/dist/commands/tags/repo.d.ts +2 -0
  59. package/dist/commands/tags/repo.js +18 -5
  60. package/dist/commands/tags/store.js +1 -1
  61. package/dist/commands/todo/contentModel.d.ts +4 -3
  62. package/dist/commands/todo/contentModel.js +78 -28
  63. package/dist/commands/todo/dryRun.d.ts +1 -0
  64. package/dist/commands/todo/dryRun.js +17 -13
  65. package/dist/commands/todo/index.d.ts +6 -0
  66. package/dist/commands/todo/index.js +89 -123
  67. package/dist/commands/todo/repo.d.ts +36 -1
  68. package/dist/commands/todo/repo.js +146 -37
  69. package/dist/commands/todo/taskIdentity.d.ts +2 -0
  70. package/dist/commands/todo/taskIdentity.js +64 -0
  71. package/dist/commands/todo/taskModel.js +27 -17
  72. package/dist/commands/update.d.ts +13 -1
  73. package/dist/commands/update.js +135 -23
  74. package/dist/commands/writeDryRun.d.ts +3 -2
  75. package/dist/commands/writeDryRun.js +4 -3
  76. package/dist/commands/writeDurability.d.ts +5 -0
  77. package/dist/commands/writeDurability.js +22 -0
  78. package/dist/config/paths.js +6 -1
  79. package/dist/config/resolve.d.ts +2 -0
  80. package/dist/config/resolve.js +23 -9
  81. package/dist/config/store.d.ts +3 -0
  82. package/dist/config/store.js +61 -14
  83. package/dist/config/types.d.ts +4 -0
  84. package/dist/daemon/client.d.ts +26 -1
  85. package/dist/daemon/client.js +236 -12
  86. package/dist/daemon/ensure.js +74 -12
  87. package/dist/daemon/index.d.ts +42 -0
  88. package/dist/daemon/index.js +202 -52
  89. package/dist/daemon/lifecycle.d.ts +42 -0
  90. package/dist/daemon/lifecycle.js +115 -0
  91. package/dist/daemon/pid.d.ts +19 -6
  92. package/dist/daemon/pid.js +147 -13
  93. package/dist/daemon/route.d.ts +14 -0
  94. package/dist/daemon/route.js +40 -0
  95. package/dist/daemon/server.d.ts +11 -0
  96. package/dist/daemon/server.js +201 -50
  97. package/dist/daemon/types.d.ts +29 -1
  98. package/dist/daemon/types.js +1 -0
  99. package/dist/dinox.js +25 -1
  100. package/dist/powersync/connector.js +31 -16
  101. package/dist/powersync/exclusiveRuntime.d.ts +19 -0
  102. package/dist/powersync/exclusiveRuntime.js +42 -0
  103. package/dist/powersync/runtime.d.ts +24 -4
  104. package/dist/powersync/runtime.js +234 -36
  105. package/dist/powersync/syncGate.d.ts +3 -1
  106. package/dist/powersync/syncGate.js +7 -1
  107. package/dist/powersync/tokenIndex.d.ts +11 -0
  108. package/dist/powersync/tokenIndex.js +159 -22
  109. package/dist/powersync/uploader.js +107 -52
  110. package/dist/utils/abort.js +0 -4
  111. package/dist/utils/asyncRelease.d.ts +2 -0
  112. package/dist/utils/asyncRelease.js +25 -0
  113. package/dist/utils/bidlinkCanonicalization.js +3 -0
  114. package/dist/utils/dryRun.d.ts +1 -1
  115. package/dist/utils/errors.d.ts +15 -1
  116. package/dist/utils/errors.js +223 -5
  117. package/dist/utils/output.d.ts +1 -0
  118. package/dist/utils/output.js +51 -15
  119. package/dist/utils/redact.js +1 -1
  120. package/dist/utils/sqliteLease.d.ts +8 -0
  121. package/dist/utils/sqliteLease.js +59 -0
  122. package/dist/utils/syncTimeout.d.ts +7 -1
  123. package/dist/utils/syncTimeout.js +8 -7
  124. package/dist/utils/terminalSanitization.js +6 -2
  125. package/dist/utils/tiptapMarkdown.js +49 -3
  126. package/dist/utils/tiptapMarkdownSyntax.d.ts +9 -0
  127. package/dist/utils/tiptapMarkdownSyntax.js +249 -0
  128. package/dist/utils/tokenize.js +24 -2
  129. package/dist/utils/updateNotice.js +60 -12
  130. package/package.json +12 -10
  131. package/dist/commands/mcp/auth.d.ts +0 -4
  132. package/dist/commands/mcp/auth.js +0 -75
  133. package/dist/commands/mcp/index.d.ts +0 -2
  134. package/dist/commands/mcp/index.js +0 -86
  135. package/dist/commands/mcp/noteContentTools.d.ts +0 -222
  136. package/dist/commands/mcp/noteContentTools.js +0 -390
  137. package/dist/commands/mcp/server.d.ts +0 -34
  138. package/dist/commands/mcp/server.js +0 -424
  139. package/dist/commands/mcp/tools.d.ts +0 -2
  140. package/dist/commands/mcp/tools.js +0 -32
  141. package/dist/commands/mcp/types.d.ts +0 -39
  142. package/dist/commands/mcp/types.js +0 -1
  143. package/dist/utils/noteContentEngine.d.ts +0 -333
  144. package/dist/utils/noteContentEngine.js +0 -740
@@ -0,0 +1,249 @@
1
+ import remarkGfm from 'remark-gfm';
2
+ import remarkParse from 'remark-parse';
3
+ import { unified } from 'unified';
4
+ const markdownParser = unified().use(remarkParse).use(remarkGfm);
5
+ export function escapeLiteralMarkdownText(value) {
6
+ return value.replace(/[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]/g, '\\$&');
7
+ }
8
+ function mergeRanges(ranges) {
9
+ const sorted = ranges
10
+ .filter((range) => range.end > range.start)
11
+ .sort((left, right) => left.start - right.start || left.end - right.end);
12
+ const merged = [];
13
+ for (const range of sorted) {
14
+ const previous = merged[merged.length - 1];
15
+ if (!previous || range.start > previous.end) {
16
+ merged.push({ ...range });
17
+ continue;
18
+ }
19
+ previous.end = Math.max(previous.end, range.end);
20
+ }
21
+ return merged;
22
+ }
23
+ export function isMarkdownOffsetInRanges(offset, ranges) {
24
+ return ranges.some((range) => offset >= range.start && offset < range.end);
25
+ }
26
+ export function isMarkdownOffsetInSortedRanges(offset, ranges) {
27
+ let low = 0;
28
+ let high = ranges.length - 1;
29
+ while (low <= high) {
30
+ const middle = low + Math.floor((high - low) / 2);
31
+ const range = ranges[middle];
32
+ if (offset < range.start) {
33
+ high = middle - 1;
34
+ continue;
35
+ }
36
+ if (offset >= range.end) {
37
+ low = middle + 1;
38
+ continue;
39
+ }
40
+ return true;
41
+ }
42
+ return false;
43
+ }
44
+ function findMarkdownRangeAtOffset(offset, ranges) {
45
+ let low = 0;
46
+ let high = ranges.length - 1;
47
+ while (low <= high) {
48
+ const middle = low + Math.floor((high - low) / 2);
49
+ const range = ranges[middle];
50
+ if (offset < range.start) {
51
+ high = middle - 1;
52
+ continue;
53
+ }
54
+ if (offset >= range.end) {
55
+ low = middle + 1;
56
+ continue;
57
+ }
58
+ return range;
59
+ }
60
+ return null;
61
+ }
62
+ export function findMarkdownCodeRanges(source) {
63
+ const ranges = [];
64
+ const stack = [markdownParser.parse(source)];
65
+ while (stack.length > 0) {
66
+ const node = stack.pop();
67
+ if (node.type === 'code' || node.type === 'inlineCode') {
68
+ const start = node.position?.start?.offset;
69
+ const end = node.position?.end?.offset;
70
+ if (typeof start === 'number' && typeof end === 'number' && end > start) {
71
+ ranges.push({ start, end });
72
+ }
73
+ continue;
74
+ }
75
+ if (Array.isArray(node.children)) {
76
+ stack.push(...node.children);
77
+ }
78
+ }
79
+ return mergeRanges(ranges);
80
+ }
81
+ function findLinkDestinationEnd(source, start) {
82
+ let depth = 1;
83
+ let quote = null;
84
+ for (let index = start; index < source.length; index += 1) {
85
+ const char = source[index];
86
+ if (char === '\\') {
87
+ index += 1;
88
+ continue;
89
+ }
90
+ if (quote) {
91
+ if (char === quote)
92
+ quote = null;
93
+ continue;
94
+ }
95
+ if (char === '"' || char === "'") {
96
+ quote = char;
97
+ continue;
98
+ }
99
+ if (char === '(')
100
+ depth += 1;
101
+ if (char === ')') {
102
+ depth -= 1;
103
+ if (depth === 0)
104
+ return index + 1;
105
+ }
106
+ if (char === '\n' && depth === 1)
107
+ return start;
108
+ }
109
+ return start;
110
+ }
111
+ function findHtmlTagEnd(source, start) {
112
+ let quote = null;
113
+ for (let index = start + 1; index < source.length; index += 1) {
114
+ const char = source[index];
115
+ if (quote) {
116
+ if (char === quote && source[index - 1] !== '\\')
117
+ quote = null;
118
+ continue;
119
+ }
120
+ if (char === '"' || char === "'") {
121
+ quote = char;
122
+ continue;
123
+ }
124
+ if (char === '>')
125
+ return index + 1;
126
+ }
127
+ return start;
128
+ }
129
+ const VOID_HTML_ELEMENTS = new Set([
130
+ 'area',
131
+ 'base',
132
+ 'br',
133
+ 'col',
134
+ 'embed',
135
+ 'hr',
136
+ 'img',
137
+ 'input',
138
+ 'link',
139
+ 'meta',
140
+ 'param',
141
+ 'source',
142
+ 'track',
143
+ 'wbr',
144
+ ]);
145
+ function parseHtmlTag(source, start, end) {
146
+ const raw = source.slice(start, end);
147
+ const match = raw.match(/^<\s*(\/?)\s*([A-Za-z][A-Za-z0-9:-]*)(?=[\s/>])/);
148
+ if (!match) {
149
+ return null;
150
+ }
151
+ const name = match[2].toLowerCase();
152
+ return {
153
+ name,
154
+ closing: match[1] === '/',
155
+ selfClosing: /\/\s*>$/.test(raw) || VOID_HTML_ELEMENTS.has(name),
156
+ };
157
+ }
158
+ function findHtmlElementEnd(source, start, openingTagEnd) {
159
+ const opening = parseHtmlTag(source, start, openingTagEnd);
160
+ if (!opening || opening.closing || opening.selfClosing) {
161
+ return openingTagEnd;
162
+ }
163
+ let depth = 1;
164
+ for (let cursor = openingTagEnd; cursor < source.length;) {
165
+ const nextTagStart = source.indexOf('<', cursor);
166
+ if (nextTagStart === -1) {
167
+ return openingTagEnd;
168
+ }
169
+ if (source.startsWith('<!--', nextTagStart)) {
170
+ const commentEnd = source.indexOf('-->', nextTagStart + 4);
171
+ cursor = commentEnd === -1 ? source.length : commentEnd + 3;
172
+ continue;
173
+ }
174
+ const tagEnd = findHtmlTagEnd(source, nextTagStart);
175
+ if (tagEnd <= nextTagStart) {
176
+ cursor = nextTagStart + 1;
177
+ continue;
178
+ }
179
+ const tag = parseHtmlTag(source, nextTagStart, tagEnd);
180
+ if (!tag || tag.name !== opening.name) {
181
+ cursor = tagEnd;
182
+ continue;
183
+ }
184
+ if (tag.closing) {
185
+ depth -= 1;
186
+ if (depth === 0) {
187
+ return tagEnd;
188
+ }
189
+ }
190
+ else if (!tag.selfClosing) {
191
+ depth += 1;
192
+ }
193
+ cursor = tagEnd;
194
+ }
195
+ return openingTagEnd;
196
+ }
197
+ function findBareUrlEnd(source, start) {
198
+ let index = start;
199
+ while (index < source.length && !/[\s<>"']/.test(source[index])) {
200
+ index += 1;
201
+ }
202
+ return index;
203
+ }
204
+ export function findMarkdownLiteralRanges(source) {
205
+ const codeRanges = findMarkdownCodeRanges(source);
206
+ const ranges = [...codeRanges];
207
+ for (let index = 0; index < source.length;) {
208
+ const containingCodeRange = findMarkdownRangeAtOffset(index, codeRanges);
209
+ if (containingCodeRange) {
210
+ index = containingCodeRange.end;
211
+ continue;
212
+ }
213
+ if (source.startsWith('<!--', index)) {
214
+ const closing = source.indexOf('-->', index + 4);
215
+ const end = closing === -1 ? source.length : closing + 3;
216
+ ranges.push({ start: index, end });
217
+ index = end;
218
+ continue;
219
+ }
220
+ if (source[index] === '<' && /[A-Za-z!/?]/.test(source[index + 1] ?? '')) {
221
+ const tagEnd = findHtmlTagEnd(source, index);
222
+ if (tagEnd > index) {
223
+ const elementEnd = findHtmlElementEnd(source, index, tagEnd);
224
+ ranges.push({ start: index, end: elementEnd });
225
+ index = elementEnd;
226
+ continue;
227
+ }
228
+ }
229
+ if (source[index] === ']' && source[index + 1] === '(') {
230
+ const end = findLinkDestinationEnd(source, index + 2);
231
+ if (end > index + 2) {
232
+ ranges.push({ start: index + 1, end });
233
+ index = end;
234
+ continue;
235
+ }
236
+ }
237
+ const isUrlStart = source.startsWith('https://', index) ||
238
+ source.startsWith('http://', index) ||
239
+ source.startsWith('www.', index);
240
+ if (isUrlStart) {
241
+ const end = findBareUrlEnd(source, index);
242
+ ranges.push({ start: index, end });
243
+ index = end;
244
+ continue;
245
+ }
246
+ index += 1;
247
+ }
248
+ return mergeRanges(ranges);
249
+ }
@@ -1,11 +1,13 @@
1
- import { Jieba } from '@node-rs/jieba';
2
- import { dict } from '@node-rs/jieba/dict.js';
1
+ import { createRequire } from 'node:module';
2
+ const requireModule = createRequire(import.meta.url);
3
3
  let jieba = null;
4
4
  function ensureJiebaReady() {
5
5
  if (jieba) {
6
6
  return;
7
7
  }
8
8
  try {
9
+ const { Jieba } = requireModule('@node-rs/jieba');
10
+ const { dict } = requireModule('@node-rs/jieba/dict.js');
9
11
  jieba = Jieba.withDict(dict);
10
12
  }
11
13
  catch (error) {
@@ -24,10 +26,30 @@ function normalizeToken(token) {
24
26
  }
25
27
  return normalized;
26
28
  }
29
+ function isAsciiText(text) {
30
+ return /^[\x00-\x7F]*$/.test(text);
31
+ }
32
+ function tokenizeAscii(text, limit) {
33
+ const tokens = [];
34
+ for (const match of text.matchAll(/[A-Za-z0-9]+(?:[._'-][A-Za-z0-9]+)*/g)) {
35
+ const normalized = normalizeToken(match[0]);
36
+ if (!normalized) {
37
+ continue;
38
+ }
39
+ tokens.push(normalized);
40
+ if (tokens.length >= limit) {
41
+ break;
42
+ }
43
+ }
44
+ return tokens;
45
+ }
27
46
  export function tokenizeWithJieba(text, limit = 500) {
28
47
  if (!text || limit <= 0) {
29
48
  return [];
30
49
  }
50
+ if (isAsciiText(text)) {
51
+ return tokenizeAscii(text, limit);
52
+ }
31
53
  ensureJiebaReady();
32
54
  const raw = jieba.cutForSearch(text, false);
33
55
  const tokens = [];
@@ -1,7 +1,10 @@
1
+ import fsp from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { getDataDir } from '../config/paths.js';
1
4
  const PACKAGE_NAME = '@dinoxx/dinox-cli';
2
5
  const LATEST_VERSION_URL = 'https://registry.npmjs.org/@dinoxx%2Fdinox-cli/latest';
3
- let cachedUpdateNotice = null;
4
- let lastCheckedAt = 0;
6
+ const UPDATE_CACHE_FILENAME = 'update-notice.json';
7
+ let cachedLookup = null;
5
8
  const UPDATE_CACHE_TTL_MS = 5 * 60 * 1000;
6
9
  function parseSemverParts(version) {
7
10
  const base = version.trim().split('-', 1)[0] ?? '';
@@ -44,21 +47,66 @@ async function fetchLatestPublishedVersion(fetchImpl = fetch) {
44
47
  clearTimeout(timeout);
45
48
  }
46
49
  }
47
- export async function getUpdateNotice(currentVersion, fetchImpl = fetch) {
48
- const now = Date.now();
49
- if (cachedUpdateNotice && now - lastCheckedAt < UPDATE_CACHE_TTL_MS) {
50
- return cachedUpdateNotice.current === currentVersion ? cachedUpdateNotice : null;
51
- }
52
- const latestVersion = await fetchLatestPublishedVersion(fetchImpl);
53
- lastCheckedAt = now;
50
+ function buildNotice(currentVersion, latestVersion) {
54
51
  if (!latestVersion || !isVersionNewer(latestVersion, currentVersion)) {
55
- cachedUpdateNotice = null;
56
52
  return null;
57
53
  }
58
- cachedUpdateNotice = {
54
+ return {
59
55
  current: currentVersion,
60
56
  latest: latestVersion,
61
57
  message: `Update available for ${PACKAGE_NAME}: ${currentVersion} -> ${latestVersion}`,
62
58
  };
63
- return cachedUpdateNotice;
59
+ }
60
+ function getUpdateCachePath() {
61
+ return path.join(getDataDir(), UPDATE_CACHE_FILENAME);
62
+ }
63
+ async function readPersistentLookup(now) {
64
+ try {
65
+ const raw = await fsp.readFile(getUpdateCachePath(), 'utf8');
66
+ const parsed = JSON.parse(raw);
67
+ const checkedAt = typeof parsed.checkedAt === 'number' ? parsed.checkedAt : 0;
68
+ if (!Number.isFinite(checkedAt) || now - checkedAt >= UPDATE_CACHE_TTL_MS) {
69
+ return null;
70
+ }
71
+ return {
72
+ checkedAt,
73
+ latestVersion: typeof parsed.latestVersion === 'string' ? parsed.latestVersion : null,
74
+ };
75
+ }
76
+ catch {
77
+ return null;
78
+ }
79
+ }
80
+ async function writePersistentLookup(cache) {
81
+ const cachePath = getUpdateCachePath();
82
+ const dir = path.dirname(cachePath);
83
+ const tmpPath = path.join(dir, `${UPDATE_CACHE_FILENAME}.${process.pid}.${Date.now()}.tmp`);
84
+ try {
85
+ await fsp.mkdir(dir, { recursive: true, mode: 0o700 });
86
+ await fsp.writeFile(tmpPath, `${JSON.stringify(cache)}\n`, { mode: 0o600 });
87
+ await fsp.rename(tmpPath, cachePath);
88
+ }
89
+ catch {
90
+ await fsp.rm(tmpPath, { force: true }).catch(() => undefined);
91
+ }
92
+ }
93
+ export async function getUpdateNotice(currentVersion, fetchImpl = fetch) {
94
+ const now = Date.now();
95
+ const usePersistentCache = fetchImpl === fetch;
96
+ if (cachedLookup && now - cachedLookup.checkedAt < UPDATE_CACHE_TTL_MS) {
97
+ return buildNotice(currentVersion, cachedLookup.latestVersion);
98
+ }
99
+ if (usePersistentCache) {
100
+ const persisted = await readPersistentLookup(now);
101
+ if (persisted) {
102
+ cachedLookup = persisted;
103
+ return buildNotice(currentVersion, persisted.latestVersion);
104
+ }
105
+ }
106
+ const latestVersion = await fetchLatestPublishedVersion(fetchImpl);
107
+ cachedLookup = { checkedAt: now, latestVersion };
108
+ if (usePersistentCache) {
109
+ await writePersistentLookup(cachedLookup);
110
+ }
111
+ return buildNotice(currentVersion, latestVersion);
64
112
  }
package/package.json CHANGED
@@ -1,19 +1,20 @@
1
1
  {
2
2
  "name": "@dinoxx/dinox-cli",
3
- "version": "1.0.22",
3
+ "version": "1.1.0",
4
4
  "description": "Dinox CLI",
5
5
  "main": "dist/dinox.js",
6
6
  "scripts": {
7
- "build": "tsc -p tsconfig.json && node scripts/add-shebang.mjs",
7
+ "build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.json && node scripts/add-shebang.mjs",
8
8
  "dev": "tsx src/dinox.ts",
9
9
  "prepublishOnly": "pnpm run build",
10
- "publish:npm": "sh ./publish-npm.sh",
10
+ "publish:npm": "sh ./publish-npm.sh --no-bump",
11
11
  "publish:npm:dry-run": "sh ./publish-npm.sh --dry-run",
12
12
  "skills:check": "tsx scripts/check-skills.ts",
13
13
  "skills:sync:standalone": "tsx scripts/sync-standalone-skills-repo.ts",
14
14
  "skills:sync": "tsx scripts/sync-skills-reference.ts",
15
15
  "start": "node dist/dinox.js",
16
- "test": "node --test --import tsx test/*.test.ts"
16
+ "test": "node scripts/run-tests.mjs",
17
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json"
17
18
  },
18
19
  "keywords": [],
19
20
  "author": "",
@@ -30,13 +31,13 @@
30
31
  "access": "public"
31
32
  },
32
33
  "engines": {
33
- "node": ">=20"
34
+ "node": "^20.18.1 || >=22.0.0 <26.0.0"
34
35
  },
35
36
  "dependencies": {
36
37
  "@aws-sdk/client-s3": "^3.1021.0",
37
38
  "@node-rs/jieba": "^2.0.1",
38
- "@powersync/common": "^1.47.0",
39
- "@powersync/node": "^0.17.1",
39
+ "@powersync/common": "^1.57.0",
40
+ "@powersync/node": "^0.19.2",
40
41
  "@tiptap/core": "^3.20.0",
41
42
  "@tiptap/extension-blockquote": "^3.20.0",
42
43
  "@tiptap/extension-bold": "^3.20.0",
@@ -71,12 +72,13 @@
71
72
  "strip-markdown": "^6.0.0",
72
73
  "unified": "^11.0.5",
73
74
  "uuidv7": "^1.0.2",
74
- "yaml": "^2.8.2"
75
+ "yaml": "^2.9.0"
75
76
  },
76
77
  "devDependencies": {
77
- "@types/node": "^24.0.0",
78
+ "@types/better-sqlite3": "^7.6.13",
79
+ "@types/node": "^20.19.0",
78
80
  "tsx": "^4.20.3",
79
81
  "typescript": "^5.9.3"
80
82
  },
81
- "packageManager": "pnpm@11.5.0"
83
+ "packageManager": "pnpm@10.34.5"
82
84
  }
@@ -1,4 +0,0 @@
1
- import type { IncomingHttpHeaders } from 'node:http';
2
- export declare function isLoopbackHost(host: string): boolean;
3
- export declare function resolveMCPAuthToken(tokenOption: unknown, envToken?: string | undefined): string | undefined;
4
- export declare function isAuthorizedMCPRequest(headers: IncomingHttpHeaders, expectedToken?: string): boolean;
@@ -1,75 +0,0 @@
1
- import { timingSafeEqual } from 'node:crypto';
2
- import { DinoxError } from '../../utils/errors.js';
3
- const AUTH_HEADER_RE = /^Bearer\s+(.+)$/i;
4
- export function isLoopbackHost(host) {
5
- const normalized = host.trim().toLowerCase();
6
- return normalized === '127.0.0.1' || normalized === 'localhost' || normalized === '::1';
7
- }
8
- export function resolveMCPAuthToken(tokenOption, envToken = process.env.DINOX_MCP_TOKEN) {
9
- if (tokenOption !== undefined && tokenOption !== null) {
10
- if (typeof tokenOption !== 'string') {
11
- throw new DinoxError('--token must be a non-empty string');
12
- }
13
- const parsed = tokenOption.trim();
14
- if (!parsed) {
15
- throw new DinoxError('--token must be a non-empty string');
16
- }
17
- return parsed;
18
- }
19
- const fromEnv = envToken?.trim() ?? '';
20
- return fromEnv || undefined;
21
- }
22
- function readHeaderTokenValue(value) {
23
- if (typeof value === 'string') {
24
- const parsed = value.trim();
25
- return parsed || undefined;
26
- }
27
- if (!Array.isArray(value)) {
28
- return undefined;
29
- }
30
- for (const item of value) {
31
- const parsed = item.trim();
32
- if (parsed) {
33
- return parsed;
34
- }
35
- }
36
- return undefined;
37
- }
38
- function readBearerToken(headers) {
39
- const authorization = readHeaderTokenValue(headers.authorization);
40
- if (!authorization) {
41
- return undefined;
42
- }
43
- const match = authorization.match(AUTH_HEADER_RE);
44
- if (!match) {
45
- return undefined;
46
- }
47
- const token = match[1]?.trim() ?? '';
48
- return token || undefined;
49
- }
50
- function readMCPToken(headers) {
51
- const bearer = readBearerToken(headers);
52
- if (bearer) {
53
- return bearer;
54
- }
55
- const raw = readHeaderTokenValue(headers['x-dinox-token']);
56
- return raw?.trim() || undefined;
57
- }
58
- function secureTokenEquals(left, right) {
59
- const leftBuffer = Buffer.from(left);
60
- const rightBuffer = Buffer.from(right);
61
- if (leftBuffer.length !== rightBuffer.length) {
62
- return false;
63
- }
64
- return timingSafeEqual(leftBuffer, rightBuffer);
65
- }
66
- export function isAuthorizedMCPRequest(headers, expectedToken) {
67
- if (!expectedToken) {
68
- return true;
69
- }
70
- const provided = readMCPToken(headers);
71
- if (!provided) {
72
- return false;
73
- }
74
- return secureTokenEquals(provided, expectedToken);
75
- }
@@ -1,2 +0,0 @@
1
- import { Command } from 'commander';
2
- export declare function registerMCPCommands(program: Command): void;
@@ -1,86 +0,0 @@
1
- import { startMCPServer } from './server.js';
2
- import { mcpTools } from './tools.js';
3
- import { isLoopbackHost, resolveMCPAuthToken } from './auth.js';
4
- import { DEFAULT_MCP_PORT } from './types.js';
5
- import { DinoxError } from '../../utils/errors.js';
6
- import { printHumanReadableLine } from '../../utils/output.js';
7
- function parsePort(value) {
8
- if (value === undefined || value === null || value === '') {
9
- return DEFAULT_MCP_PORT;
10
- }
11
- if (typeof value !== 'string') {
12
- throw new DinoxError('--port must be a valid port number');
13
- }
14
- const trimmed = value.trim();
15
- if (!/^\d+$/.test(trimmed)) {
16
- throw new DinoxError('--port must be a valid port number');
17
- }
18
- const parsed = Number(trimmed);
19
- if (!Number.isFinite(parsed) || parsed < 1 || parsed > 65535) {
20
- throw new DinoxError('--port must be between 1 and 65535');
21
- }
22
- return Math.trunc(parsed);
23
- }
24
- function parseHost(value) {
25
- if (value === undefined || value === null || value === '') {
26
- return '127.0.0.1';
27
- }
28
- if (typeof value !== 'string') {
29
- throw new DinoxError('--host must be a valid hostname or IP address');
30
- }
31
- const trimmed = value.trim();
32
- if (!trimmed) {
33
- throw new DinoxError('--host must be a valid hostname or IP address');
34
- }
35
- return trimmed;
36
- }
37
- export function registerMCPCommands(program) {
38
- const mcp = program.command('mcp').description('MCP (Model Context Protocol) server');
39
- mcp
40
- .command('serve')
41
- .description('Start MCP server for AI agent integration')
42
- .option('--host <string>', 'bind address (default: 127.0.0.1)')
43
- .option('--port <number>', `server port (default: ${DEFAULT_MCP_PORT})`)
44
- .option('--token <string>', 'auth token (or DINOX_MCP_TOKEN env var)')
45
- .action(async (options) => {
46
- const port = parsePort(options.port);
47
- const host = parseHost(options.host);
48
- const token = resolveMCPAuthToken(options.token);
49
- const loopback = isLoopbackHost(host);
50
- if (!loopback && !token) {
51
- throw new DinoxError('--token (or DINOX_MCP_TOKEN) is required when --host is non-loopback');
52
- }
53
- printHumanReadableLine('Starting Dinox MCP server...');
54
- printHumanReadableLine(`Host: ${host}`);
55
- printHumanReadableLine(`Port: ${port}`);
56
- printHumanReadableLine(`Auth: ${token ? 'enabled' : 'disabled (loopback only)'}`);
57
- printHumanReadableLine('Protocol: Model Context Protocol (MCP)');
58
- if (!loopback) {
59
- printHumanReadableLine('warning: binding to a non-loopback host may expose your local data; use firewall/auth as needed');
60
- }
61
- printHumanReadableLine('');
62
- printHumanReadableLine('Available tools:');
63
- for (const tool of mcpTools) {
64
- printHumanReadableLine(` - ${tool.name}`);
65
- }
66
- printHumanReadableLine('');
67
- const { server, mcpServer } = await startMCPServer(port, host, token);
68
- let shuttingDown = false;
69
- const shutdown = async () => {
70
- if (shuttingDown)
71
- return;
72
- shuttingDown = true;
73
- await new Promise((resolve) => server.close(() => resolve()));
74
- await mcpServer.close();
75
- };
76
- process.on('SIGINT', () => {
77
- void shutdown().finally(() => process.exit(0));
78
- });
79
- process.on('SIGTERM', () => {
80
- void shutdown().finally(() => process.exit(0));
81
- });
82
- });
83
- mcp.action((_options, command) => {
84
- command.outputHelp();
85
- });
86
- }