An extensible image upload tool built with Rust, powered by a WebAssembly plugin system.
pic-rs lets you upload images through a configurable pipeline of WASM extensions. Extensions can be chained together using an arrangement graph, enabling flexible workflows such as compression → watermarking → uploading to multiple image hosting services.
- WASM Extension System — Extensions are WebAssembly components built on the WIT (WebAssembly Interface Types) standard, running in a sandboxed Wasmtime runtime with WASI P2 & HTTP support.
- Arrangement Pipeline — Chain multiple extensions into a directed graph. The output of one extension feeds into the next, with support for conditional branching.
- Central Repository — Install extensions from the [pic-rs extension center](Not yet built), a remote URL, or a local file.
- Per-Extension Environment Variables — Each extension has its own isolated environment configuration (API keys, tokens, etc.) managed through the CLI.
- Extension SDK — A developer-friendly API crate (
picrs_extension_api) to build your own extensions with minimal boilerplate.
git clone https://github.com/user/pic-rs.git
cd pic-rs
cargo build --release -p picrs-cliThe binary will be available at target/release/picrs-cli.
Install an extension from the central repository, a URL, or a local path:
# From central repository (name@version)
# picrs add smms@0.1.0
# From a remote URL
picrs add https://example.com/my_extension.wasm
# From a local file
picrs add /path/to/extension.wasmMost extensions require environment variables (e.g. API tokens). Check what's needed and set them:
# Check which variables are unset
picrs env check my_extension
# Set variables interactively
picrs env set-from-io my_extensionOr set them directly:
# Set variables via K=V pairs
picrs env set my_extension --env "API_TOKEN=abc123,BASE_URL=https://api.example.com"Edit ~/.picrs/picrs.toml to define extensions and their arrangement:
# "input" is the entry point; its output goes to "my_uploader"
input = { extensions = ["my_uploader"] }
# "my_uploader" sends its result to "output" (the final result)
my_uploader = { extensions = ["output"] }picrs upload /path/to/image.png| Command | Description |
|---|---|
picrs add <source> |
Add an extension from name@version, URL, or local path |
picrs upload <image> |
Upload an image through the configured arrangement pipeline |
picrs env check <name> |
Check unset environment variables for an extension |
picrs env set <name> --env K=V,... |
Set/override environment variables for an extension |
picrs env set-from-io <name> |
Interactively set environment variables for an extension |
picrs show |
List all installed extensions |
picrs show <name> |
Show metadata and details for a specific extension |
picrs sync |
Download missing extension files |
picrs sync --force |
Delete all cached extensions and re-download from scratch |
pic-rs/
├── src/
│ ├── picrs-cli/ # CLI application (clap-based)
│ ├── picrs-core/ # Core library: config, arrangements, downloader, path management
│ └── picrs_extension_rt/ # WASM runtime: loads & executes extensions via Wasmtime
├── picrs_extension_api/ # Extension SDK: WIT bindings & helpers for extension authors
├── extension-template/ # Template project for creating new extensions
├── Cargo.toml # Workspace manifest
└── LICENSE # MIT License
| Crate | Description |
|---|---|
picrs-cli |
Command-line interface built with clap. Parses commands and delegates to picrs-core. |
picrs-core |
Core logic including configuration management (~/.picrs/picrs.toml), the arrangement graph engine, extension instance management, and the extension downloader. |
picrs_extension_rt |
WebAssembly runtime powered by Wasmtime 34.0 with WASI P2 and HTTP support. Loads .wasm components and exposes the WIT interface as Rust methods. |
picrs_extension_api |
SDK for extension developers. Provides the Extension trait, register_extension! macro, WIT type re-exports, and HTTP utilities via waki. |
Extensions are WASM components that implement the picrs:extension-api/extension WIT interface.
Copy the extension-template/ directory and update extension.toml with your extension's metadata:
id = "my_extension"
name = "My Extension"
version = "0.1.0"
description = "A short description of what this extension does"
license = "MIT"
repo = "https://github.com/user/my-extension"
author = ["Your Name <you@example.com>"]
envs = [
["API_TOKEN", "Your API token for the service"],
["BASE_URL", "Base URL of the upload endpoint"]
]use picrs_extension_api::{register_extension, DataCtx, DataType, Extension as Ext, ExtensionMetadata};
struct MyExtension;
impl Ext for MyExtension {
fn get_metadata() -> ExtensionMetadata {
ExtensionMetadata::metadata_from_toml(include_str!("../extension.toml")).unwrap()
}
fn get_env_name_description() -> Result<Vec<(String, String)>, String> {
Ok(ExtensionMetadata::get_env_name_description(include_str!("../extension.toml")).unwrap())
}
fn execute_image(ctx_input: DataCtx, env_vars: Vec<(String, String)>) -> Result<DataCtx, String> {
// Your upload/processing logic here
Ok(DataCtx {
data_type: DataType::ImageUrl,
content: String::from("https://example.com/uploaded.jpg"),
})
}
// ... implement remaining trait methods
}
register_extension!(MyExtension);Extensions must be compiled as WASM components targeting wasm32-wasip2:
cargo build --target wasm32-wasip2 --releaseThe resulting .wasm file can be installed locally or published to the extension center.
The full extension interface is defined in picrs_extension_api/wit/picrs.wit:
| Function | Description |
|---|---|
get-metadata |
Return extension metadata (id, name, version, author, etc.) |
get-env-name-description |
Return required environment variable names and descriptions |
execute-image |
Process/upload an image; receives a DataCtx and returns a DataCtx |
remove-image |
Remove a previously uploaded image by ID |
image-max-size-bytes |
Query the maximum allowed image size |
free-space-bytes |
Query remaining storage space in bytes |
free-space-percent |
Query remaining storage space as a percentage |
Extensions communicate via DataCtx, which contains a DataType enum and a content string:
| DataType | Description |
|---|---|
image-url |
A URL pointing to the image |
image-path |
A local file path to the image |
image-base64 |
Base64-encoded image data |
arrange-choice |
A numeric index to select the next node in the arrangement graph (for conditional branching) |
pic-rs stores all configuration and cached data under ~/.picrs/:
~/.picrs/
├── picrs.toml # Main configuration file
├── extensions/ # Cached .wasm extension files
│ └── my_extension.wasm
└── environments/ # Per-extension environment variable files
└── my_extension.toml
# Extension declarations
[extensions.my_uploader]
version = "0.1.0" # From central repository
[extensions.custom_processor]
remote = "https://example.com/proc.wasm" # From URL
[extensions.local_ext]
local = "/path/to/extension.wasm" # From local path
# Arrangement graph — defines the processing pipeline
[arrangements]
input = { extensions = ["custom_processor"] }
custom_processor = { extensions = ["my_uploader"] }
my_uploader = { extensions = ["output"] }This project is licensed under the MIT License.
April — zhao@zhaocloud.work