[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M72YY9S08VMSBVTZ2WSCTVW5":3,"skill-same:01M72YY9S08VMSBVTZ2WSCTVW5":44},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":18,"linkOnly":19,"markdownContent":20,"markdownBody":21,"frontMatter":22,"refName":23,"commitSha":24,"skillDirectory":25,"forksCount":26,"repositoryHtmlUrl":27,"repositoryHomepageUrl":6,"ownerHtmlUrl":28,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":29,"install":30,"resources":43},"01M72YY9S08VMSBVTZ2WSCTVW5","pinme-auth",null,"Use when a PinMe project (Worker TypeScript) needs to integrate user authentication — creating email\u002Fpassword users, verifying id_tokens, querying user info, or listing users via Identity Platform auth proxy APIs.","developAgent\u002Fbmad-start","developAgent","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F264530239?v=4","01M71J8X6CY24V5ZZHFGETRMSW","01M71J8X739FS28DGQAAY7V4QR",1,"MIT","https:\u002F\u002Fgithub.com\u002FdevelopAgent\u002Fbmad-start\u002Fblob\u002Fmain\u002F.rovodev\u002Fskills\u002Fpinme-auth\u002FSKILL.md",[],[],"2026-11-11 15:11:14",false,"---\nname: pinme-auth\ndescription: Use when a PinMe project (Worker TypeScript) needs to integrate user authentication — creating email\u002Fpassword users, verifying id_tokens, querying user info, or listing users via Identity Platform auth proxy APIs.\n---\n\n# PinMe Worker Auth API Integration\n\nGuides how to call PinMe platform's Identity Platform auth proxy APIs in a PinMe Worker (TypeScript).\n\n## Environment Variables\n\n```typescript\n\u002F\u002F backend\u002Fsrc\u002Fworker.ts\nexport interface Env {\n  DB: D1Database;\n  API_KEY: string;       \u002F\u002F 项目 API Key — 用于所有 auth 接口认证\n  PROJECT_NAME: string;  \u002F\u002F 项目名 — 所有 auth 接口必须同时传递\n  BASE_URL?: string;     \u002F\u002F 可选，默认 https:\u002F\u002Fpinme.cloud\n}\n```\n\n> `API_KEY` 和 `PROJECT_NAME` 是所有 auth 接口的必填凭证，缺一不可。\n\n---\n\n## 认证方式（所有接口通用）\n\n| 参数 | 传递方式 | 必填 | 说明 |\n|------|---------|------|------|\n| `X-API-Key` | 请求头 | 是 | 项目 API Key |\n| `project_name` | Query 参数 | 是 | 必须与 `X-API-Key` 对应同一个项目 |\n\n服务端会先校验这两个字段是否匹配同一个项目，再从项目配置中取出 `tenant_id`，然后转调 Identity Platform。\n\n---\n\n## 通用错误\n\n| 场景 | HTTP | `data.error` |\n|------|------|-------------|\n| 缺少 `X-API-Key` | 401 | `X-API-Key header is required` |\n| 缺少 `project_name` | 400 | `project_name is required` |\n| API Key 和项目不匹配 | 401 | `Invalid API key or project name` |\n| 项目未配置认证租户 | 400 | `Auth service not configured for this project` |\n\n---\n\n## 通用 TypeScript 类型\n\n```typescript\ntype ApiEnvelope\u003CT> = {\n  code: number   \u002F\u002F 200=成功，其他=失败\n  msg: string    \u002F\u002F \"ok\" | \"fail\" | \"invalid param\"\n  data: T\n}\n\ntype ApiErrorData = { error?: string }\n\ntype UserInfo = {\n  uid: string\n  email: string\n  display_name: string\n  photo_url?: string\n  disabled: boolean\n  email_verified: boolean\n}\n```\n\n---\n\n## API 1: 创建用户\n\n**Endpoint:** `POST {BASE_URL}\u002Fapi\u002Fv1\u002Fauth\u002Fcreate_user?project_name={project_name}`\n\n仅用于邮箱密码注册。成功时用户已创建且验证邮件已发出；失败时自动回滚，不会留下僵尸账号。\n\n> 创建成功后用户默认仍是\"未验证\"状态，需点击邮件验证链接后，`verify_token` 才能通过校验。\n\n### 请求体\n\n```json\n{ \"email\": \"alice@example.com\", \"password\": \"Test@12345678\", \"display_name\": \"Alice\" }\n```\n\n| 字段 | 类型 | 必填 |\n|------|------|------|\n| `email` | string | 是 |\n| `password` | string | 是 |\n| `display_name` | string | 否 |\n\n### 错误\n\n| 场景 | HTTP | `data.error` |\n|------|------|-------------|\n| 缺少 email\u002Fpassword | 400 | `email and password are required` |\n| 上游创建失败 | 502 | `Failed to create user` |\n| 发送验证邮件失败 | 500 | `Failed to send verification email. Please try again.` |\n\n### TypeScript 示例\n\n```typescript\nasync function createAuthUser(\n  env: Env,\n  payload: { email: string; password: string; display_name?: string }\n): Promise\u003C{ user?: UserInfo; error?: string }> {\n  const baseUrl = env.BASE_URL ?? 'https:\u002F\u002Fpinme.cloud';\n  const resp = await fetch(\n    `${baseUrl}\u002Fapi\u002Fv1\u002Fauth\u002Fcreate_user?project_name=${encodeURIComponent(env.PROJECT_NAME)}`,\n    {\n      method: 'POST',\n      headers: { 'X-API-Key': env.API_KEY, 'Content-Type': 'application\u002Fjson' },\n      body: JSON.stringify(payload),\n    }\n  );\n  const result = await resp.json() as ApiEnvelope\u003CUserInfo | ApiErrorData>;\n  if (!resp.ok || result.code !== 200) {\n    return { error: (result.data as ApiErrorData)?.error ?? result.msg };\n  }\n  return { user: result.data as UserInfo };\n}\n```\n\n---\n\n## API 2: 校验 id_token\n\n**Endpoint:** `POST {BASE_URL}\u002Fapi\u002Fv1\u002Fauth\u002Fverify_token?project_name={project_name}`\n\n校验前端登录后拿到的 `id_token`（邮箱密码或 Google 登录均适用）。\n\n**注意：** token 合法但邮箱未验证时返回 `403`，不是 `401`。\n\n### 请求体\n\n```json\n{ \"id_token\": \"eyJhbGciOiJSUzI1NiIsImtpZCI6...\" }\n```\n\n### 成功响应 data\n\n```typescript\ntype VerifyTokenData = {\n  uid: string\n  email?: string\n  tenant_id: string\n  claims: Record\u003Cstring, unknown>\n}\n```\n\n### 错误\n\n| 场景 | HTTP | `data.error` |\n|------|------|-------------|\n| 缺少 `id_token` | 400 | `id_token is required` |\n| token 无效或过期 | 401 | `Invalid or expired token` |\n| 邮箱未验证 | 403 | `Email not verified. Please check your inbox and verify your email address.` |\n\n### TypeScript 示例\n\n```typescript\nasync function verifyAuthToken(\n  env: Env,\n  idToken: string\n): Promise\u003C{ uid?: string; email?: string; error?: string; emailNotVerified?: boolean }> {\n  const baseUrl = env.BASE_URL ?? 'https:\u002F\u002Fpinme.cloud';\n  const resp = await fetch(\n    `${baseUrl}\u002Fapi\u002Fv1\u002Fauth\u002Fverify_token?project_name=${encodeURIComponent(env.PROJECT_NAME)}`,\n    {\n      method: 'POST',\n      headers: { 'X-API-Key': env.API_KEY, 'Content-Type': 'application\u002Fjson' },\n      body: JSON.stringify({ id_token: idToken }),\n    }\n  );\n  const result = await resp.json() as ApiEnvelope\u003CVerifyTokenData | ApiErrorData>;\n  if (!resp.ok || result.code !== 200) {\n    const error = (result.data as ApiErrorData)?.error ?? result.msg;\n    return { error, emailNotVerified: resp.status === 403 };\n  }\n  const data = result.data as VerifyTokenData;\n  return { uid: data.uid, email: data.email };\n}\n```\n\n---\n\n## API 3: 查询单个用户\n\n**Endpoint:** `GET {BASE_URL}\u002Fapi\u002Fv1\u002Fauth\u002Fuser?project_name={project_name}&uid={uid}`\n\n### 错误\n\n| 场景 | HTTP | `data.error` |\n|------|------|-------------|\n| 缺少 `uid` | 400 | `uid is required` |\n| 用户不存在 | 404 | `User not found` |\n| 上游查询失败 | 502 | `Failed to get user` |\n\n### TypeScript 示例\n\n```typescript\nasync function getAuthUser(env: Env, uid: string): Promise\u003C{ user?: UserInfo; error?: string }> {\n  const baseUrl = env.BASE_URL ?? 'https:\u002F\u002Fpinme.cloud';\n  const resp = await fetch(\n    `${baseUrl}\u002Fapi\u002Fv1\u002Fauth\u002Fuser?project_name=${encodeURIComponent(env.PROJECT_NAME)}&uid=${encodeURIComponent(uid)}`,\n    { method: 'GET', headers: { 'X-API-Key': env.API_KEY } }\n  );\n  const result = await resp.json() as ApiEnvelope\u003CUserInfo | ApiErrorData>;\n  if (!resp.ok || result.code !== 200) {\n    return { error: (result.data as ApiErrorData)?.error ?? result.msg };\n  }\n  return { user: result.data as UserInfo };\n}\n```\n\n---\n\n## API 4: 列出用户（分页）\n\n**Endpoint:** `GET {BASE_URL}\u002Fapi\u002Fv1\u002Fauth\u002Flist_users?project_name={project_name}`\n\n默认 `max_results=100`，最大 `1000`。通过 `next_page_token` 循环翻页。\n\n### Query 参数\n\n| 参数 | 必填 | 说明 |\n|------|------|------|\n| `project_name` | 是 | 项目名 |\n| `page_token` | 否 | 分页游标 |\n| `max_results` | 否 | 每页数量，1–1000 |\n\n### TypeScript 示例\n\n```typescript\nasync function listAuthUsers(\n  env: Env,\n  options: { pageToken?: string; maxResults?: number } = {}\n): Promise\u003C{ users?: UserInfo[]; nextPageToken?: string; error?: string }> {\n  const baseUrl = env.BASE_URL ?? 'https:\u002F\u002Fpinme.cloud';\n  const url = new URL('\u002Fapi\u002Fv1\u002Fauth\u002Flist_users', baseUrl);\n  url.searchParams.set('project_name', env.PROJECT_NAME);\n  if (options.pageToken) url.searchParams.set('page_token', options.pageToken);\n  if (options.maxResults) url.searchParams.set('max_results', String(options.maxResults));\n\n  const resp = await fetch(url.toString(), { method: 'GET', headers: { 'X-API-Key': env.API_KEY } });\n  const result = await resp.json() as ApiEnvelope\u003C{ users: UserInfo[]; next_page_token?: string } | ApiErrorData>;\n  if (!resp.ok || result.code !== 200) {\n    return { error: (result.data as ApiErrorData)?.error ?? result.msg };\n  }\n  const data = result.data as { users: UserInfo[]; next_page_token?: string };\n  return { users: data.users, nextPageToken: data.next_page_token };\n}\n\n\u002F\u002F 批量遍历所有用户示例\nasync function* iterAllUsers(env: Env) {\n  let pageToken: string | undefined;\n  do {\n    const { users, nextPageToken, error } = await listAuthUsers(env, { pageToken, maxResults: 1000 });\n    if (error) throw new Error(error);\n    for (const user of users ?? []) yield user;\n    pageToken = nextPageToken;\n  } while (pageToken);\n}\n```\n\n---\n\n## 前端集成（Firebase Auth）\n\n`create_worker` 响应中包含 `public_client_config`，前端用它初始化 Firebase Auth SDK。\n\n### 两种 api_key 区分\n\n| 字段 | 用途 | 是否可暴露到浏览器 |\n|------|------|-----------------|\n| `data.api_key` | 项目 API Key，调用本文所有代理接口 | **不能**，只给 Worker\u002F服务端 |\n| `data.public_client_config.auth_api_key` | Firebase Web API Key，初始化前端登录 SDK | 可以 |\n\n### public_client_config 字段说明\n\n| 字段 | 前端用途 |\n|------|---------|\n| `public_client_config.auth_api_key` | `initializeApp({ apiKey })` |\n| `public_client_config.auth_domain` | `initializeApp({ authDomain })` |\n| `public_client_config.auth_project_id` | `initializeApp({ projectId })` |\n| `public_client_config.tenant_id` | `auth.tenantId = config.tenant_id`（必须设置，否则 token 归属错误） |\n\n### 前端 TypeScript 示例\n\n```typescript\nimport { initializeApp } from 'firebase\u002Fapp'\nimport {\n  type Auth,\n  getAuth,\n  GoogleAuthProvider,\n  signInWithEmailAndPassword,\n  signInWithPopup,\n} from 'firebase\u002Fauth'\n\ntype PublicClientConfig = {\n  tenant_id: string\n  auth_api_key: string\n  auth_domain: string\n  auth_project_id: string\n}\n\nexport function createProjectAuth(config: PublicClientConfig): Auth {\n  const app = initializeApp({\n    apiKey: config.auth_api_key,\n    authDomain: config.auth_domain,\n    projectId: config.auth_project_id,\n  })\n  const auth = getAuth(app)\n  auth.tenantId = config.tenant_id  \u002F\u002F 必须设置，确保 token 归属正确租户\n  return auth\n}\n\n\u002F\u002F 邮箱密码登录，返回 id_token\nexport async function loginWithEmail(auth: Auth, email: string, password: string): Promise\u003Cstring> {\n  const credential = await signInWithEmailAndPassword(auth, email, password)\n  return credential.user.getIdToken()\n}\n\n\u002F\u002F Google 登录，返回 id_token\nexport async function loginWithGoogle(auth: Auth): Promise\u003Cstring> {\n  const credential = await signInWithPopup(auth, new GoogleAuthProvider())\n  return credential.user.getIdToken()\n}\n\n\u002F\u002F 用法示例\n\u002F\u002F pinme create 会自动将 public_client_config 写入 frontend\u002Fsrc\u002Futils\u002Fconfig.ts\nimport { public_client_config } from '..\u002Futils\u002Fconfig'\n\nconst auth = createProjectAuth(public_client_config)\nconst idToken = await loginWithGoogle(auth)\n\u002F\u002F 然后把 idToken 发给自己的 Worker，由 Worker 调用 verify_token\n```\n\n> 前端只负责登录和拿 `id_token`，不要直接持有项目 `api_key`。`verify_token` 必须由 Worker\u002F服务端代调。\n> `frontend\u002Fsrc\u002Futils\u002Fconfig.ts` 由 `pinme create` 自动生成，无需手动创建。\n\n---\n\n## 典型调用链路\n\n**邮箱密码注册流程：**\n1. `create_user` → 创建用户并发出验证邮件\n2. 用户点击邮件链接完成验证\n3. 前端登录拿到 `id_token`\n4. `verify_token` → 校验 token，取得 `uid`\n5. 需要时再调 `getAuthUser` 读取完整用户信息\n\n**Google 登录流程：**\n1. 前端完成 Google Sign-In，拿到 `id_token`\n2. `verify_token` → 校验 token（无需调用 `create_user`）\n\n---\n\n## 易错点\n\n| 错误 | 正确做法 |\n|------|---------|\n| 只传 `X-API-Key`，忘记 `project_name` | 每个请求都要同时带 `X-API-Key` header 和 `project_name` query |\n| `verify_token` 返回 403 时当 token 失效处理 | 403 = 邮箱未验证，提示用户检查邮箱；401 才是 token 失效 |\n| `create_user` 成功就认为邮箱已验证 | 创建成功只代表验证邮件已发，用户必须点击后才算验证 |\n| `list_users` 只取第一页 | 有 `next_page_token` 时需继续请求，直到为空 |\n| 成功判断只看 `resp.ok` | 同时判断 `resp.ok && result.code === 200` |\n","# PinMe Worker Auth API Integration\n\nGuides how to call PinMe platform's Identity Platform auth proxy APIs in a PinMe Worker (TypeScript).\n\n## Environment Variables\n\n```typescript\n\u002F\u002F backend\u002Fsrc\u002Fworker.ts\nexport interface Env {\n  DB: D1Database;\n  API_KEY: string;       \u002F\u002F 项目 API Key — 用于所有 auth 接口认证\n  PROJECT_NAME: string;  \u002F\u002F 项目名 — 所有 auth 接口必须同时传递\n  BASE_URL?: string;     \u002F\u002F 可选，默认 https:\u002F\u002Fpinme.cloud\n}\n```\n\n> `API_KEY` 和 `PROJECT_NAME` 是所有 auth 接口的必填凭证，缺一不可。\n\n---\n\n## 认证方式（所有接口通用）\n\n| 参数 | 传递方式 | 必填 | 说明 |\n|------|---------|------|------|\n| `X-API-Key` | 请求头 | 是 | 项目 API Key |\n| `project_name` | Query 参数 | 是 | 必须与 `X-API-Key` 对应同一个项目 |\n\n服务端会先校验这两个字段是否匹配同一个项目，再从项目配置中取出 `tenant_id`，然后转调 Identity Platform。\n\n---\n\n## 通用错误\n\n| 场景 | HTTP | `data.error` |\n|------|------|-------------|\n| 缺少 `X-API-Key` | 401 | `X-API-Key header is required` |\n| 缺少 `project_name` | 400 | `project_name is required` |\n| API Key 和项目不匹配 | 401 | `Invalid API key or project name` |\n| 项目未配置认证租户 | 400 | `Auth service not configured for this project` |\n\n---\n\n## 通用 TypeScript 类型\n\n```typescript\ntype ApiEnvelope\u003CT> = {\n  code: number   \u002F\u002F 200=成功，其他=失败\n  msg: string    \u002F\u002F \"ok\" | \"fail\" | \"invalid param\"\n  data: T\n}\n\ntype ApiErrorData = { error?: string }\n\ntype UserInfo = {\n  uid: string\n  email: string\n  display_name: string\n  photo_url?: string\n  disabled: boolean\n  email_verified: boolean\n}\n```\n\n---\n\n## API 1: 创建用户\n\n**Endpoint:** `POST {BASE_URL}\u002Fapi\u002Fv1\u002Fauth\u002Fcreate_user?project_name={project_name}`\n\n仅用于邮箱密码注册。成功时用户已创建且验证邮件已发出；失败时自动回滚，不会留下僵尸账号。\n\n> 创建成功后用户默认仍是\"未验证\"状态，需点击邮件验证链接后，`verify_token` 才能通过校验。\n\n### 请求体\n\n```json\n{ \"email\": \"alice@example.com\", \"password\": \"Test@12345678\", \"display_name\": \"Alice\" }\n```\n\n| 字段 | 类型 | 必填 |\n|------|------|------|\n| `email` | string | 是 |\n| `password` | string | 是 |\n| `display_name` | string | 否 |\n\n### 错误\n\n| 场景 | HTTP | `data.error` |\n|------|------|-------------|\n| 缺少 email\u002Fpassword | 400 | `email and password are required` |\n| 上游创建失败 | 502 | `Failed to create user` |\n| 发送验证邮件失败 | 500 | `Failed to send verification email. Please try again.` |\n\n### TypeScript 示例\n\n```typescript\nasync function createAuthUser(\n  env: Env,\n  payload: { email: string; password: string; display_name?: string }\n): Promise\u003C{ user?: UserInfo; error?: string }> {\n  const baseUrl = env.BASE_URL ?? 'https:\u002F\u002Fpinme.cloud';\n  const resp = await fetch(\n    `${baseUrl}\u002Fapi\u002Fv1\u002Fauth\u002Fcreate_user?project_name=${encodeURIComponent(env.PROJECT_NAME)}`,\n    {\n      method: 'POST',\n      headers: { 'X-API-Key': env.API_KEY, 'Content-Type': 'application\u002Fjson' },\n      body: JSON.stringify(payload),\n    }\n  );\n  const result = await resp.json() as ApiEnvelope\u003CUserInfo | ApiErrorData>;\n  if (!resp.ok || result.code !== 200) {\n    return { error: (result.data as ApiErrorData)?.error ?? result.msg };\n  }\n  return { user: result.data as UserInfo };\n}\n```\n\n---\n\n## API 2: 校验 id_token\n\n**Endpoint:** `POST {BASE_URL}\u002Fapi\u002Fv1\u002Fauth\u002Fverify_token?project_name={project_name}`\n\n校验前端登录后拿到的 `id_token`（邮箱密码或 Google 登录均适用）。\n\n**注意：** token 合法但邮箱未验证时返回 `403`，不是 `401`。\n\n### 请求体\n\n```json\n{ \"id_token\": \"eyJhbGciOiJSUzI1NiIsImtpZCI6...\" }\n```\n\n### 成功响应 data\n\n```typescript\ntype VerifyTokenData = {\n  uid: string\n  email?: string\n  tenant_id: string\n  claims: Record\u003Cstring, unknown>\n}\n```\n\n### 错误\n\n| 场景 | HTTP | `data.error` |\n|------|------|-------------|\n| 缺少 `id_token` | 400 | `id_token is required` |\n| token 无效或过期 | 401 | `Invalid or expired token` |\n| 邮箱未验证 | 403 | `Email not verified. Please check your inbox and verify your email address.` |\n\n### TypeScript 示例\n\n```typescript\nasync function verifyAuthToken(\n  env: Env,\n  idToken: string\n): Promise\u003C{ uid?: string; email?: string; error?: string; emailNotVerified?: boolean }> {\n  const baseUrl = env.BASE_URL ?? 'https:\u002F\u002Fpinme.cloud';\n  const resp = await fetch(\n    `${baseUrl}\u002Fapi\u002Fv1\u002Fauth\u002Fverify_token?project_name=${encodeURIComponent(env.PROJECT_NAME)}`,\n    {\n      method: 'POST',\n      headers: { 'X-API-Key': env.API_KEY, 'Content-Type': 'application\u002Fjson' },\n      body: JSON.stringify({ id_token: idToken }),\n    }\n  );\n  const result = await resp.json() as ApiEnvelope\u003CVerifyTokenData | ApiErrorData>;\n  if (!resp.ok || result.code !== 200) {\n    const error = (result.data as ApiErrorData)?.error ?? result.msg;\n    return { error, emailNotVerified: resp.status === 403 };\n  }\n  const data = result.data as VerifyTokenData;\n  return { uid: data.uid, email: data.email };\n}\n```\n\n---\n\n## API 3: 查询单个用户\n\n**Endpoint:** `GET {BASE_URL}\u002Fapi\u002Fv1\u002Fauth\u002Fuser?project_name={project_name}&uid={uid}`\n\n### 错误\n\n| 场景 | HTTP | `data.error` |\n|------|------|-------------|\n| 缺少 `uid` | 400 | `uid is required` |\n| 用户不存在 | 404 | `User not found` |\n| 上游查询失败 | 502 | `Failed to get user` |\n\n### TypeScript 示例\n\n```typescript\nasync function getAuthUser(env: Env, uid: string): Promise\u003C{ user?: UserInfo; error?: string }> {\n  const baseUrl = env.BASE_URL ?? 'https:\u002F\u002Fpinme.cloud';\n  const resp = await fetch(\n    `${baseUrl}\u002Fapi\u002Fv1\u002Fauth\u002Fuser?project_name=${encodeURIComponent(env.PROJECT_NAME)}&uid=${encodeURIComponent(uid)}`,\n    { method: 'GET', headers: { 'X-API-Key': env.API_KEY } }\n  );\n  const result = await resp.json() as ApiEnvelope\u003CUserInfo | ApiErrorData>;\n  if (!resp.ok || result.code !== 200) {\n    return { error: (result.data as ApiErrorData)?.error ?? result.msg };\n  }\n  return { user: result.data as UserInfo };\n}\n```\n\n---\n\n## API 4: 列出用户（分页）\n\n**Endpoint:** `GET {BASE_URL}\u002Fapi\u002Fv1\u002Fauth\u002Flist_users?project_name={project_name}`\n\n默认 `max_results=100`，最大 `1000`。通过 `next_page_token` 循环翻页。\n\n### Query 参数\n\n| 参数 | 必填 | 说明 |\n|------|------|------|\n| `project_name` | 是 | 项目名 |\n| `page_token` | 否 | 分页游标 |\n| `max_results` | 否 | 每页数量，1–1000 |\n\n### TypeScript 示例\n\n```typescript\nasync function listAuthUsers(\n  env: Env,\n  options: { pageToken?: string; maxResults?: number } = {}\n): Promise\u003C{ users?: UserInfo[]; nextPageToken?: string; error?: string }> {\n  const baseUrl = env.BASE_URL ?? 'https:\u002F\u002Fpinme.cloud';\n  const url = new URL('\u002Fapi\u002Fv1\u002Fauth\u002Flist_users', baseUrl);\n  url.searchParams.set('project_name', env.PROJECT_NAME);\n  if (options.pageToken) url.searchParams.set('page_token', options.pageToken);\n  if (options.maxResults) url.searchParams.set('max_results', String(options.maxResults));\n\n  const resp = await fetch(url.toString(), { method: 'GET', headers: { 'X-API-Key': env.API_KEY } });\n  const result = await resp.json() as ApiEnvelope\u003C{ users: UserInfo[]; next_page_token?: string } | ApiErrorData>;\n  if (!resp.ok || result.code !== 200) {\n    return { error: (result.data as ApiErrorData)?.error ?? result.msg };\n  }\n  const data = result.data as { users: UserInfo[]; next_page_token?: string };\n  return { users: data.users, nextPageToken: data.next_page_token };\n}\n\n\u002F\u002F 批量遍历所有用户示例\nasync function* iterAllUsers(env: Env) {\n  let pageToken: string | undefined;\n  do {\n    const { users, nextPageToken, error } = await listAuthUsers(env, { pageToken, maxResults: 1000 });\n    if (error) throw new Error(error);\n    for (const user of users ?? []) yield user;\n    pageToken = nextPageToken;\n  } while (pageToken);\n}\n```\n\n---\n\n## 前端集成（Firebase Auth）\n\n`create_worker` 响应中包含 `public_client_config`，前端用它初始化 Firebase Auth SDK。\n\n### 两种 api_key 区分\n\n| 字段 | 用途 | 是否可暴露到浏览器 |\n|------|------|-----------------|\n| `data.api_key` | 项目 API Key，调用本文所有代理接口 | **不能**，只给 Worker\u002F服务端 |\n| `data.public_client_config.auth_api_key` | Firebase Web API Key，初始化前端登录 SDK | 可以 |\n\n### public_client_config 字段说明\n\n| 字段 | 前端用途 |\n|------|---------|\n| `public_client_config.auth_api_key` | `initializeApp({ apiKey })` |\n| `public_client_config.auth_domain` | `initializeApp({ authDomain })` |\n| `public_client_config.auth_project_id` | `initializeApp({ projectId })` |\n| `public_client_config.tenant_id` | `auth.tenantId = config.tenant_id`（必须设置，否则 token 归属错误） |\n\n### 前端 TypeScript 示例\n\n```typescript\nimport { initializeApp } from 'firebase\u002Fapp'\nimport {\n  type Auth,\n  getAuth,\n  GoogleAuthProvider,\n  signInWithEmailAndPassword,\n  signInWithPopup,\n} from 'firebase\u002Fauth'\n\ntype PublicClientConfig = {\n  tenant_id: string\n  auth_api_key: string\n  auth_domain: string\n  auth_project_id: string\n}\n\nexport function createProjectAuth(config: PublicClientConfig): Auth {\n  const app = initializeApp({\n    apiKey: config.auth_api_key,\n    authDomain: config.auth_domain,\n    projectId: config.auth_project_id,\n  })\n  const auth = getAuth(app)\n  auth.tenantId = config.tenant_id  \u002F\u002F 必须设置，确保 token 归属正确租户\n  return auth\n}\n\n\u002F\u002F 邮箱密码登录，返回 id_token\nexport async function loginWithEmail(auth: Auth, email: string, password: string): Promise\u003Cstring> {\n  const credential = await signInWithEmailAndPassword(auth, email, password)\n  return credential.user.getIdToken()\n}\n\n\u002F\u002F Google 登录，返回 id_token\nexport async function loginWithGoogle(auth: Auth): Promise\u003Cstring> {\n  const credential = await signInWithPopup(auth, new GoogleAuthProvider())\n  return credential.user.getIdToken()\n}\n\n\u002F\u002F 用法示例\n\u002F\u002F pinme create 会自动将 public_client_config 写入 frontend\u002Fsrc\u002Futils\u002Fconfig.ts\nimport { public_client_config } from '..\u002Futils\u002Fconfig'\n\nconst auth = createProjectAuth(public_client_config)\nconst idToken = await loginWithGoogle(auth)\n\u002F\u002F 然后把 idToken 发给自己的 Worker，由 Worker 调用 verify_token\n```\n\n> 前端只负责登录和拿 `id_token`，不要直接持有项目 `api_key`。`verify_token` 必须由 Worker\u002F服务端代调。\n> `frontend\u002Fsrc\u002Futils\u002Fconfig.ts` 由 `pinme create` 自动生成，无需手动创建。\n\n---\n\n## 典型调用链路\n\n**邮箱密码注册流程：**\n1. `create_user` → 创建用户并发出验证邮件\n2. 用户点击邮件链接完成验证\n3. 前端登录拿到 `id_token`\n4. `verify_token` → 校验 token，取得 `uid`\n5. 需要时再调 `getAuthUser` 读取完整用户信息\n\n**Google 登录流程：**\n1. 前端完成 Google Sign-In，拿到 `id_token`\n2. `verify_token` → 校验 token（无需调用 `create_user`）\n\n---\n\n## 易错点\n\n| 错误 | 正确做法 |\n|------|---------|\n| 只传 `X-API-Key`，忘记 `project_name` | 每个请求都要同时带 `X-API-Key` header 和 `project_name` query |\n| `verify_token` 返回 403 时当 token 失效处理 | 403 = 邮箱未验证，提示用户检查邮箱；401 才是 token 失效 |\n| `create_user` 成功就认为邮箱已验证 | 创建成功只代表验证邮件已发，用户必须点击后才算验证 |\n| `list_users` 只取第一页 | 有 `next_page_token` 时需继续请求，直到为空 |\n| 成功判断只看 `resp.ok` | 同时判断 `resp.ok && result.code === 200` |\n",{"name":5,"description":7},"main","1907bbe850fb749efa8b499b8da7ebb78a663519",".rovodev\u002Fskills\u002Fpinme-auth",0,"https:\u002F\u002Fgithub.com\u002FdevelopAgent\u002Fbmad-start","https:\u002F\u002Fgithub.com\u002FdevelopAgent","2026-07-19 05:19:22",{"source":8,"skillName":5,"commands":31,"prompt":41,"zipAvailable":42},[32,35,38],{"tool":33,"command":34},"npx","npx skills add developAgent\u002Fbmad-start --skill pinme-auth",{"tool":36,"command":37},"bunx","bunx skills add developAgent\u002Fbmad-start --skill pinme-auth",{"tool":39,"command":40},"pnpm","pnpm dlx skills add developAgent\u002Fbmad-start --skill pinme-auth","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FdevelopAgent\u002Fbmad-start\u002Fblob\u002Fmain\u002F.rovodev\u002Fskills\u002Fpinme-auth\u002FSKILL.md",true,[],[45,52,59,66,73,80],{"skillNo":46,"name":47,"nameZh":6,"description":48,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":49,"categories":50,"keywords":51,"publishedAt":18},"01M72ZN1RJ28HE3TG8CK5VMDZ0","writing-plans","Use when you have a spec or requirements for a multi-step task, before touching code","https:\u002F\u002Fgithub.com\u002FdevelopAgent\u002Fbmad-start\u002Fblob\u002Fmain\u002Fskills\u002Fwriting-plans\u002FSKILL.md",[],[],{"skillNo":53,"name":54,"nameZh":6,"description":55,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":56,"categories":57,"keywords":58,"publishedAt":18},"01M72ZMZG4KQAFDEHG6KABQW8R","to-prd","Turn the current conversation into a PRD and publish it to the project issue tracker — no interview, just synthesis of what you've already discussed.","https:\u002F\u002Fgithub.com\u002FdevelopAgent\u002Fbmad-start\u002Fblob\u002Fmain\u002Fskills\u002Fto-prd\u002FSKILL.md",[],[],{"skillNo":60,"name":61,"nameZh":6,"description":62,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":63,"categories":64,"keywords":65,"publishedAt":18},"01M72ZMWB9ZNA0Y5KGAHTW89E8","steve-jobs-perspective","史蒂夫·乔布斯(Steve Jobs)的思维框架与表达方式。基于Isaacson授权传记、Stanford演讲、\nLost Interview、D Conference系列、Make Something Wonderful、30+一手来源的深度调研，\n提炼6个核心心智模型、8条决策启发式和完整的表达DNA。\n用途：作为思维顾问，用乔布斯的视角分析产品、审视决策、提供反馈。\n当用户提到「用乔布斯的视角」「乔布斯会怎么看」「Jobs模式」「steve jobs perspective」时使用。\n即使用户只是说「帮我用乔布斯的角度想想」「如果乔布斯会怎么做」「切换到乔布斯」也应触发。","https:\u002F\u002Fgithub.com\u002FdevelopAgent\u002Fbmad-start\u002Fblob\u002Fmain\u002Fskills\u002Fsteve-jobs-perspective\u002FSKILL.md",[],[],{"skillNo":67,"name":68,"nameZh":6,"description":69,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":70,"categories":71,"keywords":72,"publishedAt":18},"01M72ZMS3ZRQY9PCRPRVWY040M","pinme","Use this skill when the user mentions \"pinme\", or needs to upload files, store to IPFS, create\u002Fpublish\u002Fdeploy websites or full-stack services (including frontend pages, backend APIs, database storage, email sending, etc.), or any feature requiring backend database\u002Fserver support.","https:\u002F\u002Fgithub.com\u002FdevelopAgent\u002Fbmad-start\u002Fblob\u002Fmain\u002Fskills\u002Fpinme\u002FSKILL.md",[],[],{"skillNo":74,"name":75,"nameZh":6,"description":76,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":77,"categories":78,"keywords":79,"publishedAt":18},"01M72ZMPJHQ8NHC9WSH41DKV1F","pinme-share","Use this skill when the user wants to share, publish, or upload a static result through PinMe, especially by generating a static HTML share page for a PinMe project link, deployed full-stack app, Codex conversation summary, report, file, demo, or any 分享\u002F发布\u002F上传分享页 request that should end with `pinme upload`.","https:\u002F\u002Fgithub.com\u002FdevelopAgent\u002Fbmad-start\u002Fblob\u002Fmain\u002Fskills\u002Fpinme-share\u002FSKILL.md",[],[],{"skillNo":81,"name":82,"nameZh":6,"description":83,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":84,"categories":85,"keywords":86,"publishedAt":18},"01M72ZMM4QBVWMAW0YYM7H0Y6P","pinme-llm","Use this skill when a PinMe project (Worker TypeScript) needs to call OpenRouter-backed LLM APIs, including models, chat\u002Fcompletions, streaming, or OpenRouter web search. Guides AI to generate correct Worker TS code.","https:\u002F\u002Fgithub.com\u002FdevelopAgent\u002Fbmad-start\u002Fblob\u002Fmain\u002Fskills\u002Fpinme-llm\u002FSKILL.md",[],[]]