| | // Spotlight semantic search readiness, as reported by CoreSpotlight. | | | // Usage: osascript -l JavaScript readiness.js [pipeline] (default: Embedding) | | | // Or paste into Script Editor and pick JavaScript instead of AppleScript language |
| | ObjC.import('CoreSpotlight'); |
| | const bind = (name, signature) => ObjC.bindFunction(name, signature); |
| | bind('xpc_connection_create_mach_service', ['pointer', ['char *', 'pointer', 'unsigned long long']]); |
| | bind('xpc_connection_set_event_handler', ['void', ['pointer', 'id']]); |
| | bind('xpc_connection_resume', ['void', ['pointer']]); |
| | bind('xpc_connection_send_message_with_reply_sync', ['pointer', ['pointer', 'pointer']]); |
| | bind('xpc_dictionary_create', ['pointer', ['pointer', 'pointer', 'unsigned long']]); |
| | bind('xpc_dictionary_set_string', ['void', ['pointer', 'char *', 'char *']]); |
| | bind('xpc_dictionary_set_uint64', ['void', ['pointer', 'char *', 'unsigned long long']]); |
| | bind('xpc_dictionary_get_data', ['pointer', ['pointer', 'char *', 'pointer']]); |
| | bind('xpc_dictionary_get_uint64', ['unsigned long long', ['pointer', 'char *']]); |
| | bind('xpc_dictionary_get_int64', ['long long', ['pointer', 'char *']]); |
| | function run(argv) { |
| | const pipeline = argv[0] \|\| 'Embedding'; |
| | // Same request -[CSSearchableIndex fetchIndexingPipelineOverallCompleteness:completionHandler:] sends | | | const connection = $.xpc_connection_create_mach_service('com.apple.spotlight.IndexAgent', null, 0); |
| | $.xpc_connection_set_event_handler(connection, ObjC.block(['void', ['pointer']], () => {})); |
| | $.xpc_connection_resume(connection); |
| | const request = $.xpc_dictionary_create(null, null, 0); |
| | $.xpc_dictionary_set_uint64(request, 'id', 1); |
| | $.xpc_dictionary_set_string(request, 'command', 'fetch-indexing-pipeline-completeness'); |
| | $.xpc_dictionary_set_string(request, 'command-type', 'index-type'); |
| | $.xpc_dictionary_set_string(request, 'n', 'CSSearchableIndexShared'); |
| | $.xpc_dictionary_set_uint64(request, 'iopt', 0); |
| | $.xpc_dictionary_set_string(request, 'indexing-pipeline', pipeline); |
| | const reply = $.xpc_connection_send_message_with_reply_sync(connection, request); | | | // Nonzero status: unknown pipeline, or no report yet (first minutes after install) |
| | if (Number($.xpc_dictionary_get_int64(reply, 'status')) !== 0) return `No report for ${pipeline}`; |
| | const size = $.xpc_dictionary_get_uint64(reply, 'indexing-pipeline-completeness-size'); |
| | const bytes = $.xpc_dictionary_get_data(reply, 'indexing-pipeline-completeness', null); |
| | const report = $.NSKeyedUnarchiver.unarchivedObjectOfClassFromDataError( |
| | $.NSClassFromString('CSIndexingPipelineOverallCompleteness'), $.NSData.dataWithBytesLength(bytes, size), null); |
| | const value = key => ObjC.unwrap(report.valueForKey(key)); |
| | const percent = key => value(key) == null ? 'n/a' : (value(key) * 100).toFixed(1) + '%'; |
| | const status = value('isSufficientlyComplete') ? 'Ready' : 'Still processing'; |
| | return [ |
| | `Search readiness: ${percent('overallCompleteness')} — ${status} (${percent('sufficientlyCompleteThreshold')} required)`, |
| | `Known app items donated: ${percent('donationCompeleteness')}`, |
| | `Search pipeline processed: ${percent('pipelineCompleteness')}`, |
| | `Report age: ${(value('pipelineReportAge') / 3600).toFixed(1)} hours`, |
| | ].join('\n'); |
| | } |