Guides
Index mail, calendar, transcript, browser, and JSONL exports as individually searchable records, with no account access and no live connectors.
An mbox file is not one document. It is four thousand messages that happen to share a file, and indexing it as a single blob makes every one of them unfindable. The same is true of a calendar export, a subtitle file, and a JSONL dump.
Export adapters split those containers into logical records: one message, one event, one cue, one bookmark, one row. Each becomes independently searchable and independently citable, while keeping its export path, exact source locator, dates, participants, thread or session identity, attachment inventory, and anchors.
The boundary is deliberate and worth stating up front. These are file adapters, not connectors. They read export files you produced and placed somewhere. They do not authenticate to an account, inspect a live browser profile database or cookie store, fetch a URL, open an attachment, execute embedded content, or unpack an archive. If you want yesterday’s mail indexed, you export yesterday’s mail.
| Source | File types | One record is | Notes |
|---|---|---|---|
| JSON Lines | .jsonl, .ndjson | One valid object per line | Optional declarative field mapping; a malformed line is isolated rather than failing the file |
.eml, .mbox | One message | Bounded MIME nesting, body, and attachment counts; attachments are inventoried, never opened or indexed | |
| Calendar | .ics | One VEVENT or recurrence exception | Timezone-aware; recurrence anchors expand over a bounded local horizon rather than to infinity |
| Transcripts | .vtt, .srt | One cue or segment | Speaker and timestamp anchors retained |
| Browser exports | .browser-export | One bookmark, history, or reading-list item | Must contain a recognized export shape; live profile databases are rejected outright |
| Explicit transcript exports | Configured .json or .txt | One segment or record | Opt-in only: generic JSON and text stay ordinary documents unless you declare otherwise |
Markdown, PDF, Office, plain text, and source code continue through the existing one-file-one-document converter lane. Nothing about adapters changes how those are handled.
For the automatic formats there is no configuration: add the export file or directory to a collection and index it.
gno collection add ~/exports --name exports \
--pattern "**/*.{jsonl,eml,mbox,ics,vtt,srt}"
gno updateCollection include stays an extension allowlist: when it is non-empty, list the extensions you want. With the default empty include, an explicitly configured JSON transcript adapter automatically adds .json to the supported-extension scan.
GNO recognises conventional id, title, text/content/body, and author names. When your export uses different ones, declare a closed mapping:
collections:
- name: exports
path: /Users/me/exports
pattern: "**/*"
include: [.jsonl, .eml, .mbox, .ics, .vtt, .srt, .browser-export]
recordAdapters:
jsonl:
fieldMapping:
id: /external_id
title: /subject
body: /payload/text
author: /owner/name
participants: /participants
threadId: /thread_id
dateFields:
created: /created_atSelectors are JSON Pointers, or ordered arrays of JSON Pointers when a field lives in different places across rows. They are data, not code: a selector cannot execute, traverse prototypes, read a file, or make a network request.
recordAdapters:
transcript:
format: json # json, text, vtt, or srtGeneric JSON and text are never guessed as transcripts. Heuristic detection here would misclassify ordinary config and data files, so it is per-collection opt-in.
Each record gets an opaque key derived from the adapter identity plus the record’s stable export identity. Reimport with the same key and source hash is a no-op; a changed source hash updates the existing virtual document in place.
Deletion is where export ingestion usually goes wrong, so GNO is strict. A complete snapshot deactivates records that disappeared from it. A partial snapshot never does. A malformed row, a truncated file, invalid framing, or any cap failure marks the import partial, and a damaged export is therefore incapable of authorizing deletion. A half-written mbox cannot wipe your mail history from the index.
For JSONL, configure fieldMapping.id or provide a conventional id when updates must preserve identity. Without one, identity is derived from the row’s canonical content, so editing a row appears as one removal plus one addition rather than an in-place update. That is correct behaviour for content-addressed rows, but it will surprise you if you expected stable IDs, so set one.
Search and gno get report the real container path in source.relPath, plus a record object with the bounded locator and metadata. The unique gno:// URI addresses GNO’s internal virtual document: use that URI or its docid with gno get.
Ask and Context Capsules carry the same record metadata alongside exact canonical-mirror line spans, so a claim can cite “message 14 of this mbox, lines 4 to 9” and be verified later. record.adapter holds the adapter ID, version, and configuration fingerprint that produced the record, which is what lets you tell “the export changed” apart from “my mapping changed”.
Virtual documents live under the reserved .gno/records/ URI namespace, and a physical directory of that name is always excluded from collection walking so real files can never collide with the reserved space.