feat: update iPhone photo inbox scripts with environment file support and refactor batch handling
This commit is contained in:
@@ -1,61 +1,37 @@
|
||||
# iPhone Photo Inbox
|
||||
# Photo Inbox
|
||||
|
||||
Local HTTP receiver for sending JPEGs from iPhone Shortcuts into Mac inboxes.
|
||||
The Shortcut sends a `profile`, and the Mac decides the destination folder and
|
||||
clipboard behavior.
|
||||
macOS HTTP receiver for sending JPEG uploads into a local photo inbox. Clients
|
||||
can be iPhone Shortcuts, curl, another phone, a script, or any system that can
|
||||
POST a JPEG file. The server currently supports macOS only because clipboard,
|
||||
Finder reveal, and notifications use macOS APIs/tools.
|
||||
|
||||
## Profiles
|
||||
By default, each upload is saved locally and the current batch is copied to the
|
||||
macOS clipboard as native file URLs.
|
||||
|
||||
`opencode`
|
||||
## Behavior
|
||||
|
||||
- Saves to `ai/inbox/photos/`
|
||||
- Copies terminal-safe paths to the clipboard
|
||||
- Best for pasting into OpenCode running in a terminal
|
||||
- Saves photos to `~/Pictures/Photo Inbox` by default.
|
||||
- Groups consecutive uploads into a batch.
|
||||
- Every new photo extends the batch by `5s`.
|
||||
- Every new photo immediately refreshes the clipboard with the full batch.
|
||||
- When no new photo arrives before debounce expires, a summary notification is shown.
|
||||
|
||||
`mattermost`
|
||||
|
||||
- Saves to `~/Pictures/iPhone Inbox`
|
||||
- Copies native macOS file URLs to the clipboard
|
||||
- Best effort for pasting one or more files directly into Mattermost
|
||||
|
||||
`general`
|
||||
|
||||
- Saves to `~/Pictures/iPhone Inbox`
|
||||
- Does not modify the clipboard
|
||||
- Useful for plain capture
|
||||
|
||||
All profiles show a macOS notification by default.
|
||||
|
||||
## Batch debounce
|
||||
|
||||
Uploads are grouped by profile. Every new photo extends the active profile batch
|
||||
by 10 seconds and immediately refreshes the clipboard with the full batch.
|
||||
|
||||
Example for `opencode`:
|
||||
|
||||
```text
|
||||
photo 1 arrives -> clipboard has photo 1 path
|
||||
photo 2 arrives within 10s -> clipboard has photo 1 + photo 2 paths
|
||||
photo 3 arrives within 10s -> clipboard has photo 1 + photo 2 + photo 3 paths
|
||||
10s pass with no new photo -> notification says the batch is ready
|
||||
```
|
||||
|
||||
This keeps the Shortcut simple while still making the clipboard usable before
|
||||
the final notification appears.
|
||||
|
||||
For `mattermost`, each upload rewrites the clipboard with the full batch using a
|
||||
small Swift helper and `NSPasteboard.writeObjects`. This is closer to Finder's
|
||||
file-copy behavior than the older AppleScript alias approach. If the native
|
||||
helper fails, the receiver falls back to AppleScript and logs the fallback.
|
||||
This uses a small Swift helper and `NSPasteboard.writeObjects`, which matches
|
||||
Finder-style file clipboard behavior.
|
||||
|
||||
## Start the receiver
|
||||
|
||||
Recommended:
|
||||
|
||||
```bash
|
||||
IPHONE_PHOTO_TOKEN="choose-a-token" python3 scripts/iphone-photo-inbox/receiver.py
|
||||
cp scripts/iphone-photo-inbox/.env.example scripts/iphone-photo-inbox/.env
|
||||
scripts/iphone-photo-inbox/run.sh
|
||||
```
|
||||
|
||||
`.env` is loaded automatically and does not override variables already exported
|
||||
in the shell. `run.sh` compiles the native pasteboard helper when it is missing
|
||||
or older than the Swift source.
|
||||
|
||||
The receiver listens on:
|
||||
|
||||
```text
|
||||
@@ -74,7 +50,19 @@ If that does not return an IP, use:
|
||||
ifconfig
|
||||
```
|
||||
|
||||
## Shortcut config
|
||||
## Generic client contract
|
||||
|
||||
Send a JPEG request body:
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--data-binary @photo.jpg \
|
||||
"http://MAC_IP:8787/upload?token=choose-a-token"
|
||||
```
|
||||
|
||||
Successful response body is the saved local file path.
|
||||
|
||||
## iPhone Shortcuts guide
|
||||
|
||||
Use a Dictionary near the top of the Shortcut:
|
||||
|
||||
@@ -82,29 +70,24 @@ Use a Dictionary near the top of the Shortcut:
|
||||
mac_ip: 192.168.11.186
|
||||
port: 8787
|
||||
token: choose-a-token
|
||||
profile: opencode
|
||||
```
|
||||
|
||||
Build the URL from the dictionary:
|
||||
|
||||
```text
|
||||
http://[mac_ip]:[port]/upload?token=[token]&profile=[profile]
|
||||
http://[mac_ip]:[port]/upload?token=[token]
|
||||
```
|
||||
|
||||
Use `profile: opencode` when the next paste target is OpenCode. Use
|
||||
`profile: mattermost` when the next paste target is Mattermost.
|
||||
|
||||
## Camera shortcut
|
||||
Camera Shortcut:
|
||||
|
||||
```text
|
||||
Dictionary
|
||||
mac_ip: 192.168.11.186
|
||||
port: 8787
|
||||
token: choose-a-token
|
||||
profile: opencode
|
||||
|
||||
Text
|
||||
http://[mac_ip]:[port]/upload?token=[token]&profile=[profile]
|
||||
http://[mac_ip]:[port]/upload?token=[token]
|
||||
|
||||
Take Photo
|
||||
Show Camera Preview: On
|
||||
@@ -116,15 +99,13 @@ Get Contents of URL
|
||||
File: Photo
|
||||
|
||||
Show Notification
|
||||
Sent to [profile]
|
||||
Sent to photo inbox
|
||||
```
|
||||
|
||||
On the tested iPhone flow, `Take Photo` already produces a JPEG, so no
|
||||
conversion step is needed.
|
||||
|
||||
## Existing photos shortcut
|
||||
|
||||
Use this when sending existing images from Photos:
|
||||
Existing Photos Shortcut:
|
||||
|
||||
```text
|
||||
Receive Images and Media from Share Sheet
|
||||
@@ -133,87 +114,88 @@ Repeat with Each Item in Shortcut Input
|
||||
Image: Repeat Item
|
||||
Format: JPEG
|
||||
Get Contents of URL
|
||||
URL: http://[mac_ip]:[port]/upload?token=[token]&profile=[profile]
|
||||
URL: http://[mac_ip]:[port]/upload?token=[token]
|
||||
Method: POST
|
||||
Request Body: File
|
||||
File: Converted Image
|
||||
End Repeat
|
||||
Show Notification
|
||||
Sent to [profile]
|
||||
Sent to photo inbox
|
||||
```
|
||||
|
||||
## Overrides
|
||||
## Configuration
|
||||
|
||||
Profile folders:
|
||||
Common `.env` values:
|
||||
|
||||
```bash
|
||||
IPHONE_PHOTO_OPENCODE_DIR="/path/to/opencode/photos"
|
||||
IPHONE_PHOTO_MATTERMOST_DIR="$HOME/Pictures/iPhone Inbox"
|
||||
IPHONE_PHOTO_GENERAL_DIR="$HOME/Pictures/iPhone Inbox"
|
||||
```
|
||||
|
||||
Global folder override for all profiles:
|
||||
|
||||
```bash
|
||||
IPHONE_PHOTO_OUTPUT_DIR="$HOME/Pictures/iPhone Inbox" \
|
||||
IPHONE_PHOTO_TOKEN="choose-a-token" \
|
||||
python3 scripts/iphone-photo-inbox/receiver.py
|
||||
```
|
||||
|
||||
Default profile when the URL does not include `profile=`:
|
||||
|
||||
```bash
|
||||
IPHONE_PHOTO_PROFILE=mattermost \
|
||||
IPHONE_PHOTO_TOKEN="choose-a-token" \
|
||||
python3 scripts/iphone-photo-inbox/receiver.py
|
||||
```
|
||||
|
||||
Clipboard override for all profiles:
|
||||
|
||||
```bash
|
||||
IPHONE_PHOTO_CLIPBOARD=image
|
||||
IPHONE_PHOTO_CLIPBOARD=files
|
||||
IPHONE_PHOTO_CLIPBOARD=terminal-path
|
||||
IPHONE_PHOTO_CLIPBOARD=path
|
||||
IPHONE_PHOTO_CLIPBOARD=file
|
||||
IPHONE_PHOTO_CLIPBOARD=none
|
||||
```
|
||||
|
||||
Debounce override:
|
||||
|
||||
```bash
|
||||
IPHONE_PHOTO_DEBOUNCE_SECONDS=5
|
||||
PHOTO_INBOX_TOKEN=choose-a-token
|
||||
PHOTO_INBOX_HOST=0.0.0.0
|
||||
PHOTO_INBOX_PORT=8787
|
||||
PHOTO_INBOX_DIR=/Users/david/Pictures/Photo Inbox
|
||||
PHOTO_INBOX_DEBOUNCE_SECONDS=5
|
||||
PHOTO_INBOX_CLIPBOARD=1
|
||||
PHOTO_INBOX_NOTIFY=1
|
||||
```
|
||||
|
||||
Other useful options:
|
||||
|
||||
```bash
|
||||
python3 scripts/iphone-photo-inbox/receiver.py --no-notify
|
||||
python3 scripts/iphone-photo-inbox/receiver.py --reveal
|
||||
IPHONE_PHOTO_DEBUG=1 python3 scripts/iphone-photo-inbox/receiver.py
|
||||
scripts/iphone-photo-inbox/run.sh --no-clipboard
|
||||
scripts/iphone-photo-inbox/run.sh --no-notify
|
||||
scripts/iphone-photo-inbox/run.sh --reveal
|
||||
PHOTO_INBOX_DEBUG=1 scripts/iphone-photo-inbox/run.sh
|
||||
```
|
||||
|
||||
## Project integration
|
||||
|
||||
Keep this utility as an isolated image mailbox. If a project wants easy access,
|
||||
link the project inbox to the mailbox instead of making this utility know about
|
||||
the project.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
mkdir -p ai/inbox
|
||||
ln -s "$HOME/Pictures/Photo Inbox" ai/inbox/photos
|
||||
```
|
||||
|
||||
Or point the receiver at a project-owned folder from `.env`:
|
||||
|
||||
```bash
|
||||
PHOTO_INBOX_DIR=/absolute/path/to/project/ai/inbox/photos
|
||||
```
|
||||
|
||||
The symlink approach keeps this utility reusable across projects and devices.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Startup should print each active profile:
|
||||
Startup should print:
|
||||
|
||||
```text
|
||||
profile opencode: dir=... clipboard=terminal-path notify=True reveal=False
|
||||
profile mattermost: dir=... clipboard=files notify=True reveal=False
|
||||
saving to: ...
|
||||
debounce seconds: 5
|
||||
clipboard: True
|
||||
notify: True
|
||||
```
|
||||
|
||||
After each upload, expect:
|
||||
|
||||
```text
|
||||
clipboard mode applied: terminal-path profile=opencode count=2
|
||||
saved ... profile=opencode batch_count=2
|
||||
clipboard updated count=2
|
||||
saved ... batch_count=2
|
||||
```
|
||||
|
||||
After the debounce window closes, expect:
|
||||
|
||||
```text
|
||||
batch notification sent profile=opencode count=2
|
||||
batch finalized profile=opencode count=2 dir=...
|
||||
batch notification sent count=2
|
||||
batch finalized count=2 dir=...
|
||||
```
|
||||
|
||||
With `PHOTO_INBOX_DEBUG=1`, a two-photo batch should report:
|
||||
|
||||
```text
|
||||
pasteboard files=2 items=2
|
||||
```
|
||||
|
||||
The native file clipboard helper lives at:
|
||||
@@ -222,20 +204,8 @@ The native file clipboard helper lives at:
|
||||
scripts/iphone-photo-inbox/copy_files_to_clipboard.swift
|
||||
```
|
||||
|
||||
For faster multi-file clipboard updates during debounce, compile it once:
|
||||
The compiled binary is ignored by git and generated by `run.sh`:
|
||||
|
||||
```bash
|
||||
swiftc scripts/iphone-photo-inbox/copy_files_to_clipboard.swift \
|
||||
-o scripts/iphone-photo-inbox/copy_files_to_clipboard
|
||||
```text
|
||||
scripts/iphone-photo-inbox/copy_files_to_clipboard
|
||||
```
|
||||
|
||||
The compiled binary is ignored by git. If it is not present, the receiver can
|
||||
run the Swift script directly, but that is slower on first use.
|
||||
|
||||
If files arrive but clipboard/notifications do not behave as expected, check:
|
||||
|
||||
- The Shortcut URL includes the intended `profile=`.
|
||||
- The receiver log shows the expected profile.
|
||||
- With `IPHONE_PHOTO_DEBUG=1`, the Mattermost profile should report `pasteboard files=2 items=2` after the second photo in a two-photo batch.
|
||||
- macOS Focus/Do Not Disturb is not hiding notifications.
|
||||
- Terminal/Codex has permission for AppleScript automation if macOS prompts.
|
||||
|
||||
Reference in New Issue
Block a user