search
Find documents and records in DEVONthink databases using flexible search parameters including query terms, group scoping, record type filters, and comparison modes.
Instructions
Search DEVONthink records. Examples: {"query": "invoice"} or {"query": "project review", "groupPath": "/Meetings", "databaseName": "MyDB"}. Note: groupPath requires databaseName and must be database-relative (e.g., "/Meetings" not "/MyDB/Meetings").
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Search query (wildcards * and ? are supported) | |
| groupUuid | No | UUID of the group to search within | |
| groupId | No | Numeric ID of the group to search within (requires databaseName) | |
| groupPath | No | Database-relative path of the group to search within (requires databaseName) | |
| databaseName | No | Database name (required for groupId or groupPath scoping) | |
| useCurrentGroup | No | Scope search to the currently selected group in DEVONthink | |
| recordType | No | Filter results to a specific record type | |
| comparison | No | Comparison mode for the search | |
| excludeSubgroups | No | Exclude records from subgroups when scoping to a group | |
| limit | No | Maximum number of results to return |
Implementation Reference
- src/tools/search/search.ts:40-169 (handler)The search tool is defined and implemented here, including its input schema (zod validation) and the run handler which executes JXA script for DEVONthink.
export const searchTool = defineTool({ name: "search", description: 'Search DEVONthink records. Examples: {"query": "invoice"} or ' + '{"query": "project review", "groupPath": "/Meetings", "databaseName": "MyDB"}. ' + "Note: groupPath requires databaseName and must be database-relative " + '(e.g., "/Meetings" not "/MyDB/Meetings").', schema: z.object({ query: z.string().describe("Search query (wildcards * and ? are supported)"), groupUuid: z .string() .optional() .describe("UUID of the group to search within"), groupId: z .number() .int() .nonnegative() .optional() .describe("Numeric ID of the group to search within (requires databaseName)"), groupPath: z .string() .optional() .describe( "Database-relative path of the group to search within (requires databaseName)" ), databaseName: z .string() .optional() .describe("Database name (required for groupId or groupPath scoping)"), useCurrentGroup: z .boolean() .optional() .describe("Scope search to the currently selected group in DEVONthink"), recordType: z .enum(RECORD_TYPE_VALUES) .optional() .describe("Filter results to a specific record type"), comparison: z .enum(COMPARISON_VALUES) .optional() .describe("Comparison mode for the search"), excludeSubgroups: z .boolean() .optional() .describe("Exclude records from subgroups when scoping to a group"), limit: z .number() .int() .positive() .optional() .describe("Maximum number of results to return"), }), run: async (args, executor) => { const { query, groupUuid, groupId, groupPath, databaseName, useCurrentGroup, recordType, comparison, excludeSubgroups, limit, } = args; const script = ` ${JXA_APP} var query = ${jxaLiteral(query)}; var groupUuid = ${jxaLiteral(groupUuid ?? null)}; var groupId = ${jxaLiteral(groupId ?? null)}; var groupPath = ${jxaLiteral(groupPath ?? null)}; var dbName = ${jxaLiteral(databaseName ?? null)}; var useCurrentGroup = ${jxaLiteral(useCurrentGroup ?? false)}; var recordType = ${jxaLiteral(recordType ?? null)}; var comparison = ${jxaLiteral(comparison ?? null)}; var excludeSubgroups = ${jxaLiteral(excludeSubgroups ?? false)}; var limit = ${jxaLiteral(limit ?? null)}; ${JXA_RESOLVE_DB} // Resolve the search scope group if specified var searchIn = null; if (groupUuid) { searchIn = app.getRecordWithUuid(groupUuid); if (!searchIn || !searchIn.uuid()) throw new Error("Group not found for UUID: " + groupUuid); } else if (typeof groupId === "number" && groupId >= 0) { if (!db) throw new Error("databaseName is required when using groupId"); searchIn = db.getRecordAt(groupId); if (!searchIn || !searchIn.uuid()) throw new Error("Group not found for ID: " + groupId); } else if (groupPath) { if (!db) throw new Error("databaseName is required when using groupPath"); searchIn = app.getRecordAt(groupPath, {in: db}); if (!searchIn || !searchIn.uuid()) throw new Error("Group not found at path: " + groupPath); } else if (useCurrentGroup) { searchIn = app.currentGroup(); } // Build search options var searchOpts = {}; if (searchIn) searchOpts["in"] = searchIn; else if (db) searchOpts["in"] = db.root(); if (comparison) searchOpts["comparison"] = comparison; if (excludeSubgroups) searchOpts["excludeSubgroups"] = true; // Execute search var results = app.search(query, searchOpts); // Filter by record type if specified if (recordType) { results = results.filter(function(r) { return r.type() === recordType; }); } // Apply limit if (limit && limit > 0 && results.length > limit) { results = results.slice(0, limit); } // Map to summaries var summaries = results.map(function(record) { return ${JXA_RECORD_SUMMARY}; }); JSON.stringify(summaries); `; const result = executor.run(script); return JSON.parse(result.stdout); }, });