Skip to content

Conversation

@minodisk
Copy link

@minodisk minodisk commented Jan 2, 2026

Summary

  • Add support for external CLI commands for image upload and delete operations via -u and -d flags
  • Introduce Storage interface to abstract image storage (Google Drive vs external CLI)
  • Support template variables ({{mime}}, {{id}}, {{env.XXX}}) in upload/delete commands
  • Environment variables DECK_UPLOAD_MIME and DECK_DELETE_ID are passed to external commands

Motivation

This provides a workaround for #372.

In restricted Google Workspace environments where public file sharing is prohibited, Google Drive cannot be used for image storage. This feature allows users to use alternative storage backends (e.g., GCS, S3) via external CLI tools.

Usage

deck apply -u "my-uploader upload --mime {{mime}}" -d "my-uploader delete --id {{id}}" slides.md

External CLI Protocol

Upload command:

  • Receives image data via stdin
  • Environment variable DECK_UPLOAD_MIME contains the MIME type
  • Template variable {{mime}} is also available
  • Must output: public URL (line 1) and uploaded ID (line 2)

Delete command:

  • Environment variable DECK_DELETE_ID contains the uploaded ID
  • Template variable {{id}} is also available

Example Implementation

reprint - A GCS-based image storage CLI that implements this protocol

deck apply -u "reprint-gcs upload --mime {{mime}}" -d "reprint-gcs delete --object-id {{id}}" slides.md

Test plan

  • Tested with external storage CLI (reprint-gcs → GCS)
  • Tested with Google Drive storage (default behavior)
  • Verified both storage paths work correctly

🤖 Generated with Claude Code

## Summary
- Add `-u` (`--image-upload-command`) and `-d` (`--image-delete-command`) flags to the `apply` command
- Enable using external CLI tools for image upload and deletion instead of Google Drive
- Allows integration with custom storage backends (GCS, S3, etc.) via CLI wrappers
- Introduce `Storage` interface for consistent architecture

## Architecture
- `Storage` interface with `Upload()` and `Delete()` methods
- `googleDriveStorage`: default implementation using Google Drive
- `externalStorage`: implementation using external CLI commands

## CLI Specification

### Upload Command
The upload command receives:
- **stdin**: image binary data
- **Environment variable**: `DECK_UPLOAD_MIME` (MIME type of the image)
- **Template variable**: `{{mime}}`, `{{env.XXX}}`

Expected output (stdout):
```
<public URL>
<uploaded ID for deletion>
```

### Delete Command
The delete command receives:
- **Environment variable**: `DECK_DELETE_ID` (uploaded ID to delete)
- **Template variable**: `{{id}}`, `{{env.XXX}}`

## Example Usage
```bash
deck apply -i PRESENTATION_ID \
  -u "reprint-gcs upload" \
  -d "reprint-gcs delete" \
  slides.md
```

## Test plan
- [x] Verify build passes
- [x] Verify existing Google Drive upload still works when no external CLI is specified
- [x] Test with reprint-gcs
@Songmu
Copy link
Collaborator

Songmu commented Jan 3, 2026

This is a good idea and a challenge we want to solve.
It's a clean design, but we need to discuss whether introducing this complexity is necessary and whether this protocol is the right approach.

@minodisk
Copy link
Author

minodisk commented Jan 3, 2026

Thank you for the feedback!

On Complexity

The design intentionally keeps the default behavior unchanged — users who can use Google Drive don't need any additional configuration. The complexity only applies to users like me who face Google Workspace organizational restrictions and cannot attach images via Google Drive.

I also tried implementing GCS support directly inside deck, but it felt unclean to embed specific storage implementations. Delegating to external CLI keeps deck's core simple and allows users to choose their preferred storage backend.

On Protocol

For the input side, I referenced laminate's protocol — thank you for that elegant design!

For the output (which returns 2 values: public URL and uploaded ID), I went with a simple line-based format, but I'm honestly uncertain about this choice and thinking JSON might be better for extensibility. I'd appreciate any feedback on the protocol as a whole if you have suggestions for a better approach.

@github-actions
Copy link
Contributor

github-actions bot commented Jan 4, 2026

Code Metrics Report

main (25012a6) main (25012a6) +/-
Coverage 64.6% 64.6% 0.0%
Code to Test Ratio 1:0.9 1:0.9 0.0
Test Execution Time 5m49s 7m14s +1m25s
Details
  |                     | main (25012a6) | main (25012a6) |  +/-   |
  |---------------------|----------------|----------------|--------|
  | Coverage            |          64.6% |          64.6% |   0.0% |
  |   Files             |             31 |             31 |      0 |
  |   Lines             |           3405 |           3405 |      0 |
  |   Covered           |           2203 |           2203 |      0 |
  | Code to Test Ratio  |          1:0.9 |          1:0.9 |    0.0 |
  |   Code              |           7020 |           7020 |      0 |
  |   Test              |           6581 |           6581 |      0 |
- | Test Execution Time |          5m49s |          7m14s | +1m25s |

Code coverage of files in pull request scope (66.3% → 66.3%)

Files Coverage +/- Status
cmd/apply.go 19.4% 0.0% modified
deck.go 73.5% 0.0% modified
md/cel.go 45.5% 0.0% modified
md/md.go 79.2% 0.0% modified
preload.go 80.6% 0.0% modified

Reported by octocov

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants