@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.
- package/LICENSE +21 -0
- package/README.md +90 -50
- package/THIRD_PARTY_NOTICES.md +22 -0
- package/assets/screenshots/webui-ask-answer.png +0 -0
- package/assets/screenshots/webui-collections.png +0 -0
- package/assets/screenshots/webui-editor.png +0 -0
- package/assets/screenshots/webui-home.png +0 -0
- package/assets/skill/SKILL.md +12 -12
- package/assets/skill/cli-reference.md +59 -57
- package/assets/skill/examples.md +8 -7
- package/assets/skill/mcp-reference.md +8 -4
- package/package.json +32 -25
- package/src/app/constants.ts +43 -42
- package/src/cli/colors.ts +1 -1
- package/src/cli/commands/ask.ts +44 -43
- package/src/cli/commands/cleanup.ts +9 -8
- package/src/cli/commands/collection/add.ts +12 -12
- package/src/cli/commands/collection/index.ts +4 -4
- package/src/cli/commands/collection/list.ts +26 -25
- package/src/cli/commands/collection/remove.ts +10 -10
- package/src/cli/commands/collection/rename.ts +10 -10
- package/src/cli/commands/context/add.ts +1 -1
- package/src/cli/commands/context/check.ts +17 -17
- package/src/cli/commands/context/index.ts +4 -4
- package/src/cli/commands/context/list.ts +11 -11
- package/src/cli/commands/context/rm.ts +1 -1
- package/src/cli/commands/doctor.ts +86 -84
- package/src/cli/commands/embed.ts +30 -28
- package/src/cli/commands/get.ts +27 -26
- package/src/cli/commands/index-cmd.ts +9 -9
- package/src/cli/commands/index.ts +16 -16
- package/src/cli/commands/init.ts +13 -12
- package/src/cli/commands/ls.ts +20 -19
- package/src/cli/commands/mcp/config.ts +30 -28
- package/src/cli/commands/mcp/index.ts +4 -4
- package/src/cli/commands/mcp/install.ts +17 -17
- package/src/cli/commands/mcp/paths.ts +133 -133
- package/src/cli/commands/mcp/status.ts +21 -21
- package/src/cli/commands/mcp/uninstall.ts +13 -13
- package/src/cli/commands/mcp.ts +2 -2
- package/src/cli/commands/models/clear.ts +12 -11
- package/src/cli/commands/models/index.ts +5 -5
- package/src/cli/commands/models/list.ts +31 -30
- package/src/cli/commands/models/path.ts +1 -1
- package/src/cli/commands/models/pull.ts +19 -18
- package/src/cli/commands/models/use.ts +4 -4
- package/src/cli/commands/multi-get.ts +38 -36
- package/src/cli/commands/query.ts +21 -20
- package/src/cli/commands/ref-parser.ts +10 -10
- package/src/cli/commands/reset.ts +40 -39
- package/src/cli/commands/search.ts +14 -13
- package/src/cli/commands/serve.ts +4 -4
- package/src/cli/commands/shared.ts +11 -10
- package/src/cli/commands/skill/index.ts +5 -5
- package/src/cli/commands/skill/install.ts +18 -17
- package/src/cli/commands/skill/paths-cmd.ts +11 -10
- package/src/cli/commands/skill/paths.ts +23 -23
- package/src/cli/commands/skill/show.ts +13 -12
- package/src/cli/commands/skill/uninstall.ts +16 -15
- package/src/cli/commands/status.ts +25 -24
- package/src/cli/commands/update.ts +3 -3
- package/src/cli/commands/vsearch.ts +17 -16
- package/src/cli/context.ts +5 -5
- package/src/cli/errors.ts +3 -3
- package/src/cli/format/search-results.ts +37 -37
- package/src/cli/options.ts +43 -43
- package/src/cli/program.ts +455 -459
- package/src/cli/progress.ts +1 -1
- package/src/cli/run.ts +24 -23
- package/src/collection/add.ts +9 -8
- package/src/collection/index.ts +3 -3
- package/src/collection/remove.ts +7 -6
- package/src/collection/types.ts +6 -6
- package/src/config/defaults.ts +1 -1
- package/src/config/index.ts +5 -5
- package/src/config/loader.ts +19 -18
- package/src/config/paths.ts +9 -8
- package/src/config/saver.ts +14 -13
- package/src/config/types.ts +53 -52
- package/src/converters/adapters/markitdownTs/adapter.ts +21 -19
- package/src/converters/adapters/officeparser/adapter.ts +18 -16
- package/src/converters/canonicalize.ts +12 -12
- package/src/converters/errors.ts +26 -22
- package/src/converters/index.ts +8 -8
- package/src/converters/mime.ts +25 -25
- package/src/converters/native/markdown.ts +10 -9
- package/src/converters/native/plaintext.ts +8 -7
- package/src/converters/path.ts +2 -2
- package/src/converters/pipeline.ts +11 -10
- package/src/converters/registry.ts +8 -8
- package/src/converters/types.ts +14 -14
- package/src/converters/versions.ts +4 -4
- package/src/index.ts +4 -4
- package/src/ingestion/chunker.ts +10 -9
- package/src/ingestion/index.ts +6 -6
- package/src/ingestion/language.ts +62 -62
- package/src/ingestion/sync.ts +50 -49
- package/src/ingestion/types.ts +10 -10
- package/src/ingestion/walker.ts +14 -13
- package/src/llm/cache.ts +51 -49
- package/src/llm/errors.ts +40 -36
- package/src/llm/index.ts +9 -9
- package/src/llm/lockfile.ts +6 -6
- package/src/llm/nodeLlamaCpp/adapter.ts +13 -12
- package/src/llm/nodeLlamaCpp/embedding.ts +9 -8
- package/src/llm/nodeLlamaCpp/generation.ts +7 -6
- package/src/llm/nodeLlamaCpp/lifecycle.ts +11 -10
- package/src/llm/nodeLlamaCpp/rerank.ts +6 -5
- package/src/llm/policy.ts +5 -5
- package/src/llm/registry.ts +6 -5
- package/src/llm/types.ts +2 -2
- package/src/mcp/resources/index.ts +15 -13
- package/src/mcp/server.ts +25 -23
- package/src/mcp/tools/get.ts +25 -23
- package/src/mcp/tools/index.ts +32 -29
- package/src/mcp/tools/multi-get.ts +34 -32
- package/src/mcp/tools/query.ts +29 -27
- package/src/mcp/tools/search.ts +14 -12
- package/src/mcp/tools/status.ts +12 -11
- package/src/mcp/tools/vsearch.ts +26 -24
- package/src/pipeline/answer.ts +9 -9
- package/src/pipeline/chunk-lookup.ts +1 -1
- package/src/pipeline/contextual.ts +4 -4
- package/src/pipeline/expansion.ts +23 -21
- package/src/pipeline/explain.ts +21 -21
- package/src/pipeline/fusion.ts +9 -9
- package/src/pipeline/hybrid.ts +41 -42
- package/src/pipeline/index.ts +10 -10
- package/src/pipeline/query-language.ts +39 -39
- package/src/pipeline/rerank.ts +8 -7
- package/src/pipeline/search.ts +22 -22
- package/src/pipeline/types.ts +8 -8
- package/src/pipeline/vsearch.ts +21 -24
- package/src/serve/CLAUDE.md +21 -15
- package/src/serve/config-sync.ts +9 -8
- package/src/serve/context.ts +19 -18
- package/src/serve/index.ts +1 -1
- package/src/serve/jobs.ts +7 -7
- package/src/serve/public/app.tsx +79 -25
- package/src/serve/public/components/AddCollectionDialog.tsx +382 -0
- package/src/serve/public/components/CaptureButton.tsx +60 -0
- package/src/serve/public/components/CaptureModal.tsx +365 -0
- package/src/serve/public/components/IndexingProgress.tsx +333 -0
- package/src/serve/public/components/ShortcutHelpModal.tsx +106 -0
- package/src/serve/public/components/ai-elements/code-block.tsx +42 -32
- package/src/serve/public/components/ai-elements/conversation.tsx +16 -14
- package/src/serve/public/components/ai-elements/inline-citation.tsx +33 -32
- package/src/serve/public/components/ai-elements/loader.tsx +5 -4
- package/src/serve/public/components/ai-elements/message.tsx +39 -37
- package/src/serve/public/components/ai-elements/prompt-input.tsx +97 -95
- package/src/serve/public/components/ai-elements/sources.tsx +12 -10
- package/src/serve/public/components/ai-elements/suggestion.tsx +10 -9
- package/src/serve/public/components/editor/CodeMirrorEditor.tsx +142 -0
- package/src/serve/public/components/editor/MarkdownPreview.tsx +311 -0
- package/src/serve/public/components/editor/index.ts +6 -0
- package/src/serve/public/components/preset-selector.tsx +29 -28
- package/src/serve/public/components/ui/badge.tsx +13 -12
- package/src/serve/public/components/ui/button-group.tsx +13 -12
- package/src/serve/public/components/ui/button.tsx +23 -22
- package/src/serve/public/components/ui/card.tsx +16 -16
- package/src/serve/public/components/ui/carousel.tsx +36 -35
- package/src/serve/public/components/ui/collapsible.tsx +1 -1
- package/src/serve/public/components/ui/command.tsx +17 -15
- package/src/serve/public/components/ui/dialog.tsx +13 -12
- package/src/serve/public/components/ui/dropdown-menu.tsx +13 -12
- package/src/serve/public/components/ui/hover-card.tsx +6 -5
- package/src/serve/public/components/ui/input-group.tsx +45 -43
- package/src/serve/public/components/ui/input.tsx +6 -6
- package/src/serve/public/components/ui/progress.tsx +5 -4
- package/src/serve/public/components/ui/scroll-area.tsx +11 -10
- package/src/serve/public/components/ui/select.tsx +19 -18
- package/src/serve/public/components/ui/separator.tsx +6 -5
- package/src/serve/public/components/ui/table.tsx +18 -18
- package/src/serve/public/components/ui/textarea.tsx +4 -4
- package/src/serve/public/components/ui/tooltip.tsx +5 -4
- package/src/serve/public/globals.css +27 -4
- package/src/serve/public/hooks/use-api.ts +8 -8
- package/src/serve/public/hooks/useCaptureModal.tsx +83 -0
- package/src/serve/public/hooks/useKeyboardShortcuts.ts +85 -0
- package/src/serve/public/index.html +4 -4
- package/src/serve/public/lib/utils.ts +6 -0
- package/src/serve/public/pages/Ask.tsx +27 -26
- package/src/serve/public/pages/Browse.tsx +28 -27
- package/src/serve/public/pages/Collections.tsx +439 -0
- package/src/serve/public/pages/Dashboard.tsx +166 -40
- package/src/serve/public/pages/DocView.tsx +258 -73
- package/src/serve/public/pages/DocumentEditor.tsx +510 -0
- package/src/serve/public/pages/Search.tsx +80 -58
- package/src/serve/routes/api.ts +272 -155
- package/src/serve/security.ts +4 -4
- package/src/serve/server.ts +66 -48
- package/src/store/index.ts +5 -5
- package/src/store/migrations/001-initial.ts +24 -23
- package/src/store/migrations/002-documents-fts.ts +7 -6
- package/src/store/migrations/index.ts +4 -4
- package/src/store/migrations/runner.ts +17 -15
- package/src/store/sqlite/adapter.ts +123 -121
- package/src/store/sqlite/fts5-snowball.ts +24 -23
- package/src/store/sqlite/index.ts +1 -1
- package/src/store/sqlite/setup.ts +12 -12
- package/src/store/sqlite/types.ts +4 -4
- package/src/store/types.ts +19 -19
- package/src/store/vector/index.ts +3 -3
- package/src/store/vector/sqlite-vec.ts +23 -20
- package/src/store/vector/stats.ts +10 -8
- package/src/store/vector/types.ts +2 -2
- package/vendor/fts5-snowball/README.md +6 -6
- package/assets/screenshots/webui-ask-answer.jpg +0 -0
- package/assets/screenshots/webui-home.jpg +0 -0
package/src/serve/security.ts
CHANGED
|
@@ -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(
|
|
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(
|
|
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 ===
|
|
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:
|
|
81
|
+
{ error: { code: "CSRF_VIOLATION", message: "Forbidden" } },
|
|
82
82
|
{ status: 403 }
|
|
83
83
|
);
|
|
84
84
|
}
|
package/src/serve/server.ts
CHANGED
|
@@ -6,13 +6,14 @@
|
|
|
6
6
|
* @module src/serve/server
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
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
|
|
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
|
-
|
|
37
|
-
|
|
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(
|
|
90
|
-
headers.set(
|
|
91
|
-
headers.set(
|
|
92
|
-
headers.set(
|
|
93
|
-
headers.set(
|
|
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 !==
|
|
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:
|
|
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(
|
|
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(
|
|
169
|
-
process.once(
|
|
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:
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
195
|
+
"/api/health": {
|
|
192
196
|
GET: () => withSecurityHeaders(handleHealth(), isDev),
|
|
193
197
|
},
|
|
194
|
-
|
|
198
|
+
"/api/status": {
|
|
195
199
|
GET: async () =>
|
|
196
200
|
withSecurityHeaders(await handleStatus(store), isDev),
|
|
197
201
|
},
|
|
198
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
306
|
+
"/api/capabilities": {
|
|
289
307
|
GET: () =>
|
|
290
308
|
withSecurityHeaders(handleCapabilities(ctxHolder.current), isDev),
|
|
291
309
|
},
|
|
292
|
-
|
|
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
|
-
|
|
323
|
+
"/api/models/status": {
|
|
306
324
|
GET: () => withSecurityHeaders(handleModelStatus(), isDev),
|
|
307
325
|
},
|
|
308
|
-
|
|
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
|
-
|
|
334
|
+
"/api/jobs/:id": {
|
|
317
335
|
GET: (req: Request) => {
|
|
318
336
|
const url = new URL(req.url);
|
|
319
|
-
const id = decodeURIComponent(url.pathname.split(
|
|
337
|
+
const id = decodeURIComponent(url.pathname.split("/").pop() || "");
|
|
320
338
|
return withSecurityHeaders(handleJob(id), isDev);
|
|
321
339
|
},
|
|
322
340
|
},
|
|
323
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
}
|
package/src/store/index.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* @module src/store
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
export type { Migration } from
|
|
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
|
|
16
|
+
} from "./migrations";
|
|
17
17
|
|
|
18
18
|
// SQLite adapter
|
|
19
|
-
export { SqliteAdapter } from
|
|
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
|
|
41
|
-
export { err, ok } from
|
|
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
|
|
9
|
-
|
|
10
|
-
import type {
|
|
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:
|
|
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
|
-
|
|
82
|
+
"CREATE INDEX IF NOT EXISTS idx_documents_collection ON documents(collection)"
|
|
82
83
|
);
|
|
83
84
|
db.exec(
|
|
84
|
-
|
|
85
|
+
"CREATE INDEX IF NOT EXISTS idx_documents_active ON documents(active)"
|
|
85
86
|
);
|
|
86
87
|
db.exec(
|
|
87
|
-
|
|
88
|
+
"CREATE INDEX IF NOT EXISTS idx_documents_mirror_hash ON documents(mirror_hash)"
|
|
88
89
|
);
|
|
89
90
|
db.exec(
|
|
90
|
-
|
|
91
|
+
"CREATE INDEX IF NOT EXISTS idx_documents_docid ON documents(docid)"
|
|
91
92
|
);
|
|
92
|
-
db.exec(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
178
|
+
"CREATE INDEX IF NOT EXISTS idx_ingest_errors_occurred ON ingest_errors(occurred_at DESC)"
|
|
178
179
|
);
|
|
179
180
|
db.exec(
|
|
180
|
-
|
|
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(
|
|
187
|
-
db.exec(
|
|
188
|
-
db.exec(
|
|
189
|
-
db.exec(
|
|
190
|
-
db.exec(
|
|
191
|
-
db.exec(
|
|
192
|
-
db.exec(
|
|
193
|
-
db.exec(
|
|
194
|
-
db.exec(
|
|
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
|
|
11
|
-
|
|
12
|
-
import type {
|
|
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:
|
|
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(
|
|
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(
|
|
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
|
|
8
|
+
export type { Migration } from "./runner";
|
|
9
9
|
export {
|
|
10
10
|
getDbFtsTokenizer,
|
|
11
11
|
getSchemaVersion,
|
|
12
12
|
needsFtsRebuild,
|
|
13
13
|
runMigrations,
|
|
14
|
-
} from
|
|
14
|
+
} from "./runner";
|
|
15
15
|
|
|
16
16
|
// Import all migrations
|
|
17
|
-
import { migration as m001 } from
|
|
18
|
-
import { migration as m002 } from
|
|
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
|
|
9
|
-
|
|
10
|
-
import type {
|
|
11
|
-
import {
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
-
|
|
119
|
+
"MIGRATION_FAILED",
|
|
118
120
|
`FTS tokenizer mismatch: DB has '${dbTokenizer}', config has '${ftsTokenizer}'. ` +
|
|
119
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
160
|
-
setMeta(db,
|
|
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 :
|
|
174
|
-
return err(
|
|
175
|
+
cause instanceof Error ? cause.message : "Unknown migration error";
|
|
176
|
+
return err("MIGRATION_FAILED", message, cause);
|
|
175
177
|
}
|
|
176
178
|
}
|
|
177
179
|
|