When a user pastes an image into Claude CLI running in a sprite, it should work exactly as if Claude CLI was running locally. No extra steps, no visible difference.
- User copies image (screenshot, from browser, from Finder)
- User pastes in Claude CLI (Cmd+V / Ctrl+V)
- Terminal converts clipboard to file path:
/var/folders/.../image.png - Claude CLI reads the file from that path
- Image appears in conversation
- User copies image on local machine
- User pastes in Claude CLI running in sprite (Cmd+V / Ctrl+V)
- [NEW] Sprite console intercepts, uploads file, rewrites path
- Claude CLI reads the file from remote path:
/tmp/pasted-image.png - Image appears in conversation
The user should not notice any difference.
┌─────────────────────────────────────────────────────────────────────┐
│ Terminal (Ghostty) │
│ - User presses Cmd+V │
│ - Clipboard contains image → converted to temp file path │
│ - Sends to sprite console: "/var/folders/abc/image.png" │
└─────────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Sprite Console Client (INTERCEPTION POINT) │
│ │
│ Input buffer: "/var/folders/abc/image.png" │
│ │
│ 1. Detect: looks like local file path │
│ 2. Check: file exists at /var/folders/abc/image.png │
│ 3. Upload: POST to sprite API with file contents │
│ 4. Receive: remote path "/tmp/pasted-a1b2c3.png" │
│ 5. Rewrite: replace local path with remote path in buffer │
│ 6. Send to sprite: "/tmp/pasted-a1b2c3.png" │
│ │
└─────────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Sprite VM │
│ - Receives: "/tmp/pasted-a1b2c3.png" │
│ - File exists at that path (just uploaded) │
│ - Claude CLI reads it successfully │
│ - ✓ Works exactly like local │
└─────────────────────────────────────────────────────────────────────┘
The sprite console client needs to identify when pasted text is a local file path that should be uploaded.
# macOS paths
/Users/*
/var/folders/*
/private/var/*
/tmp/* (local tmp, not sprite tmp)
~/*
# Linux paths
/home/*
/tmp/* (if running console from Linux)
# Windows paths
C:\Users\*
%USERPROFILE%\*
def should_intercept_paste(text: str) -> bool:
# Must look like an absolute file path
if not looks_like_file_path(text):
return False
# Must exist locally
if not os.path.exists(text):
return False
# Must be a file (not directory)
if not os.path.isfile(text):
return False
# Optionally: only intercept certain file types
# (images, PDFs, common document types)
return True
def looks_like_file_path(text: str) -> bool:
# Clean up: might have quotes, escapes, whitespace
text = text.strip().strip('"\'')
# macOS
if text.startswith('/Users/'):
return True
if text.startswith('/var/folders/'):
return True
if text.startswith('/private/'):
return True
# Linux
if text.startswith('/home/'):
return True
# Windows
if re.match(r'^[A-Z]:\\', text):
return True
return False-
Path with spaces:
/Users/name/My Files/image.png- May be escaped:
/Users/name/My\ Files/image.png - May be quoted:
"/Users/name/My Files/image.png" - Must handle all formats
- May be escaped:
-
Multiple paths in one paste: Rare, but handle by processing each
-
Path that looks local but isn't:
/Users/someone/file.txtwhen file doesn't exist- Don't intercept, pass through as-is
-
Non-file paths:
/Users/someone(directory)- Don't intercept directories (or optionally zip and upload?)
-
Very large files: User pastes path to 1GB video
- Should have size limit with clear error message
- Suggest using explicit transfer for large files
POST /v1/sprites/{sprite_id}/files/upload
Authorization: Bearer {console_session_token}
Content-Type: multipart/form-data
------boundary
Content-Disposition: form-data; name="file"; filename="image.png"
Content-Type: image/png
<binary data>
------boundary
Content-Disposition: form-data; name="prefix"
pasted-
------boundary--
{
"path": "/tmp/pasted-a1b2c3d4.png",
"size": 145832,
"mime_type": "image/png"
}{
"error": "file_too_large",
"message": "File exceeds 50MB limit. Use 'sprite upload' for large files.",
"max_size": 52428800
}Files uploaded via paste go to: /tmp/pasted-{uuid}.{ext}
Using /tmp/ because:
- Standard location for temporary files
- Auto-cleaned on sprite restart
- Excluded from checkpoints by default
- No special permissions needed
Format: pasted-{8-char-uuid}.{original-extension}
Examples:
pasted-a1b2c3d4.pngpasted-e5f6g7h8.jpgpasted-i9j0k1l2.pdf
Options (choose one):
- On sprite pause/stop: All
/tmp/pasted-*files deleted - TTL-based: Delete after 1 hour
- Session-based: Delete when console session ends
- Manual: User runs cleanup command
Recommendation: On sprite stop - simplest, predictable, matches expectation that temp files are temporary.
Local paste feels instant (<50ms). For sprite paste to feel similar:
| Step | Target | Notes |
|---|---|---|
| Detect local path | <1ms | Simple regex |
| Check file exists | <5ms | Local filesystem |
| Read file | <100ms | Depends on size |
| Upload to sprite | <500ms | Network, depends on size |
| Receive response | <50ms | Small JSON |
| Total | <700ms | For typical image (<5MB) |
For files <1MB, this should feel nearly instant. For files 1-10MB, slight delay but acceptable. For files >10MB, should show progress indicator.
For files taking >500ms to upload, show progress:
⠋ Uploading image.png (2.3 MB)...
Or integrate with terminal title/status line.
User pastes: /Users/aezell/Desktop/screenshot.png
User sees: /tmp/pasted-a1b2c3d4.png (after ~200ms)
Claude sees: /tmp/pasted-a1b2c3d4.png
Result: Image loads correctly
File doesn't exist locally:
User pastes: /Users/aezell/Desktop/deleted.png
User sees: /Users/aezell/Desktop/deleted.png (passed through unchanged)
Claude sees: /Users/aezell/Desktop/deleted.png
Result: Claude shows "file not found" (expected behavior)
File too large:
User pastes: /Users/aezell/Movies/huge-video.mp4
User sees: ⚠️ File too large (1.2GB). Use 'sprite upload' for files >50MB.
/Users/aezell/Movies/huge-video.mp4
Claude sees: /Users/aezell/Movies/huge-video.mp4
Result: User understands they need different approach
Upload fails (network error):
User pastes: /Users/aezell/Desktop/image.png
User sees: ⚠️ Upload failed: connection timeout. Retrying...
(retry succeeds)
/tmp/pasted-a1b2c3d4.png
# ~/.config/sprite/console.yaml
paste_interception:
enabled: true # Master switch
max_file_size: 52428800 # 50MB in bytes
show_progress: true # Show upload progress for large files
file_types: # Only intercept these types (empty = all)
- image/*
- application/pdf
- text/*Most users shouldn't need to configure anything - defaults should "just work."
-
Accidental sensitive file paste: User might paste path to sensitive file
- Mitigation: Files go to
/tmp/, not persisted - Optional: Prompt for confirmation for non-image files?
- Mitigation: Files go to
-
Path traversal: Malicious path like
/../../../etc/passwd- Mitigation: Validate path is absolute, file exists, is a regular file
-
Large file DoS: User pastes many large files
- Mitigation: Rate limiting, size limits, cleanup on session end
-
File type validation: Is the file actually what it claims to be?
- For images: Verify magic bytes match extension
- Optional: Full content-type sniffing
- Detect macOS paths (
/Users/*,/var/folders/*) - Upload images only (png, jpg, gif, webp)
- Simple replacement, no progress
- Size limit: 10MB
- Progress indicator for large files
- Support more file types
- Windows path support
- Better error messages
- Configuration options
- Retry logic
- Rate limiting
- Metrics/logging
- Paste PNG screenshot → works
- Paste JPEG from browser → works
- Paste file from Finder → works
- Paste path with spaces → works
- Paste quoted path → works
- Paste escaped path → works
- Paste non-existent file → passes through
- Paste directory → passes through
- Paste 1MB file → fast
- Paste 10MB file → works, shows progress
- Paste 100MB file → rejected with message
- Paste during upload → queued correctly
- Network failure → retries, then passes through with error
- Rapid paste spam → handled gracefully