An agent-friendly CLI for read-only DEVONthink access with optional semantic indexing and citation-key mapping.
- Read-only DEVONthink operations (search/read/browse)
- Keyword retrieval via DEVONthink, semantic retrieval via the local vector index, and related-document lookup
- Citation key mapping via bibliography JSON (
file -> id) - Default JSON output for easy AI-agent integration
- Configurable index directory per command (
--index-dir)
- macOS with DEVONthink 4.2+
- Node.js 20+
- Gemini, OpenAI, or OpenAI-compatible embeddings for
dtx semantic databases/groups/documentscommands anddtx keyworddo not require embedding API keys
brew install tombener/tap/dtxThen run:
dtx helpnpm install
npm run build
npm linkdtx now supports a global config file at:
~/.dtx/config.jsonPriority order for user-facing configuration is:
- CLI flags
- Environment variables
~/.dtx/config.json
Example:
{
"defaultGroupUuid": "33203673-B7E2-4F3F-9D87-6E83EB4781EA",
"indexDir": "~/Library/CloudStorage/Dropbox/bibliography/dtx-index",
"bibliographyJsonPath": "~/Library/CloudStorage/Dropbox/bibliography/bibliography.json",
"embeddingProvider": "gemini",
"embeddingModel": "gemini-embedding-001",
"googleApiKey": "..."
}stdout: JSON responsestderr: progress logs (for long-running operations like indexing)
Success shape:
{
"ok": true,
"data": {},
"meta": {
"elapsedMs": 123
}
}Error shape:
{
"ok": false,
"error": {
"code": "MISSING_ARGUMENT",
"message": "..."
},
"meta": {}
}dtx version
dtx doctor [--index-dir <path>]
dtx keyword --query "<q>" [--database <name>] [--group <uuid>] [--limit <n>] [--abstract]
dtx semantic [--query "<q>"] [--database <name>] [--group <uuid>] [--limit <n>] [--per-doc <n>] [--context] [--debug] [--index-dir <path>] [--citation-key <key>] [--uuid <recordUuid>]
dtx databases list
dtx groups list [--uuid <groupUuid>] [--limit <n>]
dtx documents get (--uuid <recordUuid> | --citation-key <key>) [--max-length <n>] [--bib <path>]
dtx documents citation-key --uuid <recordUuid> [--bib <path>]
dtx documents related --uuid <recordUuid> [--limit <n>]
dtx index build [--database <name>] [--group <uuid>] [--include-md] [--force] [--bib <path>] [--index-dir <path>] [--content-max-length <n>]
dtx index status [--index-dir <path>]There are two top-level retrieval commands:
1. DEVONthink native retrieval (dtx keyword)
Passes the query directly to DEVONthink's search engine and returns document-level results. Supports all DEVONthink search operators: NEAR, AND, OR, NOT, wildcards, field qualifiers (name:, tag:, etc.), and parentheses. Use this for coarse filtering or when you need operator-based queries.
- Results include
pathand, when resolvable from bibliography JSON,citationKey,author, andyear dtx keyworddefaults to the configured Zotero group scope unless you pass--databaseor--group--limitdefaults to10- Abstracts are included in results by default;
--abstractis accepted for explicitness dtx documents getaccepts either a DEVONthink--uuidor a bibliographic--citation-keydtx documents citation-key --uuid <recordUuid>resolves the mapped citation key without reading document content- Bibliographic enrichment is sourced from
bibliography.json
2. Local index retrieval (dtx semantic)
Queries the local vector index built by dtx index build and returns passage-level results. It embeds the query and performs cosine similarity search, then re-ranks with lexical signals.
-
dtx semanticdefaults to the configured Zotero group scope unless you pass--databaseor--group -
--limitdefaults to10 -
--per-docdefaults to2 -
It supports
--uuid <recordUuid>and--citation-key <key>to scope retrieval to a known document -
With no query plus
--uuid/--citation-key, it returns the document's indexed passages in order -
dtx semanticrequires an embedding API key at query time -
dtx documents relatedUses DEVONthinkSee Also/compare()and returns related documents for one known UUID.
flowchart TD
Q["Query / UUID"] --> D["keyword"]
D --> D1["DEVONthink native search\n(supports NEAR / Boolean / wildcards)"]
D1 --> D2["Document results"]
Q --> PS["semantic"]
PS --> PS1["Embed query"]
PS1 --> PS2["Cosine similarity search\nover local vector index"]
PS2 --> PS3["Re-rank with lexical signals"]
PS3 --> PP
PP["Merge adjacent passages\n& build excerpt"] --> PR["Passage results"]
Q --> R["documents related"]
R --> R1["DEVONthink compare / See Also"]
R1 --> R2["Related document results"]
Priority order:
--index-dir <path>DT_INDEX_DIR(env)~/.dtx/config.json:indexDir
Index files:
vectors.binchunks.jsonmeta.jsonchunks.001.json,chunks.002.json, ... (auto-generated chunk shards)
dtx index build \
--database Inbox \
--bib ~/Library/CloudStorage/Dropbox/bibliography/bibliography.json \
--index-dir ~/Library/CloudStorage/Dropbox/bibliography/dtx-indexDefaults for dtx index build:
- Group UUID: configure via
~/.dtx/config.json:defaultGroupUuidorDT_DEFAULT_GROUP_UUID - Database: omitted by default; passing
--databasedisables the configured group unless--groupis also provided - Bibliography path: configure via
~/.dtx/config.json:bibliographyJsonPathorBIBLIOGRAPHY_JSON_PATH - Markdown files are excluded unless
--include-mdis provided --content-max-lengthdefaults to no truncation (0also means no truncation)- Semantic chunking defaults to
800chars with120chars of overlap - Chunk metadata shard size defaults to
10000
dtx semantic requires a local index (dtx index build):
- By default, semantic retrieval uses the configured group from
~/.dtx/config.jsonorDT_DEFAULT_GROUP_UUID - By default, results return only
excerpt; pass--contextto also includecontextText - By default, at most 2 passages per document are returned; use
--per-doc <n>to change (0 for no cap) - Pass
--debugto include internal ranking and passage-location fields - Results are post-processed into short excerpts, with adjacent hits merged
- Pass
--uuid <recordUuid>or--citation-key <key>without a query to read that document as consecutive passages - Pass
--uuid <recordUuid>or--citation-key <key>with a query to search only within that document
Set env vars in your shell/profile (or pass inline per command), or put equivalent values in ~/.dtx/config.json. Important ones:
EMBEDDING_PROVIDEREMBEDDING_MODELEMBEDDING_DIMENSIONSGOOGLE_API_KEY(whenEMBEDDING_PROVIDER=gemini)OPENAI_API_KEY(whenEMBEDDING_PROVIDER=openai)OPENAI_BASE_URLorOPENAI_COMPATIBLE_BASE_URL(whenEMBEDDING_PROVIDER=openai-compatible)OPENAI_COMPATIBLE_API_KEY(optional override whenEMBEDDING_PROVIDER=openai-compatible)BIBLIOGRAPHY_JSON_PATHDT_INDEX_DIRDT_DEFAULT_GROUP_UUIDLIST_ALL_RECORDS_TIMEOUT_MSINDEX_CRAWL_HEARTBEAT_MSCHUNK_MAX_CHARSCHUNK_OVERLAP_CHARSCHUNK_MIN_CHARSCHUNK_SHARD_SIZE
dtx does not read .env files automatically. Use dtx doctor to distinguish:
- whether the current
process.envalready contains the required keys - whether a
.envfile exists in the current working directory - whether semantic search is actually runnable right now
Example:
export EMBEDDING_PROVIDER=gemini
export GOOGLE_API_KEY=your_key
export BIBLIOGRAPHY_JSON_PATH="$HOME/Library/CloudStorage/Dropbox/bibliography/bibliography.json"
export DT_INDEX_DIR="$HOME/Library/CloudStorage/Dropbox/bibliography/dtx-index"
export DT_DEFAULT_GROUP_UUID="33203673-B7E2-4F3F-9D87-6E83EB4781EA"OpenAI-compatible example:
export EMBEDDING_PROVIDER=openai-compatible
export OPENAI_BASE_URL="http://localhost:11434/v1"
export OPENAI_API_KEY="dtx"
export EMBEDDING_MODEL="text-embedding-3-small"
# Set this when the model dimensions are not one of dtx's built-in defaults
export EMBEDDING_DIMENSIONS=1536# Use DEVONthink operators for document-level retrieval
dtx keyword --query "rural idyll NEAR gentrification" --limit 10
# Semantic retrieval over the local index
dtx semantic --query "pastoral nostalgia urban escape" --limit 8
# Read a specific paper by citation key from the semantic index
dtx semantic --citation-key "shucksmith2018rrr" --limit 20
# Resolve a citation key from a DEVONthink UUID
dtx documents citation-key \
--uuid "A1B2C3D4-E5F6-7890-1234-56789ABCDEF0" \
--bib ~/Library/CloudStorage/Dropbox/bibliography/bibliography.jsonAll DEVONthink operations are read-only.
MIT License. See LICENSE for details.