Token导航 LogoToken导航

Agent Skill · 运维和基础设施

phoenix-uploads(Phoenix uploads 命令行)

phoenix-uploads 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要围绕仓库状态、代码变更或协作事项进行整理时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

phoenix-uploads 是运维和基础设施类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为需要联网;许可证信息还不明确,第三方扫描已通过,按安装量在运维和基础设施类的 2,449 个 Skill 里排第 1,980。

总安装 326周安装 14GitHub Stars 119下载 114核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
unknown

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:phoenix-uploads(Phoenix uploads 命令行)
来源仓库:https://github.com/j-morgan6/elixir-claude-optimization
仓库路径:skills/phoenix-uploads
安装命令:
npx skills add https://github.com/j-morgan6/elixir-claude-optimization --skill phoenix-uploads
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/j-morgan6/elixir-claude-optimization --skill phoenix-uploads

这个 Skill 是做什么的

运维和基础设施类 Skill 处理部署、监控、日志和云资源,多数需要较高的执行权限。

用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息。适合在 Codex、Claude、Cursor、Gemini CLI 中围绕仓库状态、代码变更或协作事项进行整理。通过 npx 命令从指定 GitHub 仓库安装,需确认权限范围和维护状态。

使用前建议核实是否会触发联网、命令执行或文件读写操作。phoenix-uploads 属于运维和基础设施类 Skill,可作为该场景下的辅助能力补充。

来源仓库内路径:skills/phoenix-uploads

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/j-morgan6/elixir-claude-optimization --skill p…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要需要联网,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

phoenix-uploads 怎么安装?

在终端执行 npx skills add https://github.com/j-morgan6/elixir-claude-optimization --skill phoenix-upl…。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

phoenix-uploads 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

phoenix-uploads 需要什么权限?

权限级别是需要联网。该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

phoenix-uploads 是免费的吗?

公开资料没有标注明确许可证,收费和商用限制都无法确认。正式使用前请到来源仓库核对授权说明。

phoenix-uploads 用的人多吗?

公开数据是累计安装 326 次,近一周安装 14 次,下载 114 次,GitHub 119 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

phoenix-uploads 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 第三方扫描已通过,常见的高风险行为没有命中。

先想清楚的地方

  • 许可证没有明确标注,能不能商用、能不能再分发都不确定。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理运维和基础设施类任务、又不想每次都手写提示词的人。

不太适合

  • 需要明确授权才能用到公司项目里的场景。
  • 需要厂商级技术支持和 SLA 的生产系统。

运维和基础设施类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
brainstorming

头脑风暴 · Codex、Claude

3,148,776173,131操作浏览器MIT通过
copywriting

文案写作 · Codex、Claude

1,996,72425,583需要联网MIT通过
openclaw-secure-linux-cloud

OpenClaw secure linux cloud 命令行 · Codex、Claude

1,853,08840接触敏感数据未明确未通过
xget

xget 命令行 · Codex、Claude

1,799,28040执行命令未明确未通过
requesting-code-review

代码审查请求 · Codex、Claude

1,590,144173,103权限未标注MIT通过
phoenix-uploads

Phoenix uploads 命令行 · 本页

326119需要联网未明确通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

Phoenix File Uploads

RULES — Follow these with no exceptions

  1. Use manual uploads (NOT auto_upload: true) for form submission patterns
  2. Always add upload directory to static_paths() — files won't be accessible without this
  3. Handle upload errors — display error_to_string/1 output in templates
  4. Create upload directories with File.mkdir_p! before saving files
  5. Generate unique filenames — prevent collisions and path traversal attacks
  6. Validate file types server-side — never trust client MIME types
  7. Restart server after changing static_paths() — changes don't apply until restart

Upload Configuration

Manual Upload (Recommended for Most Cases)

allow_upload(:upload_name,
  accept: ~w(.jpg .jpeg .png .pdf),
  max_entries: 10,
  max_file_size: 10_000_000
)

Template Requirements:

  • Form with phx-submit event
  • Submit button to trigger upload
  • <.live_file_input> component
  • Progress indicators

Auto Upload (Advanced - Use Sparingly)

Only use auto_upload: true when:

  • Files should upload immediately on selection
  • You have handle_progress/3 callback
  • You consume entries outside form submission

⚠️ Never use auto_upload: true with form submission patterns!

Complete Upload Pattern

LiveView Module

@impl true
def mount(_params, _session, socket) do
  socket =
    socket
    |> assign(:uploaded_files, [])
    |> allow_upload(:photos,
         accept: ~w(.jpg .jpeg .png),
         max_entries: 5,
         max_file_size: 10_000_000
       )

  {:ok, socket}
end

@impl true
def handle_event("validate", _params, socket) do
  {:noreply, socket}
end

@impl true
def handle_event("save", _params, socket) do
  uploaded_files =
    consume_uploaded_entries(socket, :photos, fn %{path: path}, entry ->
      dest = Path.join(["priv", "static", "uploads", safe_filename(entry.client_name)])
      File.mkdir_p!(Path.dirname(dest))
      File.cp!(path, dest)
      {:ok, ~s(/uploads/#{Path.basename(dest)})}
    end)

  # Save to database with uploaded_files paths
  {:noreply, assign(socket, :uploaded_files, uploaded_files)}
end

defp safe_filename(original_name) do
  # Generate unique name to prevent collisions and attacks
  ext = Path.extname(original_name)
  "#{Ecto.UUID.generate()}#{ext}"
end

Template

<.simple_form for={@form} phx-change="validate" phx-submit="save">
  <.input field={@form[:title]} label="Title" />

  <div>
    <.label>Upload Photos</.label>
    <.live_file_input upload={@uploads.photos} />
  </div>

  <!-- Upload errors -->
  <%= for err <- upload_errors(@uploads.photos) do %>
    <p class="error"><%= error_to_string(err) %></p>
  <% end %>

  <!-- Entry previews and errors -->
  <%= for entry <- @uploads.photos.entries do %>
    <div>
      <.live_img_preview entry={entry} />
      <progress value={entry.progress} max="100"><%= entry.progress %>%</progress>

      <%= for err <- upload_errors(@uploads.photos, entry) do %>
        <p class="error"><%= error_to_string(err) %></p>
      <% end %>
    </div>
  <% end %>

  <:actions>
    <.button phx-disable-with="Uploading...">Upload</.button>
  </:actions>
</.simple_form>

Error Handling

Always implement error_to_string/1:

defp error_to_string(:too_large), do: "File is too large (max 10MB)"
defp error_to_string(:not_accepted), do: "File type not accepted"
defp error_to_string(:too_many_files), do: "Too many files selected"
defp error_to_string(:external_client_failure), do: "Upload failed"

Static File Serving Configuration

Critical: After uploading files, they MUST be served via static_paths.

Step 1: Define static_paths/0

# lib/my_app_web.ex
def static_paths do
  ~w(assets fonts images uploads favicon.ico robots.txt)
end

Rule: Any directory you serve files from must be listed here.

Step 2: Verify Plug.Static Configuration

# lib/my_app_web/endpoint.ex
plug Plug.Static,
  at: "/",
  from: :my_app,
  gzip: false,
  only: MyAppWeb.static_paths()

File Structure

Static files must be in priv/static/:

my_app/
├── priv/
│   └── static/
│       ├── assets/        # CSS, JS (from esbuild)
│       ├── uploads/       # User uploads
│       │   ├── image1.jpg
│       │   └── doc.pdf
│       └── favicon.ico

Serving Uploaded Files

From Templates

<!-- Image -->
<img src="/uploads/photo.jpg" alt="Photo" />

<!-- Document download -->
<.link href="/uploads/document.pdf" download>Download</.link>

From Controllers

def download(conn, %{"filename" => filename}) do
  # Sanitize filename to prevent path traversal
  safe_name = Path.basename(filename)
  path = Path.join(["priv", "static", "uploads", safe_name])

  if File.exists?(path) and String.starts_with?(path, "priv/static/uploads") do
    send_download(conn, {:file, path}, filename: safe_name)
  else
    conn
    |> put_status(:not_found)
    |> text("File not found")
  end
end

Image Previews

For image uploads, show previews:

<%= for entry <- @uploads.photos.entries do %>
  <div class="preview">
    <.live_img_preview entry={entry} width={200} />
    <button type="button" phx-click="cancel-upload" phx-value-ref={entry.ref}>
      Cancel
    </button>
  </div>
<% end %>
@impl true
def handle_event("cancel-upload", %{"ref" => ref}, socket) do
  {:noreply, cancel_upload(socket, :photos, ref)}
end

Multiple Upload Slots

You can have multiple upload configurations:

socket
|> allow_upload(:photos, accept: ~w(.jpg .jpeg .png), max_entries: 5)
|> allow_upload(:documents, accept: ~w(.pdf .docx), max_entries: 3)

External Storage (S3, etc.)

For external storage, use the :external option:

allow_upload(:photos,
  accept: ~w(.jpg .jpeg .png),
  max_entries: 5,
  external: &presign_upload/2
)

defp presign_upload(entry, socket) do
  # Generate presigned URL for S3
  {:ok, %{uploader: "S3", key: key, url: url}, socket}
end

Troubleshooting

Files Return 404

Problem: Accessing /uploads/file.jpg returns 404

Fixes:

  1. Check static_paths includes "uploads"
  2. Verify file exists in priv/static/uploads/
  3. Restart server after changing static_paths
  4. Check file permissions (should be readable)
# Debug helper
def check_static_file(path) do
  full_path = Path.join(["priv", "static", path])

  cond do
    not File.exists?(full_path) ->
      "File does not exist: #{full_path}"

    not File.readable?(full_path) ->
      "File exists but not readable: #{full_path}"

    true ->
      "File OK: #{full_path}"
  end
end

Files Work in Dev but Not Production

Problem: Files serve correctly locally but fail in production

Fixes:

  1. Run mix phx.digest before deployment:
MIX_ENV=prod mix phx.digest
  1. Check production endpoint config:
# config/runtime.exs
config :my_app, MyAppWeb.Endpoint,
  cache_static_manifest: "priv/static/cache_manifest.json"
  1. Ensure files are deployed:
# Check your deployment includes priv/static/

Security Best Practices

1. Sanitize File Paths

Never use user input directly in file paths:

# ❌ DANGEROUS - Path traversal attack
def serve_file(conn, %{"path" => user_path}) do
  send_file(conn, 200, "priv/static/#{user_path}")
end

# ✅ SAFE - Validate and constrain
def serve_file(conn, %{"filename" => filename}) do
  safe_name = Path.basename(filename)  # Remove directory traversal
  path = Path.join(["priv", "static", "uploads", safe_name])

  if File.exists?(path) and String.starts_with?(path, "priv/static/uploads") do
    send_file(conn, 200, path)
  else
    send_resp(conn, 404, "Not found")
  end
end

2. Validate File Types

Don't trust client MIME types:

def validate_file_type(path) do
  # Use a library like `file_type` to verify actual content
  case FileType.from_path(path) do
    {:ok, %{mime_type: "image/" <> _}} -> :ok
    _ -> {:error, :invalid_type}
  end
end

3. Generate Unique Filenames

Prevent collisions and path traversal:

defp safe_filename(original_name) do
  ext = Path.extname(original_name)
  "#{Ecto.UUID.generate()}#{ext}"
end

4. Limit File Sizes

Set reasonable limits:

allow_upload(:photos,
  accept: ~w(.jpg .jpeg .png),
  max_entries: 5,
  max_file_size: 10_000_000  # 10MB
)

5. Content-Type Headers

Set proper content types to prevent XSS:

def serve_image(conn, %{"id" => id}) do
  image = get_image!(id)

  conn
  |> put_resp_header("content-type", image.content_type)
  |> put_resp_header("x-content-type-options", "nosniff")
  |> send_file(200, image.path)
end

Testing Uploads

test "uploads image successfully", %{conn: conn} do
  {:ok, lv, _html} = live(conn, "/gallery")

  image =
    file_input(lv, "#upload-form", :photos, [
      %{
        name: "test.png",
        content: File.read!("test/fixtures/test.png"),
        type: "image/png"
      }
    ])

  assert render_upload(image, "test.png") =~ "100%"

  lv
  |> form("#upload-form")
  |> render_submit()

  assert has_element?(lv, "img[alt='test.png']")
end

Common Pitfalls

❌ Using auto_upload with form submit

# DON'T DO THIS
allow_upload(:photos, auto_upload: true, ...)

def handle_event("save", _params, socket) do
  consume_uploaded_entries(socket, :photos, ...)  # Won't work!
end

✅ Use manual upload instead

# DO THIS
allow_upload(:photos, ...)

def handle_event("save", _params, socket) do
  consume_uploaded_entries(socket, :photos, ...)  # Works!
end

❌ Not handling upload errors

<!-- Missing error display -->
<.live_file_input upload={@uploads.photos} />

✅ Always show errors

<.live_file_input upload={@uploads.photos} />
<%= for err <- upload_errors(@uploads.photos) do %>
  <p class="error"><%= error_to_string(err) %></p>
<% end %>

❌ Forgetting static_paths

# File saved to priv/static/uploads/
# But "uploads" not in static_paths
def static_paths, do: ~w(assets favicon.ico)  # Missing uploads!

✅ Include upload directory

def static_paths, do: ~w(assets uploads favicon.ico)

Quick Reference

# 1. Add directory to static_paths
def static_paths, do: ~w(assets uploads favicon.ico)

# 2. Create directory structure
priv/static/uploads/

# 3. Configure upload in mount
allow_upload(:photos, accept: ~w(.jpg .png), max_entries: 5)

# 4. Consume in handle_event
consume_uploaded_entries(socket, :photos, fn %{path: path}, entry ->
  dest = Path.join(["priv", "static", "uploads", safe_filename(entry.client_name)])
  File.mkdir_p!(Path.dirname(dest))
  File.cp!(path, dest)
  {:ok, "/uploads/#{Path.basename(dest)}"}
end)

# 5. Reference in templates
<img src="/uploads/#{filename}" />

# 6. Restart server to apply changes
mix phx.server

Testing

When writing tests for file upload functionality, invoke elixir-phoenix-guide:testing-essentials before writing any _test.exs file.

平台分布

Codex

33.59%
按下载量换算38

Claude

32.51%
按下载量换算37

Cursor

18.26%
按下载量换算21

Gemini CLI

10.27%
按下载量换算12

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。目前只有一个公开来源,建议同时核对源仓库。

继续浏览同类 Skills