@gmickel/gno 1.36.1 → 1.37.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.
package/README.md CHANGED
@@ -105,7 +105,7 @@ gno daemon --detach # headless indexing + resident MCP gateway
105
105
 
106
106
  **Start here** · [Quick Start](#quick-start) · [Installation](#installation) · [Agent Integration](#agent-integration) · [Search Modes](#search-modes)
107
107
 
108
- **Surfaces** · [Web UI](#web-ui) · [REST API](#rest-api) · [SDK](#sdk) · [Daemon Mode](#daemon-mode) · [Publish to gno.sh](#publish-to-gnosh)
108
+ **Surfaces** · [Web UI](#web-ui) · [Omarchy Plugin](#omarchy-plugin) · [REST API](#rest-api) · [SDK](#sdk) · [Daemon Mode](#daemon-mode) · [Publish to gno.sh](#publish-to-gnosh)
109
109
 
110
110
  **Under the hood** · [How It Works](#how-it-works) · [Features](#features) · [Local Models](#local-models) · [Fine-Tuned Models](#fine-tuned-models) · [Architecture](#architecture) · [Development](#development)
111
111
 
@@ -117,12 +117,25 @@ gno daemon --detach # headless indexing + resident MCP gateway
117
117
 
118
118
  <!-- public-truth:current-version -->
119
119
 
120
- > Current release: **v1.35.0** see [CHANGELOG.md](./CHANGELOG.md)
120
+ > Current release: **v1.36.1**. See [CHANGELOG.md](./CHANGELOG.md).
121
121
 
122
122
  <!-- /public-truth -->
123
123
 
124
124
  > Full release history: [CHANGELOG.md](./CHANGELOG.md)
125
125
 
126
+ - **Cheap peek snapshot**: `gno peek --json` and MCP `gno_peek` return a
127
+ model-free `peek@1.0` snapshot (document/collection counts, embedding backlog,
128
+ 10 recent docs with `docid` and `absPath`, pid-file serve detection).
129
+ Uninitialized reports `initialized:false` with pinned nulls and exit 0. Any
130
+ subquery failure is an atomic `RUNTIME` envelope (exit 2). `serve.running` is
131
+ true only for `gno serve --detach`. JSON search hits pin
132
+ `results[].source.absPath`. Open a document in the Web UI with
133
+ `{serveUrl}/doc?uri=<encodeURIComponent(uri)>`.
134
+ - **Prose-first snippets**: `gno search`, `gno vsearch`, and `gno query` skip
135
+ leading YAML frontmatter and prefer document prose. A frontmatter-dominated
136
+ FTS window falls back to stripped chunk prose. `--full` and `--line-numbers`
137
+ still emit raw source. `line` and `snippetRange.startLine` follow the trimmed
138
+ display range.
126
139
  - **Trustworthy local context compiler**: deterministic Context Capsules replace
127
140
  repeated agent `query → get → multi-get` orchestration with one bounded,
128
141
  citation-complete evidence handoff. The promoted benchmark retained 100%
@@ -544,7 +557,10 @@ Output formats: `--json`, `--files`, `--csv`, `--md`, `--xml`
544
557
  # Search one collection
545
558
  gno search "PostgreSQL connection pool" --collection work-docs
546
559
 
547
- # Export retrieval results for an agent
560
+ # Cheap index snapshot for a status bar or desktop widget
561
+ gno peek --json
562
+
563
+ # Export retrieval results for an agent (`source.absPath` on file-backed hits)
548
564
  gno query "authentication flow" --json -n 10
549
565
  gno query "deployment rollback" --all --files --min-score 0.4
550
566
 
@@ -645,30 +661,31 @@ Connect GNO to Claude Desktop, Cursor, Raycast, and more:
645
661
 
646
662
  ![GNO MCP](./assets/screenshots/mcp.jpg)
647
663
 
648
- GNO exposes 25 tools by default via [Model Context Protocol](https://modelcontextprotocol.io),
664
+ GNO exposes 33 tools by default via [Model Context Protocol](https://modelcontextprotocol.io),
649
665
  including the core retrieval tools below. Starting MCP with `--enable-write`
650
- adds 15 opt-in mutation tools, for 40 total.
651
-
652
- | Tool | Description |
653
- | :------------------- | :------------------------------------ |
654
- | `gno_search` | BM25 keyword search |
655
- | `gno_vsearch` | Vector semantic search |
656
- | `gno_query` | Hybrid search (recommended) |
657
- | `gno_context` | Budgeted exact evidence Capsule |
658
- | `gno_context_verify` | Verify saved Capsule provenance |
659
- | `gno_ask` | Opt-in closed-Capsule verified answer |
660
- | `gno_get` | Retrieve document by ID |
661
- | `gno_multi_get` | Batch document retrieval |
662
- | `gno_links` | Get outgoing links from document |
663
- | `gno_backlinks` | Get documents linking TO document |
664
- | `gno_similar` | Find semantically similar documents |
665
- | `gno_graph` | Get knowledge graph (nodes and edges) |
666
- | `gno_status` | Index health check |
667
- | `gno_trace_list` | List private local retrieval receipts |
668
- | `gno_trace_show` | Inspect one bounded trace receipt |
669
- | `gno_changes` | Read retained metadata-only changes |
670
- | `gno_diff` | Read one structural document delta |
671
- | `gno_impact` | Trace bounded dependency impact |
666
+ adds 18 opt-in mutation tools, for 51 total.
667
+
668
+ | Tool | Description |
669
+ | :------------------- | :---------------------------------------------- |
670
+ | `gno_search` | BM25 keyword search |
671
+ | `gno_vsearch` | Vector semantic search |
672
+ | `gno_query` | Hybrid search (recommended) |
673
+ | `gno_context` | Budgeted exact evidence Capsule |
674
+ | `gno_context_verify` | Verify saved Capsule provenance |
675
+ | `gno_ask` | Opt-in closed-Capsule verified answer |
676
+ | `gno_get` | Retrieve document by ID |
677
+ | `gno_multi_get` | Batch document retrieval |
678
+ | `gno_links` | Get outgoing links from document |
679
+ | `gno_backlinks` | Get documents linking TO document |
680
+ | `gno_similar` | Find semantically similar documents |
681
+ | `gno_graph` | Get knowledge graph (nodes and edges) |
682
+ | `gno_peek` | Cheap `peek@1.0` counts, backlog, recent, serve |
683
+ | `gno_status` | Index health check |
684
+ | `gno_trace_list` | List private local retrieval receipts |
685
+ | `gno_trace_show` | Inspect one bounded trace receipt |
686
+ | `gno_changes` | Read retained metadata-only changes |
687
+ | `gno_diff` | Read one structural document delta |
688
+ | `gno_impact` | Trace bounded dependency impact |
672
689
 
673
690
  **Design**: Default MCP mode is read-only: retrieval, opt-in verified synthesis,
674
691
  graph, status, and job inspection. Raw retrieval tools leave synthesis to your
@@ -792,6 +809,18 @@ gno.sh.
792
809
 
793
810
  ---
794
811
 
812
+ ## Omarchy Plugin
813
+
814
+ On [Omarchy](https://omarchy.org/), [**GNO Recall**](https://github.com/gmickel/omarchy-gno-recall) puts the index in your bar: a quiet health widget, an anchored panel with counts and recent documents, and a keyboard-first overlay (Super+R) for search, deep search, collection browsing, and opening documents.
815
+
816
+ ```bash
817
+ omarchy plugin add https://github.com/gmickel/omarchy-gno-recall --enable
818
+ ```
819
+
820
+ Requires gno >= 1.36.0 on `PATH`.
821
+
822
+ ---
823
+
795
824
  ## Publish to gno.sh
796
825
 
797
826
  GNO is local-first, but sometimes you want a URL to send someone. [**gno.sh**](https://gno.sh/publish) is the hosted reader on top of GNO — a polished, reading-first page for a single note or a whole collection, without mounting your vault or syncing anything.
Binary file
@@ -0,0 +1 @@
1
+ 497d8e90a34b7472eac55da861c3049b7453a33d9fdbd0a27e49f2ce36ccde9b gno-browser-clipper-v1.37.0.zip
@@ -21,5 +21,5 @@
21
21
  "content_security_policy": {
22
22
  "extension_pages": "script-src 'self'; object-src 'none'; connect-src http://127.0.0.1:*"
23
23
  },
24
- "version": "1.36.1"
24
+ "version": "1.37.0"
25
25
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gmickel/gno",
3
- "version": "1.36.1",
3
+ "version": "1.37.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",
@@ -65,6 +65,7 @@
65
65
  "test": "bun test",
66
66
  "test:web": "bun test test/serve/public --timeout 30000",
67
67
  "test:e2e": "bun scripts/web-ui-smoke.ts",
68
+ "bench:webui-first-page": "bun scripts/webui-first-page-load.ts",
68
69
  "test:e2e:clipper": "bun test test/clipper/e2e.test.ts --timeout 240000",
69
70
  "test:e2e:pdf": "bun scripts/pdf-viewer-smoke.ts",
70
71
  "smoke:pdf-viewer": "bun scripts/pdf-viewer-smoke.ts",
@@ -144,8 +145,9 @@
144
145
  "website:sync-assets": "bun scripts/sync-assets.ts",
145
146
  "sync:agents": "scripts/sync-agents.sh",
146
147
  "build:css": "tailwindcss -i src/serve/public/globals.css -o src/serve/public/globals.built.css --minify",
148
+ "build:spa": "bun scripts/build-spa-production.ts",
147
149
  "serve": "bun src/index.ts serve",
148
- "serve:dev": "NODE_ENV=development bun --hot src/index.ts serve",
150
+ "serve:dev": "bun --hot src/index.ts serve --dev",
149
151
  "version:patch": "npm version patch --no-git-tag-version",
150
152
  "version:minor": "npm version minor --no-git-tag-version",
151
153
  "version:major": "npm version major --no-git-tag-version",
package/spec/cli.md CHANGED
@@ -3335,16 +3335,26 @@ API share the listener; use `gno daemon` for authenticated non-loopback MCP.
3335
3335
  **Synopsis:**
3336
3336
 
3337
3337
  ```bash
3338
- gno serve [--port <num>] [gateway-options] [--detach] [--pid-file <path>] [--log-file <path>]
3338
+ gno serve [--port <num>] [--dev] [gateway-options] [--detach] [--pid-file <path>] [--log-file <path>]
3339
3339
  gno serve --status [--json]
3340
3340
  gno serve --stop
3341
3341
  ```
3342
3342
 
3343
+ Default `gno serve` (no `--dev`) is the production WebUI bundle, even when
3344
+ `NODE_ENV` is unset. Production serve loads the committed SPA snapshot
3345
+ (`assets/spa-production.json.gz`) so first listen does not wait on `Bun.build`.
3346
+ Refresh the snapshot with `bun scripts/build-spa-production.ts`. `--dev` is the
3347
+ operator switch for the development bundle and HMR (`serve:dev` / `bun --hot`
3348
+ remain the hot-reload path). `--dev` does not apply to `--status` or `--stop`
3349
+ (those paths do not boot the UI). A detached child inherits the same
3350
+ production default unless the parent was started with `--dev`.
3351
+
3343
3352
  **Options:**
3344
3353
 
3345
3354
  | Option | Type | Default | Description |
3346
3355
  | ---------------------- | ------- | ------------------------ | ---------------------------------------------------------------- |
3347
3356
  | `-p, --port` | number | 3000 | Port to listen on |
3357
+ | `--dev` | boolean | false | Serve the development bundle with HMR (default is production) |
3348
3358
  | `--detach` | boolean | false | Self-spawn a detached child; parent prints `{pid,url}` and exits |
3349
3359
  | `--pid-file <path>` | string | `{data}/serve.pid` | Override pid-file location (JSON metadata, absolute path) |
3350
3360
  | `--log-file <path>` | string | `{data}/serve.log` | Override log-file location (append mode) |
@@ -3423,6 +3433,7 @@ is blocked.
3423
3433
  ```bash
3424
3434
  gno serve
3425
3435
  gno serve --port 8080
3436
+ gno serve --dev
3426
3437
 
3427
3438
  # Backgrounding
3428
3439
  gno serve --detach
@@ -4164,12 +4164,29 @@ function wireServeCommand(program: Command): void {
4164
4164
  "internal detached-child marker"
4165
4165
  ).hideHelp()
4166
4166
  );
4167
+ serveCmd.addOption(
4168
+ new Option(
4169
+ "--dev",
4170
+ "serve the development bundle with HMR (default: production)"
4171
+ )
4172
+ );
4167
4173
 
4168
4174
  serveCmd.action(async (cmdOpts: Record<string, unknown>, cmd: Command) => {
4169
4175
  await handleServeAction(cmdOpts, cmd);
4170
4176
  });
4171
4177
  }
4172
4178
 
4179
+ /** CLI serve bundle mode. Default `gno serve` is production unless `--dev`. */
4180
+ export function resolveServeNodeEnv(
4181
+ dev: boolean
4182
+ ): "development" | "production" {
4183
+ return dev ? "development" : "production";
4184
+ }
4185
+
4186
+ function applyServeBundleEnv(dev: boolean): void {
4187
+ process.env.NODE_ENV = resolveServeNodeEnv(dev);
4188
+ }
4189
+
4173
4190
  // ─────────────────────────────────────────────────────────────────────────────
4174
4191
  // Serve lifecycle branching (detach / status / stop / detached-child / fg)
4175
4192
  // ─────────────────────────────────────────────────────────────────────────────
@@ -4238,6 +4255,9 @@ async function handleServeAction(
4238
4255
  return;
4239
4256
  }
4240
4257
 
4258
+ const dev = cmdOpts.dev === true;
4259
+ applyServeBundleEnv(dev);
4260
+
4241
4261
  if (cmdOpts.detach) {
4242
4262
  const port = parsePositiveInt("port", cmdOpts.port);
4243
4263
  await runServeDetach({
@@ -4245,6 +4265,7 @@ async function handleServeAction(
4245
4265
  paths,
4246
4266
  spawnDetached,
4247
4267
  argv: resolveCliArgv(cmd),
4268
+ nodeEnv: resolveServeNodeEnv(dev),
4248
4269
  });
4249
4270
  return;
4250
4271
  }
@@ -4423,6 +4444,7 @@ interface ServeDetachDeps {
4423
4444
  * can't taint each other's child argv.
4424
4445
  */
4425
4446
  argv: string[];
4447
+ nodeEnv: "development" | "production";
4426
4448
  }
4427
4449
 
4428
4450
  async function runServeDetach(deps: ServeDetachDeps): Promise<void> {
@@ -4432,6 +4454,7 @@ async function runServeDetach(deps: ServeDetachDeps): Promise<void> {
4432
4454
  const result = await deps.spawnDetached({
4433
4455
  kind: "serve",
4434
4456
  argv: childArgv,
4457
+ env: { NODE_ENV: deps.nodeEnv },
4435
4458
  pidFile: deps.paths.pidFile,
4436
4459
  logFile: deps.paths.logFile,
4437
4460
  port: deps.port,
@@ -1,9 +1,14 @@
1
- import { useCallback, useEffect, useMemo, useState } from "react";
1
+ import {
2
+ lazy,
3
+ Suspense,
4
+ useCallback,
5
+ useEffect,
6
+ useMemo,
7
+ useState,
8
+ } from "react";
2
9
  import { createRoot } from "react-dom/client";
3
10
 
4
11
  import { HelpButton } from "./components/HelpButton";
5
- import { QuickSwitcher } from "./components/QuickSwitcher";
6
- import { ShortcutHelpModal } from "./components/ShortcutHelpModal";
7
12
  import { WorkspaceTabs } from "./components/WorkspaceTabs";
8
13
  import { CaptureModalProvider, useCaptureModal } from "./hooks/useCaptureModal";
9
14
  import { useKeyboardShortcuts } from "./hooks/useKeyboardShortcuts";
@@ -21,17 +26,28 @@ import {
21
26
  updateActiveTabBrowseState,
22
27
  type WorkspaceState,
23
28
  } from "./lib/workspace-tabs";
24
- import Ask from "./pages/Ask";
25
- import Browse from "./pages/Browse";
26
29
  import ClipperPairing from "./pages/ClipperPairing";
27
- import Collections from "./pages/Collections";
28
- import Connectors from "./pages/Connectors";
29
30
  import Dashboard from "./pages/Dashboard";
30
- import DocumentEditor from "./pages/DocumentEditor";
31
- import DocView from "./pages/DocView";
32
- import GraphView from "./pages/GraphView";
33
- import Search from "./pages/Search";
34
- import TraceHistory from "./pages/TraceHistory";
31
+
32
+ const QuickSwitcher = lazy(() =>
33
+ import("./components/QuickSwitcher").then((module) => ({
34
+ default: module.QuickSwitcher,
35
+ }))
36
+ );
37
+ const ShortcutHelpModal = lazy(() =>
38
+ import("./components/ShortcutHelpModal").then((module) => ({
39
+ default: module.ShortcutHelpModal,
40
+ }))
41
+ );
42
+ const Search = lazy(() => import("./pages/Search"));
43
+ const Browse = lazy(() => import("./pages/Browse"));
44
+ const DocView = lazy(() => import("./pages/DocView"));
45
+ const DocumentEditor = lazy(() => import("./pages/DocumentEditor"));
46
+ const Collections = lazy(() => import("./pages/Collections"));
47
+ const Connectors = lazy(() => import("./pages/Connectors"));
48
+ const Ask = lazy(() => import("./pages/Ask"));
49
+ const GraphView = lazy(() => import("./pages/GraphView"));
50
+ const TraceHistory = lazy(() => import("./pages/TraceHistory"));
35
51
 
36
52
  type Route =
37
53
  | "/"
@@ -155,7 +171,9 @@ function AppContent({
155
171
  tabs={workspace.tabs}
156
172
  />
157
173
  <div className="flex-1">
158
- <Page key={pageKey} location={location} navigate={navigate} />
174
+ <Suspense fallback={null}>
175
+ <Page key={pageKey} location={location} navigate={navigate} />
176
+ </Suspense>
159
177
  </div>
160
178
  <footer className="border-t border-border/30 bg-background/60 py-6 text-center text-sm backdrop-blur-sm">
161
179
  <div className="ornament mx-auto mb-4 max-w-[8rem] text-muted-foreground/20">
@@ -217,17 +235,19 @@ function AppContent({
217
235
  </footer>
218
236
  </div>
219
237
  <HelpButton onClick={() => setShortcutHelpOpen(true)} />
220
- <QuickSwitcher
221
- location={location}
222
- navigate={(to) => navigate(to)}
223
- onCreateNote={openCapture}
224
- onOpenChange={setQuickSwitcherOpen}
225
- open={quickSwitcherOpen}
226
- />
227
- <ShortcutHelpModal
228
- onOpenChange={setShortcutHelpOpen}
229
- open={shortcutHelpOpen}
230
- />
238
+ <Suspense fallback={null}>
239
+ <QuickSwitcher
240
+ location={location}
241
+ navigate={(to) => navigate(to)}
242
+ onCreateNote={openCapture}
243
+ onOpenChange={setQuickSwitcherOpen}
244
+ open={quickSwitcherOpen}
245
+ />
246
+ <ShortcutHelpModal
247
+ onOpenChange={setShortcutHelpOpen}
248
+ open={shortcutHelpOpen}
249
+ />
250
+ </Suspense>
231
251
  </>
232
252
  );
233
253
  }
@@ -33,14 +33,19 @@ const CodeBlockContext = createContext<CodeBlockContextType>({
33
33
  code: "",
34
34
  });
35
35
 
36
- const highlighterPromise = createHighlighter({
37
- engine: createJavaScriptRegexEngine(),
38
- langs: ["text"],
39
- themes: ["one-light", "one-dark-pro"],
40
- });
36
+ let highlighterPromise: ReturnType<typeof createHighlighter> | null = null;
37
+
38
+ function getHighlighterRuntime(): ReturnType<typeof createHighlighter> {
39
+ highlighterPromise ??= createHighlighter({
40
+ engine: createJavaScriptRegexEngine(),
41
+ langs: ["text"],
42
+ themes: ["one-light", "one-dark-pro"],
43
+ });
44
+ return highlighterPromise;
45
+ }
41
46
 
42
47
  async function getHighlighter(language: BundledLanguage | "text") {
43
- const highlighter = await highlighterPromise;
48
+ const highlighter = await getHighlighterRuntime();
44
49
  if (!highlighter.getLoadedLanguages().includes(language)) {
45
50
  await highlighter.loadLanguage(language);
46
51
  }
@@ -2,7 +2,6 @@
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
- <base href="/" />
6
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
6
  <meta name="referrer" content="no-referrer" />
8
7
  <title>GNO - Local Knowledge Index</title>
@@ -1,4 +1,6 @@
1
- import { bundledLanguages, type BundledLanguage } from "shiki";
1
+ import type { BundledLanguage } from "shiki";
2
+
3
+ import { BUNDLED_LANGUAGE_IDS } from "./shiki-language-ids";
2
4
 
3
5
  export type CodeLanguage = BundledLanguage | "text";
4
6
 
@@ -41,7 +43,7 @@ export function resolveCodeLanguage(
41
43
  return "text";
42
44
  }
43
45
 
44
- if (aliased in bundledLanguages) {
46
+ if (BUNDLED_LANGUAGE_IDS.has(aliased)) {
45
47
  return aliased as BundledLanguage;
46
48
  }
47
49