pulli / pullbox
Static methods for using AppleScript and DEVONthink
Installs: 321
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 1
Forks: 0
Open Issues: 0
pkg:composer/pulli/pullbox
Requires
- php: >=8.4
- illuminate/collections: ^11.0|^12.0
- rodneyrehm/plist: ^2.0
Requires (Dev)
- laravel/pint: ^1.16
- pestphp/pest: ^v4.0.3
README
A PHP 8.4+ library providing static methods for executing AppleScript on macOS, with wrappers for system operations, Music app, dialogs, notifications, and DEVONthink.
Note: This library requires macOS to execute AppleScript commands. The
AppleScriptclass can generate scripts on any platform, but the facade classes (System,Music,Dialog,Notification,DEVONthink) require macOS.
Installation
composer require pulli/pullbox
Usage
Dialog
Display macOS dialogs with customizable buttons and optional text input.
use Pulli\Pullbox\Dialog; // Simple dialog with OK button $result = Dialog::display('Are you sure?', 'Confirmation'); // Dialog with custom buttons, default and cancel $result = Dialog::display('Save changes?', 'Save', [ 'buttons' => ['Save', 'Discard', 'Cancel'], 'defaultButton' => 'Save', 'cancelButton' => 'Cancel', ]); // Buttons referenced by position (1-indexed) $result = Dialog::display('Continue?', 'Confirm', [ 'buttons' => ['Yes', 'No'], 'defaultButton' => 1, 'cancelButton' => 2, ]); // Dialog with text input $result = Dialog::display('Enter your name:', 'Input', [ 'answer' => true, ]); // Pre-filled text input $result = Dialog::display('Rename:', 'Edit', [ 'answer' => 'current name', ]); // Password input (hidden) $result = Dialog::display('Password:', 'Auth', [ 'answer' => true, 'hiddenAnswer' => true, ]); // With icon (stop, caution, or note) $result = Dialog::display('Are you sure?', 'Warning', [ 'icon' => 'caution', ]); // Auto-dismiss after N seconds $result = Dialog::display('Closing soon...', 'Notice', [ 'givingUpAfter' => 10, ]);
Methods:
| Method | Parameters | Returns |
|---|---|---|
display |
string $message, ?string $title, array $options = [] |
?string (button pressed or text input, null if cancelled) |
Options array:
buttons— Array of button labels, max 3 (default:['OK'])defaultButton— Button name or 1-indexed position for the default buttoncancelButton— Button name or 1-indexed position for the cancel buttonanswer—truefor empty text field, or a string to pre-fill (default:false)hiddenAnswer—trueto mask input like a password fieldicon— Dialog icon:'stop','caution', or'note'givingUpAfter— Auto-dismiss the dialog after N seconds
Notification
Display macOS notifications with optional title, subtitle, and sound.
use Pulli\Pullbox\Notification; // Simple notification Notification::display('Download complete'); // With title Notification::display('Download complete', 'My App'); // With subtitle Notification::display('Download complete', 'My App', 'All files processed'); // With sound Notification::display('Download complete', 'My App', null, 'Glass'); // All parameters Notification::display('Download complete', 'My App', 'All files processed', 'Frog');
Methods:
| Method | Parameters | Returns |
|---|---|---|
display |
string $message, ?string $title = null, ?string $subtitle = null, ?string $soundName = null |
void |
System
System-level operations for managing applications.
use Pulli\Pullbox\System; // Get the Applications folder path $path = System::applicationsFolder(); // e.g. "/Applications/" // Move an app to Applications (quits it first, then relaunches) System::moveApp('MyApp', '/tmp/MyApp.app'); // Move without relaunching System::moveApp('MyApp', '/tmp/MyApp.app', launch: false); // Get app version number $version = System::versionNumber('Safari'); $version = System::versionNumber('Safari', 'CFBundleShortVersionString'); // Suppress error dialogs $version = System::versionNumber('Safari', displayExceptions: false);
Methods:
| Method | Parameters | Returns |
|---|---|---|
applicationsFolder |
— | string |
moveApp |
string $name, string $path, bool $launch = true |
void |
versionNumber |
string $appName, string $key = 'CFBundleVersion', bool $displayExceptions = true |
?string |
Music
Interact with the macOS Music app and play system sounds.
use Pulli\Pullbox\Music; use Pulli\Pullbox\Enums\SystemSound; // Export a playlist Music::exportPlaylist('My Playlist', '/path/to/export.xml'); Music::exportPlaylist('My Playlist', '/path/to/export.m3u', 'm3u'); // Play a system sound Music::playSystemSound(SystemSound::Glass); Music::playSystemSound(SystemSound::Ping);
Methods:
| Method | Parameters | Returns |
|---|---|---|
exportPlaylist |
string $name, string $to, string $format = 'xml' |
void |
playSystemSound |
SystemSound $sound |
void |
DEVONthink
Interact with DEVONthink via AppleScript. All 119 scripting commands are supported. Most methods are compatible with DEVONthink 3 and 4; AI/Chat methods require DEVONthink 4.
Records are referenced by UUID throughout. Methods that create or find records return their UUID.
use Pulli\Pullbox\DEVONthink; use Pulli\Pullbox\Enums\RecordType; use Pulli\Pullbox\Enums\UpdateMode; use Pulli\Pullbox\Enums\SummaryType; // Create a record $uuid = DEVONthink::createRecordWith([ 'name' => 'My Note', 'type' => RecordType::Markdown, 'content' => '# Hello World', ]); // Create in a specific group $uuid = DEVONthink::createRecordWith( ['name' => 'Note', 'type' => RecordType::Text, 'content' => 'Hello'], $groupUuid ); // Search $uuids = DEVONthink::search('kind:markdown name:report'); // Import, index, export $uuid = DEVONthink::importPath('/path/to/file.pdf', $groupUuid); $uuid = DEVONthink::indexPath('/path/to/folder', $groupUuid); DEVONthink::export($uuid, '/path/to/export'); // Web capture $uuid = DEVONthink::createMarkdownFrom('https://example.com'); $uuid = DEVONthink::createPdfDocumentFrom('https://example.com'); $uuid = DEVONthink::downloadUrl('https://example.com/file.zip'); // Record operations DEVONthink::move($uuid, $targetGroupUuid); DEVONthink::duplicate($uuid); DEVONthink::replicate($uuid, $targetGroupUuid); DEVONthink::delete($uuid); // Content access $text = DEVONthink::getTextOf($uuid); $path = DEVONthink::pathToRecord($uuid); $contents = DEVONthink::getRecordContents($uuid); DEVONthink::savePlainTextToRecord('Updated text', $uuid); DEVONthink::update($uuid, 'Appended text', UpdateMode::Appending); // Custom metadata DEVONthink::addCustomMetaData('value', 'myKey', $uuid); $value = DEVONthink::getCustomMetaData('myKey', $uuid); // Database operations $dbName = DEVONthink::createDatabase('/path/to/new.dtBase2'); $dbName = DEVONthink::openDatabase('/path/to/existing.dtBase2'); DEVONthink::optimize('My Database'); $errors = DEVONthink::verify('My Database'); // AI & Chat (DEVONthink 4 only) $response = DEVONthink::getChatResponseForMessage('Summarize this', engine: 'openai'); $transcript = DEVONthink::transcribe($audioUuid, language: 'en'); $summaryUuid = DEVONthink::summarizeAnnotationsOf($uuids, SummaryType::Markdown);
Record CRUD
| Method | Parameters | Returns |
|---|---|---|
createRecordWith |
array $properties, ?string $groupUuid |
?string |
delete |
string $uuid |
bool |
duplicate |
string $uuid, ?string $toGroupUuid |
?string |
move |
string $uuid, string $toGroupUuid |
?string |
moveIntoDatabase |
string $uuid, string $databaseName |
?string |
moveToExternalFolder |
string $uuid |
bool |
replicate |
string $uuid, string $toGroupUuid |
?string |
merge |
array $uuids, ?string $groupUuid |
?string |
update |
string $uuid, string $text, UpdateMode $mode, ?string $url |
bool |
Record Access
| Method | Parameters | Returns |
|---|---|---|
pathToRecord |
string $uuid |
string |
getRecordContents |
string $uuid |
string |
getRecordAt |
string $path, ?string $databaseName |
?string |
getRecordWithId |
int $id, ?string $databaseName |
?string |
getRecordWithUuid |
string $uuid |
?string |
getDatabaseWithId |
int $id |
?string |
getDatabaseWithUuid |
string $uuid |
?string |
Record Content
| Method | Parameters | Returns |
|---|---|---|
getTextOf |
string $uuid |
string |
getRichTextOf |
string $uuid |
string |
getTitleOf |
string $uuid |
string |
getMetadataOf |
string $uuid |
string |
getConcordanceOf |
string $uuid |
string |
getLinksOf |
string $uuid |
array |
getEmbeddedImagesOf |
string $uuid |
array |
getEmbeddedObjectsOf |
string $uuid |
array |
getEmbeddedSheetsAndScriptsOf |
string $uuid |
array |
getFramesOf |
string $uuid |
array |
getFaviconOf |
string $uuid |
string |
Custom Meta Data
| Method | Parameters | Returns |
|---|---|---|
addCustomMetaData |
mixed $value, string $forKey, string $uuid |
void |
getCustomMetaData |
string $forKey, string $uuid |
string |
Search & Lookup
| Method | Parameters | Returns |
|---|---|---|
search |
string $query, ?string $inGroupUuid, ?string $comparison, bool $excludeSubgroups |
array |
lookupRecordsWithComment |
string $comment, ?string $databaseName |
array |
lookupRecordsWithContentHash |
string $hash, ?string $databaseName |
array |
lookupRecordsWithFile |
string $path, ?string $databaseName |
array |
lookupRecordsWithPath |
string $path, ?string $databaseName |
array |
lookupRecordsWithTags |
array $tags, ?string $databaseName |
array |
lookupRecordsWithUrl |
string $url, ?string $databaseName |
array |
exists |
string $uuid |
bool |
existsRecordAt |
string $path, ?string $databaseName |
bool |
existsRecordWithComment |
string $comment, ?string $databaseName |
bool |
existsRecordWithContentHash |
string $hash, ?string $databaseName |
bool |
existsRecordWithFile |
string $path, ?string $databaseName |
bool |
existsRecordWithPath |
string $path, ?string $databaseName |
bool |
existsRecordWithUrl |
string $url, ?string $databaseName |
bool |
classify |
string $uuid, ?string $groupUuid |
array |
compare |
string $uuid, string $toUuid |
float |
count |
string $uuid |
int |
Import & Export
| Method | Parameters | Returns |
|---|---|---|
importRecords |
array|string $paths |
void |
importPath |
string $path, ?string $groupUuid |
?string |
importTemplate |
string $path, ?string $groupUuid |
?string |
importAttachmentsOf |
string $uuid, ?string $groupUuid |
array |
indexPath |
string $path, ?string $groupUuid |
?string |
export |
string $uuid, string $toPath |
bool |
exportTagsOf |
string $uuid |
string |
exportWebsite |
string $uuid, string $toPath |
bool |
Web Capture & Download
| Method | Parameters | Returns |
|---|---|---|
createWebDocumentFrom |
string $url, ?string $groupUuid |
?string |
createMarkdownFrom |
string $url, ?string $groupUuid |
?string |
createPdfDocumentFrom |
string $url, ?string $groupUuid |
?string |
createFormattedNoteFrom |
string $url, ?string $groupUuid |
?string |
downloadUrl |
string $url, ?string $groupUuid |
?string |
downloadMarkupFrom |
string $url |
string |
addDownload |
string $url |
void |
addReadingList |
string $url |
void |
getCachedDataForUrl |
string $url |
string |
startDownloads |
— | bool |
stopDownloads |
— | bool |
Conversion
| Method | Parameters | Returns |
|---|---|---|
convert |
string $uuid, string $toType, ?string $groupUuid |
?string |
convertFeedToHtml |
string $uuid |
string |
convertImage |
string $uuid, ?string $groupUuid |
?string |
Text & OCR
| Method | Parameters | Returns |
|---|---|---|
savePlainTextToRecord |
string $text, string $uuid |
void |
extractKeywordsFrom |
string $text |
string |
ocr |
string $uuid, ?string $groupUuid |
?string |
Database Operations
| Method | Parameters | Returns |
|---|---|---|
createDatabase |
string $path |
?string |
openDatabase |
string $path |
?string |
optimize |
string $databaseName |
bool |
verify |
string $databaseName |
int |
checkFileIntegrityOf |
string $uuid |
int |
compress |
string $databaseName |
bool |
synchronize |
?string $uuid, ?string $databaseName |
bool |
createLocation |
string $path, ?string $databaseName |
?string |
Thumbnails
| Method | Parameters | Returns |
|---|---|---|
createThumbnail |
string $uuid |
bool |
deleteThumbnail |
string $uuid |
bool |
updateThumbnail |
string $uuid |
bool |
Versions
| Method | Parameters | Returns |
|---|---|---|
getVersionsOf |
string $uuid |
array |
saveVersionOf |
string $uuid |
?string |
restoreRecordWith |
string $versionUuid |
bool |
AI & Chat (DEVONthink 4 only)
| Method | Parameters | Returns |
|---|---|---|
getChatCapabilitiesForEngine |
string $engine |
string |
getChatModelsForEngine |
string $engine |
string |
getChatResponseForMessage |
string $message, ?string $engine, ?string $model |
string |
downloadImageForPrompt |
string $prompt, ?string $engine |
string |
summarizeText |
string $text, ?string $style |
string |
summarizeAnnotationsOf |
array $uuids, SummaryType $type, ?string $groupUuid |
?string |
summarizeContentsOf |
array $uuids, SummaryType $type, ?string $style, ?string $groupUuid |
?string |
summarizeMentionsOf |
array $uuids, SummaryType $type, ?string $groupUuid |
?string |
transcribe |
string $uuid, ?string $language, ?bool $timestamps |
string |
Feeds
| Method | Parameters | Returns |
|---|---|---|
getFeedItemsOf |
string $uuid |
array |
getItemsOfFeed |
string $uuid |
array |
refresh |
string $uuid |
bool |
Sheets
| Method | Parameters | Returns |
|---|---|---|
addRow |
string $uuid |
void |
deleteRowAt |
int $row, string $uuid |
void |
getCellAt |
int $column, int $row, string $uuid |
string |
setCellAt |
int $column, int $row, string $value, string $uuid |
bool |
UI Dialogs
| Method | Parameters | Returns |
|---|---|---|
displayAuthenticationDialog |
string $message, ?string $title |
array |
displayChatDialog |
?string $message, ?string $title |
string |
displayDateEditor |
?string $date, ?string $title |
string |
displayGroupSelector |
string $message, ?string $title, ?string $databaseName |
?string |
displayNameEditor |
string $message, ?string $title, ?string $defaultName |
string |
UI Windows & Tabs
| Method | Parameters | Returns |
|---|---|---|
open |
string $uuid |
void |
openTabFor |
?string $uuid, ?string $url, ?string $referrer |
void |
openWindowFor |
string $uuid, bool $force |
void |
showSearch |
?string $query |
void |
Progress Indicator
| Method | Parameters | Returns |
|---|---|---|
showProgressIndicator |
string $title, ?bool $cancelButton, ?int $steps |
bool |
stepProgressIndicator |
?string $info |
bool |
hideProgressIndicator |
— | bool |
Workspaces
| Method | Parameters | Returns |
|---|---|---|
saveWorkspace |
string $name |
bool |
loadWorkspace |
string $name |
void |
deleteWorkspace |
string $name |
void |
Smart Rules
| Method | Parameters | Returns |
|---|---|---|
performSmartRule |
?string $name, ?string $uuid, ?string $trigger |
bool |
Imprinting
| Method | Parameters | Returns |
|---|---|---|
imprint |
string $uuid, ?string $configuration |
void |
imprintConfiguration |
string $name |
string |
imprinterConfigurationNames |
— | array |
Misc
| Method | Parameters | Returns |
|---|---|---|
addReminder |
string $uuid, ?string $alarm |
void |
logMessage |
string $message, ?string $info |
void |
pasteClipboard |
?string $groupUuid |
?string |
make |
string $type, array $properties, ?string $groupUuid |
?string |
AppleScript
Low-level class for generating and executing AppleScript code. Use this to generate scripts without executing them, or to execute arbitrary scripts safely.
use Pulli\Pullbox\AppleScript; // Execute a script (fire and forget) AppleScript::execute($script); // Execute and capture output $result = AppleScript::executeAndCapture($script); // Escape user input for safe AppleScript string interpolation $safe = AppleScript::escapeString('He said "hello"'); // Generate script strings $script = AppleScript::displayDialog('Hello', 'Title'); $script = AppleScript::displayNotification('Done', 'App', 'Subtitle', 'Glass'); $script = AppleScript::applicationsFolder(); $script = AppleScript::moveApp('MyApp', '/path/to/app'); $script = AppleScript::musicExportPlaylist('Playlist', '/path/to/file', 'xml'); $script = AppleScript::devonthinkPathToRecord('uuid'); $scripts = AppleScript::devonthinkImportRecords(['/path/to/file']); $script = AppleScript::devonthinkSavePlainTextToRecord('text', 'uuid');
Enums
RecordType
use Pulli\Pullbox\Enums\RecordType; RecordType::Bookmark; // 'bookmark' RecordType::Feed; // 'feed' RecordType::FormattedNote; // 'formatted note' RecordType::Group; // 'group' RecordType::HTML; // 'html' RecordType::Markdown; // 'markdown' RecordType::Picture; // 'picture' RecordType::Plist; // 'plist' RecordType::Quicktime; // 'quicktime' RecordType::RTF; // 'rtf' RecordType::RTFD; // 'rtfd' RecordType::Script; // 'script' RecordType::Sheet; // 'sheet' RecordType::SmartGroup; // 'smart group' RecordType::Text; // 'txt' RecordType::WebArchive; // 'web archive' RecordType::Unknown; // 'unknown'
UpdateMode
use Pulli\Pullbox\Enums\UpdateMode; UpdateMode::Appending; // 'appending' UpdateMode::Inserting; // 'inserting' UpdateMode::Replacing; // 'replacing'
SummaryType
use Pulli\Pullbox\Enums\SummaryType; SummaryType::Markdown; // 'markdown' SummaryType::Rich; // 'rich' SummaryType::Sheet; // 'sheet' SummaryType::Simple; // 'simple'
PlaylistExportFormat
use Pulli\Pullbox\Enums\PlaylistExportFormat; // Available formats PlaylistExportFormat::M3U; // 'M3U' PlaylistExportFormat::M3U8; // 'M3U8' PlaylistExportFormat::PlainText; // 'plain text' PlaylistExportFormat::UnicodeText; // 'Unicode text' PlaylistExportFormat::XML; // 'XML' // Resolve from string $format = PlaylistExportFormat::fromInput('m3u'); // PlaylistExportFormat::M3U
SystemSound
use Pulli\Pullbox\Enums\SystemSound; // Available sounds SystemSound::Basso; SystemSound::Blow; SystemSound::Bottle; SystemSound::Frog; SystemSound::Funk; SystemSound::Glass; SystemSound::Hero; SystemSound::Morse; SystemSound::Ping; SystemSound::Pop; SystemSound::Purr; SystemSound::Sosumi; SystemSound::Submarine; SystemSound::Tink; // Get the file path of a sound $path = SystemSound::Glass->filepath(); // /System/Library/Sounds/Glass.aiff
Testing
# Run all tests (excluding macOS-only) composer test -- --exclude-group=mac # Run all tests (on macOS) composer test # Run a specific test file vendor/bin/pest tests/Unit/AppleScriptTest.php
Credits
License
The MIT License (MIT). Please see License File for more information.