{
  "schemaVersion": "1.0",
  "item": {
    "slug": "phoenix-api-gen",
    "name": "Phoenix API Generator",
    "source": "tencent",
    "type": "skill",
    "category": "开发工具",
    "sourceUrl": "https://clawhub.ai/gchapim/phoenix-api-gen",
    "canonicalUrl": "https://clawhub.ai/gchapim/phoenix-api-gen",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/phoenix-api-gen",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=phoenix-api-gen",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "installMethod": "Manual import",
    "extraction": "Extract archive",
    "prerequisites": [
      "OpenClaw"
    ],
    "packageFormat": "ZIP package",
    "includedAssets": [
      "references/test-patterns.md",
      "references/phoenix-conventions.md",
      "references/ecto-patterns.md",
      "SKILL.md"
    ],
    "primaryDoc": "SKILL.md",
    "quickSetup": [
      "Download the package from Yavira.",
      "Extract the archive and review SKILL.md first.",
      "Import or place the package into your OpenClaw setup."
    ],
    "agentAssist": {
      "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
      "steps": [
        "Download the package from Yavira.",
        "Extract it into a folder your agent can access.",
        "Paste one of the prompts below and point your agent at the extracted folder."
      ],
      "prompts": [
        {
          "label": "New install",
          "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
        },
        {
          "label": "Upgrade existing",
          "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
        }
      ]
    },
    "sourceHealth": {
      "source": "tencent",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-04-30T16:55:25.780Z",
      "expiresAt": "2026-05-07T16:55:25.780Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=network",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=network",
        "contentDisposition": "attachment; filename=\"network-1.0.0.zip\"",
        "redirectLocation": null,
        "bodySnippet": null
      },
      "scope": "source",
      "summary": "Source download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this source.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/phoenix-api-gen"
    },
    "validation": {
      "installChecklist": [
        "Use the Yavira download entry.",
        "Review SKILL.md after the package is downloaded.",
        "Confirm the extracted package contains the expected setup assets."
      ],
      "postInstallChecks": [
        "Confirm the extracted package includes the expected docs or setup files.",
        "Validate the skill or prompts are available in your target agent workspace.",
        "Capture any manual follow-up steps the agent could not complete."
      ]
    },
    "downloadPageUrl": "https://openagent3.xyz/downloads/phoenix-api-gen",
    "agentPageUrl": "https://openagent3.xyz/skills/phoenix-api-gen/agent",
    "manifestUrl": "https://openagent3.xyz/skills/phoenix-api-gen/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/phoenix-api-gen/agent.md"
  },
  "agentAssist": {
    "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
    "steps": [
      "Download the package from Yavira.",
      "Extract it into a folder your agent can access.",
      "Paste one of the prompts below and point your agent at the extracted folder."
    ],
    "prompts": [
      {
        "label": "New install",
        "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
      },
      {
        "label": "Upgrade existing",
        "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
      }
    ]
  },
  "documentation": {
    "source": "clawhub",
    "primaryDoc": "SKILL.md",
    "sections": [
      {
        "title": "From OpenAPI YAML",
        "body": "Parse the OpenAPI spec — extract paths, schemas, request/response bodies.\nMap each schema to an Ecto schema + migration.\nMap each path to a controller action; group by resource context.\nGenerate auth plugs from securitySchemes.\nGenerate ExUnit tests covering happy path + validation errors."
      },
      {
        "title": "From Natural Language",
        "body": "Extract resources, fields, types, and relationships from the description.\nInfer context boundaries (group related resources).\nGenerate schemas, migrations, controllers, views, router, and tests.\nAsk the user to confirm before writing files."
      },
      {
        "title": "File Generation Order",
        "body": "Migrations (timestamps prefix: YYYYMMDDHHMMSS)\nEcto schemas + changesets\nContext modules (CRUD functions)\nControllers + FallbackController\nJSON renderers (Phoenix 1.7+ *JSON modules, or *View for older)\nRouter scope + pipelines\nAuth plugs\nTests + factories"
      },
      {
        "title": "Phoenix Conventions",
        "body": "See references/phoenix-conventions.md for project structure, naming, context patterns.\n\nKey rules:\n\nOne context per bounded domain (e.g., Accounts, Billing, Notifications).\nContext is the public API — controllers never call Repo directly.\nSchemas live under contexts: MyApp.Accounts.User.\nControllers delegate to contexts; return {:ok, resource} or {:error, changeset}.\nUse FallbackController with action_fallback/1 to handle error tuples."
      },
      {
        "title": "Ecto Patterns",
        "body": "See references/ecto-patterns.md for schema, changeset, migration details.\n\nKey rules:\n\nAlways use timestamps(type: :utc_datetime_usec).\nBinary IDs: @primary_key {:id, :binary_id, autogenerate: true} + @foreign_key_type :binary_id.\nSeparate create_changeset/2 and update_changeset/2 when create/update fields differ.\nValidate required fields, formats, and constraints in changesets — not in controllers."
      },
      {
        "title": "Multi-Tenancy",
        "body": "Add tenant_id :binary_id to every tenant-scoped table. Pattern:\n\n# In context\ndef list_resources(tenant_id) do\n  Resource\n  |> where(tenant_id: ^tenant_id)\n  |> Repo.all()\nend\n\n# In plug — extract tenant from conn and assign\ndefmodule MyAppWeb.Plugs.SetTenant do\n  import Plug.Conn\n  def init(opts), do: opts\n  def call(conn, _opts) do\n    tenant_id = get_req_header(conn, \"x-tenant-id\") |> List.first()\n    assign(conn, :tenant_id, tenant_id)\n  end\nend\n\nAlways add a composite index on [:tenant_id, <resource_id or lookup field>]."
      },
      {
        "title": "API Key",
        "body": "defmodule MyAppWeb.Plugs.ApiKeyAuth do\n  import Plug.Conn\n  def init(opts), do: opts\n  def call(conn, _opts) do\n    with [key] <- get_req_header(conn, \"x-api-key\"),\n         {:ok, account} <- Accounts.authenticate_api_key(key) do\n      assign(conn, :current_account, account)\n    else\n      _ -> conn |> send_resp(401, \"Unauthorized\") |> halt()\n    end\n  end\nend"
      },
      {
        "title": "Bearer Token",
        "body": "defmodule MyAppWeb.Plugs.BearerAuth do\n  import Plug.Conn\n  def init(opts), do: opts\n  def call(conn, _opts) do\n    with [\"Bearer \" <> token] <- get_req_header(conn, \"authorization\"),\n         {:ok, claims} <- MyApp.Token.verify(token) do\n      assign(conn, :current_user, claims)\n    else\n      _ -> conn |> send_resp(401, \"Unauthorized\") |> halt()\n    end\n  end\nend"
      },
      {
        "title": "Router Structure",
        "body": "scope \"/api/v1\", MyAppWeb do\n  pipe_through [:api, :authenticated]\n\n  resources \"/users\", UserController, except: [:new, :edit]\n  resources \"/teams\", TeamController, except: [:new, :edit] do\n    resources \"/members\", MemberController, only: [:index, :create, :delete]\n  end\nend"
      },
      {
        "title": "Test Generation",
        "body": "See references/test-patterns.md for ExUnit, Mox, factory patterns.\n\nKey rules:\n\nUse async: true on all tests that don't share state.\nUse Ecto.Adapters.SQL.Sandbox for DB isolation.\nFactory module using ex_machina or hand-rolled build/1, insert/1.\nTest contexts and controllers separately.\nFor controllers: test status codes, response body shape, and error cases.\nMock external services with Mox — define behaviours, set expectations in test."
      },
      {
        "title": "Controller Test Template",
        "body": "defmodule MyAppWeb.UserControllerTest do\n  use MyAppWeb.ConnCase, async: true\n\n  import MyApp.Factory\n\n  setup %{conn: conn} do\n    user = insert(:user)\n    conn = put_req_header(conn, \"authorization\", \"Bearer #{token_for(user)}\")\n    {:ok, conn: conn, user: user}\n  end\n\n  describe \"index\" do\n    test \"lists users\", %{conn: conn} do\n      conn = get(conn, ~p\"/api/v1/users\")\n      assert %{\"data\" => users} = json_response(conn, 200)\n      assert is_list(users)\n    end\n  end\n\n  describe \"create\" do\n    test \"returns 201 with valid params\", %{conn: conn} do\n      params = params_for(:user)\n      conn = post(conn, ~p\"/api/v1/users\", user: params)\n      assert %{\"data\" => %{\"id\" => _}} = json_response(conn, 201)\n    end\n\n    test \"returns 422 with invalid params\", %{conn: conn} do\n      conn = post(conn, ~p\"/api/v1/users\", user: %{})\n      assert json_response(conn, 422)[\"errors\"] != %{}\n    end\n  end\nend"
      },
      {
        "title": "JSON Renderer (Phoenix 1.7+)",
        "body": "defmodule MyAppWeb.UserJSON do\n  def index(%{users: users}), do: %{data: for(u <- users, do: data(u))}\n  def show(%{user: user}), do: %{data: data(user)}\n\n  defp data(user) do\n    %{\n      id: user.id,\n      email: user.email,\n      inserted_at: user.inserted_at\n    }\n  end\nend"
      },
      {
        "title": "Checklist Before Writing",
        "body": "Migrations use timestamps(type: :utc_datetime_usec)\n Binary IDs configured if project uses UUIDs\n Tenant scoping applied where needed\n Auth plug wired in router pipeline\n FallbackController handles {:error, changeset} and {:error, :not_found}\n Tests cover 200, 201, 404, 422 status codes\n Factory defined for each schema"
      }
    ],
    "body": "Phoenix API Generator\nWorkflow\nFrom OpenAPI YAML\nParse the OpenAPI spec — extract paths, schemas, request/response bodies.\nMap each schema to an Ecto schema + migration.\nMap each path to a controller action; group by resource context.\nGenerate auth plugs from securitySchemes.\nGenerate ExUnit tests covering happy path + validation errors.\nFrom Natural Language\nExtract resources, fields, types, and relationships from the description.\nInfer context boundaries (group related resources).\nGenerate schemas, migrations, controllers, views, router, and tests.\nAsk the user to confirm before writing files.\nFile Generation Order\nMigrations (timestamps prefix: YYYYMMDDHHMMSS)\nEcto schemas + changesets\nContext modules (CRUD functions)\nControllers + FallbackController\nJSON renderers (Phoenix 1.7+ *JSON modules, or *View for older)\nRouter scope + pipelines\nAuth plugs\nTests + factories\nPhoenix Conventions\n\nSee references/phoenix-conventions.md for project structure, naming, context patterns.\n\nKey rules:\n\nOne context per bounded domain (e.g., Accounts, Billing, Notifications).\nContext is the public API — controllers never call Repo directly.\nSchemas live under contexts: MyApp.Accounts.User.\nControllers delegate to contexts; return {:ok, resource} or {:error, changeset}.\nUse FallbackController with action_fallback/1 to handle error tuples.\nEcto Patterns\n\nSee references/ecto-patterns.md for schema, changeset, migration details.\n\nKey rules:\n\nAlways use timestamps(type: :utc_datetime_usec).\nBinary IDs: @primary_key {:id, :binary_id, autogenerate: true} + @foreign_key_type :binary_id.\nSeparate create_changeset/2 and update_changeset/2 when create/update fields differ.\nValidate required fields, formats, and constraints in changesets — not in controllers.\nMulti-Tenancy\n\nAdd tenant_id :binary_id to every tenant-scoped table. Pattern:\n\n# In context\ndef list_resources(tenant_id) do\n  Resource\n  |> where(tenant_id: ^tenant_id)\n  |> Repo.all()\nend\n\n# In plug — extract tenant from conn and assign\ndefmodule MyAppWeb.Plugs.SetTenant do\n  import Plug.Conn\n  def init(opts), do: opts\n  def call(conn, _opts) do\n    tenant_id = get_req_header(conn, \"x-tenant-id\") |> List.first()\n    assign(conn, :tenant_id, tenant_id)\n  end\nend\n\n\nAlways add a composite index on [:tenant_id, <resource_id or lookup field>].\n\nAuth Plugs\nAPI Key\ndefmodule MyAppWeb.Plugs.ApiKeyAuth do\n  import Plug.Conn\n  def init(opts), do: opts\n  def call(conn, _opts) do\n    with [key] <- get_req_header(conn, \"x-api-key\"),\n         {:ok, account} <- Accounts.authenticate_api_key(key) do\n      assign(conn, :current_account, account)\n    else\n      _ -> conn |> send_resp(401, \"Unauthorized\") |> halt()\n    end\n  end\nend\n\nBearer Token\ndefmodule MyAppWeb.Plugs.BearerAuth do\n  import Plug.Conn\n  def init(opts), do: opts\n  def call(conn, _opts) do\n    with [\"Bearer \" <> token] <- get_req_header(conn, \"authorization\"),\n         {:ok, claims} <- MyApp.Token.verify(token) do\n      assign(conn, :current_user, claims)\n    else\n      _ -> conn |> send_resp(401, \"Unauthorized\") |> halt()\n    end\n  end\nend\n\nRouter Structure\nscope \"/api/v1\", MyAppWeb do\n  pipe_through [:api, :authenticated]\n\n  resources \"/users\", UserController, except: [:new, :edit]\n  resources \"/teams\", TeamController, except: [:new, :edit] do\n    resources \"/members\", MemberController, only: [:index, :create, :delete]\n  end\nend\n\nTest Generation\n\nSee references/test-patterns.md for ExUnit, Mox, factory patterns.\n\nKey rules:\n\nUse async: true on all tests that don't share state.\nUse Ecto.Adapters.SQL.Sandbox for DB isolation.\nFactory module using ex_machina or hand-rolled build/1, insert/1.\nTest contexts and controllers separately.\nFor controllers: test status codes, response body shape, and error cases.\nMock external services with Mox — define behaviours, set expectations in test.\nController Test Template\ndefmodule MyAppWeb.UserControllerTest do\n  use MyAppWeb.ConnCase, async: true\n\n  import MyApp.Factory\n\n  setup %{conn: conn} do\n    user = insert(:user)\n    conn = put_req_header(conn, \"authorization\", \"Bearer #{token_for(user)}\")\n    {:ok, conn: conn, user: user}\n  end\n\n  describe \"index\" do\n    test \"lists users\", %{conn: conn} do\n      conn = get(conn, ~p\"/api/v1/users\")\n      assert %{\"data\" => users} = json_response(conn, 200)\n      assert is_list(users)\n    end\n  end\n\n  describe \"create\" do\n    test \"returns 201 with valid params\", %{conn: conn} do\n      params = params_for(:user)\n      conn = post(conn, ~p\"/api/v1/users\", user: params)\n      assert %{\"data\" => %{\"id\" => _}} = json_response(conn, 201)\n    end\n\n    test \"returns 422 with invalid params\", %{conn: conn} do\n      conn = post(conn, ~p\"/api/v1/users\", user: %{})\n      assert json_response(conn, 422)[\"errors\"] != %{}\n    end\n  end\nend\n\nJSON Renderer (Phoenix 1.7+)\ndefmodule MyAppWeb.UserJSON do\n  def index(%{users: users}), do: %{data: for(u <- users, do: data(u))}\n  def show(%{user: user}), do: %{data: data(user)}\n\n  defp data(user) do\n    %{\n      id: user.id,\n      email: user.email,\n      inserted_at: user.inserted_at\n    }\n  end\nend\n\nChecklist Before Writing\n Migrations use timestamps(type: :utc_datetime_usec)\n Binary IDs configured if project uses UUIDs\n Tenant scoping applied where needed\n Auth plug wired in router pipeline\n FallbackController handles {:error, changeset} and {:error, :not_found}\n Tests cover 200, 201, 404, 422 status codes\n Factory defined for each schema"
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/gchapim/phoenix-api-gen",
    "publisherUrl": "https://clawhub.ai/gchapim/phoenix-api-gen",
    "owner": "gchapim",
    "version": "1.0.0",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/phoenix-api-gen",
    "downloadUrl": "https://openagent3.xyz/downloads/phoenix-api-gen",
    "agentUrl": "https://openagent3.xyz/skills/phoenix-api-gen/agent",
    "manifestUrl": "https://openagent3.xyz/skills/phoenix-api-gen/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/phoenix-api-gen/agent.md"
  }
}