@@ -13,13 +13,22 @@ const GetRecordByIdentifierSchema = z
1313 uuid : z . string ( ) . optional ( ) . describe ( "UUID of the record" ) ,
1414 id : z . number ( ) . optional ( ) . describe ( "ID of the record (requires databaseName)" ) ,
1515 databaseName : z . string ( ) . optional ( ) . describe ( "Database name (required with id)" ) ,
16+ referenceURL : z
17+ . string ( )
18+ . optional ( )
19+ . describe (
20+ "A x-devonthink-item:// URL. Works for all record types including imported emails which use non-UUID identifiers." ,
21+ ) ,
1622 } )
1723 . strict ( )
1824 . refine (
1925 ( data ) =>
20- data . uuid !== undefined || ( data . id !== undefined && data . databaseName !== undefined ) ,
26+ data . referenceURL !== undefined ||
27+ data . uuid !== undefined ||
28+ ( data . id !== undefined && data . databaseName !== undefined ) ,
2129 {
22- message : "Either UUID alone, or ID with databaseName must be provided" ,
30+ message :
31+ "Either referenceURL alone, UUID alone, or ID with databaseName must be provided" ,
2332 } ,
2433 ) ;
2534
@@ -37,6 +46,7 @@ interface RecordResult {
3746 recordType : string ;
3847 kind : string ;
3948 database : string ;
49+ referenceURL : string ;
4050 creationDate ?: string ;
4151 modificationDate ?: string ;
4252 tags ?: string [ ] ;
@@ -47,7 +57,7 @@ interface RecordResult {
4757}
4858
4959const getRecordByIdentifier = async ( input : GetRecordByIdentifierInput ) : Promise < RecordResult > => {
50- const { uuid, id, databaseName } = input ;
60+ const { uuid, id, databaseName, referenceURL } = input ;
5161
5262 // Validate string inputs
5363 if ( uuid && ! isJXASafeString ( uuid ) ) {
@@ -56,90 +66,139 @@ const getRecordByIdentifier = async (input: GetRecordByIdentifierInput): Promise
5666 if ( databaseName && ! isJXASafeString ( databaseName ) ) {
5767 return { success : false , error : "Database name contains invalid characters" } ;
5868 }
69+ if ( referenceURL && ! isJXASafeString ( referenceURL ) ) {
70+ return { success : false , error : "Reference URL contains invalid characters" } ;
71+ }
5972
6073 const script = `
6174 (() => {
6275 const theApp = Application("DEVONthink");
6376 theApp.includeStandardAdditions = true;
64-
77+
6578 // Inject helper functions
6679 ${ getRecordLookupHelpers ( ) }
6780 ${ getDatabaseHelper }
68-
81+
6982 try {
7083 let targetRecord;
7184 let targetDatabase;
7285 let lookupResult;
73-
74- if (${ uuid ? `"${ escapeStringForJXA ( uuid ) } "` : "null" } ) {
86+
87+ if (${ referenceURL ? `"${ escapeStringForJXA ( referenceURL ) } "` : "null" } ) {
88+ // Reference URL lookup (x-devonthink-item:// URLs)
89+ const refURL = ${ referenceURL ? `"${ escapeStringForJXA ( referenceURL ) } "` : "null" } ;
90+ const prefix = "x-devonthink-item://";
91+
92+ // Extract the identifier part after the prefix
93+ const identifier = refURL.startsWith(prefix) ? refURL.substring(prefix.length) : refURL;
94+
95+ // Check if it looks like a UUID (hex digits and hyphens)
96+ const uuidPattern = /^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/;
97+
98+ if (uuidPattern.test(identifier)) {
99+ // Fast path: standard UUID format
100+ targetRecord = theApp.getRecordWithUuid(identifier);
101+ }
102+
103+ if (!targetRecord) {
104+ // Non-UUID format (e.g. imported emails) or UUID lookup failed.
105+ // Search all open databases for a record whose referenceURL matches.
106+ const databases = theApp.databases();
107+ for (let i = 0; i < databases.length; i++) {
108+ const db = databases[i];
109+ const results = theApp.lookupRecordsWithURL(refURL, { "in": db });
110+ if (results && results.length > 0) {
111+ // lookupRecordsWithURL matches the url property;
112+ // verify by checking referenceURL
113+ for (let j = 0; j < results.length; j++) {
114+ if (results[j].referenceURL() === refURL) {
115+ targetRecord = results[j];
116+ break;
117+ }
118+ }
119+ if (targetRecord) break;
120+ }
121+ }
122+ }
123+
124+ if (!targetRecord) {
125+ return JSON.stringify({
126+ success: false,
127+ error: "Record not found for reference URL: " + refURL
128+ });
129+ }
130+
131+ targetDatabase = targetRecord.database();
132+
133+ } else if (${ uuid ? `"${ escapeStringForJXA ( uuid ) } "` : "null" } ) {
75134 // UUID lookup - globally unique
76- const lookupOptions = {
77- uuid: ${ uuid ? `"${ escapeStringForJXA ( uuid ) } "` : "null" }
78- };
79-
135+ const lookupOptions = {};
136+ lookupOptions["uuid"] = ${ uuid ? `"${ escapeStringForJXA ( uuid ) } "` : "null" } ;
137+
80138 lookupResult = getRecord(theApp, lookupOptions);
81-
139+
82140 if (!lookupResult.record) {
83141 return JSON.stringify({
84142 success: false,
85143 error: "Record with UUID " + (${ uuid ? `"${ escapeStringForJXA ( uuid ) } "` : "null" } || "unknown") + " not found"
86144 });
87145 }
88-
146+
89147 targetRecord = lookupResult.record;
90148 // Get the database of the record
91149 targetDatabase = targetRecord.database();
92-
150+
93151 } else if (${ id !== undefined ? id : "null" } && ${ databaseName ? `"${ escapeStringForJXA ( databaseName ) } "` : "null" } ) {
94152 // ID + Database lookup
95153 targetDatabase = getDatabase(theApp, ${ databaseName ? `"${ escapeStringForJXA ( databaseName ) } "` : "null" } );
96-
97- const lookupOptions = {
98- id: ${ id } ,
99- database: targetDatabase
100- };
101-
154+
155+ const lookupOptions = {};
156+ lookupOptions["id"] = ${ id } ;
157+ lookupOptions["database"] = targetDatabase;
158+
102159 lookupResult = getRecord(theApp, lookupOptions);
103-
160+
104161 if (!lookupResult.record) {
105162 return JSON.stringify({
106163 success: false,
107164 error: "Record with ID " + ${ id } + " not found in database '" + (${ databaseName ? `"${ escapeStringForJXA ( databaseName ) } "` : "null" } || "unknown") + "'"
108165 });
109166 }
110-
167+
111168 targetRecord = lookupResult.record;
112169 }
113-
170+
114171 // Extract record properties
115- const record = {
116- id: targetRecord.id(),
117- uuid: targetRecord.uuid(),
118- name: targetRecord.name(),
119- path: targetRecord.path(),
120- location: targetRecord.location(),
121- recordType: targetRecord.recordType(),
122- kind: targetRecord.kind(),
123- database: targetDatabase.name(),
124- creationDate: targetRecord.creationDate() ? targetRecord.creationDate().toString() : null,
125- modificationDate: targetRecord.modificationDate () ? targetRecord.modificationDate ().toString() : null,
126- tags: targetRecord.tags(),
127- size: targetRecord.size()
128- } ;
129-
172+ const record = {};
173+ record["id"] = targetRecord.id();
174+ record[" uuid"] = targetRecord.uuid();
175+ record[" name"] = targetRecord.name();
176+ record[" path"] = targetRecord.path();
177+ record[" location"] = targetRecord.location();
178+ record[" recordType"] = targetRecord.recordType();
179+ record[" kind"] = targetRecord.kind();
180+ record[" database"] = targetDatabase.name();
181+ record["referenceURL"] = targetRecord.referenceURL();
182+ record["creationDate"] = targetRecord.creationDate () ? targetRecord.creationDate ().toString() : null;
183+ record["modificationDate"] = targetRecord.modificationDate() ? targetRecord.modificationDate().toString() : null;
184+ record["tags"] = targetRecord.tags();
185+ record["size"] = targetRecord.size() ;
186+
130187 // Add optional properties if available
131- if (targetRecord.url && targetRecord.url()) {
132- record.url = targetRecord.url();
133- }
134- if (targetRecord.comment && targetRecord.comment()) {
135- record.comment = targetRecord.comment();
136- }
137-
188+ try {
189+ const recordUrl = targetRecord.url();
190+ if (recordUrl) record["url"] = recordUrl;
191+ } catch (e) {}
192+ try {
193+ const recordComment = targetRecord.comment();
194+ if (recordComment) record["comment"] = recordComment;
195+ } catch (e) {}
196+
138197 return JSON.stringify({
139198 success: true,
140199 record: record
141200 });
142-
201+
143202 } catch (error) {
144203 return JSON.stringify({
145204 success: false,
@@ -155,7 +214,7 @@ const getRecordByIdentifier = async (input: GetRecordByIdentifierInput): Promise
155214export const getRecordByIdentifierTool : Tool = {
156215 name : "get_record_by_identifier" ,
157216 description :
158- 'Get a DEVONthink record using its UUID or ID. \n\nExample (UUID):\n{\n "uuid": "1234-5678-90AB-CDEF"\n}\n\nExample (ID):\n{\n "id": 12345,\n "databaseName": "MyDatabase"\n}' ,
217+ 'Get a DEVONthink record using its UUID, ID, or x-devonthink-item:// reference URL.\n\nExample (Reference URL):\n{\n "referenceURL": "x-devonthink-item://1234-5678-90AB-CDEF"\n}\n\nExample (Reference URL - email):\n{\n "referenceURL": "x-devonthink-item://message:%3Cfoo@bar.com%3E"\n} \n\nExample (UUID):\n{\n "uuid": "1234-5678-90AB-CDEF"\n}\n\nExample (ID):\n{\n "id": 12345,\n "databaseName": "MyDatabase"\n}' ,
159218 inputSchema : zodToJsonSchema ( GetRecordByIdentifierSchema ) as ToolInput ,
160219 run : getRecordByIdentifier ,
161220} ;
0 commit comments