@gmickel/gno 0.7.0 → 0.8.2

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 (209) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +90 -50
  3. package/THIRD_PARTY_NOTICES.md +22 -0
  4. package/assets/screenshots/webui-ask-answer.png +0 -0
  5. package/assets/screenshots/webui-collections.png +0 -0
  6. package/assets/screenshots/webui-editor.png +0 -0
  7. package/assets/screenshots/webui-home.png +0 -0
  8. package/assets/skill/SKILL.md +12 -12
  9. package/assets/skill/cli-reference.md +59 -57
  10. package/assets/skill/examples.md +8 -7
  11. package/assets/skill/mcp-reference.md +8 -4
  12. package/package.json +32 -25
  13. package/src/app/constants.ts +43 -42
  14. package/src/cli/colors.ts +1 -1
  15. package/src/cli/commands/ask.ts +44 -43
  16. package/src/cli/commands/cleanup.ts +9 -8
  17. package/src/cli/commands/collection/add.ts +12 -12
  18. package/src/cli/commands/collection/index.ts +4 -4
  19. package/src/cli/commands/collection/list.ts +26 -25
  20. package/src/cli/commands/collection/remove.ts +10 -10
  21. package/src/cli/commands/collection/rename.ts +10 -10
  22. package/src/cli/commands/context/add.ts +1 -1
  23. package/src/cli/commands/context/check.ts +17 -17
  24. package/src/cli/commands/context/index.ts +4 -4
  25. package/src/cli/commands/context/list.ts +11 -11
  26. package/src/cli/commands/context/rm.ts +1 -1
  27. package/src/cli/commands/doctor.ts +86 -84
  28. package/src/cli/commands/embed.ts +30 -28
  29. package/src/cli/commands/get.ts +27 -26
  30. package/src/cli/commands/index-cmd.ts +9 -9
  31. package/src/cli/commands/index.ts +16 -16
  32. package/src/cli/commands/init.ts +13 -12
  33. package/src/cli/commands/ls.ts +20 -19
  34. package/src/cli/commands/mcp/config.ts +30 -28
  35. package/src/cli/commands/mcp/index.ts +4 -4
  36. package/src/cli/commands/mcp/install.ts +17 -17
  37. package/src/cli/commands/mcp/paths.ts +133 -133
  38. package/src/cli/commands/mcp/status.ts +21 -21
  39. package/src/cli/commands/mcp/uninstall.ts +13 -13
  40. package/src/cli/commands/mcp.ts +2 -2
  41. package/src/cli/commands/models/clear.ts +12 -11
  42. package/src/cli/commands/models/index.ts +5 -5
  43. package/src/cli/commands/models/list.ts +31 -30
  44. package/src/cli/commands/models/path.ts +1 -1
  45. package/src/cli/commands/models/pull.ts +19 -18
  46. package/src/cli/commands/models/use.ts +4 -4
  47. package/src/cli/commands/multi-get.ts +38 -36
  48. package/src/cli/commands/query.ts +21 -20
  49. package/src/cli/commands/ref-parser.ts +10 -10
  50. package/src/cli/commands/reset.ts +40 -39
  51. package/src/cli/commands/search.ts +14 -13
  52. package/src/cli/commands/serve.ts +4 -4
  53. package/src/cli/commands/shared.ts +11 -10
  54. package/src/cli/commands/skill/index.ts +5 -5
  55. package/src/cli/commands/skill/install.ts +18 -17
  56. package/src/cli/commands/skill/paths-cmd.ts +11 -10
  57. package/src/cli/commands/skill/paths.ts +23 -23
  58. package/src/cli/commands/skill/show.ts +13 -12
  59. package/src/cli/commands/skill/uninstall.ts +16 -15
  60. package/src/cli/commands/status.ts +25 -24
  61. package/src/cli/commands/update.ts +3 -3
  62. package/src/cli/commands/vsearch.ts +17 -16
  63. package/src/cli/context.ts +5 -5
  64. package/src/cli/errors.ts +3 -3
  65. package/src/cli/format/search-results.ts +37 -37
  66. package/src/cli/options.ts +43 -43
  67. package/src/cli/program.ts +455 -459
  68. package/src/cli/progress.ts +1 -1
  69. package/src/cli/run.ts +24 -23
  70. package/src/collection/add.ts +9 -8
  71. package/src/collection/index.ts +3 -3
  72. package/src/collection/remove.ts +7 -6
  73. package/src/collection/types.ts +6 -6
  74. package/src/config/defaults.ts +1 -1
  75. package/src/config/index.ts +5 -5
  76. package/src/config/loader.ts +19 -18
  77. package/src/config/paths.ts +9 -8
  78. package/src/config/saver.ts +14 -13
  79. package/src/config/types.ts +53 -52
  80. package/src/converters/adapters/markitdownTs/adapter.ts +21 -19
  81. package/src/converters/adapters/officeparser/adapter.ts +18 -16
  82. package/src/converters/canonicalize.ts +12 -12
  83. package/src/converters/errors.ts +26 -22
  84. package/src/converters/index.ts +8 -8
  85. package/src/converters/mime.ts +25 -25
  86. package/src/converters/native/markdown.ts +10 -9
  87. package/src/converters/native/plaintext.ts +8 -7
  88. package/src/converters/path.ts +2 -2
  89. package/src/converters/pipeline.ts +11 -10
  90. package/src/converters/registry.ts +8 -8
  91. package/src/converters/types.ts +14 -14
  92. package/src/converters/versions.ts +4 -4
  93. package/src/index.ts +4 -4
  94. package/src/ingestion/chunker.ts +10 -9
  95. package/src/ingestion/index.ts +6 -6
  96. package/src/ingestion/language.ts +62 -62
  97. package/src/ingestion/sync.ts +50 -49
  98. package/src/ingestion/types.ts +10 -10
  99. package/src/ingestion/walker.ts +14 -13
  100. package/src/llm/cache.ts +51 -49
  101. package/src/llm/errors.ts +40 -36
  102. package/src/llm/index.ts +9 -9
  103. package/src/llm/lockfile.ts +6 -6
  104. package/src/llm/nodeLlamaCpp/adapter.ts +13 -12
  105. package/src/llm/nodeLlamaCpp/embedding.ts +9 -8
  106. package/src/llm/nodeLlamaCpp/generation.ts +7 -6
  107. package/src/llm/nodeLlamaCpp/lifecycle.ts +11 -10
  108. package/src/llm/nodeLlamaCpp/rerank.ts +6 -5
  109. package/src/llm/policy.ts +5 -5
  110. package/src/llm/registry.ts +6 -5
  111. package/src/llm/types.ts +2 -2
  112. package/src/mcp/resources/index.ts +15 -13
  113. package/src/mcp/server.ts +25 -23
  114. package/src/mcp/tools/get.ts +25 -23
  115. package/src/mcp/tools/index.ts +32 -29
  116. package/src/mcp/tools/multi-get.ts +34 -32
  117. package/src/mcp/tools/query.ts +29 -27
  118. package/src/mcp/tools/search.ts +14 -12
  119. package/src/mcp/tools/status.ts +12 -11
  120. package/src/mcp/tools/vsearch.ts +26 -24
  121. package/src/pipeline/answer.ts +9 -9
  122. package/src/pipeline/chunk-lookup.ts +1 -1
  123. package/src/pipeline/contextual.ts +4 -4
  124. package/src/pipeline/expansion.ts +23 -21
  125. package/src/pipeline/explain.ts +21 -21
  126. package/src/pipeline/fusion.ts +9 -9
  127. package/src/pipeline/hybrid.ts +41 -42
  128. package/src/pipeline/index.ts +10 -10
  129. package/src/pipeline/query-language.ts +39 -39
  130. package/src/pipeline/rerank.ts +8 -7
  131. package/src/pipeline/search.ts +22 -22
  132. package/src/pipeline/types.ts +8 -8
  133. package/src/pipeline/vsearch.ts +21 -24
  134. package/src/serve/CLAUDE.md +21 -15
  135. package/src/serve/config-sync.ts +9 -8
  136. package/src/serve/context.ts +19 -18
  137. package/src/serve/index.ts +1 -1
  138. package/src/serve/jobs.ts +7 -7
  139. package/src/serve/public/app.tsx +79 -25
  140. package/src/serve/public/components/AddCollectionDialog.tsx +382 -0
  141. package/src/serve/public/components/CaptureButton.tsx +60 -0
  142. package/src/serve/public/components/CaptureModal.tsx +365 -0
  143. package/src/serve/public/components/IndexingProgress.tsx +333 -0
  144. package/src/serve/public/components/ShortcutHelpModal.tsx +106 -0
  145. package/src/serve/public/components/ai-elements/code-block.tsx +42 -32
  146. package/src/serve/public/components/ai-elements/conversation.tsx +16 -14
  147. package/src/serve/public/components/ai-elements/inline-citation.tsx +33 -32
  148. package/src/serve/public/components/ai-elements/loader.tsx +5 -4
  149. package/src/serve/public/components/ai-elements/message.tsx +39 -37
  150. package/src/serve/public/components/ai-elements/prompt-input.tsx +97 -95
  151. package/src/serve/public/components/ai-elements/sources.tsx +12 -10
  152. package/src/serve/public/components/ai-elements/suggestion.tsx +10 -9
  153. package/src/serve/public/components/editor/CodeMirrorEditor.tsx +142 -0
  154. package/src/serve/public/components/editor/MarkdownPreview.tsx +311 -0
  155. package/src/serve/public/components/editor/index.ts +6 -0
  156. package/src/serve/public/components/preset-selector.tsx +29 -28
  157. package/src/serve/public/components/ui/badge.tsx +13 -12
  158. package/src/serve/public/components/ui/button-group.tsx +13 -12
  159. package/src/serve/public/components/ui/button.tsx +23 -22
  160. package/src/serve/public/components/ui/card.tsx +16 -16
  161. package/src/serve/public/components/ui/carousel.tsx +36 -35
  162. package/src/serve/public/components/ui/collapsible.tsx +1 -1
  163. package/src/serve/public/components/ui/command.tsx +17 -15
  164. package/src/serve/public/components/ui/dialog.tsx +13 -12
  165. package/src/serve/public/components/ui/dropdown-menu.tsx +13 -12
  166. package/src/serve/public/components/ui/hover-card.tsx +6 -5
  167. package/src/serve/public/components/ui/input-group.tsx +45 -43
  168. package/src/serve/public/components/ui/input.tsx +6 -6
  169. package/src/serve/public/components/ui/progress.tsx +5 -4
  170. package/src/serve/public/components/ui/scroll-area.tsx +11 -10
  171. package/src/serve/public/components/ui/select.tsx +19 -18
  172. package/src/serve/public/components/ui/separator.tsx +6 -5
  173. package/src/serve/public/components/ui/table.tsx +18 -18
  174. package/src/serve/public/components/ui/textarea.tsx +4 -4
  175. package/src/serve/public/components/ui/tooltip.tsx +5 -4
  176. package/src/serve/public/globals.css +27 -4
  177. package/src/serve/public/hooks/use-api.ts +8 -8
  178. package/src/serve/public/hooks/useCaptureModal.tsx +83 -0
  179. package/src/serve/public/hooks/useKeyboardShortcuts.ts +85 -0
  180. package/src/serve/public/index.html +4 -4
  181. package/src/serve/public/lib/utils.ts +6 -0
  182. package/src/serve/public/pages/Ask.tsx +27 -26
  183. package/src/serve/public/pages/Browse.tsx +28 -27
  184. package/src/serve/public/pages/Collections.tsx +439 -0
  185. package/src/serve/public/pages/Dashboard.tsx +166 -40
  186. package/src/serve/public/pages/DocView.tsx +258 -73
  187. package/src/serve/public/pages/DocumentEditor.tsx +510 -0
  188. package/src/serve/public/pages/Search.tsx +80 -58
  189. package/src/serve/routes/api.ts +272 -155
  190. package/src/serve/security.ts +4 -4
  191. package/src/serve/server.ts +66 -48
  192. package/src/store/index.ts +5 -5
  193. package/src/store/migrations/001-initial.ts +24 -23
  194. package/src/store/migrations/002-documents-fts.ts +7 -6
  195. package/src/store/migrations/index.ts +4 -4
  196. package/src/store/migrations/runner.ts +17 -15
  197. package/src/store/sqlite/adapter.ts +123 -121
  198. package/src/store/sqlite/fts5-snowball.ts +24 -23
  199. package/src/store/sqlite/index.ts +1 -1
  200. package/src/store/sqlite/setup.ts +12 -12
  201. package/src/store/sqlite/types.ts +4 -4
  202. package/src/store/types.ts +19 -19
  203. package/src/store/vector/index.ts +3 -3
  204. package/src/store/vector/sqlite-vec.ts +23 -20
  205. package/src/store/vector/stats.ts +10 -8
  206. package/src/store/vector/types.ts +2 -2
  207. package/vendor/fts5-snowball/README.md +6 -6
  208. package/assets/screenshots/webui-ask-answer.jpg +0 -0
  209. package/assets/screenshots/webui-home.jpg +0 -0
@@ -19,7 +19,7 @@
19
19
  * Cross-origin browser requests must originate from localhost/127.0.0.1.
20
20
  */
21
21
  export function validateOrigin(req: Request, port: number): boolean {
22
- const origin = req.headers.get('Origin');
22
+ const origin = req.headers.get("Origin");
23
23
  // Same-origin requests (browser fetch, curl) have no Origin header
24
24
  if (!origin) {
25
25
  return true;
@@ -48,7 +48,7 @@ export function validateToken(req: Request): boolean {
48
48
  return false;
49
49
  }
50
50
 
51
- const token = req.headers.get('X-GNO-Token');
51
+ const token = req.headers.get("X-GNO-Token");
52
52
  return token === expectedToken;
53
53
  }
54
54
 
@@ -64,7 +64,7 @@ export function isRequestAllowed(req: Request, port: number): boolean {
64
64
  const method = req.method.toUpperCase();
65
65
 
66
66
  // Safe methods - no CSRF protection needed
67
- if (method === 'GET' || method === 'HEAD' || method === 'OPTIONS') {
67
+ if (method === "GET" || method === "HEAD" || method === "OPTIONS") {
68
68
  return true;
69
69
  }
70
70
 
@@ -78,7 +78,7 @@ export function isRequestAllowed(req: Request, port: number): boolean {
78
78
  */
79
79
  export function forbiddenResponse(): Response {
80
80
  return Response.json(
81
- { error: { code: 'CSRF_VIOLATION', message: 'Forbidden' } },
81
+ { error: { code: "CSRF_VIOLATION", message: "Forbidden" } },
82
82
  { status: 403 }
83
83
  );
84
84
  }
@@ -6,13 +6,14 @@
6
6
  * @module src/serve/server
7
7
  */
8
8
 
9
- import { getIndexDbPath } from '../app/constants';
10
- import { getConfigPaths, isInitialized, loadConfig } from '../config';
11
- import { SqliteAdapter } from '../store/sqlite/adapter';
12
- import { createServerContext, disposeServerContext } from './context';
9
+ import type { ContextHolder } from "./routes/api";
10
+
11
+ import { getIndexDbPath } from "../app/constants";
12
+ import { getConfigPaths, isInitialized, loadConfig } from "../config";
13
+ import { SqliteAdapter } from "../store/sqlite/adapter";
14
+ import { createServerContext, disposeServerContext } from "./context";
13
15
  // HTML import - Bun handles bundling TSX/CSS automatically via routes
14
- import homepage from './public/index.html';
15
- import type { ContextHolder } from './routes/api';
16
+ import homepage from "./public/index.html";
16
17
  import {
17
18
  handleAsk,
18
19
  handleCapabilities,
@@ -33,8 +34,9 @@ import {
33
34
  handleSetPreset,
34
35
  handleStatus,
35
36
  handleSync,
36
- } from './routes/api';
37
- import { forbiddenResponse, isRequestAllowed } from './security';
37
+ handleUpdateDoc,
38
+ } from "./routes/api";
39
+ import { forbiddenResponse, isRequestAllowed } from "./security";
38
40
 
39
41
  export interface ServeOptions {
40
42
  /** Port to listen on (default: 3000) */
@@ -78,7 +80,7 @@ function getCspHeader(isDev: boolean): string {
78
80
  base.push("connect-src 'self'");
79
81
  }
80
82
 
81
- return base.join('; ');
83
+ return base.join("; ");
82
84
  }
83
85
 
84
86
  /**
@@ -86,11 +88,11 @@ function getCspHeader(isDev: boolean): string {
86
88
  */
87
89
  function withSecurityHeaders(response: Response, isDev: boolean): Response {
88
90
  const headers = new Headers(response.headers);
89
- headers.set('Content-Security-Policy', getCspHeader(isDev));
90
- headers.set('X-Content-Type-Options', 'nosniff');
91
- headers.set('X-Frame-Options', 'DENY');
92
- headers.set('Referrer-Policy', 'no-referrer');
93
- headers.set('Cross-Origin-Resource-Policy', 'same-origin');
91
+ headers.set("Content-Security-Policy", getCspHeader(isDev));
92
+ headers.set("X-Content-Type-Options", "nosniff");
93
+ headers.set("X-Frame-Options", "DENY");
94
+ headers.set("Referrer-Policy", "no-referrer");
95
+ headers.set("Cross-Origin-Resource-Policy", "same-origin");
94
96
 
95
97
  return new Response(response.body, {
96
98
  status: response.status,
@@ -107,12 +109,12 @@ export async function startServer(
107
109
  options: ServeOptions = {}
108
110
  ): Promise<ServeResult> {
109
111
  const port = options.port ?? 3000;
110
- const isDev = process.env.NODE_ENV !== 'production';
112
+ const isDev = process.env.NODE_ENV !== "production";
111
113
 
112
114
  // Check initialization
113
115
  const initialized = await isInitialized(options.configPath);
114
116
  if (!initialized) {
115
- return { success: false, error: 'GNO not initialized. Run: gno init' };
117
+ return { success: false, error: "GNO not initialized. Run: gno init" };
116
118
  }
117
119
 
118
120
  // Load config
@@ -159,21 +161,21 @@ export async function startServer(
159
161
 
160
162
  // Graceful shutdown handler
161
163
  const shutdown = async () => {
162
- console.log('\nShutting down...');
164
+ console.log("\nShutting down...");
163
165
  await disposeServerContext(ctxHolder.current);
164
166
  await store.close();
165
167
  shutdownController.abort();
166
168
  };
167
169
 
168
- process.once('SIGINT', shutdown);
169
- process.once('SIGTERM', shutdown);
170
+ process.once("SIGINT", shutdown);
171
+ process.once("SIGTERM", shutdown);
170
172
 
171
173
  // Start server with try/catch for port-in-use etc.
172
174
  let server: ReturnType<typeof Bun.serve>;
173
175
  try {
174
176
  server = Bun.serve({
175
177
  port,
176
- hostname: '127.0.0.1', // Loopback only - no LAN exposure
178
+ hostname: "127.0.0.1", // Loopback only - no LAN exposure
177
179
 
178
180
  // Enable development mode for HMR and console logging
179
181
  development: isDev,
@@ -181,21 +183,23 @@ export async function startServer(
181
183
  // Static routes - Bun handles HTML bundling and /_bun/* assets automatically
182
184
  routes: {
183
185
  // SPA routes - all serve the same React app
184
- '/': homepage,
185
- '/search': homepage,
186
- '/browse': homepage,
187
- '/doc': homepage,
188
- '/ask': homepage,
186
+ "/": homepage,
187
+ "/search": homepage,
188
+ "/browse": homepage,
189
+ "/doc": homepage,
190
+ "/edit": homepage,
191
+ "/collections": homepage,
192
+ "/ask": homepage,
189
193
 
190
194
  // API routes with CSRF protection wrapper
191
- '/api/health': {
195
+ "/api/health": {
192
196
  GET: () => withSecurityHeaders(handleHealth(), isDev),
193
197
  },
194
- '/api/status': {
198
+ "/api/status": {
195
199
  GET: async () =>
196
200
  withSecurityHeaders(await handleStatus(store), isDev),
197
201
  },
198
- '/api/collections': {
202
+ "/api/collections": {
199
203
  GET: async () =>
200
204
  withSecurityHeaders(await handleCollections(store), isDev),
201
205
  POST: async (req: Request) => {
@@ -208,7 +212,7 @@ export async function startServer(
208
212
  );
209
213
  },
210
214
  },
211
- '/api/sync': {
215
+ "/api/sync": {
212
216
  POST: async (req: Request) => {
213
217
  if (!isRequestAllowed(req, port)) {
214
218
  return withSecurityHeaders(forbiddenResponse(), isDev);
@@ -219,7 +223,7 @@ export async function startServer(
219
223
  );
220
224
  },
221
225
  },
222
- '/api/docs': {
226
+ "/api/docs": {
223
227
  GET: async (req: Request) => {
224
228
  const url = new URL(req.url);
225
229
  return withSecurityHeaders(await handleDocs(store, url), isDev);
@@ -234,28 +238,42 @@ export async function startServer(
234
238
  );
235
239
  },
236
240
  },
237
- '/api/docs/:id/deactivate': {
241
+ "/api/docs/:id/deactivate": {
238
242
  POST: async (req: Request) => {
239
243
  if (!isRequestAllowed(req, port)) {
240
244
  return withSecurityHeaders(forbiddenResponse(), isDev);
241
245
  }
242
246
  const url = new URL(req.url);
243
247
  // Extract id from /api/docs/:id/deactivate
244
- const parts = url.pathname.split('/');
245
- const id = decodeURIComponent(parts[3] || '');
248
+ const parts = url.pathname.split("/");
249
+ const id = decodeURIComponent(parts[3] || "");
246
250
  return withSecurityHeaders(
247
251
  await handleDeactivateDoc(store, id),
248
252
  isDev
249
253
  );
250
254
  },
251
255
  },
252
- '/api/doc': {
256
+ "/api/docs/:id": {
257
+ PUT: async (req: Request) => {
258
+ if (!isRequestAllowed(req, port)) {
259
+ return withSecurityHeaders(forbiddenResponse(), isDev);
260
+ }
261
+ const url = new URL(req.url);
262
+ // Extract id from /api/docs/:id
263
+ const id = decodeURIComponent(url.pathname.split("/").pop() || "");
264
+ return withSecurityHeaders(
265
+ await handleUpdateDoc(ctxHolder, store, id, req),
266
+ isDev
267
+ );
268
+ },
269
+ },
270
+ "/api/doc": {
253
271
  GET: async (req: Request) => {
254
272
  const url = new URL(req.url);
255
273
  return withSecurityHeaders(await handleDoc(store, url), isDev);
256
274
  },
257
275
  },
258
- '/api/search': {
276
+ "/api/search": {
259
277
  POST: async (req: Request) => {
260
278
  if (!isRequestAllowed(req, port)) {
261
279
  return withSecurityHeaders(forbiddenResponse(), isDev);
@@ -263,7 +281,7 @@ export async function startServer(
263
281
  return withSecurityHeaders(await handleSearch(store, req), isDev);
264
282
  },
265
283
  },
266
- '/api/query': {
284
+ "/api/query": {
267
285
  POST: async (req: Request) => {
268
286
  if (!isRequestAllowed(req, port)) {
269
287
  return withSecurityHeaders(forbiddenResponse(), isDev);
@@ -274,7 +292,7 @@ export async function startServer(
274
292
  );
275
293
  },
276
294
  },
277
- '/api/ask': {
295
+ "/api/ask": {
278
296
  POST: async (req: Request) => {
279
297
  if (!isRequestAllowed(req, port)) {
280
298
  return withSecurityHeaders(forbiddenResponse(), isDev);
@@ -285,11 +303,11 @@ export async function startServer(
285
303
  );
286
304
  },
287
305
  },
288
- '/api/capabilities': {
306
+ "/api/capabilities": {
289
307
  GET: () =>
290
308
  withSecurityHeaders(handleCapabilities(ctxHolder.current), isDev),
291
309
  },
292
- '/api/presets': {
310
+ "/api/presets": {
293
311
  GET: () =>
294
312
  withSecurityHeaders(handlePresets(ctxHolder.current), isDev),
295
313
  POST: async (req: Request) => {
@@ -302,10 +320,10 @@ export async function startServer(
302
320
  );
303
321
  },
304
322
  },
305
- '/api/models/status': {
323
+ "/api/models/status": {
306
324
  GET: () => withSecurityHeaders(handleModelStatus(), isDev),
307
325
  },
308
- '/api/models/pull': {
326
+ "/api/models/pull": {
309
327
  POST: (req: Request) => {
310
328
  if (!isRequestAllowed(req, port)) {
311
329
  return withSecurityHeaders(forbiddenResponse(), isDev);
@@ -313,21 +331,21 @@ export async function startServer(
313
331
  return withSecurityHeaders(handleModelPull(ctxHolder), isDev);
314
332
  },
315
333
  },
316
- '/api/jobs/:id': {
334
+ "/api/jobs/:id": {
317
335
  GET: (req: Request) => {
318
336
  const url = new URL(req.url);
319
- const id = decodeURIComponent(url.pathname.split('/').pop() || '');
337
+ const id = decodeURIComponent(url.pathname.split("/").pop() || "");
320
338
  return withSecurityHeaders(handleJob(id), isDev);
321
339
  },
322
340
  },
323
- '/api/collections/:name': {
341
+ "/api/collections/:name": {
324
342
  DELETE: async (req: Request) => {
325
343
  if (!isRequestAllowed(req, port)) {
326
344
  return withSecurityHeaders(forbiddenResponse(), isDev);
327
345
  }
328
346
  const url = new URL(req.url);
329
347
  const name = decodeURIComponent(
330
- url.pathname.split('/').pop() || ''
348
+ url.pathname.split("/").pop() || ""
331
349
  );
332
350
  return withSecurityHeaders(
333
351
  await handleDeleteCollection(ctxHolder, store, name),
@@ -346,15 +364,15 @@ export async function startServer(
346
364
  }
347
365
 
348
366
  console.log(`GNO server running at http://localhost:${server.port}`);
349
- console.log('Press Ctrl+C to stop');
367
+ console.log("Press Ctrl+C to stop");
350
368
 
351
369
  // Block until shutdown signal
352
370
  await new Promise<void>((resolve) => {
353
- shutdownController.signal.addEventListener('abort', () => resolve(), {
371
+ shutdownController.signal.addEventListener("abort", () => resolve(), {
354
372
  once: true,
355
373
  });
356
374
  });
357
375
 
358
- server.stop(true);
376
+ await server.stop(true);
359
377
  return { success: true };
360
378
  }
@@ -5,7 +5,7 @@
5
5
  * @module src/store
6
6
  */
7
7
 
8
- export type { Migration } from './migrations';
8
+ export type { Migration } from "./migrations";
9
9
  // Migrations
10
10
  export {
11
11
  getDbFtsTokenizer,
@@ -13,10 +13,10 @@ export {
13
13
  migrations,
14
14
  needsFtsRebuild,
15
15
  runMigrations,
16
- } from './migrations';
16
+ } from "./migrations";
17
17
 
18
18
  // SQLite adapter
19
- export { SqliteAdapter } from './sqlite';
19
+ export { SqliteAdapter } from "./sqlite";
20
20
  // Types and interfaces
21
21
  export type {
22
22
  ChunkInput,
@@ -37,5 +37,5 @@ export type {
37
37
  StoreErrorCode,
38
38
  StorePort,
39
39
  StoreResult,
40
- } from './types';
41
- export { err, ok } from './types';
40
+ } from "./types";
41
+ export { err, ok } from "./types";
@@ -5,13 +5,14 @@
5
5
  * @module src/store/migrations/001_initial
6
6
  */
7
7
 
8
- import type { Database } from 'bun:sqlite';
9
- import type { FtsTokenizer } from '../../config/types';
10
- import type { Migration } from './runner';
8
+ import type { Database } from "bun:sqlite";
9
+
10
+ import type { FtsTokenizer } from "../../config/types";
11
+ import type { Migration } from "./runner";
11
12
 
12
13
  export const migration: Migration = {
13
14
  version: 1,
14
- name: 'initial_schema',
15
+ name: "initial_schema",
15
16
 
16
17
  up(db: Database, ftsTokenizer: FtsTokenizer): void {
17
18
  // Collections (synced from YAML config)
@@ -78,18 +79,18 @@ export const migration: Migration = {
78
79
  `);
79
80
 
80
81
  db.exec(
81
- 'CREATE INDEX IF NOT EXISTS idx_documents_collection ON documents(collection)'
82
+ "CREATE INDEX IF NOT EXISTS idx_documents_collection ON documents(collection)"
82
83
  );
83
84
  db.exec(
84
- 'CREATE INDEX IF NOT EXISTS idx_documents_active ON documents(active)'
85
+ "CREATE INDEX IF NOT EXISTS idx_documents_active ON documents(active)"
85
86
  );
86
87
  db.exec(
87
- 'CREATE INDEX IF NOT EXISTS idx_documents_mirror_hash ON documents(mirror_hash)'
88
+ "CREATE INDEX IF NOT EXISTS idx_documents_mirror_hash ON documents(mirror_hash)"
88
89
  );
89
90
  db.exec(
90
- 'CREATE INDEX IF NOT EXISTS idx_documents_docid ON documents(docid)'
91
+ "CREATE INDEX IF NOT EXISTS idx_documents_docid ON documents(docid)"
91
92
  );
92
- db.exec('CREATE INDEX IF NOT EXISTS idx_documents_uri ON documents(uri)');
93
+ db.exec("CREATE INDEX IF NOT EXISTS idx_documents_uri ON documents(uri)");
93
94
 
94
95
  // Content (content-addressed markdown mirrors)
95
96
  db.exec(`
@@ -118,7 +119,7 @@ export const migration: Migration = {
118
119
  `);
119
120
 
120
121
  db.exec(
121
- 'CREATE INDEX IF NOT EXISTS idx_chunks_mirror_hash ON content_chunks(mirror_hash)'
122
+ "CREATE INDEX IF NOT EXISTS idx_chunks_mirror_hash ON content_chunks(mirror_hash)"
122
123
  );
123
124
 
124
125
  // FTS5 virtual table with configurable tokenizer
@@ -143,7 +144,7 @@ export const migration: Migration = {
143
144
  `);
144
145
 
145
146
  db.exec(
146
- 'CREATE INDEX IF NOT EXISTS idx_vectors_model ON content_vectors(model)'
147
+ "CREATE INDEX IF NOT EXISTS idx_vectors_model ON content_vectors(model)"
147
148
  );
148
149
 
149
150
  // LLM cache (EPIC 6+)
@@ -157,7 +158,7 @@ export const migration: Migration = {
157
158
  `);
158
159
 
159
160
  db.exec(
160
- 'CREATE INDEX IF NOT EXISTS idx_llm_cache_expires ON llm_cache(expires_at)'
161
+ "CREATE INDEX IF NOT EXISTS idx_llm_cache_expires ON llm_cache(expires_at)"
161
162
  );
162
163
 
163
164
  // Ingest errors
@@ -174,23 +175,23 @@ export const migration: Migration = {
174
175
  `);
175
176
 
176
177
  db.exec(
177
- 'CREATE INDEX IF NOT EXISTS idx_ingest_errors_occurred ON ingest_errors(occurred_at DESC)'
178
+ "CREATE INDEX IF NOT EXISTS idx_ingest_errors_occurred ON ingest_errors(occurred_at DESC)"
178
179
  );
179
180
  db.exec(
180
- 'CREATE INDEX IF NOT EXISTS idx_ingest_errors_collection ON ingest_errors(collection, rel_path)'
181
+ "CREATE INDEX IF NOT EXISTS idx_ingest_errors_collection ON ingest_errors(collection, rel_path)"
181
182
  );
182
183
  },
183
184
 
184
185
  down(db: Database): void {
185
186
  // Drop in reverse order of creation
186
- db.exec('DROP TABLE IF EXISTS ingest_errors');
187
- db.exec('DROP TABLE IF EXISTS llm_cache');
188
- db.exec('DROP TABLE IF EXISTS content_vectors');
189
- db.exec('DROP TABLE IF EXISTS content_fts');
190
- db.exec('DROP TABLE IF EXISTS content_chunks');
191
- db.exec('DROP TABLE IF EXISTS content');
192
- db.exec('DROP TABLE IF EXISTS documents');
193
- db.exec('DROP TABLE IF EXISTS contexts');
194
- db.exec('DROP TABLE IF EXISTS collections');
187
+ db.exec("DROP TABLE IF EXISTS ingest_errors");
188
+ db.exec("DROP TABLE IF EXISTS llm_cache");
189
+ db.exec("DROP TABLE IF EXISTS content_vectors");
190
+ db.exec("DROP TABLE IF EXISTS content_fts");
191
+ db.exec("DROP TABLE IF EXISTS content_chunks");
192
+ db.exec("DROP TABLE IF EXISTS content");
193
+ db.exec("DROP TABLE IF EXISTS documents");
194
+ db.exec("DROP TABLE IF EXISTS contexts");
195
+ db.exec("DROP TABLE IF EXISTS collections");
195
196
  },
196
197
  };
@@ -7,17 +7,18 @@
7
7
  * @module src/store/migrations/002-documents-fts
8
8
  */
9
9
 
10
- import type { Database } from 'bun:sqlite';
11
- import type { FtsTokenizer } from '../../config/types';
12
- import type { Migration } from './runner';
10
+ import type { Database } from "bun:sqlite";
11
+
12
+ import type { FtsTokenizer } from "../../config/types";
13
+ import type { Migration } from "./runner";
13
14
 
14
15
  export const migration: Migration = {
15
16
  version: 2,
16
- name: 'documents_fts',
17
+ name: "documents_fts",
17
18
 
18
19
  up(db: Database, ftsTokenizer: FtsTokenizer): void {
19
20
  // Drop old chunk-level FTS (no backwards compat needed per epic)
20
- db.exec('DROP TABLE IF EXISTS content_fts');
21
+ db.exec("DROP TABLE IF EXISTS content_fts");
21
22
 
22
23
  // Create document-level FTS with snowball stemmer
23
24
  // Indexes: filepath (for path searches), title, body (full content)
@@ -34,7 +35,7 @@ export const migration: Migration = {
34
35
  },
35
36
 
36
37
  down(db: Database): void {
37
- db.exec('DROP TABLE IF EXISTS documents_fts');
38
+ db.exec("DROP TABLE IF EXISTS documents_fts");
38
39
  // Note: Cannot restore content_fts - would need full reindex
39
40
  },
40
41
  };
@@ -5,17 +5,17 @@
5
5
  * @module src/store/migrations
6
6
  */
7
7
 
8
- export type { Migration } from './runner';
8
+ export type { Migration } from "./runner";
9
9
  export {
10
10
  getDbFtsTokenizer,
11
11
  getSchemaVersion,
12
12
  needsFtsRebuild,
13
13
  runMigrations,
14
- } from './runner';
14
+ } from "./runner";
15
15
 
16
16
  // Import all migrations
17
- import { migration as m001 } from './001-initial';
18
- import { migration as m002 } from './002-documents-fts';
17
+ import { migration as m001 } from "./001-initial";
18
+ import { migration as m002 } from "./002-documents-fts";
19
19
 
20
20
  /** All migrations in order */
21
21
  export const migrations = [m001, m002];
@@ -5,10 +5,12 @@
5
5
  * @module src/store/migrations/runner
6
6
  */
7
7
 
8
- import type { Database } from 'bun:sqlite';
9
- import type { FtsTokenizer } from '../../config/types';
10
- import type { MigrationResult, StoreResult } from '../types';
11
- import { err, ok } from '../types';
8
+ import type { Database } from "bun:sqlite";
9
+
10
+ import type { FtsTokenizer } from "../../config/types";
11
+ import type { MigrationResult, StoreResult } from "../types";
12
+
13
+ import { err, ok } from "../types";
12
14
 
13
15
  // ─────────────────────────────────────────────────────────────────────────────
14
16
  // Types
@@ -38,7 +40,7 @@ const BOOTSTRAP_META_TABLE = `
38
40
  )
39
41
  `;
40
42
 
41
- const GET_META = 'SELECT value FROM schema_meta WHERE key = ?';
43
+ const GET_META = "SELECT value FROM schema_meta WHERE key = ?";
42
44
 
43
45
  const SET_META = `
44
46
  INSERT INTO schema_meta (key, value, updated_at)
@@ -58,7 +60,7 @@ const SET_META = `
58
60
  */
59
61
  export function getSchemaVersion(db: Database): number {
60
62
  try {
61
- const row = db.query<{ value: string }, [string]>(GET_META).get('version');
63
+ const row = db.query<{ value: string }, [string]>(GET_META).get("version");
62
64
  return row ? Number.parseInt(row.value, 10) : 0;
63
65
  } catch {
64
66
  // Table doesn't exist yet
@@ -74,7 +76,7 @@ export function getDbFtsTokenizer(db: Database): FtsTokenizer | null {
74
76
  try {
75
77
  const row = db
76
78
  .query<{ value: string }, [string]>(GET_META)
77
- .get('fts_tokenizer');
79
+ .get("fts_tokenizer");
78
80
  return row ? (row.value as FtsTokenizer) : null;
79
81
  } catch {
80
82
  return null;
@@ -114,9 +116,9 @@ export function runMigrations(
114
116
  // Tokenizer mismatch - this requires special handling
115
117
  // For now, we error out. EPIC 5 will handle FTS rebuild.
116
118
  return err(
117
- 'MIGRATION_FAILED',
119
+ "MIGRATION_FAILED",
118
120
  `FTS tokenizer mismatch: DB has '${dbTokenizer}', config has '${ftsTokenizer}'. ` +
119
- 'Run `gno index --rebuild-fts` to recreate FTS index with new tokenizer.'
121
+ "Run `gno index --rebuild-fts` to recreate FTS index with new tokenizer."
120
122
  );
121
123
  }
122
124
 
@@ -128,7 +130,7 @@ export function runMigrations(
128
130
  const migration = sorted[i];
129
131
  if (migration && migration.version !== i + 1) {
130
132
  return err(
131
- 'MIGRATION_FAILED',
133
+ "MIGRATION_FAILED",
132
134
  `Migration versions must be sequential. Expected ${i + 1}, got ${migration.version}`
133
135
  );
134
136
  }
@@ -150,14 +152,14 @@ export function runMigrations(
150
152
  const transaction = db.transaction(() => {
151
153
  for (const migration of pending) {
152
154
  migration.up(db, ftsTokenizer);
153
- setMeta(db, 'version', migration.version.toString());
155
+ setMeta(db, "version", migration.version.toString());
154
156
  applied.push(migration.version);
155
157
  }
156
158
 
157
159
  // Set FTS tokenizer if not already set
158
160
  if (dbTokenizer === null) {
159
- setMeta(db, 'fts_tokenizer', ftsTokenizer);
160
- setMeta(db, 'created_at', new Date().toISOString());
161
+ setMeta(db, "fts_tokenizer", ftsTokenizer);
162
+ setMeta(db, "created_at", new Date().toISOString());
161
163
  }
162
164
  });
163
165
 
@@ -170,8 +172,8 @@ export function runMigrations(
170
172
  });
171
173
  } catch (cause) {
172
174
  const message =
173
- cause instanceof Error ? cause.message : 'Unknown migration error';
174
- return err('MIGRATION_FAILED', message, cause);
175
+ cause instanceof Error ? cause.message : "Unknown migration error";
176
+ return err("MIGRATION_FAILED", message, cause);
175
177
  }
176
178
  }
177
179