defmodule Mix.Tasks.Hf.Upload do
@shortdoc "Upload a file or folder to the HuggingFace Hub"
@moduledoc """
Uploads a file or folder to a HuggingFace Hub repository.
$ mix hf.upload my-org/my-model ./model.safetensors
$ mix hf.upload my-org/my-model ./outputs/ --path-in-repo models/v2/
$ mix hf.upload --type dataset my-org/my-dataset ./data/
## Options
* `--type` / `-t` — repo type: `model`, `dataset`, `space` (default: `model`)
* `--path-in-repo` — destination path in the repo (default: same as local name)
* `--commit-message` / `-m` — commit message
* `--revision` / `-r` — branch to push to (default: `main`)
* `--token` — HF API token
"""
use Mix.Task
@impl Mix.Task
def run(args) do
{opts, argv, _} =
OptionParser.parse(args,
aliases: [t: :type, m: :commit_message, r: :revision],
strict: [
type: :string,
path_in_repo: :string,
commit_message: :string,
revision: :string,
token: :string
]
)
[repo_id, local_path | _] =
case argv do
[_, _ | _] = v -> v
_ -> Mix.raise("Usage: mix hf.upload REPO_ID LOCAL_PATH")
end
token = opts[:token] || HuggingfaceClient.Config.token()
unless token, do: Mix.raise("Not logged in. Run: mix hf.login --token hf_xxx")
case File.stat!(local_path).type do
:directory -> upload_folder(repo_id, local_path, opts, token)
_ -> upload_file(repo_id, local_path, opts, token)
end
end
defp upload_folder(repo_id, local_path, opts, token) do
Mix.shell().info("Uploading folder #{local_path} to #{repo_id}...")
up_opts = [
folder_path: local_path,
path_in_repo: opts[:path_in_repo] || "",
commit_title: opts[:commit_message] || "Upload via mix hf.upload",
revision: opts[:revision] || "main",
type: String.to_existing_atom(opts[:type] || "model"),
access_token: token
]
case HuggingfaceClient.Hub.Repos.upload_folder(repo_id, up_opts) do
{:ok, commit} -> Mix.shell().info("✓ Uploaded. Commit: #{commit["commitId"] || commit["id"]}")
{:error, err} -> Mix.raise("Upload failed: #{Exception.message(err)}")
end
end
defp upload_file(repo_id, local_path, opts, token) do
dest = opts[:path_in_repo] || Path.basename(local_path)
message = opts[:commit_message] || "Upload #{dest} via mix hf.upload"
Mix.shell().info("Uploading #{local_path} → #{repo_id}/#{dest}...")
up_opts = [
path: dest,
content: File.read!(local_path),
commit_title: message,
revision: opts[:revision] || "main",
type: String.to_existing_atom(opts[:type] || "model"),
access_token: token
]
case HuggingfaceClient.Hub.Commits.upload_file(repo_id, up_opts) do
{:ok, _} -> Mix.shell().info("✓ Uploaded to #{repo_id}/#{dest}")
{:error, err} -> Mix.raise("Upload failed: #{Exception.message(err)}")
end
end
end