@gmickel/gno 0.11.0 → 0.13.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 (42) hide show
  1. package/README.md +46 -10
  2. package/assets/screenshots/webui-doc-view.jpg +0 -0
  3. package/assets/screenshots/webui-editor.jpg +0 -0
  4. package/assets/screenshots/webui-graph.jpg +0 -0
  5. package/assets/skill/SKILL.md +15 -12
  6. package/assets/skill/cli-reference.md +83 -0
  7. package/assets/skill/examples.md +48 -0
  8. package/package.json +2 -1
  9. package/src/cli/commands/embed.ts +20 -0
  10. package/src/cli/commands/graph.ts +187 -0
  11. package/src/cli/commands/links.ts +891 -0
  12. package/src/cli/commands/ref-parser.ts +1 -1
  13. package/src/cli/commands/vec.ts +287 -0
  14. package/src/cli/options.ts +9 -0
  15. package/src/cli/program.ts +279 -1
  16. package/src/core/links.ts +429 -0
  17. package/src/embed/backlog.ts +19 -1
  18. package/src/ingestion/position.ts +119 -0
  19. package/src/ingestion/strip.ts +162 -0
  20. package/src/ingestion/sync.ts +58 -1
  21. package/src/mcp/tools/index.ts +61 -0
  22. package/src/mcp/tools/links.ts +722 -0
  23. package/src/serve/public/app.tsx +6 -1
  24. package/src/serve/public/components/BacklinksPanel.tsx +329 -0
  25. package/src/serve/public/components/OutgoingLinksPanel.tsx +336 -0
  26. package/src/serve/public/components/RelatedNotesSidebar.tsx +490 -0
  27. package/src/serve/public/components/WikiLinkAutocomplete.tsx +554 -0
  28. package/src/serve/public/globals.built.css +1 -1
  29. package/src/serve/public/pages/Dashboard.tsx +11 -1
  30. package/src/serve/public/pages/DocView.tsx +344 -259
  31. package/src/serve/public/pages/DocumentEditor.tsx +69 -10
  32. package/src/serve/public/pages/GraphView.tsx +700 -0
  33. package/src/serve/routes/graph.ts +162 -0
  34. package/src/serve/routes/links.ts +500 -0
  35. package/src/serve/server.ts +49 -0
  36. package/src/store/migrations/004-doc-links.ts +64 -0
  37. package/src/store/migrations/005-graph-indexes.ts +50 -0
  38. package/src/store/migrations/index.ts +3 -1
  39. package/src/store/sqlite/adapter.ts +1075 -0
  40. package/src/store/types.ts +213 -0
  41. package/src/store/vector/sqlite-vec.ts +62 -45
  42. package/src/store/vector/types.ts +2 -0
package/README.md CHANGED
@@ -27,6 +27,25 @@ GNO is a local knowledge engine for privacy-conscious developers and AI agents.
27
27
 
28
28
  ---
29
29
 
30
+ ## What's New in v0.13
31
+
32
+ - **Knowledge Graph**: Interactive force-directed visualization of document connections
33
+ - **Graph with Similarity**: See semantic similarity as golden edges (not just wiki/markdown links)
34
+ - **CLI**: `gno graph` command with collection filtering and similarity options
35
+ - **Web UI**: `/graph` page with zoom, pan, collection filter, similarity toggle
36
+ - **MCP**: `gno_graph` tool for AI agents to explore document relationships
37
+ - **REST API**: `/api/graph` endpoint with full query parameters
38
+
39
+ ### v0.12
40
+
41
+ - **Note Linking**: Wiki-style `[[links]]`, backlinks, and AI-powered related notes
42
+ - **Tag System**: Filter searches by frontmatter tags with `--tags-any`/`--tags-all`
43
+ - **Web UI**: Outgoing links panel, backlinks panel, related notes sidebar
44
+ - **CLI**: `gno links`, `gno backlinks`, `gno similar` commands
45
+ - **MCP**: `gno_links`, `gno_backlinks`, `gno_similar` tools
46
+
47
+ ---
48
+
30
49
  ## Quick Start
31
50
 
32
51
  ```bash
@@ -144,16 +163,20 @@ Connect GNO to Claude Desktop, Cursor, Raycast, and more:
144
163
 
145
164
  ![GNO MCP](./assets/screenshots/mcp.jpg)
146
165
 
147
- GNO exposes 6 tools via [Model Context Protocol](https://modelcontextprotocol.io):
148
-
149
- | Tool | Description |
150
- | :-------------- | :-------------------------- |
151
- | `gno_search` | BM25 keyword search |
152
- | `gno_vsearch` | Vector semantic search |
153
- | `gno_query` | Hybrid search (recommended) |
154
- | `gno_get` | Retrieve document by ID |
155
- | `gno_multi_get` | Batch document retrieval |
156
- | `gno_status` | Index health check |
166
+ GNO exposes tools via [Model Context Protocol](https://modelcontextprotocol.io):
167
+
168
+ | Tool | Description |
169
+ | :-------------- | :------------------------------------ |
170
+ | `gno_search` | BM25 keyword search |
171
+ | `gno_vsearch` | Vector semantic search |
172
+ | `gno_query` | Hybrid search (recommended) |
173
+ | `gno_get` | Retrieve document by ID |
174
+ | `gno_multi_get` | Batch document retrieval |
175
+ | `gno_links` | Get outgoing links from document |
176
+ | `gno_backlinks` | Get documents linking TO document |
177
+ | `gno_similar` | Find semantically similar documents |
178
+ | `gno_graph` | Get knowledge graph (nodes and edges) |
179
+ | `gno_status` | Index health check |
157
180
 
158
181
  **Design**: MCP tools are retrieval-only. Your AI assistant (Claude, GPT-4) synthesizes answers from retrieved context. Best retrieval (GNO) + best reasoning (your LLM).
159
182
 
@@ -201,6 +224,18 @@ Full-featured markdown editor with:
201
224
  | **Keyboard Shortcuts** | ⌘S save, ⌘B bold, ⌘I italic, ⌘K link |
202
225
  | **Quick Capture** | ⌘N creates new note from anywhere |
203
226
 
227
+ ### Document Viewer
228
+
229
+ ![GNO Document Viewer](./assets/screenshots/webui-doc-view.jpg)
230
+
231
+ View documents with full context: outgoing links, backlinks, and AI-powered related notes sidebar.
232
+
233
+ ### Knowledge Graph
234
+
235
+ ![GNO Knowledge Graph](./assets/screenshots/webui-graph.jpg)
236
+
237
+ Interactive visualization of document connections. Wiki links, markdown links, and optional similarity edges rendered as a navigable constellation.
238
+
204
239
  ### Collections Management
205
240
 
206
241
  ![GNO Collections](./assets/screenshots/webui-collections.jpg)
@@ -315,6 +350,7 @@ graph TD
315
350
  | **MCP Server** | Works with Claude Desktop, Cursor, Zed, + 8 more |
316
351
  | **Collections** | Organize sources with patterns, excludes, contexts |
317
352
  | **Tag Filtering** | Frontmatter tags with hierarchical paths, filter via `--tags-any`/`--tags-all` |
353
+ | **Note Linking** | Wiki links, backlinks, related notes, cross-collection navigation |
318
354
  | **Multilingual** | 30+ languages, auto-detection, cross-lingual search |
319
355
  | **Incremental** | SHA-256 tracking, only changed files re-indexed |
320
356
  | **Keyboard First** | ⌘N capture, ⌘K search, ⌘/ shortcuts, ⌘S save |
Binary file
@@ -20,6 +20,8 @@ Fast local semantic search. Index once, search instantly. No cloud, no API keys.
20
20
  - User wants a **web UI** to browse/search documents
21
21
  - User asks to **get AI answers** from their documents
22
22
  - User wants to **tag, categorize, or filter** documents
23
+ - User asks about **backlinks, wiki links, or related notes**
24
+ - User wants to **visualize document connections** or see a **knowledge graph**
23
25
 
24
26
  ## Quick Start
25
27
 
@@ -32,18 +34,19 @@ gno search "your query" # BM25 keyword search
32
34
 
33
35
  ## Command Overview
34
36
 
35
- | Category | Commands | Description |
36
- | ------------ | ---------------------------------------------------------------- | ------------------------------------------------------ |
37
- | **Search** | `search`, `vsearch`, `query`, `ask` | Find documents by keywords, meaning, or get AI answers |
38
- | **Retrieve** | `get`, `multi-get`, `ls` | Fetch document content by URI or ID |
39
- | **Index** | `init`, `collection add/list/remove`, `index`, `update`, `embed` | Set up and maintain document index |
40
- | **Tags** | `tags`, `tags add`, `tags rm` | Organize and filter documents |
41
- | **Context** | `context add/list/rm/check` | Add hints to improve search relevance |
42
- | **Models** | `models list/use/pull/clear/path` | Manage local AI models |
43
- | **Serve** | `serve` | Web UI for browsing and searching |
44
- | **MCP** | `mcp`, `mcp install/uninstall/status` | AI assistant integration |
45
- | **Skill** | `skill install/uninstall/show/paths` | Install skill for AI agents |
46
- | **Admin** | `status`, `doctor`, `cleanup`, `reset`, `completion` | Maintenance and diagnostics |
37
+ | Category | Commands | Description |
38
+ | ------------ | ---------------------------------------------------------------- | --------------------------------------------------------- |
39
+ | **Search** | `search`, `vsearch`, `query`, `ask` | Find documents by keywords, meaning, or get AI answers |
40
+ | **Links** | `links`, `backlinks`, `similar`, `graph` | Navigate document relationships and visualize connections |
41
+ | **Retrieve** | `get`, `multi-get`, `ls` | Fetch document content by URI or ID |
42
+ | **Index** | `init`, `collection add/list/remove`, `index`, `update`, `embed` | Set up and maintain document index |
43
+ | **Tags** | `tags`, `tags add`, `tags rm` | Organize and filter documents |
44
+ | **Context** | `context add/list/rm/check` | Add hints to improve search relevance |
45
+ | **Models** | `models list/use/pull/clear/path` | Manage local AI models |
46
+ | **Serve** | `serve` | Web UI for browsing and searching |
47
+ | **MCP** | `mcp`, `mcp install/uninstall/status` | AI assistant integration |
48
+ | **Skill** | `skill install/uninstall/show/paths` | Install skill for AI agents |
49
+ | **Admin** | `status`, `doctor`, `cleanup`, `reset`, `vec`, `completion` | Maintenance and diagnostics |
47
50
 
48
51
  ## Search Modes
49
52
 
@@ -234,6 +234,76 @@ Validate context configuration.
234
234
  gno context check [--json]
235
235
  ```
236
236
 
237
+ ## Note Linking
238
+
239
+ ### gno links
240
+
241
+ List outgoing links from a document.
242
+
243
+ ```bash
244
+ gno links <ref> [options]
245
+ ```
246
+
247
+ | Option | Description |
248
+ | ------------ | ------------------------------------ |
249
+ | `--type` | Filter: `wiki`, `markdown`, or `all` |
250
+ | `--resolved` | Only show resolved links |
251
+ | `--broken` | Only show broken links |
252
+ | `--json` | JSON output |
253
+
254
+ ### gno backlinks
255
+
256
+ Find documents linking TO a target.
257
+
258
+ ```bash
259
+ gno backlinks <ref> [options]
260
+ ```
261
+
262
+ | Option | Description |
263
+ | -------- | ------------------------- |
264
+ | `-n` | Max results (default: 20) |
265
+ | `--json` | JSON output |
266
+
267
+ ### gno similar
268
+
269
+ Find semantically similar documents.
270
+
271
+ ```bash
272
+ gno similar <ref> [options]
273
+ ```
274
+
275
+ | Option | Description |
276
+ | -------------------- | ----------------------------- |
277
+ | `-n` | Max results (default: 5) |
278
+ | `--threshold` | Min similarity (0-1) |
279
+ | `--cross-collection` | Search across all collections |
280
+ | `--json` | JSON output |
281
+
282
+ **Requirements**: Embeddings must exist for source and target documents.
283
+
284
+ ### gno graph
285
+
286
+ Generate knowledge graph of document connections.
287
+
288
+ ```bash
289
+ gno graph [options]
290
+ ```
291
+
292
+ | Option | Default | Description |
293
+ | ------------------ | ------- | ------------------------------ |
294
+ | `-c, --collection` | all | Filter to single collection |
295
+ | `--limit` | 2000 | Max nodes |
296
+ | `--edge-limit` | 10000 | Max edges |
297
+ | `--similar` | false | Include similarity edges |
298
+ | `--threshold` | 0.7 | Similarity threshold (0-1) |
299
+ | `--linked-only` | true | Exclude isolated nodes |
300
+ | `--similar-top-k` | 5 | Similar docs per node (max 20) |
301
+ | `--json` | - | JSON output |
302
+
303
+ **Edge types**: `wiki` (wiki links), `markdown` (md links), `similar` (vector similarity).
304
+
305
+ **Web UI**: Access interactive graph at `http://localhost:3000/graph` via `gno serve`.
306
+
237
307
  ## Tags
238
308
 
239
309
  ### gno tags
@@ -324,6 +394,19 @@ gno doctor [--json|--md]
324
394
  gno cleanup
325
395
  ```
326
396
 
397
+ ### gno vec
398
+
399
+ Vector index maintenance. Use when `gno similar` returns empty despite embeddings.
400
+
401
+ ```bash
402
+ gno vec sync # Fast incremental sync
403
+ gno vec rebuild # Full rebuild
404
+ ```
405
+
406
+ | Option | Description |
407
+ | -------- | ----------- |
408
+ | `--json` | JSON output |
409
+
327
410
  ## MCP Server
328
411
 
329
412
  ### gno mcp
@@ -206,6 +206,54 @@ gno models use quality
206
206
  gno models pull
207
207
  ```
208
208
 
209
+ ## Note Linking
210
+
211
+ ### View outgoing links
212
+
213
+ ```bash
214
+ # List all links from a document
215
+ gno links gno://notes/readme.md
216
+
217
+ # Filter by type
218
+ gno links gno://notes/readme.md --type wiki
219
+ gno links gno://notes/readme.md --type markdown
220
+
221
+ # Show only broken links
222
+ gno links gno://notes/readme.md --broken
223
+ ```
224
+
225
+ ### Find backlinks
226
+
227
+ ```bash
228
+ # Find all documents linking TO this one
229
+ gno backlinks gno://notes/api-design.md
230
+
231
+ # More results
232
+ gno backlinks gno://notes/api-design.md -n 50
233
+ ```
234
+
235
+ ### Find related notes
236
+
237
+ ```bash
238
+ # Find semantically similar documents
239
+ gno similar gno://notes/auth.md
240
+
241
+ # Stricter threshold (default: 0.7)
242
+ gno similar gno://notes/auth.md --threshold 0.85
243
+
244
+ # Search across all collections
245
+ gno similar gno://notes/auth.md --cross-collection
246
+ ```
247
+
248
+ ### Wiki link syntax
249
+
250
+ ```markdown
251
+ # In your documents:
252
+ See [[API Design]] for details.
253
+ Check [[work:Project Plan]] for cross-collection link.
254
+ Read [[Security#OAuth]] for specific section.
255
+ ```
256
+
209
257
  ## Tagging
210
258
 
211
259
  ### List and manage tags
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gmickel/gno",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "description": "Local semantic search for your documents. Index Markdown, PDF, and Office files with hybrid BM25 + vector search.",
5
5
  "keywords": [
6
6
  "embeddings",
@@ -101,6 +101,7 @@
101
101
  "picocolors": "^1.1.1",
102
102
  "react": "^19.2.3",
103
103
  "react-dom": "^19.2.3",
104
+ "react-force-graph-2d": "^1.29.0",
104
105
  "react-markdown": "^10.1.0",
105
106
  "rehype-sanitize": "^6.0.0",
106
107
  "remark-gfm": "^4.0.1",
@@ -372,6 +372,26 @@ export async function embed(options: EmbedOptions = {}): Promise<EmbedResult> {
372
372
  return { success: false, error: result.error };
373
373
  }
374
374
 
375
+ // Sync vec index if any vec0 writes failed (matches embedBacklog behavior)
376
+ if (vectorIndex.vecDirty) {
377
+ const syncResult = await vectorIndex.syncVecIndex();
378
+ if (syncResult.ok) {
379
+ const { added, removed } = syncResult.value;
380
+ if (added > 0 || removed > 0) {
381
+ if (!options.json) {
382
+ process.stdout.write(
383
+ `\n[vec] Synced index: +${added} -${removed}\n`
384
+ );
385
+ }
386
+ }
387
+ vectorIndex.vecDirty = false;
388
+ } else if (!options.json) {
389
+ process.stdout.write(
390
+ `\n[vec] Sync failed: ${syncResult.error.message}\n`
391
+ );
392
+ }
393
+ }
394
+
375
395
  return {
376
396
  success: true,
377
397
  embedded: result.embedded,
@@ -0,0 +1,187 @@
1
+ /**
2
+ * gno graph command implementation.
3
+ * Outputs knowledge graph of document links.
4
+ *
5
+ * @module src/cli/commands/graph
6
+ */
7
+
8
+ import type { GetGraphOptions, GraphResult } from "../../store/types";
9
+
10
+ import { initStore } from "./shared";
11
+
12
+ // ─────────────────────────────────────────────────────────────────────────────
13
+ // Types
14
+ // ─────────────────────────────────────────────────────────────────────────────
15
+
16
+ export interface GraphOptions {
17
+ /** Override config path */
18
+ configPath?: string;
19
+ /** Filter by collection */
20
+ collection?: string;
21
+ /** Max nodes (default 2000) */
22
+ limitNodes?: number;
23
+ /** Max edges (default 10000) */
24
+ limitEdges?: number;
25
+ /** Include similarity edges */
26
+ includeSimilar?: boolean;
27
+ /** Similarity threshold (0-1) */
28
+ threshold?: number;
29
+ /** Include isolated nodes */
30
+ includeIsolated?: boolean;
31
+ /** Similar top-K per node */
32
+ similarTopK?: number;
33
+ /** Output format: json (default), dot, mermaid */
34
+ format?: "json" | "dot" | "mermaid";
35
+ }
36
+
37
+ export type GraphCommandResult =
38
+ | { success: true; data: GraphResult }
39
+ | { success: false; error: string; isValidation?: boolean };
40
+
41
+ // ─────────────────────────────────────────────────────────────────────────────
42
+ // Command: graph
43
+ // ─────────────────────────────────────────────────────────────────────────────
44
+
45
+ /**
46
+ * Execute gno graph command.
47
+ * Returns knowledge graph of document links.
48
+ */
49
+ export async function graph(
50
+ options: GraphOptions = {}
51
+ ): Promise<GraphCommandResult> {
52
+ const initResult = await initStore({ configPath: options.configPath });
53
+ if (!initResult.ok) {
54
+ return { success: false, error: initResult.error };
55
+ }
56
+ const { store } = initResult;
57
+
58
+ try {
59
+ const storeOptions: GetGraphOptions = {
60
+ collection: options.collection,
61
+ limitNodes: options.limitNodes,
62
+ limitEdges: options.limitEdges,
63
+ includeSimilar: options.includeSimilar,
64
+ threshold: options.threshold,
65
+ linkedOnly: !options.includeIsolated,
66
+ similarTopK: options.similarTopK,
67
+ };
68
+
69
+ const result = await store.getGraph(storeOptions);
70
+ if (!result.ok) {
71
+ return { success: false, error: result.error.message };
72
+ }
73
+
74
+ return { success: true, data: result.value };
75
+ } finally {
76
+ await store.close();
77
+ }
78
+ }
79
+
80
+ // ─────────────────────────────────────────────────────────────────────────────
81
+ // Formatters
82
+ // ─────────────────────────────────────────────────────────────────────────────
83
+
84
+ /** Escape string for DOT format (double quotes, newlines) */
85
+ function escapeDot(s: string): string {
86
+ return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
87
+ }
88
+
89
+ /** Escape string for Mermaid (quotes, brackets, newlines) */
90
+ function escapeMermaid(s: string): string {
91
+ return s
92
+ .replace(/\\/g, "\\\\")
93
+ .replace(/"/g, "#quot;")
94
+ .replace(/\[/g, "#lbrack;")
95
+ .replace(/\]/g, "#rbrack;")
96
+ .replace(/\n/g, " ");
97
+ }
98
+
99
+ /**
100
+ * Format graph result as DOT (Graphviz).
101
+ */
102
+ export function formatDot(result: GraphResult): string {
103
+ const lines: string[] = ["digraph G {"];
104
+ lines.push(" rankdir=LR;");
105
+ lines.push(
106
+ ' node [shape=box, style="rounded,filled", fillcolor="#f0f0f0"];'
107
+ );
108
+
109
+ // Nodes
110
+ for (const node of result.nodes) {
111
+ const label = escapeDot(node.title ?? node.id);
112
+ lines.push(` "${escapeDot(node.id)}" [label="${label}"];`);
113
+ }
114
+
115
+ // Edges
116
+ for (const link of result.links) {
117
+ const style =
118
+ link.type === "similar"
119
+ ? ' [style=dashed, color="#888888", dir=none]'
120
+ : "";
121
+ lines.push(
122
+ ` "${escapeDot(link.source)}" -> "${escapeDot(link.target)}"${style};`
123
+ );
124
+ }
125
+
126
+ lines.push("}");
127
+ return lines.join("\n");
128
+ }
129
+
130
+ /**
131
+ * Format graph result as Mermaid.
132
+ */
133
+ export function formatMermaid(result: GraphResult): string {
134
+ const lines: string[] = ["graph LR"];
135
+
136
+ // Build node ID map (Mermaid needs simple IDs)
137
+ const nodeIds = new Map<string, string>();
138
+ result.nodes.forEach((node, i) => {
139
+ nodeIds.set(node.id, `n${i}`);
140
+ });
141
+
142
+ // Nodes with labels
143
+ for (const node of result.nodes) {
144
+ const mermaidId = nodeIds.get(node.id) ?? node.id;
145
+ const label = escapeMermaid(node.title ?? node.id);
146
+ lines.push(` ${mermaidId}["${label}"]`);
147
+ }
148
+
149
+ // Edges
150
+ for (const link of result.links) {
151
+ const sourceId = nodeIds.get(link.source) ?? link.source;
152
+ const targetId = nodeIds.get(link.target) ?? link.target;
153
+ const arrow = link.type === "similar" ? "---" : "-->";
154
+ lines.push(` ${sourceId} ${arrow} ${targetId}`);
155
+ }
156
+
157
+ return lines.join("\n");
158
+ }
159
+
160
+ /**
161
+ * Format graph result for output.
162
+ */
163
+ export function formatGraph(
164
+ result: GraphCommandResult,
165
+ options: GraphOptions
166
+ ): string {
167
+ if (!result.success) {
168
+ if (options.format === "json" || !options.format) {
169
+ return JSON.stringify({
170
+ error: { code: "GRAPH_FAILED", message: result.error },
171
+ });
172
+ }
173
+ return `Error: ${result.error}`;
174
+ }
175
+
176
+ const { data } = result;
177
+
178
+ switch (options.format) {
179
+ case "dot":
180
+ return formatDot(data);
181
+ case "mermaid":
182
+ return formatMermaid(data);
183
+ case "json":
184
+ default:
185
+ return JSON.stringify(data, null, 2);
186
+ }
187
+ }