references/activation-map.yaml
scenarios:
- id: web-auth
label: Web login / registration / auth UI
priority: 100
signals:
- CloudBase Web 登录
- Web 注册
- auth login page
- publishable key
- 短信登录
- 邮箱登录
firstRead: auth-tool-cloudbase
thenRead:
- auth-web-cloudbase
- web-development
beforeAction:
- 先检查并开启所需登录方式,再写前端代码。
- 优先通过 `queryAppAuth` / `manageAppAuth` 获取 publishable key 并确认使用 Web SDK。
doNotUse:
- cloud-functions
- http-api-cloudbase
mustCheckBeforeAction:
- Provider status and publishable key
commonMistakes:
- 把 Web 登录实现成云函数认证逻辑。
- 未开启 provider 就直接生成登录 UI。
- id: miniapp-cloudbase
priority: 95
signals:
- 小程序 云开发
- wx.cloud
- mini program cloudbase
- OPENID
- 小程序数据库
firstRead: miniprogram-development
thenRead:
- auth-wechat-miniprogram
- cloudbase-document-database-in-wechat-miniprogram
beforeAction:
- 先确认项目是否真的使用 CloudBase。
- 使用 wx.cloud 和 OPENID 路径,不要先套 Web 认证模型。
doNotUse:
- auth-web-cloudbase
- web-development
commonMistakes:
- 给小程序生成多余的 Web 登录页。
- 混用 Web SDK 和小程序 SDK。
label: WeChat mini program + CloudBase
mustCheckBeforeAction:
- Whether the project really uses CloudBase / `wx.cloud`
- id: native-http-api
priority: 100
signals:
- Android CloudBase
- iOS CloudBase
- Flutter CloudBase
- React Native CloudBase
- 原生 App 接入
firstRead: http-api-cloudbase
thenRead:
- auth-tool-cloudbase
- relational-database-mcp-cloudbase
beforeAction:
- 先确认当前平台不支持 CloudBase SDK。
- 确认 HTTP API 鉴权方式、Base URL 和数据库能力边界;应用侧登录配置仍走 `queryAppAuth` / `manageAppAuth`。
doNotUse:
- auth-web-cloudbase
- cloudbase-document-database-web-sdk
- web-development
commonMistakes:
- 在原生 App 中误用 Web SDK。
- 未核对 OpenAPI 就猜接口。
label: Native App / Flutter / React Native
mustCheckBeforeAction:
- SDK boundary, OpenAPI, auth method
- id: web-nosql
priority: 90
signals:
- Web 文档数据库
- CloudBase collection
- 前端查库
- NoSQL Web SDK
firstRead: web-development
thenRead:
- cloudbase-document-database-web-sdk
- auth-web-cloudbase
beforeAction:
- 先确认是 Web SDK 场景。
- 确认登录态与数据库访问权限模型。
doNotUse:
- relational-database-mcp-cloudbase
- http-api-cloudbase
commonMistakes:
- 把前端查文档库误导到 MySQL 管理工具。
- 未确认登录态就直接写数据库代码。
label: Web projects + NoSQL Database
mustCheckBeforeAction:
- Login state and database access permission model
- id: postgresql-development-cloudbase
priority: 96
signals:
- CloudBase PG
- PostgreSQL
- Postgres
- PG 模式
- JS SDK v3 PostgreSQL
- app.rdb()
- queryPgDatabase
- managePgDatabase
- mysqldb OpenAPI
- PostgREST
- RLS
- service_role
- auth schema
- storage schema
- pgvector
firstRead: postgresql-development-cloudbase
thenRead:
- auth-tool-cloudbase
- auth-web-cloudbase
- web-development
- miniprogram-development
- cloud-storage-web
- http-api-cloudbase
beforeAction:
- 先用 `queryPgDatabase` / `managePgDatabase` 检查 PG 环境、schema 与权限策略。
- 用户名密码登录先用 `queryAppAuth` / `manageAppAuth` 确认或开启。
- Web 业务数据优先用 JS SDK v3 `app.rdb()`,HTTP API 只在查过 `mysqldb` OpenAPI 后使用。
- 业务数据必须落到 CloudBase PG,不要退回 NoSQL 或 MySQL 管理工具链。
doNotUse:
- relational-database-mcp-cloudbase
- cloudbase-document-database-web-sdk
commonMistakes:
- 把 CloudBase PG 当 MySQL,调用 `queryMysqlDatabase` / `manageMysqlDatabase`。
- 只做前端按钮隐藏,没有后端或数据库层权限。
- 猜 `/api/v1/rdb/rest` 这类 HTTP 路径,导致浏览器 404 后继续调试业务逻辑。
- 写非法 Vite/TypeScript 动态 import,导致页面 500 后仍继续调试业务逻辑。
label: CloudBase PostgreSQL / PG
mustCheckBeforeAction:
- PG schema, usernamePassword login, backend/RLS permission model
- id: mysql-mcp
priority: 88
signals:
- MySQL 建表
- executeWriteSQL
- security rule
- CloudBase 关系型数据库管理
firstRead: relational-database-mcp-cloudbase
thenRead:
- relational-database-web-cloudbase
- http-api-cloudbase
beforeAction:
- 先区分当前是 MCP 运维管理还是应用代码接入。
- 写操作前先跑 SELECT 或先读安全规则。
doNotUse:
- cloudbase-document-database-web-sdk
- web-development
commonMistakes:
- 在 MCP 管理场景里初始化 SDK。
- 未验证条件就直接执行写 SQL。
label: MySQL Database (relational)
mustCheckBeforeAction:
- Distinguish MCP management vs app code access
- id: cloud-functions
priority: 92
signals:
- 创建云函数
- HTTP 云函数
- getFunctionLogs
- scf_bootstrap
- runtime
firstRead: cloud-functions
thenRead:
- auth-tool-cloudbase
- ai-model-nodejs
beforeAction:
- 先区分 Event Function 与 HTTP Function。
- 创建前确定 runtime,避免后续不可变限制。
doNotUse:
- cloudrun-development
- auth-web-cloudbase
commonMistakes:
- 把 Web 登录逻辑错误地放进云函数。
- 把 HTTP 函数误写成 `exports.main(event, context)`,或误以为 Node 原生 `http` 请求里自带 `req.body`。
- HTTP 函数遗漏 `scf_bootstrap`、9000 端口或显式响应头。
label: Cloud Functions
mustCheckBeforeAction:
- Event vs HTTP function, runtime, `scf_bootstrap`
- id: cloudrun-backend
priority: 85
signals:
- CloudRun 部署
- 云托管
- container backend
- Dockerfile
firstRead: cloudrun-development
thenRead:
- auth-tool-cloudbase
- relational-database-mcp-cloudbase
beforeAction:
- 先确认这是容器服务而不是云函数。
- 检查 CORS、镜像入口和环境变量策略。
doNotUse:
- cloud-functions
commonMistakes:
- 把 CloudRun 需求收敛成云函数模板。
label: CloudRun backend
mustCheckBeforeAction:
- Container boundary, Dockerfile, CORS
- id: ai-agent
priority: 85
signals:
- AI Agent
- 智能体
- 智能体开发
- AG-UI protocol
- LangGraph
- LangChain
- CrewAI
- streaming agent
- agent UI
firstRead: cloudbase-agent
thenRead:
- cloud-functions
- cloudrun-development
beforeAction:
- 先确认是 Agent 开发而不是普通云函数。
- 确认 AG-UI 协议、SSE streaming、部署目标(云函数或 CloudRun)。
doNotUse:
- cloud-functions
- cloudrun-development
commonMistakes:
- 把 Agent 开发误当成普通云函数开发。
- 未确认 AG-UI 协议就直接写代码。
- 遗漏 SSE streaming 处理或前端事件解析。
label: AI Agent (智能体开发)
mustCheckBeforeAction:
- AG-UI protocol, scf_bootstrap, SSE streaming
- id: minimal-web-baas-demo
priority: 99
signals:
- 最小前后端
- 最小可用 demo
- 最小 fullstack
- 搭一套 demo
- 带云数据库的 demo
- 带云函数+云数据库
- 留言板
- Todo 应用
- todo app
- Notes app
- Kanban
- Lovable
- BaaS demo
- minimal web baas
- 快速 demo
firstRead: minimal-web-baas-demo
thenRead:
- web-development
- cloudbase-document-database-web-sdk
- postgresql-development-cloudbase
beforeAction:
- 按 BaaS-first 排序:Web SDK CRUD > MCP schema > 模板预热 > 云函数(默认 0)。
- 连接器尽量预启用;凭据/Trust 等待窗口并行 downloadTemplate + 安装依赖。
- 能力嗅探顺序:connector ready → envQuery → 锁定一种 DB(NoSQL 或 PG 或 MySQL)→ MCP schema → @cloudbase/js-sdk CRUD → 本地预览;禁止中途横跳。
- 先本地预览再部署;自定义域名 / DNS / 回滚非默认范围。
doNotUse:
- cloud-functions
- cloudrun-development
- spec-workflow
- ui-design
commonMistakes:
- 把最小 Demo 默认做成云函数中转 CRUD。
- 会话开头整包灌入全部 CloudBase skills,而不是 compact fast-path。
- 凭据等待期间空转,未并行模板预热。
- 未预览就先做自定义域名 / DNS / 回滚手册。
label: Minimal Web BaaS demo (fast path)
mustCheckBeforeAction:
- BaaS-first Web SDK CRUD, MCP schema only, zero cloud functions unless secrets/cron/rules-cannot-express
- id: ui-first
priority: 98
signals:
- 设计页面
- 登录页 UI
- frontend interface
- 组件样式
- prototype
firstRead: ui-design
thenRead:
- web-development
- miniprogram-development
beforeAction:
- 写任何 UI 代码前先输出设计规格。
- 再根据平台补读 Web 或小程序实现规则。
doNotUse:
- cloud-functions
commonMistakes:
- 没有设计规格就直接开始写 JSX 或 CSS。
- 生成 generic UI 而没结合平台约束。
label: UI generation
mustCheckBeforeAction:
- Design specification first
- id: ai-web
priority: 80
signals:
- Web AI 对话
- CloudBase AI 流式输出
- Web 集成模型
firstRead: web-development
thenRead:
- ai-model-web
- ui-design
beforeAction:
- 先确认前端平台和流式输出交互方式。
- UI 场景先读设计规范再实现聊天界面。
doNotUse:
- ai-model-wechat
- http-api-cloudbase
commonMistakes:
- Web 场景读成小程序或原生 App 路径。
label: AI Model (Web)
mustCheckBeforeAction:
- Platform and streaming interaction mode
- id: ai-model-call
priority: 86
signals:
- 大模型调用
- AI 模型调用
- generateText
- streamText
- generateImage
- 文本生成
- 图片生成
- 流式对话
- hunyuan-exp
- deepseek-v4-flash
- Token Credits 资源包
- 小程序成长计划
- ai_miniprogram_inspire_plan
- callCloudApi AI 模型
- CreateAIModel
firstRead: ai-model-web
thenRead:
- ai-model-nodejs
- ai-model-wechat
beforeAction:
- 先跑「调用前必须的资格检查」:用 `envQuery` 拿到 `EnvId`,再按端别优先级查资格。
- Web / Node.js 端优先 `callCloudApi(tcb, DescribeEnvPostpayPackage)` 确认 Token Credits 资源包开通;未命中返回 `https://buy.cloud.tencent.com/lowcode?buyType=resPack&envId={envId}&resourceType=token` 引导购买。
- 小程序端优先调用 `callCloudApi` 的 `DescribeActivityInfo`(参数 activityNames 为 ai_miniprogram_inspire_plan)判断成长计划是否报名;命中用 `hunyuan-exp` / `hunyuan-2.0-instruct-20251111`;未命中引导 `https://docs.cloudbase.net/ai/ai-inspire-plan` 或退回资源包 + 非 hunyuan 模型。
- 指定的模型不在托管列表时走自定义接入(CloudBase 控制台 `#/ai` 或 `callCloudApi(tcb, CreateAIModel)`),不要点名任何第三方品牌。
doNotUse:
- cloudbase-agent
- cloud-functions
- cloudrun-development
commonMistakes:
- 跳过资格检查直接写 SDK 调用,运行时才发现资源包未开通或计划未报名。
- 把小程序场景错误地退化成 Web SDK 调用。
- 图像生成忽略超时与单次 Token 费用,云函数 timeout 仍保留默认值。
- 在业务代码里硬编码第三方模型密钥,而非走「不在托管列表时的自定义接入」。
label: AI model call (大模型调用 / 文本生成 / 图片生成 / 流式对话)
mustCheckBeforeAction:
- 先跑「调用前必须的资格检查」:`DescribeActivityInfo`(小程序成长计划) + `DescribeEnvPostpayPackage`(Token Credits 资源包)
- id: ops-inspector
priority: 82
signals:
- 巡检
- 诊断
- health check
- 资源健康
- 异常日志
- error inspection
- troubleshooting
- 错误排查
firstRead: ops-inspector
thenRead:
- cloud-functions
- cloudrun-development
beforeAction:
- 先确认环境已绑定且 CLS 日志服务已开通。
- 收集所有资源状态后再下结论,避免孤立分析单一日志。
doNotUse:
- ui-design
- spec-workflow
commonMistakes:
- CLS 未开通就尝试搜索日志。
- 不指定时间范围就搜索日志,导致返回大量无关结果。
- 只看单条错误日志,不做跨资源关联分析。
label: Resource health inspection / troubleshooting
mustCheckBeforeAction:
- CLS enabled, time range for logs
- id: spec-workflow
priority: 75
signals:
- 需求文档
- 技术方案
- tasks.md
- Spec 工作流
firstRead: spec-workflow
thenRead:
- cloudbase
beforeAction:
- 先完成 requirements、design、tasks 并获得确认。
- 再进入代码实现阶段。
doNotUse:
- web-development
- cloud-functions
commonMistakes:
- 跳过需求和设计直接开始实现。
label: Spec workflow / architecture design
mustCheckBeforeAction:
- Requirements, design, tasks confirmed
references/ai-model-nodejs/references/api-reference.md
# AI Model SDK API Reference (@cloudbase/node-sdk)
> Prerequisite for all calls: the two-step preflight (eligibility + group readiness) in SKILL.md has passed.
## generateText() — non-streaming
```js
const model = ai.createModel("cloudbase");
const result = await model.generateText({
model: "deepseek-v4-flash", // must already be enabled in this env (DescribeAIModels → UpdateAIModel)
messages: [{ role: "user", content: "Give me a one-paragraph intro to Li Bai." }],
});
console.log(result.text); // generated text string
console.log(result.usage); // { prompt_tokens, completion_tokens, total_tokens }
console.log(result.messages); // full message history
console.log(result.rawResponses); // raw model responses
```
## Error Handling Pattern
```js
const model = ai.createModel("cloudbase");
try {
const result = await model.generateText({
model: "deepseek-v4-flash",
messages: [{ role: "user", content: "Summarize today's deployment logs." }],
});
console.log(result.text);
} catch (error) {
console.error("AI request failed", error);
}
```
## streamText() — streaming
```js
const model = ai.createModel("cloudbase");
const res = await model.streamText({
model: "deepseek-v4-flash",
messages: [{ role: "user", content: "Give me a one-paragraph intro to Li Bai." }],
});
// Option 1: iterate the text stream (recommended)
for await (let text of res.textStream) {
console.log(text); // incremental text chunks
}
// Option 2: iterate the data stream for full response chunks
for await (let data of res.dataStream) {
console.log(data); // full response chunk with metadata
}
// Option 3: access final results
const messages = await res.messages; // full message history
const usage = await res.usage; // token usage
```
## generateImage() — image generation
⚠️ **Image generation is only available in the Node SDK**, not in the JS SDK (Web) or WeChat Mini Program.
⚠️ **Image generation also consumes the Token Credits resource pack**, so the two-step preflight must pass before calling it. Per-call cost is higher than text and calls take longer (set cloud function timeout to 900 s).
```js
const imageModel = ai.createImageModel("hunyuan-image");
const res = await imageModel.generateImage({
model: "hunyuan-image",
prompt: "A cute kitten playing on the grass",
size: "1024x1024",
version: "v1.9",
});
console.log(res.data[0].url); // image URL (valid for 24 hours)
console.log(res.data[0].revised_prompt);// revised prompt when revise=true
```
### Image Generation Parameters
```ts
interface HunyuanGenerateImageInput {
model: "hunyuan-image"; // required
prompt: string; // required: image description
version?: "v1.8.1" | "v1.9"; // default: "v1.8.1"
size?: string; // default: "1024x1024"
negative_prompt?: string; // v1.9 only
style?: string; // v1.9 only
revise?: boolean; // default: true
n?: number; // default: 1
footnote?: string; // watermark, max 16 chars
seed?: number; // range: [1, 4294967295]
}
interface HunyuanGenerateImageOutput {
id: string;
created: number;
data: Array<{
url: string; // image URL (24h valid)
revised_prompt?: string;
}>;
}
```
## Type Definitions
```ts
interface BaseChatModelInput {
model: string; // required: model name
messages: Array<ChatModelMessage>; // required: message array
temperature?: number; // optional: sampling temperature
topP?: number; // optional: nucleus sampling
}
type ChatModelMessage =
| { role: "user"; content: string }
| { role: "system"; content: string }
| { role: "assistant"; content: string };
interface GenerateTextResult {
text: string; // generated text
messages: Array<ChatModelMessage>; // full message history
usage: Usage; // token usage
rawResponses: Array<unknown>; // raw model responses
error?: unknown; // error if any
}
interface StreamTextResult {
textStream: AsyncIterable<string>; // incremental text stream
dataStream: AsyncIterable<DataChunk>; // full data stream
messages: Promise<ChatModelMessage[]>;// final message history
usage: Promise<Usage>; // final token usage
error?: unknown; // error if any
}
interface Usage {
prompt_tokens: number;
completion_tokens: number;
total_tokens: number;
}
```
references/ai-model-nodejs/references/custom-onboarding.md
# Custom Model Onboarding (models outside the managed catalog)
When the user wants a **non-managed** text model (self-hosted, enterprise-internal, third-party OpenAI-compatible endpoint, …), **do not block**. Guide them through onboarding:
## Option 1: console flow (recommended, user handles it)
`https://tcb.cloud.tencent.com/dev?envId={envId}#/ai`
## Option 2: programmatic onboarding (`CreateAIModel`)
```
callCloudApi(service="tcb", action="CreateAIModel", params={
EnvId: "<envId>",
GroupName: "custom-<your-name>", // MUST start with "custom-" (e.g. custom-kimi, custom-openai-compat); never start with "cloudbase"
BaseUrl: "<OpenAI-compatible endpoint, e.g. https://api.moonshot.cn/v1>",
Models: [
{ Model: "<model name, e.g. kimi-k2.5>", EnableMCP: true }
],
Remark: "<optional remark>",
Status: 1,
Secret: { ApiKey: "<vendor api key supplied by the user>" }
})
```
Once onboarded, confirm with `DescribeAIModels` that the group is ready, then call `ai.createModel("<the GroupName you just registered>")` from your code. Use `UpdateAIModel` to add/remove models, rotate keys, or change `BaseUrl` (remember `Models` is a **full replacement**). Use `DeleteAIModel` to remove a custom group (builtin groups cannot be deleted).
> Custom-model billing is covered by the third-party provider and does not draw from the Token Credits resource pack. Field casing follows the live contract — fall back to camelCase on `InvalidParameter`.
references/ai-model-nodejs/SKILL.md
---
name: ai-model-nodejs
description: "Use this skill for Node.js backend AI via @cloudbase/node-sdk (>=3.16.0) — cloud functions, CloudRun, Express/Koa/NestJS, serverless APIs, scheduled jobs, LLM proxies, agent orchestration. The only SDK supporting image generation (ai.createImageModel + generateImage). Text via ai.createModel with groups cloudbase, hunyuan-exp, or custom-*; model ids (e.g. deepseek-v4-flash, glm-5, kimi-k2.6) go in the `model` field of generateText/streamText. MUST run two-step preflight before code — see body. NOT for browser/Web (use ai-model-web) or Mini Program (use ai-model-wechat)."
version: 2.33.1
alwaysApply: false
---
## Sibling skills (local only)
Sibling CloudBase skills ship beside this skill. Use local relative paths such as `../auth-tool-cloudbase/SKILL.md`.
If a referenced sibling skill file is missing from this environment, ask the user to install the full CloudBase plugin (or the missing skill). Do **not** HTTP-fetch remote skill or protocol markdown into the agent context.
## When to use this skill
Use this skill for **calling AI models from Node.js backends, cloud functions, or CloudRun services** via `@cloudbase/node-sdk`.
> 🧭 **Runtime-plane fit.** This is the right skill when the AI call truly belongs on the server: image generation (the only SDK that supports it), long-running agent jobs, orchestration across multiple tools, scheduled tasks, or flows that must keep secrets server-side. **If the user is building a Web page / frontend AI chat UI, do NOT wrap this SDK behind a backend proxy** — route to `ai-model-web` and call the model directly from the browser. For WeChat Mini Programs use `ai-model-wechat`. Routing is decided by runtime plane first; the concrete model (`deepseek-*`, `glm-*`, `hunyuan-*`, `kimi-*`, …) only affects the `model` field.
**Use it when you need to:**
- Integrate AI text generation into a backend service
- Generate images with the Hunyuan Image model
- Call AI models from CloudBase cloud functions or CloudRun
- Do server-side AI processing (agent orchestration, batch jobs, scheduled tasks)
**Do NOT use for:**
- Browser/Web apps → use the `ai-model-web` skill
- WeChat Mini Program → use the `ai-model-wechat` skill
- Runtimes without a CloudBase SDK (Python, Go, PHP, curl, etc.) → use the `http-api-cloudbase` skill (it now includes the `ai_model` OpenAPI spec for direct HTTP calls to the AI model endpoint; do NOT wrap this SDK behind an HTTP proxy)
---
## ⛔ STOP — `ai.createModel(...)` argument is **not** a vendor / model name
Read this before writing any `createModel(...)` line. Agents frequently hallucinate this argument. There are **exactly three** legal shapes. Anything else is a bug.
| ✅ Legal `ai.createModel(...)` argument | When to use it |
|----------------------------------------|----------------|
| `"cloudbase"` | **The main managed group for server-side projects** (TokenHub-backed, multi-vendor pool). Vendor + concrete model go into the **`model` field** of `generateText` / `streamText`, e.g. `{ model: "deepseek-v4-flash" }`. **No model is enabled by default — always check `DescribeAIModels` first and, if the target model is missing, enable it with `UpdateAIModel` before calling the SDK.** |
| `"hunyuan-exp"` | Only if `DescribeAIModels` explicitly returns this legacy builtin group for the current env. |
| `"custom-<your-name>"` | A user-defined GroupName you onboarded via `CreateAIModel`. **Must** start with `custom-` (e.g. `custom-kimi`, `custom-openai-compat`). |
> Image generation is a separate entry point: `ai.createImageModel("hunyuan-image")`. Do not mix it with `createModel(...)`.
### ❌ Wrong argument patterns
Anything that is not one of the three legal values above: vendor names (`"deepseek"`, `"glm"`, `"kimi"`, `"openai"`, `"moonshot"`, …), concrete model ids (`"deepseek-v4-flash"`, `"hunyuan-2.0-instruct-20251111"`), the bare placeholder `"custom"`, or a variable holding the model id. All of these are bugs in `createModel(...)`.
### ✅ Correct pattern — GroupName vs Model are two different fields
```js
const model = ai.createModel("cloudbase"); // ← GroupName
await model.generateText({
model: "deepseek-v4-flash", // ← concrete model id
messages: [...]
});
```
### Decision procedure (when the user names a specific model)
1. The user says "use DeepSeek v3.2" / "use hunyuan instruct" / "use Kimi k2.6" / "use GLM-5" / …
2. `createModel("cloudbase")` stays the same.
3. Put the model id into the **`model` field**: `{ model: "deepseek-v3.2" }`, `{ model: "hunyuan-2.0-instruct-20251111" }`, `{ model: "kimi-k2.6" }`, `{ model: "glm-5" }`, …
4. **Never assume the model is already enabled.** Before calling the SDK, verify it is present in `DescribeAIModels({ GroupName: "cloudbase" }).Models[]`. If missing, call `DescribeManagedAIModelList` to confirm the exact `Model` name the platform supports (case-sensitive — do **not** guess the spelling) and then enable it via `UpdateAIModel` with `Status: 1` (remember `Models` is a full replacement).
> If you are about to type `ai.createModel(` and the thing inside the parentheses is a vendor name, a model name, or a guess — **stop**. It is almost certainly one of the three legal values above.
---
## Mandatory Two-Step Preflight (before any SDK code)
Before calling any AI API on the server, **run the two-step preflight**: ① eligibility, ② group readiness. **Text generation and image generation draw from the same Token Credits resource pack**, and both must complete the preflight before code is emitted.
### Step 0: obtain the environment ID
Call the MCP tool `envQuery` with `action=info` and read `EnvId` from the response.
---
### Preflight ① — Eligibility (Token Credits resource pack)
Call the MCP tool:
```
callCloudApi(service="tcb", action="DescribeEnvPostpayPackage", params={ EnvId })
```
**Pass conditions (all required):**
- `envPostpayPackageInfoList` contains at least one entry
- That entry's `postpayPackageId` starts with `pkg_tcb_tokencredits_`
- That entry's `status` is NOT in `[3, 4]` (3 / 4 typically mean expired / disabled; trust the live response)
- ❌ **Not satisfied** → **stop writing code** and surface this to the user (replacing `{envId}` with the real id):
> The current environment has no active Token Credits resource pack. Please purchase one before calling any AI API:
> https://buy.cloud.tencent.com/lowcode?buyType=resPack&envId={envId}&resourceType=token
>
> Let me know once it's done and I'll re-check the resource pack status.
- ✅ **Satisfied** → proceed to preflight ②.
> Parameter casing is PascalCase by contract. If the call returns `InvalidParameter`, fall back to camelCase (`envId`) and trust the live response.
---
### Preflight ② — Group readiness (`DescribeAIModels` → `UpdateAIModel` if needed)
Eligibility alone is not enough. **Do not write `createModel("cloudbase")` yet.** First confirm that the target `GroupName` exists in the env with `Status=1`, and that the target `Model` is present in its `Models[]`.
1. **List groups configured in the current env:**
```
callCloudApi(service="tcb", action="DescribeAIModels", params={ EnvId })
```
Returns `AIModelGroups: AIModelGroup[]` with `GroupName`, `Type` (`builtin` / `custom`), `Models: [{ Model, EnableMCP, Tags }]`, `Status` (1 / 2), `BaseUrl`, `Secret`, `Remark`. The main managed `GroupName` is `cloudbase`.
2. **Never assume a model is already enabled.** Inspect `AIModelGroups[?].Models[].Model` for the target group. If the text model you plan to use (e.g. `deepseek-v4-flash`, or whatever the user asked for) is missing from the `cloudbase` group's `Models[]`, jump to step 4 and enable it — do not call `createModel("cloudbase")` yet. Image generation uses `createImageModel("hunyuan-image")` + `model: "hunyuan-image"`; verify it is likewise enabled before the call.
3. **User asked for a model from the managed catalog** (e.g. `deepseek-v3.2`, `hunyuan-2.0-instruct-20251111`): check whether that `Model` is already in the `cloudbase` group's `Models[]`. If not, jump to step 4. **Do not guess the exact model id** — confirm the canonical spelling in `DescribeManagedAIModelList` first.
4. **Enable / add a managed model** (always inspect the authoritative catalog + pricing first):
```
callCloudApi(service="tcb", action="DescribeManagedAIModelList", params={ EnvId })
```
Returns `ManagedAIModelGroup[]` with `GroupName`, `Remark`, and `Models: [{ Model, EnableMCP, ModelSpec, ModelChargingInfo }]`. **This is the single source of truth for supported model names and pricing — do not infer them from memory. Use the exact `Model` string from here when calling `UpdateAIModel`.** `ModelChargingInfo` includes input / output prices and billing unit. Surface the prices to the user before enabling.
Then enable (note: `Models` is a **full replacement** — always resend the already-enabled models together with the new one):
```
callCloudApi(service="tcb", action="UpdateAIModel", params={
EnvId,
GroupName: "cloudbase",
Models: [
// resend every model that DescribeAIModels already showed as enabled
{ Model: "<already-enabled model>" },
// append the newly-requested one, using the exact spelling from DescribeManagedAIModelList
{ Model: "<target model>" }
],
Status: 1
})
```
5. **The requested model is not in the managed catalog** (not found by `DescribeManagedAIModelList`) → jump to the next section, **Custom onboarding (models outside the managed catalog)**.
> All Actions use `service=tcb`, `Version=2018-06-08`. Parameters are PascalCase; fall back to camelCase only on `InvalidParameter`.
---
## Available Providers and Models
`ai.createModel(<GroupName>)` accepts exactly three kinds of legal values; `ai.createImageModel("hunyuan-image")` is the dedicated image-generation entry point.
### 1. `"cloudbase"` — the main managed group (recommended)
- `GroupName: "cloudbase"`, `Type: "builtin"`, `Remark: "腾讯云开发"` (Tencent CloudBase)
- Backed by **Tencent Cloud TokenHub**, a unified managed pool covering multiple vendors — **Hunyuan** (HY 2.0 Instruct, HY 2.0 Think, Hunyuan-role, Hy3 preview, …), **DeepSeek** (DeepSeek-V4-Pro, DeepSeek-V4-Flash, Deepseek-v3.2, Deepseek-v3.1, Deepseek-r1-0528, Deepseek-v3-0324, …), **Zhipu GLM** (GLM-5, GLM-5-Turbo, GLM-5.1, GLM-5V-Turbo), **Kimi** (K2.5, K2.6), **MiniMax** (M2.5, M2.7), and more. The roster evolves — **do not hard-code specific SKUs**; discover at runtime
- **No model is enabled by default.** Always call `DescribeAIModels` first to see what the env has actually enabled; if your target model is missing, call `DescribeManagedAIModelList` for the authoritative catalog + pricing and then `UpdateAIModel` (`Status: 1`, `Models` full-replacement) to enable it before making the SDK call.
- Authoritative catalog + pricing: `DescribeManagedAIModelList`
- Env-enabled set: `DescribeAIModels`
### 2. `"hunyuan-exp"` — legacy builtin group (kept for compatibility)
- Default model: `hunyuan-2.0-instruct-20251111`; additional hunyuan SKUs must be discovered at runtime via `DescribeAIModels({ GroupName: "hunyuan-exp" }).Models[]` — do not hard-code other IDs
- Use it directly only if `DescribeAIModels` actually returns this group with `Status=1`. New projects should prefer `cloudbase`
### 3. User-defined GroupName
- Onboarded via `CreateAIModel` (see the next section). The custom `GroupName` **MUST start with `custom-`** (e.g. `custom-kimi`, `custom-moonshot`, `custom-openai-compat`). This naming convention prevents future collisions with built-in / vendor GroupNames (like `cloudbase`, `hunyuan-exp`, `deepseek`, `glm`, `kimi`, `minimax`) that the platform may introduce over time
- Examples: `createModel("custom-kimi")`, `createModel("custom-openai-compat")`
### Image generation (independent API)
- `ai.createImageModel("hunyuan-image")` + `model: "hunyuan-image"`. Only supported in the Node SDK
> **Never** write guesses like `createModel("deepseek")` or `createModel("custom")` unless `DescribeAIModels` explicitly returned that exact `GroupName`.
---
## Custom onboarding (models outside the managed catalog)
When the user wants a **non-managed** text model (self-hosted, enterprise-internal, third-party OpenAI-compatible endpoint, …), **do not block**. Guide them through onboarding — console flow, the full `CreateAIModel` payload, and follow-up management steps: [custom-onboarding.md](references/custom-onboarding.md). The custom `GroupName` MUST start with `custom-`; custom-model billing is covered by the third-party provider and does not draw from the Token Credits resource pack.
---
## Installation
```bash
npm install @cloudbase/node-sdk
```
⚠️ **The AI feature requires version 3.16.0 or above.** Check with `npm list @cloudbase/node-sdk`.
---
## Initialization
### Inside a CloudBase cloud function
```js
const tcb = require('@cloudbase/node-sdk');
const app = tcb.init({ env: '<YOUR_ENV_ID>' });
exports.main = async (event, context) => {
const ai = app.ai();
// Use AI features
};
```
### Cloud function configuration for AI models
⚠️ **Important:** when creating cloud functions that use AI models (especially `generateImage()` and large text generation), set a longer timeout — these operations can be slow.
**Using the MCP tool `manageFunctions(action="createFunction")`:**
Legacy compatibility: if an older prompt still says `createFunction`, keep the same payload shape but execute it through `manageFunctions(action="createFunction")`.
Set `timeout` inside the `func` object:
- **Parameter**: `func.timeout` (number)
- **Unit**: seconds
- **Range**: 1 – 900
- **Default**: 20 seconds (usually too short for AI operations)
**Recommended timeouts:**
- **Text generation (`generateText`)**: 60 – 120 s
- **Streaming (`streamText`)**: 60 – 120 s
- **Image generation (`generateImage`)**: 300 – 900 s (recommended: 900 s)
- **Combined operations**: 900 s (maximum allowed)
### In a regular Node.js server
```js
const tcb = require('@cloudbase/node-sdk');
const app = tcb.init({
env: '<YOUR_ENV_ID>',
secretId: '<YOUR_SECRET_ID>',
secretKey: '<YOUR_SECRET_KEY>'
});
const ai = app.ai();
```
---
## SDK API Reference (on demand)
For full `generateText` / `streamText` / `generateImage` code examples, the error-handling pattern, image-generation parameters, and the complete TypeScript type definitions, read [api-reference.md](references/api-reference.md). That file (together with this SKILL.md) is the authoritative reference for `@cloudbase/node-sdk`'s AI surface — look up method signatures there before writing code. If a method or field is not documented there, stop and ask, or check the live contract via the MCP tools. No guessing.
---
## Best Practices
1. **Run the two-step preflight before writing business code** — ① eligibility: `envQuery` → `callCloudApi(tcb, DescribeEnvPostpayPackage)` to confirm the Token Credits resource pack (text + image share the same pack); ② group readiness: `DescribeAIModels` for the `cloudbase` group and its `Models[]`, `DescribeManagedAIModelList` for the authoritative supported-model catalog, `UpdateAIModel` with a full-replacement `Models[]` + `Status: 1` when the target model is missing. If the pack is missing, return the purchase link `https://buy.cloud.tencent.com/lowcode?buyType=resPack&envId={envId}&resourceType=token` instead of emitting SDK code and letting the user debug runtime errors.
2. **Never assume any model is already enabled** — not `deepseek-v4-flash`, not `hunyuan-image`, not anything. Always verify with `DescribeAIModels` first; if the target is missing, look up the exact `Model` string in `DescribeManagedAIModelList` (do **not** guess the spelling) and then `UpdateAIModel` to enable it.
3. **`createModel` accepts exactly three kinds of values** — `"cloudbase"` (the main managed group), `"hunyuan-exp"` (legacy builtin), or a user-defined GroupName registered via `CreateAIModel` (**MUST start with `custom-`**, e.g. `custom-kimi`, `custom-openai-compat`). **Never** guess with `createModel("deepseek")` / `createModel("kimi")` / `createModel("custom")` — the first two are vendor/model names, the last is a placeholder. `createImageModel("hunyuan-image")` is a separate image API — keep it as-is.
4. **Do not invent SDK method names or parameters.** This skill (SKILL.md + `references/api-reference.md`) is the authoritative reference for `@cloudbase/node-sdk`'s AI surface — look up the method signature there before writing code. If a method or field is not documented there, stop and ask, or check the live contract via the MCP tools. No guessing.
5. **Show pricing before enabling a new managed model** — `DescribeManagedAIModelList` returns `ModelSpec` (context length, max input/output tokens) + `ModelChargingInfo` (input / output / cache prices, billing unit). Show the prices to the user before calling `UpdateAIModel`.
6. **Plan timeout and quota separately for image generation** — `generateImage` costs more per call than text and takes longer. For cloud functions, set `timeout` to `900s`. HTTP-function gateways cap at 60s, so use an async-task + polling pattern. Throttle per-user concurrency and frequency to avoid burning an entire Token pack on one failure.
7. **Prefer streaming for long-form interactions** — in HTTP-function or cloud-function SSE scenarios, use `streamText` + `for await (const chunk of result.textStream)` to flush chunks back to the client incrementally. Handle stream interruption in `catch` and close the underlying response.
8. **Pin `@cloudbase/node-sdk` >= 3.16.0** on the server — image generation is only available from this version. Verify with `npm ls @cloudbase/node-sdk` to confirm the version actually loaded by the cloud function / cloud run runtime — local and production can drift.
9. **Centralize model names in config, not scattered literals.** Keep the chosen text / image model in a single constant and source from `DescribeAIModels` / `DescribeManagedAIModelList`. The managed catalog evolves; a single source of truth makes upgrades cheap. For models outside the managed catalog, follow the Custom Onboarding section — never hard-code third-party API keys in business code (let `CreateAIModel.Secret.ApiKey` hold them via CloudBase).
10. **Distinguish "preflight failure" from "model call failure"** — the former means the resource pack is not active or the target model has not been enabled via `UpdateAIModel` (guide the user to purchase / enable). The latter is a parameter issue or upstream error. Do not wrap both in one generic toast.
11. **Do not log full prompts or generated text in production** — log only `usage.total_tokens` and a short prefix. Prompts can leak sensitive content; token counts can leak cost signals.
12. **TypeScript: do NOT use `any` to silence SDK type errors.** The Node SDK ships its own types; narrow with `unknown` + a type guard, write a precise `interface` for the shape you consume, or augment types in a local `.d.ts`. Never `: any`, `as any`, `@ts-ignore`, `@ts-nocheck`. See the Engineering constitution in the `web-development` skill — it applies to backend TS too.
13. **Self-verify before claiming done.** `tsc --noEmit` + project build + actually invoke the function (local invoke / `manageFunctions(action="invokeFunction")` / direct HTTP hit) and confirm `usage.total_tokens > 0` and the returned text is not an error envelope. "It should work" without a real round-trip is not acceptable evidence.
## Reference index
All packaged reference files (required for skill lint reachability):
- [api-reference.md](references/api-reference.md) — generateText / streamText / generateImage examples, error-handling pattern, image parameters, TypeScript type definitions
- [custom-onboarding.md](references/custom-onboarding.md) — onboarding models outside the managed catalog (console flow + `CreateAIModel`)
references/ai-model-web/SKILL.md
---
name: ai-model-web
description: "Use this skill when a browser/Web app (React, Vue, Next, Nuxt, static sites, SPAs, dashboards, AI chat UI, 页面, 前端, 网页) needs AI models via @cloudbase/js-sdk. Default routing for Web/frontend AI — call directly from the browser, do NOT propose a Node.js proxy. Covers generateText and streamText; models via ai.createModel with groups cloudbase, hunyuan-exp, or custom-*, model id in the `model` field. MUST run two-step preflight before code — see body. NOT for Node.js backend (use ai-model-nodejs), Mini Program (use ai-model-wechat), or image generation (Node SDK only)."
version: 2.33.1
alwaysApply: false
---
## Sibling skills (local only)
Sibling CloudBase skills ship beside this skill. Use local relative paths such as `../auth-tool-cloudbase/SKILL.md`.
If a referenced sibling skill file is missing from this environment, ask the user to install the full CloudBase plugin (or the missing skill). Do **not** HTTP-fetch remote skill or protocol markdown into the agent context.
## When to use this skill
Use this skill for **calling AI models in browser/Web applications** via `@cloudbase/js-sdk`.
> 🧭 **Runtime-plane default for Web.** Any time the user's request is framed around a page, a Web app, the frontend, React/Vue/Next/Nuxt, a dashboard UI, or "add AI to my H5", this skill is the default routing target. **Do NOT first propose a Node.js / cloud-function / CloudRun proxy**; `@cloudbase/js-sdk` can call the model from the browser directly. Only switch to `ai-model-nodejs` if the user explicitly asks for a backend/server call, image generation, or a scenario that truly needs server-side keys or long-running work. This decision is independent of which concrete model the user picks — model names (`deepseek-*`, `glm-*`, `hunyuan-*`, `kimi-*`, …) only affect the `model` field, not the routing plane.
**Use it when you need to:**
- Integrate AI text generation into a frontend Web app
- Stream AI responses for a better UX
- Call Hunyuan / DeepSeek / GLM / Kimi / MiniMax models from the browser
**Do NOT use for:**
- Node.js backend or cloud functions → use the `ai-model-nodejs` skill
- WeChat Mini Program → use the `ai-model-wechat` skill
- Image generation → use the `ai-model-nodejs` skill (Node SDK only)
- Runtimes without a CloudBase SDK (native apps, Python, Go, etc.) → use the `http-api-cloudbase` skill (it now includes the `ai_model` OpenAPI spec for direct HTTP calls; do NOT build a custom HTTP proxy)
---
## ⛔ STOP — `ai.createModel(...)` argument is **not** a vendor / model name
Read this before writing any `createModel(...)` line. The single most common mistake when agents generate code for this SDK is hallucinating the argument. There are **exactly three** legal shapes. Anything else is a bug.
| ✅ Legal `ai.createModel(...)` argument | When to use it |
|----------------------------------------|----------------|
| `"cloudbase"` | **The main managed group for new projects** (TokenHub-backed, multi-vendor pool). Vendor + concrete model go into the **`model` field** of `generateText` / `streamText`, e.g. `{ model: "deepseek-v4-flash" }`. **No model is enabled by default — always check `DescribeAIModels` first and, if the target model is missing, enable it with `UpdateAIModel` before calling the SDK.** |
| `"hunyuan-exp"` | Only if `DescribeAIModels` explicitly returns this legacy builtin group for the current env (mainly the Mini Program Growth Plan — see `ai-model-wechat`). |
| `"custom-<your-name>"` | A user-defined GroupName you onboarded via `CreateAIModel`. **Must** start with `custom-` (e.g. `custom-kimi`, `custom-openai-compat`). |
### ❌ Do NOT write any of these — they are all wrong
```js
ai.createModel("deepseek") // wrong — that's a vendor, not a GroupName
ai.createModel("deepseek-v4-flash") // wrong — that's a model name, goes in the `model` field
ai.createModel("hunyuan") // wrong — vendor family, not a GroupName
ai.createModel("hunyuan-2.0-instruct-20251111") // wrong — model name
ai.createModel("glm") / ai.createModel("kimi") / ai.createModel("minimax") // wrong — vendor names
ai.createModel("openai") / ai.createModel("moonshot") // wrong — vendor names
ai.createModel("custom") // wrong — placeholder; use your real custom-<name>
ai.createModel(modelName) // wrong — do not reuse the variable that holds the model id
```
### ✅ Correct pattern — GroupName vs Model are two different fields
```js
const model = ai.createModel("cloudbase"); // ← GroupName
await model.generateText({
model: "deepseek-v4-flash", // ← concrete model id
messages: [...]
});
```
### Decision procedure (when the user names a specific model)
1. The user says "use DeepSeek v3.2" / "use hunyuan instruct" / "use Kimi k2.6" / "use GLM-5" / …
2. `createModel("cloudbase")` stays the same.
3. Put the model id into the **`model` field**: `{ model: "deepseek-v3.2" }`, `{ model: "hunyuan-2.0-instruct-20251111" }`, `{ model: "kimi-k2.6" }`, `{ model: "glm-5" }`, …
4. **Never assume the model is already enabled.** Before writing the SDK call, verify it is present in `DescribeAIModels({ GroupName: "cloudbase" }).Models[]`. If missing, call `DescribeManagedAIModelList` to confirm the exact `Model` name the platform supports (case-sensitive — do **not** guess the spelling), then enable it via `UpdateAIModel` with `Status: 1` (remember `Models` is a full replacement, so resend everything already enabled + the new one).
> If you are about to type `ai.createModel(` and the thing inside the parentheses is a vendor name, a model name, or a guess — **stop**. It is almost certainly one of the three legal values above.
---
## Mandatory Two-Step Preflight (before any SDK code)
Before generating any AI-related SDK code, **run the two-step preflight**: ① eligibility, ② group readiness. Emitting `createModel(...)` straight away and letting the user debug runtime errors is significantly more costly.
### Step 0: obtain the environment ID
Call the MCP tool `envQuery` with `action=info` and read `EnvId` from the response. Every subsequent check and purchase link uses this `EnvId`.
---
### Preflight ① — Eligibility (Token Credits resource pack)
Call the MCP tool:
```
callCloudApi(service="tcb", action="DescribeEnvPostpayPackage", params={ EnvId })
```
**Pass conditions (all required):**
- `envPostpayPackageInfoList` contains at least one entry
- That entry's `postpayPackageId` starts with `pkg_tcb_tokencredits_`
- That entry's `status` is NOT in `[3, 4]` (3 / 4 typically mean expired / disabled; trust the live response)
- ❌ **Not satisfied** → **stop writing code** and surface this to the user (replacing `{envId}` with the real id):
> The current environment has no active Token Credits resource pack. Please purchase one before calling any AI API:
> https://buy.cloud.tencent.com/lowcode?buyType=resPack&envId={envId}&resourceType=token
>
> Let me know once it's done and I'll re-check the resource pack status.
- ✅ **Satisfied** → proceed to preflight ②.
> Parameter casing is PascalCase by contract. If the call returns `InvalidParameter`, fall back to camelCase (`envId` / `envPostpayPackageInfoList`) and trust the live response. For the Mini Program scenario there is an additional growth-plan branch — switch to the `ai-model-wechat` skill.
---
### Preflight ② — Group readiness (`DescribeAIModels` → `UpdateAIModel` if needed)
Eligibility alone is not enough. **Do not write `createModel("cloudbase")` yet.** First confirm that the target `GroupName` exists in the env with `Status=1`, and that the target `Model` is present in its `Models[]`.
1. **List groups configured in the current env:**
```
callCloudApi(service="tcb", action="DescribeAIModels", params={ EnvId })
```
Returns `AIModelGroups: AIModelGroup[]`, where each `AIModelGroup` includes `GroupName`, `Type` (`builtin` / `custom`), `Models: [{ Model, EnableMCP, Tags }]`, `Status` (1 = on / 2 = off), `BaseUrl`, `Secret`, `Remark`. The main managed `GroupName` is `cloudbase`.
2. **Never assume a model is already enabled.** Inspect `AIModelGroups[?].Models[].Model` for the `cloudbase` group. If the target model (or, when the user did not specify one, the model you intend to default to such as `deepseek-v4-flash`) is missing, jump to step 4 and enable it — do not call `createModel("cloudbase")` yet. If the `cloudbase` group itself is missing or has `Status=2`, also jump to step 4.
3. **User asked for a model that belongs to the managed catalog** (e.g. `deepseek-v3.2`, `hunyuan-2.0-instruct-20251111`, `glm-5`, `kimi-k2.6`, …): check whether that `Model` is already in the `cloudbase` group's `Models[]`. If not, jump to step 4. **Do not guess the exact model id** — verify the canonical spelling in `DescribeManagedAIModelList` first (step 4 covers this).
4. **Enable / add a managed model** (always inspect the authoritative catalog + pricing first):
```
callCloudApi(service="tcb", action="DescribeManagedAIModelList", params={ EnvId })
```
Returns `ManagedAIModelGroup[]`, where each group lists `GroupName` (e.g. `cloudbase`), `Remark`, and `Models: [{ Model, EnableMCP, ModelSpec{ContextLength, MaxInputToken, MaxOutputToken}, ModelChargingInfo[{Type, InputPrice, OutputPrice, InputOutputUnit, CachePrice}] }]`. **This is the single source of truth for supported model names and pricing — do not infer them from memory. Use the exact `Model` string returned here when calling `UpdateAIModel`.** Also surface the prices to the user before enabling.
Then enable (note: `Models` is a **full replacement** — always resend the already-enabled models together with the new one):
```
callCloudApi(service="tcb", action="UpdateAIModel", params={
EnvId,
GroupName: "cloudbase",
Models: [
// resend every model that DescribeAIModels already showed as enabled
{ Model: "<already-enabled model, e.g. deepseek-v4-flash>" },
// append the newly-requested one, using the exact spelling from DescribeManagedAIModelList
{ Model: "<target model>" }
],
Status: 1
})
```
5. **The requested model is not in the managed catalog** (not found by `DescribeManagedAIModelList`) → jump to the next section, **Custom onboarding (models outside the managed catalog)**.
> All Actions use `service=tcb`, `Version=2018-06-08`. Parameters are PascalCase (`EnvId` / `GroupName` / `Models` / `Status`). Fall back to camelCase only if the call returns `InvalidParameter`.
---
## Available Providers and Models
`ai.createModel(<GroupName>)` accepts exactly three kinds of legal values:
### 1. `"cloudbase"` — the main managed group (recommended)
- `GroupName: "cloudbase"`, `Type: "builtin"`, `Remark: "腾讯云开发"` (Tencent CloudBase)
- Backed by **Tencent Cloud TokenHub**, a unified managed pool covering multiple vendors — **Hunyuan** (HY 2.0 Instruct, HY 2.0 Think, Hunyuan-role, Hy3 preview, …), **DeepSeek** (DeepSeek-V4-Pro, DeepSeek-V4-Flash, Deepseek-v3.2, Deepseek-v3.1, Deepseek-r1-0528, Deepseek-v3-0324, …), **Zhipu GLM** (GLM-5, GLM-5-Turbo, GLM-5.1, GLM-5V-Turbo), **Kimi** (K2.5, K2.6), **MiniMax** (M2.5, M2.7), and more. The roster evolves — **do not hard-code specific SKUs** in application code; discover at runtime.
- **No model is enabled by default.** Always call `DescribeAIModels` first to see what the env has actually enabled; if your target model is missing, call `DescribeManagedAIModelList` for the authoritative catalog + pricing and then `UpdateAIModel` (`Status: 1`, `Models` full-replacement) to enable it before making the SDK call.
- Authoritative catalog + pricing: `DescribeManagedAIModelList`
- Env-enabled set: `DescribeAIModels`
### 2. `"hunyuan-exp"` — legacy builtin group (kept for compatibility)
- Primarily relevant to the Mini Program Growth Plan scenario; do not use from Web unless the env explicitly still has it (switch to the `ai-model-wechat` skill for that flow)
- Default model: `hunyuan-2.0-instruct-20251111`; additional hunyuan SKUs must be discovered at runtime via `DescribeAIModels({ GroupName: "hunyuan-exp" }).Models[]` — do not hard-code other IDs
### 3. User-defined GroupName
- Onboarded via `CreateAIModel` (see the next section). The custom `GroupName` **MUST start with `custom-`** (e.g. `custom-kimi`, `custom-moonshot`, `custom-openai-compat`). This naming convention prevents future collisions with built-in / vendor GroupNames (`cloudbase`, `hunyuan-exp`, `deepseek`, `glm`, `kimi`, `minimax`, …) that the platform may introduce over time
- Examples: `createModel("custom-kimi")`, `createModel("custom-openai-compat")`
> **Never** write guesses like `createModel("deepseek")` or `createModel("custom")` unless `DescribeAIModels` explicitly returned that exact `GroupName` (old envs may still carry historical `deepseek` / `hunyuan-exp` builtin groups — that stays legal for compatibility, but new projects should always go through `cloudbase`).
---
## Custom onboarding (models outside the managed catalog)
When the user wants to call a **non-managed** model (self-hosted, enterprise-internal, third-party OpenAI-compatible endpoint, …), **do not block**. Guide them through onboarding:
### Option 1: console flow (recommended, user handles it)
`https://tcb.cloud.tencent.com/dev?envId={envId}#/ai`
### Option 2: programmatic onboarding (`CreateAIModel`)
```
callCloudApi(service="tcb", action="CreateAIModel", params={
EnvId: "<envId>",
GroupName: "custom-<your-name>", // MUST start with "custom-" (e.g. custom-kimi, custom-openai-compat); never start with "cloudbase"
BaseUrl: "<OpenAI-compatible endpoint, e.g. https://api.moonshot.cn/v1>",
Models: [
{ Model: "<model name, e.g. kimi-k2.5>", EnableMCP: true }
],
Remark: "<optional remark>",
Status: 1,
Secret: { ApiKey: "<vendor api key supplied by the user>" }
})
```
Once onboarded, confirm with `DescribeAIModels` that the group is ready, then call `ai.createModel("<the GroupName you just registered>")` from your code. Use `UpdateAIModel` to add/remove models, rotate keys, or change `BaseUrl` (remember `Models` is a **full replacement**). Use `DeleteAIModel` to remove a custom group (builtin groups cannot be deleted).
> Custom-model billing is covered by the third-party provider and does not draw from the Token Credits resource pack. Field casing follows the live contract — fall back to camelCase on `InvalidParameter`.
---
## Installation
```bash
npm install @cloudbase/js-sdk
```
## Initialization
> ⚠️ **Do not use anonymous sign-in as the default.** Anonymous login is **disabled by default** for new environments, and inactive existing environments have also been automatically disabled. Even when anonymous login is manually enabled, **anonymous users are denied AI model invocation permissions by default**. The AI-model skill does **not** prescribe a specific login UI — delegate that concern:
>
> - **Enabling / configuring login providers** (phone SMS, email, WeChat Open Platform, username+password, OAuth, …) → follow the **`auth-tool-cloudbase`** skill (backend config via `callCloudApi`).
> - **Building the actual sign-in flow in the browser** (login form, callbacks, session guarding) → follow the **`auth-web-cloudbase`** skill (`@cloudbase/js-sdk` auth API, e.g. `signInWithPassword`, `signInWithPhone`, `getSession`).
>
> Do **not** fall back to `signInAnonymously()` for AI features — anonymous users cannot call AI models. Only use anonymous login for non-AI read-only demos where the user explicitly requests it and accepts the trade-off.
```js
import cloudbase from "@cloudbase/js-sdk";
const app = cloudbase.init({
env: "<YOUR_ENV_ID>",
accessKey: "<YOUR_PUBLISHABLE_KEY>" // Get it from the CloudBase console
});
const auth = app.auth;
// CRITICAL: Use auth.getSession() to check login — NOT the deprecated getLoginState().
// getLoginState() returns uid even without real login (just accessKey), causing false positives.
// getSession() returns data.session === undefined when no real login exists.
// Anonymous users are DENIED AI model permissions — calling AI without real login will fail.
const { data: sessionData } = await auth.getSession();
if (!sessionData?.session || sessionData.session.user?.is_anonymous) {
// No real login or anonymous session — route to sign-in page
window.location.href = "/login";
return;
}
const ai = app.ai();
```
**Important notes:**
- Use synchronous initialization with a top-level import
- **`accessKey` causes `getLoginState()` to return misleading auth data** — the deprecated `getLoginState()` returns an object with `uid` even without real login, which breaks naive `!!loginState` checks. Use `auth.getSession()` instead: it returns `data.session === undefined` when no real login exists, so `!!data.session` is a reliable auth gate.
- The user MUST be authenticated with a verified login (phone, email, WeChat, username+password, custom) before using AI features. Anonymous users are denied AI model permissions. The exact flow is the responsibility of the `auth-web-cloudbase` skill.
- Get `accessKey` from the CloudBase console
---
## generateText() — non-streaming
> **Prerequisite:** the two-step preflight (eligibility + group readiness) has passed, and the target model has been confirmed present in `DescribeAIModels({ GroupName: "cloudbase" }).Models[]` — if it was not, it should already have been enabled via `UpdateAIModel`. The example below uses `deepseek-v4-flash` only for illustration; substitute the actual model the user asked for.
```js
const model = ai.createModel("cloudbase");
const result = await model.generateText({
model: "deepseek-v4-flash", // must already be enabled in this env (DescribeAIModels → UpdateAIModel)
messages: [{ role: "user", content: "Give me a one-paragraph intro to Li Bai." }],
});
console.log(result.text); // generated text string
console.log(result.usage); // { prompt_tokens, completion_tokens, total_tokens }
console.log(result.messages); // full message history
console.log(result.rawResponses); // raw model responses
```
---
## streamText() — streaming
> **Prerequisite:** the two-step preflight has passed.
```js
const model = ai.createModel("cloudbase");
const res = await model.streamText({
model: "deepseek-v4-flash",
messages: [{ role: "user", content: "Give me a one-paragraph intro to Li Bai." }],
});
// Option 1: iterate the text stream (recommended)
for await (let text of res.textStream) {
console.log(text); // incremental text chunks
}
// Option 2: iterate the data stream for full response chunks
for await (let data of res.dataStream) {
console.log(data); // full response chunk with metadata
}
// Option 3: access final results
const messages = await res.messages; // full message history
const usage = await res.usage; // token usage
```
---
## Error Handling Pattern
```js
const model = ai.createModel("cloudbase");
try {
const result = await model.generateText({
model: "deepseek-v4-flash",
messages: [{ role: "user", content: "Generate a concise onboarding checklist." }],
});
console.log(result.text);
} catch (error) {
console.error("Failed to call CloudBase AI from Web", error);
}
```
---
## Type Definitions
```ts
interface BaseChatModelInput {
model: string; // required: model name
messages: Array<ChatModelMessage>; // required: message array
temperature?: number; // optional: sampling temperature
topP?: number; // optional: nucleus sampling
}
type ChatModelMessage =
| { role: "user"; content: string }
| { role: "system"; content: string }
| { role: "assistant"; content: string };
interface GenerateTextResult {
text: string; // generated text
messages: Array<ChatModelMessage>; // full message history
usage: Usage; // token usage
rawResponses: Array<unknown>; // raw model responses
error?: unknown; // error if any
}
interface StreamTextResult {
textStream: AsyncIterable<string>; // incremental text stream
dataStream: AsyncIterable<DataChunk>; // full data stream
messages: Promise<ChatModelMessage[]>;// final message history
usage: Promise<Usage>; // final token usage
error?: unknown; // error if any
}
interface Usage {
prompt_tokens: number;
completion_tokens: number;
total_tokens: number;
}
```
---
## Best Practices
1. **Run the two-step preflight first** — ① eligibility (Token Credits resource pack via `DescribeEnvPostpayPackage`) + ② group readiness (`DescribeAIModels` to inspect what is enabled, `DescribeManagedAIModelList` for the authoritative supported-model catalog, `UpdateAIModel` with a full-replacement `Models[]` and `Status: 1` when the target model is missing). Skipping preflight leads straight to "model not found" / "model not enabled" errors at runtime.
2. **Never assume any model is already enabled** — not `deepseek-v4-flash`, not `hunyuan-*`, not anything. Always verify with `DescribeAIModels` first; if the target is missing, look up the exact `Model` string in `DescribeManagedAIModelList` (do **not** guess the spelling or invent vendor prefixes) and then `UpdateAIModel` to enable it.
3. **`createModel` accepts exactly three kinds of values** — `"cloudbase"` (the main managed group), `"hunyuan-exp"` (legacy builtin, Growth Plan scenarios), or a user-defined GroupName registered via `CreateAIModel` (**MUST start with `custom-`**, e.g. `custom-kimi`, `custom-openai-compat`). **Never** guess with `createModel("deepseek")` / `createModel("kimi")` / `createModel("custom")`.
4. **Do not invent SDK method names or parameters.** This SKILL.md is the authoritative reference for `@cloudbase/js-sdk`'s AI surface — look up the method signature here (or in the Type Definitions section below) before writing code. If a method or field is not documented here, stop and ask, or check the live contract via the MCP tools. No guessing.
5. **Show pricing before enabling a new managed model** — `DescribeManagedAIModelList` returns `ModelSpec` (context length, max input/output tokens) + `ModelChargingInfo` (input / output / cache prices, billing unit). Surface the prices to the user before calling `UpdateAIModel`.
6. **Use streaming for long responses** — better perceived latency and interactivity.
7. **Handle errors gracefully** — wrap AI calls in try/catch.
8. **Keep `accessKey` safe** — use a publishable key, never a secret key.
9. **Initialize early** — set up the SDK at app entry so auth and AI are both ready before routing.
10. **Do NOT use anonymous auth for AI features** — anonymous login is disabled by default for new environments, and anonymous users are denied AI model permissions. Require a verified sign-in (phone, email, username+password, WeChat, custom) before calling any AI API. Delegate provider configuration to the `auth-tool-cloudbase` skill and the browser sign-in flow to the `auth-web-cloudbase` skill; the AI-model skill checks `auth.getSession()` and verifies `loginType` before gating the call.
11. **Distinguish "preflight failure" from "model call failure"** — the former means the user needs to buy a resource pack or call `UpdateAIModel`; the latter is a prompt / parameter / network issue. Give the user different guidance for each.
12. **TypeScript: do NOT use `any` to silence type errors from the SDK.** The SDK ships its own types; if an error shows up, narrow with `unknown` + a type guard, write a precise `interface` for the shape you actually consume, or augment types in a local `.d.ts`. Never `: any`, `as any`, `@ts-ignore`, or `@ts-nocheck`. See the Engineering constitution in the `web-development` skill.
13. **Self-verify before claiming done.** Run `tsc --noEmit` + the project build + open the page with `agent-browser` and actually trigger the AI call. Confirm: (a) the text stream reaches the UI, (b) no new console errors, (c) `result.usage` is non-zero. Saying "it should work" without evidence is not acceptable — follow `web-development/browser-testing.md`.
references/ai-model-wechat/SKILL.md
---
name: ai-model-wechat
description: "Use this skill for WeChat Mini Program AI via wx.cloud.extend.AI (小程序, wx.cloud apps). Covers generateText and streamText with callbacks (onText, onEvent, onFinish); streamText needs a data wrapper, generateText returns the raw response. Models via wx.cloud.extend.AI.createModel with groups hunyuan-exp (小程序成长计划), cloudbase (main managed), or custom-*; model id goes in the data wrapper `model` field. MUST run two-step preflight before code — see body. NOT for browser/Web (use ai-model-web), Node.js backend (use ai-model-nodejs), or image generation (use ai-model-nodejs)."
version: 2.33.1
alwaysApply: false
---
## Sibling skills (local only)
Sibling CloudBase skills ship beside this skill. Use local relative paths such as `../auth-tool-cloudbase/SKILL.md`.
If a referenced sibling skill file is missing from this environment, ask the user to install the full CloudBase plugin (or the missing skill). Do **not** HTTP-fetch remote skill or protocol markdown into the agent context.
## When to use this skill
Use this skill for **calling AI models in WeChat Mini Program** using `wx.cloud.extend.AI`.
**Use it when you need to:**
- Integrate AI text generation in a Mini Program
- Stream AI responses with callback support
- Call Hunyuan models from the WeChat environment
**Do NOT use for:**
- Browser/Web apps → use `ai-model-web` skill
- Node.js backend or cloud functions → use `ai-model-nodejs` skill
- Image generation → use `ai-model-nodejs` skill (not available in Mini Program)
- Runtimes without a CloudBase SDK (native apps, Python, etc.) → use `http-api-cloudbase` skill (it now includes the `ai_model` OpenAPI spec for direct HTTP calls)
---
## ⛔ STOP — `wx.cloud.extend.AI.createModel(provider)` argument is **not** a vendor / model name
Read this before writing any `createModel(...)` line. Agents frequently hallucinate this argument. There are **exactly three** legal shapes. Anything else is a bug.
| ✅ Legal `createModel(provider)` argument | When to use it |
|-----------------------------------------|----------------|
| `"hunyuan-exp"` | The Mini Program **成长计划** (`ai_miniprogram_inspire_plan`) is enrolled for the current env. Default model: `hunyuan-2.0-instruct-20251111`. |
| `"cloudbase"` | Default fallback. Main managed group (TokenHub-backed, multi-vendor pool). Vendor + concrete model go into the **`model` field**, e.g. `{ model: "deepseek-v4-flash" }`. |
| `"custom-<your-name>"` | A user-defined GroupName you onboarded via `CreateAIModel`. **Must** start with `custom-` (e.g. `custom-kimi`, `custom-openai-compat`). |
### ❌ Do NOT write any of these — they are all wrong
```js
wx.cloud.extend.AI.createModel("deepseek") // wrong — vendor, not GroupName
wx.cloud.extend.AI.createModel("deepseek-v4-flash") // wrong — model id goes in `model`
wx.cloud.extend.AI.createModel("hunyuan") // wrong — vendor family
wx.cloud.extend.AI.createModel("hunyuan-2.0-instruct-20251111") // wrong — model name
wx.cloud.extend.AI.createModel("glm") / "kimi" / "minimax" // wrong — vendor names
wx.cloud.extend.AI.createModel("custom") // wrong — placeholder
wx.cloud.extend.AI.createModel(modelName) // wrong — do not reuse the model-id variable
```
### ✅ Correct pattern — provider vs model are two different fields
```js
// Growth Plan branch
const model = wx.cloud.extend.AI.createModel("hunyuan-exp"); // ← provider / GroupName
await model.streamText({
data: { model: "hunyuan-2.0-instruct-20251111", messages: [...] } // ← concrete model id
});
// Token Credits branch
const model = wx.cloud.extend.AI.createModel("cloudbase");
await model.streamText({
data: { model: "deepseek-v4-flash", messages: [...] }
});
```
### Decision procedure (when the user names a specific model)
1. The user says "use DeepSeek v3.2" / "use hunyuan thinking" / "use Kimi k2.6" / …
2. First run the eligibility decision tree below — the correct `provider` may be `"hunyuan-exp"` (if the env is on Growth Plan and the user asked for a `hunyuan-*` model) or `"cloudbase"` (anything else in the managed catalog).
3. Put the model id into the **`model` field** inside `data`: `{ model: "deepseek-v3.2" }`, `{ model: "hunyuan-2.0-instruct-20251111" }`, `{ model: "kimi-k2.6" }`, …
4. Before using the model id, make sure it is present in `DescribeAIModels({ GroupName: "cloudbase" }).Models[]`. If not, enable it via `UpdateAIModel`.
> If you are about to type `wx.cloud.extend.AI.createModel(` and the thing inside the parentheses is a vendor name or a model id — **stop**. It is almost certainly one of the three legal values above.
---
## Mandatory Two-Step Preflight
You MUST NOT jump straight into `wx.cloud.extend.AI.createModel(...)`. Before writing any business code, confirm **billing eligibility** and **group readiness** in this fixed order: **① eligibility → ② group readiness**. Do not swap the two.
### Preflight ① · Billing Eligibility (two parallel billing paths)
The Mini Program side has two billing paths: **小程序成长计划** (checked first; if enrolled, use `hunyuan-exp`) and **Token Credits 资源包** (generic fallback; if available, use the `cloudbase` main managed group).
1. Fetch `envId` via the MCP tool `envQuery action=info`.
2. Pick the branch by user intent:
| User intent | Eligibility to check first | `createModel` provider on hit | Model selection | Guidance on miss |
|-------------|----------------------------|-------------------------------|-----------------|------------------|
| No model specified / default call | Check **小程序成长计划** enrollment first; if not enrolled, fall back to Token Credits resource pack | Enrolled: `"hunyuan-exp"`; otherwise: `"cloudbase"` | Enrolled: `hunyuan-2.0-instruct-20251111` (the 成长计划 default). Otherwise: pick a text model with the user, then verify/enable it in the `"cloudbase"` group via `DescribeAIModels` → `DescribeManagedAIModelList` → `UpdateAIModel` | Plan not enrolled → point to `https://docs.cloudbase.net/ai/ai-inspire-plan`; resource pack missing → purchase link |
| User requests a `hunyuan-*` model | **小程序成长计划** enrollment | `"hunyuan-exp"` (plan-exclusive Token pack billing) | `hunyuan-2.0-instruct-20251111` if present; otherwise verify via `DescribeAIModels({ GroupName: "hunyuan-exp" }).Models[]` and `UpdateAIModel` to enable | Not enrolled → enroll first, or switch to `"cloudbase"` + a non-hunyuan model |
| User requests `deepseek-*` / `glm-*` / `kimi-*` / `minimax-*` / other non-hunyuan managed models | **Token Credits 资源包** activation | `"cloudbase"` | Do NOT assume the model is already enabled. `DescribeAIModels` → if missing, `DescribeManagedAIModelList` for the canonical `Model` string → `UpdateAIModel` with `Status: 1` (full-replacement `Models[]`) | Resource pack not activated → purchase link |
| User requests a third-party / self-hosted (non-managed) model | Skip billing eligibility and go to "Custom onboarding" | Custom GroupName (must start with `custom-`) | Registered via `CreateAIModel.Models[]` | Offer both console + `CreateAIModel` paths |
3. Check 小程序成长计划 enrollment:
```ts
callCloudApi({
service: "tcb",
action: "DescribeActivityInfo",
params: {
ActivityNames: ["ai_miniprogram_inspire_plan"], // PascalCase preferred; switch to camelCase if InvalidParameter is returned
},
})
```
**Hit criterion:** the response's `attendRecords` contains at least one entry where `activityName === "ai_miniprogram_inspire_plan"` and `envId` matches the current environment. On hit, default to `createModel("hunyuan-exp")` + `hunyuan-2.0-instruct-20251111`; billing uses the plan-exclusive Token pack `pkg_hunyuan_token_la_inspire_100m`.
**On miss:** do NOT silently fall back. Tell the user "the current environment is not enrolled in 小程序成长计划", surface the enrollment entry `https://docs.cloudbase.net/ai/ai-inspire-plan`, and ask whether to enroll and retry, or to switch to the Token Credits resource pack path with a non-hunyuan model.
4. Check the Token Credits resource pack (when the path leads to the `"cloudbase"` main managed group):
```ts
callCloudApi({
service: "tcb",
action: "DescribeEnvPostpayPackage",
params: {
EnvId: "<current envId>",
},
})
```
**Hit criterion:** `envPostpayPackageInfoList` contains an entry whose `postpayPackageId` starts with `pkg_tcb_tokencredits_`, has `status ∉ [3, 4]` (not expired, not disabled), and `versionSwitchStatus` is not in a blocking state.
**On miss:** surface the purchase link (replace `{envId}` with the real ID — never leave the placeholder):
```
https://buy.cloud.tencent.com/lowcode?buyType=resPack&envId={envId}&resourceType=token
```
### Preflight ② · Group Readiness (mandatory for every Mini Program AI call)
Passing eligibility does not mean the target model is callable. **No model is enabled by default** in the `"cloudbase"` main managed group — you must first call `DescribeAIModels` to see what is enabled, then (if missing) `DescribeManagedAIModelList` for the authoritative supported-model catalog and `UpdateAIModel` with `Status: 1` to enable it. The `"hunyuan-exp"` group's readiness is driven by 成长计划 enrollment — enrollment alone makes `hunyuan-2.0-instruct-20251111` available, but any other hunyuan SKU still has to be checked against `DescribeAIModels({ GroupName: "hunyuan-exp" }).Models[]` and enabled via `UpdateAIModel` if missing.
1. Query the groups and switches currently configured in the environment (`tcb` Action `DescribeAIModels`, Version `2018-06-08`):
```ts
callCloudApi({
service: "tcb",
action: "DescribeAIModels",
params: { EnvId: "<envId>" },
})
```
Returns `AIModelGroups: AIModelGroup[]`. Each `AIModelGroup` has `GroupName` (e.g. `cloudbase` / `hunyuan-exp` / your custom group), `Type` (`builtin` / `custom`), `Models: [{ Model, EnableMCP, Tags }]`, and `Status` (1=on / 2=off). Group readiness = all three of: the `GroupName` exists + `Status === 1` + the target `Model` is present in `Models[]`.
2. If the target model is not in the `DescribeAIModels` response, query the platform catalog + pricing via `DescribeManagedAIModelList` — it returns `ManagedAIModelGroup[]` including `ModelSpec` (context length, etc.) and `ModelChargingInfo` (`Uniform` / `Tiered` pricing). Pick the target model, then enable it via `UpdateAIModel`:
```ts
callCloudApi({
service: "tcb",
action: "UpdateAIModel",
params: {
EnvId: "<envId>",
GroupName: "cloudbase",
Status: 1, // 1=on, 2=off
Models: [
{ Model: "deepseek-v4-flash", EnableMCP: false },
{ Model: "deepseek-v3.2", EnableMCP: false }, // append the new model to enable
],
// ⚠️ `Models` is a FULL REPLACEMENT, not incremental; merge the old list + new entries before passing.
},
})
```
3. Once both steps pass, only THEN write `wx.cloud.extend.AI.createModel("<GroupName>")` in the Mini Program code, and pass a `model` value that exists in that group's `Models[]`.
> **Order is fixed.** Without eligibility, no enabled model will bill; without group readiness, even with eligibility you will receive `ModelNotEnabled`-class errors. Both must be done before business code.
>
> **API casing tip:** `tcb` public-service Actions officially use PascalCase (`EnvId`, `GroupName`, `ActivityNames`); some docs show camelCase. On the first call, if you hit `InvalidParameter`, switch casing and retry, then freeze the working form in your project's wrapper.
---
## Available Providers and Models
The `provider` argument of `wx.cloud.extend.AI.createModel(provider)` equals the `GroupName` returned by `DescribeAIModels`. Only three kinds of values are legal. Run the decision tree before choosing.
### A. 小程序成长计划 exclusive (default when enrolled)
| createModel provider | Default model | Other available models | Notes |
|----------------------|---------------|------------------------|-------|
| `"hunyuan-exp"` | `hunyuan-2.0-instruct-20251111` | Additional hunyuan SKUs (e.g. instruct / thinking / turbos / role variants) — **query at runtime** via `DescribeAIModels({ GroupName: "hunyuan-exp" }).Models[]`, do NOT hard-code | Legacy `Type=builtin` GroupName; billed via `pkg_hunyuan_token_la_inspire_100m`; do NOT use without 成长计划 enrollment |
### B. Main managed group (Token Credits pack scenario, recommended default)
The `"cloudbase"` GroupName is backed by **Tencent Cloud TokenHub**, a unified managed pool that covers multiple first-party and third-party vendors — including the **Hunyuan** family (HY 2.0 Instruct, HY 2.0 Think, Hunyuan-role, Hy3 preview, …), **DeepSeek** family (DeepSeek-V4-Pro, DeepSeek-V4-Flash, Deepseek-v3.2, Deepseek-v3.1, Deepseek-r1-0528, Deepseek-v3-0324, …), **Zhipu GLM** (GLM-5, GLM-5-Turbo, GLM-5.1, GLM-5V-Turbo), **Kimi** (K2.5, K2.6), **MiniMax** (M2.5, M2.7) and more. The roster evolves over time, so **do not hard-code the list in application code** — always discover it at runtime.
| createModel provider | Model readiness | How to enable a model | Notes |
|----------------------|-----------------|-----------------------|-------|
| `"cloudbase"` | **No model is enabled by default** — always check `DescribeAIModels({ GroupName: "cloudbase" }).Models[]` first | 1) Fetch the authoritative catalog + pricing via `DescribeManagedAIModelList` (do NOT guess the `Model` string). 2) Call `UpdateAIModel` with `Status: 1` and a full-replacement `Models[]` that includes the target model | Unified managed group (`Type=builtin`), Remark `"腾讯云开发"`, depends on a `pkg_tcb_tokencredits_*` resource pack |
> ⚠️ Common Mini Program mistake: writing `createModel("deepseek")` / `createModel("hunyuan")` / `createModel("glm")` / `createModel("kimi")` / `createModel("minimax")` / `createModel("custom")`. All wrong — those are **vendor / model names**, not provider / GroupName. The provider must be one of the `GroupName` values returned by `DescribeAIModels`. New projects always use the unified `"cloudbase"` managed group and select the concrete vendor model via the `model` field.
### C. Not in the managed catalog → Custom onboarding
Models involving third-party / self-hosted / OpenAI-compatible endpoints (anything not appearing in A/B) do NOT go through the billing paths above. You must register a `Type=custom` GroupName via "Custom onboarding" first. See the next section.
---
## Custom Onboarding (when not in the managed catalog)
When the user specifies a model that is neither in 成长计划 (`hunyuan-exp`) nor in the main managed group (`cloudbase`) catalog (e.g. enterprise-hosted OpenAI-compatible endpoints, third-party model services), pick one of the two paths below. Use neutral phrasing such as "third-party / self-hosted / OpenAI-compatible endpoint" — **do not name specific competitor brands**.
**Path 1 · Register in the console**
Point the user to the CloudBase console AI model page:
```
https://tcb.cloud.tencent.com/dev?envId={envId}#/ai
```
Replace `{envId}` with the real environment ID and let the user fill in model name, endpoint, API key, etc.
**Path 2 · Register via `callCloudApi` + `CreateAIModel`**
The `tcb` Action `CreateAIModel` (Version `2018-06-08`) creates a `Type=custom` AI model group in the current environment:
```ts
callCloudApi({
service: "tcb",
action: "CreateAIModel",
params: {
EnvId: "<current envId>",
GroupName: "custom-openai-compat", // ⚠️ MUST start with "custom-" (e.g. custom-kimi, custom-moonshot) to avoid colliding with built-in / vendor GroupNames; this becomes the value passed to createModel(provider)
BaseUrl: "https://api.example.com/v1",
Models: [
{ Model: "gpt-4o-mini", EnableMCP: false },
{ Model: "gpt-4o", EnableMCP: false },
],
Remark: "Internal OpenAI-compatible endpoint",
Status: 1, // 1=on, 2=off
Secret: {
// Key / ApiKey: pick one; OpenAI-compatible endpoints usually use ApiKey
ApiKey: "<vendor-api-key>",
},
},
})
```
After registration:
- Run `DescribeAIModels` to confirm the `GroupName` exists with `Status=1` and the target `Model` appears in `Models[]`.
- In the Mini Program, call `wx.cloud.extend.AI.createModel("custom-openai-compat")` and pass a registered model name (e.g. `"gpt-4o-mini"`) as the `model` field.
- To add or modify models later, use `UpdateAIModel` (remember `Models` is a **full replacement**; `Status` uses 1/2 as on/off). To delete an entire custom group, use `DeleteAIModel` (custom groups only; batch via `GroupNames.N`).
- All calls still hit the environment's billing path. If such custom models also need Token settlement, eligibility must be verified first.
---
## Prerequisites
- WeChat base library **3.7.1+**
- No extra SDK installation needed
---
## Initialization
```js
// app.js
App({
onLaunch: function() {
wx.cloud.init({ env: "<YOUR_ENV_ID>" });
}
})
```
---
## generateText() - Non-streaming
⚠️ **Different from JS/Node SDK:** the return value is the raw model response.
> **Prerequisite:** the "Mandatory Two-Step Preflight" has been completed **and** the target model has been confirmed enabled via `DescribeAIModels` (or enabled via `UpdateAIModel` if missing). The example below assumes the current environment is enrolled in 小程序成长计划 and uses `createModel("hunyuan-exp")` + `hunyuan-2.0-instruct-20251111`. If the eligibility branch landed on the resource pack, swap the provider to `"cloudbase"` and set the `model` to whatever the user chose and you have just enabled via `UpdateAIModel` — never assume `deepseek-v4-flash` is already on.
```js
const model = wx.cloud.extend.AI.createModel("hunyuan-exp");
const res = await model.generateText({
model: "hunyuan-2.0-instruct-20251111", // plan-enrolled default
messages: [{ role: "user", content: "hi" }],
});
// ⚠️ Return value is the RAW model response, NOT wrapped like JS/Node SDK
console.log(res.choices[0].message.content); // access via choices array
console.log(res.usage); // token usage
```
---
## streamText() - Streaming
⚠️ **Different from JS/Node SDK:** parameters MUST be wrapped in a `data` object; callbacks are supported.
> **Prerequisite:** the "Mandatory Two-Step Preflight" has been completed and the target model has been enabled. The example below uses the 成长计划 branch; for the resource pack branch, swap `createModel("hunyuan-exp")` to `createModel("cloudbase")` and the `model` to whatever the user chose and you have just enabled via `UpdateAIModel` (no model is enabled by default).
```js
const model = wx.cloud.extend.AI.createModel("hunyuan-exp");
// ⚠️ Parameters MUST be wrapped in a `data` object
const res = await model.streamText({
data: { // ⚠️ Required wrapper
model: "hunyuan-2.0-instruct-20251111", // plan-enrolled default
messages: [{ role: "user", content: "hi" }]
},
onText: (text) => { // Optional: incremental text callback
console.log("New text:", text);
},
onEvent: ({ data }) => { // Optional: raw event callback
console.log("Event:", data);
},
onFinish: (fullText) => { // Optional: completion callback
console.log("Done:", fullText);
}
});
// Async iteration is also available
for await (let str of res.textStream) {
console.log(str);
}
// Check for completion via eventStream
for await (let event of res.eventStream) {
console.log(event);
if (event.data === "[DONE]") { // ⚠️ Check for [DONE] to stop
break;
}
}
```
---
## Error Handling Pattern
> **Prerequisite:** the "Mandatory Two-Step Preflight" has been completed. For the resource pack branch, use `"cloudbase"` + the specific text model you just verified/enabled via `DescribeAIModels` / `UpdateAIModel` — no model is enabled by default.
```js
const model = wx.cloud.extend.AI.createModel("cloudbase");
try {
const res = await model.generateText({
model: "deepseek-v4-flash",
messages: [{ role: "user", content: "Write a welcome message" }],
});
console.log(res.choices[0].message.content);
} catch (error) {
console.error("Mini Program AI request failed", error);
}
```
---
## API Comparison: JS/Node SDK vs WeChat Mini Program
| Feature | JS/Node SDK | WeChat Mini Program |
|---------|-------------|---------------------|
| **Namespace** | `app.ai()` | `wx.cloud.extend.AI` |
| **generateText params** | Direct object | Direct object |
| **generateText return** | `{ text, usage, messages }` | Raw: `{ choices, usage }` |
| **streamText params** | Direct object | ⚠️ Wrapped in `data: {...}` |
| **streamText return** | `{ textStream, dataStream }` | `{ textStream, eventStream }` |
| **Callbacks** | Not supported | `onText`, `onEvent`, `onFinish` |
| **Image generation** | Node SDK only | Not available |
---
## Type Definitions
### streamText() Input
```ts
interface WxStreamTextInput {
data: { // ⚠️ Required wrapper object
model: string;
messages: Array<{
role: "user" | "system" | "assistant";
content: string;
}>;
};
onText?: (text: string) => void; // incremental text callback
onEvent?: (prop: { data: string }) => void; // raw event callback
onFinish?: (text: string) => void; // completion callback
}
```
### streamText() Return
```ts
interface WxStreamTextResult {
textStream: AsyncIterable<string>; // incremental text stream
eventStream: AsyncIterable<{ // raw event stream
event?: unknown;
id?: unknown;
data: string; // "[DONE]" when complete
}>;
}
```
### generateText() Return
```ts
// Raw model response (OpenAI-compatible format)
interface WxGenerateTextResponse {
id: string;
object: "chat.completion";
created: number;
model: string;
choices: Array<{
index: number;
message: {
role: "assistant";
content: string;
};
finish_reason: string;
}>;
usage: {
prompt_tokens: number;
completion_tokens: number;
total_tokens: number;
};
}
```
---
## Best Practices
1. **Run the two-step preflight before writing business code.** Fixed order: ① `DescribeActivityInfo` / `DescribeEnvPostpayPackage` for billing eligibility → ② `DescribeAIModels` for group readiness (if needed, `DescribeManagedAIModelList` for catalog + pricing, then `UpdateAIModel` to enable the target model). Only after both pass should you write `wx.cloud.extend.AI.createModel(...)`.
2. **`createModel(provider)` accepts only three kinds of values** — `"hunyuan-exp"` (成长计划 exclusive legacy group), `"cloudbase"` (main managed group, default for new projects), or the custom-onboarding `GroupName` (**MUST start with `custom-`**, e.g. `custom-kimi`, `custom-openai-compat`, to avoid colliding with built-in / vendor names). **Never** write `createModel("deepseek")` (unless `DescribeAIModels` truly returns a legacy builtin group named `deepseek`), `createModel("hunyuan")`, `createModel("kimi")`, or `createModel("custom")` — these are model/vendor names or placeholders, not GroupNames.
3. **The `model` field must come from `DescribeAIModels`.** Pass a value that actually exists in the `Models[].Model` list of the chosen group. The main managed group only has `deepseek-v4-flash` enabled by default; to use others, call `UpdateAIModel` first.
4. **Hunyuan models are strictly bound to 成长计划.** To use a `hunyuan-*` model, the 成长计划 must be enrolled. When not enrolled, guide the user to `https://docs.cloudbase.net/ai/ai-inspire-plan`, or switch to `"cloudbase"` + `deepseek-v4-flash`. Do not bypass the check and call anyway.
5. **Check pricing before enabling more models.** `DescribeManagedAIModelList` returns `ModelChargingInfo` (`Uniform` flat price / `Tiered` tiered pricing) + `ModelSpec.ContextLength`. Confirm before calling `UpdateAIModel`. `Models` is a **full replacement** — merge the old list + the new entry before passing.
6. **Check base library version.** 3.7.1+ is required; on older versions `wx.cloud.extend.AI` is `undefined` — do not debug it as a model issue.
7. **Use callbacks for UI updates.** `onText` is well-suited for progressively refreshing chat bubbles; manually concatenating from `eventStream` tends to drop separators.
8. **Check for `[DONE]`.** When iterating `eventStream`, stop only when `event.data === "[DONE]"`, otherwise the stream waits forever for the next frame.
9. **Remember the `data` wrapper.** `streamText` parameters MUST be wrapped in `data: { ... }` — unlike JS/Node SDK. Forgetting it yields a parameter error.
10. **Distinguish "not-eligible / group-not-ready" from "call failure".** The former should guide the user into enrollment / purchase / `UpdateAIModel` flows; the latter is about debugging prompts, parameters, or the network. The error messages and next actions are completely different.
11. **Do not hardcode third-party model API keys in the Mini Program.** For models outside the managed catalog, use `CreateAIModel` (`Secret.ApiKey`) so the key is stored on the CloudBase side; keep only the `GroupName` in the Mini Program.
12. **TypeScript: do NOT use `any` to silence type errors.** If the `wx.cloud.extend.AI` surface is missing types, declare a precise `interface` for the slice you actually use, or augment via a local `.d.ts`. Never `: any`, `as any`, `@ts-ignore`, `@ts-nocheck`.
13. **Self-verify before claiming done.** Build the Mini Program, open it in the WeChat DevTools simulator, exercise the real `streamText` / `generateText` flow end-to-end, and confirm: (a) the text chunks arrive via `onText`, (b) `[DONE]` terminates the stream, (c) no new console errors. "It should work" without an actual run is not acceptable evidence.
references/auth-nodejs-cloudbase/SKILL.md
---
name: auth-nodejs-cloudbase
description: CloudBase Node SDK auth guide for server-side identity, user lookup, and custom login tickets. This skill should be used when Node.js code must read caller identity, inspect end users, or bridge an existing user system into CloudBase; not when configuring providers or building client login UI.
version: 2.33.1
alwaysApply: false
---
## Sibling skills (local only)
Sibling CloudBase skills ship beside this skill. Use local relative paths such as `../auth-tool-cloudbase/SKILL.md`.
If a referenced sibling skill file is missing from this environment, ask the user to install the full CloudBase plugin (or the missing skill). Do **not** HTTP-fetch remote skill or protocol markdown into the agent context.
## Activation Contract
### Use this first when
- Node.js code in cloud functions or backend services must read caller identity, look up users, or issue custom login tickets.
- The backend responsibility is auth / identity, not provider setup or frontend login UI.
### Read before writing code if
- The task mentions `@cloudbase/node-sdk`, server-side auth, custom login tickets, or "who is calling".
- The request mixes frontend login with backend identity logic; split the flow and route client-side work elsewhere.
### Then also read
- Provider setup / publishable key -> `../auth-tool-cloudbase/SKILL.md`
- Web login UI that consumes custom tickets -> `../auth-web-cloudbase/SKILL.md`
- Raw HTTP auth client -> `../http-api-cloudbase/SKILL.md`
### Do NOT use for
- Provider enable/disable or login console configuration.
- Frontend login / sign-up UI.
- Mini program native auth.
### Common mistakes / gotchas
- Using this skill as the entry point for every auth request.
- Mixing provider-management work with Node-side identity code.
- Reaching for raw HTTP examples when Node SDK already covers the job.
## When to use this skill
Use this skill whenever the task involves **server-side authentication or identity** in a CloudBase project, and the code is running in **Node.js**, for example:
- CloudBase 云函数 (Node runtime) that needs to know **who is calling**
- Node services that use **CloudBase Node SDK** to look up user information
- Backends that issue **custom login tickets** for Web / mobile clients
- Admin or ops tools that need to inspect CloudBase end-user profiles
**Do NOT use this skill for:**
- Frontend Web login / sign-up flows using `@cloudbase/js-sdk` (handle those with the **auth-web** skill, not this Node skill).
- Direct HTTP auth API integrations (this skill does not describe raw HTTP endpoints; use the **http-api** skill instead).
- Database or storage operations that do not involve identity (use database/storage docs or skills).
When the user request mixes frontend and backend concerns (e.g. "build a web login page and a Node API that knows the user"), treat them separately:
- Use Web-side auth docs/skills for client login and UX.
- Use this Node Auth skill for how the backend sees and uses the authenticated user.
---
## How to use this skill (for a coding agent)
When you load this skill to work on a task:
1. **Clarify the runtime and responsibility**
Ask the user:
- Where does this Node code run?
- CloudBase 云函数
- Long‑running Node service using CloudBase
- What do they need from auth?
- Just the **caller identity** for authorization?
- **Look up arbitrary users** by UID / login identifier?
- **Bridge their own user system** into CloudBase via custom login?
2. **Confirm CloudBase environment and SDK**
- Ask for:
- `env` – CloudBase environment ID
- Install the latest `@cloudbase/node-sdk` from npm if it is not already available.
- Always initialize the SDK using this pattern (values can change, shape must not):
```ts
import tcb from "@cloudbase/node-sdk";
const app = tcb.init({ env: "your-env-id" });
const auth = app.auth();
```
3. **Pick the relevant scenario from this file**
- For **caller identity inside a function**, use the `getUserInfo` scenarios.
- For **full user profile or admin lookup**, use the `getEndUserInfo` and `queryUserInfo` scenarios.
- For **client systems that already have their own users**, use the **custom login ticket** scenarios built on `createTicket`.
- For **logging / security**, use the `getClientIP` scenario.
4. **Follow Node SDK API shapes exactly**
- Treat all `auth.*` methods and parameter shapes in this file as canonical.
- You may change variable names and framework (e.g. Express vs 云函数 handler), but **do not change SDK method names or parameter fields**.
- If you see a method in older code that is not listed here or in the Node SDK docs mirror, treat it as suspect and avoid using it.
5. **If you are unsure about an API**
- Consult the official CloudBase Auth Node SDK documentation.
- Only use methods and shapes that appear in the official documentation.
- If you cannot find an API you want:
- Prefer composing flows from the documented methods, or
- Explain that this skill only covers Node SDK auth, and suggest using the relevant CloudBase Web or HTTP auth documentation for client-side or raw-HTTP flows.
---
## Node auth architecture – how Node fits into CloudBase Auth
CloudBase Auth separates **where users log in** from **where backend code runs**:
- Users log in through the supported auth methods (username/password, SMS, email, WeChat, custom login, anonymous — disabled by default, etc.) using client SDKs or HTTP interfaces, as described in the official CloudBase Auth overview documentation.
- Once logged in, CloudBase attaches the user identity and tokens to the environment.
- Node code then **reads** that identity using the Node SDK, or **bridges** external identities into CloudBase using custom login.
In practice, Node code usually does one or more of:
1. **Identify the current caller**
- In 云函数, use `auth.getUserInfo()` to read `uid`, `openId`, and `customUserId`.
- Use this identity for **authorization decisions**, logging, and personalisation.
2. **Look up other users**
- Use `auth.getEndUserInfo(uid)` when you know the CloudBase `uid`.
- Use `auth.queryUserInfo({ platform, platformId, uid? })` when you only have login identifiers such as phone, email, username, or a custom ID.
3. **Issue custom login tickets**
- When you already have your own user system, your Node backend can call `auth.createTicket(customUserId, options)` and return the ticket to a trusted client.
- The client (typically Web) then uses this ticket with the Web SDK to log the user into CloudBase without forcing them to sign up again.
4. **Log client IP for security**
- In 云函数, `auth.getClientIP()` returns the caller IP, which you can use for audit logs, anomaly detection, or access control.
The scenarios later in this file turn these responsibilities into explicit, copy‑pasteable patterns.
---
## Node Auth APIs covered by this skill
This skill covers the following `auth` methods on the CloudBase Node SDK. Treat these method signatures as the only supported entry points for Node auth flows when using this skill:
- `getUserInfo(): IGetUserInfoResult`
Returns `{ openId, appId, uid, customUserId }` for the **current caller**.
- `getEndUserInfo(uid?: string, opts?: ICustomReqOpts): Promise<{ userInfo: EndUserInfo; requestId?: string }>`
Returns detailed CloudBase end‑user profile for a given `uid` or for the current caller (when `uid` is omitted).
- `queryUserInfo(query: IUserInfoQuery, opts?: ICustomReqOpts): Promise<{ userInfo: EndUserInfo; requestId?: string }>`
Finds a user by login identifier (`platform` + `platformId`) or `uid`.
- `getClientIP(): string`
Returns the caller’s IP address when running in a supported environment (e.g. 云函数).
- `createTicket(customUserId: string, options?: ICreateTicketOpts): string`
Creates a **custom login ticket** for the given `customUserId` that clients can exchange for a CloudBase login.
The exact field names and allowed values for `EndUserInfo`, `IUserInfoQuery`, and `ICreateTicketOpts` are defined by the official CloudBase Node SDK typings and documentation. When writing Node code, do not guess shapes; follow the SDK types and the examples in this file.
---
## Scenarios – Node auth patterns
### Scenario 1: Initialize Node SDK and auth in a CloudBase function
Use this when writing a CloudBase 云函数 that needs to interact with Auth:
```ts
import tcb from "@cloudbase/node-sdk";
const app = tcb.init({ env: "your-env-id" });
const auth = app.auth();
exports.main = async (event, context) => {
// Your logic here
};
```
Key points:
- Use the same `env` as configured for the function’s CloudBase 环境.
- Avoid hardcoding sensitive values; prefer environment variables or function configuration.
### Scenario 2: Get caller identity in a CloudBase function
Use this when you need to know **who is calling** your cloud function:
```ts
import tcb from "@cloudbase/node-sdk";
const app = tcb.init({ env: "your-env-id" });
const auth = app.auth();
exports.main = async (event, context) => {
const { openId, appId, uid, customUserId } = auth.getUserInfo();
console.log("Caller identity", { openId, appId, uid, customUserId });
// Use uid / customUserId for authorization decisions
// e.g. check roles, permissions, or data ownership
};
```
Best practices:
- Treat `uid` as the canonical CloudBase user identifier.
- Use `customUserId` only when you have enabled **自定义登录** and mapped your own users.
- Never trust `openId`/`appId` alone for authorization; they are WeChat‑specific identifiers.
### Scenario 3: Get full end‑user profile by UID
Use this when you know a user’s CloudBase `uid` (for example, from a database record) and you need detailed profile information:
```ts
import tcb from "@cloudbase/node-sdk";
const app = tcb.init({ env: "your-env-id" });
const auth = app.auth();
exports.main = async (event, context) => {
const uid = "user-uid";
try {
const { userInfo } = await auth.getEndUserInfo(uid);
console.log("User profile", userInfo);
} catch (error) {
console.error("Failed to get end user info", error.message);
}
};
```
Best practices:
- Call `getEndUserInfo` from trusted backend code only; do not expose it directly to untrusted clients.
- Log minimal necessary data for debugging; avoid logging full profiles in production.
### Scenario 4: Get full profile for the current caller
Use this when you want the **current caller’s** full profile without manually passing `uid`:
```ts
import tcb from "@cloudbase/node-sdk";
const app = tcb.init({ env: "your-env-id" });
const auth = app.auth();
exports.main = async (event, context) => {
try {
const { userInfo } = await auth.getEndUserInfo();
console.log("Current caller profile", userInfo);
} catch (error) {
console.error("Failed to get current caller profile", error.message);
}
};
```
This relies on the environment providing the caller’s identity (e.g. within a CloudBase 云函数). If called where no caller context exists, refer to the official docs and handle errors gracefully.
### Scenario 5: Query user by login identifier
Use this when you only know a user’s login identifier (phone, email, username, or custom ID) and need their CloudBase profile:
```ts
import tcb from "@cloudbase/node-sdk";
const app = tcb.init({ env: "your-env-id" });
const auth = app.auth();
exports.main = async (event, context) => {
try {
// Find by phone number
const { userInfo: byPhone } = await auth.queryUserInfo({
platform: "PHONE",
platformId: "+86 13800000000",
});
// Find by email
const { userInfo: byEmail } = await auth.queryUserInfo({
platform: "EMAIL",
platformId: "test@example.com",
});
// Find by customUserId
const { userInfo: byCustomId } = await auth.queryUserInfo({
platform: "CUSTOM",
platformId: "your-customUserId",
});
console.log({ byPhone, byEmail, byCustomId });
} catch (error) {
console.error("Failed to query user info", error.message);
}
};
```
Best practices:
- Prefer `uid` when you already have it; use `queryUserInfo` only when needed.
- Make sure `platformId` uses the exact format you used at sign‑up (e.g. `+86` + phone number).
### Scenario 6: Get client IP in a function
Use this for logging or basic IP‑based checks:
```ts
import tcb from "@cloudbase/node-sdk";
const app = tcb.init({ env: "your-env-id" });
const auth = app.auth();
exports.main = async (event, context) => {
const ip = auth.getClientIP();
console.log("Caller IP", ip);
// e.g. block or flag suspicious IPs
};
```
---
## Custom login tickets (Node side only)
Custom login lets you keep your existing user system while still mapping each user to a CloudBase account.
### Scenario 7: Initialize Node SDK with custom login credentials
Before issuing tickets, install the custom login private key file from the CloudBase console and load it in Node:
```ts
import tcb from "@cloudbase/node-sdk";
import path from "node:path";
const app = tcb.init({
env: "your-env-id",
credentials: require(path.join(__dirname, "tcb_custom_login.json")),
});
const auth = app.auth();
```
Keep `tcb_custom_login.json` secret and **never** bundle it into frontend code.
### Scenario 8: Issue a custom login ticket for a given customUserId
Use this in backend code that has already authenticated your own user and wants to let them log into CloudBase:
```ts
import tcb from "@cloudbase/node-sdk";
const app = tcb.init({
env: "your-env-id",
credentials: require("/secure/path/to/tcb_custom_login.json"),
});
const auth = app.auth();
exports.main = async (event, context) => {
const customUserId = "your-customUserId";
const ticket = auth.createTicket(customUserId, {
refresh: 3600 * 1000, // access_token refresh interval (ms)
expire: 24 * 3600 * 1000, // ticket expiration time (ms)
});
// Return the ticket to the trusted client (e.g. via HTTP response)
return { ticket };
};
```
Constraints for `customUserId` (from official docs):
- Length 4–32 characters.
- Allowed characters: letters, digits, and `_-#@(){}[]:.,<>+#~`.
Best practices:
- Only issue tickets after your own user authentication succeeds.
- Store `customUserId` in your own user database and keep it stable over time.
- Do not reuse `customUserId` for multiple distinct people.
### Scenario 9: How this pairs with Web custom login
This skill only covers **Node-side** ticket issuance. For the **client-side** flow:
- On the client (Web), use `@cloudbase/js-sdk`'s custom login support:
- Call your backend endpoint that returns `ticket`.
- Configure `auth.setCustomSignFunc(async () => ticketFromBackend)`.
- Call `auth.signInWithCustomTicket()` to finish login.
Keep the responsibility clear:
- Node: authenticate your own user → create ticket → return ticket securely.
- Web: receive ticket → sign into CloudBase using documented Web SDK APIs.
---
## Node auth best practices
- **Single source of truth for identity**
- Treat CloudBase `uid` as the primary key when relating end‑user records.
- Use `customUserId` only as a bridge to your own user system.
- **Least privilege**
- Perform authorization checks in Node using `uid`, roles, and ownership, not just login success.
- Avoid exposing raw `getEndUserInfo` / `queryUserInfo` results directly to clients.
- **Error handling**
- Wrap all `auth.*` calls in `try/catch` when they return promises.
- Log `error.message` (and `error.code` if present), but avoid logging sensitive data.
- **Security**
- Protect `tcb_custom_login.json` as you would any private key.
- Rotate custom login keys according to CloudBase guidance when necessary.
- Use HTTPS and proper authentication between your clients and Node backend when exchanging tickets.
---
## Summary
Use this Node Auth skill whenever you need to:
- Know **who** is calling your Node code in CloudBase.
- Look up CloudBase users by `uid` or login identifier.
- Bridge an existing user system into CloudBase with **custom login tickets**.
- Apply consistent, secure, server‑side auth best practices.
For end‑to‑end experiences, pair this skill with:
- Web‑side auth documentation (for all browser‑side login and UX using `@cloudbase/js-sdk`).
- CloudBase HTTP auth documentation (for language‑agnostic HTTP integrations, if you are using those).
Treat the official CloudBase Auth Node SDK documentation as the canonical reference for Node auth APIs, and treat the scenarios in this file as vetted best‑practice building blocks.
references/auth-tool-cloudbase/checklist.md
# Authentication Activation Checklist
Use this checklist before generating any CloudBase authentication flow.
## When this checklist applies
- Web login or registration
- SMS, email, anonymous (disabled by default), Google, or WeChat provider setup
- HTTP API auth flows for native apps or backend integrations
## Required checks
1. Identify the client platform: Web, mini program, native app, or backend.
2. Confirm whether provider configuration must happen before code generation.
3. Check which login methods are required and enable them first.
4. For Web flows, get or confirm the publishable key before writing frontend auth code.
5. Route to the matching implementation skill after provider setup:
- Web -> `auth-web-cloudbase`
- Mini program -> `auth-wechat-miniprogram`
- Native app / raw HTTP -> `http-api-cloudbase`
6. Keep MCP tool routing explicit:
- management-side login -> `auth`
- application-side auth config -> `queryAppAuth` / `manageAppAuth`
## Common failure patterns
- Writing a login page before enabling SMS or email login.
- Implementing Web login in cloud functions instead of CloudBase Auth.
- Using Web SDK patterns in native App code.
## Done criteria
- Required providers are enabled.
- Platform-specific auth path is selected.
- The next skill to read is explicit before code generation starts.
references/auth-tool-cloudbase/references/extended-guide.md
# Extended guide — auth-tool-cloudbase
> Moved from SKILL.md to satisfy Agent Skills Spec 500-line limit.
## Authentication Scenarios
### 1. Get Login Config
Preferred MCP tool path: `queryAppAuth(action="getLoginConfig")`
Recommended MCP request:
```json
{
"action": "getLoginConfig"
}
```
`queryAppAuth` uses the currently selected environment and returns a short result by default:
```json
{
"success": true,
"envId": "your-full-env-id",
"loginMethods": {
"usernamePassword": true,
"email": true,
"anonymous": false,
"phone": false
}
}
```
Fallback API path: use the official login-config API. Do **not** use `lowcode/DescribeLoginStrategy` or `lowcode/ModifyLoginStrategy` as the default path.
Query current login configuration:
```js
{
"params": { "EnvId": `env` },
"service": "tcb",
"action": "DescribeLoginConfig"
}
```
The underlying login strategy contains fields such as:
- `AnonymousLogin`
- `UserNameLogin`
- `PhoneNumberLogin`
- `EmailLogin`
- `SmsVerificationConfig`
- `MfaConfig`
- `PwdUpdateStrategy`
Parameter mapping for downstream Web auth code:
- `queryAppAuth(action="getLoginConfig")` and `manageAppAuth(action="patchLoginStrategy")` return `sdkStyle: "supabase-like"` plus `sdkHints`; treat that as the preferred frontend-auth calling guide
- `PhoneNumberLogin` controls phone OTP flows used by `auth-web-cloudbase` `auth.signInWithOtp({ phone })` and `auth.signUp({ phone })`
- `EmailLogin` controls email OTP flows used by `auth-web-cloudbase` `auth.signInWithOtp({ email })` and `auth.signUp({ email })`
- `UserNameLogin` controls username/password Web login flows used by `auth-web-cloudbase` `auth.signInWithPassword({ username, password })`; direct username/password `signUp` support is SDK/provider dependent and must be verified before use
- If the account identifier is a plain username string, do not route it through email-only helpers such as `signInWithEmailAndPassword`
- `UserNameLogin` also enables the broader password-login surface exposed by `auth.signInWithPassword({ username|email|phone, password })`
- `SmsVerificationConfig.Type = "apis"` requires both `Name` and `Method`
- `EnvId` is always the CloudBase environment ID, not the publishable key
- If the conversation only contains an environment alias, nickname, or other shorthand, resolve it to the canonical full `EnvId` first before generating auth config, SDK init examples, or console links
Internal behavior of `manageAppAuth(action="patchLoginStrategy")`:
1. Read the currently selected environment
2. Query the current login strategy
3. Merge the short `patch` into the writable strategy fields
4. Update through Manager SDK
5. Query again and return a short `loginMethods` result
---
### 2. Anonymous Login
> ⚠️ **Anonymous login is disabled by default.** Publishable `accessKey` alone does **not** create a gateway-authenticated anonymous session. Only enable anonymous login when the application explicitly requires unauthenticated client access (e.g. NoSQL CRUD demos) and you accept the associated security trade-offs. After enabling, Web clients using `@cloudbase/js-sdk` **3.x** must still call `await auth.signInAnonymously()` (or an equivalent authenticated session) **before** NoSQL `app.database()` CRUD — otherwise the gateway returns **401**. `checkLogin()` / `getSession()` alone do **not** create a usable write session. Anonymous users are also denied AI model invocation permissions by default.
Preferred MCP tool path: `manageAppAuth(action="patchLoginStrategy")`
To explicitly enable anonymous login (only when required):
```json
{
"action": "patchLoginStrategy",
"patch": {
"anonymous": true
}
}
```
The tool handles read-merge-write internally. The model does not need to build a full `ModifyLoginConfig` payload.
**Important**: Even after enabling anonymous login, anonymous users cannot call AI models by default. This permission must be explicitly granted separately if needed.
---
### 3. Username/Password Login
Preferred MCP tool path: `manageAppAuth(action="patchLoginStrategy")`
Recommended MCP request:
```json
{
"action": "patchLoginStrategy",
"patch": {
"usernamePassword": true
}
}
```
The tool handles read-merge-write internally. The model does not need to build a full `ModifyLoginConfig` payload.
---
### 4. SMS Login
Preferred MCP tool path: `manageAppAuth(action="patchLoginStrategy")`
Use `patch.phone = true/false` for the login method itself.
**Default SMS channel is ready out of the box.** After `patch.phone = true`, the CloudBase default SMS channel sends and receives verification codes without any extra setup — no SMS signature, template, or custom provider configuration is required. Frontend flow: `auth.getVerification({ phone_number })` to send the code, then `auth.signInWithSms({ verificationInfo, verificationCode, phoneNum })` to sign in. Note: SMS login is only supported in the `ap-shanghai` region, and phone numbers must include a country code (e.g. `+86 13800000000`).
Only when you need custom SMS templates, custom signatures, or a different SMS vendor should you configure a custom SMS channel (or raw API fields such as `SmsVerificationConfig`). Do not block SMS login on provider/signature setup — the default channel already works.
Short MCP example:
```json
{
"action": "patchLoginStrategy",
"patch": {
"phone": true
}
}
```
---
### 5. Email Login
Email has two layers of configuration:
- `ModifyLoginConfig.EmailLogin`: controls whether email/password login is enabled
- `ModifyProvider(Id="email")`: controls the email sender channel and SMTP configuration
- In Web auth code, this maps to `auth.signInWithOtp({ email })` and `auth.signUp({ email })`
Preferred MCP tool path:
- `manageAppAuth(action="patchLoginStrategy")` for `EmailLogin`
- `manageAppAuth(action="updateProvider")` for provider settings
Short MCP example:
```json
{
"action": "patchLoginStrategy",
"patch": {
"email": true
}
}
```
**Configure email provider (Tencent Cloud email)**:
```js
{
"params": {
"EnvId": `env`,
"Id": "email",
"On": "TRUE",
"EmailConfig": { "On": "TRUE", "SmtpConfig": {} }
},
"service": "tcb",
"action": "ModifyProvider"
}
```
**Disable email provider**:
```js
{
"params": { "EnvId": `env`, "Id": "email", "On": "FALSE" },
"service": "tcb",
"action": "ModifyProvider"
}
```
**Configure email provider (custom SMTP)**:
```js
{
"params": {
"EnvId": `env`,
"Id": "email",
"On": "TRUE",
"EmailConfig": {
"On": "FALSE",
"SmtpConfig": {
"AccountPassword": "password",
"AccountUsername": "username",
"SecurityMode": "SSL",
"SenderAddress": "sender@example.com",
"ServerHost": "smtp.qq.com",
"ServerPort": 465
}
}
},
"service": "tcb",
"action": "ModifyProvider"
}
```
---
### 6. WeChat Login
Preferred MCP tool path:
- `queryAppAuth(action="listProviders")` or `queryAppAuth(action="getProvider")`
- `manageAppAuth(action="updateProvider")`
1. Get WeChat config:
```js
{
"params": { "EnvId": `env` },
"service": "tcb",
"action": "GetProviders"
}
```
Filter by `Id == "wx_open"`, save as `WeChatProvider`.
2. Get credentials from [WeChat Open Platform](https://open.weixin.qq.com/cgi-bin/readtemplate?t=regist/regist_tmpl):
- `AppID`
- `AppSecret`
3. Update:
```js
{
"params": {
"EnvId": `env`,
"Id": "wx_open",
"On": "TRUE", // "FALSE" to disable
"Config": {
...WeChatProvider.Config,
ClientId: `AppID`,
ClientSecret: `AppSecret`
}
},
"service": "tcb",
"action": "ModifyProvider"
}
```
---
### 7. Google Login
Preferred MCP tool path:
- `queryAppAuth(action="getStaticDomain")`
- `queryAppAuth(action="listProviders")` or `queryAppAuth(action="getProvider")`
- `manageAppAuth(action="updateProvider")`
1. Get redirect URI (static hosting CDN domain):
```js
{
"params": { "EnvId": `env` },
"service": "tcb",
"action": "DescribeStaticStore"
}
```
Prefer MCP: `queryAppAuth(action="getStaticDomain")` — use `cdnDomain` / `staticDomain` from the tool response (first store’s `CdnDomain`). Raw rows are in `staticStores`.
2. Configure at [Google Cloud Console](https://console.cloud.google.com/apis/credentials):
- Create OAuth 2.0 Client ID
- Set redirect URI: `https://{staticDomain}/__auth/`
- Get `Client ID` and `Client Secret`
3. Enable:
```js
{
"params": {
"EnvId": `env`,
"ProviderType": "OAUTH",
"Id": "google",
"On": "TRUE", // "FALSE" to disable
"Name": { "Message": "Google" },
"Description": { "Message": "" },
"Config": {
"ClientId": `Client ID`,
"ClientSecret": `Client Secret`,
"Scope": "email openid profile",
"AuthorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"TokenEndpoint": "https://oauth2.googleapis.com/token",
"UserinfoEndpoint": "https://www.googleapis.com/oauth2/v3/userinfo",
"TokenEndpointAuthMethod": "CLIENT_SECRET_BASIC",
"RequestParametersMap": {
"RegisterUserSyncScope": "syncEveryLogin",
"IsGoogle": "TRUE"
}
},
"Picture": "https://qcloudimg.tencent-cloud.cn/raw/f9131c00dcbcbccd5899a449d68da3ba.png",
"TransparentMode": "FALSE",
"ReuseUserId": "TRUE",
"AutoSignUpWithProviderUser": "TRUE"
},
"service": "tcb",
"action": "ModifyProvider"
}
```
### 8. Provider Lifecycle Boundary
Use provider lifecycle APIs when the identity source itself needs to be created, updated, or removed.
Preferred MCP tool path:
- `queryAppAuth(action="listProviders")`
- `queryAppAuth(action="getProvider")`
- `manageAppAuth(action="addProvider")`
- `manageAppAuth(action="updateProvider")`
- `manageAppAuth(action="deleteProvider")`
Guidance:
- Use `addProvider` when the provider record does not exist yet and you need to create it with `providerType`, optional `providerId`, `displayName`, and `config`.
- Use `updateProvider` when the provider already exists and only its configuration or enablement state needs to change.
- Use `deleteProvider` when the provider must be removed entirely instead of only disabling it.
### 9. Client Configuration Boundary
Use client APIs for client metadata and token/session settings. Do not use them as a replacement for login strategy or provider management.
Preferred MCP tool path:
- `queryAppAuth(action="getClientConfig")`
- `manageAppAuth(action="updateClientConfig")`
Both tools should default to the current selected environment's default client. Only pass `clientId` when you intentionally want to inspect or modify a non-default client record.
**Query client config**:
```js
{
"params": { "EnvId": `env`, "Id": `env` },
"service": "tcb",
"action": "DescribeClient"
}
```
**Update client config**:
```js
{
"params": {
"EnvId": `env`,
"Id": `env`,
"AccessTokenExpiresIn": 7200,
"RefreshTokenExpiresIn": 2592000,
"MaxDevice": 3
},
"service": "tcb",
"action": "ModifyClient"
}
```
### 10. Publishable Key and API Key Boundary
Preferred MCP tool path:
- `queryAppAuth(action="getPublishableKey")`
- `manageAppAuth(action="ensurePublishableKey")`
- `queryAppAuth(action="listApiKeys")`
- `manageAppAuth(action="createApiKey")`
- `manageAppAuth(action="deleteApiKey")`
Use the shortcut pair `getPublishableKey` / `ensurePublishableKey` for the most common frontend-readiness flow.
Use the generic API key lifecycle actions when you need inventory, pagination, non-publishable keys, or explicit deletion.
**Query existing publishable key**:
```js
{
"params": { "EnvId": `env`, "KeyType": "publish_key", "PageNumber": 1, "PageSize": 10 },
"service": "tcb",
"action": "DescribeApiKeyList"
}
```
`queryAppAuth(action="getPublishableKey")` should always force `KeyType="publish_key"` and return a short payload with `publishableKey`, `keyId`, `keyName`, `expireAt`, and `createdAt`.
**List API keys**:
```json
{
"action": "listApiKeys",
"keyType": "api_key",
"pageNumber": 1,
"pageSize": 20
}
```
Use `listApiKeys` for a general key inventory view. It supports optional `keyType`, `pageNumber`, and `pageSize`.
**Ensure publishable key exists**:
```js
{
"params": { "EnvId": `env`, "KeyType": "publish_key" },
"service": "tcb",
"action": "CreateApiKey"
}
```
`manageAppAuth(action="ensurePublishableKey")` should first query the existing `publish_key`; if one already exists, return it directly; otherwise create it and return the new key. This keeps the MCP interface short and avoids requiring the model to reason about `KeyType` or whether a key already exists.
**Create a generic API key**:
```json
{
"action": "createApiKey",
"keyType": "api_key",
"keyName": "server-prod",
"expireIn": 86400
}
```
`createApiKey` defaults to `publish_key` when `keyType` is omitted, but it can also create `api_key` for generic service-side access.
The response carries a `created` flag verified against the server-side key inventory:
- `created: true` — a genuinely new key was issued.
- `created: false` — the backend returned an already-existing key (this is what happens for `publish_key`, which is unique per environment). In that case `keyName` / `expireIn` had no effect and a `warnings` array explains what was ignored.
- `created` absent — the inventory read failed, so creation could not be verified.
`keyName`, `expireAt`, and `createdAt` in the response are always the server-stored values, never an echo of the request parameters. Never treat a `created: false` result as a short-lived credential: it is the environment's long-lived publishable key, and revoking it affects all normal traffic. To provision a temporary credential, use `keyType: "api_key"` and confirm `created: true`.
**Delete an API key**:
```json
{
"action": "deleteApiKey",
"keyId": "api-key-id"
}
```
Use `deleteApiKey` only when you intentionally want to revoke that key token.
If creation fails, direct user to: "https://tcb.cloud.tencent.com/dev?envId=`env`#/env/apikey"
### 11. Custom Login Keys
Preferred MCP tool path: `manageAppAuth(action="createCustomLoginKeys")`
Use custom login keys when the application needs CloudBase custom auth integration and the standard provider setup is not enough.
references/auth-tool-cloudbase/SKILL.md
---
name: auth-tool-cloudbase
description: CloudBase auth provider configuration and login-readiness guide. This skill should be used when users need to inspect, enable, disable, or configure auth providers, publishable-key prerequisites, login methods, SMS/email sender setup, or other provider-side readiness before implementing a client or backend auth flow.
version: 2.33.1
alwaysApply: false
---
## Sibling skills (local only)
Sibling CloudBase skills ship beside this skill. Use local relative paths such as `../auth-tool-cloudbase/SKILL.md`.
If a referenced sibling skill file is missing from this environment, ask the user to install the full CloudBase plugin (or the missing skill). Do **not** HTTP-fetch remote skill or protocol markdown into the agent context.
## Activation Contract
### Use this first when
- The task is to inspect, enable, disable, or configure CloudBase auth providers, login methods, publishable key prerequisites, SMS/email delivery, or third-party login readiness.
- An auth implementation cannot proceed until provider status and login configuration are confirmed.
- A CloudBase Web auth flow needs provider verification before `auth-web-cloudbase`.
### Read before writing code if
- The request mentions provider setup, auth console configuration, publishable key retrieval, login method availability, SMS/email sender setup, or third-party provider credentials.
- The task mixes provider configuration with Web, mini program, Node, or raw HTTP auth implementation.
### Then also read
- Web auth UI -> `../auth-web-cloudbase/SKILL.md`
- Mini program native auth -> `../auth-wechat-miniprogram/SKILL.md`
- Node server-side identity / custom ticket -> `../auth-nodejs-cloudbase/SKILL.md`
- Native App / raw HTTP auth client -> `../http-api-cloudbase/SKILL.md`
### Do NOT use this as
- The default implementation guide for every login or registration request.
- A replacement for mini program native auth behavior when no provider change is involved.
- A replacement for Node-side caller identity, user lookup, or custom login ticket flows.
- A replacement for frontend integration, session handling, or client UX implementation.
### Common mistakes / gotchas
- Writing login UI before enabling the required provider.
- Treating any mention of "auth" as a provider-management task.
- Implementing Web login in cloud functions.
- Routing native App auth to Web SDK flows.
- Making configuration or code changes without first following the Change Safety Protocol (`cloudbase-platform/references/protocols/change-safety-protocol.md`).
- In an existing application, looping on provider queries after readiness is already known instead of wiring the active login and register handlers.
### Minimal checklist
- Read [Authentication Activation Checklist](checklist.md) before auth implementation.
- Anonymous login is disabled by default. Publishable `accessKey` alone does **not** create a gateway-authenticated anonymous session. With `@cloudbase/js-sdk` **3.x**, enable anonymous via this skill when needed, then clients must call `await auth.signInAnonymously()` (or an equivalent authenticated session) **before** NoSQL `app.database()` CRUD — otherwise the gateway returns **401**. For apps that require verified login (e.g. admin panels), enforce AuthGuard / RLS and reject `is_anonymous` rather than relying on the login strategy toggle alone.
## Overview
Configure CloudBase authentication providers: Anonymous, Username/Password, SMS, Email, WeChat, Google, and more.
**Prerequisites**: CloudBase environment ID (`env`)
## MCP Tool Boundary
Keep these two auth domains separate:
- `auth`: MCP / management-side login only. Use it for `status`, `start_auth`, `set_env`, `logout`, and `get_temp_credentials`.
- `queryAppAuth` / `manageAppAuth`: app-side authentication configuration. Use them for login methods, provider settings, publishable key, static domain, client config, and custom login keys.
Preferred execution order for this skill:
1. Use `queryAppAuth` / `manageAppAuth` first when the needed action exists there.
2. Use `callCloudApi` only as a fallback or for debugging raw request shapes.
3. Do not route app-side provider configuration back to the MCP `auth` tool.
4. In existing projects with active login and register handlers, stop revisiting provider setup after the required login method and publishable key are confirmed. Move back to the active frontend handler and finish the actual user flow.
---
## Extended guide
For detailed scenarios, examples, and patterns, read [extended-guide.md](references/extended-guide.md).
## Reference index
All packaged reference files (required for skill lint reachability):
- [extended-guide.md](references/extended-guide.md)
references/auth-web-cloudbase/references/extended-guide.md
# Extended guide — auth-web-cloudbase
> Moved from SKILL.md to satisfy Agent Skills Spec 500-line limit.
## Login Methods
> ⚠️ **OTP verification must go through the `data.verifyOtp({ token })` callback returned by `signInWithOtp` / `signUp`.** Do NOT invent a standalone `auth.verifyOtp({ token })` call — the standalone form additionally requires `messageId` and fails with `"messageId is required"` when it is missing. The full flow is three steps: send code → keep the returned `data` (or its `verifyOtp` callback) → verify with `data.verifyOtp({ token })`.
**1. Phone OTP (Recommended)**
- Automatically use `auth-tool-cloudbase` to turn on `SMS Login` through `manageAppAuth`
- Send the phone number to `auth.signInWithOtp({ phone, ... })`, then call the returned `verifyOtp({ token })`.
- `signInWithOtp` can automatically create a new user if the user does not exist; control this via `shouldCreateUser` parameter (default `true`).
```js
const { data, error } = await auth.signInWithOtp({ phone: '13800138000' })
const { data: loginData, error: loginError } = await data.verifyOtp({ token: '123456' })
```
**2. Email OTP**
- Automatically use `auth-tool-cloudbase` to turn on `Email Login` through `manageAppAuth`
```js
const { data, error } = await auth.signInWithOtp({ email: 'user@example.com' })
const { data: loginData, error: loginError } = await data.verifyOtp({ token: '654321' })
```
**3. Password**
All auth methods return `{ data, error }`. Always check `error` first:
```js
// Login — returns { data: { user, session }, error: null } on success
// Supports optional is_encrypt for password encryption (default false)
const { data, error } = await auth.signInWithPassword({ username: 'test_user', password: 'pass123' })
if (error) {
// Handle login failure (wrong password, user not found, provider not enabled)
console.error('Login failed:', error.message)
return false
}
// data.user.id is the uid; data.session contains the active session
const uid = data.user.id
// Also works with email or phone:
// await auth.signInWithPassword({ email: 'user@example.com', password: 'pass123' })
// await auth.signInWithPassword({ phone: '13800138000', password: 'pass123' })
// Optional: encrypt password during transmission
// await auth.signInWithPassword({ email: 'user@example.com', password: 'pass123', is_encrypt: true })
```
**Checking login state (for route guards / auth checks):**
```js
// Use auth.getSession() — NOT the deprecated getLoginState().
//
// Why: getLoginState() may return an object with uid even when only accessKey is
// present (no real login), causing route guards to incorrectly treat the caller
// as authenticated. That misleading uid is NOT a gateway session for NoSQL CRUD.
// getSession() returns data.session === undefined when no real login exists,
// making the check reliable and simple.
const { data, error } = await auth.getSession()
if (!data?.session) {
// No real login — redirect to sign-in page
window.location.href = '/login'
return
}
// Also reject anonymous sessions (when signInAnonymously() was called explicitly)
if (data.session.user?.is_anonymous) {
// Anonymous user — not allowed for protected routes
window.location.href = '/login'
return
}
// data.session contains: access_token, refresh_token, expires_in, user
// data.session.user contains the authenticated user info
const currentUser = data.session.user
// Optional: further verify identity type from the session user if needed
const hasVerifiedIdentity = Boolean(
currentUser.phone_confirmed_at ||
currentUser.email_confirmed_at ||
currentUser.user_metadata?.username
)
// ❌ Do NOT use auth.getLoginState() — it's deprecated and returns
// misleading data (uid/loginState) even without real login
// ❌ Do NOT use auth.getUser(), !!loginState, or !!loginState.uid as auth checks
```
**4. Registration**
- For username-style account systems, verify whether direct username/password signup is supported before wiring the form
- Username/password login can use plain identifiers such as `admin` or `editor`, but raw signup APIs may enforce stricter username patterns or be disabled
- Do not switch to email OTP or phone OTP unless the task explicitly says the account identifier is an email address or phone number
- If direct username signup is unsupported, create users through a backend or management API boundary; never put secret keys in browser code
```js
// Username + Password login
const login = await auth.signInWithPassword({
username: 'editor',
password: 'editor123',
})
// Email Otp
// Use only when the task explicitly requires email addresses.
// Email Otp
const emailSignUp = await auth.signUp({ email: 'new@example.com', nickname: 'User' })
const emailVerifyResult = await emailSignUp.data.verifyOtp({ token: '123456' })
// Phone Otp
// Use only when the task explicitly requires phone numbers.
// Phone Otp
const phoneSignUp = await auth.signUp({ phone: '13800138000', password: 'pass123', nickname: 'User' })
const phoneVerifyResult = await phoneSignUp.data.verifyOtp({ token: '123456' })
```
When the project already has `handleSendCode` / `handleRegister` or similar UI handlers, wire the SDK calls there directly instead of leaving them commented out in `App.tsx`.
For username-style account tasks:
```tsx
const handleRegister = async () => {
const { error } = await auth.signUp({
username,
password,
nickname: username,
})
if (error) throw error
}
const handleLogin = async () => {
const { data, error } = await auth.signInWithPassword({
username,
password,
})
if (error) throw error
// Login succeeded — data.user.id is the uid
return true
}
```
Do not use email OTP or email-only helpers for these flows unless the task explicitly says the account identifier is an email address. The corresponding form field should stay `type="text"` rather than `type="email"` for username-style account identifiers.
```tsx
const handleSendCode = async () => {
try {
const { data, error } = await auth.signUp({
phone,
password: password || undefined,
})
if (error) throw error
verifyOtpRef.current = data.verifyOtp
} catch (error) {
console.error('Failed to send sign-up code', error)
}
}
const handleRegister = async () => {
try {
if (!verifyOtpRef.current) throw new Error('Please send the code first')
const { error } = await verifyOtpRef.current({ token: code })
if (error) throw error
} catch (error) {
console.error('Failed to complete sign-up', error)
}
}
```
**5. Anonymous**
> ⚠️ **Anonymous login is disabled by default for new environments.** Publishable `accessKey` alone does **not** create a gateway-authenticated anonymous session. With `@cloudbase/js-sdk` **3.x**, you **must** call `await auth.signInAnonymously()` (or an equivalent authenticated session) **before** NoSQL `app.database()` CRUD — otherwise the gateway returns **401**. `checkLogin()` / `getSession()` alone do **not** create a usable write session. For production protected apps, prefer verified login methods; use anonymous only when the demo/product explicitly needs unauthenticated client CRUD.
- Required for minimal NoSQL Web demos that skip real login (list/add guestbook, todo, etc.)
- Automatically use `auth-tool-cloudbase` to turn on `Anonymous Login` through `manageAppAuth` (must be explicitly enabled first)
```js
// Anonymous login is disabled by default — must be explicitly enabled via auth-tool
const { data, error } = await auth.signInAnonymously()
if (error) throw error
// Now NoSQL CRUD is allowed (js-sdk 3.x + publishable key)
// await app.database().collection('messages').get()
// Optional: pass provider_token to associate anonymous session with third-party identity
// const { data, error } = await auth.signInAnonymously({ provider_token: 'xxx' })
```
**6. OAuth (Google/WeChat/GitHub)**
- Automatically use `auth-tool-cloudbase` to turn on `Google Login` or `WeChat Login` through `manageAppAuth`
- Supported providers: `google`, `wechat`, `github`, `facebook`, `apple`
- By default, OAuth callback is auto-handled when `auth.detectSessionInUrl: true` is set in init
```js
const { data, error } = await auth.signInWithOAuth({ provider: 'google' })
if (error) throw error
// Auto-redirect to OAuth provider; after callback SDK auto-completes login
window.location.href = data.url
// For non-redirect flows (popup / custom UX), use skipBrowserRedirect + type:
// await auth.signInWithOAuth({
// provider: 'github',
// options: { skipBrowserRedirect: true, type: 'sign_in' }
// })
// Then use data.url manually (e.g. window.open(data.url))
// For binding additional identity to existing user: type: 'bind_identity'
```
**Manual OAuth callback handling:**
- If `detectSessionInUrl` is not set, call `verifyOAuth` manually after redirect:
```js
const urlParams = new URLSearchParams(window.location.search)
const { data, error } = await auth.verifyOAuth({
provider: 'google',
code: urlParams.get('code'),
state: urlParams.get('state'),
})
**7. Custom Ticket**
```js
await auth.signInWithCustomTicket(async () => {
const res = await fetch('/api/ticket')
return (await res.json()).ticket
})
```
**8. ID Token (Third-party token validation)**
```js
// Direct login with a third-party JWT/OAuth token (e.g. from native SDK)
const { data, error } = await auth.signInWithIdToken({
provider: 'wechat', // or 'google', 'github', etc.
token: '<jwt-or-oauth-token>',
})
```
**9. Upgrade Anonymous**
```js
const sessionResult = await auth.getSession()
const upgradeResult = await auth.signUp({
phone: '13800000000',
anonymous_token: sessionResult.data.session.access_token,
})
await upgradeResult.data.verifyOtp({ token: '123456' })
```
---
## User Management
```js
// Sign out
const signOutResult = await auth.signOut()
// Get user profile only after auth.getSession() has returned a real session
const sessionResult = await auth.getSession()
if (!sessionResult.data?.session) throw new Error('Not signed in')
const userResult = await auth.getUser()
console.log(
userResult.data.user.email,
userResult.data.user.phone,
userResult.data.user.user_metadata?.nickName,
)
// Update user (except email, phone)
const updateProfileResult = await auth.updateUser({
nickname: 'New Name',
gender: 'MALE',
avatar_url: 'url',
})
// Update user (email or phone)
const updateEmailResult = await auth.updateUser({ email: 'new@example.com' })
const verifyEmailResult = await updateEmailResult.data.verifyOtp({
email: 'new@example.com',
token: '123456',
})
// Change password (logged in)
const resetPasswordResult = await auth.resetPasswordForOld({
old_password: 'old',
new_password: 'new',
})
// Reset password (forgot)
const reauthResult = await auth.reauthenticate()
const forgotPasswordResult = await reauthResult.data.updateUser({
nonce: '123456',
password: 'new',
})
// Link third-party
const linkIdentityResult = await auth.linkIdentity({ provider: 'google' })
// View/Unlink identities
const identitiesResult = await auth.getUserIdentities()
const unlinkIdentityResult = await auth.unlinkIdentity({
provider: identitiesResult.data.identities[0].id,
})
// Delete account
const deleteResult = await auth.deleteUser({ password: 'current' })
// Listen to state changes
const authStateSubscription = auth.onAuthStateChange((event, session, info) => {
// INITIAL_SESSION, SIGNED_IN, SIGNED_OUT, TOKEN_REFRESHED, USER_UPDATED, PASSWORD_RECOVERY, BIND_IDENTITY
})
// Get access token
const sessionResult = await auth.getSession()
await fetch('/api/protected', {
headers: { Authorization: `Bearer ${sessionResult.data.session?.access_token}` },
})
// Refresh session (extend token validity)
const refreshResult = await auth.refreshSession() // uses current refresh_token
// or with explicit token: await auth.refreshSession(refresh_token)
// Set session manually (e.g. from external auth flow or SSR hydration)
const setResult = await auth.setSession({ refresh_token: '<token-from-server>' })
// Refresh user (sync latest user data from server)
const refreshUserResult = await auth.refreshUser()
```
---
## User Type
```ts
declare type User = {
id: any
aud: string
role: string[]
email: any
email_confirmed_at: string
phone: any
phone_confirmed_at: string
confirmed_at: string
last_sign_in_at: string
app_metadata: {
provider: any
providers: any[]
}
user_metadata: {
name: any
picture: any
username: any
gender: any
locale: any
uid: any
nickName: any
avatarUrl: any
location: any
hasPassword: any
}
identities: any
created_at: string
updated_at: string
is_anonymous: boolean
}
```
---
## Complete Example
```js
class PhoneLoginPage {
async sendCode() {
const phone = document.getElementById('phone').value
if (!/^1[3-9]\d{9}$/.test(phone)) return alert('Invalid phone')
const { data, error } = await auth.signInWithOtp({ phone })
if (error) return alert('Send failed: ' + error.message)
this.verifyOtp = data.verifyOtp
document.getElementById('codeSection').style.display = 'block'
this.startCountdown(60)
}
async verifyCode() {
const code = document.getElementById('code').value
if (!code) return alert('Enter code')
if (!this.verifyOtp) return alert('Send the code first')
const { data, error } = await this.verifyOtp({ token: code })
if (error) return alert('Verification failed: ' + error.message)
console.log('Login successful:', data.user)
window.location.href = '/dashboard'
}
startCountdown(seconds) {
let countdown = seconds
const btn = document.getElementById('resendBtn')
btn.disabled = true
const timer = setInterval(() => {
countdown--
btn.innerText = `Resend in ${countdown}s`
if (countdown <= 0) {
clearInterval(timer)
btn.disabled = false
btn.innerText = 'Resend'
}
}, 1000)
}
}
```
references/auth-web-cloudbase/SKILL.md
---
name: auth-web-cloudbase
description: CloudBase Web Authentication Quick Guide for frontend integration after auth-tool has already been checked. Provides concise and practical Web authentication solutions with multiple login methods and complete user management.
version: 2.33.1
alwaysApply: false
---
## Sibling skills (local only)
Sibling CloudBase skills ship beside this skill. Use local relative paths such as `../auth-tool-cloudbase/SKILL.md`.
If a referenced sibling skill file is missing from this environment, ask the user to install the full CloudBase plugin (or the missing skill). Do **not** HTTP-fetch remote skill or protocol markdown into the agent context.
## Activation Contract
### Use this first when
- The task is a CloudBase Web login, registration, session, or user profile flow built with `@cloudbase/js-sdk` and the auth provider setup has already been checked.
### Read before writing code if
- The user needs a login page, auth modal, session handling, or protected Web route. Read `auth-tool-cloudbase` first to ensure providers are enabled, then return here for frontend integration.
### Then also read
- `../auth-tool-cloudbase/SKILL.md` for provider setup
- `../web-development/SKILL.md` for Web project structure and deployment
### Do not start here first when
- The request is a Web auth flow but provider configuration has not been verified yet.
- In that case, activate `auth-tool-cloudbase` before `auth-web-cloudbase`.
### Do NOT use for
- Mini program auth, native App auth, or server-side auth setup.
### Common mistakes / gotchas
- Skipping publishable key and provider checks.
- Replacing built-in Web auth with cloud function login logic.
- Reusing this flow in Flutter, React Native, or native iOS/Android code.
- Creating a detached helper file with `auth.signUp` / `verifyOtp` but never wiring it into the existing form handlers, so the actual button clicks still do nothing.
- Using `signInWithEmailAndPassword` or `signUpWithEmailAndPassword` for username-style accounts such as `admin` and `editor`.
- Keeping the login or register account input as `type="email"` when the task explicitly says the account identifier is a plain username string.
- Starting implementation before calling `queryAppAuth(action="getLoginConfig")` and enabling `usernamePassword` when it is still off.
- **Writing `auth.signInWithPassword(...)` or `auth.signUp(...)` code without first confirming the provider is enabled via MCP.** Before writing any sign-in or sign-up code in the browser, call `queryAppAuth(action="listProviders")` to verify the target provider (e.g. `email`, `phone`, `usernamePassword`) has `On: "TRUE"`. For email-based sign-up (`auth.signUp({ email, password })`), additionally confirm SMTP is configured — otherwise the provider may throw `"provider email not found"` or similar errors. For username/password login, use `auth.signInWithPassword({ username, password })`; registration is best done through the management API (`manageAppAuth(action="createUser")`) or by confirming email provider readiness first.
- **Treating `auth.getUser()` or deprecated `auth.getLoginState()` as proof of real login.** When the SDK is initialized with `accessKey`, the deprecated `getLoginState()` may still return an object with a valid `uid` even without any login — causing route guards that check `!!loginState` or `!!uid` to incorrectly pass. That misleading `uid` is **not** a gateway-authenticated session. Use `auth.getSession()` instead: it returns `data.session === undefined` when no real login has occurred. Only `!!data.session` from `getSession()` is a reliable authentication check.
- **Assuming publishable `accessKey` alone is enough for NoSQL CRUD.** With `@cloudbase/js-sdk` **3.x**, call **`await auth.signInAnonymously()`** (or an equivalent authenticated session such as password/OTP/OAuth) **before** any NoSQL `app.database()` `get` / `add` / `update` / `watch`. Skipping this yields **gateway 401**. `checkLogin()` / `getSession()` alone do **not** create a usable write session.
- **Copying old CloudBase auth snippets from training data.** Do not use `auth.getLoginState()`, `auth.hasLoginState()`, `auth.getCurrentUser()`, or `auth.toDefaultLoginPage()` as the default Web flow. Use the Web SDK v3 auth methods in this file and provider readiness from `auth-tool-cloudbase`.
- **Calling a standalone `auth.verifyOtp({ token })` for OTP login.** CloudBase Web SDK v3 returns `verifyOtp` as a callback on the `signInWithOtp` / `signUp` result: send the code first, keep the returned `data`, then call `data.verifyOtp({ token })`. A standalone `auth.verifyOtp({ token })` without `messageId` fails with `"messageId is required"` — seeing that error means the callback form was skipped. See `references/extended-guide.md` for the full send → save callback → verify flow.
Note: anonymous login is **disabled by default** for new environments and inactive existing environments — enable it via `auth-tool-cloudbase` before calling `signInAnonymously()`. Always use `auth.getSession()` for auth guards.
## Overview
**Prerequisites**: CloudBase environment ID (`env`)
**Prerequisites**: CloudBase environment Region (`region`)
---
## Core Capabilities
**Use Case**: Web frontend projects using `@cloudbase/js-sdk@latest` for user authentication
**Key Benefits**: **Supabase-compatible Auth API** — all methods return `{ data, error }`, supports phone, email, anonymous (disabled by default), username/password, OAuth, and third-party login methods
> 📌 **Supabase API Compatibility**: CloudBase Web SDK v3 auth module is designed with Supabase-like API ergonomics. If you are familiar with `supabase-js` auth patterns, the same mental model applies:
> - All methods return `Promise<{ data, error }>` — always check `error` first
> - `signInWithPassword`, `signInWithOtp`, `signUp`, `signOut`, `getSession`, `getUser` follow the same naming as Supabase
> - `onAuthStateChange(callback)` provides reactive auth state observation (events: `INITIAL_SESSION`, `SIGNED_IN`, `SIGNED_OUT`, `TOKEN_REFRESHED`, `USER_UPDATED`, `PASSWORD_RECOVERY`, `BIND_IDENTITY`)
> - Session management via `getSession()` / `refreshSession()` / `setSession()` mirrors Supabase patterns
>
> **Key differences from Supabase**:
> - **OTP verification**: Supabase uses a standalone `auth.verifyOtp({ phone, token, type })` call; CloudBase returns `verifyOtp` as a callback on `data` — call `data.verifyOtp({ token })` from the `signInWithOtp` / `signUp` result
> - **`accessKey`** replaces Supabase's `anonKey`; environment uses `env` + `region` instead of Supabase's `url`
> - **`signInWithIdToken`** for direct third-party token login (similar to Supabase's same-named method)
Use npm installation for modern Web projects. In React, Vue, Vite, and other bundler-based apps, install and import `@cloudbase/js-sdk` from the project dependencies instead of using a CDN script.
## Prerequisites
- Automatically use `auth-tool-cloudbase` to check app-side auth readiness via `queryAppAuth` / `manageAppAuth`, then get the `publishable key` and configure login methods.
- If `auth-tool-cloudbase` failed, let user go to `https://tcb.cloud.tencent.com/dev?envId={env}#/env/apikey` to get `publishable key` and `https://tcb.cloud.tencent.com/dev?envId={env}#/identity/login-manage` to set up login methods
### Parameter map
- For username-style identifiers, the required precondition is `loginMethods.usernamePassword === true` from `queryAppAuth(action="getLoginConfig")`. If it is false, enable it with `manageAppAuth(action="patchLoginStrategy", patch={ usernamePassword: true })` before wiring frontend auth code.
- If the conversation only provides an environment alias, nickname, or other shorthand, resolve it with `envQuery(action="list", alias=..., aliasExact=true)` first and use the returned canonical full `EnvId` for SDK init, console links, and generated config. Do not pass alias-like short forms directly into `cloudbase.init({ env })`.
- Treat CloudBase Web Auth as **Supabase-like**, not “every `supabase-js` auth example is valid unchanged”
- When `queryAppAuth` / `manageAppAuth` returns `sdkStyle: "supabase-like"` and `sdkHints`, follow those method and parameter hints first
- `auth.signInWithOtp({ phone })` and `auth.signUp({ phone })` use the phone number in a `phone` field, not `phone_number`
- `auth.signInWithOtp({ email })` and `auth.signUp({ email })` use `email`
- `auth.signInWithPassword({ username, password })` is the canonical Web login path for username/password accounts
- Treat direct Web `auth.signUp({ username, password })` as conditional. Verify `sdkHints` and the installed SDK first; some versions only support `signUp` for OTP/provider-token flows and will not create username/password users.
- If the task gives accounts like `admin`, `editor`, or another plain string without `@`, treat it as a username-style identifier rather than an email address
- `data.verifyOtp({ token })` — the `verifyOtp` callback on the `signInWithOtp` / `signUp` result `data` — expects the SMS or email code in `token`; do not invent a standalone `auth.verifyOtp({ token })` call, which additionally requires `messageId`
- `accessKey` is the publishable key from `queryAppAuth` / `manageAppAuth` via `auth-tool-cloudbase`, not a secret key
- **`accessKey` alone does not create a gateway-authenticated anonymous session.** Publishable `accessKey` initializes the SDK; it does **not** replace an explicit login for NoSQL CRUD. With `@cloudbase/js-sdk` **3.x**, call `await auth.signInAnonymously()` (or an equivalent authenticated session) **before** `app.database()` `get` / `add` / `update` / `watch` — otherwise the gateway returns **401**. Separately: the deprecated `auth.getLoginState()` may still return a misleading `uid` without login; use `auth.getSession()` for route guards (`data.session === undefined` when not logged in). `checkLogin()` / `getSession()` alone do **not** create a usable write session.
- Never set `accessKey` to `envId`, a username, or any placeholder string. If you do not have a real Publishable Key yet, do not fabricate one.
- If the task mentions provider setup, stop and read `auth-tool-cloudbase` before writing frontend code
## Quick Start
```js
// npm install @cloudbase/js-sdk
import cloudbase from '@cloudbase/js-sdk'
const app = cloudbase.init({
env: 'your-full-env-id', // Canonical full CloudBase environment ID resolved from envQuery or the console, not an alias or shorthand
region: 'ap-shanghai', // CloudBase environment Region, default 'ap-shanghai'
accessKey: 'publishable key', // required, get from auth-tool-cloudbase
// ⚠️ accessKey alone ≠ anonymous login. For NoSQL CRUD call await auth.signInAnonymously()
// (or real login) first — otherwise gateway 401. Use auth.getSession() for route guards;
// deprecated getLoginState() may return a misleading uid without a real session.
auth: { detectSessionInUrl: true }, // required
})
const auth = app.auth
// Before NoSQL app.database() CRUD (js-sdk 3.x + publishable key):
// const { error } = await auth.signInAnonymously()
// if (error) throw error
```
If the current task has not retrieved a real Publishable Key, omit `accessKey` instead of inventing one. A wrong `accessKey` can break auth-state checks and protected-route behavior.
---
## Extended guide
For detailed scenarios, examples, and patterns, read [extended-guide.md](references/extended-guide.md).
## Reference index
All packaged reference files (required for skill lint reachability):
- [extended-guide.md](references/extended-guide.md)
references/auth-wechat-miniprogram/references/extended-guide.md
# Extended guide — auth-wechat-miniprogram
> Moved from SKILL.md to satisfy Agent Skills Spec 500-line limit.
## Scenarios – WeChat Mini Program auth patterns
### Scenario 1: Initialize CloudBase in Mini Program
Use this in your Mini Program's `app.js` or entry point:
```js
// app.js
App({
onLaunch: function () {
// Initialize CloudBase
wx.cloud.init({
env: 'your-env-id', // Your CloudBase environment ID
traceUser: true // Optional: track user access in console
})
}
})
```
**Key points:**
- Call `wx.cloud.init()` once when the Mini Program launches
- Set `env` to your CloudBase environment ID
- `traceUser: true` enables user access tracking in CloudBase console (optional but recommended)
---
### Scenario 2: Get user identity in a cloud function
Use this when you need to know **who is calling** your cloud function:
```js
// Cloud function: cloudfunctions/getUserInfo/index.js
const cloud = require('wx-server-sdk')
// Initialize cloud with dynamic environment
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV
})
exports.main = async (event, context) => {
// Get user identity - this is automatically injected by WeChat
const { OPENID, APPID, UNIONID } = cloud.getWXContext()
console.log('User identity:', { OPENID, APPID, UNIONID })
// Use OPENID for user-specific operations
// For example: query user data, check permissions, etc.
return {
openid: OPENID,
appid: APPID,
unionid: UNIONID // May be undefined if not available
}
}
```
**Key points:**
- Use `cloud.getWXContext()` to get user identity
- `OPENID` is always available and uniquely identifies the user
- `APPID` identifies the Mini Program
- `UNIONID` is only available when:
- The Mini Program is bound to a WeChat Open Platform account
- The user has authorized the Mini Program
- These values are **verified and trustworthy** - no need to validate them
- Use `cloud.DYNAMIC_CURRENT_ENV` to automatically use the current environment
**Best practices:**
- Store `OPENID` in your database to associate data with users
- Use `OPENID` for authorization and access control
- Use `UNIONID` when you need to identify users across multiple Mini Programs or Official Accounts
- Never expose `OPENID` to other users (it's a private identifier)
---
### Scenario 3: Call cloud function from Mini Program
Use this in your Mini Program to call a cloud function and get user identity:
```js
// In Mini Program page
Page({
onLoad: function() {
this.getUserInfo()
},
getUserInfo: function() {
wx.cloud.callFunction({
name: 'getUserInfo', // Cloud function name
data: {}, // Optional parameters
success: res => {
console.log('User info from cloud function:', res.result)
// res.result contains { openid, appid, unionid }
// Use the user info
this.setData({
openid: res.result.openid
})
},
fail: err => {
console.error('Failed to get user info:', err)
}
})
}
})
```
**Key points:**
- Use `wx.cloud.callFunction()` to call cloud functions
- User identity is automatically passed to the cloud function
- No need to manually send user credentials
- Handle both success and error cases
---
### Scenario 4: Test authentication - Simple test function
**Cloud function (cloudfunctions/test/index.js):**
```js
const cloud = require('wx-server-sdk')
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV
})
exports.main = async (event, context) => {
// Get verified user identity - automatically injected by WeChat
const { OPENID, APPID, UNIONID } = cloud.getWXContext()
console.log('User identity:', { OPENID, APPID, UNIONID })
return {
success: true,
message: 'Authentication successful',
identity: {
openid: OPENID,
appid: APPID,
unionid: UNIONID || 'Not available'
},
timestamp: new Date().toISOString()
}
}
```
**Mini Program code:**
```js
// pages/index/index.js
Page({
data: {
userIdentity: null
},
onLoad: function() {
this.testAuth()
},
testAuth: function() {
console.log('Testing authentication...')
wx.cloud.callFunction({
name: 'test',
success: res => {
console.log('Authentication test result:', res.result)
this.setData({
userIdentity: res.result.identity
})
wx.showToast({
title: 'Auth successful',
icon: 'success'
})
},
fail: err => {
console.error('Authentication test failed:', err)
wx.showToast({
title: 'Auth failed',
icon: 'error'
})
}
})
}
})
```
**Key points:**
- No explicit login API call needed
- User identity is automatically available in cloud function
- `OPENID` is always present and verified
- `UNIONID` may be undefined if not available
- Use this pattern to verify authentication is working correctly
---
## Best practices
### 1. Always use cloud.DYNAMIC_CURRENT_ENV
```js
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV
})
```
This ensures the cloud function uses the correct environment automatically.
### 2. Store OPENID for user identification
- Use `OPENID` as the primary user identifier
- Store it in your database to associate data with users
- Never expose `OPENID` to other users
### 3. Handle UNIONID availability
```js
const { OPENID, UNIONID } = cloud.getWXContext()
if (UNIONID) {
// User has UNIONID - can be used for cross-app identification
console.log('UNIONID available:', UNIONID)
} else {
// UNIONID not available - use OPENID only
console.log('Using OPENID only:', OPENID)
}
```
### 4. Use OPENID for user-specific operations
- Use `OPENID` to identify and authorize users
- Store `OPENID` when you need to associate data with users
- Use `OPENID` in queries to ensure users only access their own data
### 5. Error handling
Always handle errors when calling cloud functions:
```js
wx.cloud.callFunction({
name: 'myFunction',
success: res => {
// Handle success
},
fail: err => {
console.error('Cloud function error:', err)
// Show user-friendly error message
wx.showToast({
title: 'Operation failed',
icon: 'error'
})
}
})
```
### 6. Initialize CloudBase early
Initialize CloudBase in `app.js` `onLaunch`:
```js
App({
onLaunch: function () {
wx.cloud.init({
env: 'your-env-id',
traceUser: true
})
}
})
```
---
## Common patterns
### Pattern 1: Get and return user identity
```js
const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
exports.main = async (event, context) => {
const { OPENID, APPID, UNIONID } = cloud.getWXContext()
return {
openid: OPENID,
appid: APPID,
unionid: UNIONID || null
}
}
```
### Pattern 2: Use OPENID for authorization
```js
const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
exports.main = async (event, context) => {
const { OPENID } = cloud.getWXContext()
// Check if user is authorized
if (OPENID === event.resourceOwnerId) {
// User is authorized to access this resource
return { authorized: true }
} else {
return { authorized: false, error: 'Unauthorized' }
}
}
```
### Pattern 3: Handle UNIONID availability
```js
const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
exports.main = async (event, context) => {
const { OPENID, UNIONID } = cloud.getWXContext()
if (UNIONID) {
// Can use UNIONID for cross-app user identification
console.log('User has UNIONID:', UNIONID)
} else {
// Fall back to OPENID only
console.log('Using OPENID only:', OPENID)
}
return { openid: OPENID, hasUnionId: !!UNIONID }
}
```
---
## v3 Web SDK Mini Program methods
If the Mini Program uses `@cloudbase/js-sdk` (Web SDK v3) instead of `wx-server-sdk`, the following auth methods are available:
### signInWithOpenId
WeChat OpenID silent login — automatically uses the current WeChat login state:
```js
import cloudbase from "@cloudbase/js-sdk"
const app = cloudbase.init({
env: "your-env-id",
region: "ap-shanghai",
})
const auth = app.auth
// OpenID silent login (default: use wx.cloud mode)
const { data, error } = await auth.signInWithOpenId()
if (error) {
console.error('OpenID login failed:', error.message)
} else {
console.log('Logged in with OpenID:', data.user?.id)
}
// For non-wx.cloud mode, pass useWxCloud: false
// const { data, error } = await auth.signInWithOpenId({ useWxCloud: false })
```
### signInWithPhoneAuth
WeChat phone number authorization login — requires the user to authorize phone number via the Mini Program button:
```js
// Step 1: In Mini Program page, use <button open-type="getPhoneNumber">
// to get the encrypted phone code
// Step 2: Pass the phoneCode to signInWithPhoneAuth
const { data, error } = await auth.signInWithPhoneAuth({
phoneCode: '<encrypted-phone-code-from-wechat>',
})
if (error) {
console.error('Phone auth failed:', error.message)
} else {
console.log('Logged in with phone:', data.user)
}
```
**Important:**
- These methods are from `@cloudbase/js-sdk`, **not** `wx-server-sdk` or `wx.cloud`
- They provide an alternative auth path for Mini Programs using the v3 Web SDK
- For the standard `wx.cloud` + cloud function path, use the scenarios above instead
- `signInWithPhoneAuth` requires the user to tap a `<button open-type="getPhoneNumber">` in the Mini Program
---
## Summary
WeChat Mini Program authentication with CloudBase is **simple and secure**:
1. **No explicit login needed** - authentication is automatic
2. **User identity is verified** - `OPENID`, `APPID`, and `UNIONID` are trustworthy
3. **Easy to use** - just call `cloud.getWXContext()` in cloud functions
4. **Secure by default** - WeChat handles all authentication verification
**Key takeaways:**
- Initialize CloudBase with `wx.cloud.init()` in Mini Program
- Use `cloud.getWXContext()` to get user identity in cloud functions
- Use `OPENID` for user identification and authorization
- Handle `UNIONID` availability appropriately
- No explicit login API calls needed - authentication is automatic
For more complex authentication scenarios or integration with other systems, consider using CloudBase custom login in combination with WeChat authentication.
references/auth-wechat-miniprogram/SKILL.md
---
name: auth-wechat-miniprogram
description: CloudBase WeChat Mini Program native authentication guide. This skill should be used when users need mini program identity handling, OPENID/UNIONID access, or `wx.cloud` auth behavior in projects where login is native and automatic.
version: 2.33.1
alwaysApply: false
---
## Sibling skills (local only)
Sibling CloudBase skills ship beside this skill. Use local relative paths such as `../auth-tool-cloudbase/SKILL.md`.
If a referenced sibling skill file is missing from this environment, ask the user to install the full CloudBase plugin (or the missing skill). Do **not** HTTP-fetch remote skill or protocol markdown into the agent context.
## Activation Contract
### Use this first when
- The task is about WeChat Mini Program auth behavior, `wx.cloud` identity, `OPENID` / `UNIONID`, or how a mini program caller is identified in CloudBase.
- The project is a CloudBase mini program and the auth question is about native mini program identity rather than provider configuration.
### Read before writing code if
- The request mentions mini program login, user identity in cloud functions, or `wx.cloud` auth assumptions.
- The user expects a Web-style login page or explicit token exchange in a mini program; route them back to native mini program auth behavior.
### Then also read
- Mini program project implementation -> `../miniprogram-development/SKILL.md`
- Cloud function implementation -> `../cloud-functions/SKILL.md`
### Do NOT use for
- Web-based WeChat login or Web auth UI.
- Provider enable/disable or auth console setup.
- Generic Node-side auth flows outside mini program identity handling.
### Common mistakes / gotchas
- Generating a Web-style login page for a `wx.cloud` mini program.
- Treating mini program auth as a provider-configuration problem.
- Forgetting that caller identity is injected in cloud functions automatically.
## When to use this skill
Use this skill for **WeChat Mini Program (小程序) authentication** in a CloudBase project.
Use it when you need to:
- Implement identity-aware WeChat Mini Program flows with CloudBase
- Access user identity (openid, unionid) in cloud functions
- Understand how WeChat authentication integrates with CloudBase
- Build Mini Program features that require user identification
**Key advantage:** WeChat Mini Program authentication with CloudBase is **seamless and automatic** - no complex OAuth flows needed. When a Mini Program calls a cloud function, the user's `openid` is automatically injected and verified by WeChat.
**Do NOT use for:**
- Web-based WeChat login (use the **auth-web** skill)
- Server-side auth with Node SDK (use the **auth-nodejs** skill)
- Non-WeChat authentication methods (use appropriate auth skills)
---
## How to use this skill (for a coding agent)
1. **Confirm CloudBase environment**
- Ask the user for:
- `env` – CloudBase environment ID
- Confirm the Mini Program is linked to the CloudBase environment
2. **Understand the authentication flow**
- WeChat Mini Program authentication is **native and automatic**
- No explicit login API calls needed in most cases
- User identity is automatically available in cloud functions
- CloudBase handles all authentication verification
3. **Pick a scenario from this file**
- For basic user identity in cloud functions, use **Scenario 2**
- For Mini Program initialization, use **Scenario 1**
- For calling a cloud function from the Mini Program and receiving user identity, use **Scenario 3**
- For testing authentication, use **Scenario 4**
4. **Follow CloudBase API shapes exactly**
- Use `wx-server-sdk` in cloud functions
- Use `wx.cloud` in Mini Program client code
- Treat method names and parameter shapes in this file as canonical
5. **If you're unsure about an API**
- Consult the official CloudBase Mini Program documentation
- Only use methods that appear in official documentation
---
## Core concepts
### How WeChat Mini Program authentication works with CloudBase
1. **Automatic authentication:**
- When a Mini Program user calls a cloud function, WeChat automatically injects the user's identity
- No need for complex OAuth flows or token management
- CloudBase verifies the authenticity of the identity
2. **User identifiers:**
- `OPENID` – Unique identifier for the user in this specific Mini Program
- `APPID` – The Mini Program's App ID
- `UNIONID` – (Optional) Unique identifier across all apps under the same WeChat Open Platform account
- Only available when the Mini Program is bound to a WeChat Open Platform account
- Useful for identifying the same user across multiple Mini Programs or Official Accounts
3. **Security:**
- The `openid`, `appid`, and `unionid` are **verified and trustworthy**
- WeChat has already completed authentication
- Developers can directly use these identifiers without additional verification
4. **No explicit login required:**
- Users are automatically authenticated when they use the Mini Program
- No need to call login APIs in most cases
- Identity is available immediately in cloud functions
---
## Extended guide
For detailed scenarios, examples, and patterns, read [extended-guide.md](references/extended-guide.md).
## Reference index
All packaged reference files (required for skill lint reachability):
- [extended-guide.md](references/extended-guide.md)
references/cloud-functions/checklist.md
# Cloud Functions Execution Checklist
Use this checklist before creating or updating a CloudBase function.
## Required checks
1. Decide whether this is an Event Function or an HTTP Function.
- Event Function: `exports.main(event, context)`, SDK/timer driven
- HTTP Function: `req` / `res`, listens on port `9000`
2. Pick the runtime before creation and state it explicitly.
- For a managed runtime, choose a language runtime (e.g. `Nodejs18.15`).
- For a container-image HTTP Function, set `runtime: "CustomImage"` and provide `imageConfig` (`imageUri` with tag; `registryId` for enterprise TCR). The image still listens on port `9000`. See `references/http-functions-custom-image.md`.
3. For HTTP Functions on a managed runtime, confirm `scf_bootstrap` exists and the Node.js binary path matches the runtime (e.g. `Nodejs18.15` → `/var/lang/node18/bin/node`). Custom Image functions do not use `scf_bootstrap`.
4. If an HTTP Function calls CloudBase resources through `@cloudbase/node-sdk` or `@cloudbase/manager-node`, complete `references/http-function-credentials.md`. Do not rely on the default temporary credential path:
- Node SDK: inject `CLOUDBASE_APIKEY`, preferably after creating a server key with `manageAppAuth(action="createApiKey", keyType="api_key")`, or inject a Tencent Cloud key pair.
- Manager SDK: inject a Tencent Cloud `SecretId` / `SecretKey` pair.
- Never commit credentials, return them to clients, or overwrite unrelated function environment variables.
5. Confirm no response path echoes `x-cloudbase-context`, full `req.headers` / `event` / `context`, or `process.env`. Follow `../cloudbase-platform/references/protocols/sensitive-runtime-data-protection.md`.
6. Confirm the function root path points to the parent directory, not the function directory itself. (Not needed for Custom Image deploys — the code lives in the image.)
7. For Custom Image deploys, confirm TCR, the CloudApp build, and SCF are in the same region, and the image tag is unique (not `:latest`). Remember Stage A (CloudApp custom build → TCR push) is a raw Tencent Cloud API path, not covered by MCP tools.
8. For HTTP Functions that need public access, configure the function security rule with `managePermissions(action="updateResourcePermission", resourceType="function")` after creation. Default rules reject unauthenticated callers with `EXCEED_AUTHORITY`. Note: anonymous login is disabled by default — use `rule: "true"` for public endpoints.
9. If creating or mutating a function **layer**, follow the account-scoped naming contract (same as MCP tool descriptions):
- New layer names must use `{layerName}_{当前envId}` (e.g. `common_cloud1-d9ghadgak3edf6b36`). Pass that full string as `layerName`; do not auto-append suffixes in client code.
- Before `createLayerVersion`, call `queryFunctions(action="listLayers")` to avoid colliding with another env's bare name.
- Treat envelope `warnings` as soft advisories (success still means the call ran). Deleting a layer version or rebinding layers can impact every env that shares that LayerName.
- Details: `references/operations-and-config.md`.
10. If the request is really for a long-running container service, reroute to `cloudrun-development`.
## Common failure patterns
- Choosing the wrong function type and compensating later.
- Mixing Event Function and HTTP Function handler shapes in the same implementation.
- Forgetting that runtime cannot be changed after creation.
- Mismatching the `scf_bootstrap` Node.js binary path with the function runtime.
- Relying on passwordless/default temporary credentials when an HTTP Function calls a CloudBase SDK.
- Returning request headers, environment dumps, or `x-cloudbase-context` from debug/hello endpoints.
- For Custom Image functions: using `:latest`, mismatched regions across TCR/CloudApp/SCF, or assuming MCP covers the CloudApp build → TCR push stage (it does not).
- Forgetting to configure function security rules for HTTP Functions that need public access.
- Treating Cloud Functions as the default answer for Web authentication.
- Creating layers with a bare name (e.g. `common`) so multiple envs share one version sequence, or ignoring MCP layer `warnings` about account-scoped sharing.
## Done criteria
- Function type and runtime are explicit.
- Packaging constraints are checked.
- HTTP Function SDK credentials are explicit and a real SDK operation was verified after deployment.
- No response echoes `x-cloudbase-context`, full headers, or credential env vars.
- If layers were created, names follow `{layerName}_{当前envId}` and duplicate checks were done via `listLayers`.
- The task is confirmed to be a function workflow rather than CloudRun.
references/cloud-functions/references.md
# Cloud Functions Reference Map
Use this file to decide which detailed reference to read after the main skill.
## Read this next when
- You already know the task belongs to Cloud Functions, but the main `SKILL.md` is intentionally keeping only the routing and guardrails.
## Reference routing
### `./references/event-functions.md`
Read this when the task is about:
- `exports.main(event, context)`
- SDK-invoked serverless functions
- timer-triggered jobs
- Event Function deployment or invocation patterns
### `./references/http-functions.md`
Read this when the task is about:
- HTTP endpoints
- REST APIs
- SSE or WebSocket services
- `scf_bootstrap`
- browser/public access paths for HTTP Functions
### `./references/http-function-credentials.md`
Read this whenever a managed-runtime or Custom Image HTTP Function calls CloudBase resources through:
- `@cloudbase/node-sdk`
- `@cloudbase/manager-node`
- `CLOUDBASE_APIKEY`
- Tencent Cloud `SecretId` / `SecretKey`
HTTP Functions must use an explicit credential path. The Event Function passwordless runtime path does not apply reliably to HTTP Functions.
### `./references/http-functions-custom-image.md`
Read this when the task is about:
- deploying an HTTP Function from a **container image** (`Runtime: CustomImage`)
- `imageConfig` / `ImageUri` / TCR image addresses
- the zip → COS → CloudApp custom build → TCR → SCF image pipeline
- choosing between a managed-runtime HTTP Function, a Custom Image HTTP Function, and a CloudRun container
### `./references/operations-and-config.md`
Read this when the task is about:
- function logs
- timeout / environment-variable updates
- timer cron format
- VPC field shape only (examples) — for TCP DB policy see `vpc-and-tcp-database.md` (exception-only)
- gateway exposure for Event Functions
- SCF layers: account-scoped naming `{layerName}_{当前envId}`, soft `warnings`, list/create/bind/delete actions
- legacy tool-name translation
- `callCloudApi` fallback for Cloud Functions
### `./references/vpc-and-tcp-database.md` (exception-only — do not read by default)
Read this **only** when the task is migrating an **existing** app that already uses classic TCP clients:
- existing `DATABASE_URL` / Prisma / TypeORM / Sequelize / `mysql2` / `pg` / Redis TCP clients
- private MySQL / PostgreSQL / Redis connectivity from Event or HTTP Functions that cannot use native SDK
**Do NOT read this for new business CRUD.** Prefer CloudBase native SDK (`app.rdb()` / `app.database()`) or MCP SQL tools. New apps must not introduce TCP DB clients, ask users for DB passwords, or treat VPC binding as the default path.
When this exception applies:
- `vpc.vpcId` / `vpc.subnetId` is mandatory for private TCP access
- **never guess** VPC IDs
## Keep these distinctions straight
- Event Function code shape: `exports.main(event, context)`
- HTTP Function code shape: `req` / `res` web server on port `9000`
- Event Functions can use the platform-provided runtime credential path; HTTP Functions that call CloudBase SDKs must follow `http-function-credentials.md`
- HTTP Access for Event Functions is a gateway configuration, not the HTTP Function runtime model
- CloudRun is the right route when the task is actually a long-lived service or broader container workload
- Custom Image HTTP Function (`Runtime: CustomImage`) still listens on the fixed port `9000` and is request-driven — distinct from a CloudRun container, which listens on the injected `PORT` and runs long-lived
references/cloud-functions/references/event-functions.md
# Event Functions Reference
Use this reference when the task is clearly about an Event Function (`exports.main(event, context)`) rather than an HTTP Function.
## Runtime and packaging facts
- Runtime is fixed at creation time and cannot be changed later.
- For new functions, prefer `Nodejs18.15` unless dependency compatibility forces an older runtime.
- Event Functions auto-install dependencies from `package.json` during deployment, so you normally do not ship `node_modules`.
- The function root path must point to the parent directory that contains the function folder.
## Minimal structure
```text
cloudfunctions/
└── myFunction/
├── index.js
└── package.json
```
```javascript
exports.main = async (event, context) => {
return {
code: 0,
message: "ok",
data: { event }
};
};
```
## Create or update flow
### Create
Use `manageFunctions(action="createFunction")` and make the function type explicit.
```javascript
manageFunctions({
action: "createFunction",
func: {
name: "myFunction",
type: "Event",
runtime: "Nodejs18.15",
timeout: 30
},
functionRootPath: "/absolute/path/to/cloudfunctions"
});
```
### Update code
Use `manageFunctions(action="updateFunctionCode")` when only code changes.
```javascript
manageFunctions({
action: "updateFunctionCode",
functionName: "myFunction",
functionRootPath: "/absolute/path/to/cloudfunctions"
});
```
### Key reminders
- `updateFunctionCode` does not change runtime.
- If runtime must change, recreate the function.
- Prefer MCP management tools when available; if MCP tools are missing in this session, use `tcb fn deploy` via `cloudbase-cli` (see guideline `tooling-fallback.md`).
## Invocation patterns
### Web
```javascript
import cloudbase from "@cloudbase/js-sdk";
const app = cloudbase.init({ env: "your-env-id" });
const result = await app.callFunction({
name: "myFunction",
data: { userId: "123" }
});
```
### Mini Program
```javascript
const result = await wx.cloud.callFunction({
name: "myFunction",
data: { userId: "123" }
});
```
### Node.js backend
```javascript
const tcb = require("@cloudbase/node-sdk");
const app = tcb.init({ env: "your-env-id" });
const result = await app.callFunction({
name: "myFunction",
data: { userId: "123" }
});
```
### Raw HTTP API
Use the CloudBase HTTP API only when the task is explicitly about raw API invocation.
```text
https://{envId}.api.tcloudbasegateway.com/v1/functions/{functionName}
```
This path requires authentication and belongs with the `http-api-cloudbase` skill, not browser-facing anonymous access.
## Common patterns
### Error handling
```javascript
exports.main = async (event, context) => {
try {
const result = await doWork(event);
return {
code: 0,
message: "Success",
data: result
};
} catch (error) {
return {
code: -1,
message: error.message,
data: null
};
}
};
```
### Environment variables
```javascript
exports.main = async () => {
const apiKey = process.env.API_KEY;
const envId = process.env.ENV_ID;
return { apiKeyExists: Boolean(apiKey), envId };
};
```
## When to stop and reroute
- If the user wants a long-lived HTTP service, SSE, or WebSocket server, reroute to HTTP Functions or CloudRun.
- If the user wants browser SDK auth or UI login, reroute to the relevant auth skill.
- If the user wants MySQL or document database schema design, reroute to the data skills instead of forcing it into a function tutorial.
references/cloud-functions/references/http-function-credentials.md
# HTTP Function Credentials for CloudBase SDKs
Use this reference whenever an HTTP Function (managed runtime or Custom Image) calls CloudBase resources through `@cloudbase/node-sdk` or `@cloudbase/manager-node`.
## Credential boundary
> **STOP:** Do not assume that an HTTP Function can use CloudBase SDKs without explicit credentials.
Event Functions can use the platform-provided runtime credential path. HTTP Functions must not depend on that default temporary credential injection: credential rotation can leave the process with invalid credentials and cause intermittent authorization failures.
Before deploying an HTTP Function that calls CloudBase SDKs:
1. Identify which SDK the function uses.
2. Select one supported explicit credential path below.
3. Store credentials in function environment variables, never in source code.
4. Read the existing function configuration, merge the credential variables, and update it with `manageFunctions(action="updateFunctionConfig")`.
5. Verify one real SDK operation after deployment.
## `@cloudbase/node-sdk`
Choose one of these server-side paths:
### Preferred: CloudBase server API Key
First inspect existing server keys:
```javascript
queryAppAuth({
action: "listApiKeys",
keyType: "api_key",
pageNumber: 1,
pageSize: 20
});
```
If no suitable dedicated key exists, create one through MCP:
```javascript
manageAppAuth({
action: "createApiKey",
keyType: "api_key",
keyName: "http-function-my-service",
expireIn: 0
});
```
Inject the returned key into the HTTP Function as `CLOUDBASE_APIKEY`. Do not print it, commit it, return it to a client, or put it in browser code.
Use a dedicated key name for each service and define a rotation/revocation owner. `expireIn: 0` avoids automatic expiry but creates a long-lived secret, so use it only when the deployment's secret-rotation process is explicit.
The Node SDK reads this variable automatically:
```javascript
const tcb = require("@cloudbase/node-sdk");
const app = tcb.init({
env: process.env.TCB_ENV
});
```
The equivalent explicit initialization field is `accessKey`, but the environment-variable path avoids embedding the key in source:
```javascript
const app = tcb.init({
env: process.env.TCB_ENV,
accessKey: process.env.CLOUDBASE_APIKEY
});
```
### Alternative: Tencent Cloud permanent key pair
Inject both:
- `TENCENTCLOUD_SECRETID`
- `TENCENTCLOUD_SECRETKEY`
Then initialize the Node SDK with the environment ID. The SDK reads the key pair from those environment variables:
```javascript
const tcb = require("@cloudbase/node-sdk");
const app = tcb.init({
env: process.env.TCB_ENV
});
```
Do not inject only one member of the pair.
Use a dedicated CAM sub-account with the minimum permissions required by the function instead of a root-account key.
## `@cloudbase/manager-node`
The Manager SDK documentation supports Tencent Cloud credentials, not the CloudBase server API Key used by `@cloudbase/node-sdk`.
Inject:
- `TENCENTCLOUD_SECRETID`
- `TENCENTCLOUD_SECRETKEY`
Pass them explicitly during initialization:
```javascript
const CloudBase = require("@cloudbase/manager-node");
const app = CloudBase.init({
envId: process.env.TCB_ENV,
secretId: process.env.TENCENTCLOUD_SECRETID,
secretKey: process.env.TENCENTCLOUD_SECRETKEY
});
```
Do not tell users to create a CloudBase API Key for Manager SDK initialization unless the Manager SDK public contract adds that capability.
Use a dedicated CAM sub-account with the minimum management permissions required by the function.
## Safe environment update
Never replace all existing environment variables with only the new credentials.
1. Call `queryFunctions(action="getFunctionDetail", functionName="...")`.
2. Merge the existing variables with the new credential variables.
3. Call `manageFunctions(action="updateFunctionConfig", ...)` with the merged object.
Do not expose credential values in logs, summaries, generated examples, or function responses. If a key leaks, revoke it immediately; for a CloudBase API Key use `manageAppAuth(action="deleteApiKey", keyId="...")`.
## Verification
After deployment:
- Invoke an endpoint that performs a harmless CloudBase SDK read.
- Confirm it succeeds more than once rather than checking only process startup.
- Inspect function logs for authentication or expired-token failures.
- Confirm no credential value appears in logs or responses.
## Official references
- Node SDK initialization: `https://docs.cloudbase.net/api-reference/server/node-sdk/initialization`
- Manager SDK initialization: `https://docs.cloudbase.net/api-reference/manager/node/introduction`
references/cloud-functions/references/http-functions-custom-image.md
# HTTP Functions — Custom Image Deployment Reference
Use this reference when an HTTP Function must run from a **container image** instead of the managed Node.js/Python runtime. This is the `Runtime: CustomImage` path: the code is packaged as a Docker image, pushed to TCR (Tencent Container Registry), and SCF runs that image.
## When to choose Custom Image (vs the other two HTTP options)
| Deployment form | Choose when | How it deploys |
| --- | --- | --- |
| **Managed runtime** (default, see `http-functions.md`) | Plain Node.js / Python, dependencies are simple | `manageFunctions(createFunction)` + `scf_bootstrap` + zip |
| **Custom Image** (this file) | Need custom system libraries / arbitrary runtime, but still want SCF request-driven execution and scale-to-zero | CloudApp custom build → TCR → SCF image function |
| **CloudRun container** (see `cloudrun-development`) | Long-lived process, persistent connections, listens on injected `PORT` | `manageCloudRun` |
Keep Custom Image HTTP Functions distinct from CloudRun containers — both use a Dockerfile, but:
- **Custom Image HTTP Function**: container listens on a **fixed port `9000`**, request-driven, scales to zero. SCF gateway sends each HTTP request into the container.
- **CloudRun container**: container listens on the **injected `PORT`** env var, long-lived process.
Do not blend the two contracts.
## CloudBase SDK credential gate
Custom Image changes packaging, not the HTTP Function credential boundary. If the image calls CloudBase resources through `@cloudbase/node-sdk` or `@cloudbase/manager-node`, read `./http-function-credentials.md` before deployment.
- Do not rely on the Event Function passwordless/default temporary credential path.
- For `@cloudbase/node-sdk`, inject `CLOUDBASE_APIKEY` from a server API Key created through `manageAppAuth(action="createApiKey", keyType="api_key")`, or inject a Tencent Cloud key pair.
- For `@cloudbase/manager-node`, inject a Tencent Cloud `SecretId` / `SecretKey` pair.
- Keep credentials out of the Dockerfile, image layers, build arguments, source code, logs, and responses.
## End-to-end pipeline (6 steps)
The link between the two stages is the TCR image address:
```text
ImageUri = {TCR_REGISTRY}/{TCR_NAMESPACE}/{ServiceName}:{VersionName}
example: ccr.ccs.tencentyun.com/your-ns/demo-app:demo-app-001
```
```text
Stage A — build the image (CloudApp custom build pipeline)
① DescribeCloudAppCosInfo -> get COS upload credentials + UnixTimestamp
② PUT zip to COS -> upload source
③ CreateCloudApp -> trigger docker build + docker push to TCR
④ DescribeCloudAppVersion -> poll until Status=SUCCESS, read VersionName
Stage B — deploy to SCF (based on the TCR image)
⑤ createFunction / updateFunctionCode -> SCF image function
⑥ getFunctionDetail (optional) -> confirm Status=Active
```
## Tooling boundary (read this before acting)
- **Stage B (SCF image deploy) is covered by `manageFunctions`.** Use `manageFunctions(action="createFunction")` with `func.runtime="CustomImage"` + `imageConfig`, and `manageFunctions(action="updateFunctionCode")` + `imageConfig` for later iterations. The Manager SDK auto-fills `ImageType=enterprise` and `ImagePort=9000`, and strips Handler / dependency install for image functions.
- **Stage A (CloudApp custom build → TCR) is NOT covered by MCP tools.** The `manageApps` / `queryApps` tools only support `static-hosting`. The custom-build pipeline (`DeployType=custom`, `CustomSteps`, `DescribeCloudAppCosInfo` with `DeployType=custom`) is a **raw Tencent Cloud API path**. Treat it as a `callCloudApi` fallback:
- Confirm the exact action name, parameters, and `X-TC-Version` from official docs **before** calling — do not guess payloads from memory.
- CloudApp build APIs are on `tcb.tencentcloudapi.com` (`X-TC-Version: 2018-06-08`).
- SCF APIs are on `scf.tencentcloudapi.com` (`X-TC-Version: 2018-04-16`).
- **Region must match.** TCR, the CloudApp build, and SCF must be in the same region (e.g. all `ap-shanghai`). Cross-region image pulls time out.
## Stage B with `manageFunctions` (the supported path)
### Create (first deploy)
```javascript
manageFunctions({
action: "createFunction",
func: {
name: "my-scf-func",
type: "HTTP",
runtime: "CustomImage"
},
imageConfig: {
imageType: "enterprise",
imageUri: "ccr.ccs.tencentyun.com/your-ns/demo-app:demo-app-001",
registryId: "tcr-xxxxxxxx",
command: "python",
args: "-u app.py",
imagePort: 9000,
containerImageAccelerate: true
}
});
```
### Update image (later iterations)
Only the tag changes; no local code packaging.
```javascript
manageFunctions({
action: "updateFunctionCode",
functionName: "my-scf-func",
imageConfig: {
imageType: "enterprise",
imageUri: "ccr.ccs.tencentyun.com/your-ns/demo-app:demo-app-002",
registryId: "tcr-xxxxxxxx"
}
});
```
### `imageConfig` fields
| Field | Required | Notes |
| --- | --- | --- |
| `imageUri` | yes | Full address **with tag**: `{domain}/{ns}/{image}:{tag}`. Never `:latest`. |
| `imageType` | — | `"enterprise"` (TCR enterprise) or `"personal"`. Defaults to `enterprise`. |
| `registryId` | enterprise only | TCR instance id `tcr-xxxxxxxx`. Required when `imageType=enterprise`. |
| `command` | — | Overrides `ENTRYPOINT`. Omit to use the Dockerfile default. |
| `args` | — | Overrides `CMD`, space-separated. |
| `imagePort` | — | Web Server: `9000` (default). Job-style image: `-1`. |
| `containerImageAccelerate` | — | Image acceleration; enable for large images to cut cold-start time. |
After deploy, confirm readiness with `queryFunctions(action="getFunctionDetail", functionName="my-scf-func")` and look for `Status=Active`. If the tool result already contains `accessUrl` / `accessUrls`, prefer those links directly. If no URL is returned yet, create a Domain/Route explicitly with `manageGateway(action="createRoute", upstreamResourceType="WEB_SCF")` and set the function security rule (anonymous login is disabled by default — see `http-functions.md`).
## Source packaging (Stage A input)
Package the **contents** of the project root, with a `Dockerfile` at the root — do not nest an extra top-level folder.
```bash
# correct: zip the contents from inside the project root
cd ./my-app
zip -r ../my-app.zip .
# wrong: this nests an extra my-app/ layer after extraction
zip -r my-app.zip my-app/
```
After extraction the build container sees:
```text
/ (workspace root)
├── Dockerfile <- must be at the root
├── package.json / requirements.txt / pom.xml ...
├── src/
└── ...
```
## Dockerfile contract for SCF image functions
```dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY . .
RUN pip install --no-cache-dir -r requirements.txt
EXPOSE 9000 # SCF Web Server functions must listen on 9000
CMD ["python", "-u", "app.py"]
```
SCF image constraints:
| Constraint | Detail |
| --- | --- |
| Web Server functions listen on `9000` | The SCF gateway sends HTTP requests into the container on this port. |
| The image must start an HTTP server on its own | Not a CLI tool, not a blocking script. |
| Image size ideally ≤ 500MB | Large images cold-start slowly; enable `containerImageAccelerate`. |
| Container start ≤ health-check timeout | Default 60s. |
## TCR credentials (Stage A push, raw API)
Use STS temporary credentials + TCR `CreateInstanceToken` to obtain a short-lived registry login, instead of storing any long-term TCR password:
- The build container injects STS credentials as `$API_SECRET_ID` / `$API_SECRET_KEY` / `$API_TOKEN`.
- In the build step, sign a TCR `CreateInstanceToken` call (`tcr.tencentcloudapi.com`, `X-TC-Version: 2019-09-24`) with those credentials to get a temporary token.
- `docker login` with that token (always via `--password-stdin`), then `docker push`.
Security red lines:
- Never print `$API_SECRET_*` / `$API_TOKEN` or pass them outside the container.
- Always use `docker login --password-stdin` so credentials never appear in `ps` or logs.
## Build-container variables (Stage A, custom build)
Use these inside `CustomSteps` commands:
| Variable | Meaning |
| --- | --- |
| `$CLOUDBASE_SERVICE_NAME` | Service name (= the `ServiceName` input) |
| `$CLOUDBASE_VERSION_NAME` | Version name — **use this as the image tag**; available at build time, unique, traceable |
| `$CLOUDBASE_VERSION_NUMBER` | Numeric version (e.g. `001`) |
| `$CLOUDBASE_ENV_ID` | Environment id |
| `$BUILD_TYPE` | `zip` / `git` |
| `$ZIP_FILE_URL` | Zip download URL (injected automatically in zip mode) |
| `$API_SECRET_ID` `$API_SECRET_KEY` `$API_TOKEN` | STS credentials (never print) |
- Reserved prefixes that must NOT be declared in `Env`: `API_*`, `CLOUDBASE_*`, `CODE_*`, `BUILD_TYPE`, `ZIP_FILE_URL`.
- Do NOT reference non-existent variables such as `$BUILD_ID`, `$CLOUDBASE_BUILD_ID`, `$CLOUDBASE_VERSION` — use `$CLOUDBASE_VERSION_NAME` for the tag.
## Common errors
### Stage A (build)
| Symptom | Likely cause |
| --- | --- |
| `Source 不能为空` | `Source.Type` empty; zip flow must set `"zip"` |
| `Commands 和 CustomSteps 不能同时为空` | Provide at least one `CustomSteps` entry |
| COS upload 403 | Missing one of the `UploadHeaders`, or the upload URL expired (>15 min) |
| `检出 ZIP 包` failed | `CosTimestamp` missing or wrong (must reuse the `UnixTimestamp` from step ①) |
| `docker push :tag` empty tag | Used a non-existent variable; use `$CLOUDBASE_VERSION_NAME` |
| `docker login unauthorized` | TCR namespace not authorized; check `TCR_INSTANCE_ID` |
| `AuthFailure.SignatureFailure` | Push script `REGION` does not match the TCR instance region |
### Stage B (deploy)
| Symptom | Likely cause |
| --- | --- |
| `ResourceNotFound.ImageConfig` | `imageUri` does not exist or the tag is wrong |
| `InvalidParameterValue.ImageUri` | Wrong format; must be `{domain}/{ns}/{image}:{tag}` |
| SCF image pull timeout | TCR and SCF are not in the same region |
| SCF image pull denied | `SCF_QcsRole` not authorized to pull from TCR |
| Function start timeout (60s) | Image too large or startup too slow; enable image acceleration / slim the image |
| Port 9000 no response | Dockerfile `EXPOSE` / `CMD` does not actually start an HTTP server on 9000 |
## Best practices
- Image tag = `$CLOUDBASE_VERSION_NAME` — available at build time, unique, traceable.
- TCR push via STS + `CreateInstanceToken` — no long-term secrets to manage.
- `Runtime` must be `"CustomImage"`, not a language runtime.
- `imageUri` must include a tag — never `:latest`.
- Keep TCR, CloudApp build, and SCF in the same region.
- SCF image ≤ 500MB + enable acceleration to control cold-start time.
references/cloud-functions/references/http-functions.md
# HTTP Functions Reference
Use this reference when the task is clearly about an HTTP Function: REST API, browser-facing endpoint, SSE stream, or WebSocket service.
## Core model
HTTP Functions are standard web services, not `exports.main(event, context)` handlers.
- Handle requests through `req` and `res`.
- Listen on port `9000`.
- Ship an executable `scf_bootstrap` file.
- Include runtime dependencies in the package; HTTP Functions do not auto-install `node_modules` for you.
- For simple HTTP APIs, prefer the Node.js native `http` module so the function shape stays explicit and dependency-light. Only introduce Express, Koa, NestJS, or similar frameworks when the user explicitly asks for one or the service complexity justifies it.
- If the service calls CloudBase resources through `@cloudbase/node-sdk` or `@cloudbase/manager-node`, read `./http-function-credentials.md` first. HTTP Functions must use explicit credentials and must not rely on the Event Function passwordless/default temporary credential path.
## Minimal structure
```text
my-http-function/
├── scf_bootstrap
├── package.json
├── node_modules/
└── index.js
```
### `scf_bootstrap`
```bash
#!/bin/bash
/var/lang/node18/bin/node index.js
```
Requirements:
- File name must be exactly `scf_bootstrap`.
- Use LF line endings.
- Make it executable with `chmod +x scf_bootstrap`.
The `scf_bootstrap` Node.js binary path must match the function runtime. Use this mapping:
| Runtime value | `scf_bootstrap` binary path |
| --- | --- |
| `Nodejs20.19` | `/var/lang/node20/bin/node` |
| `Nodejs18.15` | `/var/lang/node18/bin/node` |
| `Nodejs16.13` | `/var/lang/node16/bin/node` |
If the user specifies "Node.js 18", use runtime `Nodejs18.15` and the path `/var/lang/node18/bin/node`.
## Minimal Node.js example
```javascript
const http = require("http");
const { URL } = require("url");
// CORS headers — default to * for simple cross-origin APIs
const CORS_HEADERS = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
};
function sendJson(res, statusCode, data) {
res.writeHead(statusCode, {
"Content-Type": "application/json; charset=utf-8",
...CORS_HEADERS,
});
res.end(JSON.stringify(data));
}
function sendOptions(res) {
res.writeHead(204, CORS_HEADERS);
res.end();
}
function readJsonBody(req) {
return new Promise((resolve, reject) => {
let raw = "";
req.on("data", (chunk) => {
raw += chunk;
});
req.on("end", () => {
if (!raw) {
resolve({});
return;
}
try {
resolve(JSON.parse(raw));
} catch (error) {
reject(new Error("Invalid JSON body"));
}
});
req.on("error", reject);
});
}
const server = http.createServer(async (req, res) => {
// Handle CORS preflight
if (req.method === "OPTIONS") {
return sendOptions(res);
}
const url = new URL(req.url || "/", "http://127.0.0.1");
if (req.method === "GET" && url.pathname === "/health") {
sendJson(res, 200, { ok: true });
return;
}
if (req.method === "POST" && url.pathname === "/echo") {
try {
const body = await readJsonBody(req);
sendJson(res, 200, { received: body });
} catch (error) {
sendJson(res, 400, { error: error.message });
}
return;
}
sendJson(res, 404, { error: "Not Found" });
});
server.listen(9000);
```
## Code-writing rules
- Do not write HTTP Functions as `exports.main = async (event, context) => {}`. That is the Event Function contract.
- Start an HTTP server explicitly with `http.createServer(...)` or a framework app, and always bind to port `9000`.
- Choose one Node.js module system and keep it consistent. For simple HTTP Functions, CommonJS is the safest default: use `require(...)` and leave `"type": "module"` out of `package.json`.
- If you intentionally use ES Modules, use `import ...` consistently and do not rely on CommonJS-only globals such as bare `__dirname`, `require(...)`, or `module.exports`. When you need the current file path in ESM, derive it from `import.meta.url`.
- Treat routing, method checks, and body parsing as part of the function code. With the native `http` module, parse `req.url` yourself and read the request body from the stream before calling `JSON.parse`.
- Return JSON responses explicitly and set `Content-Type` yourself, for example `application/json; charset=utf-8`.
- **Handle CORS headers**. Browsers block cross-origin requests without proper CORS headers. Default to `Access-Control-Allow-Origin: *` for simple APIs, and always respond to `OPTIONS` preflight requests with `200` and CORS headers.
- Keep unsupported routes and methods explicit. Return `404` for unknown paths, and return `405` when the path exists but the HTTP method is not allowed.
- Keep `scf_bootstrap`, `index.js`, `package.json`, and any bundled dependencies in the function directory that will be uploaded.
- Keep credentials out of the function package. Inject them through function environment variables and preserve existing variables when updating the configuration.
### Module system note
The minimal examples in this document use CommonJS:
- `const http = require("http")`
- no `"type": "module"` in `package.json`
That combination avoids the common ESM pitfall where `__dirname` is not defined. If you switch to ES Modules, switch the whole function to `import` syntax and update any file-path logic accordingly.
## Request handling rules
- With Node native `http`, use `new URL(req.url, "http://127.0.0.1")` and read `url.searchParams` for query values.
- With Node native `http`, `req.body` does not exist. Read the body stream manually, then parse JSON yourself.
- `req.headers` -> incoming HTTP headers for **server-side** use only (auth checks, content negotiation). **Never serialize `req.headers`, `process.env`, or `x-cloudbase-context` into the HTTP response.** See `../../cloudbase-platform/references/protocols/sensitive-runtime-data-protection.md`.
- Path parameters are framework-level conveniences. With the native `http` module, match `url.pathname` yourself.
- Always send a response explicitly. With Node native `http`, use `res.writeHead(...)` and `res.end(...)`.
- Return meaningful status codes such as `400`, `401`, `404`, `405`, `500`.
- Debug endpoints, if required, must return an explicit allowlist of non-sensitive fields (for example `method` + `path`) — not a full header or env dump.
### Example with method checks
```javascript
const http = require("http");
const { URL } = require("url");
// CORS headers — default to * for simple cross-origin APIs
const CORS_HEADERS = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
};
function sendJson(res, statusCode, data) {
res.writeHead(statusCode, {
"Content-Type": "application/json; charset=utf-8",
...CORS_HEADERS,
});
res.end(JSON.stringify(data));
}
function sendOptions(res) {
res.writeHead(204, CORS_HEADERS);
res.end();
}
function readJsonBody(req) {
return new Promise((resolve, reject) => {
let raw = "";
req.on("data", (chunk) => {
raw += chunk;
});
req.on("end", () => {
if (!raw) {
resolve({});
return;
}
try {
resolve(JSON.parse(raw));
} catch (error) {
reject(new Error("Invalid JSON body"));
}
});
req.on("error", reject);
});
}
const server = http.createServer(async (req, res) => {
// Handle CORS preflight
if (req.method === "OPTIONS") {
return sendOptions(res);
}
const url = new URL(req.url || "/", "http://127.0.0.1");
if (url.pathname === "/users" && req.method === "POST") {
try {
const { name, email } = await readJsonBody(req);
if (!name || !email) {
sendJson(res, 400, { error: "name and email are required" });
return;
}
sendJson(res, 201, { name, email });
} catch (error) {
sendJson(res, 400, { error: error.message });
}
return;
}
if (url.pathname === "/users") {
sendJson(res, 405, { error: "Method Not Allowed" });
return;
}
sendJson(res, 404, { error: "Not Found" });
});
server.listen(9000);
```
### Express 5 catch-all note
If the user explicitly asks for Express, keep in mind that Express 5 uses `path-to-regexp` semantics for wildcards. Do not use bare `*` or `/*` as the catch-all route.
```javascript
app.all("/{*splat}", (req, res) => {
res.status(405).json({ error: "Method Not Allowed" });
});
```
Express 5 note: `app.all("/{*splat}", (req, res) => {` is the safe catch-all form when you also need to match the root path `/`, because the router is based on `path-to-regexp` rather than the older Express 4 wildcard behavior.
## End-to-end deployment lifecycle
This document covers the **managed-runtime** HTTP Function (ships `scf_bootstrap`, runs on a language runtime, packaged as zip). If the function instead needs custom system libraries or an arbitrary runtime, deploy it from a container image (`Runtime: CustomImage`) and read `./http-functions-custom-image.md` — the runtime contract (web server on port `9000`, CORS, security rules) is identical, only the packaging and deployment differ.
Follow these steps in order when creating a managed-runtime HTTP Function:
1. **Write the function code** — create the directory with `index.js`, `scf_bootstrap`, and `package.json`.
2. **Configure SDK credentials when needed** — if the function uses `@cloudbase/node-sdk` or `@cloudbase/manager-node`, follow `./http-function-credentials.md`. For a Node SDK server API Key, use `manageAppAuth(action="createApiKey", keyType="api_key")` and inject `CLOUDBASE_APIKEY`. Manager SDK requires Tencent Cloud `SecretId` / `SecretKey`.
3. **Deploy with `manageFunctions`** — set `type: "HTTP"`, `protocolType: "HTTP"`, and `runtime` explicitly.
4. **Configure security rules** — HTTP Functions default to a restrictive security rule. If the function should be publicly accessible, call `managePermissions(action="updateResourcePermission")` with `resourceType="function"`. Note: anonymous login is disabled by default for new environments; use `permission: "CUSTOM"` with `securityRule: '{"invoke":"true"}'` for truly public endpoints rather than relying on anonymous auth.
5. **Verify** — call the function URL and confirm it returns the expected response. If the function uses a CloudBase SDK, exercise one harmless SDK read and check logs for expired-token or authorization failures. If you get `EXCEED_AUTHORITY` at invocation, the function security rule needs to be updated (step 4).
## Deployment flow
Prefer `manageFunctions` over CLI in agent flows.
```javascript
manageFunctions({
action: "createFunction",
func: {
name: "myHttpFunction",
type: "HTTP",
protocolType: "HTTP",
runtime: "Nodejs18.15",
timeout: 60
},
functionRootPath: "/absolute/path/to/cloudfunctions"
});
```
**Important parameters:**
- `type: "HTTP"` — marks the function as an HTTP Function (not an Event Function).
- `protocolType: "HTTP"` — the wire protocol. Use `"WS"` for WebSocket.
- `runtime` — the execution runtime. Must match the `scf_bootstrap` binary path. Default is `"Nodejs18.15"` if omitted, but always set it explicitly to avoid ambiguity.
- `functionRootPath` — the parent directory of the function folder (e.g. `/path/to/cloudfunctions` if the code lives in `/path/to/cloudfunctions/myHttpFunction/`).
### Security rule configuration
After creating an HTTP Function, it will reject unauthenticated callers with `EXCEED_AUTHORITY` by default. If the function should be publicly accessible:
> ⚠️ **Note:** Anonymous login is disabled by default for new environments. For public endpoints, use `rule: "true"` to allow all callers regardless of auth state, rather than relying on anonymous login being enabled.
>
> ⚠️ **PostgreSQL environments:** platform `ModifyResourcePermission` / `DescribeResourcePermission` reject PG envs. Current MCP aligns with CLI `tcb policy set/get`: `managePermissions` / `queryPermissions` for `resourceType="function"` automatically fall back to Manager SDK `modifyEnvAuthzConfig` / `describeEnvAuthzConfig` (`authz.user.rego`). Passing `securityRule: '{"invoke":true}'` generates a public-functions OPA allow policy; you can also pass a full Rego document starting with `package authz.user`. See https://docs.cloudbase.net/cli-v1/policy/management
```javascript
managePermissions({
action: "updateResourcePermission",
resourceType: "function",
resourceId: "myHttpFunction",
permission: "CUSTOM",
securityRule: '{"invoke":true}'
});
```
- `aclTag: "CUSTOM"` with `rule: "true"` allows all callers (public access without requiring any login).
- Do NOT use `readSecurityRule` / `writeSecurityRule` — those are removed. Use `queryPermissions` / `managePermissions` instead.
- Security rule semantics for `resourceType="function"` differ from NoSQL database rules. Do not reuse `doc._openid` or `auth.openid` expressions from NoSQL security rules.
- Official reference: `https://docs.cloudbase.net/cloud-function/security-rules`
If an external caller reports `EXCEED_AUTHORITY`, inspect the function permission first with `queryPermissions(action="getResourcePermission", resourceType="function", resourceId="myHttpFunction")` before widening access.
### WebSocket
For WebSocket workloads, keep the function type as HTTP and switch `protocolType`:
```javascript
manageFunctions({
action: "createFunction",
func: {
name: "mySocketFunction",
type: "HTTP",
protocolType: "WS"
},
functionRootPath: "/absolute/path/to/cloudfunctions"
});
```
## Invocation options
### HTTP API with token
```bash
curl -L "https://{envId}.api.tcloudbasegateway.com/v1/functions/{name}?webfn=true" \
-H "Authorization: Bearer <TOKEN>"
```
This is suitable for authenticated server-to-server access.
### HTTP access path for browser/public access
Creating the function does not automatically create a browser-facing path. Add gateway access separately when the user actually needs it.
```javascript
manageGateway({
action: "createRoute",
targetName: "myHttpFunction",
upstreamResourceType: "WEB_SCF",
path: "/api/hello"
});
```
Omit `domain` to attach on the HTTP gateway IsDefault domain (`DomainType=HTTPSERVICE`, `*.{region}.app.tcloudbase.com`). That is path routing on the gateway default host — **not** a `STATIC_STORE` binding, even though the env may also show a separate IsDefault static-hosting CDN host (`*.tcloudbaseapp.com`). Verify with `queryGateway(action="listRoutes")`.
Before enabling public access, confirm both of these:
1. The access path exists.
2. The function security rule allows the intended caller identity (see Security rule configuration above). Note: anonymous login is disabled by default — for public endpoints, use `rule: "true"` instead of requiring anonymous auth.
## SSE and WebSocket notes
### SSE
```javascript
res.setHeader("Content-Type", "text/event-stream");
res.write(`data: ${JSON.stringify({ content: "Hello" })}\n\n`);
```
### WebSocket example
```javascript
const WebSocket = require("ws");
const wss = new WebSocket.Server({ port: 9000 });
wss.on("connection", (ws) => {
ws.on("message", (message) => ws.send(`Echo: ${message}`));
});
```
## When to stop and reroute
- If the task is actually a timer-triggered or SDK-invoked serverless function, reroute to Event Functions.
- If the HTTP Function needs custom system packages or an arbitrary runtime but should stay SCF request-driven and scale to zero, deploy from a container image — read `./http-functions-custom-image.md`.
- If the task needs long-lived containers, custom system packages, or broader service architecture, reroute to `cloudrun-development`.
- If the task is only about HTTP API calling patterns rather than implementation, reroute to `http-api-cloudbase`.
references/cloud-functions/references/operations-and-config.md
# Cloud Functions Operations and Config Reference
Use this reference for logs, gateway exposure, environment-variable updates, triggers, and legacy tool-name translation.
## Logs
### Preferred path
- `queryFunctions(action="listFunctionLogs")` for the log list.
- `queryFunctions(action="getFunctionLogDetail")` for a specific request log.
### Plan B: `callCloudApi`
Only use raw cloud API calls after reading the official docs or knowledge-base entry for the action and parameter contract. Do not guess the action name or payload shape from memory.
#### Log list
```javascript
callCloudApi({
service: "tcb",
action: "GetFunctionLogs",
params: {
EnvId: "{envId}",
FunctionName: "functionName",
Offset: 0,
Limit: 10,
StartTime: "2024-01-01 00:00:00",
EndTime: "2024-01-01 23:59:59"
}
});
```
#### Log detail
```javascript
callCloudApi({
service: "tcb",
action: "GetFunctionLogDetail",
params: {
StartTime: "2024-01-01 00:00:00",
EndTime: "2024-01-01 23:59:59",
LogRequestId: "request-id-from-log-list"
}
});
```
### Log query limits
- `Offset + Limit` cannot exceed `10000`.
- `StartTime` to `EndTime` cannot span more than one day.
- For large ranges, page through day-sized windows.
## Event Function HTTP access
### Preferred path
Use Domain/Route via `manageGateway(action="createRoute")`. Omit `domain` to attach the route on the HTTP gateway IsDefault domain (`DomainType=HTTPSERVICE`, typically `*.{region}.app.tcloudbase.com`).
```javascript
manageGateway({
action: "createRoute",
targetName: "functionName",
upstreamResourceType: "SCF", // Event function -> SCF; HTTP function -> WEB_SCF
path: "/api/users",
auth: false
});
```
**IsDefault vs static hosting CDN:** many environments also list an IsDefault `STATIC_STORE` domain (`*.tcloudbaseapp.com`). Omitting `domain` does **not** attach to that static-hosting CDN hostname, and it is **not** a `STATIC_STORE` upstream binding. Confirm with `queryGateway(action="listRoutes")` — inspect `Domain`, `DomainType`, `Path`, and `UpstreamResourceType` on the created route.
**Disable / enable routes:** use `manageGateway(action="disableRoute"|"enableRoute")` with `path` (and prefer an explicit `domain`). This sets `Routes[].Enable` through `ModifyHTTPServiceRoute` (there is no `ModifyGatewayRoute` action). To close the static hosting default domain, list routes, take the `STATIC_STORE` IsDefault host, then:
```javascript
manageGateway({
action: "disableRoute",
domain: "<envId>-<appId>.tcloudbaseapp.com",
path: "/"
});
```
Do not expect a `manageHosting` disable-default-domain action. `updateRoute` may also pass `enable=false` / `route.enable=false` when you already have the full route fields.
Upstream type:
- HTTP cloud function -> `upstreamResourceType="WEB_SCF"`
- Event cloud function -> `upstreamResourceType="SCF"`
- CloudRun -> `upstreamResourceType="CBR"`
- Static hosting -> `upstreamResourceType="STATIC_STORE"` (serviceName often `staticstore`)
Do **not** use deprecated GWAPI / `CreateCloudBaseGWAPI` via `callCloudApi` (blocked in evaluate mode and removed from MCP).
Do **not** pass `manageFunctions` `type="HTTP"|"Event"` into `manageGateway`; gateway uses `upstreamResourceType` only.
When a deploy/create tool returns `accessUrl` or `accessUrls`, prefer those values directly; they already rank gateway custom domains before default domains when routes exist.
## Environment variable updates
Do not overwrite function environment variables blindly.
### Safe pattern
1. Read current config with `queryFunctions(action="getFunctionDetail")`.
2. Merge existing variables with the new variables.
3. Update with `manageFunctions(action="updateFunctionConfig")`.
```javascript
const current = await queryFunctions({
action: "getFunctionDetail",
functionName: "functionName"
});
const mergedEnvVariables = {
...current.EnvVariables,
...newEnvVariables
};
await manageFunctions({
action: "updateFunctionConfig",
functionName: "functionName",
envVariables: mergedEnvVariables
});
```
## Trigger and VPC notes
### Timer triggers
Configure timer triggers through `func.triggers`.
- Type: `timer`
- Cron format: 7 fields -> second minute hour day month week year
Examples:
- `0 0 2 1 * * *` -> 2:00 AM on the first day of every month
- `0 30 9 * * * *` -> 9:30 AM every day
### VPC field shape (example only)
When a function already needs VPC egress (exception path: existing TCP DB clients), `vpc` IDs must be real (never placeholders). This is a field-shape example — not a recommendation to introduce TCP DB access. Prefer native SDK / MCP SQL for new CRUD. Full exception policy: `./vpc-and-tcp-database.md`.
```javascript
{
vpc: {
vpcId: "<real-vpc-id>",
subnetId: "<real-subnet-id>"
}
}
```
## Layers (SCF Layer)
SCF LayerName is an **account-scoped shared namespace** (not per CloudBase env). Different envs that create the same layer name share one version sequence; deleting a version can break every function in every env that binds that version.
This section mirrors the MCP `queryFunctions` / `manageFunctions` **层(Layer)说明** so skill guidance and tool descriptions stay aligned.
### Naming contract (new layers)
- Fixed format: `{layerName}_{当前envId}`
- Example: `common_cloud1-d9ghadgak3edf6b36`
- Pass the full string as `layerName`. MCP / Manager SDK pass the name through as-is — they do **not** auto-append `envId`.
- Do not reuse a bare name (e.g. `common`) in another env.
- Before create: `queryFunctions(action="listLayers")` (optionally with `searchKey`) to check for collisions.
### Preferred tools
| Goal | Tool |
| --- | --- |
| List layers / versions / detail (account-level view) | `queryFunctions(action="listLayers"\|"listLayerVersions"\|"getLayerVersionDetail")` |
| List layers bound to a function | `queryFunctions(action="listFunctionLayers")` |
| Create a layer version | `manageFunctions(action="createLayerVersion")` |
| Delete a layer version | `manageFunctions(action="deleteLayerVersion")` |
| Bind / unbind / replace function layers | `manageFunctions(action="attachLayer"\|"detachLayer"\|"updateFunctionLayers")` |
```javascript
// Create — name already includes current envId
manageFunctions({
action: "createLayerVersion",
layerName: "common_cloud1-d9ghadgak3edf6b36",
runtimes: ["Nodejs18.15"],
contentPath: "/abs/path/to/layer-content"
});
```
### Soft `warnings` (non-blocking)
Layer actions may return envelope `warnings: string[]` while `success` remains `true`. Surface them to the user; do not treat them as hard failures and do not invent a different tool path because of them.
Typical advisories (wording may vary slightly):
- create without `envId` in the name → suggest `{layerName}_{envId}` because the name may share a version sequence with other envs
- list layers / versions / detail → account-level view may include layers from other envs
- delete version → account-shared; affects all envs bound to that version
- attach / detach / updateFunctionLayers → account-shared bind/unbind impact
### Do not
- Auto-rewrite or suffix existing bare layer names in tooling (breaks callers that still use the bare name).
- Tell the user to switch to `tcb` CLI solely to avoid layer conflicts — stay on MCP when tools are available.
- Assume `listLayers` is filtered to the current env only.
## Legacy tool-name translation
Prefer the converged entrances below, but translate historical names when they appear in old prompts or old docs.
| Historical name | Current action |
| --- | --- |
| `getFunctionList` | `queryFunctions(action="listFunctions")` |
| `createFunction` | `manageFunctions(action="createFunction")` |
| `updateFunctionCode` | `manageFunctions(action="updateFunctionCode")` |
| `updateFunctionConfig` | `manageFunctions(action="updateFunctionConfig")` |
| `getFunctionLogs` | `queryFunctions(action="listFunctionLogs")` |
| `getFunctionLogDetail` | `queryFunctions(action="getFunctionLogDetail")` |
| `manageFunctionTriggers` | `manageFunctions(action="createFunctionTrigger"|"deleteFunctionTrigger")` |
| `readFunctionLayers` | `queryFunctions(action="listLayers"|"listLayerVersions"|"getLayerVersionDetail"|"listFunctionLayers")` |
| `writeFunctionLayers` | `manageFunctions(action="createLayerVersion"|"deleteLayerVersion"|"attachLayer"|"detachLayer"|"updateFunctionLayers")` |
| `createFunctionHTTPAccess` | `manageGateway(action="createRoute")` with `upstreamResourceType="WEB_SCF"` |
## CLI fallback
Use CLI when MCP function tools are **not available in this session** (first conversation, MCP just installed and needs restart, or mcporter/IDE MCP unreachable), or when the user/CI explicitly asks for CLI.
Before CLI deploy: ensure MCP is configured for the next session when missing (`mcp-setup.md`), then follow `cloudbase-cli` (`core.md` + `functions.md`: `tcb login` → confirm envId → `tcb env use` → `tcb fn deploy`). Do **not** use `tcb deploy`. Decision tree: guideline `tooling-fallback.md` (includes No npm/npx).
- `tcb fn deploy <name>` -> Event Function
- `tcb fn deploy <name> --httpFn` -> HTTP Function
- `tcb fn deploy <name> --httpFn --ws` -> HTTP Function with WebSocket
- `tcb fn deploy --all` -> Deploy all functions
- `tcb fn config update <name>` -> Update function config (timeout, memorySize, envVariables)
**Important:** When MCP tools are available in this session, prefer them over CLI unless the user asked for CLI. When MCP is missing but a shell can run `tcb`, use CLI — do not block on restart.
**Batch updates via MCP:** MCP does not have a `--all` batch parameter. To update multiple functions, call `manageFunctions(action="updateFunctionConfig")` individually for each function. CLI may use `--all` when on the CLI path.
In non-interactive CI, prefer `tcb login --apiKeyId / --apiKey` (env-injected) over interactive device-code flows.
references/cloud-functions/references/vpc-and-tcp-database.md
# VPC and non-native TCP database access (cloud functions)
> **STOP — exception-only path.** Prefer CloudBase native SDK (`app.database()` / `app.rdb()`) or MCP SQL tools (`queryMysqlDatabase` / `manageMysqlDatabase` / `queryPgDatabase` / `managePgDatabase`) for business data. Do **not** read this file for new CRUD, notepads, CMS, or greenfield APIs. Continue only when migrating an **existing** app that already ships classic TCP clients (`DATABASE_URL`, Prisma, `mysql2`, `pg`, Redis). Credentials must come from the user or console env config — do **not** infer passwords from standard MCP instance-status queries (`getInstanceInfo`).
## When this applies
Use this only when the Event Function or HTTP Function uses a **classic TCP client** to reach MySQL / PostgreSQL / Redis / MongoDB (for example `DATABASE_URL`, `mysql2`, `pg`, Prisma, TypeORM, Sequelize).
**Does NOT apply** when the function uses CloudBase native SDK surfaces only:
- Document DB: `app.database()` / `db.collection(...)`
- CloudBase PG: `app.rdb()` / HTTP API gateway paths documented for CloudBase PG
Native SDK access is platform-managed and does **not** require function VPC binding for typical CloudBase DB usage.
## Required config for TCP / non-native SDK
| Field | Meaning |
| --- | --- |
| `envVariables.DATABASE_URL` (or `MYSQL_*` / `PG*` / `REDIS_*`) | Connection string / host for the **private** DB endpoint. Set from console or user-provided secrets — never invent credentials. |
| `vpc.vpcId` | Real VPC ID of that database (same region) |
| `vpc.subnetId` | Real subnet ID in that VPC with free IPs |
Event Functions and HTTP Functions share the same SCF networking model: both need `vpc` for private TCP access.
Official MySQL integration docs require enabling 私有网络 on the function and selecting the DB VPC: [Configure network connection](https://docs.cloudbase.net/cloud-function/resource-integration/mysql).
## Do not guess VPC IDs
Agents must **not**:
- Invent `vpc-xxxxx` / `subnet-xxxxx` placeholders
- Copy sample IDs from docs into a real deploy
- Assume “same environment” implies a default VPC without reading a real source
Resolve IDs from one of:
1. Database console / CloudBase MySQL settings (intranet VPC + subnet)
2. An existing resource that already works in that VPC (`queryFunctions getFunctionDetail`, CloudRun detail, CVM, etc.)
3. `callCloudApi` VPC/subnet describe APIs after confirming action names from docs
4. The user (ask and wait)
If IDs are still unknown after those steps: **stop**, report the gap, and do not deploy TCP DB env vars as if connectivity were solved.
## MCP usage
```javascript
await manageFunctions({
action: "createFunction", // or updateFunctionConfig
func: {
name: "api",
type: "HTTP", // or Event — same VPC rule
envVariables: {
// Replace with user/console-provided secret — do not paste passwords into chat logs
DATABASE_URL: "<private-db-url-from-console-or-user>"
},
vpc: {
vpcId: "<real-vpc-id>",
subnetId: "<real-subnet-id>"
}
},
functionRootPath: "/abs/path/to/cloudfunctions"
});
```
After create/update, call `queryFunctions(action="getFunctionDetail")` and verify `VpcConfig.VpcId` / `SubnetId`. Do not treat create/update success alone as proof that private TCP DB access works.
For TCP migration only, connection payloads may be fetched via `queryMysqlDatabase(action="getConnectionInfo")`. Standard `getInstanceInfo` does **not** return credentials.
## Side effects of enabling VPC
After VPC is bound, public internet egress may require NAT / public gateway in that VPC. Prefer fixing that network path over removing VPC just to “make outbound work” when the DB is private.
references/cloud-functions/SKILL.md
---
name: cloud-functions
description: CloudBase function runtime guide for building, deploying, and debugging your own Event Functions or HTTP Functions. This skill should be used when users need application runtime code on CloudBase, not when they are merely calling CloudBase official platform APIs.
version: 2.33.1
alwaysApply: false
---
## Sibling skills (local only)
Sibling CloudBase skills ship beside this skill. Use local relative paths such as `../auth-tool-cloudbase/SKILL.md`.
If a referenced sibling skill file is missing from this environment, ask the user to install the full CloudBase plugin (or the missing skill). Do **not** HTTP-fetch remote skill or protocol markdown into the agent context.
**Cross-cutting protocols** (required before code changes or deployments):
- Change Safety Protocol: `../cloudbase-platform/references/protocols/change-safety-protocol.md`
- Deployment Gate: `../cloudbase-platform/references/protocols/deployment-gate.md`
- Sensitive Runtime Data Protection: `../cloudbase-platform/references/protocols/sensitive-runtime-data-protection.md`
# Cloud Functions Development
## Activation Contract
### Use this first when
- The task is to create, update, deploy, inspect, or debug a CloudBase Event Function or HTTP Function that serves application runtime logic.
- The request mentions function runtime, function logs, `scf_bootstrap`, function triggers, or function gateway exposure.
### Read before writing code if
- You still need to decide between Event Function and HTTP Function.
- The task mentions `manageFunctions`, `queryFunctions`, `manageGateway`, or legacy function-tool names.
- The task might require `callCloudApi` as a fallback for logs or gateway setup.
- An HTTP Function will call CloudBase resources through `@cloudbase/node-sdk` or `@cloudbase/manager-node` -> read `./references/http-function-credentials.md`. HTTP Functions must use explicit credentials; do not rely on the Event Function passwordless runtime path.
### Exception only (do not read by default)
- Migrating an **existing** app that already uses classic TCP DB clients (`DATABASE_URL` / Prisma / `mysql2` / `pg` / Redis) → read `./references/vpc-and-tcp-database.md` via `./references.md`. New business CRUD must prefer CloudBase native SDK (`app.database()` / `app.rdb()`) or MCP SQL tools instead of TCP.
### Then also read
- Detailed reference routing -> `./references.md`
- Auth setup or provider-related backend work -> `../auth-tool-cloudbase/SKILL.md`
- CloudBase Integration Center generated WeChat Pay or Official Account functions -> `../cloudbase-wechat-integration/SKILL.md` (official docs: `https://docs.cloudbase.net/integration/introduce/index.md`)
- AI in functions -> `../ai-model-nodejs/SKILL.md`
- Long-lived container services or Agent runtimes -> `../cloudrun-development/SKILL.md`
- Calling CloudBase official platform APIs from a client or script -> `../http-api-cloudbase/SKILL.md`
### Do NOT use for
- CloudRun container services.
- Web authentication UI implementation.
- Database-schema design or general data-model work.
- CloudBase official platform API clients or raw HTTP integrations that only consume platform endpoints.
- Creating Integration Center instances through guessed APIs. For WeChat Pay or Official Account generated functions, use `cloudbase-wechat-integration` for the business contract and this skill only for function operations.
- **Tasks that the CloudBase JS SDK can handle directly** — simple data reads/writes, leaderboards, file uploads, real-time queries. Reach for the matching SDK surface before writing a function: `db.collection(...).get/add/update` only for confirmed NoSQL collections, and `app.rdb().from(...)` for CloudBase PG tables. Functions add deployment complexity, CORS configuration, and HTTP gateway binding that the SDK eliminates entirely.
### Common mistakes / gotchas
- Picking the wrong function type and trying to compensate later.
- Confusing official CloudBase API client work with building your own HTTP function.
- Mixing Event Function code shape (`exports.main(event, context)`) with HTTP Function code shape (`req` / `res` on port `9000`).
- Treating HTTP Access as the implementation model for HTTP Functions. HTTP Access is a gateway configuration for Event Functions, not the HTTP Function runtime model.
- Assuming `db.collection("name").add(...)` will create a missing document-database collection automatically. Collection creation is a separate management step.
- Forgetting that runtime cannot be changed after creation.
- Using cloud functions as the first answer for Web login.
- Forgetting that HTTP Functions must ship `scf_bootstrap`, listen on port `9000`, and include dependencies.
- Assuming an HTTP Function can use CloudBase SDKs without explicit credentials. The default temporary credential path is not reliable for HTTP Functions and credential rotation can break a running service. Use a CloudBase server API Key or Tencent Cloud key pair for `@cloudbase/node-sdk`; use a Tencent Cloud key pair for `@cloudbase/manager-node`. See `references/http-function-credentials.md`.
- Forgetting to configure function security rules after creating an HTTP Function. Default rules reject anonymous callers with `EXCEED_AUTHORITY`. Note: anonymous login is disabled by default for new environments — if the function needs public access without authentication, configure the security rule to allow all callers rather than relying on anonymous login.
- Mismatching the `scf_bootstrap` Node.js binary path with the function runtime (e.g. using `/var/lang/node18/bin/node` but setting `runtime: "Nodejs16.13"`).
- For Custom Image HTTP Functions: forgetting that TCR, the CloudApp build, and SCF must be in the same region; using `:latest` instead of a unique tag; or confusing the request-driven port-`9000` image model with a long-lived CloudRun container that listens on the injected `PORT`.
- Assuming MCP covers the whole image pipeline. `manageFunctions` covers SCF image deploy (Stage B) via `runtime: "CustomImage"` + `imageConfig`, but the CloudApp custom build → TCR push (Stage A) is a raw Tencent Cloud API path — confirm action names and parameters from official docs before any `callCloudApi` fallback.
- Making code or configuration changes without first following the Change Safety Protocol (`cloudbase-platform/references/protocols/change-safety-protocol.md`).
- Exposing functions publicly or deploying without first completing the checks in `cloudbase-platform/references/protocols/deployment-gate.md`.
- **Returning `req.headers`, `process.env`, `event`, or `context` wholesale** — gateways may inject `x-cloudbase-context` (base64 temporary credentials). Never echo that header or dump credential env vars to clients. Follow `../cloudbase-platform/references/protocols/sensitive-runtime-data-protection.md`.
- **Using a bare layer name (e.g. `common`) across environments.** SCF LayerName is an account-scoped shared namespace: same name → shared version sequence. Create new layers with fixed format `{layerName}_{当前envId}` (e.g. `common_cloud1-d9ghadgak3edf6b36`). Pass the full name as `layerName` — do not invent automatic suffixes. Treat MCP layer `warnings` as soft advisories (operation still succeeds). Details: `./references/operations-and-config.md`.
- **Long-running MCP image deployments must complete the full workflow**: When using `manageFunctions` with `deployFunction` for a real `cloud` or `local` deployment, prefer `wait=false` to avoid blocking a single Tool Call for an extended period. If the tool returns a `taskId`, do not end the workflow, report success, or ask the user to wait while the status is `running`. Automatically call `queryFunctions(action="getFunctionDeployStatus", taskId="...")` and continue polling according to the reported progress until the status becomes `succeeded` or `failed`. Only after reaching a reasonable polling limit may you report that the deployment is still in progress; include the `taskId`, current stage, and latest progress. On success, report the image URI or build ID, function status, and Gateway URL. On failure, report the failed stage, error code, request ID, and diagnostic guidance. If the status is `expired`, explain that the local task record exceeded its retention window; the cloud deployment may still be running, so call `getFunctionDetail` to confirm the actual cloud-side status instead of treating it as a failure.
### Minimal checklist
- Read [Cloud Functions Execution Checklist](checklist.md) before deployment or runtime changes.
- Decide whether the task is Event Function, HTTP Function, or actually CloudRun.
- Pick the detailed reference file in [references.md](references.md) before writing implementation code.
## MCP image deployment with polling
For real `cloud` or `local` custom-image deployments, prefer:
```json
{
"action": "deployFunction",
"dryRun": false,
"confirm": true,
"wait": false,
"deployConfig": {}
}
```
The `wait` field controls whether the current MCP Tool call waits for the complete deployment:
- `wait=true`: wait for the manager deployment to reach a terminal result and return it.
- `wait=false`: return a `taskId` promptly while the deployment continues in the MCP background.
When `wait=false` returns a `taskId`, the deployment workflow is not complete. Automatically call `queryFunctions` with `action="getFunctionDeployStatus"` and that `taskId`; continue while the status is `running`, then stop only at `succeeded` or `failed`. Wait about 5 seconds before the first follow-up query and use the returned progress/`nextActions` to continue without aggressive polling. Do not tell the user to ask again or imply success before a terminal status is returned. An `expired` status means the task exceeded the maximum retention window and was force-terminated locally — the cloud deployment may still be in progress, so confirm the real state with `getFunctionDetail` instead of reporting failure.
If a reasonable polling limit is reached, report only that the task is still running, including the `taskId`, current status, current stage, and latest progress. For a terminal result, report the deployment strategy, action, image URI/digest, build ID, function status, Gateway URL, or the failed stage, error code, request ID, and diagnostic next step.
### Personal-tier TCR credentials — never put the password in tool arguments
Personal-tier image builds (`imageConfig.imageType="personal"` with `local` / `cloud`) need a TCR push credential. Read it from the MCP process environment, not from tool arguments:
- Leave `func.imageConfig.build.registryCredential` **out of the request** when `TCB_TCR_USERNAME` and `TCB_TCR_PASSWORD` are set in the MCP server `env` block — the MCP fills them in automatically, the same way `TENCENTCLOUD_SECRETID` works.
- **Never ask the user to paste the password into chat, and never write it into tool arguments.** Anything placed in arguments enters the model context and the tool-call history.
- If deployment fails with `CLOUD_REGISTRY_CREDENTIAL_MISSING` or `CLOUD_REGISTRY_CREDENTIAL_INVALID`, instruct the user to add these two variables to the `env` block of their MCP configuration and restart the MCP server. Do not work around it by passing the credential inline.
- The username is the Tencent Cloud account UIN and is not itself a secret; it may be passed explicitly if needed. Explicit arguments take precedence per field, so username-in-argument plus password-from-environment is a valid combination.
**Know when that environment channel does not exist.** It works only for a local stdio MCP server whose client configuration exposes a custom `env` block. Some GUI clients do not inherit shell exports, and IDE-embedded MCP servers usually inject credentials from a hard-coded allowlist (often only `TENCENTCLOUD_*`), leaving the user no way to set arbitrary variables. Telling those users to "set it in the MCP `env` block" is an instruction they cannot act on. Route them to an enterprise registry (`imageType="enterprise"`, which mints a short-lived TCR token instead of using a fixed password) or to `buildStrategy="image"` with an already-pushed image.
### Enterprise-tier builds require a login state with CAM permission
`cloud` / `local` builds against an enterprise registry mint a TCR token through CAM (as does `autoGrant`). Environment-level API Keys and OAuth-issued STS credentials carry no CAM policy, so those calls fail with `UnauthorizedOperation`. The MCP probes the login state before starting a real enterprise build and refuses up front rather than failing midway; treat that error as a routing signal, not a retryable fault:
- Sign in with an account-level `TENCENTCLOUD_SECRETID` / `TENCENTCLOUD_SECRETKEY` pair, **or**
- Switch to `buildStrategy="image"` and deploy an image that was pushed elsewhere, **or**
- Use a personal-tier registry — its static password goes straight to `docker login` without touching CAM, which makes it the one build path that does work for API Key users.
## Writing mode at a glance
- If the request is for SDK calls, timers, or event-driven workflows, write an **Event Function** with `exports.main = async (event, context) => {}`.
- If the request is for REST APIs, browser-facing endpoints, SSE, or WebSocket, write an **HTTP Function** with `req` / `res` on port `9000`.
- For Node.js HTTP Functions, default to the native `http` module unless the user explicitly asks for Express, Koa, NestJS, or another framework.
- If the HTTP Function needs custom system libraries or an arbitrary runtime but should still be SCF request-driven and scale to zero, deploy it as a **Custom Image HTTP Function** (`Runtime: CustomImage`) from a TCR image. The container still listens on the fixed port `9000`. See `./references/http-functions-custom-image.md`. This is distinct from a CloudRun container, which listens on the injected `PORT` and runs long-lived.
- **有 Dockerfile 的 HTTP 无状态服务可优先考虑 HTTP 云函数,不必上云托管** — a Dockerfile alone does not mean CloudRun. If the service is stateless, request-driven HTTP without long connections / custom runtime / VPC database access, prefer an HTTP Function (or Custom Image HTTP Function) — faster to deploy, cheaper, and no CloudRun environment initialization needed. Route to CloudRun (`../cloudrun-development/SKILL.md`) only for WebSocket/SSE long connections, stable independent processes, custom system dependencies, or VPC DB access.
- If the user mentions HTTP access for an existing Event Function, keep the Event Function code shape and add gateway access separately.
## HTTP Function authoring contract
Use these rules whenever you are writing the function code itself:
- Do not write an HTTP Function as `exports.main(event, context)`. That is the Event Function contract.
- Treat the function as a standard web server process that must listen on port `9000`.
- With Node.js, prefer `http.createServer((req, res) => { ... })` by default so the runtime contract stays explicit.
- With the Node.js native `http` module, do not assume Express-style helpers exist. `req.body`, `req.query`, and `req.params` are not provided for you.
- For Node.js HTTP Functions, choose one module system up front and keep it consistent. Default to CommonJS for simple functions (`require(...)`, no `"type": "module"` in `package.json`) unless you explicitly want ES Modules.
- If you do choose ES Modules (`"type": "module"` + `import ...`), do not mix in CommonJS-only globals or APIs such as `require(...)`, `module.exports`, or bare `__dirname`. In ESM, derive file paths from `import.meta.url` with `fileURLToPath(...)` only when needed.
- With the native `http` module, parse `req.url` yourself with `new URL(...)`, collect the request body from the stream, and only then call `JSON.parse`. Empty bodies should be handled explicitly instead of assuming JSON is always present.
- Return responses explicitly with `res.writeHead(...)` and `res.end(...)`, including `Content-Type` such as `application/json; charset=utf-8` for JSON APIs.
- **Handle CORS headers**. Browsers block cross-origin requests without proper CORS headers. Default to allowing all origins for simple APIs:
- Respond to `OPTIONS` preflight with `200` and CORS headers
- Include `Access-Control-Allow-Origin: *` (or specific origin) on all responses
- Include `Access-Control-Allow-Methods: GET, POST, OPTIONS` as needed
- Include `Access-Control-Allow-Headers: Content-Type` for JSON requests
- Keep routing and method handling explicit. Unknown paths should return `404`, and known paths with unsupported methods should normally return `405`.
- Keep gateway setup and security-rule changes separate from the runtime code. They affect access, not the HTTP Function programming model.
- Do not add HTTP access service configuration when the task is only to create an HTTP Function itself. Gateway paths or custom domains are separate access-layer work; public invocation requirements should be handled through the function security rule workflow (note: anonymous login is disabled by default).
- If the HTTP Function calls CloudBase through `@cloudbase/node-sdk` or `@cloudbase/manager-node`, complete the explicit credential gate in `./references/http-function-credentials.md` before deployment. Never hardcode credentials in the function package.
- **Never echo sensitive runtime data.** Do not return `req.headers`, `process.env`, or `x-cloudbase-context` in responses. Debug endpoints must use an explicit non-sensitive allowlist. See `../cloudbase-platform/references/protocols/sensitive-runtime-data-protection.md`.
## Quick decision table
| Question | Choose |
| --- | --- |
| Triggered by SDK calls or timers? | Event Function |
| Needs browser-facing HTTP endpoint? | HTTP Function |
| Needs SSE or WebSocket service? | HTTP Function |
| Needs custom system libraries / arbitrary runtime, but still SCF request-driven + scale-to-zero? | HTTP Function with `Runtime: CustomImage` (deploy from a TCR image) |
| Has a Dockerfile but is a stateless HTTP service (no long connections / custom runtime / VPC DB)? | HTTP Function (or Custom Image HTTP Function) — **not** CloudRun |
| Needs long-lived container runtime or custom system environment? | CloudRun |
| Only needs HTTP access for an existing Event Function? | Event Function + gateway access |
## How to use this skill (for a coding agent)
1. **Choose the correct runtime model first**
- Event Function -> `exports.main(event, context)`
- HTTP Function -> web server on port `9000`
- If the requirement is really a container service, reroute to CloudRun early
2. **Use the converged MCP entrances**
- Reads -> `queryFunctions`, `queryGateway`
- Writes -> `manageFunctions`, `manageGateway`
- Translate legacy names before acting rather than copying them literally
3. **Write code and deploy, do not stop at local files**
- Use `manageFunctions(action="createFunction")` for creation
- Use `manageFunctions(action="updateFunctionCode")` for code updates
- Use `manageFunctions(action="updateFunctionConfig")` for config updates (timeout, memorySize, envVariables)
- For a Custom Image HTTP Function, call `manageFunctions(action="createFunction")` with `func.runtime="CustomImage"` and `imageConfig` (`imageUri` with tag; `registryId` for enterprise TCR); iterate later with `manageFunctions(action="updateFunctionCode")` + `imageConfig`. No `functionRootPath` is needed because the code lives in the image. See `./references/http-functions-custom-image.md`.
- Keep `functionRootPath` as the directory that directly contains function folders (e.g., `cloudfunctions/` or `functions/`), NOT the project root and NOT the function subdirectory itself
- **Prefer MCP when available** — use `manageFunctions` and `queryFunctions` when those tools are in this session
- **CLI fallback when MCP is missing** — if function tools are not loaded (first session / pre-restart), configure MCP for next time, then use `tcb fn deploy` via `../cloudbase-cli/SKILL.md` (see guideline `tooling-fallback.md`). Do not stall waiting for restart.
- **Do NOT invent CLI when the runtime has no shell** — if only MCP exists and it works, stay on MCP; if neither works, report the gap
- For batch updates (multiple functions), call `manageFunctions(action="updateFunctionConfig")` individually for each function — MCP does not have a `--all` batch parameter like CLI
- If an HTTP Function uses `@cloudbase/node-sdk`, prefer a server API Key created with `manageAppAuth(action="createApiKey", keyType="api_key")` and inject it as `CLOUDBASE_APIKEY`; Tencent Cloud `SecretId` / `SecretKey` is also supported
- If an HTTP Function uses `@cloudbase/manager-node`, inject Tencent Cloud `SecretId` / `SecretKey`; do not claim that a CloudBase API Key initializes the Manager SDK
- Merge credential environment variables with the existing function configuration instead of replacing the whole environment-variable set
4. **Prefer doc-first fallbacks**
- If a task falls back to `callCloudApi`, first check the official docs or knowledge-base entry for that action
- Confirm the exact action name and parameter contract before calling it
- Do not guess raw cloud API payloads from memory
5. **Read the right detailed reference**
- Event Function details -> `./references/event-functions.md`
- HTTP Function details -> `./references/http-functions.md`
- HTTP Function CloudBase SDK credentials -> `./references/http-function-credentials.md`
- HTTP Function from a container image (`Runtime: CustomImage`, TCR image pipeline) -> `./references/http-functions-custom-image.md`
- Logs, gateway, env vars, layers (`{layerName}_{当前envId}`), and legacy mappings -> `./references/operations-and-config.md`
## Database write reminder
- If a function will write to CloudBase document database, create the target collection first through console or management tooling.
- `db.collection("feedback").add(...)` only inserts into an existing collection; it does not auto-create `feedback` when absent.
- If the product requirement says "create when missing", implement that as an explicit collection-management step before the first write instead of assuming the runtime write call will provision it.
## Function types comparison
| Feature | Event Function | HTTP Function |
| --- | --- | --- |
| Primary trigger | SDK call, timer, event | HTTP request |
| Entry shape | `exports.main(event, context)` | web server with `req` / `res` |
| Port | No port | Must listen on `9000` |
| `scf_bootstrap` | Not required | Required |
| Dependencies | Auto-installed from `package.json` | Must be packaged with function code |
| Best for | serverless handlers, scheduled jobs | APIs, SSE, WebSocket, browser-facing services |
## Minimal code skeletons
### Event Function hello world
`cloudfunctions/hello-event/index.js`
```js
exports.main = async (event, context) => {
// Do not return event/context/process.env — they may contain platform secrets.
const name = typeof event?.name === "string" ? event.name : "world";
return {
ok: true,
message: `hello ${name} from event function`,
};
};
```
`cloudfunctions/hello-event/package.json`
```json
{
"name": "hello-event",
"version": "1.0.0"
}
```
### HTTP Function hello world
`cloudfunctions/hello-http/index.js`
```js
const http = require("http");
const { URL } = require("url");
// CORS headers — default to * for simple cross-origin APIs
const CORS_HEADERS = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
};
function sendJson(res, statusCode, data) {
res.writeHead(statusCode, {
"Content-Type": "application/json; charset=utf-8",
...CORS_HEADERS,
});
res.end(JSON.stringify(data));
}
function sendOptions(res) {
res.writeHead(204, CORS_HEADERS);
res.end();
}
function readJsonBody(req) {
return new Promise((resolve, reject) => {
let raw = "";
req.on("data", (chunk) => { raw += chunk; });
req.on("end", () => {
if (!raw) { resolve({}); return; }
try { resolve(JSON.parse(raw)); } catch (e) { resolve({}); }
});
req.on("error", reject);
});
}
const server = http.createServer(async (req, res) => {
// Handle CORS preflight
if (req.method === "OPTIONS") {
return sendOptions(res);
}
const url = new URL(req.url || "/", "http://127.0.0.1");
if (req.method === "GET" && url.pathname === "/") {
sendJson(res, 200, { ok: true, message: "hello from http function" });
} else if (req.method === "POST" && url.pathname === "/") {
const body = await readJsonBody(req);
sendJson(res, 200, { received: body });
} else {
sendJson(res, 404, { error: "Not Found" });
}
});
server.listen(9000);
```
For a more complete example with routing, method checks, and error handling, see `./references/http-functions.md`.
`cloudfunctions/hello-http/scf_bootstrap`
```bash
#!/bin/bash
/var/lang/node18/bin/node index.js
```
The `scf_bootstrap` binary path must match the runtime — see the full mapping table in `./references/http-functions.md`.
`cloudfunctions/hello-http/package.json`
```json
{
"name": "hello-http",
"version": "1.0.0"
}
```
## Preferred tool map
### Function management
- `queryFunctions(action="listFunctions"|"getFunctionDetail")`
- `manageFunctions(action="createFunction")`
- `manageFunctions(action="updateFunctionCode")`
- `manageFunctions(action="updateFunctionConfig")`
### Layers (SCF Layer)
Layers are **account-scoped**, not env-scoped. Align with MCP `manageFunctions` / `queryFunctions` layer guidance:
- **Naming (required for new layers):** `{layerName}_{当前envId}` — example `common_cloud1-d9ghadgak3edf6b36`. Do not reuse a bare name like `common` in another env.
- **Create:** `manageFunctions(action="createLayerVersion", layerName="…_{envId}", …)` after `queryFunctions(action="listLayers")` to check duplicates. MCP may return a soft `warnings` entry if the name lacks the current `envId`; it does **not** rewrite the name.
- **Read:** `queryFunctions(action="listLayers"|"listLayerVersions"|"getLayerVersionDetail"|"listFunctionLayers")` — list results are an account-level view and may include layers created in other envs.
- **Bind / unbind / replace:** `manageFunctions(action="attachLayer"|"detachLayer"|"updateFunctionLayers")`
- **Delete version:** `manageFunctions(action="deleteLayerVersion")` — deleting a version can affect every env that binds that version.
- Full contract and warning semantics → `./references/operations-and-config.md`
### Logs
**Query function logs** — use the `queryFunctions` tool:
- `queryFunctions(action="listFunctionLogs", functionName="xxx")` — list execution logs of a specific function
- `queryFunctions(action="getFunctionLogDetail", requestId="xxx")` — fetch the detail of one log entry
**`queryFunctions` vs `queryLogs`**:
- `queryFunctions` queries execution logs of a single cloud function and requires `functionName`
- `queryLogs` searches CLS (cross-service log aggregation) using CLS query syntax
**Examples**:
```javascript
// List recent logs for cloud function "my-function"
queryFunctions(action="listFunctionLogs", functionName="my-function", limit=10)
// Inspect the log detail for a specific request id
queryFunctions(action="getFunctionLogDetail", requestId="abc-123")
// Cross-service error search via CLS
queryLogs(action="searchLogs", queryString='(src:app OR src:system) AND log:"ERROR"', service="tcb")
```
`queryLogs` `queryString` follows CLS syntax (see https://cloud.tencent.com/document/api/876/128127). The examples below are starting points; adapt them to the concrete log content of your query:
- Function logs: `(src:app OR src:system) AND log:"START RequestId"`
- Aggregated function request status: `| select request_id, max(status_code) as status where ((request_id='xxxx' AND retry_num=0) AND retry_num=0) AND status_code!=202 group by request_id, retry_num`
- Document database (NoSQL): `module:database`
- Document database slow-query events: `module:database AND eventType:(MongoSlowQuery)` — `MongoSlowQuery` is the document-database slow-query event
- Relational database (MySQL): `module:rdb`
- Relational database (MySQL) events: `module:rdb AND eventType:(MysqlFreeze OR MysqlRecover OR MysqlSlowQuery)` — `MysqlFreeze` = freeze, `MysqlRecover` = recover, `MysqlSlowQuery` = slow query
- Workflow (approval flow): `module:workflow`
- Data model: `module:model`
- User permissions: `module:auth`
- LLM trace logs: `module:llm AND logType:llm-tracelog`
- Gateway access logs: `logType:accesslog`
- App publish / delete events: `module:app AND eventType:(AppProdPub OR AppProdDel)` — `AppProdPub` = app publish, `AppProdDel` = app delete
If these are unavailable, read `./references/operations-and-config.md` before any `callCloudApi` fallback
### Gateway exposure
- `queryGateway(action="getRoute")` / `listRoutes` / `listCustomDomains`
- `manageGateway(action="createRoute")` — for HTTP functions pass `upstreamResourceType="WEB_SCF"`; for Event functions pass `upstreamResourceType="SCF"`. Omit `domain` to attach the route on the HTTP gateway IsDefault domain (`DomainType=HTTPSERVICE`, typically `*.{region}.app.tcloudbase.com`)
- **IsDefault vs static hosting CDN:** environments often also expose a separate IsDefault `STATIC_STORE` domain (`*.tcloudbaseapp.com`). Omitting `domain` does **not** bind that static-hosting CDN entry, and it is **not** a `STATIC_STORE` upstream binding (that requires `upstreamResourceType="STATIC_STORE"`). Verify with `queryGateway(action="listRoutes")` and check `Domain` / `DomainType` / `Path` / `UpstreamResourceType`
- `manageGateway(action="updateRoute")` / `deleteRoute` / `enableRoute` / `disableRoute` / `bindCustomDomain` / `deleteCustomDomain`
- **Disable a route or the static hosting default domain:** prefer `manageGateway(action="disableRoute", domain=..., path=...)` (looks up the existing route, sets `Routes[].Enable=false` via `ModifyHTTPServiceRoute`). `updateRoute` may also pass `enable=false` / `route.enable=false`. To close `*.tcloudbaseapp.com`, list routes, take the `STATIC_STORE` IsDefault domain, then `disableRoute` with that `domain` and usually `path="/"` — not `manageHosting`, and not `ModifyGatewayRoute`
- When tool results include `accessUrl` / `accessUrls`, prefer them directly (gateway custom-domain URLs are ranked before default domains)
- Do **not** call deprecated GWAPI actions via `callCloudApi` (`CreateCloudBaseGWAPI`, etc.)
## Related skills
- `cloudrun-development` -> container services, long-lived runtimes, Agent hosting
- `http-api-cloudbase` -> raw CloudBase HTTP API invocation patterns
- `cloudbase-platform` -> general CloudBase platform decisions
- `ops-inspector` -> AIOps-style inspection and log search across services
## Reference index
All packaged reference files (required for skill lint reachability):
- [event-functions.md](references/event-functions.md)
- [http-function-credentials.md](references/http-function-credentials.md)
- [http-functions-custom-image.md](references/http-functions-custom-image.md)
- [http-functions.md](references/http-functions.md)
- [operations-and-config.md](references/operations-and-config.md)
- [vpc-and-tcp-database.md](references/vpc-and-tcp-database.md)
references/cloud-storage-web/SKILL.md
---
name: cloud-storage-web
description: Complete guide for CloudBase cloud storage using Web SDK (@cloudbase/js-sdk) - upload, download, temporary URLs, file management, and best practices.
version: 2.33.1
alwaysApply: false
---
## Sibling skills (local only)
Sibling CloudBase skills ship beside this skill. Use local relative paths such as `../auth-tool-cloudbase/SKILL.md`.
If a referenced sibling skill file is missing from this environment, ask the user to install the full CloudBase plugin (or the missing skill). Do **not** HTTP-fetch remote skill or protocol markdown into the agent context.
# Cloud Storage Web SDK
## Activation Contract
### Use this first when
- A browser or Web app must upload, download, or manage CloudBase storage objects through `@cloudbase/js-sdk`.
- The request mentions `uploadFile`, `getTempFileURL`, `deleteFile`, or `downloadFile` in frontend code.
### Read before writing code if
- The task is browser-side storage work but you still need to separate it from Mini Program storage, backend storage management, or static hosting deployment.
- The request may be blocked by security domains or frontend auth.
### Then also read
- Web login and identity -> `../auth-web-cloudbase/SKILL.md`
- General Web app setup -> `../web-development/SKILL.md`
- Direct storage management through MCP tools -> `../cloudbase-platform/SKILL.md`
### Do NOT use for
- Mini Program file APIs.
- Backend or agent-side direct storage management through MCP.
- Static website hosting deployment via `manageHosting(action="upload")`.
- Database operations.
### Common mistakes / gotchas
- Uploading from browser code without configuring security domains.
- Using this skill for static hosting instead of storage objects.
- Mixing browser SDK upload flows with server-side file-management tasks.
- Assuming temporary download URLs are permanent links.
- Ignoring `STORAGE_NOT_EXIST`; it means the target storage bucket/resource is not ready, not that the browser upload code should fabricate a URL.
- On local Vite or dev-server tasks, forgetting to whitelist the exact current browser `host:port` before testing `app.uploadFile()`.
- Treating CloudBase PG / `pgstore` like the legacy NoSQL CloudBase storage. PG environments use a separate `pgstore` backend whose buckets are NOT auto-created from your old NoSQL bucket. If `pgstore` has no bucket, every upload returns `STORAGE_BUCKET_NOT_FOUND` and the SDK then issues `PUT https://undefined/` (visible in DevTools as `net::ERR_NAME_NOT_RESOLVED`). Treat bucket existence as a hard prerequisite, just like Supabase: in Supabase Storage every upload must target an already-created bucket; CloudBase PG follows the same model.
### Minimal checklist
- Confirm the caller is a browser/Web app.
- Initialize the Web SDK once.
- Confirm CloudBase storage exists in the current environment before testing upload. Use available MCP management/query tools to inspect or create/select the storage bucket when the environment has no default bucket. **In a PG / pgstore environment, the legacy NoSQL bucket from `DescribeEnvs` does NOT count as a usable pgstore bucket; create one explicitly before any browser upload. The legacy NoSQL bucket itself is still fine for legacy `app.uploadFile()` flows that already target it — PG and NoSQL storage coexist; this skill applies to BOTH.**
- Check security-domain/CORS requirements.
- Pick the right storage method before coding.
### Local dev recipe
When the app runs on a local browser origin and must upload files from the frontend:
1. Use `envQuery` with `action="domains"` to inspect the current security-domain whitelist.
2. Convert the browser origin into the CloudBase whitelist entry format:
- Browser origin `http://127.0.0.1:4173` -> whitelist entry `127.0.0.1:4173`
- Browser origin `http://localhost:5173` -> whitelist entry `localhost:5173`
3. If the exact current host entry is missing, call `envDomainManagement` with `action="create"` and add that host entry before relying on `app.uploadFile()`.
4. If the runtime port may change between runs, do not assume any fixed default port list is sufficient. Re-check the actual browser origin you are really using for testing or final validation, then add that exact `host:port`.
5. Tell the user that security-domain changes may take a few minutes to propagate; poll `queryEnv(action="domains")` rather than blind-sleeping for a fixed long interval.
6. Only after that should you implement and test browser-side `app.uploadFile()` flows.
If `app.uploadFile()` returns `STORAGE_NOT_EXIST`, stop editing frontend code and fix the environment-side storage resource first. Re-check the environment storage list, create or select an available bucket if the task allows it, then retry the same SDK upload flow.
If the task uses browser-side file upload, treat this as a prerequisite rather than an optional cleanup.
### Bucket existence prerequisite (mandatory before any upload code)
Just like Supabase Storage, CloudBase Storage requires the target bucket to exist before any client-side upload. This is true for both legacy CloudBase NoSQL storage (`STORAGE_NOT_EXIST`) and the newer PG / `pgstore` backend (`STORAGE_BUCKET_NOT_FOUND`).
Mental model parity with Supabase:
| Step | Supabase | CloudBase |
| ---- | -------- | --------- |
| Create bucket | `supabase.storage.createBucket('covers', { public: true })` (admin-side, with service role) | In PG mode, create a `storage.buckets` bucket through PG storage HTTP API / CLI / console / SQL on `storage.buckets` when appropriate. The browser SDK cannot create one. |
| Upload | `supabase.storage.from('covers').upload('a.png', file)` | **PG 模式**: `app.storage.from('covers').upload('a.png', file)` — `from(bucketName)` 指定 pgstore 存储桶。<br>**非 PG 模式**: `app.storage.from().upload('covers/a.png', file)` — bucket 名作为路径第一段。|
| Bucket missing error | `Bucket not found` | Browser sees `STORAGE_BUCKET_NOT_FOUND` (PG) or `STORAGE_NOT_EXIST` (NoSQL), then a follow-up `PUT https://undefined/` because the SDK still tries to PUT a missing `metadata.url`. |
Required pre-upload steps in any task that needs browser uploads:
1. List existing buckets first. For PG / pgstore, the legacy NoSQL bucket (the `6d63-…-1409864723` shape returned by `DescribeEnvs.Storages[]`) is NOT a valid pgstore bucket — do not assume it works.
2. If no usable bucket exists for the upload target (e.g. `covers`), create one through the PG storage management surface BEFORE editing frontend upload code. Adding `covers` as a path prefix in code does not auto-create a bucket.
3. After creating the bucket, the upload pattern depends on environment:
- **PG / pgstore**: `app.storage.from('covers').upload('<file>', file)` — bucket 名传入 `from()`
- **Non-PG (NoSQL)**: `app.storage.from().upload('covers/<file>', file)` — bucket 名作为路径第一段
4. If you see `net::ERR_NAME_NOT_RESOLVED` going to `https://undefined/` in DevTools, that is the SDK reacting to a missing `metadata.url` field — almost always because the bucket does not exist or the SDK request was rejected upstream. Inspect the failed `POST .../v1/storages/get-objects-upload-info` response in DevTools first; the `code` field (e.g. `STORAGE_BUCKET_NOT_FOUND`, `STORAGE_CONTENT_LENGTH_REQUIRED`, `INVALID_PARAM`) tells you exactly what to fix.
Do not silently swallow upload failures. If `uploadCoverImage()` rejects, the parent `createArticle()` MUST also reject — never proceed to `db.from(...).insert(...)` with a fabricated URL or a placeholder, and never let the UI show a success toast.
### ⚠️ PG mode upload: use `app.storage.from('bucket')`, NOT `app.uploadFile()`
In PG / pgstore environments, use `app.storage.from('covers').upload(key, file)` for uploads and `app.storage.from('covers').createSignedUrl(path, expiresIn)` for getting access URLs.
Do NOT use the legacy NoSQL APIs in PG mode:
- ❌ `app.uploadFile()` — 这是旧 NoSQL 的上传 API
- ❌ `app.getTempFileURL()` — 这是旧 NoSQL 的获取 URL 方式
- ❌ `app.storage.from().upload('covers/file', file)` — 没有传 bucket 名
Use instead:
- ✅ `app.storage.from('covers').upload('file', file)` — PG 模式上传
- ✅ `app.storage.from('covers').createSignedUrl('file', 3600)` — 获取签名 URL(返回 `fullSignedURL` 字段)
### Post-bucket: storage RLS (mandatory in PG / pgstore environments)
In **PG / pgstore** environments, storage access control is enforced through **PostgreSQL Row Level Security (RLS) on `storage.buckets` / `storage.objects`** — exactly like Supabase Storage. These tables are already granted to `anon`, `authenticated`, and `service_role`; RLS is the permission gate. Traditional storage permission labels (`READONLY` / `PRIVATE` / `CUSTOM`) and JSON storage safe rules do not apply. The default RLS policy is deny all, so even if the bucket exists, `app.storage.from('covers').upload()` from a browser will fail with `STORAGE_PERMISSION_DENIED` unless you configure policies.
Use `managePgDatabase(action="execute", confirm=true)` to run the following SQL after creating the bucket:
```sql
ALTER TABLE storage.objects ENABLE ROW LEVEL SECURITY;
-- Allow authenticated users to upload files
CREATE POLICY "authenticated_upload" ON storage.objects
FOR INSERT TO authenticated
WITH CHECK (auth.role() = 'authenticated');
-- Allow authenticated users to read/download files
CREATE POLICY "authenticated_read" ON storage.objects
FOR SELECT TO authenticated
USING (auth.role() = 'authenticated');
-- Optional: allow users to update/delete their own files
CREATE POLICY "users_manage_own" ON storage.objects
FOR UPDATE TO authenticated
USING (auth.uid() = owner_id)
WITH CHECK (auth.uid() = owner_id);
```
Key points:
- `storage.objects` RLS is **separate** from CloudBase legacy NoSQL storage security rules (`managePermissions` / `ModifyStorageSafeRule`). In PG mode, always configure storage RLS via PG SQL, not the legacy security rule API.
- Without these policies, the browser receives `STORAGE_PERMISSION_DENIED` when calling `app.storage.from('covers').upload()` in PG mode.
- Use `IF NOT EXISTS` in a `DO $$` block when re-applying to avoid "policy already exists" errors on re-run.
## Overview
Use this skill for **browser-side cloud storage operations** through the CloudBase Web SDK.
Typical tasks:
- upload files from a browser
- generate temporary download URLs
- delete files
- trigger browser downloads
## SDK initialization
```javascript
import cloudbase from "@cloudbase/js-sdk";
const app = cloudbase.init({
env: "your-env-id"
});
```
Initialization rules:
- Use synchronous initialization with a shared app instance.
- Do not re-initialize in every component.
- If the operation depends on user identity, handle auth before storage operations.
## Method routing
- Upload from browser -> `app.uploadFile()`
- Temporary preview/download URL -> `app.getTempFileURL()`
- Delete existing files -> `app.deleteFile()`
- Trigger browser download -> `app.downloadFile()`
## Upload
```javascript
const result = await app.uploadFile({
cloudPath: "uploads/avatar.jpg",
filePath: selectedFile
});
```
### Upload rules
- `cloudPath` must include the filename.
- Use `/` to create folder structure.
- **In a CloudBase PG / pgstore environment**, the `from(bucketName)` argument is used as the bucket name (e.g. `from('covers')`), and `upload(key, file)` takes a key without bucket prefix. The bucket must already exist. Same model as Supabase Storage — never upload into a not-yet-created bucket.
- Validate file type and size before upload.
- Show upload progress for larger files when UX matters.
- On local dev origins, confirm the exact frontend origin already exists in environment security domains before assuming the upload path is usable.
- Match against the whitelist entry format returned by `envQuery(action="domains")`, which is typically `host:port` instead of a full `http://...` URL.
- If the environment has no storage bucket or the SDK returns `STORAGE_NOT_EXIST` / `STORAGE_BUCKET_NOT_FOUND`, use CloudBase management/MCP storage tools to create or choose a bucket before retrying. Do not treat this as a successful optional upload.
- After `app.uploadFile()` succeeds, do **not** fabricate a public-looking URL by concatenating `envId`, bucket domain, or `cloudPath`. Use the returned `fileID` with `app.getTempFileURL()` and store or display the SDK-resolved URL instead.
### Progress example
```javascript
await app.uploadFile({
cloudPath: "uploads/avatar.jpg",
filePath: selectedFile,
onUploadProgress: ({ loaded, total }) => {
const percent = Math.round((loaded * 100) / total);
console.log(percent);
}
});
```
## Temporary URLs
```javascript
const result = await app.getTempFileURL({
fileList: [
{
fileID: "cloud://env-id/uploads/avatar.jpg",
maxAge: 3600
}
]
});
```
Use temp URLs when the browser needs to preview or download private files without exposing a permanent public link.
Typical upload + preview flow:
```javascript
const uploadResult = await app.uploadFile({
cloudPath: "uploads/avatar.jpg",
filePath: selectedFile
});
const tempUrlResult = await app.getTempFileURL({
fileList: [{ fileID: uploadResult.fileID, maxAge: 3600 }]
});
const previewUrl = tempUrlResult.fileList?.[0]?.tempFileURL || tempUrlResult.fileList?.[0]?.download_url;
if (!previewUrl) {
throw new Error("Failed to resolve temporary file URL after upload");
}
```
## Delete files
```javascript
await app.deleteFile({
fileList: ["cloud://env-id/uploads/old-avatar.jpg"]
});
```
Always inspect per-file results before assuming deletion succeeded.
## Download files
```javascript
await app.downloadFile({
fileID: "cloud://env-id/uploads/report.pdf"
});
```
Use this for browser-initiated downloads. For programmatic rendering or preview, prefer `getTempFileURL()`.
## Security-domain reminder
To avoid CORS problems, add your frontend domain in CloudBase security domains. In MCP-enabled workflows, prefer checking and updating this through tools before coding browser uploads.
```json
{ "tool": "envQuery", "action": "domains" }
```
Use the actual browser origin when deciding what to add. If the page is running on a custom domain or a local dev port, add that exact `host:port` value instead of guessing from a hard-coded list.
```json
{
"tool": "envDomainManagement",
"action": "create",
"domains": ["<actual-browser-host>:<actual-browser-port>"]
}
```
Match the real browser origin to the whitelist entry format returned by `envQuery(action="domains")`. For local Vite and preview servers, the port can vary between runs, so avoid assuming any fixed default port is sufficient.
Typical examples:
- `<your-local-host>:<actual-port>`
- `<your-custom-domain>`
## Best practices
1. Use a clear folder structure such as `uploads/`, `avatars/`, `documents/`.
2. Validate file size and type in the browser before upload.
3. Use temporary URLs with reasonable expiration windows.
4. Clean up obsolete files instead of leaving orphaned storage objects.
5. Route privileged batch-management tasks to backend or MCP flows instead of browser direct access.
## Error handling
```javascript
try {
const result = await app.uploadFile({
cloudPath: "uploads/file.jpg",
filePath: selectedFile
});
console.log(result.fileID);
} catch (error) {
console.error("Storage operation failed:", error);
}
```
references/cloudbase-agent/py/adapter-coze.md
# Coze Adapter
This guide covers using the Coze platform integration with CloudBase Agent Python SDK.
## Overview
The Coze adapter allows you to use Coze's hosted AI bots as your backend, while still exposing them through the AG-UI protocol. This is useful when:
- You want to leverage Coze's bot building capabilities
- You need to integrate Coze bots into AG-UI-compatible frontends
- You want unified authentication and middleware with other adapters
## Installation
Coze adapter is included in the `cloudbase-agent-coze` package:
```bash
pip install cloudbase-agent-coze
```
## Basic Usage
```python
from cloudbase_agent.coze import CozeAgentAdapter
from cloudbase_agent.server import AgentServiceApp
def create_agent():
return CozeAgentAdapter(
bot_id="your-bot-id",
api_key="your-api-key"
)
AgentServiceApp().run(create_agent, port=9000)
```
## Configuration
### Required Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `bot_id` | `str` | Coze bot identifier |
| `api_key` | `str` | Coze API key |
### Optional Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `base_url` | `str` | `https://api.coze.com` | Coze API endpoint |
| `debug_mode` | `bool` | `False` | Enable debug logging |
### Example with All Options
```python
adapter = CozeAgentAdapter(
bot_id="bot_1234567890",
api_key="sk-1234567890",
base_url="https://api.coze.com",
debug_mode=True
)
```
## Authentication Integration
The Coze adapter automatically extracts user ID from the request context set by authentication middleware.
### Server Setup with Auth
```python
from cloudbase_agent.server import AgentServiceApp
from cloudbase_agent.coze import CozeAgentAdapter
import jwt
def auth_middleware(input_data, request):
"""Extract user from JWT and inject into state."""
token = request.headers.get("Authorization", "").replace("Bearer ", "")
if token:
jwt_payload = jwt.decode(token, "your-secret", algorithms=["HS256"])
if input_data.state is None:
input_data.state = {}
# Inject user ID (Coze adapter reads from here)
input_data.state["__request_context__"] = {
"user": {
"id": jwt_payload["sub"],
"jwt": jwt_payload
}
}
yield
def create_agent():
return CozeAgentAdapter(
bot_id="your-bot-id",
api_key="your-api-key"
)
app = AgentServiceApp()
app.use(auth_middleware)
app.run(create_agent, port=9000)
```
### User ID Extraction
The Coze adapter reads user ID from:
```python
state["__request_context__"]["user"]["id"]
```
This is used as the `user_id` parameter when calling Coze API, enabling:
- User-specific conversation history
- Multi-tenant isolation
- Personalized responses
## Environment Variables
For production, use environment variables:
```bash
# .env
COZE_BOT_ID=bot_1234567890
COZE_API_KEY=sk-1234567890
COZE_BASE_URL=https://api.coze.com # optional
```
```python
import os
from cloudbase_agent.coze import CozeAgentAdapter
def create_agent():
return CozeAgentAdapter(
bot_id=os.getenv("COZE_BOT_ID"),
api_key=os.getenv("COZE_API_KEY"),
base_url=os.getenv("COZE_BASE_URL", "https://api.coze.com")
)
```
## Error Handling
The Coze adapter handles common errors and emits AG-UI ERROR events:
### Common Errors
| Error | Description | Solution |
|-------|-------------|----------|
| `user_id not found` | No user ID in state | Ensure auth middleware is registered |
| `Invalid API key` | Coze API key is invalid | Check COZE_API_KEY |
| `Bot not found` | Bot ID doesn't exist | Verify COZE_BOT_ID |
| `Rate limit exceeded` | Too many requests | Implement rate limiting middleware |
### Custom Error Handling
```python
from cloudbase_agent.coze import CozeAgentAdapter
def create_agent():
adapter = CozeAgentAdapter(
bot_id="your-bot-id",
api_key="your-api-key",
debug_mode=True # Enable debug logging
)
return adapter
```
## Features
### Streaming Responses
Coze adapter automatically streams responses from the Coze API:
```
TEXT_MESSAGE_START
TEXT_MESSAGE_CONTENT (chunk 1)
TEXT_MESSAGE_CONTENT (chunk 2)
...
TEXT_MESSAGE_END
```
### Tool Support
If your Coze bot uses tools, tool calls are automatically handled and streamed as AG-UI TOOL_CALL events.
### Conversation History
Coze maintains conversation history on their platform. Pass `threadId` in requests to continue conversations:
```json
{
"messages": [...],
"threadId": "conversation-123"
}
```
## Complete Example
```python
# app.py
import os
import jwt
from cloudbase_agent.server import AgentServiceApp
from cloudbase_agent.coze import CozeAgentAdapter
from cloudbase_agent.server.send_message.models import RunAgentInput
from fastapi import Request
import logging
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# JWT configuration
JWT_SECRET = os.getenv("JWT_SECRET_KEY", "dev-secret")
JWT_ALGORITHM = "HS256"
def auth_middleware(input_data: RunAgentInput, request: Request):
"""Extract user from JWT and inject into state."""
auth_header = request.headers.get("Authorization", "")
if not auth_header.startswith("Bearer "):
logger.warning("Missing or invalid Authorization header")
# For development, use a default user ID
if input_data.state is None:
input_data.state = {}
input_data.state["__request_context__"] = {
"user": {"id": "anonymous"}
}
yield
return
token = auth_header[7:]
try:
jwt_payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM])
if input_data.state is None:
input_data.state = {}
input_data.state["__request_context__"] = {
"user": {
"id": jwt_payload["sub"],
"jwt": jwt_payload
}
}
logger.info(f"Authenticated user: {jwt_payload['sub']}")
except jwt.InvalidTokenError as e:
logger.error(f"JWT validation failed: {e}")
raise
yield
def logging_middleware(input_data, request):
"""Log request details."""
logger.info(f"Request: {request.url.path}")
logger.info(f"Run ID: {input_data.runId}")
logger.info(f"Thread ID: {input_data.threadId}")
yield
logger.info("Request completed")
def create_agent():
"""Create Coze agent adapter."""
return CozeAgentAdapter(
bot_id=os.getenv("COZE_BOT_ID"),
api_key=os.getenv("COZE_API_KEY"),
debug_mode=os.getenv("DEBUG", "false").lower() == "true"
)
# Create and configure app
app = AgentServiceApp()
app.set_cors_config(allow_origins=["*"])
app.use(logging_middleware)
app.use(auth_middleware)
if __name__ == "__main__":
app.run(
create_agent,
port=int(os.getenv("PORT", "9000")),
host="0.0.0.0"
)
```
## Deployment
### Local Development
```bash
export COZE_BOT_ID=your-bot-id
export COZE_API_KEY=your-api-key
export JWT_SECRET_KEY=your-dev-secret
python app.py
```
### Docker
```dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
ENV PORT=9000
CMD ["python", "app.py"]
```
```bash
docker build -t coze-agent .
docker run -p 9000:9000 \
-e COZE_BOT_ID=your-bot-id \
-e COZE_API_KEY=your-api-key \
-e JWT_SECRET_KEY=your-secret \
coze-agent
```
### CloudRun (Tencent Cloud)
```yaml
# cloudbaserc.json
{
"envId": "your-env-id",
"services": [{
"name": "coze-agent",
"path": "./",
"runtime": "Python3.9",
"port": 9000,
"env": {
"COZE_BOT_ID": "${COZE_BOT_ID}",
"COZE_API_KEY": "${COZE_API_KEY}",
"JWT_SECRET_KEY": "${JWT_SECRET_KEY}"
}
}]
}
```
## Testing
```python
import pytest
from cloudbase_agent.coze import CozeAgentAdapter
from cloudbase_agent.core import RunAgentInput
@pytest.mark.asyncio
async def test_coze_adapter():
"""Test Coze adapter basic flow."""
adapter = CozeAgentAdapter(
bot_id="test-bot",
api_key="test-key"
)
run_input = RunAgentInput(
runId="test-run",
threadId="test-thread",
messages=[{"role": "user", "content": "Hello"}],
state={"__request_context__": {"user": {"id": "test-user"}}}
)
events = []
async for event in adapter.run(run_input):
events.append(event)
# Verify event flow
assert events[0].type == "RUN_STARTED"
assert events[-1].type == "RUN_FINISHED"
```
## Troubleshooting
### "user_id not found" Error
**Problem**: Coze adapter can't find user ID in state.
**Solution**: Ensure auth middleware is registered and sets `state.__request_context__.user.id`:
```python
app.use(auth_middleware) # Register before run()
```
### "Invalid API key" Error
**Problem**: Coze API key is invalid.
**Solution**:
1. Check your Coze API key
2. Verify it's correctly set in environment variables
3. Test with Coze API directly
### Rate Limiting
**Problem**: Hitting Coze API rate limits.
**Solution**: Implement rate limiting middleware:
```python
def rate_limit_middleware(input_data, request):
# Implement rate limiting logic
yield
```
## Examples
See `/python-sdk/examples/coze/` for complete examples.
## Next Steps
- Learn about [authentication](authentication.md)
- Deploy your server: [server-quickstart.md](server-quickstart.md)
- Build UI: [ui-clients.md](ui-clients.md)
references/cloudbase-agent/py/adapter-development.md
# Custom Adapter Development
This guide explains how to build custom AG-UI protocol adapters in Python.
## Overview
An adapter bridges an Agent framework (LangGraph, LangChain, custom logic) to the AG-UI protocol. It translates framework events into standardized AG-UI events that clients can consume.
## AbstractAgent Interface
All adapters must implement the `AbstractAgent` interface:
```python
from typing import Any, AsyncGenerator
from cloudbase_agent.core import RunAgentInput, Event
class AbstractAgent:
"""Abstract base class for all AG-UI protocol adapters."""
async def run(self, run_input: RunAgentInput) -> AsyncGenerator[Event, None]:
"""
Execute the agent and yield AG-UI protocol events.
:param run_input: Input data containing messages, state, tools, etc.
:yields: AG-UI protocol events
"""
raise NotImplementedError
```
## Event Types
AG-UI protocol defines these event types:
```python
from cloudbase_agent.core import EventType
class EventType:
RUN_STARTED = "RUN_STARTED"
RUN_FINISHED = "RUN_FINISHED"
TEXT_MESSAGE_START = "TEXT_MESSAGE_START"
TEXT_MESSAGE_CONTENT = "TEXT_MESSAGE_CONTENT"
TEXT_MESSAGE_END = "TEXT_MESSAGE_END"
TOOL_CALL_START = "TOOL_CALL_START"
TOOL_CALL_ARGS_CHUNK = "TOOL_CALL_ARGS_CHUNK"
TOOL_CALL_END = "TOOL_CALL_END"
TOOL_RESULT = "TOOL_RESULT"
STATE_SNAPSHOT = "STATE_SNAPSHOT"
ERROR = "ERROR"
```
## Minimal Adapter Example
```python
from typing import Any, AsyncGenerator
from cloudbase_agent.core import RunAgentInput, Event, EventType
from uuid import uuid4
class SimpleEchoAgent:
"""Simplest possible adapter - echoes user messages."""
async def run(self, run_input: RunAgentInput) -> AsyncGenerator[Event, None]:
"""Echo back the user's message."""
# 1. Yield RUN_STARTED
yield Event(type=EventType.RUN_STARTED, runId=run_input.runId)
# 2. Get last user message
last_message = run_input.messages[-1] if run_input.messages else None
user_content = last_message.get("content", "") if last_message else ""
# 3. Generate response
message_id = str(uuid4())
response_text = f"Echo: {user_content}"
# 4. Yield TEXT_MESSAGE events
yield Event(
type=EventType.TEXT_MESSAGE_START,
runId=run_input.runId,
messageId=message_id,
role="assistant"
)
yield Event(
type=EventType.TEXT_MESSAGE_CONTENT,
runId=run_input.runId,
messageId=message_id,
content=response_text
)
yield Event(
type=EventType.TEXT_MESSAGE_END,
runId=run_input.runId,
messageId=message_id
)
# 5. Yield RUN_FINISHED
yield Event(type=EventType.RUN_FINISHED, runId=run_input.runId)
```
Deploy it:
```python
from cloudbase_agent.server import AgentServiceApp
AgentServiceApp().run(lambda: SimpleEchoAgent(), port=9000)
```
## Streaming Response Pattern
For LLM streaming responses:
```python
from openai import AsyncOpenAI
class StreamingLLMAgent:
"""Agent with streaming LLM responses."""
def __init__(self, api_key: str):
self.client = AsyncOpenAI(api_key=api_key)
async def run(self, run_input: RunAgentInput) -> AsyncGenerator[Event, None]:
yield Event(type=EventType.RUN_STARTED, runId=run_input.runId)
# Convert messages to OpenAI format
messages = [
{"role": msg["role"], "content": msg["content"]}
for msg in run_input.messages
]
# Stream response
message_id = str(uuid4())
yield Event(
type=EventType.TEXT_MESSAGE_START,
runId=run_input.runId,
messageId=message_id,
role="assistant"
)
stream = await self.client.chat.completions.create(
model="gpt-4",
messages=messages,
stream=True
)
async for chunk in stream:
content = chunk.choices[0].delta.content
if content:
yield Event(
type=EventType.TEXT_MESSAGE_CONTENT,
runId=run_input.runId,
messageId=message_id,
content=content
)
yield Event(
type=EventType.TEXT_MESSAGE_END,
runId=run_input.runId,
messageId=message_id
)
yield Event(type=EventType.RUN_FINISHED, runId=run_input.runId)
```
## Tool Calling Pattern
For agents that call tools:
```python
class ToolCallingAgent:
"""Agent with tool calling support."""
async def run(self, run_input: RunAgentInput) -> AsyncGenerator[Event, None]:
yield Event(type=EventType.RUN_STARTED, runId=run_input.runId)
# Decide to call a tool
tool_call_id = str(uuid4())
tool_name = "get_weather"
tool_args = {"location": "San Francisco"}
# 1. Yield TOOL_CALL_START
yield Event(
type=EventType.TOOL_CALL_START,
runId=run_input.runId,
toolCallId=tool_call_id,
toolName=tool_name
)
# 2. Yield TOOL_CALL_ARGS_CHUNK (can stream args)
import json
args_json = json.dumps(tool_args)
yield Event(
type=EventType.TOOL_CALL_ARGS_CHUNK,
runId=run_input.runId,
toolCallId=tool_call_id,
argsChunk=args_json
)
# 3. Yield TOOL_CALL_END
yield Event(
type=EventType.TOOL_CALL_END,
runId=run_input.runId,
toolCallId=tool_call_id
)
# 4. Execute tool (if server-side tool)
result = await self.execute_tool(tool_name, tool_args)
# 5. Yield TOOL_RESULT
yield Event(
type=EventType.TOOL_RESULT,
runId=run_input.runId,
toolCallId=tool_call_id,
result=result
)
# 6. Continue with response using tool result
# ... (yield TEXT_MESSAGE events)
yield Event(type=EventType.RUN_FINISHED, runId=run_input.runId)
```
## State Snapshot Pattern
For stateful agents:
```python
class StatefulAgent:
"""Agent that maintains and shares state."""
async def run(self, run_input: RunAgentInput) -> AsyncGenerator[Event, None]:
yield Event(type=EventType.RUN_STARTED, runId=run_input.runId)
# Process and update state
current_state = run_input.state or {}
current_state["message_count"] = current_state.get("message_count", 0) + 1
current_state["last_message_time"] = time.time()
# ... (process messages)
# Yield STATE_SNAPSHOT
yield Event(
type=EventType.STATE_SNAPSHOT,
runId=run_input.runId,
snapshot=current_state
)
yield Event(type=EventType.RUN_FINISHED, runId=run_input.runId)
```
## Error Handling Pattern
```python
class RobustAgent:
"""Agent with proper error handling."""
async def run(self, run_input: RunAgentInput) -> AsyncGenerator[Event, None]:
try:
yield Event(type=EventType.RUN_STARTED, runId=run_input.runId)
# Your logic here
# ...
yield Event(type=EventType.RUN_FINISHED, runId=run_input.runId)
except Exception as e:
# Yield ERROR event
yield Event(
type=EventType.ERROR,
runId=run_input.runId,
error={
"code": "AGENT_ERROR",
"message": str(e),
"details": {"traceback": traceback.format_exc()}
}
)
# Still yield RUN_FINISHED
yield Event(type=EventType.RUN_FINISHED, runId=run_input.runId)
```
## Complete Example: Custom Framework Adapter
```python
from typing import Any, AsyncGenerator
from cloudbase_agent.core import RunAgentInput, Event, EventType
from uuid import uuid4
import logging
logger = logging.getLogger(__name__)
class MyCustomFrameworkAgent:
"""
Adapter for a custom agent framework.
This example shows how to integrate any custom agent logic
with the AG-UI protocol.
"""
def __init__(self, config: dict):
"""
Initialize the adapter.
:param config: Configuration for your custom framework
"""
self.config = config
# Initialize your framework here
self.agent = self._initialize_agent()
def _initialize_agent(self):
"""Initialize your custom agent framework."""
# Your framework initialization logic
return CustomFrameworkAgent(self.config)
async def run(self, run_input: RunAgentInput) -> AsyncGenerator[Event, None]:
"""
Execute agent and yield AG-UI protocol events.
:param run_input: Input from AG-UI client
:yields: AG-UI protocol events
"""
try:
# 1. Start
yield Event(type=EventType.RUN_STARTED, runId=run_input.runId)
logger.info(f"Run started: {run_input.runId}")
# 2. Extract input data
messages = run_input.messages
state = run_input.state or {}
tools = run_input.tools or []
# 3. Get user context (if auth middleware is used)
user_id = self._get_user_id(state)
logger.info(f"User: {user_id}")
# 4. Execute your custom framework
message_id = str(uuid4())
# Start message
yield Event(
type=EventType.TEXT_MESSAGE_START,
runId=run_input.runId,
messageId=message_id,
role="assistant"
)
# Your framework's execution (can be streaming)
async for chunk in self.agent.process(messages, state):
# Handle different chunk types
if chunk["type"] == "text":
yield Event(
type=EventType.TEXT_MESSAGE_CONTENT,
runId=run_input.runId,
messageId=message_id,
content=chunk["content"]
)
elif chunk["type"] == "tool_call":
yield Event(
type=EventType.TOOL_CALL_START,
runId=run_input.runId,
toolCallId=chunk["id"],
toolName=chunk["name"]
)
yield Event(
type=EventType.TOOL_CALL_ARGS_CHUNK,
runId=run_input.runId,
toolCallId=chunk["id"],
argsChunk=chunk["args"]
)
yield Event(
type=EventType.TOOL_CALL_END,
runId=run_input.runId,
toolCallId=chunk["id"]
)
elif chunk["type"] == "state_update":
yield Event(
type=EventType.STATE_SNAPSHOT,
runId=run_input.runId,
snapshot=chunk["state"]
)
# End message
yield Event(
type=EventType.TEXT_MESSAGE_END,
runId=run_input.runId,
messageId=message_id
)
# 5. Finish
yield Event(type=EventType.RUN_FINISHED, runId=run_input.runId)
logger.info(f"Run finished: {run_input.runId}")
except Exception as e:
logger.error(f"Error in run: {e}", exc_info=True)
yield Event(
type=EventType.ERROR,
runId=run_input.runId,
error={
"code": "AGENT_ERROR",
"message": str(e)
}
)
yield Event(type=EventType.RUN_FINISHED, runId=run_input.runId)
def _get_user_id(self, state: dict) -> str:
"""Extract user ID from state (set by auth middleware)."""
return state.get("__request_context__", {}).get("user", {}).get("id", "anonymous")
```
## Testing Your Adapter
### Unit Test
```python
import pytest
from cloudbase_agent.core import RunAgentInput
@pytest.mark.asyncio
async def test_adapter_basic_flow():
"""Test basic event flow."""
adapter = MyCustomFrameworkAgent(config={})
run_input = RunAgentInput(
runId="test-run",
threadId="test-thread",
messages=[{"role": "user", "content": "Hello"}]
)
events = []
async for event in adapter.run(run_input):
events.append(event)
# Verify event sequence
assert events[0].type == EventType.RUN_STARTED
assert events[-1].type == EventType.RUN_FINISHED
# Verify message events
message_events = [e for e in events if "MESSAGE" in e.type]
assert len(message_events) >= 3 # START, CONTENT, END
@pytest.mark.asyncio
async def test_adapter_error_handling():
"""Test error handling."""
adapter = MyCustomFrameworkAgent(config={"force_error": True})
run_input = RunAgentInput(
runId="test-error",
threadId="test-thread",
messages=[]
)
events = []
async for event in adapter.run(run_input):
events.append(event)
# Verify ERROR event is emitted
error_events = [e for e in events if e.type == EventType.ERROR]
assert len(error_events) == 1
```
### Integration Test
```python
from fastapi.testclient import TestClient
from cloudbase_agent.server import AgentServiceApp
def test_adapter_via_http():
"""Test adapter through HTTP server."""
app_instance = AgentServiceApp()
fastapi_app = app_instance.build(
create_agent=lambda: MyCustomFrameworkAgent(config={})
)
client = TestClient(fastapi_app)
response = client.post(
"/send-message",
json={
"messages": [{"role": "user", "content": "Hello"}],
"runId": "test-run",
"threadId": "test-thread"
},
headers={"Accept": "text/event-stream"}
)
assert response.status_code == 200
# Parse SSE events
lines = response.text.split("\n")
events = []
for line in lines:
if line.startswith("data: "):
import json
event_data = json.loads(line[6:])
events.append(event_data)
# Verify event flow
assert events[0]["type"] == "RUN_STARTED"
assert events[-1]["type"] == "RUN_FINISHED"
```
## Best Practices
1. **Always yield RUN_STARTED first** - Clients expect this
2. **Always yield RUN_FINISHED last** - Even after errors
3. **Use proper event sequence** - START → CONTENT → END for messages
4. **Handle errors gracefully** - Yield ERROR event, don't raise exceptions
5. **Stream when possible** - Better UX with incremental updates
6. **Log important events** - Helps with debugging
7. **Extract user context** - Use `state.__request_context__.user` if available
8. **Validate input** - Check required fields before processing
9. **Use type hints** - Better IDE support and catch errors early
10. **Write tests** - Both unit and integration tests
## Common Pitfalls
### ❌ Not yielding RUN_STARTED/FINISHED
```python
async def run(self, run_input):
# Missing RUN_STARTED
yield Event(type=EventType.TEXT_MESSAGE_CONTENT, content="Hello")
# Missing RUN_FINISHED
```
### ❌ Raising exceptions instead of ERROR events
```python
async def run(self, run_input):
if error:
raise Exception("Error") # ❌ Breaks SSE stream
```
Should be:
```python
async def run(self, run_input):
if error:
yield Event(type=EventType.ERROR, error={"message": "Error"})
yield Event(type=EventType.RUN_FINISHED)
```
### ❌ Not handling missing state
```python
user_id = run_input.state["__request_context__"]["user"]["id"] # ❌ May crash
```
Should be:
```python
user_id = run_input.state.get("__request_context__", {}).get("user", {}).get("id")
```
## Examples
See `/python-sdk/examples/` for complete examples:
- `langgraph/` - LangGraph adapter patterns
- `langchain/` - LangChain adapter patterns
- `coze/` - Third-party API integration
## Next Steps
- Deploy your adapter: [server-quickstart.md](server-quickstart.md)
- Understand protocol details: [agui-protocol.md](agui-protocol.md)
- Add authentication: [authentication.md](authentication.md)
- Build UI: [ui-clients.md](ui-clients.md)
references/cloudbase-agent/py/adapter-langgraph.md
# LangGraph Adapter Guide
Complete guide for integrating LangGraph agents with CloudBase Agent Python SDK.
---
## Overview
The CloudBase Agent LangGraph adapter (`cloudbase_agent.langgraph`) provides seamless integration with LangGraph workflows, offering:
- **Native LangGraph Support**: Wrap any `CompiledStateGraph` as an CloudBase Agent agent
- **AG-UI Compatibility**: Automatic stability patches for frontend integration
- **Streaming Support**: Real-time message streaming to clients
- **Memory Persistence**: LangGraph checkpoint support for conversation history
- **Callback System**: Monitor agent events in real-time
- **Resource Cleanup**: Automatic cleanup after request completion
---
## Quick Start
### 1. Install Dependencies
```bash
pip install cloudbase-agent-langgraph cloudbase-agent-server langgraph langchain-openai
```
This installs:
- `cloudbase-agent-langgraph` - LangGraph adapter
- `langgraph` - LangGraph framework
- `langchain` - LangChain core
- `langchain-openai` - OpenAI integration
### 2. Create Your First Agent
```python
# agent.py
from langgraph.graph import StateGraph, MessagesState, END, START
from langgraph.checkpoint.memory import MemorySaver
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage
from cloudbase_agent.langgraph import LangGraphAgent
# Define state
class State(MessagesState):
pass
# Define chat node
def chat_node(state: State, config, writer):
"""Generate AI response."""
chat_model = ChatOpenAI(model="gpt-4o-mini")
system = SystemMessage(content="You are a helpful assistant.")
messages = [system, *state["messages"]]
chunks = []
for chunk in chat_model.stream(messages, config):
writer({"messages": [chunk]}) # Stream to client
chunks.append(chunk)
return {"messages": chunks}
# Build workflow
def build_workflow():
graph = StateGraph(State)
graph.add_node("chat", chat_node)
graph.add_edge(START, "chat")
graph.add_edge("chat", END)
memory = MemorySaver()
return graph.compile(checkpointer=memory)
# Wrap with CloudBase Agent
agent = LangGraphAgent(
name="chatbot",
description="A helpful conversational assistant",
graph=build_workflow()
)
```
### 3. Deploy as HTTP Service
```python
# server.py
from cloudbase_agent.server import AgentServiceApp
AgentServiceApp().run(
lambda: {"agent": agent},
port=9000,
enable_openai_endpoint=True
)
```
---
## LangGraphAgent Configuration
### Basic Configuration
```python
from cloudbase_agent.langgraph import LangGraphAgent
agent = LangGraphAgent(
name="my-agent", # Required: Agent identifier
description="Agent description", # Optional: For documentation
graph=build_workflow(), # Required: CompiledStateGraph
use_callbacks=True, # Optional: Enable callback system (default: False)
)
```
### Advanced Configuration
```python
agent = LangGraphAgent(
name="advanced-agent",
description="Advanced agent with full configuration",
graph=compiled_graph,
use_callbacks=True,
# Add callbacks
callbacks=[ConsoleLogger(), MetricsCollector()],
# Add tool proxy for permission control
tool_proxy=permission_checker,
)
# Add callbacks dynamically
agent.add_callback(DatabaseLogger())
```
---
## State Management
### Basic MessagesState
```python
from langgraph.graph import MessagesState
class State(MessagesState):
"""Simplest state - just conversation history."""
pass
```
### Extended State with Tools
```python
from langgraph.graph import MessagesState
from typing import List, Any
class State(MessagesState):
"""State with tool support."""
tools: List[Any] # Available tools
```
### Custom State Fields
```python
from langgraph.graph import MessagesState
from typing import Optional
class State(MessagesState):
"""State with custom fields."""
user_id: str # User identifier
context: Optional[dict] # Additional context
preference: str # User preferences
```
---
## Streaming Response
### StreamWriter Pattern
LangGraph nodes receive a `writer` parameter for streaming:
```python
from langgraph.types import StreamWriter
def chat_node(state: State, config, writer: StreamWriter):
"""Node with streaming support."""
chat_model = ChatOpenAI(model="gpt-4o-mini")
chunks = []
for chunk in chat_model.stream(messages, config):
# Stream chunk to client immediately
writer({"messages": [chunk]})
# Collect for final state
chunks.append(chunk)
# Return collected chunks for state
return {"messages": chunks}
```
### Handling Missing Writer
```python
def chat_node(state: State, config, writer: StreamWriter = None):
"""Node with fallback for missing writer."""
# Provide no-op fallback
if writer is None:
def writer(x):
pass
# Use writer safely
for chunk in chat_model.stream(messages):
writer({"messages": [chunk]})
```
---
## Memory & Checkpointing
### In-Memory Checkpointer
For development and testing:
```python
from langgraph.checkpoint.memory import MemorySaver
def build_workflow():
graph = StateGraph(State)
# ... add nodes and edges ...
memory = MemorySaver() # In-memory storage
return graph.compile(checkpointer=memory)
```
### Using Conversation ID
```bash
# Each conversation gets unique thread_id
curl -X POST http://localhost:9000/send-message \
-H "Content-Type: application/json" \
-d '{
"conversationId": "user_123_conv_456",
"messages": [{"role": "user", "content": "Hello!"}]
}'
```
The `conversationId` is automatically mapped to LangGraph's `thread_id` for checkpoint retrieval.
### Persistent Checkpointer
For production with PostgreSQL:
```python
from langgraph.checkpoint.postgres import PostgresSaver
# Create PostgreSQL checkpointer
checkpointer = PostgresSaver.from_conn_string(
"postgresql://user:pass@localhost/dbname"
)
def build_workflow():
graph = StateGraph(State)
# ... add nodes and edges ...
return graph.compile(checkpointer=checkpointer)
```
---
## Tool Integration
### Defining Tools
```python
from typing import List, Any
from langchain_core.utils.function_calling import convert_to_openai_function
class State(MessagesState):
tools: List[Any]
def chat_node(state: State, config, writer):
chat_model = ChatOpenAI(model="gpt-4o-mini")
# Get and bind tools
tools = state.get("tools", [])
if tools:
# Convert tool definitions to OpenAI format
tools_list = [convert_to_openai_function(tool) for tool in tools]
chat_model = chat_model.bind_tools(tools_list)
# Use model with tools
for chunk in chat_model.stream(messages, config):
writer({"messages": [chunk]})
```
### Providing Tools via API
```bash
curl -X POST http://localhost:9000/send-message \
-H "Content-Type: application/json" \
-d '{
"conversationId": "conv_123",
"messages": [{"role": "user", "content": "Search the web"}],
"tools": [
{
"name": "search_web",
"description": "Search the internet",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string"}
},
"required": ["query"]
}
}
]
}'
```
---
## Callbacks
### Built-in Callback Interface
```python
class MyCallback:
"""Custom callback for monitoring."""
async def on_text_message_content(self, event, buffer):
"""Called when text message content is streaming."""
print(f"AI: {buffer}")
async def on_tool_call_args(self, event, buffer, partial_args):
"""Called when tool call arguments are parsed."""
tool_name = getattr(event, "tool_name", "unknown")
print(f"Tool: {tool_name}, Args: {partial_args}")
async def on_run_started(self, event):
"""Called when agent run starts."""
print(f"Started: {event.run_id}")
async def on_run_finished(self, event):
"""Called when agent run finishes."""
print(f"Finished: {event.run_id}")
async def on_run_error(self, event):
"""Called when an error occurs."""
print(f"Error: {getattr(event, 'message', 'Unknown')}")
```
### Adding Callbacks
```python
# Method 1: During agent creation
agent = LangGraphAgent(
name="my-agent",
graph=workflow,
use_callbacks=True,
callbacks=[MyCallback()]
)
# Method 2: After creation
agent.add_callback(MyCallback())
```
---
## Error Handling
### AG-UI Protocol Errors
CloudBase Agent automatically converts exceptions to AG-UI error events:
```python
def chat_node(state: State, config, writer):
try:
# Your logic here
result = dangerous_operation()
return {"messages": [result]}
except Exception as e:
# Error is automatically formatted as AG-UI error event
from langchain_core.messages import AIMessage
return {"messages": [AIMessage(content=f"Error: {str(e)}")]}
```
### Custom Error Handling
```python
from cloudbase_agent.server.errors import install_exception_handlers
from fastapi import FastAPI
app = FastAPI()
# Install AG-UI error handlers
install_exception_handlers(app)
# Now all exceptions are converted to AG-UI error events
```
---
## Complete Example: Human-in-the-Loop
```python
#!/usr/bin/env python3
from typing import Optional
from langgraph.graph import StateGraph, MessagesState, END, START
from langgraph.checkpoint.memory import MemorySaver
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, AIMessage
from cloudbase_agent.langgraph import LangGraphAgent
class State(MessagesState):
"""State for human-in-the-loop workflow."""
pending_approval: Optional[dict] = None
def chat_node(state: State, config, writer):
"""Generate AI response."""
chat_model = ChatOpenAI(model="gpt-4o-mini")
system = SystemMessage(content="You are a helpful assistant.")
messages = [system, *state["messages"]]
chunks = []
for chunk in chat_model.stream(messages, config):
writer({"messages": [chunk]})
chunks.append(chunk)
# Check if approval is needed
final_message = chunks[-1] if chunks else AIMessage(content="")
if "sensitive" in final_message.content.lower():
return {
"messages": chunks,
"pending_approval": {
"action": "send_message",
"content": final_message.content
}
}
return {"messages": chunks}
def approval_node(state: State, config, writer):
"""Wait for human approval."""
if state.get("pending_approval"):
writer({
"messages": [AIMessage(
content="This action requires approval. Please approve or reject."
)]
})
# Workflow will interrupt here for human input
return state
return state
def should_wait_approval(state: State) -> str:
"""Decide if approval is needed."""
if state.get("pending_approval"):
return "approval"
return END
def build_workflow():
"""Build human-in-the-loop workflow."""
graph = StateGraph(State)
graph.add_node("chat", chat_node)
graph.add_node("approval", approval_node)
graph.add_edge(START, "chat")
graph.add_conditional_edges(
"chat",
should_wait_approval,
{
"approval": "approval",
END: END
}
)
memory = MemorySaver()
return graph.compile(
checkpointer=memory,
interrupt_before=["approval"] # Pause before approval
)
# Create agent
agent = LangGraphAgent(
name="human-in-the-loop",
description="Agent with human approval workflow",
graph=build_workflow(),
use_callbacks=True
)
# Deploy
if __name__ == "__main__":
from cloudbase_agent.server import AgentServiceApp
AgentServiceApp().run(
lambda: {"agent": agent},
port=9000
)
```
---
## Best Practices
### 1. Always Use MemorySaver
```python
# ✅ Correct: With memory
memory = MemorySaver()
workflow = graph.compile(checkpointer=memory)
# ❌ Wrong: No memory - conversations won't persist
workflow = graph.compile()
```
### 2. Stream Immediately
```python
# ✅ Correct: Stream as you generate
for chunk in model.stream(messages):
writer({"messages": [chunk]}) # Immediate streaming
chunks.append(chunk)
# ❌ Wrong: Collect first, then stream - defeats streaming purpose
chunks = list(model.stream(messages))
for chunk in chunks:
writer({"messages": [chunk]})
```
### 3. Handle Missing Writer
```python
# ✅ Correct: Fallback for testing
def chat_node(state, config, writer=None):
if writer is None:
writer = lambda x: None
# Use writer safely
writer({"messages": [chunk]})
# ❌ Wrong: Assume writer always exists
def chat_node(state, config, writer):
writer({"messages": [chunk]}) # Fails in tests
```
### 4. Use Environment Variables
```python
# .env
OPENAI_API_KEY=sk-...
OPENAI_MODEL=gpt-4o-mini
OPENAI_TEMPERATURE=0.7
# Load in code
from dotenv import load_dotenv
load_dotenv()
# Use in node
import os
chat_model = ChatOpenAI(
model=os.getenv("OPENAI_MODEL", "gpt-4o-mini"),
api_key=os.getenv("OPENAI_API_KEY"),
temperature=float(os.getenv("OPENAI_TEMPERATURE", "0.7"))
)
```
---
## Troubleshooting
### Issue: Conversation history not persisting
**Solution**: Ensure you're using a checkpointer:
```python
from langgraph.checkpoint.memory import MemorySaver
memory = MemorySaver()
workflow = graph.compile(checkpointer=memory)
```
### Issue: Streaming not working
**Solution**: Make sure you're calling `writer()` with proper format:
```python
# ✅ Correct format
writer({"messages": [chunk]})
# ❌ Wrong format
writer(chunk) # Missing dict wrapper
```
### Issue: Tool calls not working
**Solution**: Ensure tools are in state and properly bound:
```python
class State(MessagesState):
tools: List[Any] # Add tools field
def chat_node(state, config, writer):
tools = state.get("tools", [])
if tools:
tools_list = [convert_to_openai_function(t) for t in tools]
model = model.bind_tools(tools_list)
```
---
## Next Steps
- **Server Deployment**: See `server-quickstart.md` for server configuration
- **Authentication**: See `authentication.md` for auth patterns
- **Observability**: Enable with `export AUTO_TRACES_STDOUT=true`
- **Examples**: Check `python-sdk/examples/langgraph/` for complete working examples
references/cloudbase-agent/py/agent-deployment.md
# Agent Deployment Guide
## Core Principle
**Always use the `manageAgent` MCP tool to deploy Agent services.**
It natively supports SSE streaming, session persistence, and Python 3.10 runtime — purpose-built for Agent scenarios.
Do **NOT** use `createFunction` or `manageCloudRun` for Agent deployment.
## Why HTTP Cloud Functions First
| Dimension | HTTP Cloud Functions | CloudRun |
|-----------|---------------------|----------|
| SSE Streaming | ✅ Native support | ✅ Supported |
| WebSocket | ✅ Native support | ✅ Supported |
| Deployment Complexity | Low (no Dockerfile needed) | High (container config required) |
| Cost | Pay-per-invocation, scales to zero | Pay-per-instance-hour |
| Cold Start | Yes, mitigated with provisioned instances | Yes, mitigated with min instances |
| Supported Runtimes | Node.js, Python | Any |
---
## ⚠️ DEPLOYMENT IS A 4-STEP BLOCKING PIPELINE
<!-- INSTRUCTION (AI MUST EXECUTE EVERY STEP IN ORDER) -->
> **AI: You MUST execute Steps 1→2→3→4 in order. Do NOT skip any step.**
> **Do NOT call `manageAgent` until Step 3 (Verification) passes ALL checks.**
```
Step 1: Ensure Python 3.10 ──▶ Step 2: Build env/ ──▶ Step 3: Verify ──▶ Step 4: Deploy
(BLOCKING) (BLOCKING) (BLOCKING) (final)
```
---
## Step 1: Ensure Python 3.10
<!-- INSTRUCTION (AI MUST EXECUTE) -->
Execute one of the following to ensure Python 3.10 is available:
```bash
# Check if python3.10 already exists
python3.10 --version 2>/dev/null || python3 --version
# If NOT 3.10.x, install it:
# Option A: brew (macOS)
brew install python@3.10
# Option B: pyenv
pyenv install 3.10
pyenv local 3.10
```
**Verification (REQUIRED before proceeding):**
```bash
python3.10 --version
# MUST output: Python 3.10.x
# If it shows 3.11+ or 3.9-, STOP and fix before continuing.
```
<details>
<summary>Why must it be Python 3.10? (background info, AI may skip)</summary>
pip's `--python-version` flag correctly selects wheel files for the target version, but it does **NOT** reliably evaluate environment markers (e.g., `exceptiongroup; python_version < "3.11"`) — it may use the **host interpreter's version** instead of the target version. This causes conditional dependencies like `exceptiongroup` (required by `anyio` on Python < 3.11) to be silently skipped, leading to `ModuleNotFoundError` at runtime on the cloud (which runs Python 3.10).
</details>
---
## Step 2: Build env/ (One-Shot Install)
<!-- INSTRUCTION (AI MUST EXECUTE) -->
> ### ⚠️ CRITICAL: `env/` is an immutable build artifact
>
> The ONLY correct workflow is:
> 1. **Delete** → `rm -rf ./env`
> 2. **Install** → single `pip install` command (below)
> 3. **Never touch again**
>
> **NEVER** run a second `pip install` into `env/`. **NEVER** manually copy/move/delete files inside `env/`.
> If you need to change anything, edit `requirements.txt` and redo steps 1–2 from scratch.
**Execute this script as-is. Do NOT break it into separate steps. Do NOT modify any line.**
```bash
#!/bin/bash
set -euo pipefail
PROJECT_DIR="$(pwd)"
# ── Detect Python 3.10 ──
PYTHON_BIN=""
if command -v python3.10 &>/dev/null; then
PYTHON_BIN="python3.10"
elif python3 --version 2>&1 | grep -q "3\.10\."; then
PYTHON_BIN="python3"
else
echo "❌ ERROR: Python 3.10 not found. Run Step 1 first."
exit 1
fi
echo "✅ Using: $PYTHON_BIN ($($PYTHON_BIN --version 2>&1))"
# ── Atomic env/ rebuild ──
rm -rf ./env && mkdir ./env
# ── One-shot install ALL deps ──
$PYTHON_BIN -m pip install -r ./requirements.txt \
--platform manylinux2014_x86_64 \
--target ./env \
--python-version 3.10 \
--only-binary=:all: \
--upgrade
echo "✅ env/ built successfully"
```
If `pip install` reports **any** errors, **STOP** and resolve the error first. Do NOT ignore errors and proceed to deploy — the resulting `env/` will be incomplete.
---
## Step 3: Verify env/ Integrity (MANDATORY)
<!-- INSTRUCTION (AI MUST EXECUTE) -->
> **Do NOT call `manageAgent` until ALL checks below pass.**
> **If ANY check fails, the ONLY fix is: edit requirements.txt → rm -rf env/ → re-run Step 2.**
### 3a. Verify all top-level packages are present
```bash
# List all packages from requirements.txt and verify they exist in env/
# This works for ANY framework — no hardcoded package names
python3.10 -c "
import subprocess, sys, os
# Read requirements.txt
with open('requirements.txt') as f:
reqs = [line.strip().split('==')[0].split('>=')[0].split('<=')[0].split('~=')[0].split('[')[0].strip()
for line in f if line.strip() and not line.startswith('#') and not line.startswith('-')]
# For each requirement, check if it's importable from env/
env_path = os.path.abspath('./env')
failed = []
for req in reqs:
# Convert package name to import name (hyphens → underscores)
import_name = req.replace('-', '_').lower()
# Check if directory or .py file exists
found = (os.path.isdir(os.path.join(env_path, import_name)) or
os.path.isfile(os.path.join(env_path, import_name + '.py')) or
os.path.isfile(os.path.join(env_path, import_name + '.so')))
if not found:
# Some packages have different import names, try dist-info
dist_matches = [d for d in os.listdir(env_path)
if d.endswith('.dist-info') and req.replace('-','_').lower() in d.lower()]
if dist_matches:
found = True
if not found:
failed.append(f'{req} (expected: {import_name})')
else:
print(f' ✅ {req}')
if failed:
print()
for f in failed:
print(f' ❌ MISSING: {f}')
print()
print('Fix: Check requirements.txt spelling, then rm -rf env/ and re-run Step 2')
sys.exit(1)
else:
print()
print('✅ All packages verified in env/')
"
```
### 3b. Verify entry point imports work
```bash
# Dynamically test that the project's main entry file can resolve imports
# Replace 'server.py' with whatever file the project uses as entry point
PYTHONPATH=./env python3.10 -c "
import sys, ast, os
# Find entry point (server.py or main.py)
entry = None
for candidate in ['server.py', 'main.py', 'app.py']:
if os.path.isfile(candidate):
entry = candidate
break
if not entry:
print('⚠️ No standard entry file found (server.py/main.py/app.py). Skipping import check.')
sys.exit(0)
print(f'Checking imports from {entry}...')
# Parse and extract top-level imports
with open(entry) as f:
tree = ast.parse(f.read())
modules = set()
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for alias in node.names:
modules.add(alias.name.split('.')[0])
elif isinstance(node, ast.ImportFrom) and node.module:
modules.add(node.module.split('.')[0])
# Filter to non-stdlib, non-relative modules
import importlib.util
failed = []
for mod in sorted(modules):
if mod.startswith('_') or mod in ('os', 'sys', 'json', 'logging', 'typing', 'datetime', 'pathlib', 'asyncio', 'abc', 'enum', 'dataclasses', 'collections', 'functools', 'importlib', 'contextlib', 'inspect', 'traceback', 're', 'io', 'copy', 'math', 'time', 'uuid', 'hashlib', 'base64', 'urllib', 'http', 'socket', 'subprocess', 'platform', 'struct', 'itertools', 'operator', 'warnings', 'signal', 'threading', 'multiprocessing', 'concurrent', 'queue', 'pickle', 'shelve', 'tempfile', 'shutil', 'glob', 'fnmatch', 'string', 'textwrap', 'codecs', 'csv', 'configparser', 'argparse', 'getpass', 'secrets', 'hmac', 'ssl', 'email', 'html', 'xml', 'pprint'):
continue
spec = importlib.util.find_spec(mod)
if spec:
print(f' ✅ {mod}')
else:
failed.append(mod)
print(f' ❌ {mod}')
if failed:
print(f'\n❌ Import verification failed for: {failed}')
print('Fix: Ensure these are in requirements.txt, then rm -rf env/ and re-run Step 2')
sys.exit(1)
else:
print('\n✅ All imports verified')
"
```
### 3c. Verify scf_bootstrap
```bash
# Check scf_bootstrap exists, is executable, and sets PYTHONPATH
test -f ./scf_bootstrap || { echo "❌ scf_bootstrap not found"; exit 1; }
test -x ./scf_bootstrap || { echo "❌ scf_bootstrap not executable. Run: chmod +x scf_bootstrap"; exit 1; }
grep -q 'PYTHONPATH.*env' ./scf_bootstrap || { echo "❌ scf_bootstrap missing PYTHONPATH=./env"; exit 1; }
echo "✅ scf_bootstrap OK"
```
**All 3 checks passed? → Proceed to Step 4.**
---
## Step 4: Deploy with manageAgent
```
manageAgent(action="create", runtime="Python3.10", installDependency=false, targetPath="...")
```
**IMPORTANT**: Do NOT add `env/` to the `ignore` list — it must be uploaded with the code.
---
## Error Recovery Playbook
> **Golden Rule: ANY problem with `env/` has exactly ONE fix:**
> ```
> edit requirements.txt (if needed) → rm -rf env/ → re-run Step 2 script → re-run Step 3
> ```
> **There is NO other fix. Never deviate from this.**
### Error: `pip install` reports "no matching distribution"
- **Cause**: A package doesn't have a `manylinux2014_x86_64` wheel for Python 3.10
- **Fix**: Pin a version in `requirements.txt` that has a compatible wheel, or check spelling
- **Then**: `rm -rf env/` → re-run Step 2
### Error: `ModuleNotFoundError` at runtime (ANY module)
- **Cause 1**: The module is missing from `requirements.txt` → add it
- **Cause 2**: `env/` was built with Python 3.11+ → ensure Python 3.10, rebuild
- **Cause 3**: `env/` was built incrementally (multiple pip installs) → rebuild atomically
- **Fix**: `rm -rf env/` → re-run Step 2
### Error: Namespace package submodule missing (e.g., `cloudbase_agent.xxx`)
- **Cause**: Multiple `pip install` commands into `env/` caused namespace package fragmentation
- **Fix**: `rm -rf env/` → re-run Step 2 (single command installs all packages atomically)
### ⛔ PROHIBITED OPERATIONS (will cause deployment failures)
- ⛔ Running a second `pip install` into an existing `env/`
- ⛔ Copying files from another project directory into `env/`
- ⛔ Manually creating or modifying `__init__.py` inside `env/`
- ⛔ Deleting selective directories inside `env/` and reinstalling partial deps
- ⛔ Using `pip install` inside `scf_bootstrap` (wastes cold-start time)
---
## Python Runtime Version
**Always select Python 3.10 runtime** (`runtime="Python3.10"`). This is the recommended version for CloudBase Agent Python SDK because:
- Full compatibility with all `cloudbase-agent-*` packages
- Best performance for async/await patterns used by FastAPI
- Stable and well-tested on the CloudBase platform
Do **NOT** use Python 3.9 or earlier — many SDK features require Python >= 3.10.
## Code Adaptation Notes
### Port Listening
Your server **must** listen on the port from environment variable `SCF_RUNTIME_PORT`:
```python
import os
from cloudbase_agent.server import AgentServiceApp
port = int(os.environ.get("SCF_RUNTIME_PORT", "9000"))
AgentServiceApp().run(create_agent, port=port, host="0.0.0.0")
```
### Startup Script
The startup script must be named `scf_bootstrap` (no file extension), placed in the project root, and have executable permissions:
```bash
#!/bin/bash
export PYTHONPATH="./env:$PYTHONPATH"
/var/lang/python310/bin/python3 -u server.py
```
Make it executable:
```bash
chmod +x scf_bootstrap
```
### CORS Configuration
Ensure CORS is properly configured for cross-origin requests:
```python
from cloudbase_agent.server import AgentServiceApp
app = AgentServiceApp()
app.set_cors_config(allow_origins=["*"])
app.run(create_agent, port=port)
```
Or if using FastAPI directly:
```python
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
```
## Complete Deployment Example
### Project Structure
```
my-agent/
├── agents/
│ └── chat/agent.py # Agent workflow (any framework)
├── env/ # Pre-installed dependencies (built by Step 2)
├── server.py # Main entry point
├── scf_bootstrap # CloudBase startup script
├── requirements.txt # Dependencies
└── .env # Environment variables (local only)
```
### scf_bootstrap
```bash
#!/bin/bash
export PYTHONPATH="./env:$PYTHONPATH"
/var/lang/python310/bin/python3 -u server.py
```
### requirements.txt (example — varies by framework)
```
# Core (always needed)
cloudbase-agent-server
python-dotenv
# Framework adapter (pick ONE based on your choice)
cloudbase-agent-langgraph # For LangGraph-based agents
# cloudbase-agent-crewai # For CrewAI-based agents
# cloudbase-agent-coze # For Coze platform agents
# LLM provider (example)
langchain-openai
```
## When to Use CloudRun Instead
Despite HTTP Cloud Functions being preferred, use CloudRun in these cases:
- Custom Docker image required (special system-level dependencies like FFmpeg, Chromium, etc.)
- Resource requirements exceed Cloud Function limits
- Persistent local file storage needed
- Need to install native C extensions that require specific OS packages
For CloudRun deployment, use a Dockerfile with Python 3.11:
```dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
ENV PORT=9000
CMD ["python", "server.py"]
```
## Summary
| Decision | Choice |
|----------|--------|
| **Deployment tool** | `manageAgent` MCP tool (MUST USE) |
| **Python runtime** | Python 3.10 (MUST USE, `runtime="Python3.10"`) |
| **Dependency strategy** | Local pre-packaging to `./env` (**MUST use Python 3.10 interpreter**, `installDependency=false`) |
| **Build workflow** | Step 1 (Python) → Step 2 (build env/) → Step 3 (verify) → Step 4 (deploy) |
| **env/ rebuild rule** | ALWAYS atomic: `rm -rf env/` → single `pip install` — NEVER incremental |
| **Default platform** | HTTP Cloud Functions |
| **Fallback platform** | CloudRun (only for special requirements) |
| **Startup script** | `scf_bootstrap` — set `PYTHONPATH="./env:$PYTHONPATH"`, do NOT `pip install` at startup |
| **Port** | Read from `SCF_RUNTIME_PORT` env var |
references/cloudbase-agent/py/authentication.md
# Authentication and User Context
This guide explains how to implement authentication and manage user context in CloudBase Agent Python SDK using the framework's reserved fields pattern.
## Overview
CloudBase Agent uses a standardized approach for passing user context through the request lifecycle:
```
HTTP Request (JWT in header)
↓ (middleware extracts)
state["__request_context__"]["user"]["id"]
state["__request_context__"]["user"]["jwt"]
↓ (available to)
Agent / Adapter / Tools
```
## Framework Reserved Fields
CloudBase Agent reserves specific fields in `state` for user authentication:
| Field | Type | Description | Access |
|-------|------|-------------|--------|
| `state["__request_context__"]["user"]["id"]` | `str` | User identifier | Read-only (set by middleware) |
| `state["__request_context__"]["user"]["jwt"]` | `dict` | JWT payload | Read-only (set by middleware) |
**⚠️ Security Warning**: These fields are set by authentication middleware and should be treated as read-only. Modifying them in your agent logic may lead to security vulnerabilities.
## Implementation Pattern
### 1. Authentication Middleware (Write)
Middleware extracts user information from the request and injects it into `state`:
```python
import jwt
from fastapi import Request
from cloudbase_agent.server.send_message.models import RunAgentInput
from typing import Generator
def auth_middleware(
input_data: RunAgentInput,
request: Request
) -> Generator[None, None, None]:
"""
Extract user from JWT and inject into state.
This middleware:
1. Extracts JWT from Authorization header
2. Verifies the token
3. Injects user info into framework reserved fields
"""
# Extract token
auth_header = request.headers.get("Authorization", "")
token = auth_header.replace("Bearer ", "")
if token:
try:
# Verify JWT (use your own secret and algorithm)
jwt_payload = jwt.decode(
token,
"your-secret-key",
algorithms=["HS256"]
)
# Initialize state if needed
if input_data.state is None:
input_data.state = {}
# ✅ Inject into framework reserved fields
input_data.state["__request_context__"] = {
"user": {
"id": jwt_payload["sub"], # User ID from JWT sub claim
"jwt": jwt_payload # Full JWT payload
}
}
except jwt.InvalidTokenError as e:
# Handle invalid token (log but don't block in this example)
print(f"Invalid JWT token: {e}")
# You could raise an exception here to block the request
# raise InvalidRequestError(message="Invalid authentication token")
yield # Continue to next middleware or agent
```
### 2. Register Middleware
```python
from cloudbase_agent.server import AgentServiceApp
app = AgentServiceApp()
app.use(auth_middleware) # Register before run()
app.run(create_agent, port=9000)
```
### 3. Adapter/Agent (Read)
In your adapter or agent, read the user information:
```python
def get_user_id_from_state(state: dict) -> str:
"""
Safely extract user ID from framework reserved field.
:param state: Agent state dictionary
:return: User ID string
:raises ValueError: If user ID not found
"""
request_context = state.get("__request_context__", {})
user = request_context.get("user", {})
user_id = user.get("id")
if not user_id:
raise ValueError(
"user_id is required but not found in "
"state.__request_context__.user.id. "
"Please ensure auth middleware is registered."
)
return user_id
# Usage in agent
def my_agent_function(state: dict):
user_id = get_user_id_from_state(state)
jwt_payload = state.get("__request_context__", {}).get("user", {}).get("jwt", {})
# Use user_id and jwt_payload for your logic
user_data = fetch_user_data(user_id)
# ...
```
## Example: Complete Authentication Flow
### Step 1: Define Authentication Middleware
```python
# auth.py
import jwt
from fastapi import Request, HTTPException
from cloudbase_agent.server.send_message.models import RunAgentInput
from cloudbase_agent.server.errors.exceptions import InvalidRequestError
from typing import Generator
# Your JWT configuration
JWT_SECRET = "your-secret-key"
JWT_ALGORITHM = "HS256"
def jwt_auth_middleware(
input_data: RunAgentInput,
request: Request
) -> Generator[None, None, None]:
"""
JWT authentication middleware.
Extracts and verifies JWT, then injects user info into state.
Raises error if token is invalid or missing for protected routes.
"""
# Extract Authorization header
auth_header = request.headers.get("Authorization", "")
if not auth_header:
raise InvalidRequestError(
message="Missing Authorization header",
details={"header": "Authorization"}
)
# Parse Bearer token
if not auth_header.startswith("Bearer "):
raise InvalidRequestError(
message="Invalid Authorization header format. Expected 'Bearer <token>'",
details={"format": "Bearer <token>"}
)
token = auth_header[7:] # Remove "Bearer " prefix
try:
# Verify and decode JWT
jwt_payload = jwt.decode(
token,
JWT_SECRET,
algorithms=[JWT_ALGORITHM]
)
# Validate required claims
if "sub" not in jwt_payload:
raise InvalidRequestError(
message="JWT missing 'sub' claim",
details={"claim": "sub"}
)
# Initialize state if needed
if input_data.state is None:
input_data.state = {}
# Inject user info into framework reserved fields
input_data.state["__request_context__"] = {
"user": {
"id": jwt_payload["sub"],
"jwt": jwt_payload
}
}
# Log successful authentication (optional)
print(f"Authenticated user: {jwt_payload['sub']}")
except jwt.ExpiredSignatureError:
raise InvalidRequestError(
message="JWT token has expired",
details={"error": "expired"}
)
except jwt.InvalidTokenError as e:
raise InvalidRequestError(
message=f"Invalid JWT token: {str(e)}",
details={"error": "invalid_token"}
)
yield # Continue to agent execution
```
### Step 2: Use in Coze Adapter
```python
# agent.py
from cloudbase_agent.coze import CozeAgentAdapter
from cloudbase_agent.server import AgentServiceApp
from auth import jwt_auth_middleware
def create_agent():
"""
Create Coze agent.
User ID will be automatically extracted from state by the adapter.
"""
return CozeAgentAdapter(
bot_id="your-bot-id",
api_key="your-api-key"
)
# Start server with auth middleware
app = AgentServiceApp()
app.use(jwt_auth_middleware) # Register auth middleware
app.run(create_agent, port=9000)
```
### Step 3: Coze Adapter Internal Logic
The Coze adapter reads user ID automatically:
```python
# Inside cloudbase_agent.coze.agent.py (framework code)
class CozeAgentAdapter:
def _get_user_id(self, run_input: RunAgentInput) -> str:
"""Get user_id from state.__request_context__.user.id."""
state = run_input.state or {}
# Read from framework reserved field
request_context = state.get("__request_context__", {})
user_info = request_context.get("user", {})
user_id = user_info.get("id")
if not user_id:
raise ValueError(
"user_id is required but not found in "
"state.__request_context__.user.id. "
"Please ensure auth middleware is registered."
)
return user_id.strip()
```
## Custom User Context Fields
You can add custom fields alongside framework reserved fields:
```python
def auth_middleware(input_data: RunAgentInput, request: Request):
"""Auth middleware with custom fields."""
# Verify JWT
jwt_payload = verify_jwt(extract_token(request))
# Initialize state
if input_data.state is None:
input_data.state = {}
# Set framework reserved fields + custom fields
input_data.state["__request_context__"] = {
"user": {
"id": jwt_payload["sub"], # ← Framework reserved
"jwt": jwt_payload, # ← Framework reserved
},
# ✅ Custom fields (allowed)
"tenant_id": jwt_payload.get("tenant_id"),
"permissions": jwt_payload.get("permissions", []),
"session_id": request.headers.get("X-Session-ID"),
}
yield
# Usage in agent
def my_agent(state: dict):
# Read framework reserved fields
user_id = state["__request_context__"]["user"]["id"]
# Read custom fields
tenant_id = state["__request_context__"].get("tenant_id")
permissions = state["__request_context__"].get("permissions", [])
if "admin" not in permissions:
raise PermissionError("Admin permission required")
```
## Security Best Practices
### 1. Use Strong Secrets
```python
import os
JWT_SECRET = os.environ.get("JWT_SECRET_KEY")
if not JWT_SECRET or len(JWT_SECRET) < 32:
raise ValueError("JWT_SECRET_KEY must be at least 32 characters")
```
### 2. Validate All Claims
```python
def validate_jwt_payload(payload: dict) -> None:
"""Validate JWT payload structure."""
required_claims = ["sub", "exp", "iat"]
for claim in required_claims:
if claim not in payload:
raise ValueError(f"Missing required claim: {claim}")
# Validate expiration (PyJWT does this automatically, but double-check)
import time
if payload["exp"] < time.time():
raise ValueError("Token expired")
```
### 3. Implement Token Refresh
```python
def refresh_token_middleware(input_data, request):
"""Check token expiration and handle refresh."""
jwt_payload = input_data.state.get("__request_context__", {}).get("user", {}).get("jwt", {})
# Check if token expires soon (e.g., within 5 minutes)
if jwt_payload.get("exp", 0) - time.time() < 300:
# Add header to response suggesting refresh
request.state.should_refresh_token = True
yield
```
### 4. Rate Limit by User
```python
from collections import defaultdict
from time import time
user_request_counts = defaultdict(list)
def rate_limit_by_user_middleware(input_data, request):
"""Rate limit per user ID."""
user_id = input_data.state.get("__request_context__", {}).get("user", {}).get("id")
if user_id:
now = time()
# Clean old requests
user_request_counts[user_id] = [
t for t in user_request_counts[user_id]
if now - t < 60 # 1-minute window
]
if len(user_request_counts[user_id]) >= 10:
raise Exception(f"Rate limit exceeded for user {user_id}")
user_request_counts[user_id].append(now)
yield
```
## Testing Authentication
### Unit Test
```python
import pytest
from fastapi import Request
from cloudbase_agent.server.send_message.models import RunAgentInput
from auth import jwt_auth_middleware
def test_auth_middleware_with_valid_token():
"""Test middleware with valid JWT."""
# Create mock request with valid token
token = create_test_jwt({"sub": "user123"})
request = Request(scope={
"type": "http",
"headers": [(b"authorization", f"Bearer {token}".encode())]
})
input_data = RunAgentInput(
messages=[],
runId="test-run",
threadId="test-thread"
)
# Execute middleware
gen = jwt_auth_middleware(input_data, request)
next(gen)
# Verify user info was injected
assert input_data.state["__request_context__"]["user"]["id"] == "user123"
def test_auth_middleware_with_missing_token():
"""Test middleware rejects missing token."""
request = Request(scope={"type": "http", "headers": []})
input_data = RunAgentInput(messages=[], runId="test", threadId="test")
with pytest.raises(InvalidRequestError):
gen = jwt_auth_middleware(input_data, request)
next(gen)
```
### Integration Test
```python
from fastapi.testclient import TestClient
def test_authenticated_request():
"""Test full request with authentication."""
client = TestClient(app)
token = create_test_jwt({"sub": "user123"})
response = client.post(
"/send-message",
json={"messages": [{"role": "user", "content": "Hello"}]},
headers={"Authorization": f"Bearer {token}"}
)
assert response.status_code == 200
```
## Migration from forwarded_props
If you're migrating from the old `forwarded_props` pattern:
### Before (Old Pattern)
```python
# ❌ Old: forwarded_props
def create_jwt_preprocessor():
def jwt_preprocessor(request: RunAgentInput, http_context: Request):
user_id = extract_user_id_from_request(http_context)
if not request.forwarded_props:
request.forwarded_props = {}
request.forwarded_props["user_id"] = user_id
return jwt_preprocessor
```
### After (New Pattern)
```python
# ✅ New: state.__request_context__
def auth_middleware(input_data: RunAgentInput, request: Request):
user_id = extract_user_id_from_request(request)
if input_data.state is None:
input_data.state = {}
input_data.state["__request_context__"] = {
"user": {"id": user_id}
}
yield
```
## Summary
| Aspect | Implementation |
|--------|---------------|
| **Write (Middleware)** | `state["__request_context__"]["user"]["id"] = user_id` |
| **Read (Adapter)** | `state.get("__request_context__", {}).get("user", {}).get("id")` |
| **Security** | Verify JWT, validate claims, use strong secrets |
| **Custom Fields** | Add alongside reserved fields in `__request_context__` |
| **Testing** | Unit test middleware, integration test full flow |
## Next Steps
- Learn about [server deployment](server-quickstart.md)
- Understand [middleware patterns](server-quickstart.md#middleware-system)
- Integrate with [Coze adapter](adapter-coze.md)
- Build [UI clients](ui-clients.md)
references/cloudbase-agent/py/references/observability.md
# CloudBase Agent Observability Reference
## Overview
CloudBase Agent provides comprehensive observability features including logging, metrics, and distributed tracing.
## Logging
### Basic Configuration
```python
from cloudbase_agent.server import create_server
import logging
server = create_server(
log_level="INFO",
log_format="json", # or "text"
log_output="stdout" # or file path
)
```
### Structured Logging
```python
from cloudbase_agent.server.logging import get_logger
logger = get_logger(__name__)
# Structured log with context
logger.info(
"Agent request received",
extra={
"conversation_id": "conv_123",
"user_id": "user_456",
"agent_type": "react",
"duration_ms": 150
}
)
```
### Log Levels
```python
logger.debug("Detailed debugging information")
logger.info("General information")
logger.warning("Warning messages")
logger.error("Error messages", exc_info=True)
logger.critical("Critical errors")
```
## Metrics
### Prometheus Metrics
```python
from cloudbase_agent.server.metrics import (
Counter,
Histogram,
Gauge,
Summary
)
# Define metrics
requests_total = Counter(
"agent_requests_total",
"Total agent requests",
["agent_type", "status"]
)
request_duration = Histogram(
"agent_request_duration_seconds",
"Request duration in seconds",
["agent_type"],
buckets=[0.1, 0.5, 1.0, 2.5, 5.0, 10.0]
)
active_conversations = Gauge(
"active_conversations",
"Number of active conversations"
)
# Use metrics
requests_total.labels(agent_type="react", status="success").inc()
request_duration.labels(agent_type="react").observe(1.23)
active_conversations.set(42)
```
### Metrics Endpoint
```python
from cloudbase_agent.server import create_server
server = create_server(
enable_metrics=True,
metrics_path="/metrics" # Default Prometheus endpoint
)
```
### Custom Metrics
```python
from cloudbase_agent.server.metrics import register_metric
# Register custom metric
tool_calls = Counter(
"agent_tool_calls_total",
"Total tool calls",
["tool_name", "status"]
)
register_metric(tool_calls)
# Use in tool
@tool
def my_tool(param: str) -> dict:
try:
result = do_work(param)
tool_calls.labels(tool_name="my_tool", status="success").inc()
return result
except Exception as e:
tool_calls.labels(tool_name="my_tool", status="error").inc()
raise
```
## Distributed Tracing
### OpenTelemetry Setup
```python
from cloudbase_agent.server.tracing import configure_tracing
configure_tracing(
service_name="my-agent-service",
exporter="otlp", # or "jaeger", "zipkin"
endpoint="http://localhost:4317",
sample_rate=1.0 # Sample all traces (0.0 to 1.0)
)
```
### Automatic Instrumentation
```python
from cloudbase_agent.server import create_server
# Enable automatic tracing
server = create_server(
enable_tracing=True,
trace_agent_runs=True,
trace_tool_calls=True,
trace_llm_calls=True
)
```
### Manual Tracing
```python
from cloudbase_agent.server.tracing import trace, get_current_span
@trace(name="custom_operation")
async def custom_operation(param: str):
# Current span auto-created
span = get_current_span()
span.set_attribute("param_length", len(param))
# Nested spans
with trace("sub_operation"):
result = await sub_operation(param)
span.set_attribute("result_size", len(result))
return result
```
### Trace Context Propagation
```python
from cloudbase_agent.server.tracing import inject_trace_context, extract_trace_context
# Inject context into HTTP headers
headers = {}
inject_trace_context(headers)
# Make HTTP request with context
async with httpx.AsyncClient() as client:
response = await client.get(url, headers=headers)
# Extract context from incoming request
context = extract_trace_context(request.headers)
```
## Agent Run Tracking
### Automatic Tracking
```python
from cloudbase_agent.langgraph import create_react_agent
# Automatic run tracking enabled
agent = create_react_agent(
model=model,
tools=tools,
enable_observability=True
)
# Each run automatically tracked with:
# - Run ID
# - Duration
# - Token usage
# - Tool calls
# - Errors
```
### Custom Run Metadata
```python
from cloudbase_agent.server.observability import track_run
@track_run(
run_type="react_agent",
metadata={"version": "1.0.0"}
)
async def invoke_agent(input_data: dict):
result = await agent.ainvoke(input_data)
return result
```
## Error Tracking
### Sentry Integration
```python
from cloudbase_agent.server.errors import configure_error_tracking
configure_error_tracking(
dsn="https://xxx@sentry.io/xxx",
environment="production",
release="1.0.0",
traces_sample_rate=0.1
)
```
### Error Context
```python
from cloudbase_agent.server.errors import capture_exception, set_error_context
set_error_context({
"conversation_id": "conv_123",
"user_id": "user_456"
})
try:
result = risky_operation()
except Exception as e:
capture_exception(e, extra={
"operation": "risky_operation",
"input": input_data
})
raise
```
## Health Checks
### Health Check Endpoint
```python
from cloudbase_agent.server import create_server
server = create_server(
enable_health_check=True,
health_check_path="/health"
)
```
### Custom Health Checks
```python
from cloudbase_agent.server.health import HealthCheck, HealthStatus
class DatabaseHealthCheck(HealthCheck):
name = "database"
async def check(self) -> HealthStatus:
try:
await db.execute("SELECT 1")
return HealthStatus.HEALTHY
except Exception as e:
return HealthStatus.UNHEALTHY, str(e)
# Register
server.add_health_check(DatabaseHealthCheck())
```
## Performance Monitoring
### APM Integration
```python
from cloudbase_agent.server.apm import configure_apm
configure_apm(
service_name="my-agent",
server_url="http://apm-server:8200",
environment="production"
)
```
### Performance Metrics
```python
from cloudbase_agent.server.metrics import track_performance
@track_performance(metric_name="agent_processing")
async def process_request(data: dict):
# Automatically tracks:
# - Duration
# - Memory usage
# - CPU time
return await agent.ainvoke(data)
```
## Dashboard Integration
### Grafana Dashboard
CloudBase Agent provides pre-built Grafana dashboards:
```bash
# Import dashboard
curl -X POST http://grafana:3000/api/dashboards/import \
-H "Content-Type: application/json" \
-d @dashboards/cloudbase-agent-overview.json
```
### Custom Dashboards
Key metrics to monitor:
- `agent_requests_total` - Request volume
- `agent_request_duration_seconds` - Latency
- `agent_errors_total` - Error rate
- `active_conversations` - Concurrent users
- `llm_tokens_total` - Token usage
- `tool_calls_total` - Tool usage
## Best Practices
1. **Structured Logging**: Always use structured logs with context
2. **Metrics Labels**: Use consistent label names across metrics
3. **Trace Sampling**: Adjust sample rate based on traffic volume
4. **Error Context**: Include relevant context when capturing errors
5. **Health Checks**: Implement health checks for all dependencies
6. **Alerts**: Set up alerts for critical metrics
## Common Patterns
### Request Tracking
```python
from cloudbase_agent.server.observability import RequestTracker
async def handle_request(request):
tracker = RequestTracker(request)
try:
result = await process_request(request.data)
tracker.success(result)
return result
except Exception as e:
tracker.error(e)
raise
finally:
tracker.finalize()
```
### Performance Profiling
```python
from cloudbase_agent.server.profiling import profile
@profile(enabled=True)
async def expensive_operation(data):
# Automatically profiles:
# - Function calls
# - Memory allocations
# - I/O operations
return await process(data)
```
## Troubleshooting
### High Latency
1. Check `agent_request_duration_seconds` histogram
2. Review trace spans to identify slow operations
3. Monitor `llm_response_time` metrics
4. Check tool execution times
### Error Spikes
1. Check `agent_errors_total` counter
2. Review error logs with `level=error`
3. Check Sentry for error details
4. Analyze error traces
### Memory Issues
1. Monitor `process_memory_bytes` gauge
2. Check for memory leaks in traces
3. Review conversation storage TTL settings
4. Analyze heap dumps if needed
## See Also
- [Server Reference](./server.md) - Server configuration
- [Storage Reference](./storage.md) - Storage monitoring
- [Recipes](./recipes.md) - Observability patterns
references/cloudbase-agent/py/references/recipes.md
# CloudBase Agent Recipes
Common patterns and complete examples for building agents with CloudBase Agent.
## Recipe 1: Basic Chat Agent
Complete example of a simple conversational agent.
```python
from cloudbase_agent.server import create_server, tool
from cloudbase_agent.langgraph import create_react_agent
from langchain_openai import ChatOpenAI
# Define tools
@tool
def get_weather(city: str) -> str:
"""Get current weather for a city."""
return f"Weather in {city}: Sunny, 22°C"
# Create model and agent
model = ChatOpenAI(model="gpt-4")
agent = create_react_agent(
model=model,
tools=[get_weather]
)
# Create server
server = create_server()
server.add_agent("/chat", agent)
if __name__ == "__main__":
server.run(host="0.0.0.0", port=9000)
```
## Recipe 2: Multi-Agent System
Orchestrate multiple specialized agents.
```python
from cloudbase_agent.langgraph import create_react_agent, create_router_agent
# Specialized agents
research_agent = create_react_agent(
model=model,
tools=[search_web, read_document],
system_message="You are a research assistant."
)
writing_agent = create_react_agent(
model=model,
tools=[grammar_check, format_text],
system_message="You are a writing assistant."
)
# Router agent
router = create_router_agent(
agents={
"research": research_agent,
"writing": writing_agent
},
model=model
)
server.add_agent("/assistant", router)
```
## Recipe 3: Persistent Conversations
Maintain conversation history across sessions.
```python
from cloudbase_agent.server.storage import RedisStorage, ConversationStorage
from cloudbase_agent.langgraph import create_checkpointer
# Setup storage
storage = RedisStorage(url="redis://localhost:6379")
conv_storage = ConversationStorage(storage)
checkpointer = create_checkpointer(storage)
# Create agent with persistence
agent = create_react_agent(
model=model,
tools=tools,
checkpointer=checkpointer
)
# Handle request with conversation ID
@server.post("/chat")
async def chat(request):
conversation_id = request.conversation_id
# Load conversation
messages = await conv_storage.load_conversation(conversation_id)
# Invoke agent
result = await agent.ainvoke(
{"messages": messages + [request.message]},
config={"configurable": {"thread_id": conversation_id}}
)
# Save conversation
await conv_storage.save_conversation(
conversation_id,
messages + [request.message, result["messages"][-1]]
)
return result
```
## Recipe 4: Streaming Responses
Stream agent responses in real-time.
```python
from cloudbase_agent.server import StreamingResponse
@server.post("/chat/stream")
async def chat_stream(request):
async def generate():
async for chunk in agent.astream(request.data):
# Yield SSE format
yield f"data: {json.dumps(chunk)}\n\n"
return StreamingResponse(
generate(),
media_type="text/event-stream"
)
```
## Recipe 5: Human-in-the-Loop
Implement approval workflows.
```python
from cloudbase_agent.langgraph import interrupt
from cloudbase_agent.server.approval import ApprovalManager
approval_manager = ApprovalManager(storage)
@tool
def send_email(to: str, subject: str, body: str) -> str:
"""Send an email (requires approval)."""
# Request approval
approval_id = interrupt(
"email_approval",
data={"to": to, "subject": subject, "body": body}
)
# Wait for approval
approved = approval_manager.wait_for_approval(approval_id)
if approved:
# Actually send email
email_service.send(to, subject, body)
return "Email sent successfully"
else:
return "Email cancelled by user"
# Approval endpoint
@server.post("/approve/{approval_id}")
async def approve(approval_id: str, approved: bool):
await approval_manager.set_approval(approval_id, approved)
return {"status": "ok"}
```
## Recipe 6: Tool-Based Generative UI
Generate UI components based on tool results.
```python
from cloudbase_agent.server import tool, ui_component
@tool
@ui_component("chart")
def analyze_data(dataset: str) -> dict:
"""Analyze dataset and return chart data."""
data = load_dataset(dataset)
analysis = perform_analysis(data)
return {
"type": "line_chart",
"data": analysis["timeseries"],
"config": {
"xAxis": "date",
"yAxis": "value",
"title": f"Analysis of {dataset}"
}
}
# Frontend receives:
# {
# "tool": "analyze_data",
# "result": {...},
# "ui": {
# "component": "chart",
# "props": {...}
# }
# }
```
## Recipe 7: Rate Limiting
Implement request rate limiting.
```python
from cloudbase_agent.server.middleware import RateLimiter
rate_limiter = RateLimiter(
storage=storage,
requests_per_minute=60,
burst=10
)
@server.post("/chat")
@rate_limiter.limit(key=lambda req: req.user_id)
async def chat(request):
return await agent.ainvoke(request.data)
```
## Recipe 8: Authentication & Authorization
Secure agent endpoints.
```python
from cloudbase_agent.server.auth import APIKeyAuth, JWTAuth
# API Key auth
api_key_auth = APIKeyAuth(
storage=storage,
header="X-API-Key"
)
# JWT auth
jwt_auth = JWTAuth(
secret="your-secret-key",
algorithm="HS256"
)
@server.post("/chat")
@api_key_auth.require()
async def chat(request):
user_id = request.auth.user_id
return await agent.ainvoke(request.data)
@server.post("/admin/chat")
@jwt_auth.require(roles=["admin"])
async def admin_chat(request):
return await admin_agent.ainvoke(request.data)
```
## Recipe 9: Error Recovery
Implement robust error handling.
```python
from cloudbase_agent.server.errors import AgentError, ToolError
from tenacity import retry, stop_after_attempt, retry_if_exception_type
@retry(
stop=stop_after_attempt(3),
retry=retry_if_exception_type(ToolError)
)
async def invoke_with_retry(agent, input_data):
try:
return await agent.ainvoke(input_data)
except ToolError as e:
logger.warning(f"Tool error, retrying: {e}")
raise
except AgentError as e:
logger.error(f"Agent error: {e}")
return {"error": str(e), "fallback": "default_response"}
```
## Recipe 10: Monitoring & Alerting
Complete observability setup.
```python
from cloudbase_agent.server.observability import setup_observability
from cloudbase_agent.server.metrics import Counter, Histogram
# Setup
setup_observability(
service_name="my-agent",
enable_tracing=True,
enable_metrics=True,
enable_logging=True
)
# Custom metrics
agent_errors = Counter(
"agent_errors_total",
"Total agent errors",
["error_type"]
)
# Middleware
@server.middleware("http")
async def observability_middleware(request, call_next):
with track_request(request):
try:
response = await call_next(request)
return response
except Exception as e:
agent_errors.labels(error_type=type(e).__name__).inc()
raise
# Alerts (example with Prometheus Alertmanager)
# rules.yml:
# - alert: HighErrorRate
# expr: rate(agent_errors_total[5m]) > 0.1
# annotations:
# summary: "High error rate detected"
```
## Recipe 11: Background Tasks
Process long-running tasks asynchronously.
```python
from cloudbase_agent.server.tasks import TaskQueue
task_queue = TaskQueue(storage=storage)
@task_queue.task(name="process_document")
async def process_document(doc_id: str):
"""Process a document in the background."""
document = load_document(doc_id)
result = await agent.ainvoke({
"task": "analyze",
"document": document
})
save_result(doc_id, result)
return result
@server.post("/documents/process")
async def submit_document(request):
task_id = await process_document.delay(request.doc_id)
return {"task_id": task_id, "status": "processing"}
@server.get("/tasks/{task_id}")
async def get_task_status(task_id: str):
status = await task_queue.get_status(task_id)
return status
```
## Recipe 12: Multi-Modal Agent
Handle text, images, and other media.
```python
from cloudbase_agent.server import tool
from langchain_openai import ChatOpenAI
@tool
def analyze_image(image_url: str) -> str:
"""Analyze an image and describe its contents."""
# Vision model
vision_model = ChatOpenAI(model="gpt-4-vision-preview")
result = vision_model.invoke([
{"type": "image_url", "image_url": image_url},
{"type": "text", "text": "What's in this image?"}
])
return result.content
# Multi-modal agent
agent = create_react_agent(
model=ChatOpenAI(model="gpt-4-vision-preview"),
tools=[analyze_image, search_web]
)
```
## See Also
- [Server Reference](./server.md) - Server API details
- [LangGraph Reference](./langgraph.md) - Agent patterns
- [Tools Reference](./tools.md) - Tool system
- [Storage Reference](./storage.md) - Data persistence
- [Observability Reference](./observability.md) - Monitoring
references/cloudbase-agent/py/references/server.md
# Server Reference (`cloudbase_agent.server`)
FastAPI-based HTTP server with dual-protocol support (AG-UI + OpenAI).
## Exports
| Export | Purpose |
|--------|---------|
| `AgentServiceApp` | FastAPI wrapper with CORS, healthz, middleware |
| `create_send_message_adapter` | AG-UI native SSE streaming adapter |
| `create_openai_adapter` | OpenAI-compatible `/chat/completions` adapter |
| `RunAgentInput` | Request model (messages, thread_id, run_id, state, tools, context, forwarded_props) |
| `OpenAIChatCompletionRequest` | OpenAI-compatible request model |
| `AgentCreatorResult` | TypedDict: `{"agent": ..., "cleanup": optional_fn}` |
| `HealthzConfig` | Health check config (service_name, version, custom_checks) |
## Three Deployment Methods
### Method 1: One-line (simplest)
```python
AgentServiceApp().run(create_agent, port=9000)
```
### Method 2: Build + customize (recommended for multi-agent)
```python
app = AgentServiceApp()
fastapi_app = app.build(
create_agent,
base_path="/api",
enable_openai_endpoint=True,
enable_healthz=True,
)
# Add custom routes to fastapi_app...
uvicorn.run(fastapi_app, host="0.0.0.0", port=9000)
```
### Method 3: Core adapters (maximum flexibility)
```python
from fastapi import FastAPI
from cloudbase_agent.server import create_send_message_adapter, create_openai_adapter
app = FastAPI()
@app.post("/my-agent/send-message")
async def send_message(request: RunAgentInput):
return await create_send_message_adapter(create_my_agent, request)
@app.post("/my-agent/chat/completions")
async def chat(request: OpenAIChatCompletionRequest):
return await create_openai_adapter(create_my_agent, request)
```
## AgentServiceApp Constructor
```python
AgentServiceApp(
observability=None, # Optional[ObservabilityConfig | List[ObservabilityConfig]]
)
```
## AgentServiceApp Methods
| Method | Returns | Purpose |
|--------|---------|---------|
| `.set_cors_config(allow_origins, allow_credentials, allow_methods, allow_headers)` | self | Configure CORS |
| `.use(middleware)` | self | Register middleware (generator pattern) |
| `.build(create_agent, base_path, enable_cors, enable_openai_endpoint, enable_healthz, healthz_config)` | FastAPI | Build configured app |
| `.run(create_agent, base_path, host, port, enable_openai_endpoint, enable_healthz, healthz_config)` | None | Build + run with uvicorn |
## Middleware Pattern
Middlewares use Python's generator pattern with `yield` — code before yield runs pre-processing, code after yield runs post-processing (onion model).
```python
def my_middleware(input_data: RunAgentInput, request: Request):
# Pre-processing (runs before agent)
auth = request.headers.get("Authorization")
if auth and auth.startswith("Bearer "):
if not input_data.forwarded_props:
input_data.forwarded_props = {}
input_data.forwarded_props["user_id"] = decode_jwt(auth[7:])
yield # Control passes to agent
# Post-processing (runs after agent, optional)
print("Request completed")
app = AgentServiceApp()
app.use(my_middleware)
app.run(create_agent, port=9000)
```
## Agent Creator Pattern
Factory function called per-request. Supports optional cleanup callback:
```python
def create_agent() -> AgentCreatorResult:
db = connect_database()
agent = LangGraphAgent(graph=workflow, name="my-agent")
def cleanup():
db.close() # Guaranteed to run after stream completes
return {"agent": agent, "cleanup": cleanup}
```
## Multi-Agent Server
### Option A: Manual routes
```python
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from cloudbase_agent.server import create_send_message_adapter, create_openai_adapter, RunAgentInput, OpenAIChatCompletionRequest
from cloudbase_agent.server.errors import install_exception_handlers
app = FastAPI(title="Multi-Agent Server")
install_exception_handlers(app)
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
@app.post("/agentic_chat/send-message")
async def chat_send(request: RunAgentInput):
return await create_send_message_adapter(create_chat_agent, request)
@app.post("/agentic_chat/chat/completions")
async def chat_openai(request: OpenAIChatCompletionRequest):
return await create_openai_adapter(create_chat_agent, request)
@app.post("/human_in_the_loop/send-message")
async def hitl_send(request: RunAgentInput):
return await create_send_message_adapter(create_hitl_agent, request)
```
### Option B: Mount sub-apps
```python
main_app = FastAPI()
chat_app = AgentServiceApp().build(create_chat_agent, enable_openai_endpoint=True)
hitl_app = AgentServiceApp().build(create_hitl_agent, enable_openai_endpoint=True)
main_app.mount("/agentic_chat", chat_app)
main_app.mount("/human_in_the_loop", hitl_app)
```
references/cloudbase-agent/py/references/storage.md
# CloudBase Agent Storage System Reference
## Overview
CloudBase Agent provides a unified storage interface for managing agent state, conversation history, and persistent data.
## Storage Interface
### Basic Operations
```python
from cloudbase_agent.server.storage import Storage
# Initialize storage
storage = Storage(backend="redis", url="redis://localhost:6379")
# Store data
await storage.set("key", {"data": "value"})
# Retrieve data
data = await storage.get("key")
# Delete data
await storage.delete("key")
# Check existence
exists = await storage.exists("key")
```
## Storage Backends
### Redis Backend
```python
from cloudbase_agent.server.storage import RedisStorage
storage = RedisStorage(
url="redis://localhost:6379",
db=0,
decode_responses=True,
max_connections=10
)
```
### Memory Backend (Development)
```python
from cloudbase_agent.server.storage import MemoryStorage
storage = MemoryStorage() # In-memory, no persistence
```
### PostgreSQL Backend
```python
from cloudbase_agent.server.storage import PostgresStorage
storage = PostgresStorage(
connection_string="postgresql://user:pass@localhost/cloudbase_agent_db",
table_name="agent_storage"
)
```
## Conversation Storage
### Store Conversation State
```python
from cloudbase_agent.server.storage import ConversationStorage
conv_storage = ConversationStorage(storage)
# Save conversation
await conv_storage.save_conversation(
conversation_id="conv_123",
messages=[
{"role": "user", "content": "Hello"},
{"role": "assistant", "content": "Hi there!"}
],
metadata={"user_id": "user_456"}
)
# Load conversation
conversation = await conv_storage.load_conversation("conv_123")
```
### Conversation History
```python
# Get all conversations for a user
conversations = await conv_storage.list_conversations(
user_id="user_456",
limit=10,
offset=0
)
# Search conversations
results = await conv_storage.search_conversations(
query="agent features",
user_id="user_456"
)
```
## Checkpointing
### LangGraph Checkpointer
```python
from cloudbase_agent.langgraph import create_checkpointer
# Create checkpointer from storage
checkpointer = create_checkpointer(storage)
# Use with LangGraph
graph = create_react_agent(
model=model,
tools=tools,
checkpointer=checkpointer
)
```
### Manual Checkpointing
```python
# Save checkpoint
await checkpointer.put(
config={"configurable": {"thread_id": "thread_123"}},
checkpoint={
"values": graph_state,
"next": ["tool_node"],
"metadata": {"step": 5}
}
)
# Load checkpoint
checkpoint = await checkpointer.get(
config={"configurable": {"thread_id": "thread_123"}}
)
```
## TTL and Expiration
```python
# Set data with TTL
await storage.set("temp_key", {"data": "value"}, ttl=3600) # 1 hour
# Update TTL
await storage.expire("temp_key", ttl=7200) # 2 hours
# Get TTL
remaining = await storage.ttl("temp_key")
```
## Batch Operations
```python
# Batch set
await storage.mset({
"key1": {"value": 1},
"key2": {"value": 2},
"key3": {"value": 3}
})
# Batch get
values = await storage.mget(["key1", "key2", "key3"])
# Batch delete
await storage.delete_many(["key1", "key2", "key3"])
```
## Namespacing
```python
# Create namespaced storage
user_storage = storage.namespace("user:123")
# Operations are automatically prefixed
await user_storage.set("preferences", {"theme": "dark"})
# Actually stores at "user:123:preferences"
# Nested namespaces
session_storage = user_storage.namespace("session:456")
# Keys stored at "user:123:session:456:*"
```
## Transactions
### Redis Transactions
```python
async with storage.transaction() as txn:
await txn.set("counter", 0)
value = await txn.get("counter")
await txn.set("counter", value + 1)
# Auto-commits on successful exit
```
### PostgreSQL Transactions
```python
async with storage.transaction() as txn:
await txn.execute("UPDATE users SET balance = balance - 100 WHERE id = 1")
await txn.execute("UPDATE users SET balance = balance + 100 WHERE id = 2")
# Auto-commits or rolls back
```
## Serialization
### Custom Serializers
```python
from cloudbase_agent.server.storage import Storage, JSONSerializer, PickleSerializer
# JSON serializer (default)
storage = Storage(backend="redis", serializer=JSONSerializer())
# Pickle serializer (Python objects)
storage = Storage(backend="redis", serializer=PickleSerializer())
# Custom serializer
class CustomSerializer:
def serialize(self, obj):
return msgpack.packb(obj)
def deserialize(self, data):
return msgpack.unpackb(data)
storage = Storage(backend="redis", serializer=CustomSerializer())
```
## Monitoring
### Storage Metrics
```python
# Get storage stats
stats = await storage.stats()
# Returns: {
# "keys_count": 1234,
# "memory_usage": 5242880, # bytes
# "hit_rate": 0.95
# }
# Health check
is_healthy = await storage.health_check()
```
## Migration
### Data Migration
```python
from cloudbase_agent.server.storage import migrate_storage
# Migrate from Redis to PostgreSQL
await migrate_storage(
source=redis_storage,
destination=postgres_storage,
batch_size=100,
transform_fn=lambda k, v: (k, transform(v))
)
```
## Best Practices
1. **Use Namespacing**: Organize keys with namespaces to avoid collisions
2. **Set Appropriate TTLs**: Use TTL for temporary data to prevent memory bloat
3. **Batch Operations**: Use batch operations for multiple keys to reduce latency
4. **Connection Pooling**: Configure connection pools for production workloads
5. **Error Handling**: Always handle storage errors gracefully
6. **Monitoring**: Track storage metrics and set up alerts
## Common Patterns
### Session Management
```python
class SessionManager:
def __init__(self, storage: Storage):
self.storage = storage.namespace("sessions")
async def create_session(self, user_id: str) -> str:
session_id = generate_session_id()
await self.storage.set(
session_id,
{"user_id": user_id, "created_at": datetime.now()},
ttl=3600 # 1 hour
)
return session_id
async def get_session(self, session_id: str) -> dict | None:
return await self.storage.get(session_id)
```
### Rate Limiting
```python
async def rate_limit(user_id: str, limit: int = 100, window: int = 3600):
key = f"rate_limit:{user_id}"
count = await storage.get(key) or 0
if count >= limit:
raise RateLimitError("Too many requests")
await storage.set(key, count + 1, ttl=window)
```
## See Also
- [Server Reference](./server.md) - Server configuration
- [LangGraph Reference](./langgraph.md) - Checkpointing integration
- [Recipes](./recipes.md) - Storage use cases
references/cloudbase-agent/py/references/tools.md
# CloudBase Agent Tools System Reference
## Overview
CloudBase Agent provides a flexible tool system for integrating external capabilities into agents.
## Core Concepts
### Tool Definition
```python
from cloudbase_agent.server import tool
@tool
def search_database(query: str, limit: int = 10) -> list[dict]:
"""Search the database for matching records.
Args:
query: Search query string
limit: Maximum number of results to return
Returns:
List of matching records
"""
# Implementation
return results
```
### Tool Registry
```python
from cloudbase_agent.server import ToolRegistry
# Create registry
registry = ToolRegistry()
# Register tools
registry.register(search_database)
registry.register(update_record)
# Get all tools
tools = registry.get_tools()
```
## Built-in Tool Types
### HTTP Tools
```python
from cloudbase_agent.server.tools import HttpTool
http_tool = HttpTool(
name="fetch_data",
method="GET",
url="https://api.example.com/data",
headers={"Authorization": "Bearer TOKEN"}
)
```
### Database Tools
```python
from cloudbase_agent.server.tools import DatabaseTool
db_tool = DatabaseTool(
name="query_users",
connection_string="postgresql://localhost/mydb",
query="SELECT * FROM users WHERE active = true"
)
```
## Tool Execution
### Synchronous Execution
```python
result = await tool.execute({"query": "search term", "limit": 5})
```
### Async Tool Support
```python
@tool
async def async_search(query: str) -> dict:
"""Async tool example."""
async with httpx.AsyncClient() as client:
response = await client.get(f"https://api.example.com/search?q={query}")
return response.json()
```
## Tool Validation
### Input Validation
```python
from pydantic import BaseModel, Field
class SearchParams(BaseModel):
query: str = Field(..., min_length=1, max_length=100)
limit: int = Field(default=10, ge=1, le=100)
@tool
def validated_search(params: SearchParams) -> list[dict]:
"""Tool with Pydantic validation."""
return search(params.query, params.limit)
```
## Error Handling
```python
from cloudbase_agent.server.tools import ToolError
@tool
def safe_operation(data: dict) -> dict:
"""Tool with error handling."""
try:
result = risky_operation(data)
return {"success": True, "data": result}
except ValueError as e:
raise ToolError(f"Invalid input: {e}")
except Exception as e:
raise ToolError(f"Operation failed: {e}")
```
## Tool Metadata
```python
@tool(
name="custom_name",
description="Detailed description",
tags=["search", "database"],
version="1.0.0"
)
def advanced_tool(param: str) -> dict:
"""Advanced tool with metadata."""
return {}
```
## Tool Composition
### Chaining Tools
```python
@tool
def fetch_and_process(query: str) -> dict:
"""Chain multiple operations."""
# Fetch data
raw_data = fetch_data(query)
# Process data
processed = process_data(raw_data)
# Store results
store_results(processed)
return {"status": "complete", "count": len(processed)}
```
## Integration with Agents
### LangGraph Integration
```python
from cloudbase_agent.langgraph import create_react_agent
agent = create_react_agent(
model=model,
tools=[search_database, update_record, async_search]
)
```
### Custom Tool Nodes
```python
from langgraph.prebuilt import ToolNode
tool_node = ToolNode([search_database, update_record])
# Add to graph
graph.add_node("tools", tool_node)
```
## Best Practices
1. **Clear Descriptions**: Write detailed docstrings for AI to understand tool purpose
2. **Type Hints**: Always use type hints for parameters and return values
3. **Error Handling**: Catch and wrap errors with meaningful messages
4. **Validation**: Use Pydantic models for complex input validation
5. **Async Support**: Use async tools for I/O-bound operations
6. **Idempotency**: Make tools safe to retry when possible
## Common Patterns
### Retry Logic
```python
from tenacity import retry, stop_after_attempt, wait_exponential
@tool
@retry(stop=stop_after_attempt(3), wait=wait_exponential())
async def resilient_api_call(endpoint: str) -> dict:
"""API call with automatic retries."""
async with httpx.AsyncClient() as client:
response = await client.get(endpoint)
response.raise_for_status()
return response.json()
```
### Caching Results
```python
from functools import lru_cache
@tool
@lru_cache(maxsize=100)
def cached_lookup(key: str) -> dict:
"""Cached database lookup."""
return db.query(key)
```
## See Also
- [Server Reference](./server.md) - Server configuration
- [LangGraph Reference](./langgraph.md) - Agent integration
- [Recipes](./recipes.md) - Common use cases
references/cloudbase-agent/py/server-quickstart.md
# Server Quickstart Guide
This guide shows you how to create and deploy CloudBase Agent Python agents as HTTP services using FastAPI.
---
## Three Deployment Methods
CloudBase Agent Python SDK provides three flexible deployment methods, each suited for different use cases:
### Method 1: Core Adapters (Maximum Flexibility)
Use `create_send_message_adapter()` and `create_openai_adapter()` directly for complete control over routes.
```python
from fastapi import FastAPI
from cloudbase_agent.server import (
create_send_message_adapter,
create_openai_adapter,
RunAgentInput,
OpenAIChatCompletionRequest
)
from cloudbase_agent.server.errors import install_exception_handlers
app = FastAPI()
# Required: Install exception handlers for AG-UI protocol compatibility
install_exception_handlers(app)
# Define routes manually
@app.post("/my-agent/send-message")
async def send_message(request: RunAgentInput):
return await create_send_message_adapter(create_agent, request)
@app.post("/my-agent/chat/completions")
async def openai_endpoint(request: OpenAIChatCompletionRequest):
return await create_openai_adapter(create_agent, request)
```
**Advantages:**
- Full control over route paths
- Easy to add custom middleware per route
- Clear separation of concerns
- Ideal for complex multi-agent systems
**Use when:**
- You need custom route structures
- You want fine-grained control
- You're building complex applications
### Method 2: AgentServiceApp with Custom Routes (Recommended)
Use `AgentServiceApp.build()` for automatic features with flexibility:
```python
from fastapi import FastAPI
from cloudbase_agent.server import AgentServiceApp, HealthzConfig
# Create main app
main_app = FastAPI(title="My Service")
# Build agent app with automatic features
agent_app = AgentServiceApp()
agent_fastapi = agent_app.build(
create_agent,
base_path="",
enable_cors=False, # Handle in main app
enable_openai_endpoint=True,
enable_healthz=True,
healthz_config=HealthzConfig(
service_name="my-agent",
version="1.0.0"
)
)
# Mount to main app
main_app.mount("/my-agent", agent_fastapi)
```
**Advantages:**
- Automatic health check endpoints
- Built-in OpenAI compatibility
- Less boilerplate code
- Modular agent deployment
**Use when:**
- You want automatic health checks
- You're deploying multiple agents
- You need both CloudBase Agent native and OpenAI endpoints
### Method 3: One-Line Deployment (Simplest)
For single-agent deployments, use the one-line approach:
```python
from cloudbase_agent.server import AgentServiceApp, HealthzConfig
AgentServiceApp().run(
create_agent,
port=9000,
enable_openai_endpoint=True,
healthz_config=HealthzConfig(
service_name="my-agent",
version="1.0.0"
)
)
```
**Advantages:**
- Extremely simple (one line!)
- Perfect for prototyping
- Automatic CORS and health checks
- No boilerplate needed
**Use when:**
- You have a single agent
- You want the fastest way to start
- You don't need custom routes
---
## Agent Creator Pattern
All three methods use an "agent creator" function that returns an `AgentCreatorResult`:
```python
from cloudbase_agent.langgraph import LangGraphAgent
from cloudbase_agent.server import AgentCreatorResult
def create_agent() -> AgentCreatorResult:
"""Create agent with optional cleanup."""
agent = LangGraphAgent(
name="my-agent",
description="A helpful assistant",
graph=build_workflow(),
use_callbacks=True
)
# Optional: Add callbacks
agent.add_callback(ConsoleLogger())
# Optional: Define cleanup function
def cleanup():
# Close connections, release resources, etc.
print("Cleanup completed")
return {"agent": agent, "cleanup": cleanup}
```
**Why use creator functions?**
- Agent instance is created fresh per request
- Ensures proper isolation
- Automatic cleanup after request completes
- Supports resource management (DB connections, file handles, etc.)
---
## Complete Example: Multi-Agent Server
Here's a production-ready example with multiple agents:
```python
#!/usr/bin/env python3
import logging
import uvicorn
from dotenv import load_dotenv
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from cloudbase_agent.langgraph import LangGraphAgent
from cloudbase_agent.server import (
AgentCreatorResult,
AgentServiceApp,
HealthzConfig,
OpenAIChatCompletionRequest,
RunAgentInput,
create_openai_adapter,
create_send_message_adapter,
)
from cloudbase_agent.server.errors import install_exception_handlers
# Load environment variables
load_dotenv()
# Configure logging
logging.basicConfig(level=logging.INFO)
# Import your agent workflows
from agents.chat.agent import build_chat_workflow
from agents.assistant.agent import build_assistant_workflow
# Initialize workflows
chat_workflow = build_chat_workflow()
assistant_workflow = build_assistant_workflow()
# Agent creator for chat bot
def create_chat_agent() -> AgentCreatorResult:
agent = LangGraphAgent(
name="chatbot",
description="A conversational assistant",
graph=chat_workflow,
use_callbacks=True
)
return {"agent": agent}
# Agent creator for assistant
def create_assistant_agent() -> AgentCreatorResult:
agent = LangGraphAgent(
name="assistant",
description="A helpful AI assistant",
graph=assistant_workflow,
use_callbacks=True
)
def cleanup():
print(f"Cleanup for {agent.name}")
return {"agent": agent, "cleanup": cleanup}
def main():
# Method 1: Using core adapters
app = FastAPI(
title="CloudBase Agent Multi-Agent Server",
version="1.0.0"
)
# Install exception handlers (required for Method 1)
install_exception_handlers(app)
# Add CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Chat bot endpoints
@app.post("/chatbot/send-message")
async def chatbot_send_message(request: RunAgentInput):
return await create_send_message_adapter(create_chat_agent, request)
@app.post("/chatbot/chat/completions")
async def chatbot_openai(request: OpenAIChatCompletionRequest):
return await create_openai_adapter(create_chat_agent, request)
# Assistant endpoints
@app.post("/assistant/send-message")
async def assistant_send_message(request: RunAgentInput):
return await create_send_message_adapter(create_assistant_agent, request)
@app.post("/assistant/chat/completions")
async def assistant_openai(request: OpenAIChatCompletionRequest):
return await create_openai_adapter(create_assistant_agent, request)
# Health check
@app.get("/healthz")
def healthz():
from datetime import datetime
import platform
return {
"status": "healthy",
"timestamp": datetime.utcnow().isoformat() + "Z",
"version": "1.0.0",
"python_version": platform.python_version(),
"agents": [
{"name": "chatbot", "endpoints": ["/chatbot/send-message", "/chatbot/chat/completions"]},
{"name": "assistant", "endpoints": ["/assistant/send-message", "/assistant/chat/completions"]},
]
}
uvicorn.run(app, host="0.0.0.0", port=9000)
if __name__ == "__main__":
main()
```
---
## Testing Your Server
### 1. CloudBase Agent Native Endpoint
```bash
curl -X POST http://localhost:9000/chatbot/send-message \
-H "Content-Type: application/json" \
-d '{
"conversationId": "conv_123",
"messages": [
{"role": "user", "content": "Hello!"}
]
}'
```
### 2. OpenAI-Compatible Endpoint
```bash
curl -X POST http://localhost:9000/chatbot/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "chatbot",
"messages": [
{"role": "user", "content": "Hello!"}
],
"stream": true
}'
```
### 3. Using OpenAI Python Client
```python
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:9000",
api_key="dummy" # Not required for local
)
response = client.chat.completions.create(
model="chatbot",
messages=[{"role": "user", "content": "Hello!"}],
stream=True
)
for chunk in response:
print(chunk.choices[0].delta.content, end="")
```
### 4. Health Check
```bash
curl http://localhost:9000/healthz
```
---
## Callbacks for Monitoring
Add callbacks to your agent for real-time monitoring:
```python
class ConsoleLogger:
"""Log agent events to console."""
async def on_text_message_content(self, event, buffer):
print(f"[AI] {buffer}", end="", flush=True)
async def on_tool_call_args(self, event, buffer, partial_args):
tool_name = getattr(event, "tool_name", "unknown")
if partial_args:
print(f"\n[TOOL] {tool_name}: {partial_args}")
async def on_run_started(self, event):
print(f"\n{'=' * 60}")
print(f"Run Started: {event.run_id}")
print(f"{'=' * 60}")
async def on_run_finished(self, event):
print(f"\n{'=' * 60}")
print(f"Run Finished: {event.run_id}")
print(f"{'=' * 60}\n")
async def on_run_error(self, event):
print(f"\nERROR: {getattr(event, 'message', 'Unknown')}\n")
def create_agent() -> AgentCreatorResult:
agent = LangGraphAgent(
name="my-agent",
graph=build_workflow(),
use_callbacks=True # Enable callbacks
)
# Add console logger
agent.add_callback(ConsoleLogger())
return {"agent": agent}
```
---
## Production Considerations
### 1. Use Environment Variables
```python
# .env
OPENAI_API_KEY=sk-...
OPENAI_MODEL=gpt-4o-mini
OPENAI_TEMPERATURE=0.7
OPENAI_BASE_URL=https://api.openai.com/v1 # Optional
# Load in code
from dotenv import load_dotenv
load_dotenv()
```
### 2. Enable Observability
```bash
export AUTO_TRACES_STDOUT=true
python server.py
```
Or programmatically:
```python
from cloudbase_agent.observability import ConsoleTraceConfig, enable_tracing
enable_tracing(ConsoleTraceConfig())
```
### 3. Add Authentication Middleware
See `authentication.md` for details on implementing JWT-based authentication.
### 4. Configure CORS Properly
```python
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://your-frontend.com"], # Specific origins in production
allow_credentials=True,
allow_methods=["POST", "GET"],
allow_headers=["*"],
)
```
### 5. Use Production Server
```bash
# Install gunicorn
pip install gunicorn uvicorn[standard]
# Run with multiple workers
gunicorn server:app.app \
-w 4 \
-k uvicorn.workers.UvicornWorker \
--bind 0.0.0.0:9000
```
---
## Next Steps
- **LangGraph Integration**: See `adapter-langgraph.md` for detailed LangGraph usage
- **Coze Integration**: See `adapter-coze.md` for Coze platform integration
- **Authentication**: See `authentication.md` for auth patterns
- **Custom Adapters**: See `adapter-development.md` for creating custom framework adapters
references/cloudbase-agent/py/skill.md
---
name: cloudbase-agent-python
description: "Build production-ready AI agent backends using the CloudBase Agent Python SDK — create agents with LangGraph/CrewAI/LlamaIndex, serve them via FastAPI with AG-UI protocol streaming + OpenAI-compatible endpoints, add tools (bash, filesystem, MCP, code execution), memory (in-memory, TDAI, MySQL, MongoDB), observability (OpenTelemetry/Langfuse), and middleware (auth, logging). Use this skill when the user wants to create an AI agent server, build a chatbot backend, set up human-in-the-loop workflows, integrate MCP tools, add agent observability, or deploy an agent API — even if they don't explicitly mention 'CloudBase Agent.'"
version: 2.21.1
alwaysApply: true
---
# CloudBase Agent Python SDK
Build production-ready AI agent backends with multi-framework support, streaming
protocol, rich tools, persistent memory, and full observability.
> **Note:** This skill is for **Python** projects only.
## When to use this skill
Use this skill for **AI agent development** when you need to:
- Deploy AI agents as HTTP services with AG-UI protocol support
- Build agent backends using LangGraph, CrewAI, or LlamaIndex frameworks
- Create custom agent adapters implementing the AbstractAgent interface
- Understand AG-UI protocol events and message streaming
- Build production-ready agent servers with FastAPI
**Do NOT use for:**
- Simple AI model calling without agent capabilities (use `ai-model-*` skills)
- CloudBase cloud functions (use `cloud-functions` skill)
- CloudRun backend services without agent features (use `cloudrun-development` skill)
- TypeScript/JavaScript agent projects (use `cloudbase-agent` skill, refer to the `ts/` sub-directory)
## How to use this skill (for a coding agent)
1. **Choose the right adapter**
- Use LangGraph adapter for stateful, graph-based workflows
- Use CrewAI adapter for multi-agent collaboration patterns
- Build custom adapter for specialized agent logic
2. **Write agent code** — follow the adapter-specific doc from the Routing table
3. **Deploy the agent server** — follow the **blocking deployment pipeline** in [agent-deployment](agent-deployment.md)
## Routing (Execution Order)
> ⚠️ **Deployment is a BLOCKING 4-step pipeline.** Steps marked ✅ BLOCKING
> must be completed AND verified before proceeding to the next step.
> Do NOT call `manageAgent` until all blocking steps pass.
| Step | Task | Document | Blocking? |
|------|------|----------|-----------|
| 0 | **Choose adapter & write agent code** | See "Adapter Selection" below | — |
| 1 | **Ensure Python 3.10** | [agent-deployment](agent-deployment.md) § Step 1 | ✅ BLOCKING |
| 2 | **Build env/ (one-shot)** | [agent-deployment](agent-deployment.md) § Step 2 | ✅ BLOCKING |
| 3 | **Verify env/ integrity** | [agent-deployment](agent-deployment.md) § Step 3 | ✅ BLOCKING |
| 4 | **Deploy with manageAgent** | [agent-deployment](agent-deployment.md) § Step 4 | — |
### Adapter Selection (Step 0)
| Framework | Read | Install |
|-----------|------|---------|
| LangGraph (stateful graphs) | [adapter-langgraph](adapter-langgraph.md) | `cloudbase-agent-langgraph` |
| CrewAI (multi-agent crews) | [adapter-development](adapter-development.md) | `cloudbase-agent-crewai` |
| Coze platform | [adapter-coze](adapter-coze.md) | `cloudbase-agent-coze` |
| Custom / raw FastAPI | [server-quickstart](server-quickstart.md) + [adapter-development](adapter-development.md) | `cloudbase-agent-server` |
### Additional References (read on demand, NOT required for deployment)
| Task | Read |
|------|------|
| Server setup, middleware, multi-agent, CORS | [server-quickstart](server-quickstart.md) |
| Authentication and user context | [authentication](authentication.md) |
## Quick Start (Framework-Agnostic)
**Prerequisites:** Python >= 3.10 is required.
**1. Install dependencies (pick ONE adapter):**
```bash
# Option A: LangGraph-based agent
pip install cloudbase-agent-langgraph
# Option B: CrewAI-based agent
pip install cloudbase-agent-crewai
# Option C: Custom / minimal
pip install cloudbase-agent-server
```
**2. Create server entry point:**
```python
# server.py — this pattern works with ANY adapter
import os
from dotenv import load_dotenv
load_dotenv()
from cloudbase_agent.server import AgentServiceApp, AgentCreatorResult
# Import your agent (framework-specific, see adapter docs)
# from agents.chat.agent import create_my_agent
def create_agent() -> AgentCreatorResult:
agent = create_my_agent() # Your agent factory
return {"agent": agent}
app = AgentServiceApp()
app.set_cors_config(allow_origins=["*"])
if __name__ == "__main__":
port = int(os.environ.get("SCF_RUNTIME_PORT", "9000"))
app.run(create_agent, port=port, host="0.0.0.0")
```
**3. Deploy to CloudBase:**
Follow the **4-step deployment pipeline** in [agent-deployment](agent-deployment.md).
---
## Architecture
```
Client (React / MiniProgram / curl)
│ HTTP POST + SSE streaming
▼
┌─────────────────────────────────────────────┐
│ AgentServiceApp (FastAPI) │
│ ├─ /send-message ← AG-UI SSE │
│ ├─ /chat/completions ← OpenAI-compat │
│ └─ Middleware chain (onion model) │
├─────────────────────────────────────────────┤
│ Agent Layer │
│ ├─ LangGraphAgent ├─ CrewAIAgent │
│ ├─ LlamaIndexAgent ├─ CozeAgent/DifyAgent │
│ └─ BaseAgent (extend for custom) │
├──────────────────┬──────────────────────────┤
│ Tools │ Storage │
│ Bash/FS/Code/MCP│ Memory + LongTermMemory │
├─────────────────────────────────────────────┤
│ Observability (OpenTelemetry + Langfuse) │
└─────────────────────────────────────────────┘
```
## Installation
CloudBase Agent Python SDK is published to PyPI as separate packages. **Note: PyPI package names use hyphens (`cloudbase-agent-*`), and Python imports use the same namespace (`cloudbase_agent.*`)**.
```bash
# Core + Server + LangGraph (most common)
pip install cloudbase-agent-langgraph
# Individual packages
pip install cloudbase-agent-core # Core framework
pip install cloudbase-agent-server # FastAPI server
pip install cloudbase-agent-langgraph # LangGraph integration
pip install cloudbase-agent-tools # Tool system
pip install cloudbase-agent-storage # Memory/Storage
pip install cloudbase-agent-observability # OpenTelemetry/Langfuse
pip install cloudbase-agent-coze # Coze platform
pip install cloudbase-agent-crewai # CrewAI integration
```
**Import Note**: All packages share the `cloudbase_agent` namespace:
```python
# After installing cloudbase-agent-langgraph, import from cloudbase_agent
from cloudbase_agent.langgraph import LangGraphAgent
from cloudbase_agent.server import AgentServiceApp
from cloudbase_agent.tools import create_bash_tool
```
## Reference Documents
Based on what the user needs, read the corresponding reference document.
**Only read the relevant reference — don't load all of them.**
| User Need | Reference | What It Covers |
|-----------|-----------|---------------|
| **Deploying agent to CloudBase** | Read [agent-deployment](agent-deployment.md) | **manageAgent MCP tool (MUST USE)**, 4-step blocking pipeline, Python 3.10, env/ build, verification |
| Server setup, deployment, middleware, multi-agent, CORS | Read `references/server.md` | AgentServiceApp 3 deployment methods, middleware (generator/yield/onion model), multi-agent server, Agent Creator pattern, health checks |
| LangGraph agent, callbacks, tool proxy, HITL, checkpoints | Read [adapter-langgraph](adapter-langgraph.md) | LangGraphAgent constructor, AgentCallback protocol, ToolProxy, human-in-the-loop with interrupt(), TDAICheckpointSaver, client-defined tools |
| Tools: bash, filesystem, code execution, MCP, custom tools | Read `references/tools.md` | create_bash_tool, 8 file tools, code executors, MCPToolkit/CloudBaseMCPServer, @tool decorator, BaseTool, framework adapters |
| Memory, persistence, short/long-term, MySQL, MongoDB | Read `references/storage.md` | InMemoryMemory, TDAIMemory, MySQLMemory, MongoDBMemory, TDAILongTermMemory, Mem0LongTermMemory, LangGraph checkpoint |
| Tracing, monitoring, Langfuse, OpenTelemetry | Read `references/observability.md` | ConsoleTraceConfig, OTLPTraceConfig, setup_observability, env vars, manual observation spans |
| Common patterns, JWT auth, MCP integration, production | Read `references/recipes.md` | JWT middleware, MCP + LangGraph, production deployment, adding tools to agents, client-defined tools |
## Key Imports Quick Reference
```python
# Server
from cloudbase_agent.server import AgentServiceApp, AgentCreatorResult
from cloudbase_agent.server import create_send_message_adapter, create_openai_adapter
from cloudbase_agent.server import RunAgentInput, OpenAIChatCompletionRequest
# Agents
from cloudbase_agent.langgraph import LangGraphAgent
from cloudbase_agent.crewai import CrewAIAgent
# Tools
from cloudbase_agent.tools import create_bash_tool, create_read_tool, create_write_tool
from cloudbase_agent.tools import MCPToolkit, CloudBaseMCPServer, CloudBaseTool
from cloudbase_agent.tools import tool, BaseTool # custom tools
# Storage
from cloudbase_agent.storage import InMemoryMemory, TDAIMemory
from cloudbase_agent.storage import TDAILongTermMemory, Mem0LongTermMemory
from cloudbase_agent.langgraph import TDAICheckpointSaver, TDAIStore
# Observability
from cloudbase_agent.observability import ConsoleTraceConfig, OTLPTraceConfig, setup_observability
# Schemas
from cloudbase_agent.schemas import Message, MessageRole, StreamEvent, EventType
```
## Project Structure Convention
```
my-agent-project/
├── agents/
│ ├── agentic_chat/agent.py # build_workflow() → agent instance
│ ├── human_in_the_loop/agent.py
│ └── __init__.py
├── server.py # Main entry: AgentServiceApp().run(...)
├── scf_bootstrap # CloudBase startup script (required for deployment)
├── .env # OPENAI_API_KEY, etc.
└── requirements.txt
```
## Environment Variables
| Variable | Purpose |
|----------|---------|
| `OPENAI_API_KEY` | OpenAI API key |
| `AUTO_TRACES_STDOUT` | Enable console tracing (`true`) |
| `LANGFUSE_PUBLIC_KEY` / `LANGFUSE_SECRET_KEY` | Langfuse keys |
| `TDAI_ENDPOINT` / `TDAI_API_KEY` | TDAI memory/checkpoint endpoint |
| `SCF_RUNTIME_PORT` | CloudBase runtime port (set automatically during deployment) |
## Key Design Decisions
1. **Agent Creator Pattern**: Every request creates a fresh agent via factory function. Supports cleanup callbacks for resource release.
2. **Dual Protocol**: Every agent supports both AG-UI native (SSE + rich events) and OpenAI-compatible (`/chat/completions`).
3. **Middleware = Generator**: Use `yield` — pre-yield = pre-processing, post-yield = post-processing (onion model).
4. **Namespace Package**: `cloudbase_agent` spans multiple PyPI packages (cloudbase-agent-core, cloudbase-agent-server, cloudbase-agent-langgraph, etc.). PyPI names use hyphens, but all imports use `from cloudbase_agent.xxx import ...`.
5. **Observability Auto-Integration**: Install `cloudbase-agent-observability` and tracing works automatically — zero config needed.
6. **Deploy with manageAgent**: Always use the `manageAgent` MCP tool for CloudBase deployment. Follow the **4-step blocking pipeline** in [agent-deployment](agent-deployment.md).
references/cloudbase-agent/SKILL.md
---
name: cloudbase-agent
description: Build and deploy AI agents with CloudBase Agent SDK (TypeScript & Python). Implements the AG-UI protocol for streaming agent-UI communication. Use when deploying agent servers, using LangGraph/LangChain/CrewAI adapters, building custom adapters, understanding AG-UI protocol events, or building web/mini-program UI clients. Supports both TypeScript (@cloudbase/agent-server) and Python (cloudbase-agent-server via FastAPI).
version: 2.33.1
alwaysApply: false
---
## Sibling skills (local only)
Sibling CloudBase skills ship beside this skill. Use local relative paths such as `../auth-tool-cloudbase/SKILL.md`.
If a referenced sibling skill file is missing from this environment, ask the user to install the full CloudBase plugin (or the missing skill). Do **not** HTTP-fetch remote skill or protocol markdown into the agent context.
# CloudBase Agent SDK — Language Router
This skill supports **TypeScript** and **Python**. Determine the language first, then read the corresponding skill file. If the user does not explicitly specify which programming language to use, TypeScript must be enforced.
## Step 1: Determine Language
| Signal | Language |
|--------|----------|
| User says "TypeScript", "Node.js", "TS" | **TypeScript** |
| User says "Python", "FastAPI", "pip" | **Python** |
| No clear signal | **TypeScript** |
## Step 2: Read the Language-Specific Skill File
- **TypeScript** → Read [ts/skill.md](ts/skill.md) — then follow ALL instructions in that file
- **Python** → Read [py/skill.md](py/skill.md) — then follow ALL instructions in that file
**⚠️ IMPORTANT:** After determining the language, you MUST read the corresponding skill file above. Do NOT proceed with any code generation until you have read it. Each language skill file is self-contained with its own quick start, routing table, deployment instructions, and adapter guides.
references/cloudbase-agent/ts/adapter-development.md
# Building Custom Adapters
An adapter bridges your AI framework to the AG-UI protocol. It converts AG-UI input (messages, tools, state) into your framework's format, and converts your framework's streaming output into AG-UI events.
**Prerequisites:** Deep understanding of both your AI framework's API and the AG-UI protocol events.
**When to build your own:** No existing adapter for your framework (check AG-UI ecosystem first).
Extend `AbstractAgent` and implement `run()` that returns `Observable<BaseEvent>`.
## Structure
```typescript
import { AbstractAgent, RunAgentInput, BaseEvent, EventType } from "@ag-ui/client";
import { Observable, Subscriber } from "rxjs";
export class MyAdapter extends AbstractAgent {
run(input: RunAgentInput): Observable<BaseEvent> {
return new Observable((subscriber) => this._run(subscriber, input));
}
private async _run(subscriber: Subscriber<BaseEvent>, input: RunAgentInput) {
const { messages, runId, threadId, tools } = input;
subscriber.next({ type: EventType.RUN_STARTED, threadId, runId });
try {
// 1. Convert AG-UI input to your framework's format
// 2. Call your framework
// 3. Convert your framework's output to AG-UI events (see Event Sequence below)
subscriber.next({ type: EventType.RUN_FINISHED, threadId, runId });
} catch (error) {
subscriber.next({ type: EventType.RUN_ERROR, message: error.message });
}
subscriber.complete();
}
}
```
## Event Sequence (Brief)
**Text:** `TEXT_MESSAGE_START` → `TEXT_MESSAGE_CONTENT` (repeat) → `TEXT_MESSAGE_END`
**Tool call:** `TOOL_CALL_START` → `TOOL_CALL_ARGS` → `TOOL_CALL_END`
**Tool result (server-executed tools only):** `TOOL_CALL_RESULT`
Always emit full lifecycle. `parentMessageId` links tool calls to their parent message.
For complete event reference, see [AG-UI Protocol](https://docs.ag-ui.com/concepts/events).
references/cloudbase-agent/ts/adapter-langchain.md
# @cloudbase/agent-adapter-langchain
Adapter that wraps LangChain's `createAgent()` as an AG-UI compatible agent. Provides `LangchainAgent` wrapper class and `clientTools()` middleware for client tools support.
## Basic Usage
```typescript
import { createAgent as createLangchainAgent } from "langchain";
import { MemorySaver } from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";
import { LangchainAgent, clientTools } from "@cloudbase/agent-adapter-langchain";
const model = new ChatOpenAI({ model: "gpt-4o" });
const checkpointer = new MemorySaver();
const lcAgent = createLangchainAgent({
model,
checkpointer,
middleware: [clientTools()],
});
const agent = new LangchainAgent({ agent: lcAgent });
```
## Checkpointer (Required)
`LangchainAgent` requires the agent to be created with a checkpointer.
### MemorySaver (Development)
```typescript
import { MemorySaver } from "@langchain/langgraph";
const lcAgent = createLangchainAgent({
model,
checkpointer: new MemorySaver(),
middleware: [clientTools()],
});
```
### CloudBaseSaver (Production)
Persistent storage using Tencent CloudBase document database. On CloudBase cloud function/cloudrun, requests are authenticated - extract user ID from the JWT in Authorization header.
```typescript
import { run } from "@cloudbase/agent-server";
import { LangchainAgent, clientTools } from "@cloudbase/agent-adapter-langchain";
import { CloudBaseSaver } from "@cloudbase/agent-adapter-langgraph";
import { createAgent as createLangchainAgent } from "langchain";
import tcb from "@cloudbase/node-sdk";
const app = tcb.init({ env: process.env.CLOUDBASE_ENV_ID });
run({
createAgent: ({ request }) => {
// Extract user ID from JWT (sub field)
const token = request.headers.get("Authorization")?.slice(7);
const payload = JSON.parse(atob(token.split(".")[1]));
const userId = payload.sub;
const checkpointer = new CloudBaseSaver({
db: app.database(),
userId, // Multi-tenant isolation
});
const lcAgent = createLangchainAgent({
model,
checkpointer,
middleware: [clientTools()],
});
return { agent: new LangchainAgent({ agent: lcAgent }) };
},
port: 9000,
});
```
## With @cloudbase/agent-server
```typescript
import { run } from "@cloudbase/agent-server";
run({
createAgent: () => ({ agent }),
port: 9000,
});
```
## clientTools() Middleware
Enables client-defined tools in your LangChain agent:
- **Injects client tools** - Adds client tools to the LLM's available tool list
- **Routes to END** - When a client tool is called, skips ToolNode and routes to END so client can execute
references/cloudbase-agent/ts/adapter-langgraph.md
# @cloudbase/agent-adapter-langgraph
Adapter that wraps a compiled LangGraph `StateGraph` workflow as an AG-UI compatible agent. Provides `ClientStateAnnotation` with pre-wired `messages` and `client.tools` fields for seamless AG-UI protocol integration.
## Installation
```bash
npm install @cloudbase/agent-adapter-langgraph@latest @langchain/langgraph @langchain/openai
```
**Important:** Always use `@latest` for `@cloudbase/agent-*` packages to get the newest stable releases. Do NOT specify version ranges like `^1.0.0` or exact versions like `1.0.0`, as the package versions may not follow semantic versioning expectations and such versions may not exist.
For projects requiring version locking, install first with `@latest`, then commit `package-lock.json`.
## Exports
```typescript
import {
LanggraphAgent,
ClientStateAnnotation,
ClientState,
CloudBaseSaver // Tencent CloudBase checkpointer
} from "@cloudbase/agent-adapter-langgraph";
```
## Basic Usage
```typescript
import { LanggraphAgent } from "@cloudbase/agent-adapter-langgraph";
const agent = new LanggraphAgent({
compiledWorkflow: graph, // compiled StateGraph (required)
logger: myLogger, // optional
});
```
## Checkpointer (Required)
`LanggraphAgent` requires the workflow to be compiled with a checkpointer.
### MemorySaver (Development)
```typescript
import { MemorySaver } from "@langchain/langgraph";
const graph = workflow.compile({ checkpointer: new MemorySaver() });
```
### CloudBaseSaver (Production)
Persistent storage using Tencent CloudBase document database. On CloudBase cloud function/cloudrun, requests are authenticated - extract user ID from the JWT in Authorization header.
```typescript
import { run } from "@cloudbase/agent-server";
import { CloudBaseSaver, LanggraphAgent } from "@cloudbase/agent-adapter-langgraph";
import tcb from "@cloudbase/node-sdk";
const app = tcb.init({ env: process.env.CLOUDBASE_ENV_ID });
run({
createAgent: ({ request }) => {
// Extract user ID from JWT (sub field)
const token = request.headers.get("Authorization")?.slice(7);
const payload = JSON.parse(atob(token.split(".")[1]));
const userId = payload.sub;
const checkpointer = new CloudBaseSaver({
db: app.database(),
userId, // Multi-tenant isolation
});
const graph = workflow.compile({ checkpointer });
return { agent: new LanggraphAgent({ compiledWorkflow: graph }) };
},
port: 9000,
});
```
## Client Tools
Client tools are tools defined by the client, not the server. They let the agent request actions only the client can perform (e.g., show modal, navigate, access local storage). The flow:
1. **Client** defines tools with handlers and sends them in request
2. **Server** binds client tools alongside server tools, LLM can call any
3. **Server** detects client tool call → routes to END (doesn't execute)
4. **Client** receives `TOOL_CALL_*` events, executes handler locally
5. **Client** sends tool result back, agent resumes
### Server Side: Bind and Route
```typescript
import { ClientState } from "@cloudbase/agent-adapter-langgraph";
// 1. Bind client tools to model (alongside server tools)
async function chatNode(state: ClientState) {
const clientTools = state.client?.tools || [];
const modelWithTools = model.bindTools([...clientTools, ...serverTools]);
// ...
}
// 2. Route client tool calls to END (let client handle)
function shouldContinue(state: ClientState): "tools" | "end" {
const lastMessage = state.messages[state.messages.length - 1];
if (lastMessage.tool_calls?.length > 0) {
const hasServerToolCall = lastMessage.tool_calls.some(tc => serverToolNames.has(tc.name));
if (hasServerToolCall) return "tools"; // Server executes
}
return "end"; // Client tool or no tool → end, client handles
}
```
## Complete Workflow Pattern
```typescript
import { StateGraph, START, END, Command } from "@langchain/langgraph";
import { ClientStateAnnotation, ClientState } from "@cloudbase/agent-adapter-langgraph";
import { MemorySaver } from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";
import { SystemMessage } from "@langchain/core/messages";
import { RunnableConfig } from "@langchain/core/runnables";
async function chatNode(state: ClientState, config?: RunnableConfig) {
const model = new ChatOpenAI({ model: "gpt-4o" });
const modelWithTools = model.bindTools([...(state.client?.tools || [])], {
parallel_tool_calls: false, // Recommended: avoid race conditions
});
const response = await modelWithTools.invoke([
new SystemMessage({ content: "You are a helpful assistant." }),
...state.messages,
], config);
return new Command({ goto: END, update: { messages: [response] } });
}
const workflow = new StateGraph(ClientStateAnnotation)
.addNode("chat_node", chatNode)
.addEdge(START, "chat_node");
export const graph = workflow.compile({ checkpointer: new MemorySaver() });
```
## With @cloudbase/agent-server
Deploy your LangGraph workflow as an HTTP endpoint that speaks the AG-UI protocol. Clients can connect via SSE to stream events.
```typescript
import { run } from "@cloudbase/agent-server";
import { LanggraphAgent } from "@cloudbase/agent-adapter-langgraph";
run({
createAgent: () => ({
agent: new LanggraphAgent({ compiledWorkflow: graph })
}),
port: 3000
});
```
references/cloudbase-agent/ts/agent-deployment.md
# Agent Deployment Guide
## Core Principle
**Always use the `manageAgent` MCP tool to deploy Agent services.**
It natively supports SSE streaming, session persistence, and Node.js 20 runtime — purpose-built for Agent scenarios.
Do **NOT** use `createFunction` or `manageCloudRun` for Agent deployment.
## Why HTTP Cloud Functions First
| Dimension | HTTP Cloud Functions | CloudRun |
|-----------|---------------------|----------|
| SSE Streaming | ✅ Native support | ✅ Supported |
| WebSocket | ✅ Native support | ✅ Supported |
| Deployment Complexity | Low (no Dockerfile needed) | High (container config required) |
| Cost | Pay-per-invocation, scales to zero | Pay-per-instance-hour |
| Cold Start | Yes, mitigated with provisioned instances | Yes, mitigated with min instances |
| Supported Runtimes | Node.js, Python | Any |
## Deployment Steps (HTTP Cloud Functions)
1. Ensure project has `scf_bootstrap` startup script (see below)
2. Deploy using `manageAgent` MCP tool with `runtime="Nodejs20.19"`:
```
manageAgent(action="create", runtime="Nodejs20.19", installDependency=true, targetPath="...")
```
3. Set environment variables (OPENAI_API_KEY, OPENAI_BASE_URL, OPENAI_MODEL, etc.)
4. Verify SSE connectivity
> ⚠️ **CRITICAL**: Always set `installDependency=true` to let cloud install dependencies automatically. Without this, you'll get `ERR_MODULE_NOT_FOUND` errors.
> For server code and adapter usage, see [server-quickstart](server-quickstart.md) and [adapter-langgraph](adapter-langgraph.md).
## Dependency Alignment Policy (CRITICAL)
**Always use `latest` for `@cloudbase/agent-*` and `@langchain/*` packages. Never specify version ranges.**
**Reference example** (adapt based on your actual dependencies):
```json
{
"dependencies": {
"@cloudbase/agent-server": "latest",
"@cloudbase/agent-adapter-langgraph": "latest",
"@langchain/langgraph": "latest",
"@langchain/openai": "latest"
}
}
```
> **Why?** `@cloudbase/agent-adapter-langgraph` has peer dependency on specific `@langchain/core` versions. Specifying version ranges like `^0.3.44` causes `[ResourceNotFound.Package] Dependency error` during cloud build.
---
## Node.js Runtime Version
**Always select Node.js 20 runtime** (`runtime="Nodejs20"`):
- Full compatibility with all `@cloudbase/agent-*` packages
- ES Module support (`"type": "module"` in package.json)
- Stable and well-tested on the CloudBase platform
Do **NOT** use Node.js 16 or earlier — many SDK features require Node.js >= 20.
## Startup Script (scf_bootstrap)
The startup script must be named `scf_bootstrap` (no file extension), placed in the project root, and have executable permissions:
```bash
#!/bin/sh
node src/index.js
```
```bash
chmod +x scf_bootstrap
```
> **IMPORTANT**: The `scf_bootstrap` script should be minimal — just start the Node.js application. Do NOT include `npm install` in this script. Dependencies are handled during deployment.
> **NOTE**: Use `#!/bin/sh` (not `#!/bin/bash`) for maximum compatibility. The entry point should match your actual server entry file.
## Port & CORS
- Your server **should** listen on port `9000` (the default for CloudBase Agent)
- In production (CloudBase), CORS is handled by the API gateway — no need to enable it in code
- For local development, conditionally enable CORS via an environment variable (e.g., `ENABLE_CORS=true`)
## Environment Variables
| Variable | Required | Purpose |
|----------|----------|---------|
| `OPENAI_API_KEY` | ✅ | OpenAI API key or compatible service key |
| `OPENAI_BASE_URL` | ✅ | API base URL, e.g. `https://api.openai.com/v1` |
| `OPENAI_MODEL` | ✅ | Model name, e.g. `gpt-4o` or `gpt-3.5-turbo` |
| `LOG_LEVEL` | ❌ | Log level: `trace`/`debug`/`info`/`warn`/`error`/`fatal` (default: `info`) |
| `ENABLE_CORS` | ❌ | Set to `true` to enable CORS (local dev only) |
## When to Use CloudRun Instead
Despite HTTP Cloud Functions being preferred, use CloudRun in these cases:
- Custom Docker image required (special system-level dependencies like FFmpeg, Chromium, etc.)
- Resource requirements exceed Cloud Function limits
- Persistent local file storage needed
- Need to install native C extensions that require specific OS packages
For CloudRun deployment, use a Dockerfile:
```dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm i --production
COPY src ./src
ENV NODE_ENV=production
EXPOSE 9000
CMD ["node", "src/index.js"]
```
## Summary
| Decision | Choice |
|----------|--------|
| **Deployment tool** | `manageAgent` MCP tool (MUST USE) |
| **Node.js runtime** | Node.js 20.19 (MUST USE, `runtime="Nodejs20.19"`) |
| **Dependency install** | `installDependency=true` (MUST SET, or get `ERR_MODULE_NOT_FOUND`) |
| **Default platform** | HTTP Cloud Functions |
| **Fallback platform** | CloudRun (only for special requirements) |
| **Startup script** | `scf_bootstrap` — `#!/bin/sh` + `node src/index.js` |
| **Port** | Listen on port `9000` |
| **CORS** | Production uses API gateway; local dev via `ENABLE_CORS` env var |
| **Module system** | ES Modules (`"type": "module"` in package.json) |
references/cloudbase-agent/ts/agui-protocol.md
# AG-UI Protocol
Open, event-based protocol for agent-UI communication. Server streams events to client via SSE.
## Event Patterns
**Start-Content-End**: For streaming content
```
TEXT_MESSAGE_START → TEXT_MESSAGE_CONTENT (repeat) → TEXT_MESSAGE_END
TOOL_CALL_START → TOOL_CALL_ARGS (repeat) → TOOL_CALL_END
```
**Lifecycle**: Wraps every agent run
```
RUN_STARTED → [events] → RUN_FINISHED | RUN_ERROR
```
**Snapshot-Delta**: For state sync
```
STATE_SNAPSHOT (full state) → STATE_DELTA (JSON Patch updates)
```
## Core Events
| Event | Key Fields |
|-------|------------|
| `RUN_STARTED` | threadId, runId |
| `RUN_FINISHED` | threadId, runId |
| `RUN_ERROR` | message, code? |
| `TEXT_MESSAGE_START` | messageId, role |
| `TEXT_MESSAGE_CONTENT` | messageId, delta |
| `TEXT_MESSAGE_END` | messageId |
| `TOOL_CALL_START` | toolCallId, toolCallName, parentMessageId? |
| `TOOL_CALL_ARGS` | toolCallId, delta |
| `TOOL_CALL_END` | toolCallId |
| `TOOL_CALL_RESULT` | toolCallId, messageId, content |
| `STATE_SNAPSHOT` | snapshot |
| `STATE_DELTA` | delta (RFC 6902 JSON Patch) |
| `MESSAGES_SNAPSHOT` | messages[] |
## Input Types
```typescript
interface RunAgentInput {
threadId: string;
runId: string;
messages: Message[];
tools: Tool[];
state?: unknown;
context?: Context[];
forwardedProps?: Record<string, unknown>;
}
interface Message {
id: string;
role: "user" | "assistant" | "system" | "tool";
content: string;
name?: string; // for tool messages
toolCalls?: ToolCall[]; // for assistant messages
toolCallId?: string; // for tool messages
}
interface ToolCall {
id: string;
type: "function";
function: { name: string; arguments: string };
}
interface Tool {
name: string;
description: string;
parameters?: JSONSchema;
}
```
## Tool Execution Flow
**Server-executed tools:**
1. Agent emits `TOOL_CALL_START/ARGS/END`
2. Server executes tool
3. Server emits `TOOL_CALL_RESULT`
4. Agent continues with result
**Client tools:**
1. Agent emits `TOOL_CALL_START/ARGS/END`
2. Server emits `RUN_FINISHED` (run pauses)
3. Client executes tool locally
4. Client sends new request with tool result in messages
5. Agent continues
## Full Reference
For complete protocol specification: https://docs.ag-ui.com/concepts/events
references/cloudbase-agent/ts/server-quickstart.md
# @cloudbase/agent-server
Deploy AG-UI compatible agents as HTTP servers.
## Installation
```bash
npm install @cloudbase/agent-server@latest
```
**Important:** Always use `@latest` for `@cloudbase/agent-*` packages. Do NOT use version ranges like `^1.0.0` or exact versions, as these versions may not exist. The packages do not follow traditional semantic versioning.
## Deployment Methods
### run() - Standalone
```typescript
import { run } from "@cloudbase/agent-server";
run({ createAgent: () => ({ agent }), port: 9000 });
```
### createExpressServer() - Get App
```typescript
import { createExpressServer } from "@cloudbase/agent-server";
const app = createExpressServer({ createAgent: () => ({ agent }) });
app.listen(9000);
```
### createExpressRoutes() - Add to Existing
```typescript
import { createExpressRoutes } from "@cloudbase/agent-server";
createExpressRoutes({ createAgent: () => ({ agent }), express: app, basePath: "/api/" });
```
## Endpoints Created
| Endpoint | Purpose |
|----------|---------|
| `/agui` | CopilotKit RPC endpoint |
| `/send-message` | AG-UI endpoint (SSE) |
| `/healthz` | Health check |
| `/chat/completions` | OpenAI-compatible endpoint |
| `/v1/aibot/bots/:agentId/...` | Same endpoints with bot ID (when no basePath) |
## AgentCreatorContext
```typescript
interface AgentCreatorContext {
request: Request; // Web Standard Request
logger?: Logger; // Pino-style logger (AGUI routes only)
requestId?: string; // Unique request ID (AGUI routes only)
}
createAgent: (ctx: AgentCreatorContext) => ({
agent, // Your adapter instance
cleanup?: () => void // Called when request ends
})
```
## Cleanup Pattern
```typescript
createAgent: (ctx) => {
const db = connectToDatabase();
ctx.logger?.info("Connected to database");
return {
agent: new LanggraphAgent({ workflow }),
cleanup: () => db.close()
};
}
```
## All Options
```typescript
run({
createAgent,
port: 9000,
basePath: "/api/", // Custom base path (default: dual endpoints)
cors: true, // or { origin: "https://..." }
useAGUI: true, // Enable /agui endpoint (default: true)
aguiOptions: {
runtimeOptions: {}, // CopilotRuntimeOptions
endpointOptions: {} // CreateCopilotRuntimeServerOptions
},
logger: createConsoleLogger("debug"),
observability: { type: "otlp", url: "...", headers: {...} }
});
```
## Logger Exports
```typescript
import {
noopLogger, // Silent logger (default)
createConsoleLogger, // Console logger
generateRequestId,
extractRequestId,
getOrGenerateRequestId
} from "@cloudbase/agent-server";
// Custom logger (Pino-style interface)
const logger = {
info: (obj, msg) => console.log(msg, obj),
error: (obj, msg) => console.error(msg, obj),
debug: (obj, msg) => console.debug(msg, obj),
child: (bindings) => ({ ...logger })
};
```
## Observability
Requires `@cloudbase/agent-observability` package:
```typescript
run({
createAgent,
observability: { type: "console" } // Logs traces to stdout
});
// OTLP exporter (Langfuse, Jaeger, etc.)
run({
createAgent,
observability: {
type: "otlp",
url: "https://cloud.langfuse.com/api/public/otlp/v1/traces",
headers: { Authorization: "Basic xxx" }
}
});
// Multiple exporters
run({
createAgent,
observability: [
{ type: "console" },
{ type: "otlp", url: "http://localhost:4318/v1/traces" }
]
});
```
## Error Handling
```typescript
import { ErrorCode, isErrorWithCode } from "@cloudbase/agent-server";
// ErrorCode enum values for error handling
```
references/cloudbase-agent/ts/skill.md
---
name: cloudbase-agent
description: "Build and deploy AI agents with Cloudbase Agent (TypeScript), a TypeScript SDK implementing the AG-UI protocol. Use when: (1) deploying agent servers with @cloudbase/agent-server, (2) using LangGraph adapter with ClientStateAnnotation, (3) using LangChain adapter with clientTools(), (4) building custom adapters that implement AbstractAgent, (5) understanding AG-UI protocol events, (6) building web UI clients with @ag-ui/client, (7) building WeChat Mini Program UIs with @cloudbase/agent-ui-miniprogram."
version: 2.21.1
alwaysApply: true
---
# Cloudbase Agent (TypeScript)
TypeScript SDK for deploying AI agents as HTTP services using the AG-UI protocol.
> **Note:** This skill is for **TypeScript/JavaScript** projects only.
## When to use this skill
Use this skill for **AI agent development** when you need to:
- Deploy AI agents as HTTP services with AG-UI protocol support
- Build agent backends using LangGraph or LangChain frameworks
- Create custom agent adapters implementing the AbstractAgent interface
- Understand AG-UI protocol events and message streaming
- Build web UI clients that connect to AG-UI compatible agents
- Build WeChat Mini Program UIs for AI agent interactions
**Do NOT use for:**
- Simple AI model calling without agent capabilities (use `ai-model-*` skills)
- CloudBase cloud functions (use `cloud-functions` skill)
- CloudRun backend services without agent features (use `cloudrun-development` skill)
## How to use this skill (for a coding agent)
1. **Choose the right adapter**
- Use LangGraph adapter for stateful, graph-based workflows
- Use LangChain adapter for chain-based agent patterns
- Build custom adapter for specialized agent logic
2. **Deploy the agent server**
- Use `@cloudbase/agent-server` to expose HTTP endpoints
- Configure CORS, logging, and observability as needed
- **Prefer deploying to CloudBase using `manageAgent` MCP tool** (see [agent-deployment](agent-deployment.md))
- **Before deploy, read Dependency Alignment Policy in [agent-deployment](agent-deployment.md) to avoid cloud build dependency errors**
3. **Build the UI client**
- Use `@ag-ui/client` for web applications
- Use `@cloudbase/agent-ui-miniprogram` for WeChat Mini Programs
- Connect to the agent server's `/send-message` or `/agui` endpoints
4. **Follow the routing table below** to find detailed documentation for each task
## Routing
| Task | Read |
|------|------|
| Deploy agent to CloudBase (**read this first**) | [agent-deployment](agent-deployment.md) |
| Deploy agent server (@cloudbase/agent-server) | [server-quickstart](server-quickstart.md) |
| Use LangGraph adapter | [adapter-langgraph](adapter-langgraph.md) |
| Use LangChain adapter | [adapter-langchain](adapter-langchain.md) |
| Build custom adapter | [adapter-development](adapter-development.md) |
| Understand AG-UI protocol | [agui-protocol](agui-protocol.md) |
| Build UI client (Web or Mini Program) | [ui-clients](ui-clients.md) |
| Deep-dive @cloudbase/agent-ui-miniprogram | [ui-miniprogram](ui-miniprogram.md) |
## Quick Start
**Prerequisites:** Node.js >= 20 is required.
**1. Install dependencies:**
```bash
npm install @cloudbase/agent-server@latest @cloudbase/agent-adapter-langgraph@latest
```
**Critical:** Always use `@latest` for all `@cloudbase/agent-*` packages. For dependency version rules, see [Dependency Alignment Policy](agent-deployment.md#dependency-alignment-policy-critical) in agent-deployment.md.
**2. Create and run your agent:**
```typescript
import { run } from "@cloudbase/agent-server";
import { LanggraphAgent } from "@cloudbase/agent-adapter-langgraph";
run({
createAgent: () => ({ agent: new LanggraphAgent({ workflow }) }),
port: 9000,
});
```
references/cloudbase-agent/ts/ui-clients.md
# Building UI Clients
Connect your UI to AG-UI endpoints served by @cloudbase/agent-server.
## Web Applications
Use `@ag-ui/client` (official AG-UI SDK):
```bash
npm install @ag-ui/client@latest
```
```typescript
import { HttpAgent } from "@ag-ui/client";
const agent = new HttpAgent({ url: "http://localhost:9000/send-message" });
for await (const event of agent.run({
threadId: "thread-1",
runId: "run-1",
messages: [{ id: "m1", role: "user", content: "Hello" }]
})) {
console.log(event.type, event);
}
```
See AG-UI documentation for full API: https://docs.ag-ui.com
## WeChat Mini Program
Use `@cloudbase/agent-ui-miniprogram` (headless behavior mixin):
```bash
npm install @cloudbase/agent-ui-miniprogram@latest
```
```typescript
import { createAGUIBehavior, CloudbaseTransport } from "@cloudbase/agent-ui-miniprogram";
Component({
behaviors: [createAGUIBehavior({
transport: new CloudbaseTransport({ botId: "your-bot-id" })
})],
methods: {
onSend() {
this.agui.sendMessage(this.data.inputText);
}
}
});
// State: this.data.agui.uiMessages, this.data.agui.isRunning
```
Beyond basic usage, the package offers more `createAGUIBehavior` options, `this.agui.*` namespace methods, state getters, and UIMessage format for rendering.
references/cloudbase-agent/ts/ui-miniprogram.md
# @cloudbase/agent-ui-miniprogram
WeChat Mini Program SDK for AG-UI protocol. Headless behavior mixin pattern.
## Installation
```bash
npm install @cloudbase/agent-ui-miniprogram@latest
```
## Basic Usage
```typescript
import { createAGUIBehavior, CloudbaseTransport } from "@cloudbase/agent-ui-miniprogram";
const transport = new CloudbaseTransport({ botId: "your-bot-id" });
Component({
behaviors: [createAGUIBehavior({ transport })],
methods: {
onSend() {
this.agui.sendMessage(this.data.inputText);
}
}
});
```
## createAGUIBehavior Options
```typescript
createAGUIBehavior({
transport, // Transport instance (CloudbaseTransport)
messages: [], // Initial message history
tools: [{ // Client tools the agent can invoke
name: "get_weather",
description: "Get weather",
parameters: { type: "object", properties: { city: { type: "string" } } },
handler: async ({ args }) => ({ temp: 72 })
}],
threadId: "custom-id", // Custom thread ID (auto-generated if omitted)
contexts: [], // Additional context objects
onRawEvent: (event) => {} // Callback for each raw AG-UI event
})
```
## Namespace Methods (this.agui.*)
| Method | Description |
|--------|-------------|
| `init({ transport, threadId? })` | Initialize transport at runtime |
| `sendMessage(text \| Message[])` | Send message and run agent |
| `appendMessage(message)` | Add message without running agent |
| `setMessages(messages)` | Replace entire message history |
| `reset()` | Reset to initial state |
| `setThreadId(id)` | Change thread ID |
| `addTool(tool)` | Register a client tool |
| `removeTool(name)` | Remove tool by name |
| `updateTool(name, updates)` | Update tool properties |
| `clearTools()` | Remove all tools |
## State Getters (this.agui.* or this.data.agui.*)
| Property | Type | Description |
|----------|------|-------------|
| `messages` | `Message[]` | Raw message history |
| `uiMessages` | `UIMessage[]` | Messages formatted for UI rendering |
| `isRunning` | `boolean` | Agent is processing |
| `runId` | `string \| null` | Current run ID |
| `activeToolCalls` | `ToolCallState[]` | Tool calls in progress |
| `error` | `AGUIClientError \| null` | Last error |
| `threadId` | `string` | Current thread ID |
| `tools` | `Tool[]` | Registered tools (definitions only) |
| `contexts` | `Context[]` | Context objects |
| `config` | `CreateAGUIBehaviorOptions` | Current configuration |
## CloudbaseTransport
Production transport for WeChat Cloud Development:
```typescript
import { CloudbaseTransport } from "@cloudbase/agent-ui-miniprogram";
const transport = new CloudbaseTransport({
botId: "bot-xxxxxx" // From Cloud Development console
});
```
Requires `wx.cloud.extend.AI.bot.sendMessage` API.
## Imperative Pattern
Use `aguiBehavior` (no static config) for runtime-only initialization:
```typescript
import { aguiBehavior, CloudbaseTransport } from "@cloudbase/agent-ui-miniprogram";
Component({
behaviors: [aguiBehavior],
lifetimes: {
attached() {
this.agui.init({
transport: new CloudbaseTransport({ botId: "my-bot" })
});
}
}
});
```
## Client Tool Example
```typescript
Component({
behaviors: [createAGUIBehavior({ transport })],
lifetimes: {
attached() {
this.agui.addTool({
name: "show_toast",
description: "Show a toast message",
parameters: {
type: "object",
properties: { title: { type: "string" } },
required: ["title"]
},
handler: async ({ args }) => {
wx.showToast({ title: args.title });
return { success: true };
}
});
}
}
});
```
## UIMessage Format
`uiMessages` groups consecutive same-role messages with parts:
```typescript
interface UIMessage {
id: string;
role: "user" | "assistant";
parts: (TextPart | ToolPart)[];
}
interface TextPart { type: "text"; text: string; }
interface ToolPart {
type: "tool";
toolCallId: string;
name: string;
args?: Record<string, unknown>;
status: "pending" | "ready" | "executing" | "completed" | "failed";
result?: unknown;
error?: AGUIClientError;
}
```
references/cloudbase-cli/references/access.md
# Access — CloudBase CLI
Three independent modules for configuring external access to CloudBase environments:
| Module | Commands | Purpose |
|--------|----------|---------|
| **CORS** | `tcb cors list/add/rm` | Security domains for cross-origin access |
| **Domains** | `tcb domains ls/add/rm` | Bind/unbind custom domains with TLS |
| **Routes** | `tcb routes list/add/edit/delete` | Map request paths to backend services |
> ⚠️ Routes require the domain to exist first — use the system default domain or bind one via `tcb domains add` before creating routes.
---
## When to Use
- Configuring CORS security domains for cross-origin access
- Binding or unbinding custom domains to a CloudBase environment
- Creating, editing, or deleting routing rules (path -> service mapping)
- Setting up a complete domain + routing + CORS workflow
## Do NOT use for
- Storage ACL permissions (use `tcb-storage`)
- Role-based access control / user permissions (use `tcb-permission`)
- Static file hosting deployment (use `tcb-hosting`)
- Web app deployment (use `tcb-app`)
---
## Workflow 1: CORS Configuration
### Step 1 — List current security domains
```bash
tcb cors list -e <envId> --json
```
### Step 2 — Add domains
```bash
# Single domain
tcb cors add api.example.com -e <envId> --yes
# Multiple domains (comma-separated, no protocol prefix)
tcb cors add localhost:3000,dev.example.com,app.example.com -e <envId> --yes
```
> ⚠️ CORS domains do NOT auto-include subdomains — each subdomain must be added separately.
### Step 3 — Remove domains
```bash
tcb cors rm old.example.com -e <envId> --yes
```
### Step 4 — Verify
```bash
tcb cors list -e <envId> --json
```
**Parameters:** `<domain>` (no `https://` prefix, comma-separated for multiple), `-e/--envId`, `--yes`, `--json`, `--dry-run`
---
## Workflow 2: Custom Domain Binding
### Step 1 — Check existing domains
```bash
tcb domains ls -e <envId> --json
```
### Step 2 — Bind domain (SSL cert required)
```bash
# Direct connection (default)
tcb domains add api.example.com --certid <certId> -e <envId> --yes
# CDN-accelerated
tcb domains add cdn.example.com --certid <certId> --access-type CDN -e <envId> --yes
# Custom CNAME
tcb domains add custom.example.com --certid <certId> --access-type CUSTOM --custom-cname <cname> -e <envId> --yes
```
**Access types:** `DIRECT` (default, request goes straight to CloudBase), `CDN` (CDN-accelerated), `CUSTOM` (custom CNAME target)
### Step 3 — Configure DNS
After binding, set a CNAME record pointing your domain to the CloudBase endpoint returned in the response.
### Step 4 — Verify binding
```bash
tcb domains ls -e <envId> --filter "Domain=api.example.com" --json
```
### Step 5 — Unbind domain
> ⚠️ If the domain has routes bound, you MUST delete all routes first, then unbind the domain.
```bash
# Check for routes on this domain
tcb routes list -e <envId> --filter "Domain=api.example.com" --json
# Delete routes first (if any)
tcb routes delete api.example.com -e <envId> -p /api/users --yes
# Then unbind domain
tcb domains rm api.example.com -e <envId> --yes
```
**Parameters:** `<domain>`, `--certid` (required for add), `--access-type`, `--custom-cname`, `--disable`, `--filter`, `--offset/--limit`
---
## Workflow 3: Routing Rules
### Step 1 — List routes
```bash
tcb routes list -e <envId> --json
tcb routes list -e <envId> --filter "Domain=api.example.com" --json
```
### Step 2 — Create routes
```bash
# Single route
tcb routes add -e <envId> --data '{
"domain": "api.example.com",
"routes": [{
"path": "/api/users",
"upstreamResourceType": "CBR",
"upstreamResourceName": "user-service"
}]
}' --yes
# Multiple routes in one call
tcb routes add -e <envId> --data '{
"domain": "api.example.com",
"routes": [
{"path": "/api/users", "upstreamResourceType": "CBR", "upstreamResourceName": "user-service"},
{"path": "/api/orders", "upstreamResourceType": "CBR", "upstreamResourceName": "order-service"},
{"path": "/api/fn", "upstreamResourceType": "SCF", "upstreamResourceName": "my-function"}
]
}' --yes
```
> ⚠️ If the path already exists under that domain, `routes add` will fail — use `routes edit` instead.
**`upstreamResourceType` values:** `CBR` (CloudBase Run), `SCF` (Cloud Function), `STATIC_STORE` (Static Hosting), `WEB_SCF` (Web Cloud Function), `LH` (Lighthouse)
### Step 3 — Edit routes (incremental update)
`routes edit` is an **incremental update** — only pass `domain`, `path` (to locate), and the fields you want to change:
```bash
# Enable auth on existing route
tcb routes edit -e <envId> --data '{
"domain": "api.example.com",
"routes": [{"path": "/api/users", "enableAuth": true}]
}' --yes
# Add QPS rate limiting
tcb routes edit -e <envId> --data '{
"domain": "api.example.com",
"routes": [{
"path": "/api/users",
"qpsPolicy": {"qpsTotal": 500, "qpsPerClient": {"limitBy": "ClientIP", "limitValue": 50}}
}]
}' --yes
```
> ⚠️ No need to repeat `upstreamResourceType`/`upstreamResourceName` when editing — only changed fields required.
### Step 4 — Delete routes
```bash
tcb routes delete api.example.com -e <envId> -p /api/users --yes
```
> ⚠️ `-p <path>` is **required** for `routes delete` — omitting it will error.
### Route JSON fields
| Field | Required | Description |
|-------|:--------:|-------------|
| `domain` | ✅ | System default or custom-bound domain |
| `routes[].path` | ✅ | Route path (no wildcards) |
| `routes[].upstreamResourceType` | ✅ (add) | Backend service type |
| `routes[].upstreamResourceName` | ✅ (add) | Backend service name |
| `routes[].enable` | | Enable route (default: true) |
| `routes[].enableAuth` | | Enable auth (default: false) |
| `routes[].enableSafeDomain` | | Enable CORS domain check (default: true) |
| `routes[].pathRewrite.prefix` | | Path rewrite prefix |
| `routes[].qpsPolicy.qpsTotal` | | Total QPS limit (max 500) |
---
## Complete Scenario: Domain + Routes + CORS
```bash
ENV_ID="env-xxx"
DOMAIN="api.example.com"
CERT_ID="cert-abc123"
# 1. Add CORS for frontend
tcb cors add app.example.com -e $ENV_ID --yes
# 2. Bind custom domain
tcb domains add $DOMAIN --certid $CERT_ID -e $ENV_ID --yes
# 3. Configure DNS CNAME (manual step)
# 4. Create routes
tcb routes add -e $ENV_ID --data "{
\"domain\": \"$DOMAIN\",
\"routes\": [
{\"path\": \"/api/users\", \"upstreamResourceType\": \"CBR\", \"upstreamResourceName\": \"user-service\"},
{\"path\": \"/api/fn\", \"upstreamResourceType\": \"SCF\", \"upstreamResourceName\": \"my-function\"}
]
}" --yes
# 5. Verify everything
tcb cors list -e $ENV_ID --json
tcb domains ls -e $ENV_ID --filter "Domain=$DOMAIN" --json
tcb routes list -e $ENV_ID --filter "Domain=$DOMAIN" --json
```
### Teardown (reverse order)
```bash
# Delete routes first
tcb routes delete $DOMAIN -e $ENV_ID -p /api/users --yes
tcb routes delete $DOMAIN -e $ENV_ID -p /api/fn --yes
# Unbind domain
tcb domains rm $DOMAIN -e $ENV_ID --yes
# Remove CORS entry
tcb cors rm app.example.com -e $ENV_ID --yes
```
---
## Command Quick Reference
```bash
# CORS
tcb cors list -e <envId> [--json]
tcb cors add <domain> -e <envId> --yes # comma-separated for multiple
tcb cors rm <domain> -e <envId> --yes
# Domains
tcb domains ls -e <envId> [--json] [--filter "Domain=xxx"]
tcb domains add <domain> --certid <certId> -e <envId> --yes [--access-type CDN]
tcb domains rm <domain> -e <envId> --yes
# Routes
tcb routes list -e <envId> [--json] [--filter "Domain=xxx"]
tcb routes add -e <envId> --data '<json>' --yes
tcb routes edit -e <envId> --data '<json>' --yes # incremental update
tcb routes delete <domain> -e <envId> -p <path> --yes
```
---
## Common Errors
| Error | Cause | Fix |
|-------|-------|-----|
| `域名 xxx 不存在` | Route references unbound domain | Use system default domain, or `domains add` first |
| `域名下有路由绑定` | Trying to unbind domain with routes | Delete all routes on that domain first |
| `路径 xxx 已存在` | Duplicate path on `routes add` | Use `routes edit` to modify existing route |
| `请提供 -p 参数` | `routes delete` missing path | Add `-p <path>` parameter |
| `域名 xxx 已存在` | Duplicate CORS/domain add | Skip, or remove then re-add |
| `证书 xxx 不存在` | Invalid cert ID | Get correct ID from Tencent Cloud SSL console |
| `域名未备案` | Domain lacks ICP filing | Complete ICP filing first |
---
## Self-Check
- [ ] `tcb` >= 3.0.0 and logged in with correct environment
- [ ] CORS: domain format has no protocol prefix (`api.example.com`, not `https://api.example.com`)
- [ ] Domains: SSL certificate ID is ready (`--certid`)
- [ ] Domains: domain has ICP filing completed
- [ ] Routes: target domain exists (system default or custom-bound)
- [ ] Routes: using `routes add` for new paths, `routes edit` for existing paths
- [ ] Routes: `--data` JSON is valid and includes `domain` + `routes[].path`
- [ ] Unbind sequence: delete routes first, then unbind domain
- [ ] Added `--yes` for CI; `--json` for programmatic parsing
references/cloudbase-cli/references/app.md
# App — CloudBase CLI
> ⚠️ **Agent default: prefer MCP `manageApps` / `manageHosting`, or CLI `hosting.md` (local build + `tcb hosting deploy`).**
> `tcb deploy` in this file is an experimental all-in-one shorthand — use only when the user explicitly asks for it.
Deploy web applications with automatic framework detection, cloud build, and CDN hosting.
App = framework build + deploy; for pre-built static files only, use `hosting` instead.
## When to Use
- User **explicitly** asks for `tcb deploy` / zero-config cloud-build web deploy
- Managing web app versions, build status, or redeployment after that path was already chosen
- Deploying monorepo sub-projects with that experimental flow
## Do NOT use for
- Default agent fallback when MCP is missing — use `hosting` (or MCP when available)
- Pre-built static files without build step — use `hosting`
- Cloud functions — use `functions`
- Containerized long-running services — use `cloudrun`
- Database operations — use `mysql` or `nosql`
---
## Workflow 1: First Deploy (Zero Config)
```bash
# 1. Confirm target environment
tcb env list
# 2. Deploy from project root (auto-detects framework)
tcb deploy --env-id <envId>
# Or specify a service name
tcb deploy my-app --env-id <envId>
```
CLI auto-completes: detect framework -> infer build command + output dir -> upload to COS -> cloud build (~3-5 min) -> output access URL -> save config to `cloudbaserc.json`.
### Supported Frameworks
| Framework | Detection signal | Default build cmd | Default output dir |
|-----------|-----------------|-------------------|-------------------|
| React | `react-scripts` in package.json | `npm run build` | `build` |
| Vue | `@vue/cli-service` / `vite` | `npm run build` | `dist` |
| Vite | `vite` in devDependencies | `npm run build` | `dist` |
| Next.js | `next` in dependencies | `npm run build` | `.next` |
| Nuxt | `nuxt` in dependencies | `npm run build` | `.output` |
| Angular | `@angular/core` | `ng build` | `dist/<name>` |
| Static | No build tool detected | _(none)_ | `.` |
> ⚠️ If framework is not detected (`Cannot auto-detect project framework`), specify explicitly with `--framework react --build-command "npm run build" --output-dir dist`.
---
## Workflow 2: Redeployment
```bash
# Redeploy (reads saved config from cloudbaserc.json)
tcb deploy --env-id <envId>
# Force overwrite — skip confirmation prompt
tcb deploy my-app --env-id <envId> --force
```
> ⚠️ Overwrite creates a new version (e.g. `my-app-002 -> my-app-003`). Previous versions are preserved, never deleted.
### Monorepo Sub-project
```bash
# Option A: CLI flag (relative path only)
tcb deploy my-app --env-id <envId> --cwd ./packages/frontend
# Option B: cloudbaserc.json
# { "app": { "root": "packages/frontend" } }
```
> ⚠️ `--cwd` must be a relative path, not absolute. `root` in config is relative to `cloudbaserc.json`, not CWD.
---
## Workflow 3: Version Management
```bash
# List all versions
tcb app versions list my-app --env-id <envId>
# View latest version details
tcb app versions detail my-app --env-id <envId>
# View a specific version
tcb app versions detail my-app --version-name my-app-001 --env-id <envId>
# Extract fail reason (JSON mode)
tcb app versions detail my-app --env-id <envId> --json | jq '.data.failReason'
```
Build status values: `PENDING` (waiting) | `BUILDING` (in progress) | `SUCCESS` | `FAILED` (check `failReason`).
---
## Workflow 4: Deletion
```bash
# Preview deletion (always do this first)
tcb app delete my-app --env-id <envId> --dry-run
# Interactive confirmation
tcb app delete my-app --env-id <envId>
# Skip confirmation (CI/CD)
tcb app delete my-app --env-id <envId> --yes
```
> ⚠️ Deletion is irreversible — removes the app and all its versions.
---
## cloudbaserc.json App Config
Auto-saved after first deployment:
```json
{
"envId": "env-xxx",
"app": {
"serviceName": "my-app",
"framework": "react",
"installCommand": "npm install",
"buildCommand": "npm run build",
"outputDir": "./dist",
"deployPath": "/my-app",
"root": "packages/frontend",
"envVariables": { "REACT_APP_API": "https://api.example.com" },
"ignore": ["tests/**", "docs/**"]
}
}
```
| Field | Notes |
|-------|-------|
| `serviceName` | Auto-inferred from directory name if not specified |
| `installCommand` | ⚠️ Omitted = skipped in `--yes`/`--json` mode (unless `package.json` exists) |
| `buildCommand` | Auto-detected; omit to skip build |
| `outputDir` | ⚠️ Use `./dist` for builds, `./` for static-only. Always use `./` prefix |
| `deployPath` | Defaults to `/<serviceName>`. Only non-default values are saved to config |
| `envVariables` | ⚠️ Build-time only — injected during `npm run build`, NOT at runtime. Never put secrets here |
| `ignore` | Resolved from `cloudbaserc.json` location. `node_modules`/`.git` always excluded |
---
## Command Quick Reference
```bash
tcb deploy [name] --env-id <id> # Deploy (shorthand)
tcb app deploy [name] --env-id <id> # Deploy (full form)
tcb app list # List all apps
tcb app info <name> --env-id <id> # App details
tcb app versions list <name> --env-id <id> # List versions
tcb app versions detail <name> --env-id <id> # Version details
tcb app delete <name> --env-id <id> # Delete app
```
Key flags:
| Flag | Purpose |
|------|---------|
| `--framework <name>` | Override auto-detection |
| `--build-command <cmd>` | Override build command (empty string to skip) |
| `--output-dir <dir>` | Override output directory |
| `--deploy-path <path>` | CDN mount path (default: `/<serviceName>`) |
| `--cwd <path>` | Project directory for monorepo |
| `--force` | Skip overwrite confirmation |
| `--yes` | Skip all interactive prompts |
| `--json` | JSON output for CI/CD |
| `--verbose` | Verbose output for debugging |
### `--yes` / `--json` Auto-detection Logic
| Parameter | Auto-detection |
|-----------|----------------|
| `installCommand` | Has `package.json`? -> `npm install`; otherwise skip |
| `buildCommand` | Detect `build`/`pack`/`prebuild` script; otherwise skip |
| `outputDir` | Has build command? -> `./dist`; otherwise `./` |
| `deployPath` | Default `/<serviceName>` |
> ⚠️ `--yes` without `--env-id` hangs in CI — non-interactive mode cannot open the environment selector. Always pass both.
To skip build entirely in CI: `tcb deploy my-app --env-id env-xxx --build-command '' --output-dir './' --yes`
---
## Common Errors
| Error | Cause | Fix |
|-------|-------|-----|
| Build failed (`FAILED`) | Wrong build command, missing deps, Node.js mismatch | Check `failReason` via `--json`; verify local build works; cloud uses Node 18 |
| Framework not detected | No framework signature in `package.json` | Specify `--framework`, `--build-command`, `--output-dir` explicitly |
| Directory not found | `--cwd` or `root` points to non-existent path | Verify path with `ls`; use relative path |
| Build timeout (5 min) | Large upload or slow build | Add `ignore` patterns; check for accidental `node_modules` upload |
| Name conflict prompt | App already exists | Use `--force` or `--yes` to skip; creates new version |
| URL unreachable after deploy | CDN propagation or bad `outputDir` | Wait 1-2 min; verify `outputDir` contains `index.html` |
| Env ID required | `--yes`/`--json` without `--env-id` | Always pass `--env-id` in non-interactive mode |
---
## ⚠️ 打包目录 ≠ 上传目录(大目录被整个打进 zip 的坑)
**`tcb app deploy` / `manageApps(deployApp)` 打包的是项目根目录(`localPath`),不是 `outputDir`!**
- 上传的只是 `outputDir`(如 `web/out`),但生成 `cloudapp-<ts>-<rand>.zip` 时会把**整个项目根**压缩进去
- 若项目根下有 `target/`(Rust)、`.next/`、`dist-old/`、`build/` 等大目录,会被整个打进 zip(实测 ato 项目 54GB target → 34GB zip,占满 /tmp/var/folders 磁盘)
- 默认 exclude 只有 `node_modules/**`、`.git/**`、`.DS_Store`、`**/.DS_Store`——**没有 `target/**` 等**
**规避**(三选一,推荐 ①+③):
1. 部署时显式传 `--ignore "**/target/**"`(CLI)或 `ignore: ["**/target/**", ...]`(MCP deployApp)
2. 项目根 `cloudbaserc.json` 的 `app.ignore` 加 `**/target/**`(CLI 会合并)
3. 部署完成后删除残留:`rm -f /private/var/folders/*/*/T/cloudapp-*.zip`(部署进程异常时残留不清理会堆积)
**判断**:部署日志显示 "Project directory: xxx" 只代表 framework 检测目录,**不代表打包目录**;怀疑打包过大时先看生成的 zip 大小。
---
## Self-Check
- [ ] `tcb` CLI installed, version >= 3.0.0
- [ ] Logged in (`tcb login`) and correct environment set (`tcb env use <envId>`)
- [ ] Framework auto-detected correctly (or specified explicitly)
- [ ] `outputDir` uses `./` prefix and matches actual build output
- [ ] `envVariables` contain no secrets (build-time only, may leak into bundle)
- [ ] For monorepo: `root`/`--cwd` uses relative path
- [ ] For CI/CD: `--env-id` + `--yes` both specified
- [ ] For deletion: previewed with `--dry-run` first
- [ ] For redeployment: `cloudbaserc.json` config reviewed for correctness
references/cloudbase-cli/references/cloudrun.md
# CloudRun — CloudBase CLI
Deploy and manage containerized/server-rendered applications with traffic shifting and canary releases.
CloudRun = persistent containers; for event-triggered serverless, use `functions` instead.
## When to Use
- Deploying containerized or server-rendered applications to CloudBase
- Managing CloudRun services (init, deploy, list, delete)
- Setting up canary deployment or traffic shifting between versions
- Running function-based CloudRun services locally for testing
- Need persistent long-running services (web APIs, backends)
## Do NOT use for
- Serverless event-triggered functions — use `functions`
- Static file hosting — use `hosting`
- Web app deployment with framework auto-detection — use `app`
- Database operations — use `mysql` or `nosql`
---
## Workflow 1: Init and Deploy
### Step 1: Initialize project
```bash
# Initialize from template
tcb cloudrun init --service-name <serviceName> --template <templateName>
# Initialize in a specific directory
tcb cloudrun init --service-name <serviceName> --template <templateName> --target <path>
```
### Step 2: Deploy
> ⚠️ **Brand-new environment first-time deploy** — confirm CloudRun is initialized in the target env before the first deploy (console `环境 → 云托管 → 开通`, or `CreateCloudRunEnv` via the tcbr service). Deploying to an env with no 大租户 record silently lands in the legacy 小租户 path and creates wrong small-tenant services/versions.
```bash
# Basic deploy
tcb cloudrun deploy --service-name <serviceName> --env-id <envId>
# Container-based: specify port
tcb cloudrun deploy --service-name <serviceName> --port 8080 --env-id <envId>
# With online dependency installation
tcb cloudrun deploy --service-name <serviceName> --install-dependency true --env-id <envId>
# Deploy with canary mode (new version starts at 0% traffic)
tcb cloudrun deploy --service-name <serviceName> --traffic --env-id <envId>
# CI/CD: skip confirmation
tcb cloudrun deploy --service-name <serviceName> --force --env-id <envId>
```
> ⚠️ Without `--traffic` flag, the new version replaces the old one with 100% traffic immediately. Use `--traffic` when you want gradual rollout.
> ⚠️ `--force` skips the confirmation prompt but does NOT preview changes — it just skips the prompt.
### Step 3: Verify
```bash
# List all services
tcb cloudrun list --env-id <envId>
# Filter by name or type
tcb cloudrun list --service-name <serviceName> --env-id <envId>
tcb cloudrun list --service-type container --env-id <envId>
```
### Container-based Deploy Example (Node.js API)
```dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 80
CMD ["node", "server.js"]
```
```bash
# Build and push image
docker build -t ccr.ccs.tencentyun.com/my-repo/api:v1.0.0 .
docker push ccr.ccs.tencentyun.com/my-repo/api:v1.0.0
# Deploy
tcb app deploy \
--service-name api \
--image ccr.ccs.tencentyun.com/my-repo/api:v1.0.0 \
--env-id <envId> \
--remark "v1.0.0 initial"
```
> ⚠️ Use `--remark` for meaningful version tracking (e.g. `"v1.2.3 feat: add auth"`). Avoid vague remarks like `"update"`.
---
## Workflow 2: Traffic Shifting (Canary)
Traffic shifting enables canary releases, blue/green deployments, and instant rollbacks.
### View current traffic
```bash
tcb cloudrun traffic get --service-name <serviceName> --env-id <envId>
```
### Canary release (recommended pattern)
```bash
# 1. Deploy new version at 0% traffic
tcb app deploy \
--service-name my-service \
--image ccr.ccs.tencentyun.com/my-repo/app:v2.0.0 \
--env-id <envId> \
--remark "v2.0.0 canary"
# 2. Get new version name
tcb app versions list my-service --env-id <envId>
# 3. Shift 10% -> monitor ~15 min
tcb cloudrun traffic set \
--service-name my-service --env-id <envId> \
--version-weights <newVersion>=10,<stableVersion>=90
# 4. Shift 50% -> monitor
tcb cloudrun traffic set \
--service-name my-service --env-id <envId> \
--version-weights <newVersion>=50,<stableVersion>=50
# 5. Full rollout
tcb cloudrun traffic set \
--service-name my-service --env-id <envId> \
--version-weights <newVersion>=100
```
> ⚠️ Always note the current stable version name BEFORE starting a rollout. Run `tcb app versions list` first.
> ⚠️ `traffic promote` sets canary to 100% and removes the stable version — this is irreversible. `traffic rollback` does the opposite.
### Instant rollback
```bash
tcb cloudrun traffic set \
--service-name my-service --env-id <envId> \
--version-weights <stableVersion>=100
```
### Monitoring after traffic shift
```bash
tcb logs search --service my-service --level error --env-id <envId>
tcb logs search --service my-service --limit 50 --env-id <envId>
tcb app info my-service --env-id <envId>
```
Rollback triggers: error rate > 1%, P99 latency > 2x baseline, any crash/OOM in logs.
### Multi-environment Promotion (dev -> staging -> prod)
```bash
IMAGE=ccr.ccs.tencentyun.com/my-repo/my-service:v1.2.0
tcb app deploy --service-name svc --image $IMAGE --env-id dev-env-xxx
# test in dev...
tcb app deploy --service-name svc --image $IMAGE --env-id staging-env-xxx
# sign-off...
tcb app deploy --service-name svc --image $IMAGE --env-id prod-env-xxx --dry-run
tcb app deploy --service-name svc --image $IMAGE --env-id prod-env-xxx \
--remark "v1.2.0 promoted from staging"
```
---
## Workflow 3: Local Development
```bash
# Run function-based service locally
tcb cloudrun run --env-id <envId>
# With hot reload
tcb cloudrun run --hot-reload true --env-id <envId>
# On specific port
tcb cloudrun run --port 3000 --env-id <envId>
# Agent mode (for AI agent debugging)
tcb cloudrun run --mode agent --agent-id <agentId> --env-id <envId>
# Dry run (validate without starting)
tcb cloudrun run --dry-run true --env-id <envId>
```
> ⚠️ `tcb cloudrun run` only supports function-based services. Container-based services must be tested via Docker locally.
### Function-based vs Container-based
| Capability | Function-based | Container-based |
|-----------|---------------|----------------|
| `tcb cloudrun run` (local) | Supported | Not supported |
| Custom Dockerfile | No | Yes |
| Port configuration | Auto-detected | Must specify `--port` |
| Hot reload | `--hot-reload true` | Not supported |
| Agent mode | Supported | Not supported |
---
## Workflow 4: Download and Delete
```bash
# Download latest deployed code
tcb cloudrun download --service-name <serviceName> --target <path> --env-id <envId>
# Force overwrite existing directory
tcb cloudrun download --service-name <serviceName> --force --env-id <envId>
# Delete a service (interactive confirmation)
tcb cloudrun delete --service-name <serviceName> --env-id <envId>
# Force delete (CI/CD)
tcb cloudrun delete --service-name <serviceName> --force --env-id <envId>
```
> ⚠️ Deletion removes all versions and traffic configuration. There is no undo or `--dry-run` for CloudRun delete.
> ⚠️ `tcb cloudrun download` downloads the latest deployed code only, not a specific version. Does not include runtime config (env vars, secrets).
---
## Secrets Injection
```bash
# Set secrets (injected as env vars at container startup)
tcb secrets set DATABASE_URL "mysql://..." --env-id <envId>
tcb secrets set API_SECRET "..." --env-id <envId>
# List configured secrets
tcb secrets list --env-id <envId>
```
> ⚠️ Secrets are shared across ALL services in the environment, not per-service.
> ⚠️ Changing a secret does NOT auto-restart running services — you must redeploy.
---
## Command Quick Reference
```bash
tcb cloudrun init --service-name <n> --template <t> # Init project
tcb cloudrun deploy --service-name <n> --env-id <id> # Deploy service
tcb cloudrun list --env-id <id> # List services
tcb cloudrun download --service-name <n> --env-id <id> # Download code
tcb cloudrun delete --service-name <n> --env-id <id> # Delete service
tcb cloudrun run --env-id <id> # Local run (function only)
tcb cloudrun traffic get --service-name <n> --env-id <id> # View traffic
tcb cloudrun traffic set --service-name <n> --env-id <id> --version-weights ... # Set traffic
```
Key flags: `--force` (skip confirmation), `--traffic` (canary mode on deploy), `--port` (container port), `--hot-reload true` (local dev), `--install-dependency true` (online install), `--remark` (version label), `--dry-run` (preview deploy).
---
## Common Errors
| Error | Cause | Fix |
|-------|-------|-----|
| Local run fails for container service | `tcb cloudrun run` is function-only | Use Docker to test container services locally |
| New version gets no traffic | Deployed with `--traffic` (canary mode) | Explicitly shift traffic with `tcb cloudrun traffic set` |
| Deploy prompt hangs in CI | Missing `--force` flag | Always use `--force` for non-interactive pipelines |
| Secret not available in container | Secret set after deploy | Redeploy the service after changing secrets |
| Download missing config | Download only includes source code | Runtime config (env vars, secrets) not included |
---
## Self-Check
- [ ] `tcb` CLI installed, version >= 3.0.0
- [ ] Logged in (`tcb login`) and correct environment set (`tcb env use <envId>`)
- [ ] Service type determined: function-based or container-based
- [ ] For container: `--port` matches app's listening port; image built and pushed
- [ ] For canary: current stable version name noted before deploying
- [ ] For canary: traffic shifting plan ready (10% -> 50% -> 100%)
- [ ] Secrets stored via `tcb secrets set`, not hardcoded
- [ ] For CI/CD: `--force` flag added to skip confirmation
- [ ] Post-deploy: service endpoint tested and traffic distribution verified
references/cloudbase-cli/references/core.md
# Core — CloudBase CLI
> Core foundation for all CloudBase CLI operations (云开发 CLI 核心基础).
> This reference covers authentication, environment setup, documentation queries, config, and error handling.
## When to Use
- **Any** CloudBase CLI operation (always start here)
- Authenticating with `tcb login` or switching environments with `tcb env use`
- Querying CLI docs with `tcb docs` or checking command help with `--help`
- Diagnosing CLI errors via exit codes
- **First-session fallback:** CloudBase MCP tools are not in this conversation yet (not installed, or installed but session not restarted) — use `tcb login` + the matching domain command now while MCP is configured for next time (do **not** use `tcb deploy`; see guideline `tooling-fallback.md` if npm/npx is missing)
## Do NOT use for
- CloudBase SDK development (use matching SDK skills)
- CloudBase MCP server operations when MCP tools are already available (prefer MCP unless the user asked for CLI)
- Tencent Cloud console-only operations (this reference is CLI-only)
## First-session + MCP coexistence
1. If MCP is missing: install/configure it (plugin / `mcp.json` / mcporter) so the **next** session gets tools.
2. Immediately continue with CLI in **this** session: `tcb login` → confirm envId → `tcb env use <envId>` → domain command from the matching reference (`functions` / `hosting` / `cloudrun` / …). Do **not** use `tcb deploy`.
3. Tell the user briefly that after restart/reload, MCP becomes the preferred path.
4. Do not hard-code SecretId/SecretKey into MCP config as the default path.
5. If `npm`/`npx`/`node` are missing, stop and follow guideline `tooling-fallback.md` (“No npm/npx”) before retrying installs.
---
## Workflow 1: Authentication
### Quick Commands
```bash
tcb login # Interactive login (device code, recommended; has Tencent Cloud account)
tcb login --flow web # Web authorization (same-machine only)
tcb login --cloudbase-api-key <cloudbaseApiKey> -e <envId> # Env API Key (no Tencent Cloud account)
tcb login --apiKeyId <Id> --apiKey <Key> # CI / Tencent Cloud SecretId+SecretKey
tcb login --apiKeyId <Id> --apiKey <Key> --token <T> # Temp token (CI, more secure)
tcb logout # Clear local credentials
```
### Choosing a login method
| Situation | Method |
|-----------|--------|
| User has a Tencent Cloud account | Device code (`tcb login`) — default / recommended |
| Same machine + local browser OK | Web flow (`tcb login --flow web`) |
| No Tencent Cloud account; only an environment API Key | `--cloudbase-api-key` **with** `-e` / `--env-id` |
| CI / automation with SecretId + SecretKey | `--apiKeyId` + `--apiKey` (+ optional `--token`) |
> Do **not** confuse `--cloudbase-api-key` (CloudBase **environment** API Key) with `--apiKeyId` / `--apiKey` (Tencent Cloud **account** credentials).
### Login Methods
**1. Device Code Authorization (default, recommended)**
Use when the user **has a Tencent Cloud account**. Typical IDE path (e.g. CodeBuddy): start chatting and complete Device login when prompted — no pre-login required.
```bash
tcb login
# Prints a device code + verification URL.
# Open URL in any browser (can be a different machine), enter code to authorize.
```
> Works in remote SSH, headless servers, WSL. Browser and CLI need **not** be on the same machine.
**2. Web Authorization (same-machine only)**
```bash
tcb login --flow web
# Opens browser on local machine; CLI receives token via local callback.
```
> ⚠️ Requires browser and CLI on the same machine. Falls back to key-based login if browser cannot open.
**3. CloudBase Environment API Key (no Tencent Cloud account)**
Use when the user **does not** have a Tencent Cloud account and only holds an environment-level API Key (e.g. issued by a partner/admin). Must specify the target envId via global `-e` / `--env-id`.
```bash
# Install CLI if needed
npm i -g @cloudbase/cli
# Environment API Key login (envId is required)
tcb login --cloudbase-api-key <cloudbaseApiKey> -e <cloudbaseEnvId>
# Equivalent long flag:
# tcb login --cloudbase-api-key <cloudbaseApiKey> --env-id <cloudbaseEnvId>
```
> 💡 API Keys can be created in the [CloudBase console](https://tcb.cloud.tencent.com/dev) environment settings (when you have admin access).
>
> ⚠️ Environment API Keys grant env-scoped privileges. Never commit them to git or paste into screenshots.
> ⚠️ **Never hardcode credentials.** Prefer env vars / secrets injection when scripting.
>
> If the user already has a Tencent Cloud account, prefer Device login instead — do **not** ask them to use `--cloudbase-api-key`.
**4. CI / Non-Interactive Login (Tencent Cloud SecretId / SecretKey)**
```bash
# Permanent credentials
tcb login --apiKeyId $SECRET_ID --apiKey $SECRET_KEY
# Temporary token (shorter TTL, more secure for CI)
tcb login --apiKeyId $TMP_SECRET_ID --apiKey $TMP_SECRET_KEY --token $SESSION_TOKEN
```
> ⚠️ **Never hardcode credentials.** Always inject via environment variables.
> 不要把密钥硬编码在命令里,通过环境变量注入。
>
> This is **not** the same as `--cloudbase-api-key`. Use SecretId/SecretKey for CI with a Tencent Cloud account/sub-account.
### Checking Login Status
```bash
tcb login
# If already logged in: prints "您已登录,无需再次登录!" and exits.
```
> ⚠️ Do **not** use `tcb env list` to check login status — sub-accounts may lack list permissions, causing misleading errors.
### Sub-account Policies (子账号策略)
Sub-accounts need these CAM policies to use the CLI:
| Policy | Purpose |
|--------|---------|
| `QcloudAccessForTCBRole` | TCB access to cloud resources |
| `QcloudAccessForTCBRoleInAccessCloudBaseRun` | TCB access to VPC/CVM for CloudRun |
| `QcloudCamReadOnlyAccess` | Required for web/device code login; without it, only API key login works |
> ⚠️ If sub-account device/web login fails, grant `QcloudCamReadOnlyAccess` or switch to `--apiKeyId / --apiKey`.
### Auth Troubleshooting
| Issue | Solution |
|-------|----------|
| `Not logged in` | Run `tcb login` |
| Cannot open browser / browser loop | Use default device code flow (no `--flow` flag) |
| No Tencent Cloud account; only env API Key | `tcb login --cloudbase-api-key <key> -e <envId>` |
| `--cloudbase-api-key` without envId | Always pass `-e` / `--env-id` (required by CLI) |
| Device code not working in CI | Use `--apiKeyId / --apiKey` credential login |
| Sub-account web/device login fails | Grant `QcloudCamReadOnlyAccess`, or use key login |
| Permission denied on resources | Check sub-account CAM policies for the specific TCB resource |
---
## Workflow 2: Environment Setup
### Core Principle
> 操作任何云开发资源前,必须先确认 envId。优先让用户明确告知 envId。
⚠️ **Always confirm envId before any operation** to avoid accidentally modifying production.
Ask the user to provide the envId directly — do not auto-discover.
### Login -> Environment Flow
```
tcb login
|
Ask user: "Which environment? (please provide the envId)"
|
User knows envId?
+-- YES --> tcb env use <envId>
+-- NO --> tcb env list <-- fallback only; sub-accounts may see limited results
|
User selects --> tcb env use <envId>
```
### Basic Operations
```bash
tcb env use <envId> # Set default env for all subsequent commands
tcb env detail <envId> # View environment details
tcb env rename <newAlias> --env-id <envId> --yes # Rename alias
tcb env create --alias <name> --package <packageId> --yes # Create new env
```
> ⚠️ `tcb env list` may return incomplete results under sub-account permissions. Use only when user explicitly asks.
### Per-Command Override
```bash
tcb app deploy --env-id <envId> # Override without changing default
```
### Multi-Environment Best Practices
- Use **separate envIds** for dev / staging / production — never share
- Before production operations: confirm envId with user + `--dry-run`
- When switching environments, explicitly confirm the new envId before proceeding
---
## Workflow 3: Documentation Query (tcb docs)
> **When in doubt, query first. Never guess command signatures.**
> 不确定参数时,先查,不要猜。猜错在生产环境上可能触发你不想要的操作。
### Commands
```bash
tcb docs list # List all top-level documentation modules
tcb docs read <module|path> # Read module structure or specific document
tcb docs search "keyword" # Search documents by keyword
```
### Standard Query Flow
```
tcb docs list
|
Identify relevant module
|
tcb docs read <moduleName>
|
Browse document tree, find target path
|
tcb docs read <path> # e.g. "MySQL数据库.数据操作.字段类型"
|
Read content --> construct command
```
Or shortcut: `tcb docs search "关键词"` --> review results --> `tcb docs read <path>`
### Decision Tree
```
User describes a task
|
Know exact command + all flags?
+-- NO --> tcb docs list --> tcb docs read --> confirm flags
+-- YES --> Destructive operation?
+-- YES --> --dry-run first
+-- NO --> Execute directly
```
### Query docs when
- Unsure about subcommands or flags
- A command returned an error and you don't know why
- The user mentions a feature you haven't used before
- Combining multiple flags and want to verify compatibility
- About to perform a destructive or irreversible operation
### Skip docs when
- Simple read operation you've already run this session
- User provided all parameters and you've verified the syntax
### Anti-patterns
| Anti-pattern | Do instead |
|-------------|-----------|
| Guessing a flag name | `tcb docs list` -> `tcb docs read <module>` first |
| Running full deploy to "test" if it works | Use `--dry-run` |
| Repeating same failed command with same flags | Re-read docs, change flags |
| Assuming v2.x flag names still work | Query docs after version upgrade |
| Jumping to `tcb docs read <path>` without listing | Start with `tcb docs list` |
### --help First Rule (MANDATORY)
**Before using ANY `tcb` command for the first time, run `<command> --help` to check:**
- Parameter names and formats
- Required vs optional parameters
- Official API doc URLs (many commands include Tencent Cloud API links)
- Examples and usage patterns
```
Unsure about usage?
|
tcb <command> --help
|
Help shows API doc link?
+-- YES --> web_fetch the doc --> understand data structure --> construct correctly
+-- NO --> tcb docs search "<keyword>" --> construct from docs
```
> ⚠️ **Real lesson**: `tcb db nosql execute --help` shows `MgoCommandParam` structure doc link.
> Without reading it, agents construct wrong `Command` field format (should be a JSON-encoded
> string of MongoDB shell syntax, NOT a raw array). **This mistake cost 10+ minutes. Don't repeat it.**
### --dry-run Safety Mechanism
> 所有破坏性操作必须先 `--dry-run` 预览,等用户确认后再执行。
```bash
tcb app deploy --dry-run # Preview app version to be published
tcb fn deploy --dry-run # Preview functions to be overwritten
```
Workflow: `--dry-run` -> show preview -> wait for user confirmation -> re-run without `--dry-run`.
⚠️ Never skip `--dry-run` for destructive operations (overwrite / delete / rollback).
---
## cloudbaserc.json Quick Reference
> Project config file in project root. Defines environment, functions, servers, and app settings.
### File Location
```
project-root/
+-- cloudbaserc.json <-- Default
+-- .cloudbaserc.json <-- Alternative (hidden)
+-- custom-config.json <-- Use with --config-file flag
```
```bash
tcb app deploy --config-file config/staging.json
```
### Root Structure (`ICloudBaseConfig`)
```json
{
"envId": "env-xxxxx",
"functionRoot": "functions",
"functions": [],
"servers": [],
"app": {}
}
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `envId` | string | **Yes** | CloudBase environment ID (`env-` prefix). Override with `--env-id` |
| `functionRoot` | string | No | Base directory for cloud functions. Default: `"functions"` |
| `functions` | array | No | Cloud function configs (see below) |
| `servers` | array | No | CloudRun service configs (see below) |
| `app` | object | No | Web app hosting config (see below) |
### `functions` Array — Essential Fields
Each entry is an `ICloudFunction`:
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `name` | string | (required) | Function name, unique in env |
| `handler` | string | — | Entry point, e.g. `"index.main"` |
| `runtime` | string | auto-detected | `Nodejs18.15`, `Python3.9`, `Go1.8`, `Java11`, etc. |
| `timeout` | number | `3` | Execution timeout in seconds (1-900) |
| `memorySize` | number | `256` | Memory in MB (128-3008, multiples of 128) |
| `type` | string | `"Event"` | `"Event"` (background) or `"HTTP"` (web-accessible) |
| `envVariables` | object | `{}` | Runtime env vars (`process.env.*` / `os.environ[]`) |
| `triggers` | array | `[]` | Timer, COS, API Gateway triggers |
| `installDependency` | boolean | `true` | Auto-install deps before deploy |
| `vpc` | object | — | `{ vpcId, subnetId }` for VPC access |
| `dir` | string | `functionRoot/name` | Custom code directory |
| `ignore` | string[] | `[]` | Glob patterns to exclude from deploy |
> ⚠️ For sensitive values, use `tcb secrets` instead of `envVariables`.
**Minimal function example:**
```json
{
"functions": [{
"name": "my-function",
"handler": "index.main",
"runtime": "Nodejs18.15",
"timeout": 30,
"envVariables": { "NODE_ENV": "production" }
}]
}
```
> For full function config (triggers, image deployment, concurrency, WebSocket, VPC) see `tcb-functions` skill.
### `servers` Array
```json
{
"servers": [
{ "type": "node", "name": "api-service", "path": "services/api" }
]
}
```
> Only `"node"` type currently supported. For full CloudRun config see `tcb-cloudrun` skill.
### `app` Object — `ICloudAppConfig`
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `serviceName` | string | (required) | Service name, unique in env, used in URLs |
| `framework` | string | auto-detected | `react`, `vue`, `nextjs`, `nuxt`, `static`, etc. |
| `root` | string | `"."` | App code directory (relative to project root) |
| `installCommand` | string | framework-dependent | Empty string `""` = skip |
| `buildCommand` | string | framework-dependent | Empty string `""` = skip build |
| `outputDir` | string | `"dist"` | Build output dir. For static hosting: `"./"` |
| `deployPath` | string | `/<serviceName>` | URL path. Only non-default values are saved to config |
| `envVariables` | object | `{}` | Build-time environment variables |
| `ignore` | string[] | `[]` | Files/dirs to exclude from deploy |
**Minimal app example:**
```json
{
"app": {
"serviceName": "web-app",
"framework": "react",
"buildCommand": "npm run build",
"outputDir": "dist"
}
}
```
### Config Priority (highest to lowest)
1. **CLI flags** (`--env-id`, `--deploy-path`, etc.)
2. **cloudbaserc.json**
3. **CLI defaults**
CI/CD environment variable overrides:
```bash
export TCB_ENV_ID=env-production # Override envId
export TCB_FRAMEWORK=vue # Override framework detection
```
### Config Best Practices
- **Commit** `cloudbaserc.json` to git for team consistency; **never** add secrets
- Use separate config files per environment (`cloudbaserc.production.json`)
- Add `cloudbaserc.*.json` to `.gitignore`
- Validate: `tcb app info --config-file cloudbaserc.json --json`
---
## Error Diagnosis (Exit Codes)
CloudBase CLI uses structured exit codes for CI/CD and agent error handling.
| Code | Meaning | Typical Scenario | Recovery |
|------|---------|-----------------|----------|
| 0 | Success | — | — |
| 1 | General error | Uncategorized exception | Check message, investigate |
| 2 | Auth failed | Not logged in, token expired | `tcb login` |
| 3 | Invalid input | Missing/malformed params | Check `--help`, fix params |
| 4 | Resource not found | Wrong envId, missing function/collection | Verify with `tcb env list` / `tcb fn list` |
| 5 | Cloud API error | Network timeout, SDK error | Retry with backoff |
| 6 | Local file error | `cloudbaserc.json` missing/corrupt | Check config, `tcb init` |
### Agent Error Handling Strategy
1. **Read exit code** (`$?`) to categorize
2. **Parse error message** for details (envId, param name, etc.)
3. **Targeted recovery:**
- Code 2 -> `tcb login`
- Code 3 -> Fix params (check docs, ask user)
- Code 4 -> Verify resource exists
- Code 5 -> Retry with exponential backoff
- Code 6 -> Check/repair config
4. **Escalate to user** if recovery fails after 2-3 attempts
### CI/CD Script Pattern
```bash
tcb fn deploy || {
code=$?
case $code in
2) echo "Auth failed"; tcb login && tcb fn deploy ;;
5) echo "API error, retrying..."; sleep 30 && tcb fn deploy ;;
*) echo "Unrecoverable (code $code)"; exit $code ;;
esac
}
```
> Run `tcb help exit-codes` for source-level docs (requires CLI >= 3.0.0-alpha.9).
---
## Self-Check
> 每次 CLI 操作前的核心检查清单
### Environment Setup
- [ ] `tcb --version` >= 3.0.0
- [ ] `tcb login` completed (token valid)
- [ ] Target envId confirmed with user
- [ ] `tcb env use <envId>` executed
### Before Any Command
- [ ] Run `<command> --help` for any new command
- [ ] Check API doc links in help output (use `web_fetch` if available)
- [ ] Verify parameter names/formats match help
### Destructive Operations
- [ ] `--dry-run` to preview first
- [ ] Show preview to user
- [ ] Wait for explicit confirmation
- [ ] Re-run without `--dry-run` only after "yes"
### Error Handling
- [ ] Check exit code (`$?`)
- [ ] Apply targeted recovery per exit code table above
- [ ] Escalate to user after 2-3 failed attempts
### Common Global Flags
```bash
--env-id <envId> # Temporarily override env (does not change `env use` setting)
--verbose # Verbose logging (add when debugging)
--version # Print tcb version (verify >= 3.0.0)
```
references/cloudbase-cli/references/functions.md
# Functions — CloudBase CLI
Deploy, update, debug, and manage cloud functions (云函数) via `tcb fn` commands.
Covers both Event Functions (普通云函数) and HTTP Functions (HTTP 云函数).
## When to Use
- Deploy or update cloud functions from terminal / CI pipeline
- Query function logs, diagnose runtime errors
- Manage triggers (timer/定时触发器), layers, versions
- Inject secrets / environment variables
- Batch deploy via `cloudbaserc.json`
## Do NOT use for
- Calling functions from client code (web/miniprogram) → use `cloud-functions` skill
- CloudRun container deployments → use `references/cloudrun.md`
- SDK-based in-app function invocation → use `@cloudbase/js-sdk`
- Console UI operations
## Command Quick Reference
| Task | Command |
|------|---------|
| List all functions (列出函数) | `tcb fn list` |
| Function detail (查看详情) | `tcb fn detail <name>` |
| Deploy all functions | `tcb fn deploy --all` |
| Deploy single Event Function | `tcb fn deploy <name>` |
| Deploy HTTP Function | `tcb fn deploy <name> --httpFn` |
| Deploy HTTP + WebSocket | `tcb fn deploy <name> --httpFn --ws` |
| Deploy multiple | `tcb fn deploy fn1 fn2` |
| Update code only (仅更新代码) | `tcb fn code update <name>` |
| Update config only (仅更新配置) | `tcb fn config update <name>` |
| Invoke remotely (调用函数) | `tcb fn invoke <name>` |
| Invoke with params | `tcb fn invoke <name> --params '{"key":"val"}'` |
| Run locally (本地调试) | `tcb fn run <name>` |
| View logs (查看日志) | `tcb fn log <name>` |
| View log by RequestId | `tcb fn log <name> --reqId <id>` |
| Create trigger (创建触发器) | `tcb fn trigger create <name>` |
| Delete trigger | `tcb fn trigger delete <name> --name <trigger>` |
| List layers (层) | `tcb fn layer list` |
| Publish version (发布版本) | `tcb fn publish-version <name>` |
| Copy to another env | `tcb fn copy <name> --envId <target>` |
| Delete function | `tcb fn delete <name>` |
> Always run `tcb fn <subcommand> --help` first to check current syntax.
---
## Workflow 1: Deploy Functions
### Deploy All
```bash
# Reads cloudbaserc.json → deploys every function listed
tcb fn deploy --all
# Verify
tcb fn list
```
### Deploy Single Function
```bash
# Event Function(普通云函数)
tcb fn deploy my-function
# HTTP Function(HTTP 云函数)— requires scf_bootstrap
tcb fn deploy my-http-fn --httpFn
# Force overwrite existing
tcb fn deploy my-function --force
```
### Deploy Multiple (Selective)
```bash
tcb fn deploy func-a func-b func-c
```
### Config-Only Update (仅更新配置,不上传代码)
```bash
tcb fn config update my-function
```
> ⚠️ **Function type is locked after creation (函数类型创建后不可更改).**
> Cannot change Event → HTTP or vice versa. To switch: delete → recreate.
> ⚠️ **Runtime is locked after creation.** To change (e.g., Nodejs16 → Nodejs18):
> delete the function and create a new one.
### Deploy Modes
| Mode | Flag / Config | Use Case |
|------|--------------|----------|
| COS upload (default) | — | Code < 50 MB, standard deploy |
| ZIP package | `deployMode: "zip"` | Bundled dependencies |
| Image (镜像) | `deployMode: "image"` | Custom runtime, large dependencies |
> ⚠️ **Code encryption (代码加密)**: Once enabled, code cannot be downloaded in console.
> Enable only when you have source control in place.
> ⚠️ **`installDependency` conflict (依赖安装冲突):**
> If `installDependency: true`, the platform installs deps from `package.json` on deploy.
> Do NOT also upload `node_modules/` — they will conflict. Choose one approach.
> For HTTP Functions, `installDependency` is NOT supported; bundle `node_modules` yourself.
---
## Workflow 2: Incremental Update
When only code changed (no config changes):
```bash
tcb fn code update my-function
```
When only config changed (timeout, memory, env vars):
```bash
tcb fn config update my-function
```
Typical iteration cycle:
```bash
# 1. Edit code locally
# 2. Push code only
tcb fn code update my-function
# 3. Invoke to test
tcb fn invoke my-function --params '{"action":"test"}'
# 4. Check logs
tcb fn log my-function
```
---
## Workflow 3: Debug and Investigate
### Step 1: Query Logs (查询日志)
```bash
# Recent logs (default last 10 minutes)
tcb fn log my-function
# Filter by time range
tcb fn log my-function --offset 0 --limit 100
# Search for errors
tcb fn log my-function --keyword "Error"
tcb fn log my-function --keyword "timeout"
# Filter failures only
tcb fn log my-function --success false
```
### Step 2: Get Detailed Log by RequestId
```bash
tcb fn log my-function --reqId "abc-123-def-456"
```
### Step 3: Invoke and Inspect
```bash
# Remote invoke with test payload
tcb fn invoke my-function --params '{"action":"test"}'
# Local run for fast iteration(本地调试)
tcb fn run my-function --params '{"action":"test"}'
```
### Common Error Patterns
| Symptom | Likely Cause | Fix |
|---------|-------------|-----|
| `MODULE_NOT_FOUND` / 模块未找到 | Missing dependency | Check `package.json`; set `installDependency: true` or bundle `node_modules` |
| `Task timed out` / 超时 | Exceeds timeout | Increase `timeout` in config (max 900s); optimize code |
| `Memory size exceeded` / 内存溢出 | OOM kill | Increase `memorySize` (128–3072 MB); reduce payload |
| `Environment variable not found` | Var not set or overwritten | Check `tcb fn detail`; merge env vars on update |
| `Permission denied` / EACCES | VPC or IAM issue | Check VPC config and security group rules |
| `ECONNREFUSED` / network error | Downstream service issue | Verify VPC settings, security groups, endpoint URLs |
---
## Workflow 4: Triggers and Versions
### Timer Trigger (定时触发器 / Cron)
Config in `cloudbaserc.json`:
```jsonc
{
"functions": [{
"name": "daily-cleanup",
"triggers": [{
"name": "daily-timer",
"type": "timer",
"config": "0 0 2 * * * *" // 每天凌晨2点 (2:00 AM daily)
}]
}]
}
```
```bash
# Deploy the trigger
tcb fn trigger create daily-cleanup
# List triggers
tcb fn trigger list daily-cleanup
# Delete a trigger
tcb fn trigger delete daily-cleanup --name daily-timer
```
> Cron format: `秒 分 时 日 月 星期 年` (7 fields — note the leading seconds field).
| Expression | Schedule |
|-----------|----------|
| `0 0 2 * * * *` | 每天 02:00 |
| `0 30 9 * * * *` | 每天 09:30 |
| `0 */5 * * * * *` | 每5分钟 |
| `0 0 2 1 * * *` | 每月1号 02:00 |
| `0 0 18 * * MON-FRI *` | 工作日 18:00 |
### Multi-Environment Cron Deploy Example
```bash
# Deploy cron function to dev, then staging
tcb env use env-dev-xxx
tcb fn deploy daily-cleanup
tcb fn trigger create daily-cleanup
tcb env use env-staging-xxx
tcb fn deploy daily-cleanup
tcb fn trigger create daily-cleanup
```
### Publish a Version
```bash
tcb fn publish-version my-function
tcb fn list-version my-function
```
> ⚠️ **Traffic / concurrency (流量与并发):** Traffic split between versions and
> reserved concurrency are configured via console or API, not CLI.
### Layers (层)
```bash
tcb fn layer list
```
Layers share dependencies across functions — useful for large libraries
(`puppeteer`, `ffmpeg`) that would exceed the code size limit.
Bind layers via `cloudbaserc.json` `layers` array.
---
## Secrets Injection
Inject secrets via environment variables — never hardcode credentials.
```jsonc
// cloudbaserc.json
{
"functions": [{
"name": "my-function",
"envVariables": {
"DB_HOST": "10.0.0.1",
"API_KEY": "{{YOUR_API_KEY}}" // 替换为实际密钥
}
}]
}
```
```bash
# Update env vars without redeploying code
tcb fn config update my-function
# Verify
tcb fn detail my-function
```
> ⚠️ **Env var update is a full replace, not a merge (环境变量更新为全量覆盖).**
> Always include ALL env vars in the config — omitting one will delete it.
> Workflow: `tcb fn detail <name>` → copy existing vars → add new → update config.
For CI/CD pipelines, use shell variable substitution:
```bash
export API_KEY="$CI_SECRET_API_KEY"
```
Access in code:
```javascript
const apiKey = process.env.API_KEY;
const dbUrl = process.env.DB_URL;
```
---
## cloudbaserc.json Function Config
```jsonc
{
"envId": "your-env-id",
"functionRoot": "cloudfunctions", // 函数代码根目录
"functions": [
{
"name": "my-event-fn",
"handler": "index.main", // ⚠️ format: filename.export
"runtime": "Nodejs18.15",
"timeout": 10,
"memorySize": 256,
"installDependency": true, // 云端安装依赖 (Event only)
"envVariables": { "NODE_ENV": "production" },
"triggers": [],
"ignore": ["node_modules/**", ".git/**"]
},
{
"name": "my-http-fn",
"handler": "index.main",
"runtime": "Nodejs18.15",
"timeout": 60,
"memorySize": 512,
"isHTTP": true // HTTP 云函数
}
]
}
```
### Key Fields
| Field | Description | Default |
|-------|-------------|---------|
| `name` | 函数名称 (required) | — |
| `handler` | 入口,格式 `filename.export` | `index.main` |
| `runtime` | 运行时版本 | `Nodejs18.15` |
| `timeout` | 超时时间(秒,max 900) | `3` |
| `memorySize` | 内存(MB,128–3072,64 的倍数) | `256` |
| `installDependency` | 云端安装依赖(仅 Event Function) | `false` |
| `envVariables` | 环境变量 key-value | `{}` |
| `isHTTP` | 是否为 HTTP 云函数 | `false` |
| `triggers` | 触发器数组 | `[]` |
| `functionRoot` | 所有函数的代码根目录 | `functions` |
| `dir` | 单个函数的子目录(覆盖 name) | same as `name` |
| `ignore` | 部署时排除的文件 glob | `[]` |
> ⚠️ **`handler` format (入口格式):** Must be `filename.export` — e.g., `index.main`.
> Do NOT include file extension or directory path. Wrong: `src/index.main`, `index.js.main`.
> ⚠️ **`functionRoot` vs `dir`:** `functionRoot` is the parent directory for ALL functions.
> Each function folder name must match `name`. Use `dir` to override if the folder name
> differs: `"dir": "actual-folder-name"`.
### Supported Runtimes (运行时)
| Runtime | Value | Notes |
|---------|-------|-------|
| Node.js 18 | `Nodejs18.15` | ✅ Recommended |
| Node.js 16 | `Nodejs16.13` | ✅ Supported |
| Node.js 14 | `Nodejs14.18` | ⚠️ Maintenance |
| Node.js 12 | `Nodejs12.16` | ⚠️ Deprecated |
| Python 3.10 | `Python3.10` | HTTP Function only |
| Go 1.x | `Go1` | HTTP Function only |
| Java 11 | `Java11` | HTTP Function only |
| PHP 8 | `Php8.0` | HTTP Function only |
> HTTP Functions require `scf_bootstrap` file (executable, port 9000, LF line endings).
---
## Common Errors (Top-5)
### 1. `Function not exist` / 函数不存在
**Cause:** Name typo or function not yet deployed.
```bash
tcb fn list # verify name exists
tcb fn deploy my-function # deploy if missing
```
### 2. `Module not found` / 模块未找到
**Cause:** Dependencies not installed in deployed environment.
```bash
# Event Function: enable cloud install
# cloudbaserc.json → "installDependency": true, exclude node_modules
# HTTP Function: bundle locally
npm install --production
tcb fn deploy my-http-fn --httpFn
```
### 3. `Execution timeout` / 执行超时
**Cause:** Function exceeds configured `timeout`.
```bash
tcb fn detail my-function # check current timeout
# Increase in cloudbaserc.json → "timeout": 60
tcb fn config update my-function
```
Also check: infinite loops, unresolved promises, slow external API calls.
### 4. `Deploy timeout` / 部署超时
**Cause:** Large code package or slow network.
```bash
# Reduce package — add to cloudbaserc.json:
# "ignore": ["node_modules/**", "test/**", ".git/**", "*.md"]
# Or use installDependency: true to skip uploading node_modules
tcb fn deploy my-function
```
### 5. `HTTP 404 / CORS` — 访问不通
**Cause:** HTTP access not configured, path mismatch, or missing CORS headers.
```bash
tcb fn detail my-function # verify HTTP access config
# Ensure HTTP Function listens on port 9000
# Ensure scf_bootstrap exists and is executable (chmod +x)
# For CORS: add Access-Control-Allow-Origin header in function code
```
---
## Self-Check
Before deploying:
- [ ] Confirmed target `envId` with the user? (`tcb env list`)
- [ ] `cloudbaserc.json` has correct `functionRoot` and function `name`?
- [ ] `handler` format is `filename.export` (no path, no `.js`)?
- [ ] `runtime` explicitly set (not relying on default)?
- [ ] HTTP Function has `scf_bootstrap` with `chmod +x`, port 9000, LF endings?
- [ ] `installDependency` consistent? (Event only; no `node_modules` in upload)
- [ ] Secrets in `envVariables`, not hardcoded?
- [ ] `ignore` excludes test files, docs, `.git`?
After deploying:
- [ ] Verified with `tcb fn detail <name>`?
- [ ] Tested with `tcb fn invoke <name>`?
- [ ] Checked logs with `tcb fn log <name>` for startup errors?
- [ ] Triggers working? (`tcb fn trigger list <name>`)
When updating env vars:
- [ ] Queried current vars first? (`tcb fn detail <name>`)
- [ ] Merged existing + new vars in config?
- [ ] Did NOT overwrite — all existing vars preserved?
references/cloudbase-cli/references/hosting.md
# Hosting — CloudBase CLI
Deploy pre-built static files (HTML/CSS/JS) to CloudBase CDN hosting.
Hosting = pre-built static files with CDN; for framework build + deploy, use `app` instead.
## When to Use
- Deploying pre-built static files to CloudBase CDN hosting
- Managing hosted files: listing, downloading, or deleting
- Setting up a static website or SPA with CDN acceleration
- Need fine-grained control over individual file deployment
## Do NOT use for
- Web app deployment with framework auto-detection and build — use `app`
- File storage with ACL rules — use `storage`
- Cloud functions — use `functions`
- Containerized services — use `cloudrun`
---
## Workflow 1: Deploy Static Site
```bash
# 1. Confirm target environment
tcb env list
# 2. Check hosting status (auto-enables if not active)
tcb hosting detail --env-id <envId>
# 3. Build locally first
npm run build
# 4. Deploy built assets
tcb hosting deploy ./dist --env-id <envId> --yes
# 5. Verify
tcb hosting list --env-id <envId>
```
### Deploy Variations
```bash
# Deploy current directory
tcb hosting deploy --env-id <envId> --yes
# Deploy to a sub-path
tcb hosting deploy ./dist /v2 --env-id <envId> --yes
# Deploy a single file
tcb hosting deploy ./index.html --env-id <envId> --yes
# Update only one file (incremental)
tcb hosting deploy ./dist/index.html /index.html --env-id <envId> --yes
# CI/CD: non-interactive with JSON output
tcb hosting deploy ./dist --env-id $ENV_ID --yes --json
```
> ⚠️ Deploy overwrites existing files at the same path. There is no built-in versioning — consider cleaning old files before redeploying.
---
## Workflow 2: Safe Deletion
```bash
# 1. Always preview first
tcb hosting delete --dry-run --env-id <envId>
# 2. Delete a specific file
tcb hosting delete path/to/file --env-id <envId> --yes
# 3. Delete a directory
tcb hosting delete path/to/dir --dir --env-id <envId> --yes
```
> ⚠️ Always use `--dry-run` first before bulk deletions.
> ⚠️ CDN cache delay: deleted files may remain accessible for 5-10 minutes. Flush CDN cache in console if needed.
### Clean Redeploy Pattern
```bash
# Preview what will be deleted
tcb hosting delete --dry-run --env-id <envId>
# Delete all hosted files
tcb hosting delete --env-id <envId> --yes
# Deploy fresh build
tcb hosting deploy ./dist --env-id <envId> --yes
```
---
## Workflow 3: List and Download
### List files (with pagination)
```bash
# List all files
tcb hosting list --env-id <envId>
# Paginated listing
tcb hosting list --env-id <envId> --limit 10 --offset 20
# JSON output
tcb hosting list --env-id <envId> --json
```
> ⚠️ `meta.total` in list output excludes directories (Size=0 entries) — count may seem inaccurate.
### Download files
```bash
# Download a single file
tcb hosting download path/to/file.txt --env-id <envId>
# Download to a specific local path
tcb hosting download path/to/file.txt ./local --env-id <envId>
# Download entire directory
tcb hosting download path/to/dir ./local --dir --env-id <envId>
# Download full hosting backup
tcb hosting download / ./hosting-backup --dir --env-id <envId>
```
---
## Common Options
| Option | Description |
|--------|-------------|
| `-e, --env-id <envId>` | Target environment ID |
| `--yes` | Skip interactive confirmation |
| `--json` | JSON output for scripting |
| `--dry-run` | Preview mode (delete only) |
| `-d, --dir` | Operate on directory |
| `-l, --limit <n>` | Max items returned (default 50) |
| `--offset <n>` | Skip N items (default 0) |
---
## Command Quick Reference
```bash
tcb hosting detail --env-id <id> # View hosting service info
tcb hosting deploy <localPath> [cloudPath] --env-id <id> # Deploy files
tcb hosting delete [cloudPath] --env-id <id> # Delete files
tcb hosting list --env-id <id> # List hosted files
tcb hosting download <cloudPath> [localPath] --env-id <id> # Download files
```
---
## Common Errors
| Error | Cause | Fix |
|-------|-------|-----|
| "Hosting not enabled" | Hosting service not activated | Run `tcb hosting detail -e <envId>` to auto-enable |
| File still accessible after delete | CDN cache delay (5-10 min) | Wait or flush CDN cache in console |
| `meta.total` looks wrong | Directories (Size=0) excluded from count | This is expected behavior |
| Deploy has no effect | Deploying to wrong path or env | Verify `--env-id` and cloud path; run `tcb hosting list` to check |
---
## Self-Check
- [ ] `tcb` CLI installed, version >= 3.0.0
- [ ] Logged in (`tcb login`) and correct environment set (`tcb env use <envId>`)
- [ ] Hosting service enabled (`tcb hosting detail --env-id <envId>`)
- [ ] Build output ready locally before deploying (e.g. `npm run build` completed)
- [ ] For deletion: previewed with `--dry-run` first
- [ ] For CI/CD: `--env-id` + `--yes` both specified
- [ ] CDN cache delay considered (5-10 min after updates/deletions)
references/cloudbase-cli/references/mysql.md
# MySQL Database Operations (`tcb db`)
Execute SQL, manage instances, backups, and slow queries for CloudBase MySQL via `tcb db` commands.
⚠️ MySQL commands live under **`tcb db ...`** (NOT `tcb db mysql ...`). Don't confuse with `tcb db nosql ...` (NoSQL).
## When to Use
- Execute SQL queries or mutations against CloudBase MySQL
- Inspect, restart, or resize MySQL instances
- Create, list, restore, or delete backups
- Analyze slow queries for performance troubleshooting
## Do NOT use for
- NoSQL/MongoDB operations → use `references/nosql.md` (commands are `tcb db nosql ...`)
- In-app MySQL queries via server SDK → use `cloud-functions` skill
- Storage file management → use `references/storage.md`
- Complex stored procedures or multi-statement transactions → use a MySQL client directly
## Command Quick Reference
```
tcb db execute Execute SQL statement
tcb db instance list List MySQL instances
tcb db instance restart Restart a MySQL instance
tcb db instance config get Read instance configuration
tcb db instance config set Resize CPU/memory
tcb db backup list List backups
tcb db backup create Create a backup
tcb db backup restore Restore from backup
tcb db backup drop Delete a backup
tcb db monitor slow-query Analyze slow queries
```
### Global options
- `-e, --envId <envId>` — target environment
- `--json` — structured output (use for automation)
- `--yes` — skip confirmation for destructive/confirmation-gated operations
⚠️ In `--json` mode, interactive prompts are suppressed — commands that need `--instance-id` or `--yes` will **fail silently** without them.
---
## Workflow 1: Safe SQL Execution
Always start with read-only queries for discovery:
```bash
# Read-only query
tcb db execute -e <envId> --sql "SELECT * FROM users WHERE status = 'active' LIMIT 10" --read-only --json
# Simple connectivity test
tcb db execute -e <envId> --sql "SELECT 1" --read-only --json
```
⚠️ Always use `--read-only` for SELECT queries to prevent accidental mutations.
### Data mutations (INSERT/UPDATE/DELETE)
```bash
# Insert
tcb db execute -e <envId> --sql "INSERT INTO users (name, age, status) VALUES ('alice', 25, 'active')" --json
# Update (keep WHERE clauses narrow)
tcb db execute -e <envId> --sql "UPDATE users SET status = 'inactive' WHERE id = 1001" --json
```
⚠️ Do NOT add `--read-only` to mutation SQL. Show the exact SQL to the user before execution.
⚠️ Always include a `WHERE` clause in `UPDATE` and `DELETE`. Without it, **all rows** are affected.
### `tcb db execute` options
| Option | Description |
|--------|-------------|
| `-s, --sql <sql>` | Required — the SQL statement |
| `--read-only` | Run in read-only mode |
| `--json` | Rows for SELECT; affected-row info for mutations |
---
## Workflow 2: Instance Management
### Inspect instances
```bash
# List all instances
tcb db instance list -e <envId> --json
# Get instance configuration
tcb db instance config get -e <envId> --instance-id <instanceId> --json
```
### Resize instance
```bash
# Step 1: Check current config
tcb db instance config get -e <envId> --instance-id <instanceId> --json
# Step 2: Resize (both --cpu and --memory required)
tcb db instance config set -e <envId> --instance-id <instanceId> --cpu 2 --memory 4 --yes
```
⚠️ Resizing changes CPU and memory together. May cause brief service interruption.
⚠️ In `--json` mode, `config set` **requires `--yes`** — otherwise it fails silently (no interactive prompt available).
### Restart instance
```bash
tcb db instance restart -e <envId> --instance-id <instanceId>
```
⚠️ Restart causes **service interruption**. Only use when:
- Instance is unresponsive
- Configuration changes require restart
- User explicitly confirms after understanding impact
⚠️ In `--json` mode, `--instance-id` is **required** for `restart`, `config get`, and `monitor slow-query`.
---
## Workflow 3: Backup and Restore
### Create and list backups
```bash
# List existing backups
tcb db backup list -e <envId> --json
# List with time range
tcb db backup list -e <envId> --start-time "2026-03-01 00:00:00" --end-time "2026-03-31 23:59:59" --json
# Create a manual backup
tcb db backup create -e <envId>
# Create logical backup of specific databases
tcb db backup create -e <envId> --type logic --databases db1,db2 --name nightly-manual
```
### Restore from backup — two strategies
⚠️ Verify which strategy you need before running. Mixing flags causes validation failure.
```bash
# Strategy A: Snapshot rollback (requires --backup-id)
tcb db backup restore -e <envId> --strategy snapRollback --backup-id <backupId>
# Strategy B: Point-in-time rollback (requires --expect-time)
tcb db backup restore -e <envId> --strategy timeRollback --expect-time "2024-03-15 14:00:00"
```
| Strategy | Required flag | Use case |
|----------|--------------|----------|
| `snapRollback` | `--backup-id` | Restore from a known backup artifact |
| `timeRollback` | `--expect-time` | Roll back to a verified timestamp |
⚠️ Restore is a **cluster-level** operation. Confirm scope and impact with the user.
### Delete a backup
```bash
tcb db backup drop -e <envId> --backup-id <backupId> --yes
```
⚠️ Backup deletion is **irreversible**. Confirm the backup ID and check retention/compliance requirements first.
### Recommended backup workflow
```bash
# 1. List to identify the target
tcb db backup list -e <envId> --json
# 2. Create safety backup before risky operations
tcb db backup create -e <envId>
# 3. Restore if needed
tcb db backup restore -e <envId> --strategy snapRollback --backup-id <backupId>
```
---
## Workflow 4: Slow Query Analysis
```bash
# Basic slow query inspection
tcb db monitor slow-query -e <envId> --instance-id <instanceId> --json
# Scoped by time range and threshold
tcb db monitor slow-query -e <envId> --instance-id <instanceId> \
--start "2026-03-01 00:00:00" --end "2026-03-01 23:59:59" \
--threshold 1 --json
```
### `monitor slow-query` options
| Option | Description |
|--------|-------------|
| `--instance-id` | Required in `--json` mode |
| `--start`, `--end` | Time range filter |
| `--threshold` | Local filter in seconds |
| `--order-by` | `QueryTime`, `LockTime`, `RowsExamined`, or `RowsSent` |
| `--order-by-type` | `asc` or `desc` |
| `--database` | Filter by database name |
| `--username` | Filter by user |
| `--limit`, `--offset` | Pagination |
**Analysis strategy:** Sort by `QueryTime` first → pivot to `RowsExamined` to find inefficient scans → filter by `--database` or `--username` for shared workloads.
---
## Real-World Scenarios
### Inspect-first workflow (recommended starting point)
```bash
tcb db instance list -e <envId> --json
tcb db backup list -e <envId> --json
tcb db monitor slow-query -e <envId> --instance-id <instanceId> --json
```
### Safe read → mutate cycle
```bash
# Preview affected rows
tcb db execute -e <envId> --sql "SELECT COUNT(*) FROM logs WHERE created_at < '2025-01-01'" --read-only --json
# Delete after user confirmation
tcb db execute -e <envId> --sql "DELETE FROM logs WHERE created_at < '2025-01-01'" --json
# Verify
tcb db execute -e <envId> --sql "SELECT COUNT(*) FROM logs" --read-only --json
```
### Full backup-restore cycle
```bash
tcb db backup list -e <envId> --json # identify backups
tcb db backup create -e <envId> # safety backup
tcb db backup restore -e <envId> --strategy snapRollback --backup-id <backupId>
```
### Cleanup old backups
```bash
tcb db backup list -e <envId> --json # identify old backups
tcb db backup drop -e <envId> --backup-id <backupId> --yes
```
---
## Common Errors
| Error / Symptom | Cause | Fix |
|-----------------|-------|-----|
| Missing instance in `--json` mode | `--instance-id` omitted | Add `--instance-id <id>` explicitly |
| `config set` fails silently in JSON mode | Missing `--yes` | Add `--yes` to skip suppressed prompt |
| `--sql` missing | No SQL statement provided | Add `--sql "..."` |
| Backup restore validation error | Strategy/flag mismatch | `snapRollback` → `--backup-id`; `timeRollback` → `--expect-time` |
| `--cpu`/`--memory` missing | Incomplete resize | Both `--cpu` and `--memory` are required |
| `UPDATE`/`DELETE` affects all rows | Missing `WHERE` clause | Always add a `WHERE` condition |
| No rows returned for mutation | Expected result set from INSERT/UPDATE | Mutations return affected-row info, not rows |
| Timeout on large queries | Query scans too many rows | Add indexes, use `LIMIT`, or narrow the `WHERE` clause |
---
## Self-Check
- [ ] Using `tcb db ...` (not `tcb db mysql ...`) for MySQL commands?
- [ ] Started with `--read-only` for exploratory queries?
- [ ] Included `WHERE` clause in all `UPDATE` and `DELETE` statements?
- [ ] Specified `--instance-id` explicitly (especially in `--json` mode)?
- [ ] For `config set --json`: included `--yes`?
- [ ] For backup restore: verified correct strategy + matching required flag?
- [ ] For destructive ops (restart, resize, backup drop): confirmed with user?
- [ ] Used `--json` for automation and script consumption?
- [ ] Showed exact SQL to user before executing mutations?
references/cloudbase-cli/references/nosql.md
# NoSQL Database Operations (`tcb db nosql`)
Execute MongoDB-style commands against CloudBase document database — CRUD, aggregation, backup, and restore.
⚠️ **Always run `tcb db nosql execute --help` first** — this is the most error-prone command family due to nested JSON encoding.
## When to Use
- Execute MongoDB-style CRUD against CloudBase NoSQL document database
- Run aggregation or count commands on document collections
- Manage backup/restore workflows for document collections
- Query restoreable timestamps or collections
- Track restore task status
## Do NOT use for
- MySQL/SQL database operations → use `references/mysql.md` (commands are `tcb db ...` without `nosql`)
- In-app database queries via Web/Mini-Program SDK → use `cloudbase-document-database-web-sdk` skill
- Cloud function database access via server SDK → use `cloud-functions` skill
- Storage file management → use `references/storage.md`
## Command Quick Reference
```
tcb db nosql execute Run Mongo-style commands
tcb db nosql backup time Discover restoreable timestamps
tcb db nosql backup collection List restoreable collections at a given time
tcb db nosql backup restore Submit a restore task
tcb db nosql backup task Track restore task status
```
⚠️ Don't confuse `tcb db nosql ...` (NoSQL) with `tcb db ...` (MySQL) — they are different command families.
### Global options
- `-e, --envId <envId>` — target environment
- `--json` — structured output (use for automation)
- `--tag <tag>` — select instance when multiple document DBs exist in the environment
---
## The MgoCommandParam Format (Critical)
Every `execute` call takes a `--command` argument: a **JSON array** of `MgoCommandParam` objects.
```json
[
{
"TableName": "users",
"CommandType": "QUERY",
"Command": "{\"find\":\"users\",\"filter\":{\"status\":\"active\"},\"limit\":10}"
}
]
```
⚠️ The `Command` field must be a **JSON-encoded string** (with escaped quotes), NOT a raw JSON object. This is the #1 source of errors.
### Two-layer structure
1. **Outer layer** — the `MgoCommands` JSON array (parsed by the CLI)
2. **Inner layer** — the `Command` value: a stringified MongoDB shell command
**Build process:** Write the inner MongoDB JSON first → stringify it (escape all `"` as `\"`) → paste into the `Command` field.
### CommandType → Command template
| `CommandType` | `Command` template |
|---|---|
| `QUERY` | `{"find":"<coll>","filter":{...},"limit":N}` |
| `INSERT` | `{"insert":"<coll>","documents":[{...}]}` |
| `UPDATE` | `{"update":"<coll>","updates":[{"q":{...},"u":{"$set":{...}}}]}` |
| `DELETE` | `{"delete":"<coll>","deletes":[{"q":{...},"limit":1}]}` |
| `COMMAND` (count) | `{"count":"<coll>","query":{...}}` |
| `COMMAND` (aggregate) | `{"aggregate":"<coll>","pipeline":[...],"cursor":{}}` |
Notes:
- `TableName` usually matches the target collection name.
- ⚠️ `UPDATE` and `DELETE` commonly fail because users pass an object instead of the required `updates`/`deletes` **array**.
- ⚠️ Aggregation requires `"cursor":{}` — omitting it causes an error.
---
## Workflow 1: Query Documents
```bash
tcb db nosql execute -e <envId> --command \
'[{"TableName":"users","CommandType":"QUERY","Command":"{\"find\":\"users\",\"filter\":{\"status\":\"active\"},\"limit\":10}"}]' --json
```
⚠️ Always start with a single **read** command before attempting writes.
---
## Workflow 2: Insert Documents
```bash
tcb db nosql execute -e <envId> --command \
'[{"TableName":"products","CommandType":"INSERT","Command":"{\"insert\":\"products\",\"documents\":[{\"name\":\"Widget A\",\"price\":29.99},{\"name\":\"Widget B\",\"price\":49.99}]}"}]' --json
```
---
## Workflow 3: Update Documents
```bash
tcb db nosql execute -e <envId> --command \
'[{"TableName":"users","CommandType":"UPDATE","Command":"{\"update\":\"users\",\"updates\":[{\"q\":{\"name\":\"alice\",\"status\":\"pending\"},\"u\":{\"$set\":{\"status\":\"active\",\"age\":26}}}]}"}]'
```
Inner `Command` (after unescaping) for reference:
```json
{
"update": "users",
"updates": [{
"q": { "name": "alice", "status": "pending" },
"u": { "$set": { "status": "active", "age": 26 } }
}]
}
```
---
## Workflow 4: Delete Documents
```bash
tcb db nosql execute -e <envId> --command \
'[{"TableName":"sessions","CommandType":"DELETE","Command":"{\"delete\":\"sessions\",\"deletes\":[{\"q\":{\"expiredAt\":{\"$lt\":\"2024-01-01\"}},\"limit\":0}]}"}]' --json
```
- `"limit": 1` → delete one matching document
- `"limit": 0` → delete **all** matching documents
⚠️ Always preview with a QUERY command before running DELETE.
---
## Workflow 5: Aggregation
```bash
tcb db nosql execute -e <envId> --command \
'[{"TableName":"orders","CommandType":"COMMAND","Command":"{\"aggregate\":\"orders\",\"pipeline\":[{\"$match\":{\"status\":\"done\"}},{\"$group\":{\"_id\":\"$product\",\"total\":{\"$sum\":\"$amount\"}}}],\"cursor\":{}}"}]' --json
```
⚠️ The `"cursor":{}` field is **required** for aggregation — omitting it causes an error.
---
## Workflow 6: Backup and Restore
Resolve restore inputs **in order** — do not skip steps:
```bash
# Step 1: Discover restoreable timestamps
tcb db nosql backup time -e <envId> --json
# Step 2: List restoreable collections at that time
tcb db nosql backup collection -e <envId> --time "2024-03-15 14:00:00" --json
# Step 3: Submit restore (creates NEW collections, does NOT overwrite)
tcb db nosql backup restore -e <envId> \
--time "2024-03-15 14:00:00" \
--tables '[{"OldTableName":"users","NewTableName":"users_restore_20240315"}]'
# Step 4: Track restore progress
tcb db nosql backup task -e <envId> --json
```
⚠️ Restore creates **new** collections with `NewTableName`. Original collections remain untouched.
### Backup command options
| Command | Required options |
|---------|-----------------|
| `backup time` | `-e <envId>` |
| `backup collection` | `-e <envId>`, `--time`; optionally `--filters users,orders` |
| `backup restore` | `-e <envId>`, `--time`, `--tables` (non-empty JSON array) |
| `backup task` | `-e <envId>` |
### Validation rules
- `--tables` must parse as a **non-empty** JSON array.
- `--time` is required for `backup collection` and `backup restore`.
- Use `--tag <tag>` when the environment has multiple document database instances.
---
## Workflow 7: Multi-Collection Batch Query
```bash
tcb db nosql execute -e <envId> --command '[
{"TableName":"users","CommandType":"QUERY","Command":"{\"find\":\"users\",\"filter\":{},\"limit\":5}"},
{"TableName":"products","CommandType":"QUERY","Command":"{\"find\":\"products\",\"filter\":{\"price\":{\"$gt\":100}},\"limit\":5}"}
]' --json
```
⚠️ Validate each command individually before batching multiple operations.
---
## Shell Quoting Rules
- Wrap the entire `--command` value in **single quotes** (`'...'`) in bash/zsh
- Use double quotes inside JSON keys and string values
- Escape double quotes inside the inner `Command` string as `\"`
- ⚠️ If the command contains `$set`, `$gt`, etc., single quotes **prevent shell `$` interpolation**
```bash
# CORRECT — single quotes protect $ and inner \"
tcb db nosql execute -e <envId> --command '[{"TableName":"users","CommandType":"UPDATE","Command":"{\"update\":\"users\",\"updates\":[{\"q\":{\"name\":\"alice\"},\"u\":{\"$set\":{\"status\":\"active\"}}}]}"}]'
# WRONG — double quotes cause shell to interpret $ and break JSON
tcb db nosql execute -e <envId> --command "[{"TableName":"users"...}]"
```
**Practical tips:**
- Build the inner JSON in an editor first, then compress to one line for the shell.
- When debugging, validate the outer payload first (must be a JSON array), then inspect only the inner `Command` string.
- If parsing still fails, check for unescaped backslashes in the inner string.
### Connector options (advanced)
Use `--instance-id` and `--database-name` only when targeting a specific connector. If not needed, omit them for the simplest working command.
---
## Common Errors
| Error / Symptom | Cause | Fix |
|-----------------|-------|-----|
| `--command` parse failure | Not a valid JSON array | Validate JSON locally; must be `[...]` not `{...}` |
| `Command` field rejected | Raw object instead of string | Stringify inner JSON: escape `"` as `\"` |
| `UPDATE`/`DELETE` fails silently | Missing `updates`/`deletes` array | Use `"updates":[{...}]` not a bare object |
| `$set` / `$gt` resolves to empty | Shell interprets `$` as variable | Switch to single quotes around `--command` |
| Wrong database targeted | Multiple instances, no `--tag` | Add `--tag <tag>` |
| `--tables` parse failure | Not a non-empty JSON array | Validate: `[{"OldTableName":"x","NewTableName":"y"}]` |
| `aggregate` returns error | Missing `"cursor":{}` | Add `\"cursor\":{}` to the aggregate command |
| Restore seems to have no effect | Looking at original collection | Check the `NewTableName` collection instead |
---
## Self-Check
- [ ] Ran `tcb db nosql execute --help` to verify current command syntax?
- [ ] `--command` is a valid JSON **array** (not a single object)?
- [ ] Inner `Command` field is a JSON-encoded **string** (with escaped quotes)?
- [ ] Used single quotes around `--command` value in shell?
- [ ] Started with a read command before writes?
- [ ] For restore: discovered time → collections → submitted restore (in order)?
- [ ] Used `--tag` when environment has multiple document DB instances?
- [ ] Used `--json` for automation and debugging?
references/cloudbase-cli/references/permission.md
# Permission — CloudBase CLI
CloudBase access control has **three independent layers** — know which one to use before running any command:
| Layer | Command | Controls |
|-------|---------|----------|
| **Resource Permission** | `tcb permission get/set` | Access level on a specific resource (table, collection, function, storage) |
| **Role** | `tcb role ...` | Policy bundles + member assignments (identity dimension) |
| **User** | `tcb user ...` | Account attributes only (name, email, status) — NOT role binding |
> ⚠️ Role policies and resource permissions are **two parallel systems with NO automatic sync**. Changing a role policy does NOT affect `permission get` results, and vice versa. Audit both separately.
---
## When to Use
- Managing resource-level access (table/collection/function/storage access levels)
- Creating, updating, or deleting roles with policies and user assignments
- Managing user accounts (create, update status, delete)
- Auditing permission state across resources and roles
## Do NOT use for
- Storage ACL rules (use `tcb-storage` `rules get/update`)
- CORS / domain / routing access (use `tcb-access`)
- CloudBase console access control (CLI-managed permissions only)
---
## Workflow 1: Manage Resource Permissions (`tcb permission`)
### Step 1 — Query current state
```bash
tcb permission get --env-id <envId> # all resource types
tcb permission get table --env-id <envId> # all tables
tcb permission get table:users,orders --env-id <envId> # specific resources (max 100)
tcb permission get function --env-id <envId> # functions
```
> ⚠️ Do NOT use `function:` (colon with empty resource) — returns empty results. Use `function` instead.
### Step 2 — Set permissions
```bash
# Fixed level
tcb permission set table:users --level readonly --env-id <envId>
tcb permission set storage:assets --level private --env-id <envId>
# Function (custom only, --rule required)
tcb permission set function --level custom \
--rule '{"*":{"invoke":"auth != null && auth.loginType != '\''ANONYMOUS'\''"}}' \
--env-id <envId>
# Rule without level => defaults to custom
tcb permission set collection:posts --rule '{"read": true, "write": false}' --env-id <envId>
```
**Combination rules:**
- Must provide at least `--level` or `--rule`
- `--rule` without `--level` => auto `custom`; `custom` level requires `--rule`
- `function` only supports `custom`
- ⚠️ `set` requires `type:resource` for table/collection/storage — only `function` can omit resource name
### Allowed levels by resource type
| Resource | Levels |
|----------|--------|
| `table` | `readonly`, `private`, `adminwrite`, `adminonly` |
| `collection` | `readonly`, `private`, `adminwrite`, `adminonly`, `custom` |
| `function` | `custom` only |
| `storage` | `readonly`, `private`, `adminwrite`, `adminonly`, `custom` |
---
## Workflow 2: Manage Roles (`tcb role`)
### Step 1 — List and inspect
```bash
tcb role list --env-id <envId>
tcb role list --type custom --detail --env-id <envId>
tcb role get --id <roleId> --detail --env-id <envId>
```
> ⚠️ `role get` query conditions `--id` / `--identity` / `--name` are **mutually exclusive** — pass exactly one.
### Step 2 — Create or update (parameter sets differ!)
| Action | Policies param | Members param |
|--------|---------------|---------------|
| `role create` | `--policies` | `--users` |
| `role update` | `--add-policies` / `--remove-policies` | `--add-users` / `--remove-users` |
> ⚠️ Do NOT use `--add-policies` with `create`, or `--policies` with `update` — they will fail.
```bash
# Create with preset policy codes
tcb role create --name "developer" --identity dev_role \
--policies '["FunctionsAccess","StoragesAccess"]' \
--users "u1001,u1002" --env-id <envId>
# Update: add policies + members
tcb role update --id <roleId> \
--add-policies '["CloudrunAccess"]' \
--add-users "u1003" --yes --env-id <envId>
# Update: remove
tcb role update --id <roleId> \
--remove-policies '["StoragesDeny"]' \
--remove-users "u1002" --yes --env-id <envId>
```
**Preset policy codes:** `AdministratorAccess`, `FunctionsAccess`, `StoragesAccess`, `CloudrunAccess`, `FunctionsDeny`, `StoragesDeny`, `CloudrunDeny`
### Step 3 — Custom policy objects
Policies array can mix preset codes (strings) and custom objects:
```bash
tcb role update --id <roleId> --add-policies '[
"FunctionsAccess",
{
"code": "api_guard",
"name": "API Guard",
"description": "Allow /api, deny /api/admin",
"effect": "deny",
"expression": {
"version": "1.0",
"statement": [
{"action": "functions:/api/*", "resource": "*", "effect": "allow"},
{"action": "functions:/api/admin/*", "resource": "*", "effect": "deny"}
]
}
}
]' --yes --env-id <envId>
```
**Policy object fields:** `code` (required), `name` (required), `description`, `effect` (`allow`|`deny`), `expression` (JSON object, NOT string) with `version` ("1.0") and `statement` array.
> ⚠️ `expression` must be a JSON **object**, not a string. When `allow` and `deny` both match, **deny wins**.
### Step 4 — System role constraints
| Role Type | Modify users | Modify policies | Modify name |
|-----------|:---:|:---:|:---:|
| 管理员 (Admin) | ✅ | ❌ | ❌ |
| 注册用户/组织成员/匿名用户/所有用户 | ❌ | ✅ | ❌ |
| Custom roles | ✅ | ✅ | ✅ |
### Step 5 — Delete roles
```bash
tcb role delete <roleId1> <roleId2> --yes --env-id <envId> # max 100, custom only
```
---
## Workflow 3: Manage Users (`tcb user`)
```bash
# List (filters combinable)
tcb user list --name alice --email a@example.com --env-id <envId>
# Create
tcb user create alice --uid u1001 --type internalUser --status ACTIVE --env-id <envId>
# Update (NO --role parameter!)
tcb user update u1001 --status BLOCKED --env-id <envId>
# Delete (max 100)
tcb user delete u1001 u1002 --yes --env-id <envId>
```
> ⚠️ `tcb user update` has NO `--role` param. To assign roles, use `tcb role create --users` or `tcb role update --add-users`.
---
## Workflow 4: Audit & Revoke
```bash
# 1) Full role inventory
tcb role list --detail --env-id <envId> --json
# 2) Spot-check critical resources
tcb permission get table:users,orders --env-id <envId>
tcb permission get function --env-id <envId>
# 3) Revoke temporary access
tcb role update --id <roleId> --remove-users "u_temp" --yes --env-id <envId>
# 4) Optional: block account
tcb user update u_temp --status BLOCKED --env-id <envId>
```
---
## Decision Guide
| Goal | Command |
|------|---------|
| Change a resource's access level | `permission set` |
| Manage access policies for an identity group | `role create/update` |
| Assign user to a role | `role create --users` (new) or `role update --add-users` (existing) |
| Change user profile/status | `user update` |
| Full audit | `role list --detail` + `permission get` on key resources |
---
## Command Quick Reference
```bash
tcb permission get [resourceArg] # Query resource permissions
tcb permission set <resourceArg> # Set resource permissions
tcb role list # List roles
tcb role get # Get single role (--id/--identity/--name, pick ONE)
tcb role create # Create role (--policies, --users)
tcb role update # Update role (--add-*/-remove-*, --id required)
tcb role delete <roleIds...> # Delete roles (custom only, max 100)
tcb user list # List users
tcb user create <name> # Create user
tcb user update <uid> # Update user (NO --role!)
tcb user delete <uids...> # Delete users (max 100)
```
**Global flags:** `--env-id <envId>` (required), `--json` (machine output), `--yes` (skip confirmation for CI)
---
## Common Errors
| Error | Cause | Fix |
|-------|-------|-----|
| "请且仅传入一个查询条件" | `role get` with multiple or zero query conditions | Use exactly one of `--id`/`--identity`/`--name` |
| "资源类型不支持权限级别" | `permission set` level incompatible with resource type | Check allowed levels table above |
| "权限级别为 custom 时,需要提供 --rule" | Missing `--rule` with custom level | Add `--rule` JSON |
| JSON parse error | `--policies`/`--add-policies` not valid JSON array | Validate JSON before execution |
| Role update silently fails | Violating system role constraints | Check system role table — admin can't change policies, etc. |
| "不存在的命令" | Using `permission list` or `role detail` | Correct: `permission get` / `role get` |
---
## Real-World Scenarios
### Scenario 1: Team Onboarding
```bash
tcb role list --type custom --env-id <envId> # confirm role exists
tcb role update --id <devRoleId> --add-users "<newUid>" --yes --env-id <envId>
tcb role get --id <devRoleId> --detail --env-id <envId> # verify
```
### Scenario 2: Contractor Temporary Access + Revocation
```bash
# Grant
tcb role create --name "contractor-ro" --identity contractor_ro \
--policies '["StoragesAccess"]' --env-id <envId>
tcb role update --id <roleId> --add-users "<contractorUid>" --yes --env-id <envId>
# Revoke on contract end
tcb role update --id <roleId> --remove-users "<contractorUid>" --yes --env-id <envId>
tcb user update <contractorUid> --status BLOCKED --env-id <envId>
```
### Scenario 3: Store RBAC (Owner / Manager / Clerk)
```bash
# Owner: full access + admin console
tcb role create --name "owner" --identity shop_owner \
--policies '["FunctionsAccess",{"code":"owner_admin","name":"Admin Access","effect":"allow","expression":{"version":"1.0","statement":[{"action":"functions:/shop/admin/*","resource":"*","effect":"allow"}]}}]' \
--env-id <envId>
# Clerk: POS only, deny refund
tcb role create --name "clerk" --identity shop_clerk \
--policies '[{"code":"clerk_pos","name":"POS Only","effect":"allow","expression":{"version":"1.0","statement":[{"action":"functions:/shop/pos/*","resource":"*","effect":"allow"},{"action":"functions:/shop/pos/refund/*","resource":"*","effect":"deny"}]}}]' \
--env-id <envId>
# Resource baseline (separate from role policies!)
tcb permission set table:orders --level private --yes --env-id <envId>
tcb permission set function --level custom --rule '{"*":{"invoke":"auth != null"}}' --yes --env-id <envId>
```
---
## Self-Check
- [ ] `tcb` >= 3.0.0 and logged in with correct environment
- [ ] Identified correct command: `permission` (resource) vs `role` (identity) vs `user` (account)
- [ ] For `permission set`: resource format is `type:resource` (except `function`)
- [ ] For `role create`: using `--policies`/`--users` (NOT `--add-*`)
- [ ] For `role update`: using `--add-*`/`--remove-*` (NOT `--policies`), `--id` is set
- [ ] For `role get`: exactly ONE of `--id`/`--identity`/`--name`
- [ ] System role constraints checked before update
- [ ] Policy JSON: `expression` is object (not string), has `version` + `statement`
- [ ] `--yes` added for CI; `--json` added for programmatic parsing
- [ ] Audited BOTH role policies AND resource permissions (they are independent)
references/cloudbase-cli/references/storage.md
# Cloud Storage Management (`tcb storage`)
Manage CloudBase cloud storage — upload, download, delete, copy/move files, generate temp URLs, and configure ACL rules.
## When to Use
- Upload or download files to/from CloudBase cloud storage
- Delete files (single, batch, or wildcard-based)
- Generate temporary access URLs for stored files
- Copy or move files within cloud storage
- Manage storage ACL permission rules
## Do NOT use for
- Static website hosting with CDN → use `references/hosting.md`
- Web app deployment → use `references/app.md`
- Database operations → use `references/mysql.md` or `references/nosql.md`
- In-app file operations via Web/Mini-Program SDK → use `cloud-storage-web` skill
- Large-scale data migration (>10 GB) → use the console bulk-import tool
## Command Quick Reference
```
tcb storage upload Upload local file(s) or directory
tcb storage download Download file(s) or directory
tcb storage rm Delete file(s) — supports wildcards and --dry-run
tcb storage list List files in storage
tcb storage url Get temporary access URL
tcb storage detail Get file metadata
tcb storage cp Copy or move files in cloud
tcb storage rules get Get storage ACL rules
tcb storage rules update Update storage ACL rules
```
⚠️ `storage delete`, `storage get-acl`, `storage set-acl` are **deprecated** — use the new commands:
| Old (deprecated) | New command |
|-------------------|-------------|
| `storage delete` | `storage rm` |
| `storage get-acl` | `storage rules get` |
| `storage set-acl` | `storage rules update` |
---
## Workflow 1: Upload Files
```bash
# Single file
tcb storage upload ./logo.png images/logo.png -e <envId>
# Directory (recursive)
tcb storage upload ./images images/ -e <envId>
# With retry (0-10 retries, default 1)
tcb storage upload ./images images/ --times 3 --interval 1000 -e <envId>
```
⚠️ **`cloudPath` must NOT start with `/`** — this is the #1 upload error.
```bash
# WRONG — errors with "cloudPath cannot start with /"
tcb storage upload ./logo.png /images/logo.png
# CORRECT
tcb storage upload ./logo.png images/logo.png
```
For 50+ file uploads, check `cloudbase-error.log` for partial failure details. Retry with `--times 5 --interval 1000`.
---
## Workflow 2: Download Files
```bash
# Single file
tcb storage download images/logo.png ./logo.png -e <envId>
# Directory — requires --dir
tcb storage download images/ ./images --dir -e <envId>
```
⚠️ Missing `--dir` when downloading a folder will fail or only affect one file.
---
## Workflow 3: Safe File Deletion
Always preview before executing:
```bash
# 1. Dry-run preview (no actual deletion)
tcb storage rm "*.tmp" --dry-run -e <envId>
# 2. Execute after confirming
tcb storage rm "*.tmp" --force -e <envId>
```
### Deletion patterns
```bash
tcb storage rm file.txt -e <envId> # Single file
tcb storage rm file1.txt file2.txt -e <envId> # Multiple files
tcb storage rm "*.log" -e <envId> # Wildcard — current dir only
tcb storage rm "temp/**" -e <envId> # Recursive wildcard
tcb storage rm folder/ --dir -e <envId> # Directory
```
⚠️ Wildcard patterns **must be quoted** — use `"*.log"`, not `*.log`. Unquoted globs expand against your local filesystem, not cloud storage.
⚠️ Deleting 2+ files triggers a confirmation prompt. Use `--force` to skip (required in CI/scripts).
### Wildcard rules
| Pattern | Meaning |
|---------|---------|
| `*` | Match any filename in current directory (not across `/`) |
| `**` | Match any path including `/` (recursive) |
| `?` | Match single character (not `/`) |
```bash
# Only root-level .log files
tcb storage rm "*.log" -e <envId>
# .log files in ALL directories
tcb storage rm "**/*.log" -e <envId>
```
---
## Workflow 4: Temporary URL Generation
```bash
# Default expiry: 3600 seconds
tcb storage url images/logo.png -e <envId>
# Custom expiry (1-86400 seconds)
tcb storage url data.json --expires 7200 -e <envId>
```
---
## Workflow 5: Copy and Move Files
```bash
tcb storage cp images/a.jpg backup/a.jpg -e <envId> # Copy
tcb storage cp old/data.json new/data.json --move -e <envId> # Move (copy + delete source)
tcb storage cp src.txt dest.txt --force -e <envId> # Overwrite existing
tcb storage cp src.txt dest.txt --skip-existing -e <envId> # Skip if exists
```
⚠️ `cp` only supports **file-level** operations, NOT directories. To copy a directory, script a loop over `tcb storage list` output and copy each file individually.
---
## Workflow 6: ACL Permission Management
```bash
# Get current rules
tcb storage rules get -e <envId>
# Set predefined ACL
tcb storage rules update --acl READONLY -e <envId>
# Set custom rules
tcb storage rules update --acl CUSTOM \
--rule '{"read": true, "write": "auth.openid == resource.openid"}' -e <envId>
```
### Predefined ACL types
| ACL value | Read | Write | Use case |
|-----------|------|-------|----------|
| `READONLY` | Everyone | Creator + admin | Public assets (images, documents) |
| `PRIVATE` | Creator + admin | Creator + admin | User private data (default) |
| `ADMINWRITE` | Everyone | Admin only | Read-only public resources |
| `ADMINONLY` | Admin only | Admin only | Sensitive internal data |
| `CUSTOM` | Per rule | Per rule | Fine-grained access control |
### Custom rule format
```json
{ "read": <condition>, "write": <condition> }
```
At least one of `read` or `write` must be present. Condition values:
- `true` — unrestricted
- `false` — deny all
- Expression string — evaluated per request
| Variable | Description |
|----------|-------------|
| `auth.openid` | OpenID of the currently authenticated user |
| `resource.openid` | OpenID of the user who uploaded the file |
**Example rules:**
```bash
# Public read, owner-only write
--rule '{"read": true, "write": "auth.openid == resource.openid"}'
# Authenticated users only (read + write)
--rule '{"read": "auth != null", "write": "auth != null"}'
# Public read, no writes
--rule '{"read": true, "write": false}'
```
---
## Real-World Scenarios
### Static asset deploy
```bash
npm run build
tcb storage upload ./dist/ website/ -e <envId>
tcb storage rules update --acl READONLY -e <envId>
tcb storage list website/ -e <envId> # verify
```
### Upload user content with signed URL
```bash
tcb storage upload ./uploads/avatar-001.jpg avatars/user-001.jpg -e <envId>
tcb storage url avatars/user-001.jpg --expires 3600 -e <envId>
tcb storage detail avatars/user-001.jpg -e <envId>
```
### Backup and restore files
```bash
tcb storage download backups/ ./local-backups/ --dir -e <envId>
tcb storage upload ./local-backups/ backups/ -e staging-env-xxx # restore to different env
tcb storage list backups/ -e <envId> # verify file count
```
### Batch cleanup old files
```bash
tcb storage rm "temp/**" --dry-run -e <envId> # preview
tcb storage rm "temp/**" --force -e <envId> # execute
tcb storage rm "**/*.log" --force -e <envId> # cross-directory cleanup
```
### Copy files for migration
```bash
tcb storage cp data/report.pdf archive/2024/report.pdf -e <envId>
tcb storage cp old-path/config.json new-path/config.json --move -e <envId>
```
---
## Common Errors
| Error / Symptom | Cause | Fix |
|-----------------|-------|-----|
| `cloudPath cannot start with /` | Leading `/` in cloud path | Remove the leading `/` |
| `FILE_NOT_FOUND` | File doesn't exist or wrong path | Check with `tcb storage list`; ensure no leading `/`; use `--dir` for folders |
| Partial upload (`failedCount > 0`) | Network issues on large batch | Retry: `--times 5 --interval 1000`; check `cloudbase-error.log` |
| Delete hangs in CI | Confirmation prompt blocking | Add `--force` |
| `cp` destination already exists | Target file present | Use `--force` (overwrite) or `--skip-existing` |
| `cp` silently skips subdirectories | `cp` is file-only | Loop over `tcb storage list` and copy each file |
| `command not found: delete` | Deprecated command | Use `tcb storage rm` |
| `command not found: get-acl` | Deprecated command | Use `tcb storage rules get` / `rules update` |
### JSON output key fields
| Command | Key fields |
|---------|-----------|
| `storage rm` (success) | `{ deletedCount, files }` |
| `storage rm` (not found) | `{ error: true, code: "FILE_NOT_FOUND", notFoundPaths }` |
| `storage list` | `[{ key, lastModified, eTag, size }]` + `total` |
| `storage url` | `{ url, expires }` |
| `storage detail` | `{ size, type, date, eTag }` |
| `storage rules get` | `{ acl, aclDesc, rule }` |
### Debugging tips
```bash
tcb storage detail images/logo.png -e <envId> # check file exists + metadata
tcb storage list images/ -e <envId> # list directory contents
tcb storage rm "temp/**" --dry-run -e <envId> # preview before delete
tcb storage rm file.txt --json -e <envId> # script-friendly output
```
---
## Self-Check
- [ ] `cloudPath` does NOT start with `/`?
- [ ] Used `--dry-run` before batch/wildcard deletions?
- [ ] Wildcard patterns are properly quoted in shell?
- [ ] Used new commands (`rm`, `rules get/update`) not deprecated ones?
- [ ] Used `--dir` for directory download/delete?
- [ ] Used `--force` for non-interactive CI/script usage?
- [ ] Verified result with `list` / `detail` after each operation?
references/cloudbase-cli/SKILL.md
---
name: cloudbase-cli
description: CloudBase CLI (tcb, 云开发CLI, Tencent CloudBase命令行) resource management skill. Use when deploying cloud functions, CloudRun, storage, NoSQL/MySQL, static hosting, permissions, CORS/domains via tcb; for CI/CD and batch ops; when the user prefers CLI; or as the first-session fallback when CloudBase MCP tools are not loaded yet (after install/config, before IDE restart). Covers tcb login (device code for Tencent Cloud accounts; --cloudbase-api-key -e for environment API Key without an account; --apiKeyId/--apiKey for CI) and domain commands (fn/hosting/cloudrun/…) as MCP auth/manage parity — do not default to tcb deploy.
version: 2.33.1
alwaysApply: false
---
# CloudBase CLI
Manage CloudBase resources via `tcb` CLI — deterministic, scriptable, auditable.
Primary interface for CI/CD and batch ops; **also the first-session fallback** when MCP tools are not yet available in the conversation.
## Sibling skills (local only)
Sibling CloudBase skills ship beside this skill. Use local relative paths such as `../auth-tool-cloudbase/SKILL.md`.
If a referenced sibling skill file is missing from this environment, ask the user to install the full CloudBase plugin (or the missing skill). Do **not** HTTP-fetch remote skill or protocol markdown into the agent context.
**Cross-cutting protocols** (required before code changes or deployments):
- Change Safety Protocol: `../cloudbase-platform/references/protocols/change-safety-protocol.md`
- Deployment Gate: `../cloudbase-platform/references/protocols/deployment-gate.md`
- MCP vs CLI fallback: `../cloudbase/references/tooling-fallback.md` (packaged beside this skill as the `cloudbase` entry guideline)
## Core Principles
1. **`--help` first — never guess commands.**
tcb CLI changes between versions. Before using any command for the first time,
run `tcb <command> --help` to check parameters and discover official doc links.
2. **Deployment Gate.**
Before any deployment, publish, custom domain, or CloudRun operation, you must first complete the checks in `cloudbase-platform/references/protocols/deployment-gate.md`.
3. **Verify your work.**
After deploying or modifying any resource, run the corresponding list/detail
command to confirm the change took effect.
3. **Dry-run before destructive actions.**
Use `--dry-run` for delete/overwrite operations. Show the preview to the user
and wait for explicit confirmation before executing.
4. **Confirm environment first.**
Always verify envId with the user before operations. Run `tcb env use <envId>`
to avoid accidentally modifying production.
5. **Recover from errors, don't loop.**
If a command fails after 2-3 attempts, check the exit code (`$?`), read the
error message, consult `tcb docs search`, and try a different approach.
6. **First-session fallback, not MCP replacement.**
If CloudBase MCP tools are missing in this session, use this skill to unblock
login/manage. Still configure MCP (plugin / mcp.json) so the **next** session
can prefer MCP. When MCP tools are already available, prefer MCP unless the
user asked for CLI/CI. Route deploy work through the domain reference table
below — **do not** recommend `tcb deploy`.
7. **No npm/npx.**
If Node/npm/npx are missing, tell the user to install Node.js LTS (or use the
IDE marketplace MCP path) before retrying CLI/plugin install. See guideline
`tooling-fallback.md`.
## When to use this skill
Use when the user wants to manage CloudBase resources via command line, **or** when MCP is not usable yet:
- **First session / post-install:** MCP not in the tool list, or just configured and needs restart → `tcb login` + the matching domain command now; leave MCP ready for next session
- Deploy/debug cloud functions, static hosting, CloudRun services (via domain refs — not `tcb deploy`)
- Manage storage, hosting, databases (NoSQL/MySQL)
- Configure permissions, CORS, domains, routing
- CI/CD scripting, batch operations, terminal-based resource management
- User explicitly prefers CLI over MCP
## Do NOT use for
- SDK-based in-app integration (web/miniprogram/node) → use `cloud-functions`,
`cloudbase-document-database-web-sdk`, `auth-web-cloudbase`, etc.
- When CloudBase MCP tools are already available in this session and the user did not ask for CLI → prefer MCP
- Console UI operations
- CloudBase Agent SDK development → use `cloudbase-agent-ts`
## How to use this skill (for a coding agent)
1. **Always load `references/core.md` first** — it covers authentication,
environment switching, `tcb docs` queries, and error diagnosis.
2. **Route to the correct domain reference** using the Routing table below.
3. **Load only the one reference file** that matches the user's task.
Do not preload all references.
4. **Stop loading more context** once you have the workflow and command
syntax for the current task.
5. **If the task shifts to SDK/in-app code**, switch to the appropriate
SDK skill (e.g., `cloud-functions`, `cloudbase-document-database-web-sdk`) instead.
## Routing
| User Task | Read |
|-----------|------|
| Login, env switching, tcb docs, error diagnosis | `references/core.md` |
| Deploy/debug cloud functions | `references/functions.md` |
| Deploy static site / SPA (preferred CLI web path) | `references/hosting.md` |
| Deploy CloudRun service | `references/cloudrun.md` |
| Experimental all-in-one web shorthand (`tcb deploy`) — avoid unless user explicitly asks | `references/app.md` |
| Upload/download files, ACL rules | `references/storage.md` |
| NoSQL (MongoDB) database operations | `references/nosql.md` |
| MySQL database operations | `references/mysql.md` |
| Roles, policies, access control | `references/permission.md` |
| CORS, custom domains, routing rules | `references/access.md` |
## Quick workflow
1. `tcb login` → confirm envId with user → `tcb env use <envId>`
2. `tcb <command> --help` to verify syntax
3. Execute the command (with `--dry-run` for destructive ops)
4. Verify the result with the corresponding `list` / `detail` command
5. Report the outcome to the user
## Minimum self-check
- [ ] Loaded `references/core.md` before any domain module?
- [ ] Confirmed target envId with the user?
- [ ] Used `--help` for unfamiliar commands?
- [ ] Used `--dry-run` before destructive operations?
- [ ] Verified the result after each operation?
- [ ] Stayed within CLI scope — did not drift into SDK code?
## Reference index
All packaged reference files (required for skill lint reachability):
- [access.md](references/access.md)
- [app.md](references/app.md)
- [cloudrun.md](references/cloudrun.md)
- [core.md](references/core.md)
- [functions.md](references/functions.md)
- [hosting.md](references/hosting.md)
- [mysql.md](references/mysql.md)
- [nosql.md](references/nosql.md)
- [permission.md](references/permission.md)
- [storage.md](references/storage.md)
references/cloudbase-code-review/references/lint-rules/README.md
# cloudbase-lint: CloudBase 代码检查脚本
> 安全说明:本目录不提交可执行脚本文件。下面只提供可审阅的 JavaScript 代码块;仅在用户明确同意时,将其复制到临时本地文件 `cloudbase-lint.mjs` 后运行。
## 使用方法
```bash
# 可选:用户同意后,从下方代码块复制全部内容到临时 cloudbase-lint.mjs
# 然后在项目根目录运行
node cloudbase-lint.mjs --project-dir .
```
## 脚本代码
```javascript
#!/usr/bin/env node
/**
* cloudbase-lint: CloudBase 项目代码检查脚本
*
* 检查 CloudBase 项目中的常见错误:
* - auth 路由守卫误用(getUser 代替 getSession)
* - 密码错误 API(signInWithEmailAndPassword 等)
* - PG 表未创建
* - 安全域名未配置
* - 存储 URL 拼接
* - 已废弃 API 使用
* - 云函数/云托管回显 x-cloudbase-context、headers、process.env 或 httpbin
*
* 返回 0 表示无错误,返回 1 表示发现问题。
*/
import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs';
import { join, resolve } from 'node:path';
const args = process.argv.slice(2);
const projectDirIdx = args.indexOf('--project-dir');
const projectDir = projectDirIdx >= 0 ? resolve(args[projectDirIdx + 1]) : process.cwd();
if (!existsSync(projectDir)) {
console.error(`[cloudbase-lint] project-dir not found: ${projectDir}`);
process.exit(1);
}
function findSourceFiles(dir) {
const results = [];
const entries = readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory() && entry.name !== 'node_modules' && entry.name !== 'dist' && entry.name !== '.git') {
results.push(...findSourceFiles(fullPath));
} else if (entry.isFile() && /\.(ts|tsx|js|jsx|html|sql|json)$/i.test(entry.name)) {
results.push(fullPath);
}
}
return results;
}
function readFileSafe(filePath) {
try {
const stat = statSync(filePath);
if (stat.size > 1024 * 1024) return '';
return readFileSync(filePath, 'utf-8');
} catch { return ''; }
}
const results = [];
function record(ruleId, severity, message, filePath, line) {
results.push({ ruleId, severity, message, filePath, line });
}
// ─── Auth Rules ─────────────────────────────────────────────────────────────
function checkAuthGetUser(files) {
const authGuardFiles = files.filter(f =>
/(auth|guard|ProtectedRoute|checkAuth)/i.test(f) || /auth\.(ts|tsx|js)$/i.test(f)
);
for (const file of authGuardFiles) {
const content = readFileSafe(file);
if (!content) continue;
const lines = content.split('\n');
for (let i = 0; i < lines.length; i++) {
if (/auth\.getUser\s*\(\s*\)/.test(lines[i]) && !content.includes('getSession')) {
record('AUTH001', 'error',
`路由守卫中使用 auth.getUser()。getUser() 不是可靠登录证明(显式 signInAnonymously() 后也会返回匿名 user)。应使用 auth.getSession() 并检查 data?.session;accessKey 不会自动创建 gateway 会话。`,
file, i + 1);
}
}
}
}
function checkEmailPasswordApi(files) {
const pattern = /sign(?:In|Up)WithEmailAndPassword\s*\(/;
for (const file of files.filter(f => /auth|login|register|sign/i.test(f))) {
const content = readFileSafe(file);
if (!content) continue;
content.split('\n').forEach((line, i) => {
if (pattern.test(line)) {
record('AUTH-WEB-004', 'error',
`用户名场景禁止使用 signInWithEmailAndPassword / signUpWithEmailAndPassword。应使用 auth.signInWithPassword({ username, password }) / auth.signUp({ username, password })`,
file, i + 1);
}
});
}
}
function checkLoginFormType(files) {
for (const file of files.filter(f => /login|register/i.test(f))) {
const content = readFileSafe(file);
if (!content) continue;
content.split('\n').forEach((line, i) => {
if (/type\s*=\s*["']email["']/.test(line) && /user|account|用户名|账号/.test(content)) {
record('AUTH-WEB-005', 'error',
`登录/注册表单中 input type="email" 不适用于用户名场景。应使用 type="text"。`,
file, i + 1);
}
});
}
}
function checkPlaceholderAccessKey(files) {
const pattern = /accessKey\s*:\s*['"]<.+>['"]|accessKey\s*:\s*['"]envId['"]|accessKey\s*:\s*['"]YOUR_/;
for (const file of files) {
const content = readFileSafe(file);
if (!content) continue;
content.split('\n').forEach((line, i) => {
if (pattern.test(line)) {
record('AUTH-WEB-010', 'error',
`accessKey 被设置为占位符或 envId 字符串。必须使用真实的 publishable key。`,
file, i + 1);
}
});
}
}
function checkGetLoginState(files) {
for (const file of files) {
const content = readFileSafe(file);
if (!content) continue;
content.split('\n').forEach((line, i) => {
if (/getLoginState\s*\(\s*\)/.test(line)) {
record('AUTH-WEB-012', 'error',
`使用了已废弃的 auth.getLoginState()。应使用 auth.getSession()。`,
file, i + 1);
}
});
}
}
function checkOldWebAuthApi(files) {
const pattern = /auth\.(hasLoginState|getCurrentUser|toDefaultLoginPage)\s*\(/;
for (const file of files.filter(f => /auth|login|register|guard|ProtectedRoute|App\.(tsx|jsx|ts|js)$/i.test(f))) {
const content = readFileSafe(file);
if (!content) continue;
content.split('\n').forEach((line, i) => {
if (pattern.test(line)) {
record('AUTH-WEB-013', 'error',
`使用了旧 CloudBase Web Auth API(hasLoginState/getCurrentUser/toDefaultLoginPage)。应使用 auth.getSession()、signInWithPassword/signInWithOtp/signUp 等 Supabase-like API,并先通过 auth-tool 配置 provider。`,
file, i + 1);
}
});
}
}
function checkCdnRef(files) {
for (const file of files.filter(f => /\.html$|index\.html|\.(tsx|jsx)$/i.test(f))) {
const content = readFileSafe(file);
if (!content) continue;
content.split('\n').forEach((line, i) => {
if (/static\.cloudbase\.net.*(cloudbase|tcb)/i.test(line) && /script/i.test(line)) {
record('AUTH-WEB-017', 'warning',
`使用了 CDN script 引用。现代 Web 项目应使用 npm install @cloudbase/js-sdk。`,
file, i + 1);
}
});
}
}
function checkAnonymousSession(files) {
for (const file of files.filter(f => /auth|guard|ProtectedRoute/i.test(f))) {
const content = readFileSafe(file);
if (!content) continue;
if (content.includes('getSession') && !content.includes('is_anonymous') &&
/guard|ProtectedRoute|Navigate.*\/login/i.test(content)) {
record('AUTH-WEB-018', 'warning',
`路由守卫使用了 getSession() 但未检查匿名用户(data.session.user?.is_anonymous)。`,
file, 0);
}
}
}
function checkOldLoginStrategy(files) {
for (const file of files) {
const content = readFileSafe(file);
if (!content) continue;
content.split('\n').forEach((line, i) => {
if (/lowcode.*LoginStrategy|DescribeLoginStrategy|ModifyLoginStrategy/i.test(line)) {
record('AUTH-TOOL-009', 'error',
`使用了 lowcode/DescribeLoginStrategy/ModifyLoginStrategy。应使用 DescribeLoginConfig / ModifyLoginConfig。`,
file, i + 1);
}
});
}
}
// ─── NoSQL Rules ────────────────────────────────────────────────────────────
function checkWxCloudDatabase(files) {
for (const file of files.filter(f => /app\.(ts|tsx|js|jsx)$|backend\.(ts|tsx|js)$|main\.(ts|tsx|js)$/i.test(f))) {
const content = readFileSafe(file);
if (!content) continue;
content.split('\n').forEach((line, i) => {
if (/wx\.cloud\.database\s*\(/.test(line) && !file.includes('miniprogram') && !file.includes('wx')) {
record('NOSQL-001', 'error',
`浏览器代码中使用 wx.cloud.database()。Web 项目必须使用 app.database()。`,
file, i + 1);
}
});
}
}
function checkOpenidManual(files) {
for (const file of files) {
const content = readFileSafe(file);
if (!content) continue;
content.split('\n').forEach((line, i) => {
if (/['"]_openid['"]\s*[:=]\s*['"]/.test(line) && /\.(add|insert|update|set)\s*\(/.test(content)) {
record('NOSQL-004', 'error',
`手动设置了 _openid。_openid 由 SDK 自动管理,禁止手动传入。`,
file, i + 1);
}
});
}
}
function checkAddResultId(files) {
for (const file of files) {
const content = readFileSafe(file);
if (!content) continue;
const lines = content.split('\n');
for (let i = 0; i < lines.length; i++) {
if (/\.\s*add\s*\(/.test(lines[i]) && !/\.add\s*\(\s*\)/.test(lines[i])) {
const ctx = lines.slice(i, i + 5).join('\n');
if (/result\.id\b/.test(ctx) && !/result\._id\b/.test(ctx)) {
record('NOSQL-006', 'warning',
`.add() 返回文档 ID 在 result._id,不是 result.id。`,
file, i + 1);
}
}
}
}
}
// ─── Storage Rules ─────────────────────────────────────────────────────────
function checkStorageUrlConcat(files) {
for (const file of files) {
const content = readFileSafe(file);
if (!content) continue;
content.split('\n').forEach((line, i) => {
if (/envId.*bucket|bucket.*envId|拼接.*URL|fileID.*\.com\/|tcb\.com\/.*\$\{/i.test(line)) {
record('STO-002', 'warning',
`检测到可能的 URL 拼接。上传后应使用 fileID + app.getTempFileURL() 获取可访问 URL。`,
file, i + 1);
}
});
}
}
function checkLocalhostDomain(files) {
const all = files.map(f => readFileSafe(f)).join('\n');
if (!/(uploadFile|upload|cloudbase|tcb)/i.test(all)) return;
const pkgPath = join(projectDir, 'package.json');
if (existsSync(pkgPath)) {
const pkg = readFileSafe(pkgPath);
if (pkg && !all.includes('localhost:5173') && !all.includes('CreateAuthDomain') && !all.includes('authDomain')) {
record('STORAGE001', 'error',
'需要将 localhost:5173 加入安全域名,否则浏览器上传会因跨域失败。',
pkgPath, 0);
}
}
}
function checkPgStorageApiRecommendation(files) {
const all = files.map(f => readFileSafe(f)).join('\n');
const usesPg = /app\.rdb\s*\(|\.rdb\s*\(|db\s*\.\s*from\s*\(/.test(all);
if (!usesPg) return;
for (const file of files.filter(f => /storage|upload|backend/i.test(f))) {
const content = readFileSafe(file);
if (!content) continue;
const lines = content.split('\n');
lines.forEach((line, i) => {
if (/app\.storage\s*\(/.test(line)) {
record('PG-CR004', 'error',
'PG Web 文件/图片上传禁止调用 app.storage();app.storage 是属性。推荐使用 app.storage.from("bucket").upload("key", file)。',
file, i + 1);
}
if (/app\.uploadFile\s*\(/.test(line)) {
record('PG-CR004', 'warning',
'PG Web 文件/图片上传不推荐优先使用旧式 app.uploadFile(...);推荐使用 app.storage.from("bucket").upload("key", file)。',
file, i + 1);
}
if (/\.upload\s*\(\s*\{[^}]*cloudPath|\.upload\s*\(\s*\{[^}]*filePath/.test(line)) {
record('PG-CR004', 'warning',
'PG Web Storage 推荐 app.storage.from("bucket").upload("key", file) 参数形态,不推荐 upload({ cloudPath, filePath })。',
file, i + 1);
}
if (/app\.storage\.from\(\s*['"]([^'"]+)['"]\s*\)\.upload\(\s*['"]\1\//.test(line)) {
record('PG-CR004', 'warning',
'PG Web Storage 的 bucket 已经传给 from("bucket"),upload("key", file) 的 key 不要重复 bucket 前缀。',
file, i + 1);
}
});
}
}
// ─── PostgreSQL Rules ───────────────────────────────────────────────────────
function checkCreateTable(files) {
const tables = new Set();
const fromPattern = /db\s*\.\s*from\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
for (const file of files) {
const c = readFileSafe(file);
if (!c) continue;
let m;
while ((m = fromPattern.exec(c)) !== null) tables.add(m[1]);
}
if (tables.size === 0) return;
let hasCreate = false;
for (const file of files) {
const c = readFileSafe(file);
if (!c) continue;
if (/CREATE\s+TABLE|createTable|create_table|executePgSql|managePgDatabase/i.test(c)) { hasCreate = true; break; }
if (/\.sql$/i.test(file) && /CREATE\s+TABLE/i.test(readFileSafe(file))) { hasCreate = true; break; }
}
if (!hasCreate) {
for (const t of tables) record('PG-CR001', 'error',
`代码中使用 db.from("${t}") 但未找到 CREATE TABLE。PG 表不会自动创建。`,
'', 0);
}
}
function checkUploadConfig(files) {
const sf = files.find(f => /storage|upload/i.test(f) || /cms-service|article/i.test(f));
if (!sf) return;
const c = readFileSafe(sf);
if (!c) return;
if (c.includes('upload') && !c.includes('STORAGE_NOT_EXIST') && !c.includes('getStorage')) {
const all = files.map(f => readFileSafe(f)).join('\n');
if (!all.includes('localhost')) record('PG-CR003', 'warning',
'图片上传需要 localhost:5173 已加入安全域名,否则 CORS 会失败。',
sf, 0);
}
}
function checkSensitiveRuntimeEcho(files) {
const backendHint = /(cloudfunctions|cloudrun|functions|\/server\/|\/api\/)/i;
const backendContent = /(createServer\s*\(|express\s*\(|exports\.main\s*=|kennethreitz\/httpbin|image:\s*.*httpbin)/i;
for (const file of files) {
const content = readFileSafe(file);
if (!content) continue;
if (!backendHint.test(file) && !backendContent.test(content)) continue;
const lines = content.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (/x-cloudbase-context/i.test(line) && /(res\.(json|send|end)|return\s*\{)/.test(line)) {
record('SEC001', 'error',
'禁止在响应中返回 x-cloudbase-context(可能含临时云密钥)。见 sensitive-runtime-data-protection 协议。',
file, i + 1);
}
if (/(headers\s*:\s*req\.headers|headers\s*:\s*event\.headers|env\s*:\s*process\.env)/.test(line)) {
record('SEC001', 'error',
'禁止回显 req.headers / event.headers / process.env。网关注入的凭证会被泄露。',
file, i + 1);
}
if (/httpbin/i.test(line)) {
record('SEC001', 'error',
'禁止部署 httpbin 等请求反射镜像;改用固定 health/hello 响应。',
file, i + 1);
}
}
}
}
// ─── Main ───────────────────────────────────────────────────────────────────
console.error(`[cloudbase-lint] Scanning: ${projectDir}`);
const files = findSourceFiles(projectDir);
console.error(`[cloudbase-lint] Files: ${files.length}`);
checkAuthGetUser(files);
checkEmailPasswordApi(files);
checkLoginFormType(files);
checkPlaceholderAccessKey(files);
checkGetLoginState(files);
checkOldWebAuthApi(files);
checkCdnRef(files);
checkAnonymousSession(files);
checkOldLoginStrategy(files);
checkWxCloudDatabase(files);
checkOpenidManual(files);
checkAddResultId(files);
checkStorageUrlConcat(files);
checkLocalhostDomain(files);
checkPgStorageApiRecommendation(files);
checkCreateTable(files);
checkUploadConfig(files);
checkSensitiveRuntimeEcho(files);
const report = {
timestamp: new Date().toISOString(),
projectDir,
filesScanned: files.length,
results,
summary: {
errors: results.filter(r => r.severity === 'error').length,
warnings: results.filter(r => r.severity === 'warning').length,
passed: results.length === 0,
},
};
console.log(JSON.stringify(report, null, 2));
process.exit(report.summary.errors > 0 ? 1 : 0);
```
## 规则清单
| 规则 ID | 严重性 | 检查内容 |
|---------|--------|---------|
| AUTH001 | error | 路由守卫用 getUser() 而非 getSession() |
| AUTH-WEB-004 | error | 用户名场景用 signInWithEmailAndPassword |
| AUTH-WEB-005 | error | 登录表单 type="email" |
| AUTH-WEB-010 | error | accessKey 设为占位符 |
| AUTH-WEB-012 | error | 使用废弃的 getLoginState() |
| AUTH-WEB-013 | error | 使用旧 Web Auth API(hasLoginState/getCurrentUser/toDefaultLoginPage) |
| AUTH-WEB-017 | warning | CDN 引用而非 npm |
| AUTH-WEB-018 | warning | getSession 缺 is_anonymous 检查 |
| AUTH-TOOL-009 | error | 使用旧的 DescribeLoginStrategy |
| NOSQL-001 | error | Web 代码用 wx.cloud.database() |
| NOSQL-004 | error | 手动设置 _openid |
| NOSQL-006 | warning | .add() 用 result.id 而非 result._id |
| STO-002 | warning | URL 拼接替代 getTempFileURL |
| STORAGE001 | error | 缺少 localhost:5173 安全域名 |
| PG-CR001 | error | db.from() 无对应 CREATE TABLE |
| PG-CR003 | warning | 上传缺少存储配置 |
| PG-CR004 | mixed | PG Web 文件/图片上传推荐 app.storage.from("bucket").upload("key", file);app.storage() 为错误 |
| PG-CR005 | error | PG 模式下存储上传缺少 storage.objects RLS 配置 |
| SEC001 | error | 回显 x-cloudbase-context / headers / process.env,或部署 httpbin |
references/cloudbase-code-review/references/RULES_INDEX.md
# Rule Index Matrix
Rules are extracted from CloudBase skill documentation. Each rule has a unique ID, module, severity, and applicable frontends.
## Rule inclusion boundary
Only add rules that are backed by stable SDK/API documentation, repeated evaluation failures, or deterministic runtime behavior. Do not add a hard rule from one trace's workaround alone; record those as attribution observations until independently verified.
## Cartesian Product: Module × Frontend → Applicable Rules
| Module | Web | MiniProgram | Node.js | CloudRun |
|--------|-----|-------------|---------|----------|
| auth | AUTH-WEB-001~019, AUTH-TOOL-001~014 | AUTH-WX-001~011, AUTH-TOOL-001~014 | AUTH-NODE-001~014, AUTH-TOOL-001~014 | AUTH-NODE-001~014, AUTH-TOOL-001~014 |
| nosql | NOSQL-001~016 | NOSQL-MP-001~005, NOSQL-004~016 inter | — | — |
| relational-database | RDB-WEB-001~005, RDB-TOOL-001~008 | — | RDB-TOOL-001~008 | RDB-TOOL-001~008 |
| storage | STO-001~007 | — | — | — |
| data-model | — | — | DM-001~007 | DM-001~007 |
| security / runtime-credentials | — | — | SEC001 | SEC001 |
---
## Module: security / runtime-credentials
| Rule ID | Severity | Check | Applies to | Description |
|---------|----------|-------|-----------|-------------|
| SEC001 | error | lint + LLM | Node, CR | 禁止回显 `x-cloudbase-context`、完整 headers / `process.env`,禁止部署 httpbin 类反射镜像 |
---
## Module: auth
### Web (AUTH-WEB-)
| Rule ID | Severity | Check | Description |
|---------|----------|-------|-------------|
| AUTH-WEB-001 | error | LLM | 禁止用云函数登录逻辑替代内置 Web Auth |
| AUTH-WEB-002 | error | LLM | 禁止在 Flutter/React Native/原生中复用 Web Auth |
| AUTH-WEB-003 | warning | LLM | 创建 auth 辅助函数后必须接入现有表单处理器 |
| AUTH-WEB-004 | error | lint | 用户名账号禁止使用 signInWithEmailAndPassword/SignUpWithEmailAndPassword |
| AUTH-WEB-005 | error | lint | 用户名输入框不得保留 type="email" |
| AUTH-WEB-006 | error | LLM | 必须先 queryAppAuth 确认 provider 已启用,再写登录代码 |
| AUTH-WEB-007 | error | lint | 禁止用 auth.getUser() / getLoginState() 做路由守卫判断 |
| AUTH-WEB-008 | error | lint | 必须用 auth.getSession() 做路由守卫(data.session === undefined 判断) |
| AUTH-WEB-009 | warning | LLM | 匿名登录默认禁用;accessKey ≠ 匿名会话,NoSQL 前须 signInAnonymously() |
| AUTH-WEB-010 | error | lint | 禁止把 accessKey 设为 envId 或占位符字符串 |
| AUTH-WEB-011 | error | lint | auth 方法返回 { data, error },必须先检查 error |
| AUTH-WEB-012 | error | lint | 禁止使用已废弃的 auth.getLoginState() |
| AUTH-WEB-013 | error | lint | 禁止使用旧 Web Auth API(hasLoginState/getCurrentUser/toDefaultLoginPage) |
| AUTH-WEB-013 | warning | LLM | 用户名注册必须 5-24 字符(字母/数字/下划线) |
| AUTH-WEB-014 | error | LLM | 用户名字符串禁止使用邮箱 OTP 或手机 OTP |
| AUTH-WEB-015 | warning | lint | OTP 验证是 signInWithOtp 返回的 data 上的回调,非独立调用 |
| AUTH-WEB-016 | warning | lint | signInWithOtp({ phone }) 使用 phone 字段非 phone_number |
| AUTH-WEB-017 | warning | lint | 现代 Web 项目必须用 npm install @cloudbase/js-sdk,非 CDN |
| AUTH-WEB-018 | error | lint | 匿名用户 session 也需拒绝(检查 is_anonymous) |
| AUTH-WEB-019 | warning | LLM | SDK 调用必须接入已有 UI 处理器 |
### Auth Provider 配置 (AUTH-TOOL-, 跨所有前端)
| Rule ID | Severity | Check | Applies to | Description |
|---------|----------|-------|-----------|-------------|
| AUTH-TOOL-001 | error | LLM | Web, MP, Node, CR | 禁止在启用 provider 之前编写登录 UI |
| AUTH-TOOL-002 | warning | LLM | Web, MP, Node, CR | 禁止把"auth"的任意提及都当作 provider-management 任务 |
| AUTH-TOOL-003 | error | lint | Web, Node | 禁止在云函数中实现 Web 登录逻辑 |
| AUTH-TOOL-004 | error | LLM | Web, Node | 禁止将原生 App 认证路由到 Web SDK |
| AUTH-TOOL-005 | error | LLM | Web, MP, Node, CR | 配置/代码改动前必须遵循 Change Safety Protocol |
| AUTH-TOOL-006 | warning | LLM | Web | 确认 provider 后必须回前端完成用户流,不要循环查询 |
| AUTH-TOOL-007 | error | LLM | Web, MP, Node, CR | MCP auth 与 queryAppAuth/manageAppAuth 是两个独立域 |
| AUTH-TOOL-008 | error | LLM | Web, MP, Node, CR | 禁止将应用侧 provider 配置路由到 MCP auth 工具 |
| AUTH-TOOL-009 | error | lint | Web, MP, Node, CR | 禁止使用 lowcode/DescribeLoginStrategy/ModifyLoginStrategy |
| AUTH-TOOL-010 | error | lint | Web | 用户名禁止路由到 email-only 辅助函数 |
| AUTH-TOOL-011 | error | LLM | Web, MP, Node, CR | EnvId 是环境 ID,不是 publishable key |
| AUTH-TOOL-012 | error | LLM | Web, MP, Node, CR | 环境别名必须通过 envQuery 解析为规范 EnvId |
| AUTH-TOOL-013 | warning | LLM | Web, MP, Node, CR | 匿名登录默认禁用 |
| AUTH-TOOL-014 | warning | LLM | Web, MP, Node, CR | 匿名用户调用 AI 模型需显式授权 |
### Node.js (AUTH-NODE-)
| Rule ID | Severity | Check | Applies to | Description |
|---------|----------|-------|-----------|-------------|
| AUTH-NODE-001 | warning | LLM | Node | 禁止爬取原始 HTTP 示例当 Node SDK 已覆盖时 |
| AUTH-NODE-002 | error | lint | Node, CR | SDK 初始化必须使用规范模式(tcb.init → app.auth()) |
| AUTH-NODE-003 | warning | LLM | Node, CR | 不在文档中的方法视为可疑 |
| AUTH-NODE-004 | error | lint | Node, CR, MP | 禁止仅凭 openId/appId 做授权决策 |
| AUTH-NODE-005 | error | lint | Node, Web | 禁止将 tcb_custom_login.json 打包到前端代码 |
| AUTH-NODE-006 | warning | LLM | Node, CR | customUserId 必须 4-32 字符 |
| AUTH-NODE-007 | error | LLM | Node, CR | 禁止对不同用户复用同一 customUserId |
| AUTH-NODE-008 | error | LLM | Node, CR | tcb_custom_login.json 必须像私钥一样保护 |
| AUTH-NODE-009 | error | LLM | Node, Web, CR | ticket 签发必须使用 HTTPS + 认证 |
| AUTH-NODE-010 | error | lint | Node, CR | auth.* 调用必须包裹在 try/catch |
| AUTH-NODE-011 | error | lint | Node, CR | 禁止将 getEndUserInfo 原始结果直接暴露给客户端 |
| AUTH-NODE-012 | warning | LLM | Node, CR | 优先用 uid 查用户,仅在必要时用 queryUserInfo |
| AUTH-NODE-013 | warning | LLM | Node, CR | platformId 必须使用注册时的精确格式 |
| AUTH-NODE-014 | error | LLM | Node, CR | ticket 只在用户自身认证成功后签发 |
### MiniProgram (AUTH-WX-)
| Rule ID | Severity | Check | Description |
|---------|----------|-------|-------------|
| AUTH-WX-001 | error | lint | 禁止为 wx.cloud 小程序生成 Web 风格登录页 |
| AUTH-WX-002 | error | LLM | 禁止将小程序 auth 当作 provider-configuration 问题 |
| AUTH-WX-003 | error | lint | 云函数中直接使用 cloud.getWXContext() 获取调用者身份 |
| AUTH-WX-004 | error | lint | 云函数必须使用 cloud.DYNAMIC_CURRENT_ENV |
| AUTH-WX-005 | error | LLM | 禁止将 OPENID 暴露给其他用户 |
| AUTH-WX-006 | error | lint | 小程序 auth 不需要显式登录 API 调用 |
| AUTH-WX-007 | error | lint | 云函数必须用 wx-server-sdk,客户端用 wx.cloud |
| AUTH-WX-008 | warning | lint | UNIONID 仅绑定微信开放平台后才可用,需处理 undefined |
| AUTH-WX-009 | info | LLM | OPENID/APPID/UNIONID 已微信验证,不需要额外验证 |
| AUTH-WX-010 | error | lint | app.js 的 onLaunch 中必须 wx.cloud.init |
| AUTH-WX-011 | error | lint | 禁止为小程序用户生成 Web 风格 OAuth 流 |
---
## Module: nosql
### Web (NOSQL-)
| Rule ID | Severity | Check | Description |
|---------|----------|-------|-------------|
| NOSQL-001 | error | lint | 禁止在浏览器中使用 wx.cloud.database() 或 Node SDK |
| NOSQL-002 | error | lint | 禁止动态 import 懒加载初始化 CloudBase |
| NOSQL-003 | error | LLM | 安全规则是验证器,不是过滤器 |
| NOSQL-004 | error | lint | 禁止手动传入 _openid 到 data 参数 |
| NOSQL-005 | warning | LLM | CUSTOM 规则变更通常数秒到约 30 秒生效;勿盲等数分钟,先核对规则与客户端写入模式 |
| NOSQL-006 | error | lint | .add() 返回值中文档 ID 在 result._id |
| NOSQL-007 | error | lint | 写入必须检查 result.updated/result.deleted |
| NOSQL-008 | error | LLM | CMS 角色区分必须用 CUSTOM 规则 |
| NOSQL-009 | error | lint | get() 语法:点号在括号外(get('...').role) |
| NOSQL-010 | warning | lint | CUSTOM 规则表达式 ≤1024 字符,get() ≤3 次 |
| NOSQL-011 | error | LLM | ADMINWRITE 前端只能读 |
| NOSQL-012 | warning | LLM | 全局数据权限必须通过云函数实现 |
| NOSQL-013 | warning | LLM | READONLY 允许匿名读,但新环境默认禁用匿名登录 |
| NOSQL-014 | warning | lint | 嵌套字段更新必须用点号表示法 |
| NOSQL-015 | error | LLM | .doc(authorId).update() 对非 _id 字段不可用 |
| NOSQL-016 | warning | LLM | get('database.user_roles.'+auth.uid) 仅 _id=uid 时有效 |
### MiniProgram (NOSQL-MP-)
| Rule ID | Severity | Check | Description |
|---------|----------|-------|-------------|
| NOSQL-MP-001 | error | lint | 禁止复制 Web SDK 代码到小程序;必须用 wx.cloud.database() |
| NOSQL-MP-002 | error | lint | 禁止手动设置 _openid |
| NOSQL-MP-003 | warning | LLM | 小程序内置身份不意味着可忽略安全规则 |
| NOSQL-MP-004 | warning | LLM | 全局权限必须走云函数 |
| NOSQL-MP-005 | error | lint | 小程序安全规则用 auth.openid/doc._openid,非 auth.uid |
---
## Module: relational-database
### Web (RDB-WEB-)
| Rule ID | Severity | Check | Description |
|---------|----------|-------|-------------|
| RDB-WEB-001 | error | lint | 必须使用 app.rdb(),不能把 app 当关系型数据库客户端 |
| RDB-WEB-002 | error | lint | 禁止在每个组件中重新初始化 CloudBase |
| RDB-WEB-003 | error | lint | 禁止懒加载或发明不支持的 init 参数 |
| RDB-WEB-004 | error | LLM | schema 变更/管理员操作必须走 MCP 工具 |
| RDB-WEB-005 | info | lint | rdb() 查询模式:.from().select()/.insert()/.update()/.delete() |
### PG CMS application review (PG-CR-)
| Rule ID | Severity | Check | Description |
|---------|----------|-------|-------------|
| PG-CR001 | error | lint | PG 表必须显式创建(CREATE TABLE)|
| PG-CR002 | error | LLM | RLS 策略不能只开启不配置 |
| PG-CR003 | warning | lint | PG Web 文件/图片上传需要 CloudBase 存储配置 |
| PG-CR004 | mixed | lint/LLM | PG Web 文件/图片上传推荐 app.storage.from("bucket").upload("key", file);app.storage() 为错误 |
| PG-CR005 | error | lint/LLM | PG 模式下存储上传必须配置 storage.objects RLS |
### All (RDB-TOOL-, 管理端)
| Rule ID | Severity | Check | Applies to | Description |
|---------|----------|-------|-----------|-------------|
| RDB-TOOL-001 | error | lint | Node, CR | MCP 上下文不要初始化 SDK,必须用 MCP 工具 |
| RDB-TOOL-002 | error | lint | Node, CR | 写操作前先检查 MySQL 是否就绪 |
| RDB-TOOL-003 | error | lint | Node, CR | 新表必须包含 _openid 列 |
| RDB-TOOL-004 | error | lint | Node, CR | 建表后必须审查权限 |
| RDB-TOOL-005 | error | lint | Node, CR | 销毁 MySQL 需确认 |
| RDB-TOOL-006 | warning | LLM | Node, CR | 破坏性操作前先总结 |
| RDB-TOOL-007 | info | lint | Node, CR | 预配/销毁用不同查询接口 |
| RDB-TOOL-008 | error | lint | Node, CR | 旧 security rule 工具已移除 |
---
## Module: storage (Web)
| Rule ID | Severity | Check | Description |
|---------|----------|-------|-------------|
| STO-001 | error | lint | 上传前必须配置安全域名(精确 host:port)|
| STO-002 | error | lint | 禁止拼接 URL 伪造公开链接;必须用 getTempFileURL() |
| STO-003 | warning | LLM | 临时 URL 有有效期,非永久 |
| STO-004 | warning | lint | 删除后逐项检查 fileList 结果 |
| STO-005 | warning | LLM | 特权管理操作走后端/MCP |
| STO-006 | info | LLM | 安全域名传播需数分钟 |
| STO-007 | info | LLM | 预览用 getTempFileURL,下载用 downloadFile |
---
## Module: data-model (Node.js, CloudRun)
| Rule ID | Severity | Check | Description |
|---------|----------|-------|-------------|
| DM-001 | warning | LLM | 简单 SQL 操作不应使用建模技能 |
| DM-002 | error | LLM | 禁止混合 SQL 和 NoSQL 设计 |
| DM-003 | error | lint | modifyDataModel 不支持更新已有模型 |
| DM-004 | warning | lint | Mermaid 命名规范(PascalCase/camelCase) |
| DM-005 | info | LLM | required()/unique() 仅在用户明确指定时用 |
| DM-006 | warning | LLM | 优先以草稿创建,发布前验证 |
| DM-007 | warning | LLM | 关系标签必须与实际字段名关联 |
---
## Cross-cutting
| Rule ID | Severity | Check | Applies to | Description |
|---------|----------|-------|-----------|-------------|
| SKILL001 | warning | LLM | All | searchKnowledgeBase 后必须 Read 完整文件 |
references/cloudbase-code-review/references/rules/cross-cutting/AUTH001.md
# AUTH001 路由守卫必须用 `auth.getSession()` 而非 `auth.getUser()`
- **Module**: cross-cutting (auth)
- **Severity**: error
- **Stage**: code-generation
- **适用于**: Web, MiniProgram, Node.js
---
## 正则检查 (Lint)
`references/lint-rules/README.md` 中可选 lint 代码块的扫描条件:
- 查找文件名匹配 `auth | guard | ProtectedRoute | checkAuth` 的文件
- 检查文件内容是否包含 `auth.getUser()`
- 如果发现 `auth.getUser()` 且同一文件 **没有** `getSession`,触发 AUTH001
## LLM 检查
请人工或 LLM 审查以下问题:
1. 项目中的路由守卫(通常是 `ProtectedRoute.tsx` 或类似组件)如何判断用户是否已登录?
2. 是否使用了 `auth.getSession()` 来获取当前会话?
3. 检查 `data?.session` 而非 `data?.user`?
```typescript
// ✅ 正确写法
const { data } = await auth.getSession()
if (!data?.session) return <Navigate to="/login" replace />
// ❌ 错误写法 — getUser() 不是可靠登录证明;显式 signInAnonymously() 后也会返回匿名 user
const { data } = await auth.getUser()
if (!data?.user) return <Navigate to="/login" replace /> // 匿名用户会绕过
```
4. 是否也检查了 `data.session.user?.is_anonymous` 来排除显式匿名登录?
## 修复指引
1. 在路由守卫中将 `auth.getUser()` 替换为 `auth.getSession()`
2. 将判断条件从 `data?.user` 改为 `data?.session`
3. 可选:增加 `data.session.user?.is_anonymous` 检查
```typescript
const { data, error } = await auth.getSession()
if (error || !data?.session) {
// 未登录或会话已过期,重定向到登录页
return <Navigate to="/login" replace />
}
// data.session 存在;若产品不允许匿名,再检查 is_anonymous
```
## 根因
Publishable `accessKey` 仅初始化 SDK,**不会**自动创建可供 gateway 鉴权的匿名会话。`@cloudbase/js-sdk` **3.x** 下 NoSQL `app.database()` CRUD 前必须显式 `await auth.signInAnonymously()`(或等价登录),否则 gateway **401**。
路由守卫侧:不要用 `auth.getUser()` 判断是否已登录——在调用过 `signInAnonymously()` 后它会返回匿名 user,导致「需真实登录」的守卫被绕过;废弃的 `getLoginState()` 甚至可能在未登录时返回误导性 `uid`。应使用 `auth.getSession()`:未登录时 `data.session === undefined`。若产品不允许匿名访问,再检查 `data.session.user?.is_anonymous`。
references/cloudbase-code-review/references/rules/cross-cutting/SEC001.md
# SEC001 禁止回显 `x-cloudbase-context`、完整 headers 或敏感环境变量
- **Module**: cross-cutting (security / runtime-credentials)
- **Severity**: error
- **Stage**: code-generation
- **适用于**: Node.js (Cloud Functions), CloudRun
---
## 正则检查 (Lint)
`references/lint-rules/README.md` 中可选 lint 代码块的扫描条件:
- 查找后端 / 函数 / 云托管相关源码(路径或文件名含 `cloudfunctions`、`cloudrun`、`functions`、`server`、`api`,或内容含 `createServer` / `express` / `exports.main`)
- 若同一文件出现以下任一模式,触发 SEC001:
- 字面量 `x-cloudbase-context`(任意大小写)出现在响应拼装路径附近
- `res.json` / `res.end` / `res.send` / `return {` 同时回传 `req.headers`、`event.headers`、`process.env`、或完整 `context`
- Dockerfile / compose / deploy 配置引用 `httpbin` 镜像
## LLM 检查
请人工或 LLM 审查以下问题:
1. HTTP 处理函数是否把 `req.headers`、`event`、`context` 或 `process.env` **整包**写进响应?
2. 是否存在 debug / echo / inspect 路由会打印全部请求头?
3. CloudRun 是否部署了 `httpbin` 或同类「反射请求」镜像?
4. 响应里是否可能包含 `x-cloudbase-context`、`TENCENTCLOUD_SECRET*`、`CLOUDBASE_APIKEY` 等凭证?
```js
// ❌ 错误 — 网关注入的 x-cloudbase-context 会被泄露
app.get('/debug', (req, res) => res.json({ headers: req.headers, env: process.env }))
// ✅ 正确 — 只返回业务需要的非敏感字段
app.get('/health', (_req, res) => res.json({ ok: true }))
```
## 修复指引
1. 删除或改写所有「回显 headers / env / context」的 debug 接口
2. 将 CloudRun 调试镜像换成固定返回的 hello-world / health check
3. 凭证只用于服务端 SDK 初始化,绝不写入 HTTP 响应
4. 完整协议见 `../../../../cloudbase-platform/references/protocols/sensitive-runtime-data-protection.md`
## 根因
CloudBase 网关可能在云托管 / 容器请求中注入 `x-cloudbase-context`(base64,可含临时云密钥)。业务代码若像 httpbin 一样原样返回请求头,或把 `process.env` 返回给客户端,攻击者即可获得账号下云资源访问权限。这与主动返回云函数环境变量属于同一类开发者侧泄露。
references/cloudbase-code-review/references/rules/cross-cutting/SKILL001.md
# SKILL001 `searchKnowledgeBase` 后必须 `Read` 完整文件内容
- **Module**: cross-cutting (skill-usage)
- **Severity**: warning
- **Stage**: code-generation
---
## 正则检查 (Lint)
`references/lint-rules/README.md` 中可选 lint 代码块的扫描条件:
- 检查 trace 或日志中是否有 `searchKnowledgeBase` 调用
- 检查对应的 skill 文件是否被 `Read` 过
- 如果 `searchKnowledgeBase(mode="skill")` 返回了文件路径但未 `Read` 该路径,触发 SKILL001
当前 lint 脚本暂不覆盖此项(需要解析 trace.json)。
## LLM 检查
请人工或 LLM 审查以下问题:
1. 本次开发过程中是否调用了 `searchKnowledgeBase(mode="skill")` 来查询 skill?
2. 如果是,是否 `Read` 了返回的完整 skill 文件路径?
3. `searchKnowledgeBase` 返回的通常是 skill 路径 + 元信息(name, description),**不是完整内容**。关键的 API 签名、参数说明、陷阱警告都在完整 SKILL.md 中。
4. 确认没有"搜到了但没读完"的情况。
## 修复指引
1. 调用 `searchKnowledgeBase(mode="skill", skillName="xxx")`
2. 从返回结果中提取 `absolute path`(如 `/Users/.../.claude/skills/xxx/SKILL.md`)
3. 执行 `Read` 工具,传入该绝对路径
4. 阅读完整内容后再写代码
## 根因
`searchKnowledgeBase(mode="skill")` 返回的内容以元信息为主(路径、标题、简介),不包含完整文档。agent 如果只依赖返回的片段就写代码,容易遗漏 skill 中的重要约束和陷阱。必须再 `Read` 一次拿到完整内容。
references/cloudbase-code-review/references/rules/postgresql/PG-CR001.md
# PG-CR001 PG 表必须显式创建(CREATE TABLE)
- **Module**: postgresql
- **Severity**: error
- **Stage**: code-generation
- **适用于**: Web, CloudRun
---
## 正则检查 (Lint)
`references/lint-rules/README.md` 中可选 lint 代码块的扫描条件:
- 在项目所有 `.ts/.tsx/.js/.jsx` 文件中搜索 `db.from("表名")` 模式
- 如果发现 `db.from("articles")` 或 `db.from("posts")` 等调用
- 检查是否同时存在 `CREATE TABLE` / `createTable` / `executePgSql` / `manageMysqlDatabase` 等建表操作
- 如果只有 `db.from()` 调用但没有建表操作,触发 PG-CR001
## LLM 检查
请人工或 LLM 审查以下问题:
1. 项目代码中使用了哪些 PG 表名(`db.from("xxx")`)?
2. 这些表是否已在 PG 中被创建?
3. **建表前是否先检查了表结构?** 在 CREATE TABLE 之前,是否先调用了 `queryPgDatabase(action="sql", sql="SELECT column_name, data_type FROM information_schema.columns WHERE table_name='xxx'")` 确认表是否存在及其精确列名?
4. **是否依赖了 `CREATE TABLE IF NOT EXISTS` 的静默跳过行为?** 如果表已存在但列名不匹配(例如预期 `uid` 但实际是 `user_id`),`IF NOT EXISTS` 会静默跳过,导致所有 CRUD 查询用错字段名。必须使用 `ALTER TABLE` 或 `DROP TABLE ... CASCADE`(确认数据影响后)重建。
5. 建表是通过什么方式完成的?
- MCP 工具 `executePgSql` 或 `manageMysqlDatabase`?
- SQL 脚本?
- ORM migration?
6. 表结构是否包含必要字段?
- articles 表:`title`, `content`, `author_id`, `status`, `created_at`, `updated_at`
- users 表:`id/uid`, `username`, `role`
7. 建表操作是否在实际 CRUD 调用**之前**执行?顺序对吗?
## 修复指引
通过 MCP 工具执行建表 SQL。对于 CloudBase PG 环境,使用 `managePgDatabase(action=execute, confirm=true)`;对于 MySQL 环境,使用 `manageMysqlDatabase(action=executeSQL)`。
示例 SQL:```sql
CREATE TABLE IF NOT EXISTS public.articles (
id BIGSERIAL PRIMARY KEY,
title TEXT NOT NULL,
summary TEXT,
cover_image TEXT,
content TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'draft',
author_id TEXT NOT NULL,
author_name TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
```
- 确保建表 SQL 在 CRUD 代码之前执行
- 如果已有 `db.from("users")` 调用但使用 CloudBase Auth 内置的 `auth.users` 表,可以不额外建表
- 业务角色扩展表(`user_roles` / `profiles` 等)仍需显式创建
## 根因
CloudBase PG 不会自动根据 `db.from("表名")` 建表。agent 写了 CRUD 代码但没有建表步骤,运行时所有查询都会返回 404。
references/cloudbase-code-review/references/rules/postgresql/PG-CR002.md
# PG-CR002 RLS 策略不能只开启不配置
- **Module**: postgresql
- **Severity**: error
- **Stage**: code-generation
- **适用于**: Web, CloudRun
---
## 正则检查 (Lint)
不支持。RLS 策略的语义检查无法通过正则可靠判断,此规则仅通过 LLM 检查。
## LLM 检查
请人工或 LLM 审查以下问题:
1. 项目中是否在 PG 上启用了行级安全(RLS)?
2. 如果启用了 RLS,是否为每张表创建了对应的策略(policy)?
3. 检查策略是否覆盖了目标业务场景:
| 操作 | admin 策略 | editor 策略 |
|------|-----------|------------|
| SELECT | 所有文章可见 | 所有文章可见(或仅自己的?按需求) |
| INSERT | 可创建 | 可创建(author_id = uid) |
| UPDATE | 可更新所有 | 仅更新自己的(author_id = uid) |
| DELETE | 可删除所有 | 仅删除自己的(author_id = uid) |
4. **检查策略中是否使用了 `current_user` 或 `current_setting(...)`?** 这是常见错误!`current_user` 返回的是数据库角色名(如 `authenticated`),不是 CloudBase 认证用户 ID。必须使用 `auth.uid()`。
5. 检查策略是否使用了 `auth.uid()` 来获取当前用户 ID?
6. **检查 `auth.uid()` 与 owner 列的类型是否匹配。** CloudBase 的 `auth.uid()` 返回 **`text`**(不是 `uuid`,与 Supabase 不同)。owner 列应优先用 `varchar(64)` / `text`;若列类型是 `uuid`,策略中必须写 `auth.uid()::uuid`,否则会报 `operator does not exist: uuid = text`。当身份可能是微信 openid 等非 UUID 字符串时,不要用 `uuid` 列。
7. 仅开启了 RLS(`ALTER TABLE ... ENABLE ROW LEVEL SECURITY`)但没有创建策略,等于拒绝所有访问。
8. 如果选择不放 RLS 而用在应用层(CRUD 代码中)做权限判断,确认后端/数据库层确实限制了 editor 只能操作自己的文章,而 admin 可以操作全部。
## 修复指引
```sql
-- 开启 RLS
ALTER TABLE public.articles ENABLE ROW LEVEL SECURITY;
-- admin:所有操作放行(假设 admin 角色记录在业务角色表中)
CREATE POLICY admin_all ON public.articles
FOR ALL
USING (auth.uid() IN (
SELECT uid FROM public.user_roles WHERE role = 'admin'
));
-- editor:只能操作自己的文章
CREATE POLICY editor_select ON public.articles
FOR SELECT
USING (true); -- editor 可查看所有
CREATE POLICY editor_insert ON public.articles
FOR INSERT
WITH CHECK (author_id = auth.uid());
CREATE POLICY editor_update ON public.articles
FOR UPDATE
USING (author_id = auth.uid());
CREATE POLICY editor_delete ON public.articles
FOR DELETE
USING (author_id = auth.uid());
```
## 根因
只执行 `ENABLE ROW LEVEL SECURITY` 而不创建策略,所有操作会被默认拒绝(等同于 `FOR ALL USING (false)`)。agent 经常只做了"开启 RLS"这一步,但没创建实际策略。
references/cloudbase-code-review/references/rules/postgresql/PG-CR003.md
# PG-CR003 PG Web 文件/图片上传需要 CloudBase 存储配置
- **Module**: postgresql
- **Severity**: warning
- **Stage**: code-generation
- **适用于**: Web
---
## 正则检查 (Lint)
`references/lint-rules/README.md` 中可选 lint 代码块的扫描条件:
- 检查项目中是否包含 `uploadFile` / `upload` / `storage` / 文件字段 URL 写入等关键词
- 如果存在上传逻辑,检查是否同时处理了:
- 安全域名(`localhost`)
- 存储初始化(`app.upload` / `cloudbase.upload`)
- 如果只有上传逻辑但没有安全域名和存储配置,触发 PG-CR003(仅 warning)
## LLM 检查
请人工或 LLM 审查以下问题:
1. 文件/图片上传用的是什么方式?
- 浏览器直传到 CloudBase 存储?
- 经过后端代理上传?
2. 如果使用浏览器直传,`localhost:5173`(Vite 默认端口)是否已加入安全域名?
3. 上传失败时是否有错误处理?是否会在 PG 记录创建/更新时静默失败但不提示用户?
4. 如果云存储尚未开通(`STORAGE_NOT_EXIST` 错误),是否先调用了存储相关 API 开通?
5. 上传后的 URL / fileID 是否正确写入对应 PG 记录字段?
## 修复指引
```typescript
// 1. 确保安全域名已配置(通过 MCP 或 CloudBase Console)
// 2. 检查存储是否已开通
// 3. 上传文件
// 具体上传 API 形态以当前 PG Web skill / SDK 文档为准
const result = await uploadFileOrImage(fileObject);
const uploadedUrlOrFileID = result.url || result.fileID;
```
## 根因
浏览器直传 CloudBase 存储需要:
1. 环境已开通存储资源
2. 当前访问 `host:port` 在安全域名白名单中
3. Vite 默认 `localhost:5173` 不在默认白名单中
agent 经常写了上传逻辑但忽略了安全域名配置,导致上传 403/跨域错误,而文章创建时又没做错误处理,让上传失败静默消失。
references/cloudbase-code-review/references/rules/postgresql/PG-CR004.md
# PG-CR004 CloudBase PG 场景文件/图片上传推荐使用 Storage v3 API
- **Module**: postgresql (PG storage)
- **Severity**: mixed(`app.storage()` 为 error;旧式/非推荐上传 API 为 warning)
- **Stage**: code-generation
- **适用于**: Web + CloudBase PG 场景
---
## 正则检查 (Lint)
`references/lint-rules/README.md` 中可选 lint 代码块的扫描条件:
仅当项目使用 CloudBase PG(例如出现 `app.rdb()`、`.rdb()` 或 `db.from(...)`)时启用本规则:
- 如果代码中出现 `app.storage()`:记录 **error**。`app.storage` 是属性,不是函数。
- 如果 PG 场景上传逻辑中使用 `app.uploadFile(...)`:记录 **warning**。旧式顶层上传 API 不推荐用于 PG Web 文件/图片上传。
- 如果上传逻辑中出现 `storage.upload({ cloudPath, filePath })`:记录 **warning**。PG 场景推荐 Storage v3 的 `app.storage.from("bucket").upload("key", file)` 参数形态。
## LLM 检查
请人工或 LLM 审查以下问题:
1. 当前项目是否是 CloudBase PG Web 应用,并涉及文件/图片上传?
2. 上传实现是否优先使用 `app.storage.from("bucket").upload("key", file)`,并把 bucket 放在 `from()` 参数里?
3. 是否避免在 `upload(key, file)` 的 key 中重复 bucket 前缀?
4. 是否错误地把 `app.storage` 当函数调用为 `app.storage()`?
5. 是否使用了旧式 `app.uploadFile(...)`;如果使用,是否有明确理由且与当前 SDK/评测约束一致?
6. 上传返回结果是否被检查,失败时是否阻止文章继续保存?
## 修复指引
CloudBase PG Web 场景推荐使用以下模式:
```typescript
const storage = app.storage.from(bucketName)
const { data, error } = await storage.upload(path, file)
if (error) {
throw new Error(error.message || "上传失败")
}
```
避免以下写法:
```typescript
// ❌ app.storage 不是函数
const storage = app.storage()
// ⚠️ PG Web 场景不推荐优先使用旧式顶层上传 API
await app.uploadFile({ cloudPath, filePath: file })
// ⚠️ 参数形态不符合推荐 Storage v3 API
await storage.upload({ cloudPath, filePath: file })
```
## 边界
这不是通用 CloudBase Storage 规则;它只适用于 CloudBase PG Web 这类需要在 PG 数据模型中关联文件/图片 URL 或 fileID 的场景。非 PG 场景是否使用其他 Storage API,应以对应 skill、SDK 文档和项目约束为准。
## 根因
CloudBase PG Web 场景中,文件/图片上传更适合走 Web Storage v3 API:`app.storage.from("bucket").upload("key", file)`,其中 bucket 必须放在 `from()` 参数中,`upload()` 的 key 不要重复 bucket 前缀。agent 容易从旧示例、顶层 storage API 或类型片段误推成 `app.storage()`、`app.uploadFile(...)` 或 `storage.upload({ cloudPath, filePath })`。其中 `app.storage()` 是确定性运行时错误;旧式 API 则不符合当前 PG 场景的推荐路径。
references/cloudbase-code-review/references/rules/postgresql/PG-CR005.md
# PG-CR005 PG 模式下存储上传必须配置 storage.objects RLS
- **Module**: postgresql
- **Severity**: error
- **Stage**: code-generation
- **适用于**: Web (PG / pgstore)
---
## 正则检查 (Lint)
`references/lint-rules/README.md` 中可选 lint 代码块的扫描条件:
- 检查项目中是否包含 `uploadFile` / `upload` / `storage` / 文件字段 URL 写入等关键词
- 如果存在上传逻辑,检查项目文件中是否包含 `ALTER TABLE storage.objects ENABLE ROW LEVEL SECURITY` 或 `CREATE POLICY.*ON storage.objects` 或 `authenticated_upload`
- 如果只有上传逻辑但没有 storage RLS 配置语句,触发 PG-CR005(error)
## LLM 检查
请人工或 LLM 审查以下问题:
1. 项目是否运行在 CloudBase PG / pgstore 环境?
2. 如果是,存储桶创建后是否配置了 `storage.objects` 表的 RLS 策略?
3. 存储 RLS 策略是否至少允许认证用户上传(`FOR INSERT TO authenticated WITH CHECK (auth.role() = 'authenticated')`)和读取(`FOR SELECT TO authenticated USING (auth.role() = 'authenticated')`)?
4. 配置方式是否正确使用了 `managePgDatabase(action="execute", confirm=true)` 执行 SQL?不要使用 CloudBase 传统安全规则 API(`managePermissions` / `ModifyStorageSafeRule`),那是 NoSQL 环境用的。
5. 如果上传失败(`STORAGE_PERMISSION_DENIED`),是否检查了 storage RLS 配置?
## 修复指引
通过 `managePgDatabase(action="execute", confirm=true)` 执行以下 SQL:
```sql
ALTER TABLE storage.objects ENABLE ROW LEVEL SECURITY;
CREATE POLICY "authenticated_upload" ON storage.objects
FOR INSERT TO authenticated
WITH CHECK (auth.role() = 'authenticated');
CREATE POLICY "authenticated_read" ON storage.objects
FOR SELECT TO authenticated
USING (auth.role() = 'authenticated');
```
详细指引见 `cloud-storage-web/SKILL.md` "Post-bucket: storage RLS" 章节。
## 根因
PG / pgstore 模式下,存储权限通过 PostgreSQL RLS 在 `storage.objects` 表上控制(类似 Supabase Storage),**不是** CloudBase 传统 NoSQL 安全规则。默认 RLS 为 deny all,上传前必须配置允许策略,否则浏览器端 `app.uploadFile()` 会返回 `STORAGE_PERMISSION_DENIED`。Agent 通常只配置了数据库业务表(articles/user_roles)的 RLS,不知道存储桶也需要配置。
references/cloudbase-code-review/references/rules/storage/STORAGE001.md
# STORAGE001 本地开发安全域名必须包含 `localhost:5173`
- **Module**: storage
- **Severity**: error
- **Stage**: config
- **适用于**: Web
---
## 正则检查 (Lint)
`references/lint-rules/README.md` 中可选 lint 代码块的扫描条件:
- 检查项目代码中是否包含 CloudBase 相关引用(`cloudbase` / `tcb` / `upload`)
- 如果是,检查所有文件的内容是否包含 `localhost:5173` 或 `CreateAuthDomain` 或 `authDomain`
- 如果都没有,触发 STORAGE001(仅在项目使用 CloudBase 时触发)
## LLM 检查
请人工或 LLM 审查以下问题:
1. 项目是否使用了 CloudBase 云存储?(上传文件、图片等)
2. 如果是,`localhost:5173`(Vite dev server 默认地址)是否已加入安全域名?
3. 加入方式:
- 通过 MCP 的 `manageEnv(action="addSecurityDomain")` 工具(旧工具名 `envDomainManagement` 已废弃收编)?
- 通过 CloudBase Console 手动添加?
- 通过 `CreateAuthDomain` API?
4. 如果有其他开发端口(如 `5174`、`4173`),是否也加了?
5. 生产环境的域名是否也已在安全域名列表中?
## 修复指引
通过 MCP 工具 `manageEnv(action="addSecurityDomain", domains=[...])` 添加安全域名(旧工具名 `envDomainManagement` 已废弃),或通过 `CreateAuthDomain` API 添加:
```typescript
// 通过 CreateAuthDomain API
await cloudbase.commonService("tcb", TCB_VERSION).call({
Action: "CreateAuthDomain",
Param: {
EnvId: envId,
Domains: ["localhost:5173"],
},
});
```
> 注意:`localhost:5173` 是 Vite 默认 dev server 地址。如果修改了 Vite 配置(如使用 `--port` 或 `--host`),需相应调整。
## 根因
CloudBase 安全域名机制会拦截未注册的 origin 发出的请求。Vite 默认的 `localhost:5173` 不在白名单中,浏览器直传 CloudBase 存储时会因跨域失败。agent 在写本地项目时需要主动把 dev server 地址加入安全域名。
references/cloudbase-code-review/SKILL.md
---
name: cloudbase-code-review
description: "Code review and validation for CloudBase projects. After writing code for Web / miniprogram / CloudRun / cloud-function projects, call this skill to check for known pitfalls — auth guard misuse, missing database tables, RLS misconfiguration, storage domain setup, and SDK API misuse. Supports automated lint scripts (regex-based) + LLM semantic review."
version: 2.33.1
alwaysApply: false
---
## Sibling skills (local only)
Sibling CloudBase skills ship beside this skill. Use local relative paths such as `../auth-tool-cloudbase/SKILL.md`.
If a referenced sibling skill file is missing from this environment, ask the user to install the full CloudBase plugin (or the missing skill). Do **not** HTTP-fetch remote skill or protocol markdown into the agent context.
# CloudBase Code Review
> **One-liner**: After implementing CloudBase features, call this skill to catch common mistakes before users do.
## When to use
Call this skill **after** completing a CloudBase implementation task, before declaring done:
- You implemented auth (login / register / route guard)
- You created database tables or wrote CRUD (NoSQL / PostgreSQL / MySQL)
- You set up CloudBase Storage (file upload, hosting)
- You configured security rules or RLS policies
- You wrote MCP-dependent code
- You wrote Cloud Function or CloudRun HTTP handlers (check for credential / header echo leaks)
## How it works
The skill runs in two layers:
| Layer | Method | Speed | What it catches |
|-------|--------|-------|-----------------|
| **Lint (optional)** | No executable script is shipped. If the user approves running lint, review the code block in `references/lint-rules/README.md`, copy it to a temporary local `cloudbase-lint.mjs`, then run `node cloudbase-lint.mjs --project-dir <path>` | Seconds | Deterministic regex checks — wrong API calls, missing configs, pattern mismatches |
| **LLM review** | Read each rule's "LLM 检查" section, inspect code semantically | Variable | Semantic issues — route guard logic, RLS completeness, architecture-level problems |
## Rule index
See `references/RULES_INDEX.md` for the full matrix (module × frontend type → applicable rules).
## Rule boundary
Do not promote a single failed run or case-specific workaround into a hard rule. A rule should be backed by stable SDK/API documentation, repeated failures, or deterministic runtime behavior. Case-specific observations belong in attribution reports; only broadly applicable constraints should enter `RULES_INDEX.md` or the optional lint checklist.
## Quick start
```bash
# Step 1: Read relevant rules for identified modules
# references/rules/cross-cutting/AUTH001.md
# references/rules/cross-cutting/SEC001.md
# references/rules/postgresql/PG-CR001.md
# ...
# Optional: if the user approves running lint, review the script code block in
# references/lint-rules/README.md, copy it to a temporary cloudbase-lint.mjs,
# then run: node cloudbase-lint.mjs --project-dir .
# Step 2: For each applicable rule, read the "LLM 检查" section
# and manually inspect your code before claiming done.
```
## Rule format
Each rule `.md` file follows this structure:
```markdown
# RULE-ID Rule Name
- **Module**: which module (auth / postgresql / storage / ...)
- **Severity**: error | warning
- **Stage**: code-generation | deployment | config
## 正则检查 (Lint)
The condition checked by the optional script code block in `references/lint-rules/README.md`.
## LLM 检查
Semantic review prompt for human or LLM to evaluate.
## 修复指引
How to fix the issue.
```
## Reference index
All packaged reference files (required for skill lint reachability):
- [RULES_INDEX.md](references/RULES_INDEX.md)
- [lint-rules/README.md](references/lint-rules/README.md)
- [rules/cross-cutting/AUTH001.md](references/rules/cross-cutting/AUTH001.md)
- [rules/cross-cutting/SEC001.md](references/rules/cross-cutting/SEC001.md)
- [rules/cross-cutting/SKILL001.md](references/rules/cross-cutting/SKILL001.md)
- [rules/postgresql/PG-CR001.md](references/rules/postgresql/PG-CR001.md)
- [rules/postgresql/PG-CR002.md](references/rules/postgresql/PG-CR002.md)
- [rules/postgresql/PG-CR003.md](references/rules/postgresql/PG-CR003.md)
- [rules/postgresql/PG-CR004.md](references/rules/postgresql/PG-CR004.md)
- [rules/postgresql/PG-CR005.md](references/rules/postgresql/PG-CR005.md)
- [rules/storage/STORAGE001.md](references/rules/storage/STORAGE001.md)
references/cloudbase-document-database-in-wechat-miniprogram/aggregation.md
# Aggregation Queries with CloudBase
This document explains how to perform aggregation operations for data analysis and statistics in CloudBase document database.
## Overview
Aggregation queries allow you to:
- Group data by specific fields
- Calculate statistics (count, sum, average, etc.)
- Transform and reshape data
- Perform complex data analysis
## Basic Aggregation Syntax
```javascript
const result = await db.collection('collectionName')
.aggregate()
.group({ /* grouping configuration */ })
.end();
console.log('Results:', result.list);
```
**Note:** Aggregation queries use `.end()` instead of `.get()`
## Grouping Data
### Simple Grouping with Count
Count documents by a specific field:
```javascript
// Count todos by priority
const result = await db.collection('todos')
.aggregate()
.group({
_id: '$priority', // Group by priority field
count: {
$sum: 1 // Count documents in each group
}
})
.end();
console.log('By priority:', result.list);
// Output: [
// { _id: 'high', count: 15 },
// { _id: 'medium', count: 23 },
// { _id: 'low', count: 8 }
// ]
```
### Field Reference Syntax
Use `$` prefix to reference document fields:
- `$priority` - References the `priority` field
- `$status` - References the `status` field
- `$user.name` - References nested fields
## Aggregation Operators
### Accumulator Operators
| Operator | Description | Usage |
|----------|-------------|-------|
| `$sum` | Sum values | `{ total: { $sum: '$amount' } }` |
| `$avg` | Average values | `{ avgScore: { $avg: '$score' } }` |
| `$min` | Minimum value | `{ minPrice: { $min: '$price' } }` |
| `$max` | Maximum value | `{ maxPrice: { $max: '$price' } }` |
| `$first` | First value | `{ first: { $first: '$date' } }` |
| `$last` | Last value | `{ last: { $last: '$date' } }` |
| `$push` | Array of all values | `{ items: { $push: '$name' } }` |
## Common Aggregation Patterns
### Count by Category
```javascript
// Count users by role
const result = await db.collection('users')
.aggregate()
.group({
_id: '$role',
count: { $sum: 1 }
})
.end();
```
### Sum and Average
```javascript
// Calculate total and average order amount by customer
const result = await db.collection('orders')
.aggregate()
.group({
_id: '$customerId',
totalAmount: { $sum: '$amount' },
averageAmount: { $avg: '$amount' },
orderCount: { $sum: 1 }
})
.end();
```
### Find Min and Max
```javascript
// Find price range by product category
const result = await db.collection('products')
.aggregate()
.group({
_id: '$category',
minPrice: { $min: '$price' },
maxPrice: { $max: '$price' },
avgPrice: { $avg: '$price' }
})
.end();
```
### Multiple Groups
```javascript
// Group by status and priority
const result = await db.collection('todos')
.aggregate()
.group({
_id: {
status: '$status',
priority: '$priority'
},
count: { $sum: 1 }
})
.end();
// Output: [
// { _id: { status: 'active', priority: 'high' }, count: 5 },
// { _id: { status: 'active', priority: 'low' }, count: 3 },
// { _id: { status: 'completed', priority: 'high' }, count: 10 }
// ]
```
## Pipeline Stages
Aggregation supports multiple stages in a pipeline:
### Match Stage (Filter)
Filter documents before grouping:
```javascript
const result = await db.collection('orders')
.aggregate()
.match({
status: 'completed',
createdAt: db.command.gte(new Date('2025-01-01'))
})
.group({
_id: '$customerId',
totalRevenue: { $sum: '$amount' }
})
.end();
```
### Sort Stage
Sort the aggregation results:
```javascript
const result = await db.collection('todos')
.aggregate()
.group({
_id: '$assignee',
taskCount: { $sum: 1 }
})
.sort({
taskCount: -1 // -1 for descending, 1 for ascending
})
.end();
```
### Limit Stage
Limit the number of results:
```javascript
// Top 10 customers by order count
const result = await db.collection('orders')
.aggregate()
.group({
_id: '$customerId',
orderCount: { $sum: 1 }
})
.sort({ orderCount: -1 })
.limit(10)
.end();
```
### Project Stage
Reshape output documents:
```javascript
const result = await db.collection('users')
.aggregate()
.group({
_id: '$department',
employeeCount: { $sum: 1 },
avgSalary: { $avg: '$salary' }
})
.project({
department: '$_id',
employees: '$employeeCount',
averageSalary: '$avgSalary',
_id: 0 // Exclude _id from output
})
.end();
```
## Complete Pipeline Example
```javascript
// Comprehensive sales analysis
const salesAnalysis = await db.collection('orders')
.aggregate()
// Stage 1: Filter to completed orders in 2025
.match({
status: 'completed',
orderDate: db.command.gte(new Date('2025-01-01'))
})
// Stage 2: Group by product category
.group({
_id: '$category',
totalRevenue: { $sum: '$amount' },
orderCount: { $sum: 1 },
avgOrderValue: { $avg: '$amount' },
maxOrder: { $max: '$amount' },
minOrder: { $min: '$amount' }
})
// Stage 3: Sort by revenue descending
.sort({
totalRevenue: -1
})
// Stage 4: Limit to top 5 categories
.limit(5)
// Stage 5: Reshape output
.project({
category: '$_id',
revenue: '$totalRevenue',
orders: '$orderCount',
averageValue: '$avgOrderValue',
range: {
min: '$minOrder',
max: '$maxOrder'
},
_id: 0
})
.end();
console.log('Top 5 categories:', salesAnalysis.list);
```
## Time-based Aggregations
### Group by Date
```javascript
// Count orders by date
const result = await db.collection('orders')
.aggregate()
.group({
_id: {
year: db.command.aggregate.dateToString({
format: '%Y',
date: '$createdAt'
}),
month: db.command.aggregate.dateToString({
format: '%m',
date: '$createdAt'
})
},
orderCount: { $sum: 1 },
revenue: { $sum: '$amount' }
})
.sort({
'_id.year': 1,
'_id.month': 1
})
.end();
```
## Array Aggregations
### Working with Array Fields
```javascript
// Unwind array fields for analysis
const result = await db.collection('orders')
.aggregate()
.unwind('$items') // Flatten items array
.group({
_id: '$items.productId',
totalQuantity: { $sum: '$items.quantity' },
totalRevenue: { $sum: '$items.total' }
})
.sort({ totalRevenue: -1 })
.limit(10)
.end();
```
## Performance Tips
1. **Use match early**: Filter data before grouping to reduce processing
2. **Index match fields**: Ensure fields used in match stage are indexed
3. **Limit results**: Use limit to reduce data transfer
4. **Avoid large groups**: Very large groups can impact performance
5. **Project only needed fields**: Remove unnecessary fields early
## Common Use Cases
### Dashboard Statistics
```javascript
// Get overview statistics
const stats = await db.collection('todos')
.aggregate()
.group({
_id: null, // Single group for overall stats
total: { $sum: 1 },
completed: {
$sum: {
$cond: [{ $eq: ['$status', 'completed'] }, 1, 0]
}
},
active: {
$sum: {
$cond: [{ $eq: ['$status', 'active'] }, 1, 0]
}
}
})
.end();
```
### User Activity Analysis
```javascript
// Analyze user activity
const userActivity = await db.collection('activities')
.aggregate()
.match({
timestamp: db.command.gte(new Date(Date.now() - 30 * 24 * 60 * 60 * 1000))
})
.group({
_id: '$userId',
actionCount: { $sum: 1 },
lastAction: { $max: '$timestamp' },
actions: { $push: '$actionType' }
})
.sort({ actionCount: -1 })
.limit(20)
.end();
```
## Error Handling
Always handle aggregation errors:
```javascript
try {
const result = await db.collection('orders')
.aggregate()
.group({
_id: '$category',
total: { $sum: '$amount' }
})
.end();
if (result.list.length === 0) {
console.log('No data found');
} else {
console.log('Aggregation results:', result.list);
}
} catch (error) {
console.error('Aggregation failed:', error);
}
```
references/cloudbase-document-database-in-wechat-miniprogram/complex-queries.md
# Complex Queries with CloudBase
This document provides detailed guidance on constructing complex queries using CloudBase document database.
## Query Operators
Access operators through `db.command`:
```javascript
const _ = db.command;
```
### Comparison Operators
| Operator | Usage | Description |
|----------|-------|-------------|
| `gt` | `_.gt(value)` | Greater than |
| `gte` | `_.gte(value)` | Greater than or equal |
| `lt` | `_.lt(value)` | Less than |
| `lte` | `_.lte(value)` | Less than or equal |
| `eq` | `_.eq(value)` | Equal to |
| `neq` | `_.neq(value)` | Not equal to |
### Array Operators
| Operator | Usage | Description |
|----------|-------|-------------|
| `in` | `_.in([values])` | Value exists in array |
| `nin` | `_.nin([values])` | Value not in array |
## Building Complex Queries
### Multiple Conditions
Combine multiple conditions in the `where()` object:
```javascript
const result = await db.collection('todos')
.where({
// Age greater than 18
age: _.gt(18),
// Tags include 'tech' or 'study'
tags: _.in(['tech', 'study']),
// Created within last week
createdAt: _.gte(new Date(Date.now() - 7 * 24 * 60 * 60 * 1000))
})
.get();
```
### Sorting Results
Use `orderBy()` to sort results:
```javascript
// Single field sorting
db.collection('posts')
.orderBy('createdAt', 'desc')
.get()
// Multiple field sorting (chain multiple orderBy calls)
db.collection('products')
.orderBy('category', 'asc')
.orderBy('price', 'desc')
.get()
```
**Sort directions:**
- `'asc'` - Ascending order
- `'desc'` - Descending order
### Limiting Results
Control the number of results returned:
```javascript
// Limit to 10 results
db.collection('posts')
.limit(10)
.get()
```
**Limits:**
- Default: 100 records
- Maximum: 1000 records per query
### Field Selection
Optimize queries by selecting only needed fields:
```javascript
const result = await db.collection('users')
.field({
title: true, // Include title
completed: true, // Include completed
createdAt: true, // Include createdAt
_id: false // Exclude _id
})
.get();
```
**Field selection rules:**
- `true` - Include field in results
- `false` - Exclude field from results
- If not specified, all fields are included by default
## Complete Complex Query Example
Here's a comprehensive example combining all query features:
```javascript
const _ = db.command;
const result = await db.collection('todos')
.where({
// Status must be 'active' or 'pending'
status: _.in(['active', 'pending']),
// Priority is high
priority: 'high',
// Age greater than 18
age: _.gt(18),
// Created in the last 30 days
createdAt: _.gte(new Date(Date.now() - 30 * 24 * 60 * 60 * 1000))
})
.field({
title: true,
status: true,
priority: true,
assignee: true,
createdAt: true
})
.orderBy('createdAt', 'desc')
.orderBy('priority', 'asc')
.limit(50)
.skip(0)
.get();
console.log('Found', result.data.length, 'todos');
console.log('Results:', result.data);
```
## Query Performance Tips
1. **Use Indexes**: Create indexes on frequently queried fields
2. **Limit Fields**: Only select fields you need with `.field()`
3. **Apply Filters Early**: Use specific `where()` conditions to reduce data scanned
4. **Reasonable Limits**: Don't query more data than necessary
5. **Optimize Sort Fields**: Sort on indexed fields when possible
## Common Query Patterns
### Date Range Queries
```javascript
const startDate = new Date('2025-01-01');
const endDate = new Date('2025-12-31');
db.collection('events')
.where({
eventDate: _.gte(startDate).and(_.lte(endDate))
})
.get()
```
### Text Search (Exact Match)
```javascript
// Exact title match
db.collection('articles')
.where({
title: 'Specific Title'
})
.get()
```
### Multiple Value Matching
```javascript
// Find users with specific roles
db.collection('users')
.where({
role: _.in(['admin', 'moderator', 'editor'])
})
.get()
```
### Excluding Values
```javascript
// Find posts not in draft or archived status
db.collection('posts')
.where({
status: _.nin(['draft', 'archived'])
})
.get()
```
### Combining with Logical Operators
```javascript
// Users over 18 OR with verified status
db.collection('users')
.where({
_or: [
{ age: _.gt(18) },
{ verified: true }
]
})
.get()
```
## Error Handling
Always handle potential errors:
```javascript
try {
const result = await db.collection('todos')
.where({ status: _.in(['active']) })
.orderBy('priority', 'desc')
.limit(10)
.get();
if (result.data.length === 0) {
console.log('No matching documents found');
} else {
console.log('Found documents:', result.data);
}
} catch (error) {
console.error('Query failed:', error);
// Handle error appropriately
}
```
references/cloudbase-document-database-in-wechat-miniprogram/crud-operations.md
# CRUD Operations with CloudBase
This document covers Create, Update, and Delete operations for CloudBase document database.
## Create Operations
### Adding a Single Document
Add a new document to a collection:
```javascript
// Add a single document
const result = await db.collection('todos').add({
title: 'Learn CloudBase',
description: 'Study the database API',
completed: false,
priority: 'high',
createdAt: new Date()
});
console.log('Added document with ID:', result._id);
```
**Return Value:**
```javascript
{
_id: "generated-doc-id", // Auto-generated document ID
// ... other metadata
}
```
### Adding with Custom ID
Specify your own document ID:
```javascript
// Add with custom ID
const result = await db.collection('todos')
.doc('custom-todo-id')
.set({
title: 'Custom ID Todo',
completed: false,
createdAt: new Date()
});
```
**Note:** Use `.set()` with `.doc()` to specify a custom ID. If document exists, it will be overwritten.
### Adding Multiple Documents
Add multiple documents at once:
```javascript
// Batch add documents
const todos = [
{ title: 'Task 1', completed: false },
{ title: 'Task 2', completed: false },
{ title: 'Task 3', completed: true }
];
// Add one by one
for (const todo of todos) {
await db.collection('todos').add(todo);
}
// Or use Promise.all for parallel insertion
const results = await Promise.all(
todos.map(todo => db.collection('todos').add(todo))
);
console.log('Added', results.length, 'documents');
```
### Data Validation
Validate data before insertion:
```javascript
function validateTodo(todo) {
if (!todo.title || todo.title.trim() === '') {
throw new Error('Title is required');
}
if (typeof todo.completed !== 'boolean') {
throw new Error('Completed must be a boolean');
}
return true;
}
async function addTodo(todoData) {
try {
validateTodo(todoData);
const result = await db.collection('todos').add({
...todoData,
createdAt: new Date(),
updatedAt: new Date()
});
return result;
} catch (error) {
console.error('Failed to add todo:', error);
throw error;
}
}
```
## Update Operations
### Update by Document ID
Update a specific document by its ID:
```javascript
// Update by ID
const result = await db.collection('todos')
.doc('todo-id-123')
.update({
completed: true,
updatedAt: new Date()
});
console.log('Updated:', result.updated, 'document(s)');
```
**Return Value:**
```javascript
{
updated: 1, // Number of documents updated
// ... other metadata
}
```
### Update with Conditions
Update documents matching specific conditions:
```javascript
// Update all incomplete high-priority todos
const result = await db.collection('todos')
.where({
completed: false,
priority: 'high'
})
.update({
priority: 'urgent',
updatedAt: new Date()
});
console.log('Updated', result.updated, 'documents');
```
### Partial Updates
Only update specific fields (other fields remain unchanged):
```javascript
// Only update the title, leave other fields unchanged
await db.collection('todos')
.doc('todo-id-123')
.update({
title: 'Updated Title'
});
```
### Nested Field Updates (Important)
When updating nested object fields, you **must use dot notation** if you want to preserve sibling fields.
**WRONG: This replaces the entire object and deletes sibling fields:**
```javascript
// DANGER: If 'user' had an 'email' field, it is now deleted!
await db.collection('profiles')
.doc('profile-123')
.update({
user: {
name: 'New Name' // Replaces the ENTIRE 'user' object
}
});
```
**CORRECT: This only updates the specific nested field:**
```javascript
// SAFE: Only updates 'name', preserves 'email' and other fields in 'user'
await db.collection('profiles')
.doc('profile-123')
.update({
'user.name': 'New Name' // Use dot notation for nested fields
});
```
### Update with Operators
Use update operators for complex updates:
```javascript
const _ = db.command;
// Increment a counter
await db.collection('posts')
.doc('post-123')
.update({
views: _.inc(1) // Increment views by 1
});
// Add item to array
await db.collection('todos')
.doc('todo-123')
.update({
tags: _.push(['urgent']) // Add 'urgent' to tags array
});
// Remove item from array
await db.collection('todos')
.doc('todo-123')
.update({
tags: _.pull('completed') // Remove 'completed' from tags
});
// Multiply a number
await db.collection('products')
.doc('product-123')
.update({
price: _.mul(1.1) // Increase price by 10%
});
```
### Common Update Operators
| Operator | Description | Example |
|----------|-------------|---------|
| `_.inc(n)` | Increment by n | `views: _.inc(1)` |
| `_.mul(n)` | Multiply by n | `price: _.mul(1.5)` |
| `_.push(items)` | Add to array | `tags: _.push(['new'])` |
| `_.pull(item)` | Remove from array | `tags: _.pull('old')` |
| `_.set(value)` | Set to value | `status: _.set('active')` |
| `_.remove()` | Remove field | `tempField: _.remove()` |
### Set vs Update
**`.update()`** - Updates only specified fields:
```javascript
// Only updates 'title', other fields remain unchanged
await db.collection('todos')
.doc('todo-123')
.update({ title: 'New Title' });
```
**`.set()`** - Replaces entire document:
```javascript
// Replaces entire document, removes unspecified fields
await db.collection('todos')
.doc('todo-123')
.set({ title: 'New Title', completed: false });
```
### Batch Updates
Update multiple documents efficiently:
```javascript
// Update all incomplete todos assigned to a user
async function reassignTodos(oldUserId, newUserId) {
const result = await db.collection('todos')
.where({
assigneeId: oldUserId,
completed: false
})
.update({
assigneeId: newUserId,
updatedAt: new Date()
});
return result.updated;
}
const updatedCount = await reassignTodos('user-1', 'user-2');
console.log('Reassigned', updatedCount, 'todos');
```
## Delete Operations
### Delete by Document ID
Delete a specific document:
```javascript
// Delete by ID
const result = await db.collection('todos')
.doc('todo-id-123')
.remove();
console.log('Deleted:', result.deleted, 'document(s)');
```
**Return Value:**
```javascript
{
deleted: 1, // Number of documents deleted
// ... other metadata
}
```
### Delete with Conditions
Delete documents matching conditions:
```javascript
// Delete all completed todos older than 30 days
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
const result = await db.collection('todos')
.where({
completed: true,
completedAt: db.command.lt(thirtyDaysAgo)
})
.remove();
console.log('Deleted', result.deleted, 'old completed todos');
```
### Conditional Delete
Delete only if conditions are met:
```javascript
async function deleteTodoIfOwner(todoId, userId) {
try {
const result = await db.collection('todos')
.where({
_id: todoId,
ownerId: userId // Only delete if user is owner
})
.remove();
if (result.deleted === 0) {
throw new Error('Todo not found or user is not owner');
}
return true;
} catch (error) {
console.error('Delete failed:', error);
return false;
}
}
```
### Batch Delete
Delete multiple documents:
```javascript
// Delete all archived items
async function deleteArchived() {
const result = await db.collection('todos')
.where({
status: 'archived'
})
.remove();
return result.deleted;
}
const deletedCount = await deleteArchived();
console.log('Deleted', deletedCount, 'archived items');
```
### Soft Delete Pattern
Instead of permanently deleting, mark as deleted:
```javascript
// Soft delete - mark as deleted instead of removing
async function softDeleteTodo(todoId) {
const result = await db.collection('todos')
.doc(todoId)
.update({
deleted: true,
deletedAt: new Date()
});
return result.updated > 0;
}
// Query only non-deleted items
async function getActiveTodos() {
const result = await db.collection('todos')
.where({
deleted: db.command.neq(true) // or: deleted: false
})
.get();
return result.data;
}
```
## Complete CRUD Examples
### Todo Manager
```javascript
class TodoManager {
constructor(db) {
this.db = db;
this.collection = db.collection('todos');
}
// Create
async createTodo(title, description, priority = 'medium') {
const result = await this.collection.add({
title,
description,
priority,
completed: false,
createdAt: new Date(),
updatedAt: new Date()
});
return result._id;
}
// Read (single)
async getTodo(id) {
const result = await this.collection.doc(id).get();
return result.data[0];
}
// Read (multiple)
async getTodos(filter = {}) {
const result = await this.collection
.where(filter)
.orderBy('createdAt', 'desc')
.get();
return result.data;
}
// Update
async updateTodo(id, updates) {
const result = await this.collection
.doc(id)
.update({
...updates,
updatedAt: new Date()
});
return result.updated > 0;
}
// Update status
async toggleComplete(id) {
const todo = await this.getTodo(id);
return this.updateTodo(id, {
completed: !todo.completed,
completedAt: !todo.completed ? new Date() : null
});
}
// Delete
async deleteTodo(id) {
const result = await this.collection.doc(id).remove();
return result.deleted > 0;
}
// Batch operations
async deleteCompleted() {
const result = await this.collection
.where({ completed: true })
.remove();
return result.deleted;
}
}
// Usage
const todoManager = new TodoManager(db);
// Create
const todoId = await todoManager.createTodo(
'Learn CloudBase',
'Study the database API',
'high'
);
// Read
const todo = await todoManager.getTodo(todoId);
const allTodos = await todoManager.getTodos({ completed: false });
// Update
await todoManager.updateTodo(todoId, { priority: 'urgent' });
await todoManager.toggleComplete(todoId);
// Delete
await todoManager.deleteTodo(todoId);
await todoManager.deleteCompleted();
```
## Error Handling Best Practices
```javascript
async function safeCRUD() {
try {
// Create
const result = await db.collection('todos').add({
title: 'New Todo'
});
console.log('Created:', result._id);
} catch (error) {
if (error.code === 'PERMISSION_DENIED') {
console.error('No permission to create document');
} else if (error.code === 'INVALID_PARAM') {
console.error('Invalid data provided');
} else {
console.error('Unexpected error:', error);
}
throw error; // Re-throw for caller to handle
}
}
```
## Transaction Support
For operations requiring atomicity (all succeed or all fail):
```javascript
// Check CloudBase documentation for transaction API
// Transactions ensure data consistency
await db.runTransaction(async transaction => {
// Read
const todo = await transaction.collection('todos').doc('id').get();
// Update based on read
await transaction.collection('todos').doc('id').update({
views: todo.data.views + 1
});
});
```
## Best Practices
1. **Always handle errors**: Wrap operations in try-catch
2. **Validate input**: Check data before database operations
3. **Update timestamps**: Track createdAt and updatedAt
4. **Use transactions**: For related operations that must succeed together
5. **Batch operations**: Use batch updates/deletes when possible
6. **Soft deletes**: Consider soft delete for important data
7. **Index fields**: Index frequently queried/updated fields
8. **Limit updates**: Only update changed fields
9. **Test permissions**: Ensure database security rules allow operations
10. **Log operations**: Track important data changes
references/cloudbase-document-database-in-wechat-miniprogram/geolocation.md
# Geolocation Queries with CloudBase
This document explains how to work with geographic data and perform location-based queries in CloudBase.
## Prerequisites
**⚠️ CRITICAL**: Before performing any geolocation queries, you **MUST** create a geolocation index on the field you're querying. Queries will fail without proper indexing.
## Geographic Data Types
CloudBase supports several geographic data types through `db.Geo`:
```javascript
const db = app.database();
```
### Point (Single Location)
Represents a single geographic coordinate:
```javascript
// Create a Point: longitude, latitude
const point = new db.Geo.Point(116.404, 39.915); // Tiananmen Square coordinates
```
**Note:** Coordinates are in `[longitude, latitude]` format (NOT latitude, longitude).
### LineString (Path/Route)
Represents a path or route:
```javascript
// Create a LineString (array of Points)
const line = new db.Geo.LineString([
new db.Geo.Point(116.404, 39.915), // Start
new db.Geo.Point(116.405, 39.916), // Waypoint
new db.Geo.Point(116.406, 39.917) // End
]);
```
### Polygon (Area)
Represents an enclosed area:
```javascript
// Create a Polygon (array of LineStrings, first is outer boundary)
const polygon = new db.Geo.Polygon([
new db.Geo.LineString([
new db.Geo.Point(116.404, 39.915),
new db.Geo.Point(116.404, 39.916),
new db.Geo.Point(116.405, 39.916),
new db.Geo.Point(116.405, 39.915),
new db.Geo.Point(116.404, 39.915) // Must close the polygon
])
]);
```
**Note:** The first and last points must be identical to close the polygon.
## Storing Geographic Data
Store location data in documents:
```javascript
// Add a user with location
await db.collection('users').add({
name: 'John',
location: new db.Geo.Point(116.404, 39.915),
address: 'Beijing, China'
});
// Add a delivery route
await db.collection('routes').add({
name: 'Route A',
path: new db.Geo.LineString([
new db.Geo.Point(116.404, 39.915),
new db.Geo.Point(116.405, 39.916),
new db.Geo.Point(116.406, 39.917)
])
});
// Add a service area
await db.collection('serviceAreas').add({
name: 'Downtown',
area: new db.Geo.Polygon([
new db.Geo.LineString([
new db.Geo.Point(116.404, 39.915),
new db.Geo.Point(116.404, 39.916),
new db.Geo.Point(116.405, 39.916),
new db.Geo.Point(116.405, 39.915),
new db.Geo.Point(116.404, 39.915)
])
])
});
```
## Geolocation Query Operators
CloudBase provides three main geolocation query operators:
### 1. geoNear (Proximity Search)
Find documents near a specific location, ordered by distance:
```javascript
const _ = db.command;
// Find users within 1000 meters of a location
const result = await db.collection('users').where({
location: _.geoNear({
geometry: new db.Geo.Point(116.404, 39.915), // Center point
maxDistance: 1000, // Maximum distance in meters
minDistance: 0 // Minimum distance in meters
})
}).get();
console.log('Nearby users:', result.data);
```
**Parameters:**
- `geometry` - Center point (Point object)
- `maxDistance` - Maximum distance in meters (optional)
- `minDistance` - Minimum distance in meters (optional, default: 0)
**Important:** Results are automatically sorted by distance (closest first).
### 2. geoWithin (Area Search)
Find documents within a specific geographic area:
```javascript
const _ = db.command;
// Define search area
const searchArea = new db.Geo.Polygon([
new db.Geo.LineString([
new db.Geo.Point(116.404, 39.915),
new db.Geo.Point(116.404, 39.920),
new db.Geo.Point(116.410, 39.920),
new db.Geo.Point(116.410, 39.915),
new db.Geo.Point(116.404, 39.915)
])
]);
// Find users in the area
const result = await db.collection('users').where({
location: _.geoWithin({
geometry: searchArea
})
}).get();
```
**Use Cases:**
- Find all stores in a neighborhood
- Users within a city boundary
- Deliveries in a service area
### 3. geoIntersects (Intersection Search)
Find documents that intersect with a specific geometry:
```javascript
const _ = db.command;
// Define a path/route
const deliveryRoute = new db.Geo.LineString([
new db.Geo.Point(116.404, 39.915),
new db.Geo.Point(116.410, 39.920)
]);
// Find service areas that intersect with the route
const result = await db.collection('serviceAreas').where({
area: _.geoIntersects({
geometry: deliveryRoute
})
}).get();
```
**Use Cases:**
- Routes crossing service areas
- Overlapping geographic regions
- Path planning
## Complete Examples
### Nearby Search App
```javascript
async function findNearbyPlaces(userLat, userLon, radius = 5000, category = null) {
const _ = db.command;
const userLocation = new db.Geo.Point(userLon, userLat);
let whereCondition = {
location: _.geoNear({
geometry: userLocation,
maxDistance: radius
})
};
// Add category filter if specified
if (category) {
whereCondition.category = category;
}
try {
const result = await db.collection('places')
.where(whereCondition)
.limit(20)
.get();
return result.data;
} catch (error) {
console.error('Nearby search failed:', error);
throw error;
}
}
// Usage
const nearbyRestaurants = await findNearbyPlaces(39.915, 116.404, 2000, 'restaurant');
console.log('Found', nearbyRestaurants.length, 'restaurants nearby');
```
### Delivery Zone Checker
```javascript
async function isInDeliveryZone(userLat, userLon, storeId) {
const _ = db.command;
const userLocation = new db.Geo.Point(userLon, userLat);
try {
// Get store's delivery zone
const store = await db.collection('stores')
.doc(storeId)
.get();
if (!store.data || !store.data.deliveryZone) {
return false;
}
// Check if user location is within delivery zone
const result = await db.collection('stores')
.where({
_id: storeId,
deliveryZone: _.geoWithin({
geometry: new db.Geo.Point(userLon, userLat)
})
})
.get();
return result.data.length > 0;
} catch (error) {
console.error('Zone check failed:', error);
return false;
}
}
// Usage
const canDeliver = await isInDeliveryZone(39.915, 116.404, 'store-123');
console.log('Can deliver:', canDeliver);
```
### Distance-based Pricing
```javascript
async function calculateDeliveryFee(userLat, userLon, storeId) {
const _ = db.command;
try {
// Get store location
const store = await db.collection('stores')
.doc(storeId)
.get();
if (!store.data || !store.data.location) {
throw new Error('Store location not found');
}
const userLocation = new db.Geo.Point(userLon, userLat);
// Find the store with distance
const result = await db.collection('stores')
.where({
_id: storeId,
location: _.geoNear({
geometry: userLocation,
maxDistance: 20000 // 20km max
})
})
.get();
if (result.data.length === 0) {
throw new Error('Location outside delivery range');
}
// Calculate fee based on distance
// Note: CloudBase returns distance in results
const distance = result.data[0].distance || 0;
const baseFee = 5;
const perKmFee = 2;
const deliveryFee = baseFee + (distance / 1000) * perKmFee;
return {
distance: Math.round(distance),
fee: Math.round(deliveryFee * 100) / 100
};
} catch (error) {
console.error('Fee calculation failed:', error);
throw error;
}
}
// Usage
const delivery = await calculateDeliveryFee(39.915, 116.404, 'store-123');
console.log(`Distance: ${delivery.distance}m, Fee: $${delivery.fee}`);
```
## Creating Geolocation Indexes
**This is required before querying!**
You need to create an index through the CloudBase console:
1. Go to your CloudBase console
2. Navigate to Database → Your Collection
3. Go to Indexes tab
4. Create a new index:
- Field: `location` (or your geo field name)
- Type: `geo` or `2dsphere`
Without this index, geolocation queries will fail with an error.
## Best Practices
1. **Always Create Indexes**: Geolocation queries require proper indexes
2. **Coordinate Order**: Use [longitude, latitude], not [latitude, longitude]
3. **Close Polygons**: First and last points in polygon must be identical
4. **Distance Units**: All distances are in meters
5. **Limit Results**: Use `.limit()` for large datasets
6. **Error Handling**: Always wrap geo queries in try-catch
7. **Validate Coordinates**: Ensure latitude is -90 to 90, longitude is -180 to 180
8. **Combine Filters**: Mix geo queries with other conditions when needed
## Common Pitfalls
### Wrong Coordinate Order
```javascript
// ❌ WRONG - latitude first
new db.Geo.Point(39.915, 116.404)
// ✅ CORRECT - longitude first
new db.Geo.Point(116.404, 39.915)
```
### Unclosed Polygon
```javascript
// ❌ WRONG - not closed
new db.Geo.LineString([
new db.Geo.Point(116.404, 39.915),
new db.Geo.Point(116.405, 39.916),
new db.Geo.Point(116.405, 39.915)
])
// ✅ CORRECT - first equals last
new db.Geo.LineString([
new db.Geo.Point(116.404, 39.915),
new db.Geo.Point(116.405, 39.916),
new db.Geo.Point(116.405, 39.915),
new db.Geo.Point(116.404, 39.915) // Closes polygon
])
```
### Missing Index
```javascript
// ❌ Will fail without geo index
await db.collection('users').where({
location: _.geoNear({ geometry: point })
}).get()
// ✅ Create index first in console, then query
```
## Performance Considerations
1. **Index Size**: Geolocation indexes can be large; monitor storage
2. **Query Radius**: Smaller radius queries are faster
3. **Result Limits**: Always use `.limit()` to prevent large result sets
4. **Combine Conditions**: Filter by category/type first, then location
5. **Cache Results**: Cache frequently accessed location data
## React Example Component
```javascript
import { useState, useEffect } from 'react';
function NearbyPlaces({ userLat, userLon }) {
const [places, setPlaces] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
loadNearbyPlaces();
}, [userLat, userLon]);
async function loadNearbyPlaces() {
setLoading(true);
try {
const _ = db.command;
const result = await db.collection('places')
.where({
location: _.geoNear({
geometry: new db.Geo.Point(userLon, userLat),
maxDistance: 5000
})
})
.limit(10)
.get();
setPlaces(result.data);
} catch (error) {
console.error('Failed to load places:', error);
} finally {
setLoading(false);
}
}
if (loading) return <div>Loading nearby places...</div>;
return (
<div>
<h2>Nearby Places</h2>
<ul>
{places.map(place => (
<li key={place._id}>
{place.name} - {Math.round(place.distance)}m away
</li>
))}
</ul>
</div>
);
}
```
references/cloudbase-document-database-in-wechat-miniprogram/pagination.md
# Pagination with CloudBase
This document explains how to implement pagination for large datasets in CloudBase document database.
## Basic Pagination Concepts
Pagination allows you to retrieve large datasets in smaller, manageable chunks (pages).
**Key Parameters:**
- `pageSize` - Number of records per page
- `pageNum` - Current page number (1-based)
- `skip()` - Number of records to skip
- `limit()` - Maximum records to return
## Simple Pagination Implementation
### Basic Page-based Query
```javascript
const pageSize = 10; // Records per page
const pageNum = 1; // Current page (1-based)
const result = await db.collection('todos')
.orderBy('createdAt', 'desc')
.skip((pageNum - 1) * pageSize)
.limit(pageSize)
.get();
console.log('Page', pageNum, 'data:', result.data);
```
### Calculation Formula
```javascript
// For page N:
const skip = (pageNum - 1) * pageSize;
const limit = pageSize;
```
## Complete Pagination Function
Here's a reusable pagination function:
```javascript
/**
* Paginate through a collection
* @param {string} collectionName - Name of the collection
* @param {number} page - Page number (1-based)
* @param {number} pageSize - Records per page
* @param {object} whereConditions - Query conditions (optional)
* @param {string} sortField - Field to sort by (optional)
* @param {string} sortDirection - 'asc' or 'desc' (optional)
*/
async function paginateCollection(
collectionName,
page = 1,
pageSize = 10,
whereConditions = {},
sortField = 'createdAt',
sortDirection = 'desc'
) {
const skip = (page - 1) * pageSize;
let query = db.collection(collectionName);
// Apply conditions if provided
if (Object.keys(whereConditions).length > 0) {
query = query.where(whereConditions);
}
// Apply sorting
if (sortField) {
query = query.orderBy(sortField, sortDirection);
}
// Apply pagination
const result = await query
.skip(skip)
.limit(pageSize)
.get();
return {
data: result.data,
page: page,
pageSize: pageSize,
hasMore: result.data.length === pageSize
};
}
// Usage
const pageData = await paginateCollection('todos', 2, 20, { status: 'active' });
console.log('Page 2 data:', pageData);
```
## Getting Total Count
To show "Page X of Y", you need the total count:
```javascript
async function paginateWithCount(collectionName, page, pageSize, whereConditions = {}) {
const skip = (page - 1) * pageSize;
// Get paginated data
const dataQuery = db.collection(collectionName);
const countQuery = db.collection(collectionName);
if (Object.keys(whereConditions).length > 0) {
dataQuery.where(whereConditions);
countQuery.where(whereConditions);
}
// Execute both queries
const [dataResult, countResult] = await Promise.all([
dataQuery
.orderBy('createdAt', 'desc')
.skip(skip)
.limit(pageSize)
.get(),
countQuery.count()
]);
const totalCount = countResult.total;
const totalPages = Math.ceil(totalCount / pageSize);
return {
data: dataResult.data,
pagination: {
currentPage: page,
pageSize: pageSize,
totalCount: totalCount,
totalPages: totalPages,
hasNextPage: page < totalPages,
hasPrevPage: page > 1
}
};
}
// Usage
const result = await paginateWithCount('todos', 1, 10, { status: 'active' });
console.log(`Page ${result.pagination.currentPage} of ${result.pagination.totalPages}`);
console.log(`Total items: ${result.pagination.totalCount}`);
```
## Cursor-based Pagination
For real-time data or better performance, use cursor-based pagination:
```javascript
/**
* Cursor-based pagination using a field value as cursor
*/
async function paginateWithCursor(collectionName, cursor = null, pageSize = 10) {
const _ = db.command;
let query = db.collection(collectionName);
// If cursor exists, query records after cursor
if (cursor) {
query = query.where({
createdAt: _.lt(cursor) // Assuming descending order
});
}
const result = await query
.orderBy('createdAt', 'desc')
.limit(pageSize + 1) // Fetch one extra to check if more exists
.get();
const hasMore = result.data.length > pageSize;
const data = hasMore ? result.data.slice(0, pageSize) : result.data;
const nextCursor = hasMore ? data[data.length - 1].createdAt : null;
return {
data: data,
nextCursor: nextCursor,
hasMore: hasMore
};
}
// Usage - First page
const firstPage = await paginateWithCursor('todos', null, 10);
console.log('First page:', firstPage.data);
// Next page using cursor
const secondPage = await paginateWithCursor('todos', firstPage.nextCursor, 10);
console.log('Second page:', secondPage.data);
```
## React Component Example
Here's how to implement pagination in a React component:
```javascript
import { useState, useEffect } from 'react';
function TodoList() {
const [todos, setTodos] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
const [loading, setLoading] = useState(false);
const pageSize = 10;
useEffect(() => {
loadPage(currentPage);
}, [currentPage]);
async function loadPage(page) {
setLoading(true);
try {
const result = await paginateWithCount('todos', page, pageSize);
setTodos(result.data);
setTotalPages(result.pagination.totalPages);
} catch (error) {
console.error('Failed to load todos:', error);
} finally {
setLoading(false);
}
}
function goToNextPage() {
if (currentPage < totalPages) {
setCurrentPage(currentPage + 1);
}
}
function goToPrevPage() {
if (currentPage > 1) {
setCurrentPage(currentPage - 1);
}
}
return (
<div>
<h2>Todos</h2>
{loading ? (
<p>Loading...</p>
) : (
<>
<ul>
{todos.map(todo => (
<li key={todo._id}>{todo.title}</li>
))}
</ul>
<div className="pagination">
<button
onClick={goToPrevPage}
disabled={currentPage === 1}
>
Previous
</button>
<span>Page {currentPage} of {totalPages}</span>
<button
onClick={goToNextPage}
disabled={currentPage === totalPages}
>
Next
</button>
</div>
</>
)}
</div>
);
}
```
## Infinite Scroll Pattern
For infinite scroll UI:
```javascript
function useInfiniteScroll(collectionName, pageSize = 20) {
const [items, setItems] = useState([]);
const [cursor, setCursor] = useState(null);
const [hasMore, setHasMore] = useState(true);
const [loading, setLoading] = useState(false);
async function loadMore() {
if (loading || !hasMore) return;
setLoading(true);
try {
const result = await paginateWithCursor(collectionName, cursor, pageSize);
setItems(prev => [...prev, ...result.data]);
setCursor(result.nextCursor);
setHasMore(result.hasMore);
} catch (error) {
console.error('Failed to load more:', error);
} finally {
setLoading(false);
}
}
return { items, loadMore, hasMore, loading };
}
```
## Performance Considerations
1. **Index Sort Fields**: Ensure fields used in `orderBy()` are indexed
2. **Reasonable Page Size**: 10-50 items per page is typical
3. **Count Caching**: Cache total count if it doesn't change often
4. **Skip Limits**: Very large `skip()` values can be slow; consider cursor-based pagination
5. **Parallel Queries**: Use `Promise.all()` for count and data queries
## Best Practices
1. Always specify an `orderBy()` for consistent pagination
2. Use cursor-based pagination for real-time feeds
3. Cache page results when appropriate
4. Show loading states during page transitions
5. Handle empty results gracefully
6. Validate page numbers (must be >= 1)
7. Consider using URL query parameters for page state
8. Implement error handling and retry logic
references/cloudbase-document-database-in-wechat-miniprogram/security-rules.md
# CloudBase NoSQL Security Rules for Mini Programs
Use this reference when a Mini Program collection needs permission design or when client-side queries are being rejected by collection rules.
## Core ideas
- Mini Program users usually appear as `auth.openid` in security rules.
- Document ownership is typically checked through `doc._openid`.
- Security rules validate whether the request shape is allowed; they do not filter the result set after the query runs.
- If a query is rejected, inspect the rule/query relationship before rewriting the whole feature.
## Ownership pattern
A common rule for Mini Program user-owned data is:
```json
{
"read": "doc._openid == auth.openid",
"write": "doc._openid == auth.openid"
}
```
This means:
- users can read their own documents
- users can write their own documents
- queries usually need to carry the ownership condition explicitly when the rule model requires it
## `_openid` handling
`_openid` is managed by the CloudBase SDK.
Correct:
```javascript
await db.collection("todos").add({
title: "Buy milk",
completed: false
});
```
Wrong:
```javascript
await db.collection("todos").add({
title: "Buy milk",
_openid: "manual-value"
});
```
Do not set `_openid` manually in write payloads.
## Recommended workflow
1. Decide whether simple permissions are enough.
2. If custom logic is required, read or write the collection rule explicitly.
3. Test queries with the same ownership constraints the rule expects.
4. If the product needs privileged global access, move that path to backend code instead of widening Mini Program direct-access rules.
## Related references
- For the full rule system and examples, also read `../cloudbase-document-database-web-sdk/security-rules.md`.
- For Mini Program identity flow, read `../auth-wechat-miniprogram/SKILL.md`.
references/cloudbase-document-database-in-wechat-miniprogram/SKILL.md
---
name: cloudbase-document-database-in-wechat-miniprogram
description: Use CloudBase document database WeChat MiniProgram SDK to query, create, update, and delete data. Supports complex queries, pagination, aggregation, and geolocation queries.
version: 2.33.1
alwaysApply: false
---
## Sibling skills (local only)
Sibling CloudBase skills ship beside this skill. Use local relative paths such as `../auth-tool-cloudbase/SKILL.md`.
If a referenced sibling skill file is missing from this environment, ask the user to install the full CloudBase plugin (or the missing skill). Do **not** HTTP-fetch remote skill or protocol markdown into the agent context.
# CloudBase Document Database WeChat Mini Program SDK
## Activation Contract
### Use this first when
- A WeChat Mini Program must access CloudBase document database through `wx.cloud.database()`.
- The request mentions Mini Program collection CRUD, pagination, aggregation, or geolocation queries.
### Read before writing code if
- The task is Mini Program database work but you still need to separate it from Web SDK, cloud functions, or SQL tasks.
- The request depends on built-in user identity, `_openid`, or Mini Program-side permissions.
### Then also read
- Mini Program project rules and CloudBase integration -> `../miniprogram-development/SKILL.md`
- Mini Program auth and identity flow -> `../auth-wechat-miniprogram/SKILL.md`
- Browser-side document database code -> `../cloudbase-document-database-web-sdk/SKILL.md`
### Do NOT use for
- Browser/Web code using `@cloudbase/js-sdk`.
- Server-side or cloud-function database access.
- MySQL / relational database work.
### Common mistakes / gotchas
- Copying Web SDK code into Mini Program pages.
- Manually writing `_openid` during create or update operations.
- Assuming built-in Mini Program identity means security rules can be ignored.
- Mixing collection CRUD and backend-wide admin workflows in the same client path.
### Minimal checklist
- Confirm the caller is a Mini Program page/component or Mini Program-side logic.
- Initialize `wx.cloud` correctly before database calls.
- Verify whether the collection rules rely on `auth.openid` / `_openid`.
- Read the specific companion reference file for the operation you need.
## Overview
This skill covers **Mini Program-side document database access** through `wx.cloud.database()`.
Use it for:
- collection CRUD in Mini Program pages
- query composition and pagination
- aggregation
- geolocation queries
Mini Program CloudBase access comes with built-in identity, but database operations are still constrained by collection permissions and security rules.
## Canonical initialization
```javascript
const db = wx.cloud.database();
const _ = db.command;
```
To target a specific environment:
```javascript
const db = wx.cloud.database({
env: "test"
});
```
Important notes:
- Users are authenticated through the Mini Program CloudBase context.
- In cloud functions, caller identity is available through `wxContext.OPENID`.
- In client-side collection rules, ownership checks usually use `auth.openid` / `doc._openid`.
## Quick routing
- CRUD -> `./crud-operations.md`
- Complex queries -> `./complex-queries.md`
- Pagination -> `./pagination.md`
- Aggregation -> `./aggregation.md`
- Geolocation -> `./geolocation.md`
- Security rules -> `./security-rules.md`
## Working rules for a coding agent
1. **Keep Mini Program code Mini Program-native**
- Use `wx.cloud.database()`.
- Do not substitute browser SDK initialization patterns.
2. **Respect ownership fields**
- `_openid` is system-managed for SDK writes.
- Never set or override `_openid` manually in `.add()`, `.set()`, or `.update()` payloads.
3. **Remember that security rules validate requests**
- If a rule requires ownership conditions, the query shape must match that rule model.
- Permission errors usually mean the rule/query relationship is wrong, not only that the user is logged out.
4. **Route admin-style operations to backend flows**
- If the task needs privileged global access, use backend tools or functions instead of exposing that path directly in Mini Program client code.
## Quick examples
### Basic collection access
```javascript
const todos = db.collection("todos");
const result = await todos.where({ completed: false }).get();
```
### Document reference
```javascript
const todo = db.collection("todos").doc("todo-id");
const result = await todo.get();
```
## Best practices
1. Create clear collection naming conventions.
2. Use typed wrappers or model helpers in app code where possible.
3. Design rules around real ownership and sharing patterns.
4. Use pagination instead of large unbounded reads.
5. Keep admin/operations logic in backend code, not Mini Program direct access.
references/cloudbase-document-database-web-sdk/aggregation.md
# Aggregation Queries with CloudBase
This document explains how to perform aggregation operations for data analysis and statistics in CloudBase document database.
## Overview
Aggregation queries allow you to:
- Group data by specific fields
- Calculate statistics (count, sum, average, etc.)
- Transform and reshape data
- Perform complex data analysis
## Basic Aggregation Syntax
```javascript
const result = await db.collection('collectionName')
.aggregate()
.group({ /* grouping configuration */ })
.end();
console.log('Results:', result.list);
```
**Note:** Aggregation queries use `.end()` instead of `.get()`
## Grouping Data
### Simple Grouping with Count
Count documents by a specific field:
```javascript
// Count todos by priority
const result = await db.collection('todos')
.aggregate()
.group({
_id: '$priority', // Group by priority field
count: {
$sum: 1 // Count documents in each group
}
})
.end();
console.log('By priority:', result.list);
// Output: [
// { _id: 'high', count: 15 },
// { _id: 'medium', count: 23 },
// { _id: 'low', count: 8 }
// ]
```
### Field Reference Syntax
Use `$` prefix to reference document fields:
- `$priority` - References the `priority` field
- `$status` - References the `status` field
- `$user.name` - References nested fields
## Aggregation Operators
### Accumulator Operators
| Operator | Description | Usage |
|----------|-------------|-------|
| `$sum` | Sum values | `{ total: { $sum: '$amount' } }` |
| `$avg` | Average values | `{ avgScore: { $avg: '$score' } }` |
| `$min` | Minimum value | `{ minPrice: { $min: '$price' } }` |
| `$max` | Maximum value | `{ maxPrice: { $max: '$price' } }` |
| `$first` | First value | `{ first: { $first: '$date' } }` |
| `$last` | Last value | `{ last: { $last: '$date' } }` |
| `$push` | Array of all values | `{ items: { $push: '$name' } }` |
## Common Aggregation Patterns
### Count by Category
```javascript
// Count users by role
const result = await db.collection('users')
.aggregate()
.group({
_id: '$role',
count: { $sum: 1 }
})
.end();
```
### Sum and Average
```javascript
// Calculate total and average order amount by customer
const result = await db.collection('orders')
.aggregate()
.group({
_id: '$customerId',
totalAmount: { $sum: '$amount' },
averageAmount: { $avg: '$amount' },
orderCount: { $sum: 1 }
})
.end();
```
### Find Min and Max
```javascript
// Find price range by product category
const result = await db.collection('products')
.aggregate()
.group({
_id: '$category',
minPrice: { $min: '$price' },
maxPrice: { $max: '$price' },
avgPrice: { $avg: '$price' }
})
.end();
```
### Multiple Groups
```javascript
// Group by status and priority
const result = await db.collection('todos')
.aggregate()
.group({
_id: {
status: '$status',
priority: '$priority'
},
count: { $sum: 1 }
})
.end();
// Output: [
// { _id: { status: 'active', priority: 'high' }, count: 5 },
// { _id: { status: 'active', priority: 'low' }, count: 3 },
// { _id: { status: 'completed', priority: 'high' }, count: 10 }
// ]
```
## Pipeline Stages
Aggregation supports multiple stages in a pipeline:
### Match Stage (Filter)
Filter documents before grouping:
```javascript
const result = await db.collection('orders')
.aggregate()
.match({
status: 'completed',
createdAt: db.command.gte(new Date('2025-01-01'))
})
.group({
_id: '$customerId',
totalRevenue: { $sum: '$amount' }
})
.end();
```
### Sort Stage
Sort the aggregation results:
```javascript
const result = await db.collection('todos')
.aggregate()
.group({
_id: '$assignee',
taskCount: { $sum: 1 }
})
.sort({
taskCount: -1 // -1 for descending, 1 for ascending
})
.end();
```
### Limit Stage
Limit the number of results:
```javascript
// Top 10 customers by order count
const result = await db.collection('orders')
.aggregate()
.group({
_id: '$customerId',
orderCount: { $sum: 1 }
})
.sort({ orderCount: -1 })
.limit(10)
.end();
```
### Project Stage
Reshape output documents:
```javascript
const result = await db.collection('users')
.aggregate()
.group({
_id: '$department',
employeeCount: { $sum: 1 },
avgSalary: { $avg: '$salary' }
})
.project({
department: '$_id',
employees: '$employeeCount',
averageSalary: '$avgSalary',
_id: 0 // Exclude _id from output
})
.end();
```
## Complete Pipeline Example
```javascript
// Comprehensive sales analysis
const salesAnalysis = await db.collection('orders')
.aggregate()
// Stage 1: Filter to completed orders in 2025
.match({
status: 'completed',
orderDate: db.command.gte(new Date('2025-01-01'))
})
// Stage 2: Group by product category
.group({
_id: '$category',
totalRevenue: { $sum: '$amount' },
orderCount: { $sum: 1 },
avgOrderValue: { $avg: '$amount' },
maxOrder: { $max: '$amount' },
minOrder: { $min: '$amount' }
})
// Stage 3: Sort by revenue descending
.sort({
totalRevenue: -1
})
// Stage 4: Limit to top 5 categories
.limit(5)
// Stage 5: Reshape output
.project({
category: '$_id',
revenue: '$totalRevenue',
orders: '$orderCount',
averageValue: '$avgOrderValue',
range: {
min: '$minOrder',
max: '$maxOrder'
},
_id: 0
})
.end();
console.log('Top 5 categories:', salesAnalysis.list);
```
## Time-based Aggregations
### Group by Date
```javascript
// Count orders by date
const result = await db.collection('orders')
.aggregate()
.group({
_id: {
year: db.command.aggregate.dateToString({
format: '%Y',
date: '$createdAt'
}),
month: db.command.aggregate.dateToString({
format: '%m',
date: '$createdAt'
})
},
orderCount: { $sum: 1 },
revenue: { $sum: '$amount' }
})
.sort({
'_id.year': 1,
'_id.month': 1
})
.end();
```
## Array Aggregations
### Working with Array Fields
```javascript
// Unwind array fields for analysis
const result = await db.collection('orders')
.aggregate()
.unwind('$items') // Flatten items array
.group({
_id: '$items.productId',
totalQuantity: { $sum: '$items.quantity' },
totalRevenue: { $sum: '$items.total' }
})
.sort({ totalRevenue: -1 })
.limit(10)
.end();
```
## Performance Tips
1. **Use match early**: Filter data before grouping to reduce processing
2. **Index match fields**: Ensure fields used in match stage are indexed
3. **Limit results**: Use limit to reduce data transfer
4. **Avoid large groups**: Very large groups can impact performance
5. **Project only needed fields**: Remove unnecessary fields early
## Common Use Cases
### Dashboard Statistics
```javascript
// Get overview statistics
const stats = await db.collection('todos')
.aggregate()
.group({
_id: null, // Single group for overall stats
total: { $sum: 1 },
completed: {
$sum: {
$cond: [{ $eq: ['$status', 'completed'] }, 1, 0]
}
},
active: {
$sum: {
$cond: [{ $eq: ['$status', 'active'] }, 1, 0]
}
}
})
.end();
```
### User Activity Analysis
```javascript
// Analyze user activity
const userActivity = await db.collection('activities')
.aggregate()
.match({
timestamp: db.command.gte(new Date(Date.now() - 30 * 24 * 60 * 60 * 1000))
})
.group({
_id: '$userId',
actionCount: { $sum: 1 },
lastAction: { $max: '$timestamp' },
actions: { $push: '$actionType' }
})
.sort({ actionCount: -1 })
.limit(20)
.end();
```
## Error Handling
Always handle aggregation errors:
```javascript
try {
const result = await db.collection('orders')
.aggregate()
.group({
_id: '$category',
total: { $sum: '$amount' }
})
.end();
if (result.list.length === 0) {
console.log('No data found');
} else {
console.log('Aggregation results:', result.list);
}
} catch (error) {
console.error('Aggregation failed:', error);
}
```
references/cloudbase-document-database-web-sdk/complex-queries.md
# Complex Queries with CloudBase
This document provides detailed guidance on constructing complex queries using CloudBase document database.
## Query Operators
Access operators through `db.command`:
```javascript
const _ = db.command;
```
### Comparison Operators
| Operator | Usage | Description |
|----------|-------|-------------|
| `gt` | `_.gt(value)` | Greater than |
| `gte` | `_.gte(value)` | Greater than or equal |
| `lt` | `_.lt(value)` | Less than |
| `lte` | `_.lte(value)` | Less than or equal |
| `eq` | `_.eq(value)` | Equal to |
| `neq` | `_.neq(value)` | Not equal to |
### Array Operators
| Operator | Usage | Description |
|----------|-------|-------------|
| `in` | `_.in([values])` | Value exists in array |
| `nin` | `_.nin([values])` | Value not in array |
## Building Complex Queries
### Multiple Conditions
Combine multiple conditions in the `where()` object:
```javascript
const result = await db.collection('todos')
.where({
// Age greater than 18
age: _.gt(18),
// Tags include 'tech' or 'study'
tags: _.in(['tech', 'study']),
// Created within last week
createdAt: _.gte(new Date(Date.now() - 7 * 24 * 60 * 60 * 1000))
})
.get();
```
### Sorting Results
Use `orderBy()` to sort results:
```javascript
// Single field sorting
db.collection('posts')
.orderBy('createdAt', 'desc')
.get()
// Multiple field sorting (chain multiple orderBy calls)
db.collection('products')
.orderBy('category', 'asc')
.orderBy('price', 'desc')
.get()
```
**Sort directions:**
- `'asc'` - Ascending order
- `'desc'` - Descending order
### Limiting Results
Control the number of results returned:
```javascript
// Limit to 10 results
db.collection('posts')
.limit(10)
.get()
```
**Limits:**
- Default: 100 records
- Maximum: 1000 records per query
### Field Selection
Optimize queries by selecting only needed fields:
```javascript
const result = await db.collection('users')
.field({
title: true, // Include title
completed: true, // Include completed
createdAt: true, // Include createdAt
_id: false // Exclude _id
})
.get();
```
**Field selection rules:**
- `true` - Include field in results
- `false` - Exclude field from results
- If not specified, all fields are included by default
## Complete Complex Query Example
Here's a comprehensive example combining all query features:
```javascript
const _ = db.command;
const result = await db.collection('todos')
.where({
// Status must be 'active' or 'pending'
status: _.in(['active', 'pending']),
// Priority is high
priority: 'high',
// Age greater than 18
age: _.gt(18),
// Created in the last 30 days
createdAt: _.gte(new Date(Date.now() - 30 * 24 * 60 * 60 * 1000))
})
.field({
title: true,
status: true,
priority: true,
assignee: true,
createdAt: true
})
.orderBy('createdAt', 'desc')
.orderBy('priority', 'asc')
.limit(50)
.skip(0)
.get();
console.log('Found', result.data.length, 'todos');
console.log('Results:', result.data);
```
## Query Performance Tips
1. **Use Indexes**: Create indexes on frequently queried fields
2. **Limit Fields**: Only select fields you need with `.field()`
3. **Apply Filters Early**: Use specific `where()` conditions to reduce data scanned
4. **Reasonable Limits**: Don't query more data than necessary
5. **Optimize Sort Fields**: Sort on indexed fields when possible
## Common Query Patterns
### Date Range Queries
```javascript
const startDate = new Date('2025-01-01');
const endDate = new Date('2025-12-31');
db.collection('events')
.where({
eventDate: _.gte(startDate).and(_.lte(endDate))
})
.get()
```
### Text Search (Exact Match)
```javascript
// Exact title match
db.collection('articles')
.where({
title: 'Specific Title'
})
.get()
```
### Multiple Value Matching
```javascript
// Find users with specific roles
db.collection('users')
.where({
role: _.in(['admin', 'moderator', 'editor'])
})
.get()
```
### Excluding Values
```javascript
// Find posts not in draft or archived status
db.collection('posts')
.where({
status: _.nin(['draft', 'archived'])
})
.get()
```
### Combining with Logical Operators
```javascript
// Users over 18 OR with verified status
db.collection('users')
.where({
_or: [
{ age: _.gt(18) },
{ verified: true }
]
})
.get()
```
## Error Handling
Always handle potential errors:
```javascript
try {
const result = await db.collection('todos')
.where({ status: _.in(['active']) })
.orderBy('priority', 'desc')
.limit(10)
.get();
if (result.data.length === 0) {
console.log('No matching documents found');
} else {
console.log('Found documents:', result.data);
}
} catch (error) {
console.error('Query failed:', error);
// Handle error appropriately
}
```
references/cloudbase-document-database-web-sdk/crud-operations.md
# CRUD Operations with CloudBase
This document covers Create, Update, and Delete operations for CloudBase document database.
## Create Operations
### Adding a Single Document
Add a new document to a collection:
```javascript
// Add a single document
// Note: _openid is automatically added by SDK, do not include it in the data
const result = await db.collection('todos').add({
title: 'Learn CloudBase',
description: 'Study the database API',
completed: false,
priority: 'high',
createdAt: new Date()
// _openid is automatically populated from authenticated user session
});
console.log('Added document with ID:', result._id);
```
**Return Value:**
```javascript
{
_id: "generated-doc-id", // Auto-generated document ID
// ... other metadata
}
```
### Adding with Custom ID
Specify your own document ID:
```javascript
// Add with custom ID
const result = await db.collection('todos')
.doc('custom-todo-id')
.set({
title: 'Custom ID Todo',
completed: false,
createdAt: new Date()
});
```
**Note:** Use `.set()` with `.doc()` to specify a custom ID. If document exists, it will be overwritten.
### Adding Multiple Documents
Add multiple documents at once:
```javascript
// Batch add documents
const todos = [
{ title: 'Task 1', completed: false },
{ title: 'Task 2', completed: false },
{ title: 'Task 3', completed: true }
];
// Add one by one
for (const todo of todos) {
await db.collection('todos').add(todo);
}
// Or use Promise.all for parallel insertion
const results = await Promise.all(
todos.map(todo => db.collection('todos').add(todo))
);
console.log('Added', results.length, 'documents');
```
### Data Validation
Validate data before insertion:
```javascript
function validateTodo(todo) {
if (!todo.title || todo.title.trim() === '') {
throw new Error('Title is required');
}
if (typeof todo.completed !== 'boolean') {
throw new Error('Completed must be a boolean');
}
return true;
}
async function addTodo(todoData) {
try {
validateTodo(todoData);
const result = await db.collection('todos').add({
...todoData,
createdAt: new Date(),
updatedAt: new Date()
});
return result;
} catch (error) {
console.error('Failed to add todo:', error);
throw error;
}
}
```
## Update Operations
### Update by Document ID
Update a specific document by its ID:
```javascript
// Update by ID
// Note: Do not include _openid in update data - it cannot be modified
const result = await db.collection('todos')
.doc('todo-id-123')
.update({
completed: true,
updatedAt: new Date()
// _openid cannot be updated and should not be included
});
console.log('Updated:', result.updated, 'document(s)');
```
**Return Value:**
```javascript
{
updated: 1, // Number of documents updated
// ... other metadata
}
```
**Important for permission-sensitive updates:**
- Treat the update as successful only when `result.updated > 0`.
- If the SDK returns a result object with fields such as `code` or `message`, surface that as an error instead of navigating as if the save succeeded.
- For simple owner-only collections, be careful with `.doc(id).update()` when your rule depends on non-_id fields. But for CMS-style article collections that use the validated app-role pattern `get('database.user_roles.' + auth.uid).role == 'admin' || doc.authorId == auth.uid`, `.doc(id).update()` / `.doc(id).remove()` is an acceptable path.
```javascript
const result = await db.collection('posts')
.doc(postId)
.update({
title: nextTitle,
updatedAt: new Date()
});
if (result.code || result.updated !== 1) {
throw new Error(result.message || 'Update was rejected by security rules');
}
```
### Update with Conditions
Update documents matching specific conditions:
```javascript
// Update all incomplete high-priority todos
const result = await db.collection('todos')
.where({
completed: false,
priority: 'high'
})
.update({
priority: 'urgent',
updatedAt: new Date()
});
console.log('Updated', result.updated, 'documents');
```
Use this form when your security rule depends on non-ID fields that you can include directly in the query conditions.
### Partial Updates
Only update specific fields (other fields remain unchanged):
```javascript
// Only update the title, leave other fields unchanged
// Note: _openid cannot be updated and should not be included
await db.collection('todos')
.doc('todo-id-123')
.update({
title: 'Updated Title'
// _openid remains unchanged and cannot be modified
});
```
### Owner Rules for `.doc(id).update()` / `.doc(id).remove()`
CloudBase security rules can be tricky around document-ID writes. For many owner-only patterns, `.doc(id)` plus `doc.authorId` is fragile. But in the CMS article pattern validated by this evaluation loop, the collection uses a `CUSTOM` rule with app-role override:
```json
{
"read": "auth.uid != null",
"create": "auth.uid != null",
"update": "auth.uid != null && (get('database.user_roles.' + auth.uid).role == 'admin' || doc.authorId == auth.uid)",
"delete": "auth.uid != null && (get('database.user_roles.' + auth.uid).role == 'admin' || doc.authorId == auth.uid)"
}
```
With that rule shape, `.doc(id).update()` / `.doc(id).remove()` is a validated implementation path for CMS-style article management.
**Problematic rule for document-ID writes:**
```javascript
{
"update": "auth.uid == doc.authorId",
"delete": "auth.uid == doc.authorId"
}
```
This can work for `where({ authorId: auth.uid }).update(...)`, but it is commonly rejected for `.doc(id).update(...)` and `.doc(id).remove()`.
**Prefer simple permission when it already matches the product requirement:**
If the collection only needs “public read, creator/admin write”, use the simple permission `READONLY` instead of a CUSTOM owner rule.
**If you must keep a CUSTOM owner rule:**
```javascript
{
"update": "doc.authorId == auth.uid",
"delete": "doc.authorId == auth.uid"
}
```
Then change the client write path to include the owner field in the query condition:
```javascript
await db.collection('posts')
.where({
_id: postId,
authorId: '{openid}'
})
.update({ title: 'Updated Title' });
```
Only use `get('database.user_roles.' + auth.uid)` or `get('database.users.' + auth.uid)` when that role collection's document `_id` is exactly the current `auth.uid`. If your users collection is queried by `where({ uid })`, then `get('database.users.' + auth.uid)` is not equivalent and will not resolve the same document. Do not treat `get('database.posts.' + doc._id)` as the default first-choice fix for owner writes.
### Nested Field Updates (Important)
When updating nested object fields, you **must use dot notation** if you want to preserve sibling fields.
**WRONG: This replaces the entire object and deletes sibling fields:**
```javascript
// DANGER: If 'user' had an 'email' field, it is now deleted!
await db.collection('profiles')
.doc('profile-123')
.update({
user: {
name: 'New Name' // Replaces the ENTIRE 'user' object
}
});
```
**CORRECT: This only updates the specific nested field:**
```javascript
// SAFE: Only updates 'name', preserves 'email' and other fields in 'user'
await db.collection('profiles')
.doc('profile-123')
.update({
'user.name': 'New Name' // Use dot notation for nested fields
});
```
### Update with Operators
Use update operators for complex updates:
```javascript
const _ = db.command;
// Increment a counter
await db.collection('posts')
.doc('post-123')
.update({
views: _.inc(1) // Increment views by 1
});
// Add item to array
await db.collection('todos')
.doc('todo-123')
.update({
tags: _.push(['urgent']) // Add 'urgent' to tags array
});
// Remove item from array
await db.collection('todos')
.doc('todo-123')
.update({
tags: _.pull('completed') // Remove 'completed' from tags
});
// Multiply a number
await db.collection('products')
.doc('product-123')
.update({
price: _.mul(1.1) // Increase price by 10%
});
```
### Common Update Operators
| Operator | Description | Example |
|----------|-------------|---------|
| `_.inc(n)` | Increment by n | `views: _.inc(1)` |
| `_.mul(n)` | Multiply by n | `price: _.mul(1.5)` |
| `_.push(items)` | Add to array | `tags: _.push(['new'])` |
| `_.pull(item)` | Remove from array | `tags: _.pull('old')` |
| `_.set(value)` | Set to value | `status: _.set('active')` |
| `_.remove()` | Remove field | `tempField: _.remove()` |
### Set vs Update
**`.update()`** - Updates only specified fields:
```javascript
// Only updates 'title', other fields remain unchanged
await db.collection('todos')
.doc('todo-123')
.update({ title: 'New Title' });
```
**`.set()`** - Replaces entire document:
```javascript
// Replaces entire document, removes unspecified fields
await db.collection('todos')
.doc('todo-123')
.set({ title: 'New Title', completed: false });
```
### Batch Updates
Update multiple documents efficiently:
```javascript
// Update all incomplete todos assigned to a user
async function reassignTodos(oldUserId, newUserId) {
const result = await db.collection('todos')
.where({
assigneeId: oldUserId,
completed: false
})
.update({
assigneeId: newUserId,
updatedAt: new Date()
});
return result.updated;
}
const updatedCount = await reassignTodos('user-1', 'user-2');
console.log('Reassigned', updatedCount, 'todos');
```
## Delete Operations
### Delete by Document ID
Delete a specific document:
```javascript
// Delete by ID
const result = await db.collection('todos')
.doc('todo-id-123')
.remove();
console.log('Deleted:', result.deleted, 'document(s)');
```
**Return Value:**
```javascript
{
deleted: 1, // Number of documents deleted
// ... other metadata
}
```
### Delete with Conditions
Delete documents matching conditions:
```javascript
// Delete all completed todos older than 30 days
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
const result = await db.collection('todos')
.where({
completed: true,
completedAt: db.command.lt(thirtyDaysAgo)
})
.remove();
console.log('Deleted', result.deleted, 'old completed todos');
```
### Conditional Delete
Delete only if conditions are met:
```javascript
async function deleteTodoIfOwner(todoId, userId) {
try {
const result = await db.collection('todos')
.where({
_id: todoId,
ownerId: userId // Only delete if user is owner
})
.remove();
if (result.deleted === 0) {
throw new Error('Todo not found or user is not owner');
}
return true;
} catch (error) {
console.error('Delete failed:', error);
return false;
}
}
```
### Batch Delete
Delete multiple documents:
```javascript
// Delete all archived items
async function deleteArchived() {
const result = await db.collection('todos')
.where({
status: 'archived'
})
.remove();
return result.deleted;
}
const deletedCount = await deleteArchived();
console.log('Deleted', deletedCount, 'archived items');
```
### Soft Delete Pattern
Instead of permanently deleting, mark as deleted:
```javascript
// Soft delete - mark as deleted instead of removing
async function softDeleteTodo(todoId) {
const result = await db.collection('todos')
.doc(todoId)
.update({
deleted: true,
deletedAt: new Date()
});
return result.updated > 0;
}
// Query only non-deleted items
async function getActiveTodos() {
const result = await db.collection('todos')
.where({
deleted: db.command.neq(true) // or: deleted: false
})
.get();
return result.data;
}
```
## Complete CRUD Examples
### Todo Manager
```javascript
class TodoManager {
constructor(db) {
this.db = db;
this.collection = db.collection('todos');
}
// Create
async createTodo(title, description, priority = 'medium') {
const result = await this.collection.add({
title,
description,
priority,
completed: false,
createdAt: new Date(),
updatedAt: new Date()
});
return result._id;
}
// Read (single)
async getTodo(id) {
const result = await this.collection.doc(id).get();
return result.data[0];
}
// Read (multiple)
async getTodos(filter = {}) {
const result = await this.collection
.where(filter)
.orderBy('createdAt', 'desc')
.get();
return result.data;
}
// Update
async updateTodo(id, updates) {
const result = await this.collection
.doc(id)
.update({
...updates,
updatedAt: new Date()
});
return result.updated > 0;
}
// Update status
async toggleComplete(id) {
const todo = await this.getTodo(id);
return this.updateTodo(id, {
completed: !todo.completed,
completedAt: !todo.completed ? new Date() : null
});
}
// Delete
async deleteTodo(id) {
const result = await this.collection.doc(id).remove();
return result.deleted > 0;
}
// Batch operations
async deleteCompleted() {
const result = await this.collection
.where({ completed: true })
.remove();
return result.deleted;
}
}
// Usage
const todoManager = new TodoManager(db);
// Create
const todoId = await todoManager.createTodo(
'Learn CloudBase',
'Study the database API',
'high'
);
// Read
const todo = await todoManager.getTodo(todoId);
const allTodos = await todoManager.getTodos({ completed: false });
// Update
await todoManager.updateTodo(todoId, { priority: 'urgent' });
await todoManager.toggleComplete(todoId);
// Delete
await todoManager.deleteTodo(todoId);
await todoManager.deleteCompleted();
```
## Error Handling Best Practices
```javascript
async function safeCRUD() {
try {
// Create
const result = await db.collection('todos').add({
title: 'New Todo'
});
console.log('Created:', result._id);
} catch (error) {
if (error.code === 'PERMISSION_DENIED') {
console.error('No permission to create document');
} else if (error.code === 'INVALID_PARAM') {
console.error('Invalid data provided');
} else {
console.error('Unexpected error:', error);
}
throw error; // Re-throw for caller to handle
}
}
```
## Transaction Support
For operations requiring atomicity (all succeed or all fail):
```javascript
// Check CloudBase documentation for transaction API
// Transactions ensure data consistency
await db.runTransaction(async transaction => {
// Read
const todo = await transaction.collection('todos').doc('id').get();
// Update based on read
await transaction.collection('todos').doc('id').update({
views: todo.data.views + 1
});
});
```
## Best Practices
1. **Always handle errors**: Wrap operations in try-catch
2. **Validate input**: Check data before database operations
3. **Update timestamps**: Track createdAt and updatedAt
4. **Use transactions**: For related operations that must succeed together
5. **Batch operations**: Use batch updates/deletes when possible
6. **Soft deletes**: Consider soft delete for important data
7. **Index fields**: Index frequently queried/updated fields
8. **Limit updates**: Only update changed fields
9. **Configure security rules**: Use `managePermissions(action="updateResourcePermission")` to set database permissions before operations. See `./security-rules.md` for details. **Note:** Security rule changes take effect after a few minutes due to caching.
10. **Log operations**: Track important data changes
## Important: `_openid` Field Management
**CRITICAL: Never include `_openid` in write operations**
The `_openid` field is **automatically managed by the CloudBase SDK** and should **never** be included in any write operation data:
- **Automatic Assignment**: When you perform create, update, or set operations through the SDK, the system automatically writes the `_openid` field based on the current authenticated user's identity
- **Do Not Include**: The `_openid` field should **not** appear in any `data` parameter for write operations (`.add()`, `.update()`, `.set()`)
- **Error on Manual Setting**: If you manually include or modify `_openid` in write operations, the operation will **fail with an error**
**Correct Usage:**
```javascript
// Correct: Do not include _openid
await db.collection('todos').add({
title: 'My Todo',
completed: false
// _openid is automatically added by SDK
});
// Wrong: Including _openid will cause an error
await db.collection('todos').add({
title: 'My Todo',
completed: false,
_openid: 'some-id' // ERROR: Cannot manually set _openid
});
```
**Note:** The `_openid` field is used internally by CloudBase for user identification and permission control. It is automatically populated from the authenticated user's session and cannot be manually overridden.
references/cloudbase-document-database-web-sdk/geolocation.md
# Geolocation Queries with CloudBase
This document explains how to work with geographic data and perform location-based queries in CloudBase.
## Prerequisites
**⚠️ CRITICAL**: Before performing any geolocation queries, you **MUST** create a geolocation index on the field you're querying. Queries will fail without proper indexing.
## Geographic Data Types
CloudBase supports several geographic data types through `db.Geo`:
```javascript
const db = app.database();
```
### Point (Single Location)
Represents a single geographic coordinate:
```javascript
// Create a Point: longitude, latitude
const point = new db.Geo.Point(116.404, 39.915); // Tiananmen Square coordinates
```
**Note:** Coordinates are in `[longitude, latitude]` format (NOT latitude, longitude).
### LineString (Path/Route)
Represents a path or route:
```javascript
// Create a LineString (array of Points)
const line = new db.Geo.LineString([
new db.Geo.Point(116.404, 39.915), // Start
new db.Geo.Point(116.405, 39.916), // Waypoint
new db.Geo.Point(116.406, 39.917) // End
]);
```
### Polygon (Area)
Represents an enclosed area:
```javascript
// Create a Polygon (array of LineStrings, first is outer boundary)
const polygon = new db.Geo.Polygon([
new db.Geo.LineString([
new db.Geo.Point(116.404, 39.915),
new db.Geo.Point(116.404, 39.916),
new db.Geo.Point(116.405, 39.916),
new db.Geo.Point(116.405, 39.915),
new db.Geo.Point(116.404, 39.915) // Must close the polygon
])
]);
```
**Note:** The first and last points must be identical to close the polygon.
## Storing Geographic Data
Store location data in documents:
```javascript
// Add a user with location
await db.collection('users').add({
name: 'John',
location: new db.Geo.Point(116.404, 39.915),
address: 'Beijing, China'
});
// Add a delivery route
await db.collection('routes').add({
name: 'Route A',
path: new db.Geo.LineString([
new db.Geo.Point(116.404, 39.915),
new db.Geo.Point(116.405, 39.916),
new db.Geo.Point(116.406, 39.917)
])
});
// Add a service area
await db.collection('serviceAreas').add({
name: 'Downtown',
area: new db.Geo.Polygon([
new db.Geo.LineString([
new db.Geo.Point(116.404, 39.915),
new db.Geo.Point(116.404, 39.916),
new db.Geo.Point(116.405, 39.916),
new db.Geo.Point(116.405, 39.915),
new db.Geo.Point(116.404, 39.915)
])
])
});
```
## Geolocation Query Operators
CloudBase provides three main geolocation query operators:
### 1. geoNear (Proximity Search)
Find documents near a specific location, ordered by distance:
```javascript
const _ = db.command;
// Find users within 1000 meters of a location
const result = await db.collection('users').where({
location: _.geoNear({
geometry: new db.Geo.Point(116.404, 39.915), // Center point
maxDistance: 1000, // Maximum distance in meters
minDistance: 0 // Minimum distance in meters
})
}).get();
console.log('Nearby users:', result.data);
```
**Parameters:**
- `geometry` - Center point (Point object)
- `maxDistance` - Maximum distance in meters (optional)
- `minDistance` - Minimum distance in meters (optional, default: 0)
**Important:** Results are automatically sorted by distance (closest first).
### 2. geoWithin (Area Search)
Find documents within a specific geographic area:
```javascript
const _ = db.command;
// Define search area
const searchArea = new db.Geo.Polygon([
new db.Geo.LineString([
new db.Geo.Point(116.404, 39.915),
new db.Geo.Point(116.404, 39.920),
new db.Geo.Point(116.410, 39.920),
new db.Geo.Point(116.410, 39.915),
new db.Geo.Point(116.404, 39.915)
])
]);
// Find users in the area
const result = await db.collection('users').where({
location: _.geoWithin({
geometry: searchArea
})
}).get();
```
**Use Cases:**
- Find all stores in a neighborhood
- Users within a city boundary
- Deliveries in a service area
### 3. geoIntersects (Intersection Search)
Find documents that intersect with a specific geometry:
```javascript
const _ = db.command;
// Define a path/route
const deliveryRoute = new db.Geo.LineString([
new db.Geo.Point(116.404, 39.915),
new db.Geo.Point(116.410, 39.920)
]);
// Find service areas that intersect with the route
const result = await db.collection('serviceAreas').where({
area: _.geoIntersects({
geometry: deliveryRoute
})
}).get();
```
**Use Cases:**
- Routes crossing service areas
- Overlapping geographic regions
- Path planning
## Complete Examples
### Nearby Search App
```javascript
async function findNearbyPlaces(userLat, userLon, radius = 5000, category = null) {
const _ = db.command;
const userLocation = new db.Geo.Point(userLon, userLat);
let whereCondition = {
location: _.geoNear({
geometry: userLocation,
maxDistance: radius
})
};
// Add category filter if specified
if (category) {
whereCondition.category = category;
}
try {
const result = await db.collection('places')
.where(whereCondition)
.limit(20)
.get();
return result.data;
} catch (error) {
console.error('Nearby search failed:', error);
throw error;
}
}
// Usage
const nearbyRestaurants = await findNearbyPlaces(39.915, 116.404, 2000, 'restaurant');
console.log('Found', nearbyRestaurants.length, 'restaurants nearby');
```
### Delivery Zone Checker
```javascript
async function isInDeliveryZone(userLat, userLon, storeId) {
const _ = db.command;
const userLocation = new db.Geo.Point(userLon, userLat);
try {
// Get store's delivery zone
const store = await db.collection('stores')
.doc(storeId)
.get();
if (!store.data || !store.data.deliveryZone) {
return false;
}
// Check if user location is within delivery zone
const result = await db.collection('stores')
.where({
_id: storeId,
deliveryZone: _.geoWithin({
geometry: new db.Geo.Point(userLon, userLat)
})
})
.get();
return result.data.length > 0;
} catch (error) {
console.error('Zone check failed:', error);
return false;
}
}
// Usage
const canDeliver = await isInDeliveryZone(39.915, 116.404, 'store-123');
console.log('Can deliver:', canDeliver);
```
### Distance-based Pricing
```javascript
async function calculateDeliveryFee(userLat, userLon, storeId) {
const _ = db.command;
try {
// Get store location
const store = await db.collection('stores')
.doc(storeId)
.get();
if (!store.data || !store.data.location) {
throw new Error('Store location not found');
}
const userLocation = new db.Geo.Point(userLon, userLat);
// Find the store with distance
const result = await db.collection('stores')
.where({
_id: storeId,
location: _.geoNear({
geometry: userLocation,
maxDistance: 20000 // 20km max
})
})
.get();
if (result.data.length === 0) {
throw new Error('Location outside delivery range');
}
// Calculate fee based on distance
// Note: CloudBase returns distance in results
const distance = result.data[0].distance || 0;
const baseFee = 5;
const perKmFee = 2;
const deliveryFee = baseFee + (distance / 1000) * perKmFee;
return {
distance: Math.round(distance),
fee: Math.round(deliveryFee * 100) / 100
};
} catch (error) {
console.error('Fee calculation failed:', error);
throw error;
}
}
// Usage
const delivery = await calculateDeliveryFee(39.915, 116.404, 'store-123');
console.log(`Distance: ${delivery.distance}m, Fee: $${delivery.fee}`);
```
## Creating Geolocation Indexes
**This is required before querying!**
You need to create an index through the CloudBase console:
1. Go to your CloudBase console
2. Navigate to Database → Your Collection
3. Go to Indexes tab
4. Create a new index:
- Field: `location` (or your geo field name)
- Type: `geo` or `2dsphere`
Without this index, geolocation queries will fail with an error.
## Best Practices
1. **Always Create Indexes**: Geolocation queries require proper indexes
2. **Coordinate Order**: Use [longitude, latitude], not [latitude, longitude]
3. **Close Polygons**: First and last points in polygon must be identical
4. **Distance Units**: All distances are in meters
5. **Limit Results**: Use `.limit()` for large datasets
6. **Error Handling**: Always wrap geo queries in try-catch
7. **Validate Coordinates**: Ensure latitude is -90 to 90, longitude is -180 to 180
8. **Combine Filters**: Mix geo queries with other conditions when needed
## Common Pitfalls
### Wrong Coordinate Order
```javascript
// ❌ WRONG - latitude first
new db.Geo.Point(39.915, 116.404)
// ✅ CORRECT - longitude first
new db.Geo.Point(116.404, 39.915)
```
### Unclosed Polygon
```javascript
// ❌ WRONG - not closed
new db.Geo.LineString([
new db.Geo.Point(116.404, 39.915),
new db.Geo.Point(116.405, 39.916),
new db.Geo.Point(116.405, 39.915)
])
// ✅ CORRECT - first equals last
new db.Geo.LineString([
new db.Geo.Point(116.404, 39.915),
new db.Geo.Point(116.405, 39.916),
new db.Geo.Point(116.405, 39.915),
new db.Geo.Point(116.404, 39.915) // Closes polygon
])
```
### Missing Index
```javascript
// ❌ Will fail without geo index
await db.collection('users').where({
location: _.geoNear({ geometry: point })
}).get()
// ✅ Create index first in console, then query
```
## Performance Considerations
1. **Index Size**: Geolocation indexes can be large; monitor storage
2. **Query Radius**: Smaller radius queries are faster
3. **Result Limits**: Always use `.limit()` to prevent large result sets
4. **Combine Conditions**: Filter by category/type first, then location
5. **Cache Results**: Cache frequently accessed location data
## React Example Component
```javascript
import { useState, useEffect } from 'react';
function NearbyPlaces({ userLat, userLon }) {
const [places, setPlaces] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
loadNearbyPlaces();
}, [userLat, userLon]);
async function loadNearbyPlaces() {
setLoading(true);
try {
const _ = db.command;
const result = await db.collection('places')
.where({
location: _.geoNear({
geometry: new db.Geo.Point(userLon, userLat),
maxDistance: 5000
})
})
.limit(10)
.get();
setPlaces(result.data);
} catch (error) {
console.error('Failed to load places:', error);
} finally {
setLoading(false);
}
}
if (loading) return <div>Loading nearby places...</div>;
return (
<div>
<h2>Nearby Places</h2>
<ul>
{places.map(place => (
<li key={place._id}>
{place.name} - {Math.round(place.distance)}m away
</li>
))}
</ul>
</div>
);
}
```
references/cloudbase-document-database-web-sdk/pagination.md
# Pagination with CloudBase
This document explains how to implement pagination for large datasets in CloudBase document database.
## Basic Pagination Concepts
Pagination allows you to retrieve large datasets in smaller, manageable chunks (pages).
**Key Parameters:**
- `pageSize` - Number of records per page
- `pageNum` - Current page number (1-based)
- `skip()` - Number of records to skip
- `limit()` - Maximum records to return
## Simple Pagination Implementation
### Basic Page-based Query
```javascript
const pageSize = 10; // Records per page
const pageNum = 1; // Current page (1-based)
const result = await db.collection('todos')
.orderBy('createdAt', 'desc')
.skip((pageNum - 1) * pageSize)
.limit(pageSize)
.get();
console.log('Page', pageNum, 'data:', result.data);
```
### Calculation Formula
```javascript
// For page N:
const skip = (pageNum - 1) * pageSize;
const limit = pageSize;
```
## Complete Pagination Function
Here's a reusable pagination function:
```javascript
/**
* Paginate through a collection
* @param {string} collectionName - Name of the collection
* @param {number} page - Page number (1-based)
* @param {number} pageSize - Records per page
* @param {object} whereConditions - Query conditions (optional)
* @param {string} sortField - Field to sort by (optional)
* @param {string} sortDirection - 'asc' or 'desc' (optional)
*/
async function paginateCollection(
collectionName,
page = 1,
pageSize = 10,
whereConditions = {},
sortField = 'createdAt',
sortDirection = 'desc'
) {
const skip = (page - 1) * pageSize;
let query = db.collection(collectionName);
// Apply conditions if provided
if (Object.keys(whereConditions).length > 0) {
query = query.where(whereConditions);
}
// Apply sorting
if (sortField) {
query = query.orderBy(sortField, sortDirection);
}
// Apply pagination
const result = await query
.skip(skip)
.limit(pageSize)
.get();
return {
data: result.data,
page: page,
pageSize: pageSize,
hasMore: result.data.length === pageSize
};
}
// Usage
const pageData = await paginateCollection('todos', 2, 20, { status: 'active' });
console.log('Page 2 data:', pageData);
```
## Getting Total Count
To show "Page X of Y", you need the total count:
```javascript
async function paginateWithCount(collectionName, page, pageSize, whereConditions = {}) {
const skip = (page - 1) * pageSize;
// Get paginated data
const dataQuery = db.collection(collectionName);
const countQuery = db.collection(collectionName);
if (Object.keys(whereConditions).length > 0) {
dataQuery.where(whereConditions);
countQuery.where(whereConditions);
}
// Execute both queries
const [dataResult, countResult] = await Promise.all([
dataQuery
.orderBy('createdAt', 'desc')
.skip(skip)
.limit(pageSize)
.get(),
countQuery.count()
]);
const totalCount = countResult.total;
const totalPages = Math.ceil(totalCount / pageSize);
return {
data: dataResult.data,
pagination: {
currentPage: page,
pageSize: pageSize,
totalCount: totalCount,
totalPages: totalPages,
hasNextPage: page < totalPages,
hasPrevPage: page > 1
}
};
}
// Usage
const result = await paginateWithCount('todos', 1, 10, { status: 'active' });
console.log(`Page ${result.pagination.currentPage} of ${result.pagination.totalPages}`);
console.log(`Total items: ${result.pagination.totalCount}`);
```
## Cursor-based Pagination
For real-time data or better performance, use cursor-based pagination:
```javascript
/**
* Cursor-based pagination using a field value as cursor
*/
async function paginateWithCursor(collectionName, cursor = null, pageSize = 10) {
const _ = db.command;
let query = db.collection(collectionName);
// If cursor exists, query records after cursor
if (cursor) {
query = query.where({
createdAt: _.lt(cursor) // Assuming descending order
});
}
const result = await query
.orderBy('createdAt', 'desc')
.limit(pageSize + 1) // Fetch one extra to check if more exists
.get();
const hasMore = result.data.length > pageSize;
const data = hasMore ? result.data.slice(0, pageSize) : result.data;
const nextCursor = hasMore ? data[data.length - 1].createdAt : null;
return {
data: data,
nextCursor: nextCursor,
hasMore: hasMore
};
}
// Usage - First page
const firstPage = await paginateWithCursor('todos', null, 10);
console.log('First page:', firstPage.data);
// Next page using cursor
const secondPage = await paginateWithCursor('todos', firstPage.nextCursor, 10);
console.log('Second page:', secondPage.data);
```
## React Component Example
Here's how to implement pagination in a React component:
```javascript
import { useState, useEffect } from 'react';
function TodoList() {
const [todos, setTodos] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
const [loading, setLoading] = useState(false);
const pageSize = 10;
useEffect(() => {
loadPage(currentPage);
}, [currentPage]);
async function loadPage(page) {
setLoading(true);
try {
const result = await paginateWithCount('todos', page, pageSize);
setTodos(result.data);
setTotalPages(result.pagination.totalPages);
} catch (error) {
console.error('Failed to load todos:', error);
} finally {
setLoading(false);
}
}
function goToNextPage() {
if (currentPage < totalPages) {
setCurrentPage(currentPage + 1);
}
}
function goToPrevPage() {
if (currentPage > 1) {
setCurrentPage(currentPage - 1);
}
}
return (
<div>
<h2>Todos</h2>
{loading ? (
<p>Loading...</p>
) : (
<>
<ul>
{todos.map(todo => (
<li key={todo._id}>{todo.title}</li>
))}
</ul>
<div className="pagination">
<button
onClick={goToPrevPage}
disabled={currentPage === 1}
>
Previous
</button>
<span>Page {currentPage} of {totalPages}</span>
<button
onClick={goToNextPage}
disabled={currentPage === totalPages}
>
Next
</button>
</div>
</>
)}
</div>
);
}
```
## Infinite Scroll Pattern
For infinite scroll UI:
```javascript
function useInfiniteScroll(collectionName, pageSize = 20) {
const [items, setItems] = useState([]);
const [cursor, setCursor] = useState(null);
const [hasMore, setHasMore] = useState(true);
const [loading, setLoading] = useState(false);
async function loadMore() {
if (loading || !hasMore) return;
setLoading(true);
try {
const result = await paginateWithCursor(collectionName, cursor, pageSize);
setItems(prev => [...prev, ...result.data]);
setCursor(result.nextCursor);
setHasMore(result.hasMore);
} catch (error) {
console.error('Failed to load more:', error);
} finally {
setLoading(false);
}
}
return { items, loadMore, hasMore, loading };
}
```
## Performance Considerations
1. **Index Sort Fields**: Ensure fields used in `orderBy()` are indexed
2. **Reasonable Page Size**: 10-50 items per page is typical
3. **Count Caching**: Cache total count if it doesn't change often
4. **Skip Limits**: Very large `skip()` values can be slow; consider cursor-based pagination
5. **Parallel Queries**: Use `Promise.all()` for count and data queries
## Best Practices
1. Always specify an `orderBy()` for consistent pagination
2. Use cursor-based pagination for real-time feeds
3. Cache page results when appropriate
4. Show loading states during page transitions
5. Handle empty results gracefully
6. Validate page numbers (must be >= 1)
7. Consider using URL query parameters for page state
8. Implement error handling and retry logic
references/cloudbase-document-database-web-sdk/realtime.md
# Realtime Database with CloudBase
CloudBase document database supports **real-time push** functionality that allows applications to listen to all update events for documents in a specified collection that match query conditions. When monitored documents undergo any changes (such as addition, modification, deletion), the client receives notifications in real-time, enabling real-time data synchronization and updates.
## Core Features
### Real-time Data Change Monitoring
- Listen to all change events for documents in a collection that match query conditions
- Support for all types of changes: addition, modification, deletion
- Automatically push change snapshots to clients
### Use Cases
- Chat applications
- Real-time collaborative editing
- Live interactive features
- Real-time dashboards
- Multiplayer game state synchronization
## Basic Usage
### 1. Establish Monitoring
Use the `.watch()` method on the collection reference to establish monitoring:
```javascript
// db is the database instance from cloudbase js client sdk
const watcher = db.collection("todos") // Specify collection
.where({ // Specify query conditions
status: 'active',
priority: _.in(['high', 'medium'])
})
.watch({
onChange: function(snapshot) { // Data change callback
console.log("Received data snapshot", snapshot);
// Update your UI or process data here
handleDataChange(snapshot);
},
onError: function(err) { // Error handling callback
console.error("Monitoring closed due to error", err);
// Handle errors, such as attempting to re-establish connection
handleWatchError(err);
}
});
```
### 2. Close Monitoring
When you no longer need to monitor data changes, call the `watcher.close()` method:
```javascript
// Close monitoring when page or component unmounts
watcher.close();
```
## API Details
### watch(options)
Create a real-time data listener that returns a `watcher` object.
**Parameters:**
- `options.onChange` (Function): Callback function when data changes
- `options.onError` (Function): Callback function when monitoring encounters an error
**onChange callback parameter snapshot:**
```javascript
{
docChanges: [
{
id: 'document-id',
dataType: 'init' | 'update' | 'delete' | 'add',
queueType: 'init' | 'update' | 'delete' | 'enqueue' | 'dequeue',
doc: { // Document content after change
// Document fields
}
},
// More changes...
],
docs: [ // All documents in the query result set
// Document content...
]
}
```
**Change types in onChange callback:**
- `init`: Initialization, sends all data when first establishing connection
- `update`: Document content update
- `add`: New document added
- `delete`: Document deleted
**Watcher object methods:**
- `watcher.close()`: Close monitoring and release resources
## Best Practices
### 1. Specific Query Conditions
Set as specific query conditions as possible in the `.where()` method to monitor only the data changes you truly need:
```javascript
// Recommended: Specific query conditions
db.collection("messages")
.where({
chatRoomId: currentChatRoomId,
isDeleted: false
})
.watch({...});
// Not recommended: Monitoring entire collection
db.collection("messages").watch({...});
```
### 2. Close Monitoring in a Timely Manner
Be sure to close monitoring when pages or components unmount to prevent memory leaks:
**React Component Example:**
```javascript
import { useEffect } from 'react';
function ChatRoom({ roomId }) {
useEffect(() => {
const watcher = db.collection("messages")
.where({ chatRoomId: roomId })
.watch({
onChange: handleNewMessages,
onError: handleError
});
// Close monitoring when component unmounts
return () => {
watcher.close();
};
}, [roomId]);
}
```references/cloudbase-document-database-web-sdk/security-rules.md
# CloudBase NoSQL Database Security Rules
This document covers how to configure security rules for CloudBase NoSQL database collections to control read/write permissions.
## Overview
**Important:** To control database permissions, you **MUST** use `managePermissions(action="updateResourcePermission")` to configure security rules. Security rule changes take effect after a few minutes due to caching.
**General Rule:** In most cases, use **simple permissions** (READONLY, PRIVATE, ADMINWRITE, ADMINONLY). Only use CUSTOM rules when you need fine-grained control.
**Scope note:** The detailed semantics in this document apply only to CloudBase **NoSQL database collections** with `resourceType: "noSqlDatabase"`. Examples such as `doc._openid`, `auth.openid`, query-condition subset validation, and `create` / `update` / `delete` JSON rule templates are **not** generic rules for `function`, `storage`, or `sqlDatabase` resources.
**Official references:**
- General security rules overview: `https://cloud.tencent.com/document/product/876/41802`
- NoSQL database security rules: `https://docs.cloudbase.net/database/security-rules`
- Cloud function security rules: `https://docs.cloudbase.net/cloud-function/security-rules`
- Storage security rules: `https://docs.cloudbase.net/storage/security-rules`
### Critical Understanding: Query Condition Requirements
**Security rules are validation-based, NOT filter-based.**
For query or update operations, the **input query conditions must be a subset of the security rules**. The system does **not** actually fetch data from the database. Instead, it validates whether the input query conditions form a subset of the security rules. If the query conditions are not a subset of the rules, it indicates an attempt to access data without permission, and the operation will be **directly rejected**.
**Example:**
- If you define a read/write rule: `auth.openid == doc._openid`
- This means the query condition's `_openid` must equal the current user's `openid` (provided by the system-assigned, non-tamperable `auth.openid`)
- If the query condition doesn't include this constraint, it indicates an attempt to access records where `_openid` is not equal to the user's own, which will be rejected by the backend
**Key Points:**
- Security rules **validate** queries, they don't **filter** results
- The system performs **rule matching** before any database access
- Query conditions must match or be more restrictive than the security rule
- Missing required conditions in queries will result in permission denied errors
- For **create** operations, the system validates the **written data** against the security rules, not query conditions
## Data Permission Management System
CloudBase provides a multi-layered data permission management mechanism that ensures data security while meeting different business scenario permission control requirements.
### Permission Management Hierarchy
CloudBase data permission management includes two levels:
| Permission Type | Control Granularity | Applicable Scenarios | Configuration Complexity |
|----------------|---------------------|----------------------|--------------------------|
| **Basic Permission Control** | Collection level | Simple permission needs | Low |
| **Security Rules** | Document level | Complex business logic | High |
### Basic Permission Control
**Configuration Method:**
Configure permissions for each collection in the [CloudBase Platform](https://tcb.cloud.tencent.com/dev) collection management page.
**Permission Options:**
| Permission Type | Applicable Scenarios | Usage Recommendation |
|----------------|----------------------|----------------------|
| **Read all data, modify own data** | Public content, such as articles, products | Suitable for content display applications |
| **Read and modify own data** | Private data, such as user profiles | Suitable for personal information management |
| **Read all data, cannot modify** | Configuration data, such as system settings | Suitable for read-only configuration and reference data |
| **No permission** | Sensitive data, such as financial information | Suitable for sensitive data requiring server-side processing |
### Security Rules (CUSTOM)
**Function Overview:**
Security rules provide more flexible, extensible, and fine-grained permission control capabilities, supporting dynamic permission judgment based on document content.
**Core Features:**
- **Document-level control**: Can decide access permissions based on specific document content
- **Expression-driven**: Uses programming-like expressions to define permission logic
- **Dynamic permissions**: Supports dynamic permission judgment based on user identity, time, and data content
- **Client-only restriction**: Only restricts client user access, does not affect server-side (cloud function) operations
**Configuration Entry:**
Configure security rules in the [CloudBase Platform/Database](https://tcb.cloud.tencent.com/dev#/db/doc/model) collection management page.
## Permission Categories
CloudBase provides two types of permissions:
### 1. Simple Permissions (Recommended for Most Cases)
These are pre-configured permission templates that cover most common scenarios:
- **READONLY**: All users can read, only creator and admin can write
- **PRIVATE**: Only creator and admin can read/write
- **ADMINWRITE**: All users can read, only admin can write
- **ADMINONLY**: Only admin can read/write
### 2. Custom Security Rules (CUSTOM)
Use CUSTOM when you need fine-grained control based on document data, user identity, or complex conditions.
## Configuring Security Rules
### Using MCP Tool `managePermissions`
**Important:** When developing applications that need permission control, you **MUST** call `managePermissions(action="updateResourcePermission")` to configure database security rules. Do not assume permissions are already configured.
Compatibility note:
- Canonical plugin name: `permissions`
- Legacy plugin aliases `security-rule`, `security-rules`, `secret-rule`, `secret-rules`, and `access-control` still resolve to the `permissions` plugin
- Legacy tools `readSecurityRule` and `writeSecurityRule` are removed; use `queryPermissions` and `managePermissions`
**Scope reminder:** The examples below are for `resourceType: "noSqlDatabase"` only. Do not reuse NoSQL-only expressions such as `doc._openid`, `auth.openid`, query-subset validation, or `create` / `update` / `delete` rule templates as generic guidance for `function`, `storage`, or `sqlDatabase` permissions.
**Basic Usage:**
```javascript
// Example: Set simple permission (PRIVATE)
await managePermissions({
action: "updateResourcePermission",
resourceType: "noSqlDatabase",
resourceId: "collectionName", // Collection name
permission: "PRIVATE",
// securityRule parameter not needed for simple permissions
});
```
**Cache Notice:** After configuring security rules, changes take effect after a few minutes (typically 2-5 minutes) due to caching. Wait a few minutes before testing the new rules.
### Simple Permission Examples
```javascript
// Example 1: Public read, creator-only write
await managePermissions({
action: "updateResourcePermission",
resourceType: "noSqlDatabase",
resourceId: "posts",
permission: "READONLY"
});
// Example 2: Private collection (only creator and admin)
await managePermissions({
action: "updateResourcePermission",
resourceType: "noSqlDatabase",
resourceId: "userSettings",
permission: "PRIVATE"
});
// Example 3: Public read, admin-only write
await managePermissions({
action: "updateResourcePermission",
resourceType: "noSqlDatabase",
resourceId: "announcements",
permission: "ADMINWRITE"
});
// Example 4: Admin-only access
await managePermissions({
action: "updateResourcePermission",
resourceType: "noSqlDatabase",
resourceId: "adminLogs",
permission: "ADMINONLY"
});
```
## Custom Security Rules (CUSTOM)
### When to Use CUSTOM
Use CUSTOM rules when you need:
- User-specific data access (e.g., users can only read/write their own documents)
- Complex conditions based on document fields
- Time-based access control
- Role-based permissions
### Custom Rule Format
Custom security rules use JSON structure with operation types as keys and conditions as values:
```json
{
"read": "<condition>",
"write": "<condition>",
"create": "<condition>",
"update": "<condition>",
"delete": "<condition>"
}
```
**Operation Types:**
| Operation Type | Description | Default Value | Example Scenarios |
|----------------|-------------|---------------|-------------------|
| **read** | Read documents | `false` | Query, get documents |
| **write** | Write documents (general) | `false` | Default rule when specific write operations are not specified |
| **create** | Create documents | Inherits `write` | Add new data |
| **update** | Update documents | Inherits `write` | Modify existing data |
| **delete** | Delete documents | Inherits `write` | Delete data |
> 💡 Note: If specific write operation rules (create/update/delete) are not specified, the `write` rule will be automatically used.
**Condition Values:**
- `true` or `false`: Simple boolean permission
- Expression string: JavaScript-like expression that evaluates to true/false
### Predefined Variables (Global Variables)
Custom rules can use these predefined variables:
| Variable | Type | Description | Example |
|----------|------|-------------|---------|
| `auth` | Object | User authentication info (null if not logged in) | `auth.openid`, `auth.uid` |
| `doc` | Object | Document data or query conditions | `doc.userId`, `doc.status` |
| `request` | Object | Request information | `request.data` |
| `now` | Number | Current timestamp in milliseconds | `now > doc.expireTime` |
**User Identity Information (auth):**
| Field | Type | Description | Applicable Scenarios |
|-------|------|-------------|---------------------|
| **openid** | String | WeChat user OpenID | WeChat Mini Program login |
| **uid** | String | User unique ID | Web login |
| **loginType** | String | Login method | Distinguish different login channels |
**LoginType Values:**
- `WECHAT_PUBLIC`: WeChat Official Account
- `WECHAT_OPEN`: WeChat Open Platform
- `ANONYMOUS`: Anonymous login (disabled by default for new environments)
- `EMAIL`: Email login
- `CUSTOM`: Custom login
**Request Object:**
- `request.data`: Data object passed in the request (only available for create/update operations)
**Doc Object:**
- Contains all fields of the current document being accessed
- For queries, `doc` represents the query conditions
**Important: `_openid` Field Management**
The `_openid` field is **automatically managed by the CloudBase SDK** and should **never** be included in write operations:
- **Automatic Assignment**: When performing create, update, or set operations through the SDK, the system automatically writes the `_openid` field based on the current authenticated user's identity
- **Do Not Include**: The `_openid` field should **not** appear in any `data` parameter for write operations (`.add()`, `.update()`, `.set()`)
- **Error on Manual Setting**: If you manually include or modify `_openid` in write operations, the operation will **fail with an error**
- **Security Rules Usage**: In security rules, you can reference `doc._openid` to check the document's owner, but you cannot modify it through write operations
**Example:**
```javascript
// Correct: Do not include _openid in write operations
await db.collection('todos').add({
title: 'My Todo',
completed: false
// _openid is automatically added by SDK
});
// Wrong: Including _openid will cause an error
await db.collection('todos').add({
title: 'My Todo',
_openid: 'some-id' // ERROR: Cannot manually set _openid
});
// Correct: Use _openid in security rules for permission checks
{
"read": "doc._openid == auth.openid",
"write": "doc._openid == auth.openid"
}
```
### Custom Rule Examples
**Example 1: User can only read/write their own documents**
```javascript
await managePermissions({
action: "updateResourcePermission",
resourceType: "noSqlDatabase",
resourceId: "userTodos",
permission: "CUSTOM",
securityRule: JSON.stringify({
"read": "auth.uid == doc.user_id",
"write": "auth.uid == doc.user_id"
})
});
```
**Example 2: Public read, authenticated users can create, only owner can update/delete**
```javascript
await managePermissions({
action: "updateResourcePermission",
resourceType: "noSqlDatabase",
resourceId: "publicPosts",
permission: "CUSTOM",
securityRule: JSON.stringify({
"read": true,
"create": "auth != null",
"update": "auth.uid == doc.author_id",
"delete": "auth.uid == doc.author_id"
})
});
```
> Warning: This owner-only pattern is not the best fit for CMS article collections that need app-level admin override. For article collections with admin override, prefer a `CUSTOM` rule that combines `get('database.user_roles.' + auth.uid).role == 'admin'` with `doc.authorId == auth.uid`, and keep frontend writes on `.doc(id).update()` / `.doc(id).remove()`.
**Example 2A: Keep owner-only CUSTOM rule and switch the client write path to `where(...)`**
```javascript
await managePermissions({
action: "updateResourcePermission",
resourceType: "noSqlDatabase",
resourceId: "publicPosts",
permission: "CUSTOM",
securityRule: JSON.stringify({
"read": true,
"create": "auth != null",
"update": "doc.author_id == auth.uid",
"delete": "doc.author_id == auth.uid"
})
});
```
And update through an explicit owner subset:
```javascript
await db.collection('publicPosts')
.where({
_id: postId,
author_id: '{openid}'
})
.update({ title: 'Updated Title' });
```
**Example 3: Prevent price modification on update**
```javascript
await managePermissions({
action: "updateResourcePermission",
resourceType: "noSqlDatabase",
resourceId: "orders",
permission: "CUSTOM",
securityRule: JSON.stringify({
"read": "auth.uid == doc.user_id",
"create": "auth != null",
"update": "auth.uid == doc.user_id && (doc.price == request.data.price || request.data.price == undefined)",
"delete": false
})
});
```
**Example 4: Admin-only delete, users can read/write their own**
```javascript
await managePermissions({
action: "updateResourcePermission",
resourceType: "noSqlDatabase",
resourceId: "userData",
permission: "CUSTOM",
securityRule: JSON.stringify({
"read": "auth.uid == doc.user_id",
"write": "auth.uid == doc.user_id",
"delete": false // Only admin can delete (admin bypasses rules)
})
});
```
### Expression Syntax
**Expression Length Limit:** Expressions are pseudo-code statements. When configuring, expressions cannot be too long. A single expression is limited to **1024 characters**.
Custom rules support JavaScript-like expressions:
**Supported Operators:**
| Operator | Description | Example | Example Explanation (Collection Query) |
|----------|-------------|---------|----------------------------------------|
| **==** | Equal to | `auth.uid == 'zzz'` | User's uid is zzz |
| **!=** | Not equal to | `auth.uid != 'zzz'` | User's uid is not zzz |
| **>** | Greater than | `doc.age > 10` | Query condition's age property is greater than 10 |
| **>=** | Greater than or equal | `doc.age >= 10` | Query condition's age property is greater than or equal to 10 |
| **<** | Less than | `doc.age < 10` | Query condition's age property is less than 10 |
| **<=** | Less than or equal | `doc.age <= 10` | Query condition's age property is less than or equal to 10 |
| **in** | Exists in collection | `auth.uid in ['zzz','aaa']` | User's uid is one of ['zzz','aaa'] |
| **!(xx in [])** | Does not exist in collection | `!(auth.uid in ['zzz','aaa'])` | User's uid is not any of ['zzz','aaa'] |
| **&&** | Logical AND | `auth.uid == 'zzz' && doc.age > 10` | User's uid is zzz AND query condition's age property is greater than 10 |
| **\|\|** | Logical OR | `auth.uid == 'zzz' \|\| doc.age > 10` | User's uid is zzz OR query condition's age property is greater than 10 |
| **.** | Object element access | `auth.uid` | User's uid |
| **[]** | Array access operator | `get('database.collection_a.user')[auth.uid] == 'zzz'` | In collection_a, document with id 'user', key is user uid, property value is zzz |
### Supported Database Commands
Security rules support the following database commands:
**Logic Commands:**
| Command | Description |
|---------|-------------|
| `or` | `\|\|` Logical OR |
| `and` | `&&` Logical AND |
**Query Commands:**
| Command | Description |
|---------|-------------|
| `eq` | `==` |
| `ne` / `neq` | `!=` |
| `gt` | `>` |
| `gte` | `>=` |
| `lt` | `<` |
| `lte` | `<=` |
| `in` | `in` |
| `nin` | `!(in [])` |
**Update Commands:**
| Command | Description |
|---------|-------------|
| `set` | Overwrite write, `{key: set(object)}` |
| `remove` | Delete field, `{key: remove()}` |
**Example Expressions:**
```javascript
// User ID matches document owner
"auth.uid == doc.user_id"
// User is authenticated
"auth != null"
// User ID in allowed list
"auth.uid in ['admin1', 'admin2']"
// Complex condition
"auth.uid == doc.user_id && doc.status == 'active'"
// Price not modified or undefined
"doc.price == request.data.price || request.data.price == undefined"
```
### Built-in Functions
#### get() Function: Cross-Document Permission Verification
**Function Description:**
The `get()` function allows accessing other document data during permission verification, enabling complex cross-document permission control.
**Syntax:** `get('database.collectionName.documentId')`
**Usage Examples:**
**Role-based Permission Control:**
```json
{
"read": "get('database.user_roles.' + auth.uid).role in ['admin', 'editor']",
"write": "get('database.user_roles.' + auth.uid).role == 'admin'"
}
```
**Important syntax note:** put the field access **after** `get(...)`.
```json
{
"write": "get('database.user_roles.' + auth.uid).role == 'admin'"
}
```
Do **not** write:
```json
{
"write": "get('database.user_roles.' + auth.uid + '.role') == 'admin'"
}
```
Do **not** use JS template-literal placeholders inside the rule string either:
```json
{
"write": "get('database.user_roles.${auth.uid}').role == 'admin'"
}
```
Security rules are expression strings, so use concatenation:
```json
{
"write": "get('database.user_roles.' + auth.uid).role == 'admin'"
}
```
**Admin-or-owner control:**
```json
{
"update": "get('database.users.' + auth.uid).role == 'admin' || doc.authorId == auth.uid",
"delete": "get('database.users.' + auth.uid).role == 'admin' || doc.authorId == auth.uid"
}
```
If this collection only needs simple owner-only writes, `READONLY` may be enough. But if the product requirement is “admin users in the app can edit/delete all articles while editors only own their own articles”, use a `CUSTOM` rule such as:
```json
{
"read": "auth.uid != null",
"create": "auth.uid != null",
"update": "auth.uid != null && (get('database.user_roles.' + auth.uid).role == 'admin' || doc.authorId == auth.uid)",
"delete": "auth.uid != null && (get('database.user_roles.' + auth.uid).role == 'admin' || doc.authorId == auth.uid)"
}
```
For that CMS pattern, `.doc(id).update()` / `.doc(id).remove()` is a validated path, as long as article documents really store `authorId` and `user_roles` documents are keyed by `uid`.
**Associated Data Permissions:**
```json
{
"read": "auth.uid == get('database.projects.' + doc.projectId).owner"
}
```
**Usage Limitations:**
> **Important:** When using the `get()` function, note the following limitations:
- **Variable restrictions in get parameters**: Variables `doc` that exist in get parameters must appear in query conditions in `==` or `in` format. If using `in` format, only `in` with a single value is allowed, i.e., `doc.shopId in array, array.length == 1`
- Maximum 3 `get` functions per expression
- Maximum access to 10 different documents
- Maximum nesting depth of 2 levels (i.e., `get(get(path))`)
- Generates additional database read operations (billed)
**Billing Notes:**
> **Important:** Security rules themselves are not charged, but additional data access by security rules will be counted in billing:
- **get() function**: Each `get()` produces additional data access
- **Document ID queries for all write operations**: All write operations for document ID queries produce one data access
- **Variable usage**: When not using variables, each `get()` produces one read operation. When using variables, each `get()` produces one read operation for each variable value. For example: rule `get(\`database.collection.${doc._id}\`).test`, when querying `_.or([{_id:1},{_id:2},{_id:3},{_id:4},{_id:5}])` will produce 5 reads. The system will cache reads for the same doc and field.
**Important:** Using `get()` or accessing `doc` counts toward database quota as it reads from the service.
## Best Practices
### 1. Rule Design Principles
- **Principle of Least Privilege:** Only grant necessary permissions
- **Clarity:** Rule expressions should be clear and understandable
- **Performance Considerations:** Avoid excessive `get()` function calls
### 2. General Best Practices
1. **Prefer Simple Permissions:** Use READONLY, PRIVATE, ADMINWRITE, or ADMINONLY for most cases
2. **Use CUSTOM Sparingly:** Only when you need fine-grained control
3. **Test After Configuration:** Wait a few minutes for cache to clear before testing
4. **Avoid Complex Expressions:** Keep custom rules simple and readable
5. **Document Your Rules:** Comment complex rules for future maintenance
6. **Handle Errors:** Always handle permission denied errors in your application code
### 3. Debugging Tips
- Start with simple rules and gradually increase complexity
- Fully test various scenarios in the development environment
- Pay attention to permission error messages in the console
- Reasonably use logs to record permission verification processes
**CRITICAL ERROR: Using ADMINWRITE with Frontend SDK**
| Error Scenario | Symptoms | Root Cause | Correct Approach |
|---------------|----------|------------|------------------|
| Using `ADMINWRITE` for cart/order collections | `.add()` or `.update()` fails<br>Keeps loading or permission error | "ADMIN" in `ADMINWRITE` refers to cloud function environment<br>Frontend SDK has no admin privileges | Use `CUSTOM` rules<br>`{"read": "auth.uid != null", "write": "auth.uid != null"}` |
| Using `PRIVATE` for product collections | Product list disappears after login | `PRIVATE` only allows creator and admin to read<br>Regular users have no permission | Use `READONLY`<br>All users can read, creator and admin can write |
**Key Understanding**:
- `ADMINWRITE` = Cloud functions have write access, Frontend SDK **can only read**
- `CUSTOM` = Configurable read/write permissions for Frontend SDK
- `READONLY` = All users (including anonymous) can read, creator and admin can write. Note: although `READONLY` permits anonymous reads at the ACL level, anonymous login is disabled by default for new environments — callers still need an active login method to obtain a session.
### Role-Based Access Limitations
Security rules work **per request** and cannot selectively grant access to “some” users while denying others unless those users belong to the same ownership context. Typical examples that fail:
- Allowing customer service reps to view **all** orders while normal users only see their own
- Granting merchandisers permission to edit every product while other employees cannot
For these scenarios:
1. Keep frontend collections locked down with `CUSTOM` rules that restrict users to their own data
2. Build **management console APIs** with **cloud functions** (CloudBase Run or functions)
3. Cloud functions bypass security rules, so they can read/write all data safely based on backend authentication/authorization
> TL;DR: **Frontend SDK permissions ≠ backend role management.** If a role needs global data access (e.g., admin dashboard), implement it via cloud functions and never expose that data directly through frontend security rules.
## Query Restrictions and Optimization
### Valid Queries
In actual use, queries are mainly divided into two types: **document ID queries** and **collection queries**.
- **Document ID queries**: Specify a single document ID through `doc` conditions
- **Collection queries**: Can be queries through `where` conditions or aggregate search `match` restriction conditions. For aggregate search, only the first `match` restriction condition is matched.
### Query Condition Requirements
**Critical: Rule Matching Mechanism**
For query or update operations, the input query conditions **must be a subset** of the security rules. The system does **not** actually fetch data from the database. Instead, it validates whether the input query conditions form a subset of the security rules. If the query conditions are not a subset of the rules, it indicates an attempt to access data without permission, and the operation will be **directly rejected**.
**Key Points:**
- Security rules **validate** queries, they don't **filter** results
- Query conditions must match or be more restrictive than the security rule
- Missing required conditions in queries will result in permission denied errors
- The system performs **rule matching** before any database access
**Critical implication for document-ID writes:**
- `.doc(id).update(...)` and `.doc(id).remove()` only provide `_id` as the write condition
- A rule that depends on another field such as `doc.authorId` or `doc.status` cannot be validated from that request alone
- For document-ID writes, prefer one of these paths:
- use a simple permission such as `READONLY` when it already matches “public read, creator/admin write”, or
- keep `doc.field`-based CUSTOM rules and switch to `where(...)` writes that explicitly include the required owner/status fields in the query
**Operation Types Affected:**
The following operation types are subject to rule matching validation:
- **read**: Query conditions must be a subset of the read rule
- **write**: Query conditions must be a subset of the write rule (general write operations)
- **update**: Query conditions must be a subset of the update rule (or write rule if update is not specified)
- **delete**: Query conditions must be a subset of the delete rule (or write rule if delete is not specified)
**Special Case - Create Operations:**
For **create** operations, the system validates whether the **data being written** complies with the security rules, rather than validating query conditions. The written data must satisfy the create rule (or write rule if create is not specified).
**Collection Query Examples:**
```javascript
// Security rule configuration for collection 'test'
// Restricts queries to only records where age > 10
{
"read": "doc.age > 10"
}
// Complies with security rule
// Query condition (age > 15) is a subset of the rule (age > 10)
const res = await db.collection('test').where({
age: _.gt(15)
}).get()
// Does not comply with security rule
// Query condition (age > 8) is NOT a subset of the rule (age > 10)
// This would attempt to access records with age between 8-10, which violates the rule
const res = await db.collection('test').where({
age: _.gt(8)
}).get()
// Complies with security rule (aggregate query)
let res = await db.collection('test').aggregate().match({
age: _.gt(10) // Matches the rule exactly
}).project({
age: 1
}).end()
// Does not comply with security rule (aggregate query)
let res = await db.collection('test').aggregate().match({
age: _.gt(8) // Not a subset of age > 10
}).project({
age: 1
}).end()
```
**Create Operation Example:**
```javascript
// Security rule configuration
{
"create": "auth.uid != null && request.data.userId == auth.uid"
}
// Complies with security rule
// Written data includes userId matching current user's uid
// Note: _openid is automatically added by SDK, do not include it
await db.collection('userPosts').add({
userId: currentUser.uid, // Matches auth.uid
title: "My Post",
content: "Post content"
// _openid is automatically populated by SDK based on authenticated user
})
// Does not comply with security rule
// Written data has userId that doesn't match current user's uid
await db.collection('userPosts').add({
userId: "other-user-id", // Does not match auth.uid
title: "My Post",
content: "Post content"
})
// Also wrong: Cannot manually set _openid
await db.collection('userPosts').add({
userId: currentUser.uid,
_openid: "some-id", // ERROR: Cannot manually set _openid
title: "My Post"
})
```
### Template Variables for Automatic Replacement
In query conditions, if the key is `_openid` and the value is `{openid}`, or if the key is `uid` and the value is `{uid}`, the server will automatically replace the value with the actual user's openid or uid.
**Important:** Under basic permission control, query conditions don't need to pass `_openid`, but security rules require explicit passing to ensure query conditions comply with security rules. All query conditions must include openid/uid. You can use template variables `{openid}` or `{uid}` to refer to the current logged-in user's openid or uid.
### Document ID Query Transformation (Migration Required)
**Important:** Security rules require query conditions to be a subset of the rules (all restrictions on `doc` must appear in query conditions and query condition restrictions must be a subset of rule restrictions). This differs from the implicit default behavior of old permission configurations, so developers need to pay attention to the following upgrade/compatibility handling.
**Why Transformation is Needed:**
Since `doc()` operations (doc.get, doc.set, etc.) only specify `_id`, their query conditions only include `{_id: "xxx"}`, which in most cases will not satisfy the subset requirement of security rules (unless reading under `"read": true` or writing under `"write": true`). Therefore, they need to be converted to equivalent forms where query conditions include security rules or their subsets.
**Operation Types Affected:**
- **read, update, delete**: If security rules contain `doc` restrictions, the system will first read the document data from the database once, then judge whether it complies with security rules.
- **create**: Will validate whether the written data complies with security rule restrictions.
- **update**: Only validates existing document data in the database, does not validate written data; does not guarantee atomicity of this operation.
**Transformation Examples:**
```javascript
// Security rule configuration
{
"read": "doc._openid == auth.openid"
}
// Document with id='ccc' has data: { age: 12, _openid: 'user123' }
// Does not comply with security rules (does not meet subset requirement)
let queryRes = db.collection('collection_a').doc('ccc').get()
// Complies with security rules (rewritten as where query)
let queryRes = db.collection('collection_a')
.where({
_id: "ccc",
_openid: "{openid}" // Template variable automatically replaced
})
.get()
// For WeChat Mini Program (using openid)
db.collection('posts')
.where({
_id: 'postId',
_openid: '{openid}' // Auto-replaced with current user's openid
})
.get();
// For Web (using uid)
db.collection('posts')
.where({
_id: 'postId',
uid: '{uid}' // Auto-replaced with current user's uid
})
.get();
```
## Common Patterns
### Pattern 1: User-Owned Data (Basic Permission Mapping)
**All users can read, only creator and admin can write:**
For WeChat login:
```json
{
"read": true,
"write": "doc._openid == auth.openid"
}
```
For non-WeChat login (Web):
```json
{
"read": true,
"write": "doc._openid == auth.uid"
}
```
**Only creator and admin can read/write:**
For WeChat login:
```json
{
"read": "doc._openid == auth.openid",
"write": "doc._openid == auth.openid"
}
```
For non-WeChat login (Web):
```json
{
"read": "doc._openid == auth.uid",
"write": "doc._openid == auth.uid"
}
```
**All users can read, only admin can write:**
```json
{
"read": true,
"write": false
}
```
**Only admin can read/write:**
```json
{
"read": false,
"write": false
}
```
### Pattern 2: Public Read, Authenticated Write
```json
{
"read": true,
"write": "auth != null"
}
```
### Pattern 3: Public Read, Owner Write
```json
{
"read": true,
"create": "auth != null",
"update": "auth.uid == doc.owner_id",
"delete": "auth.uid == doc.owner_id"
}
```
### Pattern 4: Immutable After Creation
```json
{
"read": true,
"create": "auth != null",
"update": false,
"delete": false
}
```
### Pattern 5: Complex Business Logic
**Article Publishing System:**
```json
{
"read": "doc.published == true || doc.author == auth.uid",
"create": true,
"update": "doc.author == auth.uid",
"delete": "doc.author == auth.uid && doc.published == false"
}
```
**Collaborative Document System:**
```json
{
"read": "auth.uid in doc.readers || auth.uid in doc.editors || doc.owner == auth.uid",
"write": "auth.uid in doc.editors || doc.owner == auth.uid"
}
```
### Pattern 6: Time-Based Control
**Time-Limited Activity Data:**
```json
{
"read": "now >= doc.startTime && now <= doc.endTime",
"write": "doc.owner == auth.uid && now <= doc.endTime"
}
```
### Pattern 7: Data Owner Pattern
```json
{
"read": "doc._openid == auth.openid",
"write": "doc._openid == auth.openid"
}
```
## Propagation And Verification
- Security rule updates usually take effect within a few seconds to about 30 seconds. Retry soon; do not blind-wait several minutes before re-checking the rule itself.
- Right after changing a collection to `CUSTOM`, do not assume the first `DATABASE_PERMISSION_DENIED` means the expression is still wrong.
- Keep the same login state, wait briefly, and retry the exact same write before changing the rule again.
- For Web SDK writes, do not treat "no thrown exception" as success.
- Check the returned payload:
- `update()` should have `updated > 0`
- `remove()` should have `deleted > 0`
- if `result.code` or `result.message` exists, especially `DATABASE_PERMISSION_DENIED`, treat it as a real backend rejection
### Pattern 8: Status-Based Permissions
```json
{
"read": "doc.status == 'published' || doc.author == auth.uid",
"update": "doc.author == auth.uid && doc.status != 'locked'"
}
```
## Error Handling
When database operations fail due to permissions:
```javascript
try {
const result = await db.collection('protected').get();
} catch (error) {
if (error.code === 'PERMISSION_DENIED') {
console.error('Permission denied: User does not have access');
// Handle permission error
}
}
```
## Role-Based Access Control Implementation
You can use CloudBase data models and custom security rules to implement role-based access control in your application.
### Example: Collaborative Writing Application
**Business Requirements:**
- Each story has one owner; stories can be shared with writers
- Writers have all access permissions that commenters have, plus can edit story content
- Owners can edit any part of the story and control other users' access permissions
- Regular users can only view stories and comments, write their own comments, but cannot edit stories
### Data Structure
**stories Collection:**
Each story document:
```json
{
"id": "storyid",
"title": "A Great Story",
"content": "Once upon a time ..."
}
```
**roles Collection:**
Each role document tracks user roles for a story:
```json
{
"id": "storyid",
"roles": {
"alice": "owner",
"bob": "writer",
"david": "writer"
// ...
}
}
```
**comments Collection:**
Each comment document:
```json
{
"id": "commentId",
"storyid": "storyid",
"user": "alice",
"content": "I think this is a great story!"
}
```
### Security Rules Configuration
**roles Collection Rules:**
Owners can change roles, allow story writers to read roles:
```json
{
"write": "doc.roles[auth.uid] === 'owner'",
"read": "doc.roles[auth.uid] in ['owner', 'writer']"
}
```
**stories Collection Rules:**
Owners and story writers can change stories, others can read stories:
```json
{
"read": true,
"write": "get(`database.roles.${doc.id}`).roles[auth.uid] in ['owner', 'writer']"
}
```
**comments Collection Rules:**
Allow everyone to post comments. Only comment owners can update and delete comments:
```json
{
"read": true,
"create": true,
"update": "doc.user == auth.uid",
"delete": "doc.user == auth.uid"
}
```
### Key Points
- Use a separate `roles` collection to manage user roles for each story
- Use `get()` function to access role information in security rules
- Role-based permissions are checked dynamically based on the roles collection
- This pattern can be extended to more complex permission scenarios
## Permission Selection Guide
### Choose Based on Business Complexity
| Business Scenario | Recommended Solution | Reason |
|------------------|---------------------|--------|
| Simple application | Basic permission control | Simple configuration, meets basic needs |
| Complex business logic | Security rules | Flexible expressions, supports complex judgment |
| Enterprise application | Role permissions + Basic permissions | Organization support, clear permission hierarchy |
| High security requirements | Security rules + Role permissions | Multi-layer protection, fine-grained control |
### Permission Configuration Recommendations
1. **Start Simple:** Use basic permissions first, upgrade gradually as needed
2. **Layered Design:** Basic permissions handle general logic, security rules handle special logic
3. **Test and Verify:** Fully test various permission scenarios in the development environment
4. **Document:** Record permission design ideas and configuration descriptions in detail
Through reasonable permission configuration, you can build a data access control system that is both secure and flexible, meeting various complex business requirements.
## References
- [CloudBase Security Rules Documentation](https://cloud.tencent.com/document/product/876/123478)
- [Security Rules Introduction](/rule/introduce)
- MCP Tool: `managePermissions` - Configure resource permissions
- MCP Tool: `queryPermissions` - Read current resource permissions
references/cloudbase-document-database-web-sdk/SKILL.md
---
name: cloudbase-document-database-web-sdk
description: Use CloudBase document database Web SDK only for confirmed NoSQL collection work. Query, create, update, and delete document data; if the task mentions PostgreSQL / CloudBase PG / app.rdb(), route to postgresql-development instead.
version: 2.33.1
alwaysApply: false
---
## Sibling skills (local only)
Sibling CloudBase skills ship beside this skill. Use local relative paths such as `../auth-tool-cloudbase/SKILL.md`.
If a referenced sibling skill file is missing from this environment, ask the user to install the full CloudBase plugin (or the missing skill). Do **not** HTTP-fetch remote skill or protocol markdown into the agent context.
# CloudBase Document Database Web SDK
## Activation Contract
### Use this first when
- A browser or Web app must read or write CloudBase document database data through `@cloudbase/js-sdk`.
- The request mentions `app.database()`, `db.collection()`, `.where()`, `.watch()`, pagination, aggregation, or geolocation queries in a Web frontend.
### Read before writing code if
- The task is clearly browser-side, but you still need to decide between Web SDK, Mini Program SDK, or backend access.
- The request touches login state, collection permissions, or realtime updates.
### Then also read
- Web login and caller identity -> `../auth-web-cloudbase/SKILL.md`
- General Web app structure -> `../web-development/SKILL.md`
- Mini Program database code -> `../cloudbase-document-database-in-wechat-miniprogram/SKILL.md`
### Do NOT use for
- Mini Program code using `wx.cloud.database()`.
- Server-side or cloud-function database access.
- SQL / MySQL database operations.
- Pure resource-permission administration with no browser SDK code.
- **NEW business tables that the task explicitly asks to put in CloudBase PostgreSQL (CloudBase PG).** Before applying this skill, call `envQuery(action="info", envId=...)` and read `EnvInfo.RuntimeBackends`. If `postgresql === true` AND the task asks for a new business table to live in PG, switch to the `postgresql-development-cloudbase` skill for that table: it goes through `app.rdb()`, uses PG row-level security (`CREATE POLICY`), and uploads via `app.storage.from('<bucket>').upload('<key>', file)` against an explicitly-created pgstore bucket.
- Existing NoSQL collections in the same env keep using THIS skill — PG and NoSQL coexist in CloudBase PG environments. The rule is "follow the task / existing surface", not "PG env forbids NoSQL".
### SDK Code vs MCP Tools
**When to write SDK code (use this skill):**
- The task explicitly asks to "modify code" or "use SDK"
- The task asks to implement app/frontend logic
- The task mentions specific SDK methods like `db.collection().add()`, `.get()`, `.update()`
- The context shows an existing Web project with SDK initialization (e.g., `index.js` already has `cloudbase.init()`)
**When to use MCP tools instead:**
- The task asks to manage CloudBase resources (create collection, set permissions, etc.)
- The task involves admin/management operations without writing app code
- The task mentions tools like `writeNoSqlDatabaseContent`, `managePermissions`, etc.
**Key distinction:** If the user says "使用 JS SDK 执行 XX 操作" (use JS SDK to perform XX operation) or "修改代码" (modify code), write SDK code in the project files. Do not use MCP database write tools for app-level data operations.
### Common mistakes / gotchas
- Querying before the user is signed in when the collection rules require identity.
- Using `wx.cloud.database()` or Node SDK patterns in browser code.
- Initializing CloudBase lazily with dynamic imports instead of a shared synchronous app instance.
- Treating security rules as result filters rather than request validators.
- **Expecting a `CUSTOM` security rule to take effect immediately after you call `managePermissions(updateResourcePermission)`.** The backend caches rule evaluators for **2–5 minutes**; first writes after a rule change may silently fail or be rejected with `DATABASE_PERMISSION_DENIED` even when the expression is correct. Either (a) wait a few minutes and retry the same write before assuming the rule is wrong, or (b) verify the rule is live by reading `result.code` / `result.message` on every write and by doing a `get()` round-trip on the just-written `_id`; do not treat a resolved promise as success. See `security-rules.md` → "Propagation And Verification" for the full pattern.
- Misreading the return shape of `db.collection(...).add(...)`. In the CloudBase Web SDK, the created document ID is exposed at top-level `result._id`, not `result.id`, `result.data.id`, or `result.insertedId`.
- For CMS-style collections that need **app-level admin users** to edit/delete all records while editors can only edit/delete their own records, do not oversimplify the rule to `READONLY`. A validated pattern is a `CUSTOM` rule that reads role from `user_roles` by `auth.uid` and combines it with `doc.authorId == auth.uid`, while frontend writes can stay on `.doc(id).update()` / `.doc(id).remove()`.
- Forgetting pagination or indexes for larger collections.
### Minimal checklist
- Confirm this is browser-side document database work.
- Initialize CloudBase once and reuse the same `app` / `db` instance.
- Verify auth expectations before CRUD.
- Read the right companion reference file for the specific operation.
## Overview
This skill covers **browser-side document database usage** via `@cloudbase/js-sdk`.
Use it for:
- CRUD in a Web app
- complex queries and pagination
- aggregation
- realtime listeners with `watch()`
- geolocation queries
## Canonical initialization
```javascript
import cloudbase from "@cloudbase/js-sdk";
const app = cloudbase.init({
env: "your-env-id"
});
const db = app.database();
const _ = db.command;
```
Important rules:
- Sign in before querying if the collection rules require identity.
- Keep a single shared app/database instance.
- Do not hide initialization inside ad-hoc async loaders unless the framework truly requires it.
## Quick routing
- CRUD -> `./crud-operations.md`
- Complex queries -> `./complex-queries.md`
- Pagination -> `./pagination.md`
- Aggregation -> `./aggregation.md`
- Realtime listeners -> `./realtime.md`
- Geolocation -> `./geolocation.md`
- Security rules -> `./security-rules.md`
## Working rules for a coding agent
1. **Start from the auth model**
- If the page relies on logged-in user identity, read the Web auth skill before writing database code.
2. **Keep browser code browser-native**
- Use `app.database()` and collection references.
- Do not mix in MCP management flows or SQL mental models.
3. **Respect security rules**
- Collection rules can reject requests before data is read.
- If the requirement is simple owner-only write access, `READONLY` can be enough.
- If the requirement is “app-level admin can edit/delete all, editor only own”, use a `CUSTOM` rule. A validated CMS pattern is `get('database.user_roles.' + auth.uid).role == 'admin' || doc.authorId == auth.uid`.
- For that CMS pattern, frontend writes can stay on `.doc(id).update()` / `.doc(id).remove()`.
- Reuse whichever role collection already exists and can be addressed by `_id == auth.uid`. In this CMS pattern, `user_roles` keyed by uid is acceptable.
- If the task fails with permission issues, inspect the rule model rather than assuming the query syntax is wrong.
4. **Return user-friendly errors**
- Database errors must become readable UI or application errors, not silent failures.
- For writes, do not treat a resolved promise as success by default. Check write result fields such as `updated` / `deleted` or surfaced `code` / `message`.
5. **Persist IDs from create operations correctly**
- For Web SDK `.add(...)`, the newly created document ID is `result._id`.
- Do not look for the ID under `result.id`, `result.data`, or other driver-specific fields.
## Quick examples
### Simple query
```javascript
const result = await db.collection("todos")
.where({ completed: false })
.get();
```
### Create and capture document ID
```javascript
const result = await db.collection("posts").add({
title: "New article",
content: "...",
createdAt: new Date()
});
const articleId = result._id;
```
### Ordered pagination
```javascript
const result = await db.collection("posts")
.orderBy("createdAt", "desc")
.skip(20)
.limit(10)
.get();
```
### Field selection
```javascript
const result = await db.collection("users")
.field({ name: true, email: true, _id: false })
.get();
```
## Best practices
1. Define collection-level types or model wrappers in the app code.
2. Use meaningful collection naming conventions.
3. Select only required fields.
4. Add indexes for frequent filters or sort keys.
5. Pair frontend CRUD with explicit permission design.
6. Use pagination instead of unbounded reads.
## Error handling
```javascript
try {
const result = await db.collection("todos").get();
console.log(result.data);
} catch (error) {
console.error("Database error:", error);
}
```
When the SDK returns an operation result, check error indicators and translate them into readable application behavior.
references/cloudbase-platform/references/protocols/change-safety-protocol.md
# Change Safety Protocol
**Mandatory protocol** for all non-trivial code and configuration changes.
## When This Protocol Applies
You must follow this protocol for any of the following:
- Changes to data models, fields, query conditions, or schemas
- Modifications to permissions, security rules, or access control
- Changes to authentication flows, login methods, or user management
- Interface contracts, function signatures, or API behavior changes
- Deployment configuration, environment variables, routing, or hosting settings
**Exempt** for purely trivial edits (text changes, comments, logs, formatting, or debug statements).
## Required Steps (Non-Negotiable)
1. **Before making any edits**, explicitly declare the impact in one clear sentence:
> This change affects: [specific files / fields / permissions / contracts / behavior]
2. **Obtain explicit user confirmation** before proceeding with any code or configuration changes.
3. **After editing**, immediately perform verification:
- Run syntax or type checking
- Execute at least one minimal verification case
- Confirm that no new errors were introduced
4. **Escalation rule (hard stop)**
- If the same root-cause symptom occurs **3 or more times** during this task → **stop patching immediately**.
- You must output:
- One-sentence root cause analysis
- Complete scope of impact
- Recommended holistic fix (do not continue making incremental patches)
## Recommended Response Templates
**Before editing:**
> This change affects: [xxx]. Do you confirm I should proceed?
**After verification:**
> Syntax check and minimal verification case completed. Change is effective.
**When escalation triggers:**
> The same issue has recurred 3 times. Stopping incremental fixes. Root cause analysis: ...
## Purpose
This protocol exists to eliminate endless correction loops and repeated trial-and-error. It enforces structured thinking and significantly reduces decision entropy while maintaining very low token cost.
**Usage instruction:**
> Before any non-trivial code or configuration change, strictly follow the Change Safety Protocol in `cloudbase-platform/references/protocols/change-safety-protocol.md`.
references/cloudbase-platform/references/protocols/deployment-gate.md
# Deployment Gate
**Mandatory pre-check** for any deployment, release, public exposure, custom domain, CloudRun, or mini program upload/publish operations.
## When to Apply
You must read and complete this gate before:
- Deploying or updating CloudRun services
- Binding custom domains or enabling HTTPS
- Publishing static hosting
- Uploading or publishing mini programs via miniprogram-ci
- Exposing cloud functions or HTTP services publicly
**Rule**: Never proceed with deployment actions until you have checked the relevant items below and obtained user confirmation on all gaps.
## Pre-Check Tables by Scenario
### Custom Domain / HTTPS Access
| Check Item | Consequence if Missing | Required Action |
|-------------------------------------|-------------------------------------|----------------------------------------------|
| Environment plan supports custom domains | Feature unavailable, repeated errors | Verify current plan tier |
| Existing custom domain reusable? | Unnecessary re-bind / cert prompts | `queryGateway(listCustomDomains)` first; if one exists, `createRoute(domain=...)` — no certificateId |
| ICP filing completed | Cannot bind **new** domain | Complete ICP filing or choose alternative |
| SSL certificate obtained + certificateId available | **New** domain bind fails | Only when first-time `bindCustomDomain`; retrieve certificateId from SSL console |
**Critical distinction**:
- Security Domain (`manageEnv` action=addSecurityDomain/removeSecurityDomain; deprecated alias `envDomainManagement`) ≠ Custom Domain (`manageGateway` Domain/Route)
- Reusing an existing custom domain = `createRoute` (no cert). Binding a brand-new custom domain = `bindCustomDomain` (needs certificateId).
### CloudRun (Container Services)
| Check Item | Consequence if Missing | Required Action |
|-------------------------------------|-------------------------------------|----------------------------------------------|
| Application port matches Dockerfile / code | Startup failure or 502 | Confirm listening port (commonly 9000) |
| Health check path correctly configured | Frequent restarts / unhealthy | Set correct path (e.g. `/` or `/health`) |
| Required environment variables and secrets injected | Runtime errors | Configure via console or MCP before deploy |
| No httpbin / header-echo / env-dump endpoints | `x-cloudbase-context` temporary credentials leak to callers | Follow `sensitive-runtime-data-protection.md`; health checks must return fixed non-secret payloads only |
| TCP DB/cache dependency detected (`DATABASE_URL`, MySQL/PG/Redis host) | Deploy succeeds but runtime cannot reach DB | Set `serverConfig.VpcConf` to the DB's VPC/subnet; use private DB host; see `cloudrun-development/references/vpc-and-database.md` |
| `OpenAccessTypes` vs `VpcConf` understood | Agent configures public ingress but omits private network | Ingress (`OpenAccessTypes`) ≠ egress VPC (`VpcConf`); both may be required |
### Static Website Hosting
| Check Item | Consequence if Missing | Required Action |
|-------------------------------------|-------------------------------------|----------------------------------------------|
| Content-Disposition / caching headers suitable for public web access | Files download instead of render | Verify ACL and response headers |
| Custom domain required | Must complete domain + SSL checks first | Warn user before starting |
### Mini Program Upload / Publish (miniprogram-ci)
| Check Item | Consequence if Missing | Required Action |
|-------------------------------------|-------------------------------------|----------------------------------------------|
| Upload IP added to mini program whitelist | Upload rejected | Add current egress IP in WeChat backend |
| AppID matches target environment | Publishing to wrong app | Double-check `project.config.json` |
### Cloud Functions Public Exposure / HTTP Access
| Check Item | Consequence if Missing | Required Action |
|-------------------------------------|-------------------------------------|----------------------------------------------|
| Function security rule configured to allow required callers | `EXCEED_AUTHORITY` errors | Configure via `managePermissions` immediately after creation |
| Anonymous login status understood | Public access unexpectedly blocked | Explicitly inform user (disabled by default on new environments) |
| Responses do not echo headers / env / `x-cloudbase-context` | Temporary credential leak | Follow `sensitive-runtime-data-protection.md` before public exposure |
| Non-native TCP DB/cache env (`DATABASE_URL`, MySQL/PG/Redis host) without `vpc` | Deploy succeeds but runtime cannot reach private DB | Set real `vpc.vpcId` + `vpc.subnetId` from DB console / resource detail / user — never invent IDs; see `cloud-functions/references/vpc-and-tcp-database.md`. Native `app.rdb()` / `app.database()` does not need VPC. |
## Mandatory Declaration Template
Before starting any deployment-related work, you must output something like this and wait for user confirmation:
> This deployment has the following prerequisites. Please confirm:
> - [ ] Plan supports required features (custom domain, CloudRun, etc.)
> - [ ] ICP filing and SSL certificate ready (if using custom domain)
> - [ ] CloudRun port and health check configured
> - [ ] If CloudRun or cloud functions use TCP MySQL/PostgreSQL/Redis (non-native SDK): real VPC + subnet IDs + private DB endpoint ready (do not invent IDs)
> - [ ] Mini program upload IP whitelist updated (if applicable)
> - [ ] Function / hosting security rules configured for public access
>
> Any missing item above will very likely cause deployment failure. Proceed?
## Usage Instruction
When the task involves deployment, publishing, custom domains, CloudRun, or public exposure:
> Before any deployment or publish action, you must first complete the full checks in `cloudbase-platform/references/protocols/deployment-gate.md` and present the declaration template to the user.
references/cloudbase-platform/references/protocols/sensitive-runtime-data-protection.md
# Sensitive Runtime Data Protection
**Mandatory protocol** when writing, reviewing, or deploying CloudBase Cloud Functions or CloudBase Run (Function mode / Container mode) code that handles HTTP requests, request headers, environment variables, or debug/echo tooling.
## Why this exists
CloudBase gateways may inject platform context into backend requests. The header `x-cloudbase-context` is a base64-encoded payload that can include temporary cloud credentials for the account/environment. If application code echoes request headers, dumps `process.env`, or deploys a generic request inspector (for example `httpbin`), those credentials can leak to any caller and grant broad cloud-resource access.
This is the same class of failure as returning cloud-function environment variables or SecretId/SecretKey to clients: the platform cannot fully prevent unsafe application code from disclosing what it can already read.
## Hard rules (non-negotiable)
1. **Never return `x-cloudbase-context` to clients**
- Do not put it in response bodies, response headers, logs shipped to clients, error payloads, or debug endpoints.
- Do not base64-decode it and print, store in public storage, or forward it to untrusted systems.
- Treat case variants the same (`X-CloudBase-Context`, `x-cloudbase-context`, etc.).
2. **Never echo raw request metadata wholesale**
- Forbidden patterns: returning `req.headers`, `event.headers`, `event`, `context`, `os.environ`, `process.env`, or "dump everything" debug routes.
- If debugging is required, return only an explicit allowlist of non-sensitive fields (for example `method`, `path`, a business request id).
3. **Never dump runtime secrets or credential-shaped env vars**
- Do not return or log values for names such as:
- `TENCENTCLOUD_SECRETID` / `TENCENTCLOUD_SECRETKEY` / `TENCENTCLOUD_SESSIONTOKEN`
- `CLOUDBASE_APIKEY` / `CLOUDBASE_API_KEY` / `TCB_*` credential fields
- Any `*SECRET*`, `*TOKEN*`, `*PASSWORD*`, `*PRIVATE_KEY*` environment variable
- Prefer CloudBase server API Keys for app runtime auth instead of reflecting platform-injected temporary keys.
4. **Do not deploy request-echo / inspector images for CloudRun demos**
- Do **not** recommend or deploy `httpbin`, `requestbin`, `webhook.site`-style reflectors, or custom "print all headers" services on CloudBase Run.
- Prefer a minimal hello-world that returns a fixed JSON payload, or a health check that returns `{ "ok": true }` only.
5. **Keep secrets server-side**
- Inject credentials through environment variables / secret managers.
- Use them only inside the process to call CloudBase or Tencent Cloud APIs.
- Never serialize them into HTTP responses, client bundles, README samples with real values, or chat/tool output meant for end users.
## Safe vs unsafe examples
### Unsafe (do not generate)
```js
// CloudRun / HTTP Function — leaks gateway-injected credentials
app.get("/debug", (req, res) => {
res.json({ headers: req.headers, env: process.env });
});
```
```js
// Event Function — may leak platform context fields
exports.main = async (event, context) => {
return { event, context, env: process.env };
};
```
```yaml
# CloudRun — httpbin reflects x-cloudbase-context to callers
image: kennethreitz/httpbin
```
### Safe (preferred)
```js
app.get("/health", (_req, res) => {
res.json({ ok: true });
});
app.get("/whoami", (req, res) => {
// Allowlist only non-sensitive request facts
res.json({
method: req.method,
path: req.path,
// Do NOT include req.headers or credential headers
});
});
```
```js
exports.main = async (event) => {
const name = typeof event?.name === "string" ? event.name : "world";
return { ok: true, message: `hello ${name}` };
};
```
## Agent checklist before claiming done
- [ ] No response path returns `x-cloudbase-context` or full `headers` / `env` objects
- [ ] No CloudRun/function demo uses httpbin or equivalent header echo tools
- [ ] Credential env vars are read for SDK init only, never written into responses
- [ ] Debug endpoints, if any, use an explicit non-sensitive allowlist
## Usage instruction
> Before writing or deploying Cloud Function / CloudRun HTTP handlers, debug routes, or container images, follow `cloudbase-platform/references/protocols/sensitive-runtime-data-protection.md`.
references/cloudbase-platform/SKILL.md
---
name: cloudbase-platform
description: CloudBase platform overview and routing guide. This skill should be used when users need high-level capability selection, platform concepts, console navigation, or cross-platform best practices before choosing a more specific implementation skill.
version: 2.33.1
alwaysApply: false
---
## Sibling skills (local only)
Sibling CloudBase skills ship beside this skill. Use local relative paths such as `../auth-tool-cloudbase/SKILL.md`.
If a referenced sibling skill file is missing from this environment, ask the user to install the full CloudBase plugin (or the missing skill). Do **not** HTTP-fetch remote skill or protocol markdown into the agent context.
**Cross-cutting protocols** (required before code changes or deployments):
- Change Safety Protocol: `references/protocols/change-safety-protocol.md`
- Deployment Gate: `references/protocols/deployment-gate.md`
- Sensitive Runtime Data Protection: `references/protocols/sensitive-runtime-data-protection.md`
## Activation Contract
### Use this first when
- The user asks which CloudBase capability, service, or tool to use, or needs a high-level understanding of hosting, storage, authentication, cloud functions, or database options.
- The task is about console navigation, cross-platform differences, permission models, or platform-level best practices before implementation.
### Read before writing code if
- It is still unclear whether the task belongs to Web, mini program, cloud functions, storage, MySQL / NoSQL, or auth.
- The response needs platform selection, conceptual explanation, or control-plane navigation more than direct implementation steps.
### Then also read
- Minimal Web + database demo (BaaS-first, no cloud functions by default) -> `../minimal-web-baas-demo/SKILL.md`
- **Stack order for 最小前后端 / Lovable-like demos:** Web SDK CRUD > MCP schema > template warmup during credential wait > cloud functions (default count = 0). Capability sniff: connector ready → `envQuery` → lock one DB plane → MCP schema → `@cloudbase/js-sdk` CRUD → preview.
- Web app implementation -> `../web-development/SKILL.md`
- Web auth and provider setup -> `../auth-tool-cloudbase/SKILL.md`, `../auth-web-cloudbase/SKILL.md`
- Mini program development -> `../miniprogram-development/SKILL.md`
- WeChat Pay, Official Account OAuth, JSAPI Pay, or Native QR-code Pay through CloudBase Integration Center -> `../cloudbase-wechat-integration/SKILL.md` (official docs: `https://docs.cloudbase.net/integration/introduce/index.md`)
- Cloud functions -> `../cloud-functions/SKILL.md`
- Official HTTP API clients -> `../http-api-cloudbase/SKILL.md`
- Document database -> `../cloudbase-document-database-web-sdk/SKILL.md` or `../cloudbase-document-database-in-wechat-miniprogram/SKILL.md`
- CloudBase PostgreSQL / PG -> `../postgresql-development-cloudbase/SKILL.md`
- MySQL relational database / data modeling -> `../relational-database-mcp-cloudbase/SKILL.md` or `../data-model-creation/SKILL.md`
- Cloud storage -> `../cloud-storage-web/SKILL.md`
### Do NOT use for
- Direct implementation of web pages, auth flows, functions, or database operations when a more specific skill already fits.
- Low-level API parameter references or SDK recipes that belong in specialized skills.
### Common mistakes / gotchas
- Treating this general skill as the default entry point for all CloudBase development.
- Staying here after the correct implementation skill is already clear.
- Mixing platform overview with platform-specific API shapes or SDK details.
- Using this overview skill as a detour in an existing application where the active auth, storage, and data files are already obvious.
- Making code or configuration changes without first following the Change Safety Protocol (`cloudbase-platform/references/protocols/change-safety-protocol.md`).
- Starting any deployment, publish, custom domain, or CloudRun work without first completing the checks in `cloudbase-platform/references/protocols/deployment-gate.md`.
- Echoing `x-cloudbase-context`, full `req.headers`, or `process.env` from Cloud Functions / CloudRun (including httpbin-style debug images) — follow `references/protocols/sensitive-runtime-data-protection.md`.
- **Confusing security domains with custom domains**: these are two different tools for different purposes. See the "Domain Management Tools" table below for the authoritative split.
## When to use this skill
Use this skill for **CloudBase platform knowledge** when you need to:
- Understand CloudBase storage and hosting concepts
- Compare platform capabilities before implementation
- Understand cross-platform auth differences (Web vs Mini Program)
- Understand database permissions and access control
- Access CloudBase console management pages
**This skill provides foundational knowledge** that applies to all CloudBase projects, regardless of whether they are Web, Mini Program, or backend services.
---
## How to use this skill (for a coding agent)
1. **Understand platform differences**
- Web and Mini Program have completely different authentication approaches
- Must strictly distinguish between platforms
- Never mix authentication methods across platforms
- If the workspace is already an application with TODOs or prebuilt handlers, do not stay in platform overview mode. Move quickly to the concrete implementation skill and the existing files that own the flow.
2. **Follow best practices**
- Use SDK built-in authentication features (Web)
- Understand natural login-free feature (Mini Program)
- Configure appropriate database permissions
- Prefer `@cloudbase/js-sdk` direct DB access for browser CRUD; use cloud functions only for secrets, scheduled/background jobs, or elevated cross-collection logic that security rules / RLS cannot express (see `../minimal-web-baas-demo/SKILL.md` for the demo default)
3. **Use correct SDKs and APIs**
- Different platforms require different SDKs for data models
- MySQL data models must use models SDK, not collection API
- PostgreSQL / CloudBase PG work must route to `postgresql-development-cloudbase`; do not reuse NoSQL `app.database()` / `db.collection(...)` snippets or MySQL `queryMysqlDatabase` / `manageMysqlDatabase` for PG data paths
- Use `envQuery` tool to get environment ID
- In an existing Web application with fixed structure, inspect the existing `src/lib/backend.*`, `src/lib/auth.*`, `src/lib/*service.*`, and bound page handlers before broad concept reading.
4. **Use the canonical CloudBase MCP setup from the main `cloudbase` guideline**
- This platform overview intentionally does **not** duplicate the full MCP / mcporter config block
- For the canonical config snippet, CLI commands, and auth examples, read the main `cloudbase` guideline first
- Keep the same core rules here: prefer MCP when tools are available in this session; if not, configure MCP for next session and use `tcb` CLI now (`../cloudbase-cli/SKILL.md`, `../cloudbase/references/tooling-fallback.md`). Inspect tool schemas before MCP execution. Do not hard-code Secret ID / Secret Key / Env ID in config
- Keep the auth split explicit: management-side login uses `auth`, while application-side auth configuration uses `queryAppAuth` / `manageAppAuth`
---
# CloudBase Platform Knowledge
### Domain Management Tools: Clear Distinction
When working with domain-related tasks, use the correct tool based on the requirement:
| Requirement | Tool | Parameters | Purpose |
|-------------|------|------------|---------|
| **Security Domain (安全域名)** | `manageEnv(action="addSecurityDomain" \| "removeSecurityDomain")` | `domains` (array of host:port strings) | CORS/request source validation for browser uploads. No certificate involved. (Deprecated alias: `envDomainManagement`.) |
| **Reuse existing Custom Domain** | `queryGateway(listCustomDomains)` → `manageGateway(createRoute)` | `domain` = existing custom domain; route fields | Expose a service/path on an already-bound custom domain. **No certificateId.** Prefer this when a custom domain already exists. |
| **Bind new Custom Domain (自定义域名)** | `manageGateway(action="bindCustomDomain")` | `domain` (string), `certificateId` (string) | First-time bind of a new public HTTPS domain. Requires certId from SSL console. |
| **Delete Custom Domain** | `manageGateway(action="deleteCustomDomain")` | `domain` (string) | Remove custom domain binding (only after routes on that domain are deleted). |
| **Disable / enable gateway route** | `manageGateway(action="disableRoute" \| "enableRoute")` | `path` (required), prefer explicit `domain` | Toggle `Routes[].Enable` via `ModifyHTTPServiceRoute` (not `ModifyGatewayRoute`). |
| **Disable static hosting default domain** | `queryGateway(listRoutes)` → `manageGateway(disableRoute)` | `domain` = `*.tcloudbaseapp.com` (`DomainType=STATIC_STORE`, `IsDefault=true`), usually `path="/"` | Turns off public access on the shared hosting CDN default host. **Do not use `manageHosting`.** |
**Key indicators for choosing the right tool:**
- Task mentions "自定义域名访问" but env already has a custom domain → `listCustomDomains` then `createRoute(domain=...)` (no certificateId)
- Task mentions "certificate ID" or "SSL" **and** needs to bind a **new** domain → `manageGateway(action="bindCustomDomain")`
- Task mentions "浏览器上传" or "CORS" or "安全域名" → Use `manageEnv(action="addSecurityDomain" / "removeSecurityDomain")`
- Task mentions "public access" or "HTTPS" with domain → Prefer reuse via `createRoute` when possible; only `bindCustomDomain` for first-time domain bind
- Task mentions "关闭/禁用静态托管默认域名" / `*.tcloudbaseapp.com` → `queryGateway(listRoutes)` then `manageGateway(disableRoute)` with that STATIC_STORE domain; never invent `ModifyGatewayRoute`
### Error Code Troubleshooting: Route Through Official Docs
When a CloudBase tool call fails and the error message contains a specific error code (pattern `Category.Code`, e.g. `OperationDenied.FreePackageDenied`, `ResourceNotFound.*`), **always route through the official docs before acting — do not guess the meaning, do not hardcode fix recipes here**:
1. Extract the error code from the error message.
2. Look it up: `searchKnowledgeBase(mode="docs", action="searchDocs", query="<错误码>")` — official docs search covers error-code pages. Act on the documented meaning and the fix steps the doc prescribes (plan limits → upgrade guidance, misconfiguration → config fix, etc.).
3. If docs search returns nothing, fall back to the canonical error-code pages:
- Error code basics & self-service troubleshooting: `https://docs.cloudbase.net/error-code/basic`
- Control-plane cloud API error codes: `https://cloud.tencent.com/document/product/876/34823`
4. Never assert capability-per-plan or error-code semantics from memory — official docs and the console plan comparison are the only authoritative sources. Example: for Web 安全域名 plan requirements, cite `https://cloud.tencent.com/document/product/876/127357` rather than assuming which tier unlocks it.
### Recording Operation Results
When a task explicitly requires recording operation steps or results to a file (e.g., `RESULT.json`): perform the tool calls first, then write a complete record containing every attempt (action, success/failure, message) plus a `summary` with total / succeeded / failed counts. Do not write the file from memory before the calls finish.
## Storage and Hosting
1. **Static Hosting vs Cloud Storage**:
- CloudBase static hosting and cloud storage are two different buckets
- Generally, publicly accessible files can be stored in static hosting, which provides a public web address
- Static hosting supports custom domain configuration (requires console operation)
- Cloud storage is suitable for files with privacy requirements, can get temporary access addresses via temporary file URLs
- If the task needs COS SDK polling, file metadata lookup, or temporary URLs for an uploaded object, use cloud storage tools (`manageStorage` / `queryStorage`), not `manageHosting(action="upload")`
2. **Static Hosting Domain**:
- CloudBase static hosting domain and website document config can be obtained via `queryHosting(action="websiteConfig")`
- Combine with static hosting file paths to construct final access addresses
- Default shared host looks like `<envId>-<appId>.tcloudbaseapp.com` (`DomainType=STATIC_STORE`, often `IsDefault=true` in `queryGateway(listRoutes)`)
- To **disable** that default public host: `manageGateway(action="disableRoute", domain="<that-host>", path="/")` (or `updateRoute` with `enable=false`). Re-enable with `enableRoute`. Do **not** look for a `manageHosting` disable-default-domain action; do **not** call non-existent `ModifyGatewayRoute` — the API is `ModifyHTTPServiceRoute`
- **Important**: If access address is a directory, it must end with `/`
3. **Cloud Storage Public URL**:
- **CRITICAL**: `manageStorage(action=upload)` and `queryStorage(action=url)` return `temporaryUrl` which is a temporary signed URL that expires (default 1 hour). Do NOT use this as a permanent public URL.
- To get the permanent public access URL for a cloud storage object:
1. Call `envQuery(action=info)` to get environment details
2. Extract the storage CDN domain from `EnvInfo.Storages[0].CdnDomain` (e.g., `your-env-id.tcb.qcloud.la`)
3. Construct the public URL: `https://{CdnDomain}/{cloudPath}`
- Example: If `CdnDomain` is `env-xxx.tcb.qcloud.la` and `cloudPath` is `uploads/avatar.jpg`, the public URL is `https://env-xxx.tcb.qcloud.la/uploads/avatar.jpg`
- Note: The public URL is accessible only if the storage bucket ACL allows public read (default is `PRIVATE` which requires signed URLs)
## Environment and Authentication
1. **SDK Initialization**:
- CloudBase SDK initialization requires environment ID
- Can query environment ID via `envQuery` tool
- If the user only provides an environment alias, nickname, or other short form, resolve it with `envQuery(action="list", alias=..., aliasExact=true)` first and use the returned full `EnvId`
- Do not pass alias-like short forms directly into SDK init, `auth.set_env`, console URLs, or generated config files
- For Web, always initialize synchronously:
- `import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-full-env-id" });`
- Do **not** use dynamic imports like `import("@cloudbase/js-sdk")` or async wrappers such as `initCloudBase()` with internal `initPromise`
- Then proceed with login using a verified method (username/password, phone, email, or WeChat)
2. **Environment Management (via manageEnv)**:
The `manageEnv` tool provides full lifecycle management for CloudBase environments.
| Action | Description | Key Parameters |
|--------|-------------|----------------|
| `listPackages` | Query available plans | (none) |
| `create` | Create new environment (needs confirm) | `alias`, `packageId`, `resources`, `duration` |
| `modifyPlan` | Change plan (upgrade/downgrade, needs confirm) | `envId`, `packageId` |
| `renew` | Renew environment (needs confirm) | `envId`, `duration` |
**Creating an environment with specific resources:**
```
manageEnv(action="create", alias="my-env", packageId="baas_personal",
resources=["flexdb","storage","function","postgresql"], confirm="yes")
```
- **`resources`** (optional, create only): controls which CloudBase capabilities to enable:
- `flexdb` — Document database (NoSQL)
- `storage` — Cloud Storage
- `function` — Cloud Functions
- `postgresql` — PostgreSQL relational database (PG mode)
- Defaults to all four when omitted. MCP always sends non-empty `Resources` to CreateEnv.
- Do **not** pass `region`: CreateEnv does not accept Region; environment region is determined by account/package.
- ⚠️ **All paid operations** (create / modifyPlan / renew) require `confirm="yes"`.
**Querying available packages before creating:**
```
manageEnv(action="listPackages")
```
**Changing plan (e.g. personal → standard):**
```
manageEnv(action="modifyPlan", envId="your-env-id", packageId="baas_pf_standard", confirm="yes")
```
**Renewing an environment:**
```
manageEnv(action="renew", envId="your-env-id", duration=1, confirm="yes")
```
## Authentication Best Practices
**Important: Authentication methods for different platforms are completely different, must strictly distinguish!**
### Web Authentication
- **Must use SDK built-in authentication**: CloudBase Web SDK provides complete authentication features
- **Recommended method**: SMS login with `auth.getVerification()`, for detailed, refer to web auth related docs
- **Forbidden behavior**: Do not use cloud functions to implement login authentication logic
- **Session management**: For route guards and login proof, use `auth.getSession()` and require `data.session`; do not use deprecated `getLoginState()` or `auth.getUser()` / `auth.getCurrentUser()` as proof of real login.
- **Provider and login-method setup**: Use `queryAppAuth` / `manageAppAuth`, not the MCP `auth` tool
- **Anonymous login is disabled by default.** Publishable `accessKey` alone does **not** create a gateway-authenticated anonymous session. With `@cloudbase/js-sdk` **3.x**, call `await auth.signInAnonymously()` (or an equivalent authenticated session) **before** NoSQL `app.database()` CRUD, or the gateway returns **401**. If the app uses AuthGuard or RLS for access control, ensure `is_anonymous` checks are in place when anonymous access is allowed.
- **⚠️ PG RLS: Use `auth.uid()`, NOT `current_user`.** When writing RLS policies for CloudBase PostgreSQL, the user identity must use `auth.uid()` (returns the JWT `sub` / actual user ID as **`text`**, not `uuid` — unlike Supabase). Prefer owner columns as `varchar(64)` / `text`; if the column is `uuid`, cast with `auth.uid()::uuid` or you get `operator does not exist: uuid = text`. Do NOT use `current_user` or `current_setting(...)` — these PostgreSQL built-in functions return the database role name (e.g. `authenticated`), not the CloudBase auth user ID. CloudBase PG provides four auth helper functions: `auth.uid()`, `auth.role()`, `auth.email()`, `auth.jwt()`. Verify availability with `SELECT proname FROM pg_proc WHERE pronamespace = 'auth'::regnamespace`.
### Mini Program Authentication
- **Login-free feature**: Mini program CloudBase is naturally login-free, no login flow needed
- **User identifier**: In cloud functions, get `wxContext.OPENID` via wx-server-sdk
- **User management**: Manage user data in cloud functions based on openid
- **Forbidden behavior**: Do not generate login pages or login flow code
## Cloud Functions
1. **Node.js Cloud Functions**:
- Node.js cloud functions need to include `package.json`, declaring required dependencies
- Can use `manageFunctions(action="createFunction")` to create functions
- Use `manageFunctions(action="updateFunctionCode")` to deploy cloud functions
- Prioritize cloud dependency installation, do not upload node_modules
- `functionRootPath` refers to the parent directory of function directories, e.g., `cloudfunctions` directory
## Database Permissions
**⚠️ CRITICAL: Always configure permissions BEFORE writing database operation code!**
1. **Permission Model**:
- CloudBase database access has permissions
- Default basic permissions include:
- **READONLY**: Everyone can read, only creator/admin can write
- **PRIVATE**: Only creator/admin can read/write
- **ADMINWRITE**: Everyone can read, **only admin can write** (⚠️ NOT for Web SDK write!)
- **ADMINONLY**: Only admin can read/write
- **CUSTOM**: Fine-grained control with custom rules
2. **Platform Compatibility** (CRITICAL):
- ⚠️ **Web SDK cannot use `ADMINWRITE` or `ADMINONLY` for write operations**
- ✅ For user-generated content in Web apps, use **CUSTOM** rules
- ✅ For admin-managed data (products, settings), use **READONLY**
- ✅ Cloud functions have full access regardless of permission type
3. **Configuration Workflow**:
```
Create collection → Configure security rules → Write code → Test
```
- Use `managePermissions(action="updateResourcePermission")` to configure resource permissions
- If permissions were just changed, retry after a few seconds (typically within ~30s). Do not blind-wait 2-5 minutes. If it still fails, re-check the actual rule shape and active client write pattern first — most failures are misconfigured rules, not cache.
- See `no-sql-web-sdk/security-rules.md` for detailed `resourceType="noSqlDatabase"` examples only; do not treat `doc._openid`, `auth.openid`, query-subset validation, or `create` / `update` / `delete` JSON templates as generic rules for functions, storage, or SQL tables
- Official references:
- General security rules overview: `https://cloud.tencent.com/document/product/876/41802`
- NoSQL database security rules: `https://docs.cloudbase.net/database/security-rules`
- Cloud function security rules: `https://docs.cloudbase.net/cloud-function/security-rules`
- Storage security rules: `https://docs.cloudbase.net/storage/security-rules`
Compatibility note:
- Canonical plugin name: `permissions`
- Legacy plugin aliases `security-rule`, `security-rules`, `secret-rule`, `secret-rules`, and `access-control` still resolve to the `permissions` plugin
- Legacy tools `readSecurityRule` / `writeSecurityRule` are removed; prefer `queryPermissions` / `managePermissions`
4. **Common Scenarios**:
- **E-commerce products**: `READONLY` (admin manages via cloud functions)
- **Shopping carts**: `CUSTOM` with `auth.uid` check (users manage their own)
- **Orders**: `CUSTOM` with ownership validation
- **System logs**: `PRIVATE` or `ADMINONLY`
5. **Cross-Collection Operations**:
- Prefer security rules / RLS and client SDK when the permission model allows it
- Use cloud functions when the operation needs elevated privileges, server secrets, or multi-collection logic that rules cannot express
- For minimal Web demos (Todo / Notes / Kanban / 最小前后端), do **not** introduce cloud functions for CRUD — follow `../minimal-web-baas-demo/SKILL.md`
## Role Management (MCP)
CloudBase MCP provides role management via `queryPermissions` and `managePermissions` (CLI equivalent: `tcb role`). See each tool's schema for the full action list.
**⚠️ CRITICAL: Role policies and resource permissions are two independent systems with NO automatic synchronization.**
- Resource permissions (security rules) control access to specific resources (tables, collections, functions, storage)
- Roles (identity dimension) control policy bundles and member assignments
**Query** (`queryPermissions`): `listRoles`, `getRole` (by `roleId` / `roleIdentity` / `roleName`).
**Manage** (`managePermissions`): `createRole`, `updateRole`, `deleteRoles`, `addRoleMembers`, `removeRoleMembers`, `addRolePolicies`, `removeRolePolicies`.
```
managePermissions(action="createRole", roleName="Developer", roleIdentity="developer",
policies=["FunctionsAccess"], memberUids=["user-uid-1"])
```
> ⚠️ Only custom roles can be deleted. System roles are read-only.
See also: CLI equivalent commands in `cloudbase-cli/references/permission.md`
3. **Cloud Function Optimization**:
- Browser CRUD should not default to a cloud-function middleware layer; prefer `@cloudbase/js-sdk` → database (see `../minimal-web-baas-demo/SKILL.md`)
- When cloud functions are truly required, keep the count minimal and scope each function to secrets, elevated privilege, or background work
## Data Models
1. **Get Data Model Operation Object**:
- **Mini Program**: Need `@cloudbase/wx-cloud-client-sdk`, initialize `const client = initHTTPOverCallFunction(wx.cloud)`, use `client.models`
- **Cloud Function**: Need `@cloudbase/node-sdk@3.10+`, initialize `const app = cloudbase.init({env})`, use `app.models`
- **Web**: Need `@cloudbase/js-sdk`, initialize `const app = cloudbase.init({env})`, after login use `app.models`
2. **Data Model Query**:
- Can call MCP `manageDataModel` tool to:
- Query model list
- Get model detailed information (including Schema fields)
- Get specific models SDK usage documentation
3. **MySQL Data Model Invocation Rules**:
- MySQL data models cannot use collection method invocation, must use data model SDK
- **Wrong**: `db.collection('model_name').get()`
- **Correct**: `app.models.model_name.list({ filter: { where: {} } })`
- Use `manageDataModel` tool's `docs` method to get specific SDK usage
## Console Management
After creating/deploying resources, provide corresponding console links. All console URLs follow the pattern: `https://tcb.cloud.tencent.com/dev?envId=${envId}#/{path}` — replace `${envId}` with the real EnvId resolved via `envQuery` (resolve aliases first; see Environment and Authentication below), and resource names with actual values.
The CloudBase console is updated frequently. If a live, logged-in console shows a different hash path from this list, prefer the live console path over stale documentation and then update this skill to match.
### Entry points (one line each)
- Overview: `#/overview`
- Template Center: `#/cloud-template/market`
- Document Database: `#/db/doc` · Collections `#/db/doc/collection/${collectionName}` · Models `#/db/doc/model/${modelName}`
- MySQL Database: `#/db/mysql` · Tables `#/db/mysql/table/default/` (must be enabled in console first)
- Cloud Functions: `#/scf` · Detail `#/scf/detail?id=${functionName}&NameSpace=${envId}`
- CloudRun: `#/platform-run`
- Cloud Storage: `#/storage`
- AI+: `#/ai`
- Static Hosting: `#/static-hosting` (alt: `https://console.cloud.tencent.com/tcb/hosting`)
- Identity Authentication: `#/identity` · Login management `#/identity/login-manage` · Token management `#/identity/token-management`
- Weida Low-Code: `#/lowcode/apps`
- Logs & Monitoring: `#/devops/log`
- Environment Settings: `#/env/http-access` (security domains, CORS, env vars, quotas)
For configuration pages (like login management), guide users through the setup process rather than only dropping a link.
## Reference index
All packaged reference files (required for skill lint reachability):
- [protocols/change-safety-protocol.md](references/protocols/change-safety-protocol.md)
- [protocols/deployment-gate.md](references/protocols/deployment-gate.md)
- [protocols/sensitive-runtime-data-protection.md](references/protocols/sensitive-runtime-data-protection.md)
references/cloudbase-wechat-integration/references/mini-program-pay.md
# Mini Program WeChat Pay
Official docs:
- `https://docs.cloudbase.net/integration/wechat-pay-miniprogram/index.md`
- `https://docs.cloudbase.net/integration/usage/index.md`
## When To Use
Use this reference for WeChat Mini Program payment flows on CloudBase, including 小程序微信支付, `wx.cloud.callHTTPFunction`, `wx.requestPayment`, Mini Program openid handling, payment callbacks, refunds, and order-status sync.
## Agent Must Know
- The CloudBase Integration Center generated payment function is an HTTP cloud function.
- `pay-common` is an example function name; use the actual generated function name.
- Mini Program openid can be injected by CloudBase when calling through the Mini Program cloud function path.
- The client-side `wx.requestPayment` success callback is not the final business truth.
- Fulfillment must be driven by callback handling or explicit order query.
## Minimal Contract
Typical Mini Program flow:
1. Mini Program calls the generated payment function over `wx.cloud.callHTTPFunction`.
2. The request path targets the generated payment route, commonly an order-creation path such as `/wx-pay/wxpay_order`.
3. The generated function returns payment parameters for `wx.requestPayment`.
4. The Mini Program invokes `wx.requestPayment`.
5. Backend callback or query logic confirms paid state before updating business data.
Example shape:
```js
const functionName = "replace-with-generated-payment-function";
const orderResult = await wx.cloud.callHTTPFunction({
name: functionName,
path: "/wx-pay/wxpay_order",
data: {
out_trade_no: orderId,
description: "Order payment",
amount: {
total: 1,
currency: "CNY",
},
},
});
// callHTTPFunction returns { data, statusCode, header }
// The generated function typically returns { code, data, message }
// Payment params are under orderResult.data.data
const payment = orderResult.data?.data;
if (!payment) {
throw new Error("Missing payment parameters from CloudBase payment function");
}
await wx.requestPayment(payment);
```
Adjust field names to the official docs and the generated function contract before using in production.
## Implementation Checklist
- Confirm `wx.cloud.init({ env })` uses the canonical full CloudBase environment ID.
- Confirm the Mini Program AppID matches the WeChat Pay merchant binding.
- Confirm the generated function name and path in CloudBase console.
- Generate a unique `out_trade_no` on the backend or trusted business layer.
- Validate amount and product data server-side before creating payment.
- Persist pending order state before initiating payment.
- Handle payment callback idempotently.
- Query the order after client payment success before showing final fulfillment state.
## Common Extensions
- Write order and payment status to CloudBase database.
- Add an idempotency key on `out_trade_no`.
- Add fulfillment only after callback/query confirms success.
- Add refund initiation and refund callback handling if the product supports refunds.
## Do Not
- Do not place merchant keys or certificates in Mini Program code.
- Do not trust client-provided amount without server-side validation.
- Do not assume frontend success means the order is paid.
- Do not hard-code `pay-common` if the console generated a different function name.
references/cloudbase-wechat-integration/references/native-qr-pay.md
# Native QR-Code Pay
Official docs:
- `https://docs.cloudbase.net/integration/wechat-pay-native/index.md`
- `https://docs.cloudbase.net/integration/usage/index.md`
## When To Use
Use this reference for PC/Web checkout, Native WeChat Pay, QR-code payment, or flows where the generated function returns a payment `code_url` for the frontend to render as a QR code.
## Agent Must Know
- Native payment does not use `wx.requestPayment` or `WeixinJSBridge`.
- The generated payment function creates an order and returns a QR-code URL such as `code_url`.
- The frontend renders the QR code and polls or subscribes to payment state.
- Fulfillment must wait for callback or query confirmation.
## Minimal Contract
Typical Native flow:
1. Backend or frontend calls the generated payment function to create a Native order.
2. The generated function returns `code_url`.
3. The frontend renders `code_url` as a QR code.
4. The user scans the QR code in WeChat.
5. The app polls order status or waits for callback-driven state changes.
Example frontend shape:
```js
async function createNativePayment(orderId) {
const response = await fetch("/api/pay/native-order", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ orderId }),
});
const data = await response.json();
if (!data.code_url) {
throw new Error("Missing Native payment code_url");
}
return data.code_url;
}
```
In CloudBase frontend-only projects, the API wrapper can call the generated HTTP function directly if the access model and CORS/security rules are appropriate. For production, prefer a trusted backend or generated function extension that validates amount and order ownership.
## Implementation Checklist
- Confirm this is Native QR-code payment, not JSAPI or Mini Program payment.
- Confirm generated function name and Native order path.
- Generate a unique order number and persist pending state.
- Validate amount and goods details before creating payment.
- Render QR code from `code_url`.
- Poll order status with backoff, or update UI from callback-driven status.
- Expire stale QR codes and handle closed orders.
## Do Not
- Do not call `wx.requestPayment` for Native QR-code pay.
- Do not fulfill the order when the QR code is generated.
- Do not rely on frontend polling alone if callback data says otherwise.
references/cloudbase-wechat-integration/references/official-account-jsapi-pay.md
# Official Account JSAPI Pay
Official docs:
- `https://docs.cloudbase.net/integration/wechat-pay-jsapi-h5/index.md`
- `https://docs.cloudbase.net/integration/wechat-official-oauth/index.md`
- `https://docs.cloudbase.net/integration/usage/index.md`
## When To Use
Use this reference for WeChat Official Account webpage payment, JSAPI payment inside the WeChat browser, H5 checkout that calls `WeixinJSBridge.invoke`, and flows that need an official-account openid before creating the payment order.
## Agent Must Know
- JSAPI payment requires the page to run in the WeChat built-in browser.
- The payer openid must belong to the correct Official Account, not the Mini Program openid.
- Official Account OAuth is commonly needed before JSAPI order creation.
- The generated payment function name and routes must be read from the user's Integration Center setup.
- Final business state still depends on payment callback or order query.
## Minimal Contract
Typical JSAPI flow:
1. Redirect the user through Official Account OAuth to get an openid.
2. Call the generated payment function to create a JSAPI order.
3. Pass returned payment parameters to `WeixinJSBridge.invoke("getBrandWCPayRequest", ...)`.
4. Use payment callback or order query to confirm paid state.
Example invocation shape:
```js
function invokeJsapiPay(paymentParams) {
return new Promise((resolve, reject) => {
if (!window.WeixinJSBridge) {
reject(new Error("WeixinJSBridge is unavailable; open this page in WeChat"));
return;
}
window.WeixinJSBridge.invoke(
"getBrandWCPayRequest",
paymentParams,
(res) => {
if (res.err_msg === "get_brand_wcpay_request:ok") {
resolve(res);
return;
}
reject(new Error(res.err_msg || "JSAPI payment failed"));
},
);
});
}
```
Adjust request paths and parameter names to the generated function contract and official docs.
## Implementation Checklist
- Confirm the app is an Official Account web flow, not a Mini Program page.
- Confirm the page runs inside WeChat before showing JSAPI checkout.
- Obtain the Official Account openid through OAuth before order creation.
- Confirm merchant account binding matches the Official Account AppID.
- Persist pending order state before invoking payment.
- Confirm paid state through callback or query before fulfillment.
## Do Not
- Do not reuse Mini Program openid for Official Account JSAPI pay.
- Do not show JSAPI checkout in a normal desktop browser.
- Do not put AppSecret, merchant private keys, or APIv3 keys in browser code.
references/cloudbase-wechat-integration/references/official-account-oauth.md
# Official Account OAuth
Official docs:
- `https://docs.cloudbase.net/integration/wechat-official-oauth/index.md`
- `https://docs.cloudbase.net/integration/usage/index.md`
## When To Use
Use this reference for WeChat Official Account OAuth, openid retrieval, userinfo retrieval, token refresh, OAuth config inspection, or preparation for Official Account JSAPI payment.
## Agent Must Know
- Official Account openid is different from Mini Program openid.
- OAuth credentials should be configured through CloudBase Integration Center, not embedded in frontend code.
- The generated official-account function name may differ from example names such as `offiaccount-common`.
- OAuth route names must be confirmed from the generated function and official docs.
## Minimal Contract
Common generated OAuth routes include:
- `/oauth/config`
- `/oauth/token`
- `/oauth/refresh`
- `/oauth/userinfo`
- `/oauth/verify`
Typical flow:
1. Get OAuth config or construct the authorization URL according to the generated function contract.
2. Redirect the user to WeChat authorization.
3. Exchange the returned code for token/openid through the generated function.
4. Optionally fetch userinfo if the scope and product requirement allow it.
5. Store only the user identifiers and business-safe profile fields required by the app.
Example exchange shape:
```js
async function exchangeOfficialAccountCode(code) {
const response = await fetch("/api/wechat/oauth/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ code }),
});
const data = await response.json();
if (!data.openid) {
throw new Error("Missing Official Account openid");
}
return data;
}
```
Use the actual generated function path or an application backend wrapper instead of copying this path literally.
## Implementation Checklist
- Confirm the target is an Official Account web scenario.
- Confirm OAuth callback domain and redirect URI are configured.
- Confirm the generated function name and OAuth routes.
- Decide whether the product needs only openid or also userinfo.
- Store tokens securely if refresh is required.
- For JSAPI pay, pass the Official Account openid into the payment order creation flow.
## Do Not
- Do not expose AppSecret in browser code.
- Do not confuse Official Account openid with Mini Program openid.
- Do not request userinfo scope unless the product actually needs profile data.
references/cloudbase-wechat-integration/references/overview.md
# CloudBase WeChat Integration Overview
Official docs:
- `https://docs.cloudbase.net/integration/introduce/index.md`
- `https://docs.cloudbase.net/integration/usage/index.md`
## What Integration Center Provides
CloudBase Integration Center is a console-driven capability for connecting third-party services to CloudBase. For WeChat scenarios, it can generate HTTP cloud functions, inject configuration through managed environment variables, and handle platform-specific callback verification or decryption.
Use this skill for the application-side work around those integrations:
- generating client calls to the generated functions
- adding order persistence, idempotency, and fulfillment logic
- diagnosing callback, credential, and routing issues
- guiding the user through console setup without collecting secrets
## Agent Must Know
- Creation and credential binding are console-first unless official public API support is confirmed.
- Generated function names may vary. Examples such as `pay-common` and `offiaccount-common` are not a contract.
- Merchant secrets, private keys, APIv3 keys, AppSecret values, and certificates belong in CloudBase console configuration, not in source code.
- The payment callback or order-query result is the authoritative state for business fulfillment.
- Generated functions should be treated as platform-managed templates with safe business extensions, not as blank custom functions.
## Scenario Map
| User wording | Route |
| --- | --- |
| 小程序支付, 微信支付, `wx.requestPayment` | `mini-program-pay.md` |
| 公众号支付, JSAPI 支付, 微信内网页支付 | `official-account-jsapi-pay.md` |
| Native 支付, 扫码支付, 二维码支付 | `native-qr-pay.md` |
| 公众号授权, openid, userinfo, OAuth | `official-account-oauth.md` |
| 回调失败, 404, 凭证, openid 不匹配 | `troubleshooting.md` |
## Console-First Setup Checklist
1. Confirm the CloudBase environment ID.
2. Open Integration Center in the CloudBase console.
3. Choose the matching WeChat integration type.
4. Fill merchant or official-account credentials in the console form.
5. Record the generated function name and HTTP route paths.
6. Run a minimal call before adding business logic.
7. Add business data handling after the generated function works.
## Independent Distribution Notes
When this skill is installed alone:
- Use only the references in this directory plus the official docs above.
- If no CloudBase MCP tools are available, guide the user to inspect function logs and configuration in the console.
- Do not reference local repository paths that may not exist in the target platform.
references/cloudbase-wechat-integration/references/troubleshooting.md
# WeChat Integration Troubleshooting
Official docs:
- `https://docs.cloudbase.net/integration/introduce/index.md`
- `https://docs.cloudbase.net/integration/usage/index.md`
- `https://docs.cloudbase.net/integration/wechat-pay-miniprogram/index.md`
- `https://docs.cloudbase.net/integration/wechat-pay-jsapi-h5/index.md`
- `https://docs.cloudbase.net/integration/wechat-pay-native/index.md`
- `https://docs.cloudbase.net/integration/wechat-official-oauth/index.md`
## First Checks
1. Confirm the scenario: Mini Program Pay, JSAPI Pay, Native Pay, or Official Account OAuth.
2. Confirm the CloudBase environment ID.
3. Confirm the actual generated function name.
4. Confirm the exact route path from Integration Center or generated function docs.
5. Check cloud function logs before changing code.
6. Check whether the issue is credential setup, route mismatch, callback delivery, or business logic.
## Common Symptoms
### 404 or route not found
Likely causes:
- wrong function name
- wrong HTTP path
- calling Mini Program payment path from the wrong client
- generated function was deleted or redeployed incorrectly
Actions:
- inspect the generated function routes
- confirm the call target uses the actual function name
- check CloudBase function logs and HTTP access logs
### Missing credentials or credential initialization errors
Likely causes:
- Integration Center form is incomplete
- merchant certificate/APIv3 key/private key was not configured
- function environment variables were removed or overwritten
Actions:
- re-check Integration Center credential configuration in the console
- do not paste secrets into code or chat
- restore generated environment variables if they were overwritten
### Openid mismatch
Likely causes:
- Mini Program openid used for Official Account JSAPI pay
- Official Account AppID does not match merchant binding
- user authorized a different app than the one used for payment
Actions:
- identify whether the flow needs Mini Program openid or Official Account openid
- verify AppID and merchant binding
- rerun OAuth or Mini Program call in the correct client context
### Payment succeeds in frontend but order is not fulfilled
Likely causes:
- business logic trusts frontend success only
- callback did not reach the generated function
- callback handler is not idempotent
- order status query is missing
Actions:
- use callback or query as the authoritative payment state
- add idempotent order update logic
- inspect payment callback logs
- verify `out_trade_no` maps to the application's order record
### Callback not received
Likely causes:
- merchant platform notification URL is wrong
- callback path does not match generated function route
- APIv3 key/certificate mismatch prevents verification/decryption
- function security or deployment issue
Actions:
- check Integration Center callback configuration
- check merchant platform callback settings
- inspect generated function logs
- retry with a low-value test order after fixing configuration
### `callHTTPFunction is not a function`
Likely causes:
- Mini Program base library or CloudBase SDK capability is too old
- the project is not initialized with `wx.cloud.init`
- the flow is running outside Mini Program runtime
Actions:
- confirm Mini Program runtime and base library support
- initialize CloudBase with the canonical full environment ID
- use the correct client flow for Web/JSAPI/Native scenarios
## Before Editing Generated Code
- Keep generated credential handling intact.
- Add business logic around generated handlers instead of replacing verification/decryption logic.
- Preserve callback idempotency and order lookup.
- Keep secrets out of source code.
references/cloudbase-wechat-integration/references/virtual-payment.md
# Mini Program Virtual Payment (虚拟支付)
Official docs:
- `https://developers.weixin.qq.com/minigame/dev/wxcloud/guide/wechatpay/ai-virtualpayl-person.html`(AI 工具快速接入虚拟支付,含小游戏/小程序)
- `https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/virtual-payment`(企业/个体户接入指引)
- `https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/virtual-payment/person`(个人主体接入指引)
- Client API: `https://developers.weixin.qq.com/miniprogram/dev/api/payment/wx.requestVirtualPayment.html`
## When To Use
Use this reference for **虚拟支付**(virtual goods payment)flows: 道具直购、代币、`wx.requestVirtualPayment`、`xpay_*` 回调事件、OfferID / AppKey 签名、发货推送、查单兜底。
**与微信支付的边界**:虚拟支付走 MP 后台「虚拟支付」通道(`wx.requestVirtualPayment`, OfferID + AppKey 签名),与 Integration Center 生成的微信支付(`wx.requestPayment`,商户号 + APIv3)是**两套独立链路**。卖实物/服务用微信支付(见 `mini-program-pay.md`);卖虚拟道具/代币用本参考。
## Prerequisites
| 条件 | 说明 |
| --- | --- |
| 主体资质 | 个人 / 企业 / 个体户均可;个人主体需服务类目含「工具」,且**全终端月支付限额 10 万元** |
| 开通入口 | MP 后台 → 支付与交易 → 虚拟支付 → 开通 |
| 关键参数 | AppID(设置)、OfferID、现网 AppKey、沙箱 AppKey(均在 虚拟支付 → 基本配置) |
| 道具 | 虚拟支付 → 道具管理 创建并**发布**;发布后需等几分钟到半小时全平台同步,期间下单报 `COIN_OR_PRODUCT_ID_CREATED_IN_RECENTLY` |
| iOS 支付 | 需先配置「小程序简称」(Apple 展示名)并开通苹果 IAP;用户微信客户端需 **8.0.68+**,代码里先校验版本再拉起支付 |
## Sandbox vs 现网
| 模式 | 适用版本 | 限制 |
| --- | --- | --- |
| 沙箱 | 开发版 / 体验版 | 真机预览下会被 `PAYMENT_ILLEGAL_IN_SANDBOX` 拦截 |
| 现网 | 全版本 | iOS 真机需开通 IAP |
- 沙箱仅适合开发者工具内调试;真实联调用现网(`env: 0`、正式 AppKey)。
- 沙箱 AppKey 不要出现在生产代码里。
## Core Flow
```text
① 前端请求服务端下单 → 服务端生成唯一 outTradeNo,构造 signData,算 paySig + signature
② 前端调用 wx.requestVirtualPayment(payData) 拉起支付
③ 服务端确认支付并发货:
路径 A:收到 xpay_goods_deliver_notify 发货推送 → 幂等发货
路径 B:推送丢失时定时调 query_order 查单 → 已支付则补发货
④ 前端查服务端订单状态 → 展示购买成功
```
关键点:
- 发货以「发货推送」为主、`query_order` 查单兜底;**前端 success 回调不作为发货依据**。
- 幂等以平台单号 `wx_order_id`(回调里 `WeChatPayInfo.MchOrderNo`)去重。
- `outTradeNo` 每次下单重新生成、8-32 位、不能以下划线开头、不可复用。
### payData fields
| 字段 | 说明 |
| --- | --- |
| signData | JSON 字符串:`offerId` / `buyQuantity` / `env`(固定 0)/ `currencyType`(固定 CNY)/ `productId` / `goodsPrice`(单位:**分**,与后台道具价一致)/ `outTradeNo` / `attach`(透传,发货时原样返回) |
| mode | 道具直购固定 `short_series_goods` |
| paySig | 服务端用 **AppKey** 对 `requestVirtualPayment&signData` 做 HMAC-SHA256 |
| signature | 服务端用 **sessionKey**(`auth.code2Session` 获取)对 signData 做 HMAC-SHA256 |
### 签名规则
- `paySig` 消息体 = `uri + '&' + post_body`;`post_body` 必须与实际发出的请求体**完全一致**(不格式化、不改键顺序)。
- C 端下单 uri 固定 `requestVirtualPayment`;B 端服务接口(如 `/xpay/query_order`)用实际路径。
### Callback events (xpay_*)
| Event | 说明 | 处理 |
| --- | --- | --- |
| `xpay_goods_deliver_notify` | 道具发货通知 | 核心事件:幂等发货,返回 `<xml><ErrCode>0</ErrCode><ErrMsg><![CDATA[success]]></ErrMsg></xml>`,否则平台重试(最多 15 次) |
| `xpay_coin_pay_notify` | 代币支付通知 | 更新代币余额 |
| `xpay_refund_notify` | 退款通知 | 更新订单状态、回收道具 |
| `xpay_complaint_notify` | 用户投诉通知 | 记录并人工跟进 |
| `xpay_subscribe_signing_result_notify` | 订阅签约结果 | 更新订阅状态 |
| `xpay_subscribe_pay_fail_notify` | 订阅支付失败 | 提示用户 |
| `xpay_subscribe_ios_refund_query_notify` | iOS 订阅退款问询 | **3 秒内**返回 `result_code`(0=建议退款,1=拒绝),否则 Apple 连续问询 3 次后标「不确定」 |
发货推送核心字段:`OpenId`(发给谁)、`OutTradeNo`(业务单号)、`WeChatPayInfo.MchOrderNo`(平台单号)、`GoodsInfo.ProductId` / `GoodsInfo.Quantity`(发什么、发多少)。
### Query order (查单兜底)
`POST /xpay/query_order`(带 pay_sig 签名):
```json
{ "openid": "用户openid", "env": 0, "order_id": "业务单号 outTradeNo" }
```
> ⚠️ 参数名是 `order_id`(传 outTradeNo),不是 `out_trade_no`。建议每 5 分钟定时查一次未完成订单。
## Refunds & Settlement
| 终端 | 退款 | 结算周期 | 费率 |
| --- | --- | --- | --- |
| Android 等 | 开发者主动(MP 后台或 `refund_order` 接口) | T+3 | 1%(腾讯技术服务费) |
| iOS | ❌ 开发者无法主动退款;用户在 App Store → 购买记录申请,Apple 审批后推送 `xpay_refund_notify` | 约 45-60 天 | 12%(Apple 佣金) |
- 支付 180 天内退款平台退还手续费,超过 180 天不退。
- iOS 订单在前端「我的」等场景建议**隐藏退款入口**,改为引导用户去 App Store 申请。
## Common Errors
| 错误 | 原因 | 解法 |
| --- | --- | --- |
| `COIN_OR_PRODUCT_ID_CREATED_IN_RECENTLY` | 道具刚发布,平台同步延迟 | 等几分钟到半小时再试 |
| `PAYMENT_ILLEGAL_IN_SANDBOX` | 沙箱模式在真机预览下被拒 | 切现网(`env: 0` + 正式 AppKey) |
| 当前商户尚未开启 iOS 支付 | iOS 端 IAP 未开通 | MP 后台配置小程序简称 + 开通 IAP |
## On 微信云开发 (WeChat CloudBase)
用微信云开发承接时无需自建服务器/证书:云函数承担下单签名、回调处理、查单兜底,云数据库存订单。典型拆分:
- 下单云函数:生成业务单号、构造 signData、计算双签名,返回 payData
- 回调云函数:接收 `xpay_*` 推送,幂等校验后发放/回收道具(需在 MP 后台配置发货推送 URL 并订阅事件)
- 查单云函数:推送丢失时调 `query_order` 补发货,兼作订单/道具查询
配合 Nightly 微信开发者工具(≥ 2.02.2608312)与 `wechatide` CLI / IDE MCP,可自动完成云函数部署与消息推送订阅(见 `../miniprogram-development/SKILL.md` 的 DevTools 工作流)。
## Implementation Checklist
- [ ] 已开通虚拟支付,拿到 AppID / OfferID / 现网 AppKey
- [ ] 道具已创建**并发布**(留意同步延迟)
- [ ] iOS 支付:小程序简称已配置、IAP 已开通、客户端已校验微信 ≥ 8.0.68
- [ ] 签名实现与官方示例核对一致;`post_body` 与实际请求体逐字节一致
- [ ] 金额单位全程「分」,不换算;`env` 固定 0
- [ ] 发货推送已配置 URL,回调以 `wx_order_id` 幂等去重
- [ ] `query_order` 兜底查单已就绪
- [ ] 已向用户说明退款规则与费率(Android 1% / iOS 12%、个人主体月限额 10 万)
- [ ] 上线后小额真单验证:支付 → 推送 → 发货 → 后台账单金额一致
## Do Not
- Do not use 沙箱 AppKey or sandbox mode for production / real-device verification.
- Do not treat the frontend `wx.requestVirtualPayment` success callback as the fulfillment trigger.
- Do not reuse `outTradeNo` across orders.
- Do not attempt server-side refund for iOS orders (user-initiated via App Store only).
- Do not place AppKey / sessionKey in mini program client code.
- Do not mix this flow with Integration Center 微信支付 (`wx.requestPayment`) contracts.
references/cloudbase-wechat-integration/SKILL.md
---
name: cloudbase-wechat-integration
description: CloudBase WeChat integration guide for Mini Program WeChat Pay, Mini Program virtual payment (虚拟支付, wx.requestVirtualPayment), Official Account JSAPI Pay, Native QR-code Pay, Official Account OAuth, openid handling, payment callbacks, and CloudBase Integration Center generated functions. This skill should be used when users ask to add, debug, or extend WeChat payment, virtual payment, or official-account flows on CloudBase.
version: 2.33.1
alwaysApply: false
---
# CloudBase WeChat Integration
This skill routes WeChat payment and official-account work through CloudBase Integration Center. It gives the agent the stable execution contract and points to official `index.md` docs for console details that may change.
## Sibling skills (local only)
Sibling CloudBase skills ship beside this skill. Use local relative paths such as `../auth-tool-cloudbase/SKILL.md`.
If a referenced sibling skill file is missing from this environment, ask the user to install the full CloudBase plugin (or the missing skill). Do **not** HTTP-fetch remote skill or protocol markdown into the agent context.
Official CloudBase Integration Center docs (human reference — do not treat as skill markdown to fetch into agent context as a sibling skill substitute):
- CloudBase Integration Center overview: `https://docs.cloudbase.net/integration/introduce/index.md`
- CloudBase Integration Center usage: `https://docs.cloudbase.net/integration/usage/index.md`
- When cloud function deployment or log operations are needed and no sibling skill is available, use the current platform's CloudBase MCP tools or CloudBase console instead of guessing unsupported APIs.
## Activation Contract
### Use this first when
- The user asks about WeChat Pay, 小程序支付, 微信支付, JSAPI 支付, 公众号支付, Native 扫码支付, 二维码支付, refund callbacks, payment callbacks, `wx.requestPayment`, `WeixinJSBridge`, `openid`, or Official Account OAuth in a CloudBase app.
- The user asks about 虚拟支付 (virtual payment) for virtual goods: 道具直购, 代币充值, `wx.requestVirtualPayment`, OfferID, AppKey 签名, `xpay_*` callbacks (发货推送/查单/退款), or MP 后台虚拟支付开通与配置.
- The task mentions CloudBase Integration Center, 集成中心, generated payment functions, `pay-common`, `offiaccount-common`, or callback routing for WeChat payment.
- The user needs to extend a CloudBase Integration Center generated function with order persistence, idempotency, fulfillment, or payment-status sync.
### Then also read
- Mini Program structure and preview work -> `../miniprogram-development/SKILL.md` (if unavailable, use the current mini program platform docs and the mini-program payment reference in this skill)
- Web frontend work -> `../web-development/SKILL.md` (if unavailable, use the JSAPI or Native references in this skill)
- Cloud function runtime, logs, deployment, or gateway work -> `../cloud-functions/SKILL.md` (if unavailable, use CloudBase console/MCP function tools and the generated-function guidance in this skill)
### Do NOT use for
- Generic CloudBase Web Auth or Mini Program native identity work that does not involve WeChat payment or official-account OAuth.
- General CloudBase cloud function development unrelated to Integration Center generated functions.
- Creating or managing Integration Center instances through guessed MCP tools, guessed Manager SDK methods, or undocumented Cloud API actions.
- Storing merchant secrets, private keys, APIv3 keys, AppSecret values, or certificates in app source code, generated examples, README files, commits, or prompts.
## Operating Rules
1. Treat Integration Center creation as a console-first workflow unless a public Manager SDK or Cloud API contract is confirmed in official docs.
2. Use official `index.md` docs for console UI steps and credential fields; do not copy stale console screenshots or invent field names.
3. Never ask the user to paste secrets into chat. Tell them to configure merchant and official-account credentials in the CloudBase console Integration Center form.
4. Do not assume generated function names are fixed. `pay-common` and `offiaccount-common` are examples; ask for or inspect the actual function name before writing calls.
5. Treat frontend payment success as UI feedback only. The authoritative payment state must come from server-side query results or payment callbacks.
6. When extending generated functions, preserve credential environment variables and generated callback verification/decryption logic. Add business logic around order checks, persistence, idempotency, and fulfillment.
7. Before changing payment or callback code, identify the target scenario and load only the matching reference file.
## Routing
| Task | Read | Why |
| --- | --- | --- |
| Capability selection, console-first boundaries, independent distribution | `references/overview.md` | Establishes the Integration Center model and safety rules |
| Mini Program WeChat Pay, `wx.cloud.callHTTPFunction`, `wx.requestPayment` | `references/mini-program-pay.md` | Covers Mini Program openid injection, order creation, and callback expectations |
| Mini Program 虚拟支付, virtual goods, `wx.requestVirtualPayment`, `xpay_*` callbacks | `references/virtual-payment.md` | Covers OfferID/AppKey signing, sandbox vs 现网, delivery callbacks, query-order fallback, iOS IAP rules |
| Official Account JSAPI pay, H5 inside WeChat, `WeixinJSBridge.invoke` | `references/official-account-jsapi-pay.md` | Covers official-account openid and JSAPI invocation |
| Native QR-code pay for PC/Web checkout | `references/native-qr-pay.md` | Covers `code_url`, QR rendering, and polling/query flow |
| Official Account OAuth, openid/userinfo retrieval | `references/official-account-oauth.md` | Covers OAuth routes generated by the official-account integration |
| 404, missing credentials, openid mismatch, callback failures, logs | `references/troubleshooting.md` | Provides diagnosis steps before changing code |
## Quick Workflow
1. Classify the scenario: Mini Program Pay, Virtual Payment (虚拟支付), JSAPI Pay, Native Pay, Official Account OAuth, generated-function extension, or troubleshooting.
2. Load the matching reference and the official `index.md` docs linked there.
3. Confirm the actual CloudBase environment ID and generated function name.
4. Generate or modify only the required client/backend code; keep merchant credentials in Integration Center configuration.
5. Add order-status query, callback idempotency, and amount/order validation when payment state affects business data.
6. Verify through function logs, callback logs, and an end-to-end payment sandbox or low-value production test as appropriate.
## Minimum Self-Check
- Did I avoid guessing undocumented Integration Center management APIs?
- Did I use the actual generated function name instead of assuming `pay-common`?
- Did I keep all merchant secrets and certificates out of source code and chat?
- Did the payment flow rely on callback/query state rather than only frontend success?
- Did I load only the scenario reference needed for the user's task?
## Reference index
All packaged reference files (required for skill lint reachability):
- [mini-program-pay.md](references/mini-program-pay.md)
- [native-qr-pay.md](references/native-qr-pay.md)
- [official-account-jsapi-pay.md](references/official-account-jsapi-pay.md)
- [official-account-oauth.md](references/official-account-oauth.md)
- [overview.md](references/overview.md)
- [troubleshooting.md](references/troubleshooting.md)
references/cloudrun-development/references/image-deploy-troubleshooting.md
# CloudRun container deploy failure — diagnosis reference
Read this when `deploy_failed`, Pod never becomes ready, readiness/probe fails, or a third-party `imageUrl` will not stay up.
Follow the behavioral SOP in `../SKILL.md` (**docs → runtime logs → config**). This file expands signal tables. Do **not** start from a specific image name.
Official networking/VPC issues belong in [vpc-and-database.md](vpc-and-database.md), not here.
## 1. Pre-deploy: five facts from the image's own docs
Before the first `manageCloudRun(action="deploy")` with `imageUrl` (or a `FROM <image>` Dockerfile), open the **image's official run docs** (not just Docker Hub tags) and fill this table. Guessing from common defaults is how probe-delay misdiagnosis starts.
| # | Fact | Where it usually lives | CloudRun field |
| --- | --- | --- | --- |
| 1 | Foreground start command | `docker run … <cmd>`, compose `command:`, Dockerfile `ENTRYPOINT`/`CMD` | `serverConfig.EntryPoint`, `serverConfig.Cmd` |
| 2 | Listen port | `-p host:container`, compose `ports:`, docs "listens on" | `serverConfig.Port` and `EnvParams.PORT` if the app honors `PORT` |
| 3 | Bind-address / public-listen env | docs saying "set HOST=0.0.0.0" / "API disabled by default" | `serverConfig.EnvParams` |
| 4 | Required data directory | `-v host:container`, compose `volumes:` | `serverConfig.VolumesConf` (`VolumePath` = container path) |
| 5 | Health / ready signal | `/health`, `/ready`, or "probe the listen port" | CloudRun readiness probes the **service port**; a path-only health check does not replace a process that never binds |
Rules:
- Do **not** assume `80` or `3000`. Many images ignore platform `PORT`.
- Do **not** assume the image's default `CMD` is the HTTP server. Some images idle, print help, or start a supervisor with no app child.
- Bind address `127.0.0.1` inside the container makes the platform probe fail even when local `curl` inside the namespace would work.
- If docs require a volume and you omit it, the process may exit on first write — that looks like a probe failure.
## 2. After failure: which log, then which class
### 2.1 Pick the right log
| Deploy type | Build log `getDeployLog` | Runtime log `getProcessLog` |
| --- | --- | --- |
| Cloud source build (`targetPath`, CODING) | Yes — compile/package first | Yes — after the image exists |
| Existing image (`imageUrl`, `DeployType=image`) | **Skip** (no build; CODING-unaware accounts error) | **Yes** — this is the diagnosis tool |
```json
{
"action": "getProcessLog",
"detailServerName": "my-svc",
"runId": "<from latestDeploy.RunId>"
}
```
`getProcessLog` returns deploy-step lines (for example `create_version_check_vpc` / `create_eks_virtual_service` / `check_eks_virtual_service`) plus container stdout/stderr (supervisor, app, probe reason). **Image and source deploys both work; it does not depend on CODING.**
### 2.2 Classify: scheduling vs port vs exit-on-start
Pull logs **twice**, 20–40 seconds apart. Compare the two `processLogText` dumps.
**Startup logs exist ≠ the service is running.** A boot banner, s6/tini line, or "starting …" is not a ready listener.
| Class | Typical signals (first pull) | Two-pull comparison | What to change | What not to change |
| --- | --- | --- | --- | --- |
| **A. Pod still scheduling / pulling** | Only deploy-step lines; empty or truncated container stdout; image-pull errors | Second pull still has no app stdout, or still stuck on pull | Registry access, image size, switch to source-build `FROM` (section 4) | `InitialDelaySeconds` |
| **B. Port / bind problem** | App stays up; "listening on 127.0.0.1" or a port ≠ `serverConfig.Port`; probe failed on connection refused | Same PID / no repeated boot banner; listener line does not match the probe port or is loopback-only | `Port`, bind-address env, `Cmd` that actually starts the HTTP server | `InitialDelaySeconds` |
| **C. Exit-on-start / restart loop** | Crash, missing command, usage/help text, supervisor respawn, `exit 0/1` immediately | **Same boot banner / same first N lines repeat** (new timestamps, same sequence) | `Cmd`/`EntryPoint`, required env, volume, supervisor workaround (section 5) | `InitialDelaySeconds` |
Only if class is **none of the above** — two pulls show **one continuous boot** (new init lines, no banner repeat) and the process has **not listened yet** — may the start be genuinely slow. Then raise `InitialDelaySeconds` (see section 3).
### 2.3 How to read a restart loop
1. Copy the first distinctive line after PID 1 (banner, "starting gateway", "s6-supervise").
2. Search that line in the same log. **Two or more copies with advancing timestamps** ⇒ respawn storm.
3. Confirm with a second `getProcessLog`: if the newest chunk is again that banner rather than later "listening" lines, the process is not making progress.
4. A single "listening" line that never updates, followed by probe failures, is class B, not a slow start.
## 3. Readiness probe mechanics
After deploy steps finish:
1. Wait **N** seconds (`serverConfig.InitialDelaySeconds`).
2. Probe the service **port** about every **5 seconds**.
3. About **30** consecutive failures ⇒ this deploy is failed.
4. Failure window ≈ **N + 150 seconds**. This is **not** "declare failed N seconds after deploy".
**Forbidden default:** `probe failed` / `deploy_failed` ⇒ set `InitialDelaySeconds=120`.
Raising N only **delays the same failure** when the process is crash-looping or bound to loopback. It helps only when logs prove the process is still doing one-shot init (JVM warmup, migrations) and will listen if given more of the window.
## 4. `docker.io` / Docker Hub pull loops → source build
When `imageUrl` points at a public registry (`docker.io`, Docker Hub) and deploys fail in class A with repeated pull errors:
1. Stop retrying the same `imageUrl` on CloudRun nodes.
2. Add a local Dockerfile whose only job is to let **CODING** pull the public image:
```dockerfile
FROM docker.io/example/app:latest
```
3. Deploy with `targetPath` (cloud source build), not `imageUrl`.
4. CODING build machines pull `docker.io`; the build artifact is stored in CCR; CloudRun nodes pull from the **intranet**.
Keep `Cmd` / `EnvParams` / `VolumesConf` / `Port` from section 1 on that source deploy. Source build fixes **pull topology**, not a wrong start command.
Private registries (`ghcr.io`, Harbor) still use the local-pull → tag → push-to-CCR path in `../SKILL.md`.
## 5. Supervisor images (s6 / tini / supervisord)
If PID 1 is `s6-svscan`, `tini`, `supervisord`, or similar:
- The line you see first is often the **supervisor**, not the HTTP app. Search logs for the child you documented in section 1.
- Respawn storms look like class C: the same child start line every few seconds.
- `pgrep -f` / duplicate-instance guards can match **container PID 1's argv** and kill/respawn the real process. If the image's issue tracker documents this, apply that workaround (absolute `Cmd`, flags such as `--no-supervise`) instead of probe-delay tuning.
- Workaround must come from **that image's docs or issues**. Do not invent supervisor flags.
## Appendix — worked example: hermes-agent
This appendix is **one** application of sections 1–5. Do not skip the generic SOP because the image name matches.
**Image:** `docker.io/nousresearch/hermes-agent:latest`
**Docs:** https://hermes-agent.nousresearch.com/docs/user-guide/docker
### What the docs require (section 1)
| Fact | From official Docker docs | Typical CloudRun mapping |
| --- | --- | --- |
| Cmd | `gateway run` (compose `command: gateway run`) | `Cmd: ["gateway", "run"]` |
| Port | `8642` (API + health); dashboard `9119` is separate | `Port: 8642` — do not use `3000` |
| Bind / API env | API is **off** by default; loopback by default | `API_SERVER_ENABLED=true`, `API_SERVER_HOST=0.0.0.0`, `API_SERVER_KEY=…`, optional `API_SERVER_CORS_ORIGINS` |
| Volume | `-v ~/.hermes:/opt/data` | `VolumesConf` with `VolumePath: "/opt/data"` |
| Health | Port 8642 health endpoint once the API server is enabled | Probe succeeds only if the API server is on and bound to `0.0.0.0` |
### Wrong path (observed)
Assistants saw readiness probe failed / `deploy_failed` and repeatedly set `InitialDelaySeconds` from `2` to `120`. The probe window was already ~N+150s. The process was not "still warming up".
### Log class (section 2)
Two `getProcessLog` pulls showed the same boot sequence repeating (class **C**), not a single long init (slow start). Supervisor/s6 lines were present; that is **not** proof the gateway API was listening on `0.0.0.0:8642`.
### Root cause
1. Default image command did not run `gateway run`.
2. API server env was missing, so nothing public listened on the probe port.
3. `/opt/data` was not mounted.
4. Direct `docker.io` `imageUrl` also hit node pull issues — section 4 (Dockerfile `FROM docker.io/nousresearch/hermes-agent:latest` + source build) addresses pull, not Cmd/env.
### Supervisor workaround (section 5)
Known issue [NousResearch/hermes-agent#14128](https://github.com/NousResearch/hermes-agent/issues/14128): gateway duplicate-instance detection uses `pgrep -f` and can match container PID 1's path, causing a respawn storm. Documented direction: **absolute Cmd path** plus `--no-supervise`. Confirm against the current issue before copying flags.
### Correct diagnosis sentence
Not "probe delay too short". Instead: **missing start command + API bind env + data volume**, with source build only as a **pull** workaround.
references/cloudrun-development/references/vpc-and-database.md
# CloudRun VPC and Database Connectivity
Use this reference when deploying **existing / third-party apps** (GitHub projects, Docker images, classic backends) that talk to databases over **TCP connection strings**, not CloudBase SDK APIs.
Official docs: [VPC configuration for CloudBase Run](https://docs.cloudbase.net/run/deploy/networking/vpc)
## Critical distinction
| Concept | What it controls | Typical field |
| --- | --- | --- |
| **Ingress access type** | How callers reach the CloudRun **service** (public HTTPS, mini program, VPC-only ingress) | `OpenAccessTypes` |
| **Egress / private network** | Whether CloudRun **instances** join a VPC so they can reach MySQL / PostgreSQL / Redis / CVM inside that VPC | `serverConfig.VpcConf` |
These are independent. A service can be publicly reachable (`OpenAccessTypes: ["PUBLIC"]`) **and** still need `VpcConf` so the process can open a TCP connection to a private database.
## VPC bind timing (important)
Evidence and docs are mixed; do **not** assume delete+recreate is always required.
What we know:
1. **`@cloudbase/manager-node` < 5.6.2** silently dropped `VpcConf` in `parseObjectToDiffConfigItem`, so MCP/CLI deploy never sent VPC on create **or** update. That alone can look like “update ignored VPC”.
2. **`>= 5.6.2`** serializes `VpcConf` into deploy `Items` for both `CreateCloudRunServer` and `UpdateCloudRunServer`. MCP also maps `VpcConf` → top-level `vpcInfo` (`CreateType: 2`) on create.
3. **Current CloudBase docs** say VPC can be set at create **or** changed later in service settings ([VPC configuration](https://docs.cloudbase.net/run/deploy/networking/vpc)). An older product page said VPC cannot be changed; treat that as outdated unless you observe otherwise.
Practical agent rules:
1. Always pass `serverConfig.VpcConf` when the app needs TCP access to a VPC DB/cache.
2. Prefer correct VPC on **first** create.
3. If the service already exists:
- Prefer `manageCloudRun(action="updateConfig")` to change VPC / EnvParams / MinNum without re-uploading code (console-aligned `SubmitServerConfigChangeDiff`).
- Or redeploy with `VpcConf`. MCP **deploy** uses Read-Merge-Write: omitting `VpcConf` / partial `EnvParams` / omitting `OpenAccessTypes` **preserves** remote values (set `envParamsReplaceAll=true` only when you intend a full env replace).
- Then **verify** with `queryCloudRun(action="detail")` (`ServerConfig.VpcConf`).
4. If detail still shows missing/wrong VPC after a successful update, fall back to console network settings or delete + recreate — do not loop blind redeploys.
## When VpcConf is mandatory
Treat VPC binding as **required** before deploy when **any** of these signals appear:
- Env vars: `DATABASE_URL`, `DB_HOST`, `MYSQL_*`, `POSTGRES_*`, `PGHOST`, `PG_*`, `REDIS_*`, `MONGO_*`, `SQLALCHEMY_DATABASE_URI`, `SPRING_DATASOURCE_*`
- Connection URLs: `postgres://`, `postgresql://`, `mysql://`, `redis://`, `mongodb://`
- Project files: `docker-compose*.yml` with db/redis services, `.env.example` with DB hosts, ORM configs pointing at a host:port
- User intent: "use CloudBase MySQL / TencentDB / self-hosted PG / Redis in VPC"
## When VpcConf is NOT required for database access
- Browser or Mini Program apps that only use CloudBase PG via `app.rdb()` / PG HTTP gateway
- Backends that only use CloudBase NoSQL / storage SDKs over public CloudBase APIs
- Pure compute services with no VPC-private dependencies
If the user says "PostgreSQL" but the app is a classic TCP client (for example new-api, WordPress, Ghost, most Go/Java/Python ORMs), do **not** assume CloudBase PG SDK mode is a drop-in. Prefer a TCP-reachable database in the same VPC, or explicitly redesign the app to use the SDK/gateway.
## Mandatory deploy sequence (existing app + DB)
1. **Detect** DB / cache dependency signals in code and env templates.
2. **Choose DB shape**
- Existing TCP app → CloudBase MySQL / TencentDB / other VPC DB with private hostname
- New CloudBase-native app → CloudBase PG + `app.rdb()` (no CloudRun VPC required for gateway access)
3. **Resolve network**
- VPC and subnet must be in the **same region** as the CloudRun service
- Prefer the **same VPC** as the database
- Ensure the subnet has enough free IPs for CloudRun instances
- **Do NOT invent** `vpc-` / `subnet-` IDs or paste doc placeholders into a real deploy
- Resolve real IDs from the DB console, an existing working resource detail, `callCloudApi` describe APIs, or the user — **stop** if still unknown
4. **Configure security groups / allowlists** so the CloudRun subnet can reach the DB port (typically 3306 / 5432 / 6379).
5. **Set env vars to private endpoints** (intranet host), not public endpoints, unless the user explicitly requires public access and has opened the allowlist.
6. **Deploy with both** public ingress (if needed) **and** `VpcConf`:
```json
{
"action": "deploy",
"serverName": "my-existing-app",
"targetPath": "/abs/path/to/app",
"serverConfig": {
"OpenAccessTypes": ["PUBLIC"],
"Cpu": 0.5,
"Mem": 1,
"MinNum": 1,
"MaxNum": 5,
"EnvParams": "{\"DATABASE_URL\":\"postgres://user:pass@10.x.x.x:5432/db\"}",
"VpcConf": {
"VpcId": "vpc-xxxxxxxx",
"SubnetId": "subnet-xxxxxxxx"
}
}
}
```
7. **Verify** with `queryCloudRun(action="detail")` that VPC is attached, then hit an app health/db-check endpoint or inspect runtime logs for connection errors.
## Do not ship this anti-pattern
```json
{
"serverConfig": {
"OpenAccessTypes": ["PUBLIC"],
"EnvParams": "{\"DATABASE_URL\":\"postgres://...private-host...\"}"
}
}
```
Missing `VpcConf` here commonly yields deploy **success** followed by runtime `ECONNREFUSED`, timeout, or "could not connect to server".
## Troubleshooting
| Symptom | Likely cause | Fix |
| --- | --- | --- |
| Deploy OK, app cannot connect to DB | Missing/wrong VPC, or old SDK dropped `VpcConf` | Use `updateConfig` or redeploy with `VpcConf` on SDK >= 5.6.2; verify via `detail`; if still wrong, console or recreate |
| Redeploy wiped console VPC / env keys | Partial deploy without merge (legacy) | Current MCP deploy RMW preserves remote `VpcConf` / EnvParams keys / `OpenAccessTypes`; prefer `updateConfig` for config-only changes |
| Timeout to DB IP | Security group / ACL | Allow CloudRun subnet CIDR on DB port |
| Works locally, fails on CloudRun | Using `localhost` / docker-compose hostname | Replace with VPC private address |
| Connected VPC but lost outbound Internet | Public egress disabled without NAT | Keep platform public egress, or add NAT gateway in VPC |
| User asked for "PG" but SDK APIs fail in existing app | Protocol mismatch | Keep TCP DB + VPC, or refactor to `app.rdb()` |
## Agent checklist (copy into plan before deploy)
- [ ] DB dependency signals scanned
- [ ] TCP vs CloudBase SDK/gateway path decided
- [ ] `VpcId` + `SubnetId` resolved (same region as DB)
- [ ] Private connection string prepared
- [ ] Security group / allowlist planned
- [ ] `manageCloudRun` deploy includes `serverConfig.VpcConf`, **or** `updateConfig` set VPC after create
- [ ] Post-deploy connectivity verified
references/cloudrun-development/SKILL.md
---
name: cloudrun-development
description: CloudBase Run backend development rules (Function mode/Container mode). Use this skill when deploying backend services that require long connections, multi-language support, custom environments, AI agent development, or migrating existing/GitHub apps that need VPC access to MySQL/PostgreSQL/Redis. Also use when diagnosing CloudRun container deploy failures (deploy_failed, readiness/probe failed, image won't start, docker.io pull loops). For stateless HTTP services, prefer HTTP cloud functions.
version: 2.33.1
alwaysApply: false
---
## Sibling skills (local only)
Sibling CloudBase skills ship beside this skill. Use local relative paths such as `../auth-tool-cloudbase/SKILL.md`.
If a referenced sibling skill file is missing from this environment, ask the user to install the full CloudBase plugin (or the missing skill). Do **not** HTTP-fetch remote skill or protocol markdown into the agent context.
**Cross-cutting protocols** (required before writing HTTP handlers or deploying images):
- Sensitive Runtime Data Protection: `../cloudbase-platform/references/protocols/sensitive-runtime-data-protection.md`
- Deployment Gate: `../cloudbase-platform/references/protocols/deployment-gate.md`
# CloudBase Run Development
## Activation Contract
### Use this first when
- The task is to initialize, run, deploy, inspect, or debug a CloudBase Run service.
- The request needs a long-lived HTTP service, SSE, WebSocket, custom system dependencies, or container-style deployment.
- The task is to create or run an Agent service on CloudBase Run.
- The task migrates an **existing / GitHub / third-party** backend that uses classic `DATABASE_URL` / TCP database clients.
- The service requires a **stable independent process** (long connections, custom runtime, VPC database access) — see the 「云托管 vs HTTP 云函数」 decision section below. A Dockerfile alone is **not** a strong trigger.
### Read before writing code if
- You still need to choose between Function mode and Container mode.
- The prompt mentions `queryCloudRun`, `manageCloudRun`, Dockerfile, service domains, or public/private access.
- The app depends on MySQL, PostgreSQL, Redis, or other VPC-private resources over TCP → also read `references/vpc-and-database.md`.
- You are choosing between CloudRun and HTTP cloud functions for a stateless HTTP service.
- Container deploy fails (`deploy_failed`, Pod not ready, readiness/probe failed, third-party `imageUrl` won't stay up) → also read `references/image-deploy-troubleshooting.md` and follow the **Container deploy failure SOP** below. Do not start by raising `InitialDelaySeconds`.
### Then also read
- Cloud functions instead of CloudRun -> `../cloud-functions/SKILL.md`
- Agent SDK and AG-UI specifics -> `../cloudbase-agent/SKILL.md`
- Web authentication for browser callers -> `../auth-web-cloudbase/SKILL.md`
- Existing app + TCP database networking -> `references/vpc-and-database.md`
- Container image deploy failure / probe / `deploy_failed` -> `references/image-deploy-troubleshooting.md`
### Do NOT use for
- Simple Event Function or HTTP Function workflows that fit the function model better.
- Frontend-only projects with no backend service.
- Database-schema design tasks.
### Common mistakes / gotchas
- Choosing CloudRun when the request only needs a normal cloud function.
- Forgetting to listen on the platform-provided `PORT`.
- Treating CloudRun as stateful app hosting and storing important state on local disk.
- Assuming local run is available for Container mode.
- Opening public access by default when the scenario only needs private or mini-program internal access.
- **Deploying an existing app with `DATABASE_URL` / MySQL / PostgreSQL / Redis but omitting `serverConfig.VpcConf`** — deploy appears to succeed, then runtime DB connections fail.
- Confusing `OpenAccessTypes` (how users reach the service) with `VpcConf` (how the service reaches VPC databases).
- **Deploying to an environment that has not initialized CloudRun** — `CreateCloudRunServer` on an environment with no 大租户 record silently lands in the legacy 小租户 path, creating wrong small-tenant services/versions. Always ensure the environment is initialized first (`manageCloudRun(action="initEnv")`, tcbr) before the first deploy. `manageCloudRun(action="deploy")` now blocks new-service creation on uninitialized environments with guidance.
- **Using the legacy `tcb` CloudRun API** (`CreateCloudBaseRunResource` / `DescribeCloudBaseRunResource` / `DeleteCloudBaseRunResource`) — these are deprecated 小租户 open APIs and are blocked in `callCloudApi`. CloudRun always goes through `tcbr` (`CreateCloudRunEnv` / `CreateCloudRunServer`). Query a single environment's base info / whether CloudRun is enabled with `DescribeEnvBaseInfo` (`EnvId` required) — use `manageCloudRun(action="initEnv")` to open and `queryCloudRun(action="envStatus")` to poll status; query the environment list / resource info with `DescribeCloudRunEnvs` (`EnvId` optional filter).
- **Deploying `httpbin` / request-echo images or returning `req.headers` / `process.env`** — CloudBase may inject `x-cloudbase-context` (base64 temporary credentials). Echoing it leaks account cloud access. Follow `../cloudbase-platform/references/protocols/sensitive-runtime-data-protection.md`.
- **Seeing readiness probe failed / `deploy_failed` and immediately raising `InitialDelaySeconds`** — the probe window is already ~N+150s; crash loops and loopback binds are not slow-start. Follow the Container deploy failure SOP.
- **Deploying a third-party image without reading its run docs** — missing `Cmd`, bind-address env, or `VolumesConf` looks identical to a probe failure.
- **Calling `getDeployLog` for `imageUrl` deploys** — that is CODING build log; use `getProcessLog`.
- **Treating startup banners as proof the service is healthy** — pull `getProcessLog` twice and compare; a repeated boot sequence is a restart loop.
### Minimal checklist
- Choose Function mode or Container mode explicitly.
- **Confirm the environment has CloudRun initialized before the first deploy** — a brand-new environment must call `CreateCloudRunEnv` (tcbr) first; never `CreateCloudRunServer` on an uninitialized environment (it falls back to the legacy 小租户 path). `manageCloudRun(action="deploy")` validates this automatically and blocks new services on uninitialized environments. When blocked, first call `manageCloudRun(action="initEnv", envId=...)` (异步开通) and poll `queryCloudRun(action="envStatus")` until `Status=normal`, or reconsider an HTTP cloud function to bypass CloudRun entirely.
- Confirm whether the service should be public, VPC-only, or mini-program internal (**ingress**).
- If the app uses TCP databases/caches, resolve and set `VpcConf` (**egress / private network**) before deploy — see `references/vpc-and-database.md`.
- Keep the service stateless and externalize durable data.
- Use absolute paths for every local project path.
- Confirm handlers never echo `x-cloudbase-context`, full headers, or credential env vars; do not deploy httpbin-style reflectors.
- For third-party images, complete the five-item docs checklist (Cmd / port / bind env / volume / health) before deploy.
## Overview
Use CloudBase Run when the task needs a deployed backend service rather than a short-lived serverless function.
### 云托管 vs HTTP 云函数(按需求选,不按文件选)
> 核心原则:**HTTP 云函数优先**。只有需求真正需要云托管时才用云托管;有 `Dockerfile` 不等于必须上云托管。
**HTTP 云函数更合适(优先):**
- 无状态 HTTP 服务,监听 `PORT`/`9000`,只做「请求进来 → 处理 → 响应」的响应式逻辑
- 短生命周期请求,无长连接需求(SSE/WebSocket 之外的普通 API、CRUD、转发)
- 不需要自定义系统依赖 / 多语言运行时,标准 runtime 足够
- 部署更快、费用更低(按请求计费,可缩容到 0)、**无需初始化云托管环境**
- 有 `Dockerfile` 但服务本质是无状态 HTTP → 优先 HTTP 云函数(HTTP Function / Custom Image HTTP Function),不必上云托管
**云托管才需要(只有以下之一才选云托管):**
- 长连接:WebSocket、SSE 长连接、服务端推送
- 自定义系统依赖 / 任意语言运行时 / 需要稳定独立进程
- VPC 内数据库 / Redis 访问(`VpcConf` 私有网络连通)
- Agent 服务(Function mode CloudRun)
- 迁移已有 / GitHub / 第三方应用,或需要常驻进程
**决策示例:** 一个带 `Dockerfile` 的 Go/Python HTTP API,无长连接、无自定义运行时、不碰 VPC 数据库 → 选 HTTP 云函数而不是云托管;同一份代码若有 WebSocket 长连接 → 才选云托管。
### When CloudRun is a better fit
- Long connections: WebSocket, SSE, server push
- Long-running request handling or persistent service processes
- Custom runtime environments or system libraries
- Arbitrary languages or frameworks
- Stable external service endpoints with elastic scaling
- AI Agent deployment on Function mode CloudRun
- Migrating existing containerized or multi-language apps that need VPC access to databases
## Mode selection
| Dimension | Function mode | Container mode |
| --- | --- | --- |
| Best for | Fast start, Node.js service patterns, built-in framework, Agent flows | Existing containers, arbitrary runtimes, custom system dependencies |
| Port model | Framework-managed local mode, deployed service still follows platform rules | App must listen on injected `PORT` |
| Dockerfile | Not required | Required — but a Dockerfile alone does **not** mean CloudRun; first check whether the service needs long connections / custom runtime. Stateless HTTP services with a Dockerfile may fit HTTP cloud functions better. |
| Local run through tools | Supported | Not supported |
| Typical use | Streaming APIs, low-latency backend, Agent service | Custom language stack, migrated container app |
## How to use this skill (for a coding agent)
1. **Choose mode first**
- Function mode -> quickest path for HTTP/SSE/WebSocket or Agent scenarios
- Container mode -> use when Docker/custom runtime is a real requirement
2. **Follow mandatory runtime rules**
- Listen on `PORT`
- Keep the service stateless
- Put durable data in DB/storage/cache
- Keep dependencies and image size small
- Respect resource ratio guidance: `Mem = 2 × CPU`
3. **Use the correct tools**
- Read operations -> `queryCloudRun`
- Write operations -> `manageCloudRun`
- Delete requires explicit confirmation and `force: true`
- Always use absolute `targetPath`
4. **Follow the deployment sequence**
- Initialize or download code
- For a brand-new environment, ensure CloudRun is initialized first — call `manageCloudRun(action="initEnv", envId=...)` (async, idempotent) before the first deploy; `manageCloudRun(action="deploy")` blocks new services on uninitialized environments and tells you to call `initEnv`
- For Container mode, verify Dockerfile
- **Scan for DB/cache dependency signals** (`DATABASE_URL`, docker-compose DB services, ORM configs)
- If TCP DB access is required, complete the VPC checklist in `references/vpc-and-database.md` **before** deploy
- Local run when available
- Configure ingress access model **and** egress `VpcConf` when needed
- For `imageUrl` / third-party images, complete the **five-item docs checklist** in the Container deploy failure SOP before deploy
- Deploy and verify detail output + DB connectivity
- If deploy fails, follow the Container deploy failure SOP (`references/image-deploy-troubleshooting.md`) — docs → `getProcessLog` → config; do not start with `InitialDelaySeconds`
## Tool routing
### Read operations
- `queryCloudRun(action="list")` -> list services
- `queryCloudRun(action="detail")` -> inspect one service and its latest deploy status when available
- `queryCloudRun(action="templates")` -> see available starters
- `queryCloudRun(action="getDeployLog")` -> **构建日志**(CODING / `DescribeCloudRunBuildLog`)。**仅云端源码构建有意义**;已有镜像部署(`imageUrl`)没有构建过程,不要用它诊断镜像部署失败。未登录 CODING 的账号会报错(如 `User not created or may not qcloud user`)
- `queryCloudRun(action="getProcessLog")` -> **运行日志**(`tcbr/DescribeCloudRunProcessLog`)。返回部署阶段步骤(如 `create_version_check_vpc` / `create_eks_virtual_service` / `check_eks_virtual_service`)+ 容器启动/运行日志(s6-overlay、应用进程、readiness probe 失败原因)。**镜像部署与源码构建均可用,不依赖 CODING**。参数:`detailServerName`/`serverName` + 可选 `runId`(不传则取最新部署的 `RunId`;`RunId` 也可从 `detail` / `getDeployRecords` 的 `latestDeploy.RunId` 取得)
- `queryCloudRun(action="getDeployRecords")` -> list deploy records (newest first; includes `BuildId` / `RunId` / `FlowRatio` / `Status`) — use to review release history and rollback context before a traffic operation
- `queryCloudRun(action="envStatus")` -> check whether the environment's CloudRun is opened and its provisioning status (`Status=creating` opening / `normal` opened) — use after `initEnv` to poll progress or before `deploy` to confirm readiness
### Log query SOP(构建日志 vs 运行日志)
部署失败排查时**必须区分**两类日志,不要只用 `getDeployLog`:
1. **云端源码构建**(传 `targetPath`、走 CODING 构建)
- 先 `queryCloudRun(action="getDeployLog", detailServerName=..., buildId=...)` 查**构建日志**(编译/打包失败)
- 再 `queryCloudRun(action="getProcessLog", detailServerName=..., runId=...)` 查**运行日志**(部署步骤 + 容器启动/健康检查)
2. **已有镜像部署**(传 `imageUrl`、`DeployType=image`)
- **跳过** `getDeployLog`(无构建过程;且依赖 CODING,未登录会直接失败)
- 直接 `queryCloudRun(action="detail")` 或 `getDeployRecords` 取 `latestDeploy.RunId`,再 `getProcessLog` 查运行日志
```json
{
"action": "getProcessLog",
"detailServerName": "my-svc",
"runId": "<from latestDeploy.RunId>"
}
```
### Write operations
- `manageCloudRun(action="initEnv")` -> **open (initialize) CloudRun for the environment** — async, idempotent (`Status=normal` → already opened, no re-create). Use on a brand-new environment before the first deploy, or when `deploy` is blocked with an "尚未初始化云托管" message. Params: `envId` (defaults to the configured env), `packageType` (default `Trial`). Poll `queryCloudRun(action="envStatus")` until `Status=normal`.
- `manageCloudRun(action="init")` -> create local project
- `manageCloudRun(action="download")` -> pull remote code
- `manageCloudRun(action="run")` -> local run for Function mode
- `manageCloudRun(action="deploy")` -> trigger deploy + **lightweight wait for registration** (does not hang for full build). Returns `buildId` / `runId` / `taskId` + **DeployType-aware `next_step`**: **source** → `getDeployLog` then `getProcessLog`; **image** (`imageUrl`, BuildId often `0`) → **skip `getDeployLog`**, use `getDeployRecords`/`detail` for `RunId` then `getProcessLog`. Follow the returned `next_step` — do not always poll build logs. Existing services: RMW preserves remote VpcConf / EnvParams keys / OpenAccessTypes; **new services automatically validate that the environment's CloudRun is initialized** — if not, deploy is blocked with guidance to call `initEnv` first
- `manageCloudRun(action="updateConfig")` -> config-only update (no code upload; VPC / EnvParams / scaling / access types)
- `manageCloudRun(action="traffic")` -> **traffic management / canary release** (aligns with `tcb cloudrun traffic`): `trafficOp="set"` adjusts the stable/canary traffic ratio (`stablePercent` + `canaryPercent` must equal 100, e.g. 90/10); `trafficOp="promote"` promotes the canary version to full release (100%, closes gray release, irreversible); `trafficOp="rollback"` rolls back to the previous stable version (stops the releasing canary). Check `queryCloudRun(action="getDeployRecords")` first to understand current versions and traffic
- `manageCloudRun(action="delete")` -> delete service
- `manageCloudRun(action="createAgent")` -> create Agent service
## Deploying an existing image (imageUrl)
> 已有一个现成镜像(本地构建好、或第三方发布)时,不需要本地源码目录,直接 `manageCloudRun(action="deploy")` 传入 `imageUrl` 即可,走 `DeployType="image"`(容器型)部署,`targetPath` 可省略。若用户明确提到使用某个镜像或无需重新构建代码,**必须传 imageUrl**,不要仅因本地有源码目录就回退到源码构建。
**决策路径(直填 vs 本地中转):**
1. **公网匿名可拉取**(如 `ccr.ccs.tencentyun.com/...`、公开 Docker Hub 镜像)→ **直填 imageUrl**:`manageCloudRun(action="deploy", serverName=..., imageUrl="ccr.ccs.tencentyun.com/ns/img:v1", serverConfig={...})`。CloudBase 会直接拉取该 registry 地址构建部署。**若 `docker.io` / Docker Hub 在节点上反复拉取失败**,不要空转重试:改用 Dockerfile `FROM <public-image>` + `targetPath` 源码构建(CODING 拉公网镜像,产物进 CCR 内网拉取)。见下方 SOP 第 4 步。
2. **私有 / 需登录的 registry**(`ghcr.io`、私有 ECR/Harbor 等)→ **本地中转到 CCR**:
```
docker pull ghcr.io/example/app:latest
docker tag ghcr.io/example/app:latest ccr.ccs.tencentyun.com/<ns>/app:latest
docker login ccr.ccs.tencentyun.com
docker push ccr.ccs.tencentyun.com/<ns>/app:latest
```
然后把 `ccr.ccs.tencentyun.com/<ns>/app:latest` 作为 `imageUrl` 传入。中转只解决拉取,**不能替代**镜像文档里的启动命令 / 环境变量 / 数据目录。
**与 initEnv 联动:** 镜像部署同样要求环境已开通云托管。新环境首次部署前先 `manageCloudRun(action="initEnv", envId=...)`,并用 `queryCloudRun(action="envStatus")` 轮询到 `Status=normal`;未开通时 `deploy` 会被拦截并引导先 `initEnv`。
**示例:**
```json
{
"action": "deploy",
"serverName": "my-image-svc",
"imageUrl": "ccr.ccs.tencentyun.com/ns/app:latest",
"serverConfig": {
"OpenAccessTypes": ["PUBLIC"],
"Cpu": 0.5,
"Mem": 1,
"MinNum": 1,
"MaxNum": 3,
"Port": 8080,
"Cmd": ["node", "server.js"],
"EnvParams": "{\"PORT\":\"8080\",\"BIND_HOST\":\"0.0.0.0\"}"
}
}
```
`Port` / `Cmd` / `EnvParams` 必须来自镜像官方文档的五要素清单,不要套用 `3000` 或省略启动命令。第三方镜像的完整对照见 `references/image-deploy-troubleshooting.md` 附录。
部署后:`manageCloudRun(deploy)` 对镜像返回的 `next_step` 默认指向 `getProcessLog`(或先 `getDeployRecords` 取 `RunId`),**不要**改去调 `getDeployLog`。也可用 `queryCloudRun(action="detail")` 查看 `imageInfo`(镜像地址与部署类型)。镜像部署失败排查走下方 SOP。
## Container deploy failure SOP
**顺序:先查镜像官方文档 → 再查运行日志 → 最后才动配置。禁止一看到 probe failed / `deploy_failed` 就调 `InitialDelaySeconds`。**
详情与案例:`references/image-deploy-troubleshooting.md`。
### 1. 部署前:从镜像官方文档确认五要素
不要靠 Docker Hub tag 或「常见默认值」猜。部署前必须确认:
1. **启动命令** EntryPoint / Cmd(进程如何前台常驻)→ `serverConfig.EntryPoint` / `Cmd`
2. **服务端口**(进程真正 bind 的端口;不要假设 80/3000,也不要假设镜像尊重 `PORT`)→ `serverConfig.Port`
3. **对外监听环境变量**(必须 `0.0.0.0` 而不是 `127.0.0.1`、功能开关默认关闭等)→ `EnvParams`
4. **数据目录挂载** → `serverConfig.VolumesConf`
5. **健康端点**(CloudRun readiness 探的是**服务端口**,不是任意 HTTP path)
缺任何一项再部署,失败看起来都会像「健康检查失败」。
### 2. 部署失败:用 `getProcessLog` 定性
镜像部署(`imageUrl`)**跳过** `getDeployLog`(那是云端源码构建的构建日志)。从 `detail` / `getDeployRecords` 取 `RunId`,再 `queryCloudRun(action="getProcessLog")`。
**启动日志存在 ≠ 服务正常运行。** banner、s6/tini 行、sidecar "listening" 都不能证明探针目标已起来。
**两次日志对比判活:** 隔 20–40 秒再拉一次 `getProcessLog`。
| 观察 | 定性 |
| --- | --- |
| 只有调度/创建步骤(`create_eks_*`),没有容器 stdout | **Pod 调度中 / 镜像拉取** |
| 同一段启动 banner / PID 1 行重复出现(时间戳在走、内容几乎一样) | **容器启动即退出 / 重启循环** |
| 进程还在,但 listen 在 `127.0.0.1` 或端口 ≠ `serverConfig.Port` | **端口 / 绑定地址问题** |
| 两次拉取是**同一条启动过程**在往后打日志,banner 不重复 | 才可能是启动慢 |
### 3. Readiness probe 真实机制(严禁先调延迟)
部署步骤完成后:先等 **N** 秒(`InitialDelaySeconds`),再大约 **每 5 秒** 探一次服务端口,连续约 **30 次全失败** 才判本次部署失败。窗口 ≈ **N+150s**。**不是**「N 秒后立即失败」。
- **禁止:** 看到 probe failed 就把 N 改成 120。崩溃循环和 loopback 绑定不会因为 N 变大而好。
- **允许调大 N 仅当:** 两次日志证明**同一个进程还在一次性初始化**(JVM 预热、迁移)且尚未 listen。
### 4. 公网镜像(`docker.io`)反复失败 → Dockerfile 源码构建
节点直连 Docker Hub 反复失败时,不要空转 `imageUrl`。写:
```dockerfile
FROM docker.io/example/app:latest
```
用 `targetPath` 走云端源码构建:CODING 构建机拉公网镜像,产物进 CCR,云托管节点内网拉取。这只解决**拉取拓扑**,不替代第 1 步的 Cmd / 环境变量 / 卷。
### 5. Supervisor 镜像(s6 / tini / supervisord)启动即退出
PID 1 往往是监督进程,不是 HTTP 应用。用两次日志找子进程重启风暴。若镜像 issue 记录了 PID 1 / `pgrep -f` 误匹配,按文档 workaround(绝对路径 Cmd、关闭 supervise),不要调探针延迟。示例见 reference 附录。
## Access guidance
- **Web/public scenarios** -> enable PUBLIC ingress intentionally and pair it with the right auth flow.
- **Mini Program** -> prefer internal direct connection and avoid unnecessary public exposure.
- **Private ingress scenarios** -> keep public access off unless the product requirement clearly needs it.
- **Database / Redis in a VPC** -> this is **not** solved by `OpenAccessTypes`. You must set `serverConfig.VpcConf` and use the database private address. Read `references/vpc-and-database.md`.
## Quick examples
### Initialize
```json
{ "action": "init", "serverName": "my-svc", "targetPath": "/abs/ws/my-svc" }
```
### Local run (Function mode)
```json
{ "action": "run", "serverName": "my-svc", "targetPath": "/abs/ws/my-svc", "runOptions": { "port": 3000 } }
```
### Deploy (no VPC-private dependencies)
```json
{
"action": "deploy",
"serverName": "my-svc",
"targetPath": "/abs/ws/my-svc",
"serverConfig": {
"OpenAccessTypes": ["PUBLIC"],
"Cpu": 0.5,
"Mem": 1,
"MinNum": 1,
"MaxNum": 5
}
}
```
### Deploy (existing app that connects to MySQL / PostgreSQL / Redis over TCP)
```json
{
"action": "deploy",
"serverName": "my-existing-app",
"targetPath": "/abs/ws/my-existing-app",
"serverConfig": {
"OpenAccessTypes": ["PUBLIC"],
"Cpu": 0.5,
"Mem": 1,
"MinNum": 1,
"MaxNum": 5,
"EnvParams": "{\"DATABASE_URL\":\"postgres://user:pass@10.x.x.x:5432/app\"}",
"VpcConf": {
"VpcId": "vpc-xxxxxxxx",
"SubnetId": "subnet-xxxxxxxx"
}
}
}
```
**Valid `OpenAccessTypes` values**: `OA` (办公网访问), `PUBLIC` (公网访问), `MINIAPP` (小程序访问), `VPC` (VPC访问). Use `PUBLIC` for web applications that need public HTTPS access.
`MinNum: 1` is the recommended default when you want to reduce cold-start latency. If the user explicitly prefers lower cost and accepts more cold starts, explain the tradeoff and let them reduce `MinNum` to `0`.
## Best practices
1. Prefer PRIVATE/VPC or mini-program internal **ingress** when possible.
2. For TCP database access, always pair private DB URLs with `VpcConf` in the same VPC/region as the database.
3. Use environment variables for secrets and per-environment configuration — **read them server-side only; never return them in HTTP responses**.
4. Verify configuration before and after deployment with `queryCloudRun(action="detail")`.
5. Keep startup work small to reduce cold-start impact.
6. For Agent scenarios, use the Agent SDK skill for protocol and adapter details instead of duplicating them here.
7. For smoke tests, return a fixed `{ "ok": true }` / health payload — never deploy httpbin or any service that reflects request headers.
## Troubleshooting hints
- **Access failure** -> check ingress access type, domain setup, and whether the instance scaled to zero.
- **Deployment blocked with "尚未初始化云托管 / not initialized"** -> the environment needs CloudRun enabled first: call `manageCloudRun(action="initEnv", envId=...)` (异步开通) and poll `queryCloudRun(action="envStatus")` until `Status=normal`; or open the console `环境 → 云托管 → 开通`. For stateless HTTP services, consider an HTTP cloud function instead of CloudRun entirely.
- **Deployment failure** -> follow the **Container deploy failure SOP** above (and `references/image-deploy-troubleshooting.md`): image deploys skip `getDeployLog` and use `getProcessLog` only; classify scheduling vs port vs exit-on-start with two log pulls. Do **not** raise `InitialDelaySeconds` until logs prove a single slow init. Also inspect Dockerfile (source) and CPU/memory ratio.
- **Local run failure** -> remember only Function mode is supported by local-run tools.
- **Performance issues** -> reduce dependencies, optimize initialization, and tune minimum instances.
- **DB / Redis connection failure after a successful deploy** -> almost always missing or wrong `VpcConf`, wrong private host, or security group. Follow `references/vpc-and-database.md` before rewriting application code.
## Reference index
All packaged reference files (required for skill lint reachability):
- [vpc-and-database.md](references/vpc-and-database.md)
- [image-deploy-troubleshooting.md](references/image-deploy-troubleshooting.md)
references/console-links.md
# CloudBase Console Entry Points
After creating or deploying resources, provide the corresponding console management link. All console URLs follow the pattern: `https://tcb.cloud.tencent.com/dev?envId=${envId}#/{path}`.
The CloudBase console changes frequently. If a logged-in console shows a different hash path from this list, prefer the live console path and update this file instead of copying stale URLs forward.
## Common entry points
- **Overview (概览)**: `#/overview`
- **Document Database (文档型数据库)**: `#/db/doc` - Collections: `#/db/doc/collection/${collectionName}`, Models: `#/db/doc/model/${modelName}`
- **MySQL Database (MySQL 数据库)**: `#/db/mysql` - Tables: `#/db/mysql/table/default/`
- **Cloud Functions (云函数)**: `#/scf` - Detail: `#/scf/detail?id=${functionName}&NameSpace=${envId}`
- **CloudRun (云托管)**: `#/platform-run`
- **Cloud Storage (云存储)**: `#/storage`
- **Identity Authentication (身份认证)**: `#/identity` - Login: `#/identity/login-manage`, Tokens: `#/identity/token-management`
## Other useful entry points
- **Template Center**: `#/cloud-template/market`
- **AI+**: `#/ai`
- **Static Website Hosting**: `#/static-hosting`
- **Weida Low-Code**: `#/lowcode/apps`
- **Logs & Monitoring**: `#/devops/log`
- **Extensions**: `#/apis`
- **Environment Settings**: `#/env/http-access`
references/data-model-creation/SKILL.md
---
name: data-model-creation
description: "[Deprecated] Optional advanced tool for complex data modeling. For simple MySQL table creation, use relational-database-tool directly; for PostgreSQL / CloudBase PG schema work, use postgresql-development. New environments should use PostgreSQL DDL via queryPgDatabase/managePgDatabase — see postgresql-development skill instead."
version: 2.33.1
alwaysApply: false
metadata:
priority: "5"
deprecated: "true"
---
## Sibling skills (local only)
Sibling CloudBase skills ship beside this skill. Use local relative paths such as `../auth-tool-cloudbase/SKILL.md`.
If a referenced sibling skill file is missing from this environment, ask the user to install the full CloudBase plugin (or the missing skill). Do **not** HTTP-fetch remote skill or protocol markdown into the agent context.
# Data Model Creation
## Activation Contract
### Use this first when
- The user explicitly wants Mermaid `classDiagram` modeling.
- The task needs complex multi-entity relational design, visual ER-style output, or generated data-model structure rather than direct SQL.
- You need to create CloudBase data models through the dedicated modeling tools, or you need to inspect an existing model before planning follow-up changes.
### Read before writing code if
- The request mentions data model, ER diagram, Mermaid, relationship graph, or enterprise schema design.
- The user wants to reuse or update an existing published model.
### Then also read
- Direct MySQL SQL creation or schema change -> `../relational-database-mcp-cloudbase/SKILL.md`
- PostgreSQL / CloudBase PG schema work -> `../postgresql-development-cloudbase/SKILL.md`
- Broader feature planning before schema work -> `../spec-workflow/SKILL.md`
### Do NOT use for
- Simple `CREATE TABLE`, `ALTER TABLE`, or CRUD tasks.
- Document-database collection design.
- Frontend-only data-shape discussions with no modeling requirement.
### Common mistakes / gotchas
- Using Mermaid modeling for a task that only needs one or two SQL statements.
- Mixing SQL-table design and NoSQL collection design in the same model.
- Generating diagrams without first deciding entity boundaries and ownership relations.
- Publishing a new model before validating the generated fields and relationships.
### Minimal checklist
- Confirm Mermaid modeling is actually needed.
- List the core entities and relationships first.
- Decide whether this is a new model or an update.
- Keep the initial model small unless the user explicitly wants a large enterprise schema.
## Overview
This skill is an **advanced modeling path**, not the default path for database work.
- For most MySQL database tasks, use `relational-database-mcp-cloudbase` and write SQL directly. If the task says PostgreSQL, CloudBase PG, PG mode, `app.rdb()`, `queryPgDatabase`, `managePgDatabase`, or RLS, use `postgresql-development-cloudbase` instead.
- Use this skill only when diagram-driven modeling adds value.
## Quick routing
### Use `relational-database-mcp-cloudbase` instead when
- You need MySQL `CREATE TABLE`, `ALTER TABLE`, `INSERT`, `UPDATE`, `DELETE`, or `SELECT`
- The schema is small and already clear
- The user never asked for a visual model
- The task does **not** mention PostgreSQL / CloudBase PG / PG mode / `app.rdb()` / `queryPgDatabase` / `managePgDatabase` / RLS
### Use this skill when
- You need multi-entity relationship modeling
- You need Mermaid `classDiagram` output
- You want generated model structure and documentation
- You need a clean modeling pass before SQL implementation
## How to use this skill (for a coding agent)
1. **Clarify the entity set**
- Extract business entities, ownership, and relationship cardinality from the request.
- Prefer 3-5 core entities unless the user clearly asks for more.
2. **Model first, then generate**
- Draft Mermaid `classDiagram` content.
- Validate names, field types, and relationships before calling modeling tools.
3. **Use the right tools**
- Read/list existing models -> `manageDataModel(action="list"|"get"|"docs")`
- Create a new model -> `modifyDataModel` (compatibility name; create-only)
4. **Publish carefully**
- Prefer creating with unpublished or draft-like intent first.
- Publish only after checking field names, required constraints, and relationship directions.
## Mermaid generation rules
### Naming
- Class names -> PascalCase
- Field names -> camelCase
- Convert Chinese business descriptions into clear English identifiers
- Keep enum values human-readable when needed
### Type mapping
| Business meaning | Mermaid type |
| --- | --- |
| text | `string` |
| number | `number` |
| boolean | `boolean` |
| enum | `x-enum` |
| email | `email` |
| phone | `phone` |
| URL | `url` |
| image | `x-image` |
| file | `x-file` |
| rich text | `x-rtf` |
| date | `date` |
| datetime | `datetime` |
| region | `x-area-code` |
| location | `x-location` |
| array | `string[]` or another explicit array type |
### Required structure conventions
- Use `required()` only for fields the user explicitly marks as required.
- Use `unique()` only for explicit uniqueness needs.
- Use `display_field()` for the human-facing label field.
- Add concise `<<description>>` notes to important fields.
- Keep relationship labels tied to actual field names rather than vague business prose.
## Minimal example
```mermaid
classDiagram
class User {
username: string <<Username>>
email: email <<Email>>
display_field() "username"
required() ["username", "email"]
unique() ["username", "email"]
}
class Order {
orderNo: string <<Order Number>>
totalAmount: number <<Total Amount>>
userId: string <<User ID>>
display_field() "orderNo"
unique() ["orderNo"]
}
Order "n" --> "1" User : userId
%% Class naming
note for User "用户"
note for Order "订单"
```
## Tool usage guidance
### Read existing models
Use this before creating related models, checking naming consistency, or assessing how an existing model is defined:
- `manageDataModel(action="list")`
- `manageDataModel(action="get", name="ModelName")`
- `manageDataModel(action="docs", name="ModelName")`
### Create model
Use `modifyDataModel` with:
- a complete `mermaidDiagram`
- `action="create"` when you want to create new models
- a deliberate publish decision
- clear awareness that updating existing model structures is not currently supported by this tool
## Best practices
1. Prefer direct SQL unless the user clearly benefits from model-first design.
2. Keep the first model iteration small and reviewable.
3. Separate business entities from implementation-only helper fields.
4. Validate relationship direction and ownership before publishing.
5. After modeling, hand off actual MySQL SQL/table work to `relational-database-mcp-cloudbase` when needed. For PostgreSQL / CloudBase PG tables, hand off to `postgresql-development-cloudbase` instead.
references/deployment-workflow.md
# Deployment Workflow
When users request deployment to CloudBase:
## 0. Check existing deployment
- Read README.md to check for existing deployment information
- Identify previously deployed services and their URLs
- Determine if this is a new deployment or update to existing services
## 1. Backend deployment (if applicable)
- Only for Node.js cloud functions: deploy directly using `manageFunctions(action="createFunction")` / `manageFunctions(action="updateFunctionCode")`
- Legacy compatibility: if older materials mention `createFunction`, `updateFunctionCode`, or `getFunctionList`, map them to `manageFunctions(...)` and `queryFunctions(...)`
- Before deploying, decide whether the function is Event or HTTP. Event Functions use `exports.main = async (event, context) => {}`.
- HTTP Functions are standard web services: they must listen on port `9000`, include `scf_bootstrap`, and for Node.js should default to native `http.createServer((req, res) => { ... })`. Parse `req.url` and the streamed request body manually, set response headers explicitly, and do not write the function as `exports.main` unless you intentionally choose Functions Framework.
- **CLI fallback (first session / MCP missing):** If CloudBase MCP tools are not in this session — including right after plugin install before restart — do **not** stall. Configure MCP for the next session (`mcp-setup.md`), then read `cloudbase-cli` (`core.md` + the matching domain reference such as `functions.md` / `cloudrun.md` / `hosting.md`) and follow those commands after `tcb login` → `tcb env use <envId>`. Do **not** use `tcb deploy`. Full decision tree: `tooling-fallback.md`.
- **User prefers CLI / CI:** Also use `cloudbase-cli` even when MCP exists.
- For other languages backend server (Java, Go, PHP, Python, Node.js): deploy to Cloud Run
- Ensure backend code supports CORS by default
- Prepare Dockerfile for containerized deployment
- Use `manageCloudRun` tool for deployment when MCP is available; otherwise the CloudRun path in `cloudbase-cli`
- Set MinNum instances to at least 1 to reduce cold start latency
- Confirm with the user before destructive or production write operations (delete, overwrite, plan change)
## 2. Frontend deployment (if applicable)
- After backend deployment completes, update frontend API endpoints using the returned API addresses
- Build the frontend application
- **Determine whether this is a new or existing project**:
- **New project (first-time deployment)**: Use `manageApps(action="createApp", ...)` to deploy to an independent subdomain. Each app gets its own `*.webapps.tcloudbase.com` subdomain — no path collisions between projects. If MCP is unavailable, read `cloudbase-cli` → `hosting.md` (build locally, then hosting deploy). Do **not** use `tcb deploy`.
- **Existing project (re-deployment)**: Use `manageApps(action="updateApp", ...)` to update the existing app. If the original project was deployed via `manageHosting` (shared domain path), continue using `manageHosting` for consistency. CLI parity: hosting / app commands in `cloudbase-cli`.
- After uploading via MCP, call `setWebsiteDocument` to configure SPA routing — set both `indexDocument` and `errorDocument` to `"index.html"`.
- If `manageApps` fails persistently, fall back to `manageHosting` (or CLI hosting). Remind the user the URL will share the env domain path and CDN has a few minutes of cache.
## 3. Display deployment URLs
- Show backend deployment URL (if applicable)
- Show frontend deployment URL with trailing slash (/) in path
- Add random query string to frontend URL to ensure CDN cache refresh
## 4. Update documentation
- Write deployment information and service details to README.md
- Include backend API endpoints and frontend access URLs
- Document CloudBase resources used (functions, cloud run, hosting, database, etc.)
- This helps with future updates and maintenance
references/http-api-cloudbase/checklist.md
# HTTP API Routing Checklist
Use this checklist when the request comes from Android, iOS, Flutter, React Native, backend scripts, or any environment that is not using a CloudBase SDK.
## Required checks
1. Confirm the caller really needs raw HTTP APIs rather than Web SDK or MCP tools.
2. Confirm environment ID, region, and gateway base URL.
3. Choose the auth mechanism: AccessToken, API Key, or Publishable Key.
4. Query the matching OpenAPI definition before writing request code.
5. For database work, confirm which REST API is needed:
- **关系型数据库 REST** (MySQL / PostgreSQL): `https://{envId}.api.tcloudbasegateway.com/v1/rdb/rest/{table}` — PostgREST 风格
- **NoSQL REST**: `https://{envId}.api.tcloudbasegateway.com/v1/database/instances/{instance}/databases/{database}/` — EJSON 格式
6. For AI model access, confirm the calling pattern:
- **SDK 方式** (Node/Web/微信小程序): 走各端 SDK,不需要裸调 HTTP
- **HTTP API 方式**: `https://{envId}.api.tcloudbasegateway.com/...` — 参考 OpenAPI `ai_model`
7. Verify the OpenAPI spec is available in `searchKnowledgeBase` before writing code:
- `mysqldb` — 关系型数据库 REST
- `nosql` — NoSQL REST
- `ai_model` — AI 大模型接入
- `auth` / `functions` / `cloudrun` / `storage` — 其他
## Do not route here when
- The user is building a Web frontend with `@cloudbase/js-sdk`.
- The user is building a CloudBase mini program with `wx.cloud`.
- The task is MCP-driven database management rather than raw HTTP calls.
- The user is calling AI models from Node/Web/微信 — use `ai-model-*` skills instead; only use HTTP API for unsupported runtimes.
## Done criteria
- SDK support boundary is explicit.
- The correct REST API variant (关系型 / NoSQL / AI) has been identified.
- OpenAPI source has been checked (`searchKnowledgeBase` with the right `apiName`).
- The auth method and request base URL are fixed before code generation.
references/http-api-cloudbase/references/extended-guide.md
# Extended guide — http-api-cloudbase
> Moved from SKILL.md to satisfy Agent Skills Spec 500-line limit.
## Usage Examples
### Cloud Function Invocation Example
```bash
curl -X POST "https://your-env-id.api.tcloudbasegateway.com/v1/functions/YOUR_FUNCTION_NAME" \
-H "Authorization: Bearer <access_token/apikey/publishable_key>" \
-H "Content-Type: application/json" \
-d '{"name": "张三", "age": 25}'
```
For detailed API specifications, always download and reference the OpenAPI Swagger files mentioned above.
## 关系型数据库 RESTful API (PostgREST 风格)
> **适用于 MySQL 和 PostgreSQL**:两者均基于 PostgREST 风格暴露 REST API,端点格式和请求语义一致。
提供关系型数据库(MySQL / PostgreSQL)的 HTTP 操作接口。
### Base URL Patterns
Support three domain access patterns:
1. `https://{envId}.api.tcloudbasegateway.com/v1/rdb/rest/{table}`
2. `https://{envId}.api.tcloudbasegateway.com/v1/rdb/rest/{schema}/{table}`
3. `https://{envId}.api.tcloudbasegateway.com/v1/rdb/rest/{instance}/{schema}/{table}`
Where:
- `envId` is the environment ID
- `instance` is the database instance identifier
- `schema` is the database name
- `table` is the table name
If using the system database, **recommend pattern 1**.
### Request Headers
| Header | Parameter | Description | Example |
|--------|-----------|-------------|---------|
| Accept | `application/json`, `application/vnd.pgrst.object+json` | Control data return format | `Accept: application/json` |
| Content-Type | `application/json`, `application/vnd.pgrst.object+json` | Request content type | `Content-Type: application/json` |
| Prefer | Operation-dependent feature values | - `return=representation` Write operation, return data body and headers<br>- `return=minimal` Write operation, return headers only (default)<br>- `count=exact` Read operation, specify count<br>- `resolution=merge-duplicates` Upsert operation, merge conflicts<br>- `resolution=ignore-duplicates` Upsert operation, ignore conflicts | `Prefer: return=representation` |
| Authorization | `Bearer <token>` | Authentication token | `Authorization: Bearer <access_token>` |
### Query Records
**GET** `/v1/rdb/rest/{table}`
**Query Parameters**:
- `select`: Field selection, supports `*` or field list, supports join queries like `class_id(grade,class_number)`
- `limit`: Limit return count
- `offset`: Offset for pagination
- `order`: Sort field, format `field.asc` or `field.desc`
**Example**:
```bash
# Before URL encoding
curl -X GET 'https://your-env.api.tcloudbasegateway.com/v1/rdb/rest/course?select=name,position&name=like.%张三%&title=eq.文章标题' \
-H "Authorization: Bearer <access_token>"
# After URL encoding
curl -X GET 'https://your-env.api.tcloudbasegateway.com/v1/rdb/rest/course?select=name,position&name=like.%%E5%BC%A0%E4%B8%89%&title=eq.%E6%96%87%E7%AB%A0%E6%A0%87%E9%A2%98' \
-H "Authorization: Bearer <access_token>"
```
**Response Headers**:
- `Content-Range`: Data range, e.g., `0-9/100` (0=start, 9=end, 100=total)
### Insert Records
**POST** `/v1/rdb/rest/{table}`
**Request Body**: JSON object or array of objects
> 💡 **Identity fields differ by database mode**: In PostgreSQL / CloudBase PG, do **not** use `_openid`. Prefer owner columns with `DEFAULT auth.uid()` (JWT `sub`) and omit the owner field from INSERT bodies. In legacy MySQL/NoSQL-oriented examples, `_openid` may be populated by the platform; do not copy that pattern into PG tables.
**Example**:
```bash
curl -X POST 'https://your-env.api.tcloudbasegateway.com/v1/rdb/rest/course' \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d '{
"name": "数学",
"position": 1
}'
```
### Update Records
**PATCH** `/v1/rdb/rest/{table}`
**Request Body**: JSON object with fields to update
**Example**:
```bash
curl -X PATCH 'https://your-env.api.tcloudbasegateway.com/v1/rdb/rest/course?id=eq.1' \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d '{
"name": "高等数学",
"position": 2
}'
```
> ⚠️ **Important**: UPDATE requires a WHERE clause. Use query parameters like `?id=eq.1` to specify conditions.
### Delete Records
**DELETE** `/v1/rdb/rest/{table}`
**Example**:
```bash
curl -X DELETE 'https://your-env.api.tcloudbasegateway.com/v1/rdb/rest/course?id=eq.1' \
-H "Authorization: Bearer <access_token>"
```
> ⚠️ **Important**: DELETE requires a WHERE clause. Use query parameters to specify conditions.
### Error Codes and HTTP Status Codes
| Error Code | HTTP Status | Description |
|------------|-------------|-------------|
| INVALID_PARAM | 400 | Invalid request parameters |
| INVALID_REQUEST | 400 | Invalid request content: missing permission fields, SQL execution errors, etc. |
| INVALID_REQUEST | 406 | Does not meet single record return constraint |
| PERMISSION_DENIED | 401, 403 | Authentication failed: 401 for identity authentication failure, 403 for authorization failure |
| RESOURCE_NOT_FOUND | 404 | Database instance or table not found |
| SYS_ERR | 500 | Internal system error |
| OPERATION_FAILED | 503 | Failed to establish database connection |
| RESOURCE_UNAVAILABLE | 503 | Database unavailable due to certain reasons |
### Response Format
1. All POST, PATCH, DELETE operations: Request header with `Prefer: return=representation` means there is a response body, without it means only response headers.
2. POST, PATCH, DELETE response bodies are usually JSON array type `[]`. If request header specifies `Accept: application/vnd.pgrst.object+json`, it will return JSON object type `{}`.
3. If `Accept: application/vnd.pgrst.object+json` is specified but data quantity is greater than 1, an error will be returned.
### URL Encoding
When making requests, please perform URL encoding. For example:
**Original request**:
```shell
curl -i -X GET 'https://{{host}}/v1/rdb/rest/course?select=name,position&name=like.%张三%&title=eq.文章标题'
```
**Encoded request**:
```shell
curl -i -X GET 'https://{{host}}/v1/rdb/rest/course?select=name,position&name=like.%%E5%BC%A0%E4%B8%89%&title=eq.%E6%96%87%E7%AB%A0%E6%A0%87%E9%A2%98'
```
## NoSQL RESTful API
NoSQL RESTful API 提供文档型数据库(NoSQL)的 HTTP 操作接口,支持集合管理、文档 CRUD、聚合查询、事务操作和数据库命令。
### Base URL
```
https://{envId}.api.tcloudbasegateway.com/v1/database/instances/{instance}/databases/{database}/
```
| 参数 | 说明 |
|------|------|
| `envId` | 环境 ID |
| `instance` | 数据库实例 ID,默认实例使用 `(default)` |
| `database` | 数据库名称,默认数据库使用 `(default)` |
示例:
- 默认实例 + 默认数据库:`/v1/database/instances/(default)/databases/(default)/`
- 指定实例 + 默认数据库:`/v1/database/instances/test_instance/databases/(default)/`
### 请求与响应格式
- 请求支持 Relaxed 和 Strict EJSON 格式
- 响应均为 Strict EJSON 格式
- EJSON 支持的特殊类型:`ObjectId`、`Date`、`Int`、`Long`、`Decimal128`、`Binary`、`RegExp`
### 错误码与 HTTP 状态码
| 错误码 | HTTP 状态码 | 说明 |
|--------|-------------|------|
| `INVALID_PARAM` | 400 | 参数错误 |
| `DATABASE_PERMISSION_DENIED` | 401 | 权限不足 |
| `DATABASE_INVALID_OPERRATOR` | 403 | 不支持的操作 |
| `DATABASE_COLLECTION_NOT_EXIST` | 404 | 集合不存在 |
| `DOCUMENT_NOT_FOUND` | 404 | 文档不存在 |
| `DATABASE_COLLECTION_ALREADY_EXIST` | 409 | 集合已存在 |
| `DATABASE_DUPLICATE_WRITE` | 409 | 唯一索引冲突 |
| `EXCEED_REQUEST_LIMIT` | 422 | 请求次数超限 |
| `EXCEED_CONCURRENT_REQUEST_LIMIT` | 422 | 并发请求超限 |
| `DATABASE_REQUEST_FAILED` | 500 | 数据库请求失败 |
| `SYS_ERR` | 500 | 内部错误 |
| `DATABASE_TRANSACTION_CONFLICT` | 503 | 事务冲突 |
| `DATABASE_TRANSACTION_FAIL` | 503 | 事务执行失败 |
| `DATABASE_TIMEOUT` | 504 | 数据库操作超时 |
详细端点使用和请求示例,请参考官方文档:https://docs.cloudbase.net/http-api/nosql/nosql-restful-api
---
## AI 大模型接入 API
统一的大模型接入 API,支持通过 HTTP 调用已配置的 AI 大模型(支持 SSE 流式响应)。
### 认证方式
| 方式 | 说明 |
|------|------|
| `Authorization: Bearer <token>` | AccessToken 认证(推荐) |
| TC3-HMAC-SHA256 签名 | 腾讯云 API v3 签名方式 |
| `Authorization: <apikey>` | APIKey 认证 |
> AccessToken 获取方式:参考 Auth OpenAPI (`searchKnowledgeBase({ mode: "openapi", apiName: "auth" })`)
### 错误码
| 错误码 | 说明 |
|--------|------|
| `AI_MODEL_CONFIG_MISSING` | 缺少模型 API Key 或配置 |
| `AI_MODEL_PARAM_INVALID` | 输入参数无效 |
| `AI_MODEL_DISABLED` | 模型已禁用,请在控制台检查或等待约 2 分钟 |
| `AI_MODEL_NOT_SUPPORTED` | 请求模型不支持或未启用 |
| `AI_MODEL_PARAM_REQUIRED` | 缺少必需参数 `model` |
| `AI_MODEL_NOT_FOUND` | 指定的模型组不存在 |
| `EXCEED_CONCURRENT_REQUEST_LIMIT` | 并发请求超限,请稍后重试或申请更高配额 |
| `EXCEED_TOKEN_QUOTA_LIMIT` | 模型 Token 配额超限,请购买资源或调整模型组 |
详细端点和请求格式,请参考官方文档:https://docs.cloudbase.net/http-api/ai-model/ai-%E5%A4%A7%E6%A8%A1%E5%9E%8B%E6%8E%A5%E5%85%A5
以及 OpenAPI 规范:`https://docs.cloudbase.net/openapi/ai_model.v1.openapi.yaml`
---
## Online Debugging Tool
CloudBase platform provides an [online debugging tool](https://docs.cloudbase.net/http-api/basic/online-api-call) where you can test API interfaces without writing code:
1. Visit the API documentation page
2. Find the debugging tool entry
3. Fill in environment ID and request parameters
4. Click send request to view response
## API Documentation References
**⚠️ Always use `searchKnowledgeBase` tool to get OpenAPI Swagger specifications:**
Use `searchKnowledgeBase({ mode: "openapi", apiName: "<api-name>" })` with these API names:
- `auth` - Authentication API
- `mysqldb` - 关系型数据库 RESTful API (MySQL/PostgreSQL)
- `nosql` - NoSQL RESTful API (文档型数据库)
- `functions` - Cloud Functions API
- `cloudrun` - CloudRun API
- `storage` - Storage API
- `ai_model` - AI 大模型接入 API
**How to use the OpenAPI documentation:**
1. Call `searchKnowledgeBase` tool with the appropriate `apiName`
2. Parse the returned YAML content to extract:
- Endpoint paths (e.g., `/v1/rdb/rest/{table}`)
- HTTP methods (GET, POST, PATCH, DELETE)
- Path parameters, query parameters, request body schemas
- Response schemas and status codes
- Authentication requirements
3. Use the extracted information to construct accurate API calls
4. Never assume endpoint structure - always verify against swagger documentation
## Common Patterns
### Reusable Shell Variables
```bash
env="your-env-id"
token="your-access-token-or-api-key"
base="https://${env}.api.tcloudbasegateway.com"
```
### Common Request Pattern
```bash
curl -X GET "${base}/v1/rdb/rest/table_name" \
-H "Authorization: Bearer ${token}" \
-H "Content-Type: application/json"
```
### Error Handling
Always check HTTP status codes and error response format:
```json
{
"code": "ERROR_CODE",
"message": "Error message details",
"requestId": "request-unique-id"
}
```
## Common Authentication Flows
> **🌟 IMPORTANT: Default Authentication Method**
>
> When no specific signup/signin method is specified by the user, **ALWAYS use Phone SMS Verification** as the default and recommended method. It is:
> - ✅ The most user-friendly for Chinese users
> - ✅ No password to remember
> - ✅ Works for both new users (registration) and existing users (login)
> - ✅ Most secure with OTP verification
> - ✅ Supported by default in CloudBase
### Phone Number Verification Code Login (Native Apps) ⭐ RECOMMENDED
This is the **preferred** authentication flow for native mobile apps (iOS/Android/Flutter/React Native):
```
┌─────────────────────────────────────────────────────────────────────────┐
│ Step 1: Send Verification Code │
│ POST /auth/v1/verification │
│ Body: { "phone_number": "+86 13800138000", "target": "ANY" } │
│ ⚠️ IMPORTANT: phone_number MUST include "+86 " prefix WITH SPACE │
│ Response: { "verification_id": "xxx", "expires_in": 600 } │
│ 📝 SAVE verification_id for next step! │
└─────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────┐
│ Step 2: Verify Code │
│ POST /auth/v1/verification/verify │
│ Body: { "verification_id": "<saved_id>", "verification_code": "123456" }│
│ Response: { "verification_token": "xxx" } │
│ 📝 SAVE verification_token for login! │
└─────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────┐
│ Step 3: Sign In with Token │
│ POST /auth/v1/signin │
│ Body: { "verification_token": "<saved_token>" } │
│ Response: { "access_token": "xxx", "refresh_token": "xxx" } │
└─────────────────────────────────────────────────────────────────────────┘
```
**⚠️ Critical Notes:**
1. **Phone number format**: MUST be `"+86 13800138000"` with space after country code
2. **Save `verification_id`**: Returned from Step 1, required for Step 2
3. **Save `verification_token`**: Returned from Step 2, required for Step 3
## Best Practices
1. **Always use URL encoding** for query parameters containing special characters
2. **Include WHERE clauses** for UPDATE and DELETE operations
3. **Use appropriate Prefer headers** to control response format
4. **Handle errors gracefully** by checking status codes and error responses
5. **Keep tokens secure** - never expose API Keys in client-side code
6. **Use appropriate authentication method** based on your use case:
- AccessToken for user-specific operations
- API Key for server-side admin operations
- Publishable Key for public access (note: anonymous login is disabled by default for new environments)
7. **Phone number format**: Always use international format with space: `"+86 13800138000"`
8. **Verification flow**: Save `verification_id` from send step, use it in verify step
references/http-api-cloudbase/SKILL.md
---
name: http-api-cloudbase
description: CloudBase official HTTP API client guide. This skill should be used when backends, scripts, or non-SDK clients must call CloudBase platform APIs over raw HTTP instead of using a platform SDK or MCP management tool.
version: 2.33.1
alwaysApply: false
---
## Sibling skills (local only)
Sibling CloudBase skills ship beside this skill. Use local relative paths such as `../auth-tool-cloudbase/SKILL.md`.
If a referenced sibling skill file is missing from this environment, ask the user to install the full CloudBase plugin (or the missing skill). Do **not** HTTP-fetch remote skill or protocol markdown into the agent context.
## Activation Contract
### Use this first when
- The request comes from Android, iOS, Flutter, React Native, non-Node backends, or admin scripts that must call official CloudBase APIs via raw HTTP.
- The task is to consume CloudBase platform endpoints, not to build a new HTTP service on CloudBase.
### Read before writing code if
- The platform does not support a CloudBase SDK, or the user explicitly asks for HTTP API integration.
- The user says "HTTP API" but it is unclear whether they mean official CloudBase endpoints or their own business API.
### Then also read
- Auth configuration -> `../auth-tool-cloudbase/SKILL.md`
- MySQL MCP management -> `../relational-database-mcp-cloudbase/SKILL.md`
- Your own HTTP service on CloudBase -> `../cloud-functions/SKILL.md` or `../cloudrun-development/SKILL.md`
### Do NOT use for
- CloudBase Web SDK flows, mini program SDK flows, or MCP-driven management tasks.
- Building your own HTTP service or REST API on CloudBase.
### Common mistakes / gotchas
- Treating Web SDK examples as valid for native Apps.
- Guessing endpoints without reading OpenAPI definitions.
- Confusing official CloudBase HTTP APIs with your own function or CloudRun endpoint.
- Mixing raw HTTP API integration with MCP management logic.
### Minimal checklist
- Read [HTTP API Routing Checklist](checklist.md) before implementation.
## When to use this skill
Use this skill whenever you need to call **CloudBase platform features** via **raw HTTP APIs**, for example:
- Non-Node backends (Go, Python, Java, PHP, etc.)
- Integration tests or admin scripts that use curl or language HTTP clients
- Direct database operations via 关系型数据库 RESTful API (MySQL/PostgreSQL)
- Cloud function invocation via HTTP
- Any scenario where SDKs are not available or not preferred
Do **not** use this skill for:
- Frontend Web apps using `@cloudbase/js-sdk` (use **CloudBase Web** skills)
- Node.js code using `@cloudbase/node-sdk` (use **CloudBase Node** skills)
- Authentication flows (use **CloudBase Auth HTTP API** skill for auth-specific endpoints)
## How to use this skill (for a coding agent)
1. **Clarify the scenario**
- Confirm this code will call HTTP endpoints directly (not SDKs).
- Ask for:
- `env` – CloudBase environment ID
- Authentication method (AccessToken, API Key, or Publishable Key)
- Confirm which CloudBase feature is needed (database, functions, storage, etc.).
- **For user authentication**: If no specific method is requested, **always default to Phone SMS Verification** - it's the most user-friendly and secure option for Chinese users.
2. **Determine the base URL**
- Use the correct domain based on region (domestic vs. international).
- Default is domestic Shanghai region.
3. **Set up authentication**
- Choose appropriate authentication method based on use case.
- Add `Authorization: Bearer <token>` header to requests.
4. **Reference OpenAPI Swagger documentation**
- **MUST use `searchKnowledgeBase` tool** to get OpenAPI specifications
- Use the tool with `mode=openapi` and specify the `apiName`:
- `mysqldb` - 关系型数据库 RESTful API (MySQL/PostgreSQL)
- `nosql` - NoSQL RESTful API (文档型数据库)
- `functions` - Cloud Functions API
- `auth` - Authentication API
- `cloudrun` - CloudRun API
- `storage` - Storage API
- `ai_model` - AI 大模型接入 API
- Example: `searchKnowledgeBase({ mode: "openapi", apiName: "mysqldb" })`
- Parse the returned YAML content to understand exact endpoint paths, parameters, request/response schemas
- Never invent endpoints or parameters - always reference the swagger documentation
---
## Overview
CloudBase HTTP API is a set of interfaces for accessing CloudBase platform features via HTTP protocol, supporting database, user authentication, cloud functions, cloud hosting, cloud storage, AI, and more.
## OpenAPI Swagger Documentation
**⚠️ IMPORTANT: Always use `searchKnowledgeBase` tool to get OpenAPI Swagger specifications**
Before implementing any HTTP API calls, you should:
1. **Use `searchKnowledgeBase` tool to get OpenAPI documentation**:
```
searchKnowledgeBase({ mode: "openapi", apiName: "<api-name>" })
```
2. **Available API names**:
- `mysqldb` - 关系型数据库 RESTful API (MySQL/PostgreSQL)
- `nosql` - NoSQL RESTful API (文档型数据库)
- `functions` - Cloud Functions API
- `auth` - Authentication API
- `cloudrun` - CloudRun API
- `storage` - Storage API
- `ai_model` - AI 大模型接入 API
3. **Parse and use the swagger documentation**:
- Extract exact endpoint paths and HTTP methods
- Understand required and optional parameters
- Review request/response schemas
- Check authentication requirements
- Verify error response formats
4. **Never invent API endpoints or parameters** - always base your implementation on the official swagger documentation.
## Prerequisites
Before starting, ensure you have:
1. **CloudBase environment created and activated**
2. **Authentication credentials** (AccessToken, API Key, or Publishable Key)
## Authentication and Authorization
CloudBase HTTP API requires authentication. Choose the appropriate method based on your use case:
### AccessToken Authentication
**Applicable environments**: Client/Server
**User permissions**: Logged-in user permissions
**How to get**: Use `searchKnowledgeBase({ mode: "openapi", apiName: "auth" })` to get the Authentication API specification
### API Key
**Applicable environments**: Server
**User permissions**: Administrator permissions
- **Validity**: Long-term valid
- **How to get**: Get from [CloudBase Platform/ApiKey Management Page](https://tcb.cloud.tencent.com/dev?#/identity/token-management)
> ⚠️ Warning: Tokens are critical credentials for identity authentication. Keep them secure. API Key must NOT be used in client-side code.
### Publishable Key
**Applicable environments**: Client/Server
**User permissions**: Anonymous user permissions
- **Validity**: Long-term valid
- **How to get**: Get from [CloudBase Platform/ApiKey Management Page](https://tcb.cloud.tencent.com/dev?#/identity/token-management)
> 💡 Note: Can be exposed in browsers, used for requesting publicly accessible resources, effectively reducing MAU.
## API Endpoint URLs
CloudBase HTTP API uses unified domain names for API calls. The domain varies based on the environment's region.
### Domestic Regions
For environments in **domestic regions** like Shanghai (`ap-shanghai`), use:
```text
https://{your-env}.api.tcloudbasegateway.com
```
Replace `{your-env}` with the actual environment ID. For example, if environment ID is `cloud1-abc`:
```text
https://cloud1-abc.api.tcloudbasegateway.com
```
### International Regions
For environments in **international regions** like Singapore (`ap-singapore`), use:
```text
https://{your-env}.api.intl.tcloudbasegateway.com
```
Replace `{your-env}` with the actual environment ID. For example, if environment ID is `cloud1-abc`:
```text
https://cloud1-abc.api.intl.tcloudbasegateway.com
```
## Using Authentication in Requests
Add the token to the request header:
```http
Authorization: Bearer <access_token/apikey/publishable_key>
```
:::warning Note
When making actual calls, replace the entire part including angle brackets (`< >`) with your obtained key. For example, if the obtained key is `eymykey`, fill it as:
```http
Authorization: Bearer eymykey
```
:::
## Extended guide
For detailed scenarios, examples, and patterns, read [extended-guide.md](references/extended-guide.md).
## Reference index
All packaged reference files (required for skill lint reachability):
- [extended-guide.md](references/extended-guide.md)
references/mcp-setup.md
# CloudBase MCP Setup Reference
## Preferred: Install CloudBase Plugin (global)
When the user asks to install CloudBase / the AI Toolkit / the plugin, **prefer the Open Plugin Spec CLI** over hand-writing MCP JSON. One install brings MCP + Skills + Hooks.
Default: `--scope user` (global).
```bash
npx plugins add TencentCloudBase/cloudbase-plugin -y --scope user
# Optional: Sites plugin
npx plugins add TencentCloudBase/cloudbase-sites-plugin -y --scope user
```
- Omit `--target` to install into all detected supported AI IDEs.
- After install, ask the user to restart / reload the target tool (e.g. Claude Code `/reload-plugins`).
### Supported `npx plugins` targets (`--target`)
List live detection with `npx plugins targets`. Current supported target IDs:
| Target ID (`--target`) | AI IDE | Notes |
|------------------------|--------|--------|
| `claude-code` | Claude Code | Config under `~/.claude` |
| `cursor` | Cursor | Config under `~/.cursor` |
| `codex` | Codex | Config under `~/.codex` |
| `grok` | Grok Build | Config under `~/.grok`; per-user only |
| `kimi` | Kimi Code | Config under `~/.kimi-code`; per-user only |
| `github-copilot` | GitHub Copilot CLI | Config under `~/.copilot`; standalone `copilot` CLI, not `gh copilot` |
| `vscode` | Visual Studio Code | Agent plugins (Preview); per-user only |
Examples:
```bash
npx plugins add TencentCloudBase/cloudbase-plugin -y --scope user --target cursor
npx plugins targets
```
**Not supported by `npx plugins` yet** (use each product's native path): CodeBuddy, WorkBuddy, Kimi Code, Kimi Work, ZCode, WindSurf, and other IDEs without Open Plugin Spec. For those, use Approach A (native MCP) or Approach B (mcporter) below, plus Skills if needed.
**Do not double-install:** if Claude Code / Codex already has the plugin via marketplace (`claude plugin install` / `codex plugin add`), do **not** also run `npx plugins add` for the same tool.
---
## Approach A: IDE Native MCP
Configure via your IDE's MCP settings when Plugin install is unavailable:
```json
{
"mcpServers": {
"cloudbase": {
"command": "npx",
"args": ["@cloudbase/cloudbase-mcp@latest"]
}
}
}
```
**Config file locations:**
- **Cursor**: `.cursor/mcp.json`
- **Claude Code**: `.mcp.json`
- **Windsurf**: `~/.codeium/windsurf/mcp_config.json` (user-level, no project-level JSON config)
- **Cline**: Check Cline settings for project-level MCP configuration file location
- **GitHub Copilot Chat (VS Code)**: Check VS Code settings for MCP configuration file location
- **Continue**: Uses YAML format in `.continue/mcpServers/` folder:
```yaml
name: CloudBase MCP
version: 1.0.0
schema: v1
mcpServers:
- uses: stdio
command: npx
args: ["@cloudbase/cloudbase-mcp@latest"]
```
---
## Approach B: mcporter CLI
When your IDE does not support native MCP or Plugin install, use **mcporter** as the CLI.
**Step 1 — Check**: `npx mcporter list | grep cloudbase`
**Step 2 — Configure** (if not found): create `config/mcporter.json` in the project root:
```json
{
"mcpServers": {
"cloudbase": {
"command": "npx",
"args": ["@cloudbase/cloudbase-mcp@latest"],
"description": "CloudBase MCP",
"lifecycle": "keep-alive"
}
}
}
```
**Step 3 — Verify**: `npx mcporter describe cloudbase`
---
## Quick Start (mcporter CLI)
- `npx mcporter list` — list configured servers
- **Required:** `npx mcporter describe cloudbase --all-parameters` — inspect CloudBase server config and get full tool schemas with all parameters (⚠️ **必须加 `--all-parameters` 才能获取完整参数信息**)
- `npx mcporter list cloudbase --schema` — get full JSON schema for all CloudBase tools
- `npx mcporter call cloudbase.help --output json` — discover available CloudBase tools and their schemas
- `npx mcporter call cloudbase.<tool> key=value` — call a CloudBase tool
---
## Call Examples (CloudBase auth)
- Check auth & env status:
`npx mcporter call cloudbase.auth action=status --output json`
- Start device-flow login:
`npx mcporter call cloudbase.auth action=start_auth authMode=device --output json`
- Resolve env alias to full EnvId:
`npx mcporter call cloudbase.envQuery action=list alias=demo aliasExact=true fields='["EnvId","Alias","Status","IsDefault"]' --output json`
- Bind environment after login:
`npx mcporter call cloudbase.auth action=set_env envId=<full-env-id> --output json`
- Query app-side login config:
`npx mcporter call cloudbase.queryAppAuth action=getLoginConfig --output json`
- Patch app-side login strategy:
`npx mcporter call cloudbase.manageAppAuth action=patchLoginStrategy patch='{"usernamePassword":true}' --output json`
- Query publishable key:
`npx mcporter call cloudbase.queryAppAuth action=getPublishableKey --output json`
---
## Environment Management Tools (manageEnv + auth + envQuery)
Beyond authentication, CloudBase MCP provides several environment management tools.
### manageEnv — Full environment lifecycle
Query available plans, create environments, change plans, and renew:
- **List available packages**:
`npx mcporter call cloudbase.manageEnv action=listPackages --output json`
- **Create a new environment**:
```
npx mcporter call cloudbase.manageEnv action=create alias=my-env packageId=baas_personal resources='["flexdb","storage","function","postgresql"]' duration=1 confirm=yes --output json
```
Resources parameter values: `flexdb` (document database), `storage` (cloud storage), `function` (cloud functions), `postgresql` (PostgreSQL database).
Do **not** pass `region`: CreateEnv does not accept Region; environment region is determined by account/package.
- **Change plan** (e.g. upgrade to standard):
`npx mcporter call cloudbase.manageEnv action=modifyPlan envId=<envId> packageId=baas_pf_standard confirm=yes --output json`
- **Renew environment**:
`npx mcporter call cloudbase.manageEnv action=renew envId=<envId> duration=12 confirm=yes --output json`
### Auth — Environment binding & logout
- **Check status** (shows env candidates when multiple environments exist):
`npx mcporter call cloudbase.auth action=status --output json`
- **Bind to a specific environment** (after login):
`npx mcporter call cloudbase.auth action=set_env envId=<full-env-id> --output json`
- **Logout** (clears login state and cached env binding):
`npx mcporter call cloudbase.auth action=logout confirm=yes --output json`
### envQuery — Query environment details
- **List all environments**:
`npx mcporter call cloudbase.envQuery action=list --output json`
- **Get environment info** (runtime backends, storage, status):
`npx mcporter call cloudbase.envQuery action=info envId=<envId> --output json`
- **Resolve alias to EnvId**:
`npx mcporter call cloudbase.envQuery action=list alias=demo aliasExact=true fields='["EnvId","Alias","Status","IsDefault"]' --output json`
---
## No npm / npx
If `npm` / `npx` are missing, do **not** keep retrying `npx plugins` / `npx mcporter`:
1. Ask the user to install **Node.js LTS** (https://nodejs.org, or `brew install node` / `winget install OpenJS.NodeJS.LTS` / nvm / fnm), then re-check `node -v` and `npm -v`.
2. Meanwhile, prefer **IDE native plugin / marketplace / MCP UI** paths above (no hand-run `npx`), or Approach A hand-written MCP config once Node exists.
3. For login/deploy in this session without MCP tools, follow `tooling-fallback.md` (CLI via `cloudbase-cli` domain skills). Full decision tree includes the npm-missing branch.
## Important Rules
- **When MCP tools are available in this session**, prefer them for manage/deploy, and understand tool details first. Before calling any CloudBase MCP tool, run `npx mcporter describe cloudbase --all-parameters` (or `ToolSearch` in IDE) to inspect available tools and their parameters.
- **When MCP is not configured or tools are not yet loaded** (common on first session, or right after install before restart): complete the MCP setup steps above for the **next** session, then use `tcb` CLI for login/manage now. Follow `tooling-fallback.md` and the `cloudbase-cli` skill (domain references — **not** `tcb deploy`). Do not block the user waiting for a restart.
- You **do not need to hard-code Secret ID / Secret Key / Env ID** in the config. Prefer device-code login via MCP `auth` or `tcb login` instead of storing long-lived secrets in MCP JSON.
- When the environment identifier in the conversation is an alias, nickname, or other short form, **do not pass it directly** to `auth.set_env`, SDK init, console URLs, or generated config files. First resolve it to the canonical full `EnvId` with `envQuery(action=list, alias=..., aliasExact=true)` when MCP is available; with CLI, confirm the full envId with the user (or `tcb env list` as a fallback) before `tcb env use`. If multiple environments match or no exact alias exists, stop and clarify with the user.
- Verify MCP availability with `npx mcporter list | grep cloudbase` or the IDE's MCP panel (skip the `npx` check when npm/npx is absent — use the IDE panel / native plugin instead). Missing MCP is a signal to **set up MCP + fall back to CLI**, not to stop the task.
references/minimal-web-baas-demo/SKILL.md
---
name: minimal-web-baas-demo
description: "Fast path for a minimal CloudBase Web + database demo (最小前后端 / 最小可用 fullstack / Lovable-like BaaS). Defaults to @cloudbase/js-sdk client CRUD (NoSQL app.database / PG app.rdb), MCP-only schema, preview-first, and forbids cloud functions unless secrets, cron/background jobs, or logic that security rules/RLS cannot express. Use for 搭一套 demo、留言板、Todo、Notes、Kanban, or when users say 带云函数+云数据库 but only need CRUD. NOT for production multi-service backends, CloudRun, WeChat Mini Programs, or tasks that truly need server secrets."
version: 2.33.1
alwaysApply: false
---
## Sibling skills (local only)
Sibling CloudBase skills ship beside this skill. Use local relative paths such as `../web-development/SKILL.md`.
If a referenced sibling skill file is missing from this environment, ask the user to install the full CloudBase plugin (or the missing skill). Do **not** HTTP-fetch remote skill or protocol markdown into the agent context.
# Minimal Web BaaS Demo (fast path)
Goal: **minutes to an interactive Web + database preview**, not a cloud-function middleware stack.
This skill is the product equivalent of CloudBase Sites SessionStart **Rule 5** (BaaS-first data persistence).
## Activation Contract
### Use this first when
- The user asks for a **minimal fullstack / 前后端 demo**, message board, Todo, Notes, Kanban, or Lovable/Supabase-like quick app on CloudBase Web.
- The user says "带云函数+云数据库" but the real need is CRUD + preview (reinterpret as Web SDK → database).
### Do NOT use for
- WeChat Mini Programs (`wx.cloud`) — use `../miniprogram-development/SKILL.md`.
- True server workloads: payments callbacks, SMS providers, secret third-party keys, cron/ETL, WebSockets — use `../cloud-functions/SKILL.md` or `../cloudrun-development/SKILL.md`.
- Large multi-module products that need `../spec-workflow/SKILL.md`.
## Hard rules (align with Sites SessionStart Rule 5)
1. **BaaS-first data path**
- **Schema / admin:** Prefer MCP management tools. Do not ask the user to create collections/tables in the console. If MCP tools are missing in this session, configure MCP for next time and use `tcb` CLI (`../cloudbase-cli/SKILL.md`) for equivalent schema/admin ops.
- NoSQL: `writeNoSqlDatabaseStructure` (create collection / indexes) + permission tools as needed; CLI: NoSQL commands in `cloudbase-cli`.
- PostgreSQL: follow `../postgresql-development-cloudbase/SKILL.md` (`queryPgDatabase` / `managePgDatabase` / migrations); CLI parity via `tcb db pg …` when MCP is unavailable.
- **Reads / writes:** `@cloudbase/js-sdk` in the browser.
- NoSQL: `app.database()` → `db.collection(...).get()/add()/update()/watch(...)`.
- PG: `app.rdb().from(...)`.
- Prefer the template helper (often `src/utils/cloudbase.ts`). Do not invent a second SDK wrapper.
2. **Cloud functions are forbidden by default**
- Todo / Notes / Chat / Kanban / "最小前后端 demo" → **zero** cloud functions.
- Even if the user says "带云函数", deliver Web SDK → DB and explain in one sentence that cloud functions are not required for CRUD.
- Create a cloud function **only when**:
- (a) the logic cannot be expressed as database security rules / RLS, **and**
- (b) it needs server-side secrets / third-party API keys, **or** it is a scheduled / background job not triggered by a user click.
3. **Package discipline**
- Browser: `@cloudbase/js-sdk` only.
- Never install `@cloudbase/node-sdk` (or random stubs) in frontend code.
4. **Preview first, deploy second**
- Ship local list + add (or equivalent CRUD) before static hosting / CloudApp deploy.
- Custom domains, DNS, rollback runbooks → only if the user explicitly asks.
- Skip Playwright / agent-browser unless the user asks to test.
5. **UI ceremony for this path**
- Reuse the template look. **Do not** load `ui-design` four-part specs for a minimal demo unless the user asks for visual redesign.
- "Make me a X app" = X **is** the homepage (`HomePage` / `App`), not a nested demo route beside the welcome page.
6. **Anonymous (or real) session before NoSQL CRUD — js-sdk 3.x + publishable key**
- With `@cloudbase/js-sdk` **3.x** initialized via publishable `accessKey`, call **`await auth.signInAnonymously()`** (or an equivalent authenticated session) **before** any NoSQL `app.database()` `get` / `add` / `update` / `watch`.
- Skipping this yields **gateway 401**. `checkLogin()` / `getSession()` alone does **not** create a usable write session.
- Minimal example:
```js
const auth = app.auth
const { error } = await auth.signInAnonymously()
if (error) throw error
const db = app.database()
await db.collection('messages').get()
```
## Capability sniff order (partners + agents)
Use this order for every minimal Web + DB demo. **Do not reorder.** Cloud functions stay off the critical path.
```text
0. Connector pre-enabled (or shortest Trust path) ← host / partner packaging
1. Template warmup // parallel with credential wait ← downloadTemplate + install
2. envQuery(action="info") ← sniff env + RuntimeBackends
3. Lock ONE DB plane (NoSQL | PG | MySQL) ← no mid-flight thrash
4. MCP schema + minimal permissions ← writeNoSql* / PG migrate / MySQL manage
5. Browser @cloudbase/js-sdk CRUD ← app.database() / app.rdb()
6. Local preview (list + add) ← then ask before deploy
7. Cloud functions ← skip (count = 0) unless secrets/cron/rules-cannot-express
```
Stack priority for this path: **Web SDK CRUD > MCP schema > template warmup > cloud functions**.
## Standard playbook
1. **Warm template in parallel with credentials** (see partner notes below): `downloadTemplate` (`react` default, `vue` if requested) → `npm install` / `pnpm install`.
2. `envQuery(action="info")` → lock **one** DB plane (NoSQL **or** PG **or** MySQL). Do not thrash between them.
3. MCP: create the collection/table + minimal permissions.
4. Frontend: ensure session (`auth.signInAnonymously()` or equivalent), then wire list + create with `@cloudbase/js-sdk` (see Hard rule 6).
5. Start / report preview URL; ask before deploy.
6. **Cloud function count for this path = 0.**
### On-demand skills (fetch only when needed)
| Need | Skill |
| --- | --- |
| Web scaffold / hosting | `../web-development/SKILL.md` |
| NoSQL browser CRUD | `../cloudbase-document-database-web-sdk/SKILL.md` |
| PG browser CRUD + MCP schema | `../postgresql-development-cloudbase/SKILL.md` |
| Auth provider readiness | `../auth-tool-cloudbase/SKILL.md` then `../auth-web-cloudbase/SKILL.md` |
Prefer `searchKnowledgeBase(mode="skill", skillName="minimal-web-baas-demo")` (or the sibling dir id) when local skill files are unavailable. **Do not** dump every CloudBase skill at session start.
## WorkBuddy / partner packaging notes (XDF and beyond)
Any partner host (WorkBuddy, CodeBuddy connectors, vertical expert prompts, ISV wrappers) should treat this skill as the **compact fast-path brief**. Reuse the sniff order above; do not invent a cloud-function-first demo path.
| Host capability | Recommended packaging |
| --- | --- |
| Full CloudBase Sites plugin | Rely on SessionStart Rule 5 injection + this skill on demand for non-Sites cwd demos. Prefer Sites with `CLOUDBASE_SITES_AUTO_INIT=1` for empty-cwd preview. Never guess 5173 — read `.cloudbase-sites/preview.json`. |
| WorkBuddy / connector hosts | **Pre-enable** the CloudBase MCP connector for the tenant when possible; inject a short system brief that points here; warm `downloadTemplate` + `npm install` **during** credential/Trust wait (do not idle). Optional SessionStart `additionalContext` can point at this skill — do not ship a separate template-prewarm plugin. |
| Expert / vertical prompts (ISVs) | Ship a thin pack: expert Agent markdown (**no** frontmatter hooks) + optional settings/hooks. Replace any "必须云函数中转 / 前端绝不直连库" language with this BaaS-first contract. |
WorkBuddy SessionStart: https://www.workbuddy.ai/docs/cli/hooks (same `additionalContext` schema as CodeBuddy/Claude Code). Empty-dir Sites auto-init stays passive unless opted in — do not assume enabling Sites alone warms templates during credential wait.
**Do not** block first preview on custom domains or org DNS health. DNS issues are a post-preview concern.
### One-screen partner paste (optional)
```text
For 最小前后端 / Lovable-like demos: FIRST call
searchKnowledgeBase(mode="skill", skillName="minimal-web-baas-demo"), then Read.
Do not rely only on ad-hoc expert-prompt brief text.
Order: connector ready → template warmup during credential wait → envQuery →
lock one DB → MCP schema → auth.signInAnonymously() (or session) →
@cloudbase/js-sdk CRUD → preview.
Do not dump all CloudBase skills. Do not create cloud functions for CRUD.
NoSQL without session → gateway 401.
```references/miniprogram-development/references/cloudbase-integration.md
# CloudBase 小程序集成参考
本文补充 `SKILL.md`,提供实用的 **微信小程序 + CloudBase** 集成指引。
## 如何使用本参考(面向 coding agent)
1. **理解平台差异**
- 微信小程序与 Web 的认证方式完全不同。
- 必须严格区分平台。
- 绝不要把 Web 认证方法混入小程序项目。
- 使用 CloudBase 的小程序天然免登录。
2. **遵循 CloudBase 最佳实践**
- 小程序客户端使用 `wx.cloud` API。
- 依赖客户端写入前,先配置合适的数据库权限。
- 跨集合操作与特权写入优先走云函数。
- 服务端用 `cloud.getWXContext()` 取得的 `OPENID` 作为稳定用户标识。
3. **使用正确的 SDK 与 API**
- 小程序客户端按需使用 `wx.cloud.database()`、`wx.cloud.callFunction()`、`wx.cloud.uploadFile()`。
- 不要在小程序中使用 Web SDK 认证模式。
- 可用时通过 `envQuery` 获取环境 ID。
4. **选择正确的云执行面**
- **微信云开发 = CloudBase × 微信。** Nightly 开发者工具 Skills 可用时,日常小程序云操作(环境列表、NoSQL、云函数、云存储)优先用 `wechatide` / `cloudbase-operator` + 微信登录。见 [devtools-debug-preview.md](devtools-debug-preview.md) 与 [wxide-vs-cloudbase-mcp.md](wxide-vs-cloudbase-mcp.md)。
- Nightly 未覆盖的缺口(进阶权限、数据模型、MySQL/PG、更广的环境治理),或 Nightly / `wechatide` 不可用时,使用 **CloudBase MCP**(IDE MCP 或下方 mcporter)。
- **不要** 为 `wechatide` 已能完成的日常操作强制单独做腾讯云 MCP 登录。
5. **IDE MCP 不可用 / Nightly 路径不够时,经 mcporter(CLI)使用 CloudBase MCP**
- **无需** 在配置中硬编码 Secret ID / Secret Key / Env ID。
- CloudBase MCP 支持通过 `auth` 工具做 device-code 登录,可交互获取凭证。
- 在 `config/mcporter.json` 中添加 CloudBase MCP server:
若已有其他 MCP server,保留它们,仅追加 `cloudbase` 条目。
```json
{
"mcpServers": {
"cloudbase": {
"command": "npx",
"args": ["@cloudbase/cloudbase-mcp@latest"],
"description": "CloudBase MCP",
"lifecycle": "keep-alive"
}
}
}
```
- 发现工具与 schema:
- `npx mcporter list` — 列出已配置 server
- `npx mcporter describe cloudbase --all-parameters` — 检查 CloudBase server 配置并获取含全部参数的完整工具 schema(⚠️ **必须加 `--all-parameters` 才能获取完整参数信息**)
- `npx mcporter list cloudbase --schema` — 获取全部 CloudBase 工具的完整 JSON schema
- `npx mcporter call cloudbase.help --output json` — 发现可用 CloudBase 工具及其 schema
- 调用 CloudBase 工具(鉴权流程示例):
- `npx mcporter call cloudbase.auth action=status --output json`
- `npx mcporter call cloudbase.auth action=start_auth authMode=device --output json`
- `npx mcporter call cloudbase.auth action=set_env envId=env-xxx --output json`
## 1. 环境初始化
使用 CloudBase 的小程序应在应用启动时初始化一次 `wx.cloud`。
```js
App({
onLaunch() {
wx.cloud.init({
env: "your-env-id",
traceUser: true,
});
},
});
```
### 规则
- 可用时始终通过 `envQuery` 获取环境 ID。
- 优先在应用级初始化一次,避免在页面级反复初始化。
- 除非有明确理由,使用 `traceUser: true`,以便 CloudBase 将请求与当前微信用户关联。
## 2. 认证模型
小程序 CloudBase **天然免登录**。
### 必须遵守的行为
- **不要** 生成登录页或登录流程。
- **不要** 把 Web 认证模式移植到小程序。
- 在云函数中用 `cloud.getWXContext().OPENID` 获取用户身份。
```js
const cloud = require("wx-server-sdk");
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV });
exports.main = async () => {
const wxContext = cloud.getWXContext();
return {
openid: wxContext.OPENID,
};
};
```
## 3. 推荐能力边界
在正确的层级使用正确的 CloudBase 能力。
### 客户端
- `wx.cloud.database()` — 客户端安全读、用户作用域写
- `wx.cloud.uploadFile()` — 用户生成资源
- `wx.cloud.callFunction()` — 调用后端编排
### 云函数
- 特权写入
- 跨集合事务或工作流
- 第三方 API 集成
- 数据规范化 / 校验
- 经 `OPENID` 访问可信用户身份
## 4. 环境选择
- 不要硬编码随意的环境 ID。
- 优先从 `envQuery` 等工具获取环境 ID。
- 通常在 `app.js` / `app.ts` 中初始化一次 CloudBase。
## 5. 微信开发者工具与项目形态
- 请用户打开项目前,确认 `project.config.json` 含有 `appid`。
- 小程序源码通常在 `miniprogram/` 下。
- 云函数通常在 `cloudfunctions/` 下。
- 生成的页面应包含配套配置文件,如 `index.json`。
## 6. AI 与模型使用
- 在支持的基础库版本上,小程序可使用 `wx.cloud.extend.AI`。
- 保持 prompt 与模型选择明确。
- 若使用流式输出,应完整消费流,并在合适处增量更新 UI。
## 7. IDE MCP / Nightly Skills 不可用时的回退
若 Nightly `wechatide` 不可用,且 IDE 原生 MCP 集成也不可用,则通过 `mcporter` 使用 CloudBase MCP,用 device-code 鉴权完成登录,而不是把密钥写进配置。小程序日常操作仍尽量安装 Nightly:https://developers.weixin.qq.com/miniprogram/dev/devtools/nightly_backup.html
## 8. 控制台与运维链接
相关时,引导用户前往 CloudBase 控制台查看:
- 环境设置
- 数据库权限规则
- 云函数部署状态
- 存储管理
- 计费 / 套餐信息
优先用控制台指引,不要臆测权限或环境状态。
references/miniprogram-development/references/devtools-debug-preview.md
# 微信开发者工具调试与预览
本参考覆盖微信小程序项目的调试、预览、发布,以及经 **微信开发者工具 Nightly Skills**(`wechatide`)完成的日常 CloudBase 操作;并说明 Nightly 不可用时的回退路径。
何时用哪一套栈,另见 [微信 IDE Skills 与 CloudBase MCP](wxide-vs-cloudbase-mcp.md)。
## 何时阅读本参考
任务涉及以下内容时阅读本文件:
- 微信开发者工具 / Nightly / `wechatide`
- 模拟器、控制台、网络或真机调试
- 预览、上传或发布流程
- 打开项目 / `project.config.json` / `appid`
- 经开发者工具登录完成的日常云操作(NoSQL、云函数、云存储)
- 无开发者工具时用 `miniprogram-ci` 回退
## 0. 前置:Nightly 构建(内置 Skills / MCP)
微信 IDE Skills 与对应的 DevTools MCP 面随 **微信开发者工具 Nightly(开发版)** 发布,不是单独的 `npx` 包。
- 下载 / 更新日志:https://developers.weixin.qq.com/miniprogram/dev/devtools/nightly_backup.html
- 安装 Nightly 后应具备:
- CLI:`wechatide`(通常已在 PATH)
- 内置 skill 包(macOS 示例):
`/Applications/wechatwebdevtools.app/Contents/Resources/app.asar.unpacked/miniprogram-dev-skill`
- Windows(典型):在 DevTools 安装目录下的 `resources/app.asar.unpacked/miniprogram-dev-skill`
- **仅安装「微信开发者工具」并不保证有 Skills。** 若缺少 `wechatide` 或 skill 目录,先请用户安装 **Nightly**。
- Nightly 为日构建:功能/修复更快,稳定性可能低于稳定版。需要 AI Skills / MCP 工作流时使用它。
## 1. 优先路径:`wechatide`(Nightly Skills)
DevTools 工作流的原子执行入口:
```bash
wechatide -c <clientName> -t <toolName> [flags...]
```
### 必需上下文(不要臆造)
| 上下文 | 含义 |
| --- | --- |
| `-c <clientName>` | 当前 AI 客户端短名(如 `CodeBuddy`、`Claude`、`Cursor`) |
| `--project` | 含 `project.config.json` 的目录的绝对路径 |
| `appid` | 打开窗口 / 预览 / 上传前必需;从 `project.config.json` 读取 |
| `env` | 云工具所需的云环境 ID;未知时先调 `cloud_env_list` —— 绝不猜测 |
发现参数(**不要**臆造工具名或参数形态):
```bash
wechatide
wechatide -c <clientName> -t <toolName> --help
```
Nightly skill 包内的工具注册表:
- `miniprogram-tools/references/tools.yaml`
- 场景 skills:`skills/{initializer,debugger,automator,compiler,previewer,cloudbase-operator,project-manager}/SKILL.md`
若 agent 已加载 Nightly 根 skill(`miniprogram-dev-skill/SKILL.md`),遵循该包的路由。否则用本参考确定路径与优先级,再打开内置场景 skill 看细节。
### 会话引导(每会话一次)
1. 确认 `wechatide` 存在(`which wechatide` / `wechatide`)。
2. 读取 Nightly 包 `skill.yaml` 顶层 `version`(不要硬编码)。
3. 检查登录 / skill 版本:
```bash
wechatide -c <clientName> -t check_devtools_status --skill-version <versionFromSkillYaml>
```
| 结果 | 动作 |
| --- | --- |
| 响应含 `openid` | 就绪;不要每次调工具都重复检查 |
| 关于 skill 版本的 `warning` | 从 DevTools 内置路径重装 / 同步 skill,再检查 |
| 无 `openid` | 运行 `wechatide -c <clientName> -t scan_login`,等待用户扫码,再检查 |
| `command not found` | 安装 Nightly;确保 `wechatide` 在 PATH |
| 连接 / 鉴权错误 | `wechatide auth -c <clientName>`,然后重试 |
### 打开项目窗口之前
- 确认存在 `project.config.json`
- 确认 `appid` 存在且对预览/上传有效
- 确认 `miniprogramRoot` 指向真实源码树
- 相关时确认引用的资源 / 云函数目录存在
然后经 Nightly 工具打开 / 编译 / 导航(见场景 skills)。示例模式:
```bash
wechatide -c <clientName> -t project_open_window --project <absProjectPath>
wechatide -c <clientName> -t simulator_open_page --project <absProjectPath> --page pages/index/index
```
### 能力地图(仅分类)
| 分类 | 典型工具(仅名称) | 使用场景 |
| --- | --- | --- |
| project | `project_list`、`project_open_window`、`project_setting_*` | 导入 / 打开 / 设置 |
| compile | `compile_js`、`compile_wxml`、`compile_wxss`、`buildnpm` | 编译 / npm |
| simulator | `simulator_open_page`、`simulator_refresh` | 打开页面 / 刷新 |
| preview | `auto_preview`、`create_preview_qrcode` | 真机预览 |
| automation | `automation_*` | 点击 / 断言 / 截图 |
| debug | `get_app_console_content`、`get_app_network_content`、`debug_clear_cache` | 控制台 / 网络 / 缓存 |
| cloud | `cloud_env_list`、`cloud_fn_*`、`cloud_db_*`、`cloud_stor_*` | 微信登录下的日常 CloudBase 操作 |
| publish | `miniprogram_upload` | 上传体验版 |
手机快速推送优先 `auto_preview`。猜测根因前,优先用 `debugger` 场景拿控制台/网络证据。
### 经 Nightly 的云操作(日常路径)
在小程序 CloudBase 环境中列举/查询/部署集合、文档、云函数与存储:
1. 解析 `appid` + `env`(需要时用 `cloud_env_list`)
2. 使用 Nightly `cloudbase-operator` 工具(`cloud_db_*`、`cloud_fn_*`、`cloud_stor_*`)
3. 写操作走 DevTools 确认 UI —— 等待用户批准;拒绝/超时后不要重试破坏性操作
Nightly 可用时,**不要** 为这些日常操作强制经 CloudBase MCP 单独做腾讯云登录。仅在缺口时用 CloudBase MCP(见 [wxide-vs-cloudbase-mcp.md](wxide-vs-cloudbase-mcp.md))。
### 安全与失败处理
- 写操作需要用户在 DevTools 中确认
- 工具失败时:展示原始错误;不要臆造替代工具名或静默重试
- 临时下载 URL 寿命短;绝不要提交进源码
## 2. 回退:无 Nightly / 无 `wechatide`
Nightly Skills 不可用时:
1. 告知用户:仅稳定版 DevTools 可能不含 Skills/MCP;推荐上方 Nightly 下载页
2. 密钥与 IP 白名单就绪时,用 `miniprogram-ci` 做预览 / 上传 / npm 构建
3. 需要腾讯云登录的云资源操作,用 **CloudBase MCP**(IDE MCP 或 `mcporter`)
### `miniprogram-ci` 能做什么
- 预览、上传、npm 构建,以及部分云函数上传流程
### `miniprogram-ci` 前置条件
- `appid`、项目路径、代码上传私钥、微信小程序后台的 IP 白名单
### 关键限制
`miniprogram-ci` **不能** 替代模拟器面板、控制台/网络缓冲,或 `wechatide` 自动化/调试。
## 3. 建议的 agent 行为
Nightly + `wechatide` 可用时:
- 打开 / 编译 / 调试 / 预览 / 日常云操作优先 `wechatide`
- 按需携带 `clientName`、绝对路径 `--project`,以及 `appid`/`env`
- 用 `--help` 或内置 `tools.yaml` 查参数 —— 永不臆造工具
不可用时:
- 回退到 `miniprogram-ci` + CloudBase MCP
- 明确说明缺失哪些调试能力
## 4. 官方参考
- Nightly 下载:https://developers.weixin.qq.com/miniprogram/dev/devtools/nightly_backup.html
- DevTools 更新日志:https://developers.weixin.qq.com/miniprogram/dev/devtools/log.html#stable
- 小程序 CI:https://developers.weixin.qq.com/miniprogram/dev/devtools/ci.html
references/miniprogram-development/references/message-push-customer-service.md
# 消息推送与客服自动回复
面向 **微信小程序 + 云开发(CloudBase)** 的消息推送与客服消息自动回复实操指南。
## 操作面(强制)
**当前唯一支持路径:** 微信开发者工具(IDE)与 wxide CLI(Nightly 的 `wechatide`,或经典 DevTools `cli`)。
| 应当 | 禁止 |
| --- | --- |
| 通过 IDE UI 或下方 wxide CLI 高层命令完成配置 / 部署 / 预览 | 用底层传输、抓 ticket、未文档化的 CGI 绕过 CLI |
| 优先 Nightly:`wechatide -c <clientName> -t <toolName>`(用 `--help` 发现参数) | 臆造工具名或参数 |
| 在微信侧工具尚未暴露前,用 IDE **云开发控制台 → 消息推送** 做回调绑定 | 把 CloudBase MCP 的 `queryMessagePush` / `manageMessagePush` 当作小程序日常操作路径教给 agent |
**微信 IDE 暴露状态:** 规格设计了 `cloud_query_msg_push` / `cloud_manage_msg_push`(由 CloudBase MCP 的 `queryMessagePush` / `manageMessagePush` 经 `EXPOSED_TOOL_NAME` 映射;需微信侧升级 `@cloudbase/cloudbase-mcp` 后)—— **尚未暴露**。在此之前,本 skill 只把 **IDE UI + 现有 wxide CLI 云/预览命令** 当作面向 agent 的操作面。不要文档化或教授底层替代方案。
**维护者 E2E(不对产品 agent):** CloudBase-MCP msg-push 工具的完整 ticket / 回归流程在外部 skill `wxide-qbase-msgpush-e2e`(`~/.workbuddy/skills/wxide-qbase-msgpush-e2e/SKILL.md`)。只指向该处;不要把其中的底层步骤复制进本参考。
## 何时阅读本参考
- 将消息类型或事件绑定到云函数
- 实现必须能回复用户聊天的客服自动回复
- 部署接收端云函数 / 上传体验版以便真机验证
- 推送触发后查找云函数日志位置
---
## 1. 消息推送配置机制
### 消息类型 vs 事件类
回调路由以 **(MsgType, Event)** 对为键:
| 类别 | `MsgType` | `Event` | 典型用途 |
| --- | --- | --- | --- |
| 消息类型 | `text` / `image` / `voice` / `video` / `miniprogrampage` | 空字符串 (`""`) | 用户向客服发送聊天消息 / 卡片 |
| 事件类 | `event` | 具体事件名(如虚拟支付通知事件) | 平台 / 业务事件 |
规则:
- **同一 (MsgType, Event) 对只能绑定一个云函数**(重新绑定会替换原先函数)。
- `MsgType=event` 的合法事件名来自平台支持列表(IDE 消息推送面板 / 未来的 `cloud_query_msg_push` `listSupportedEvents`)。不要臆造事件字符串。
- 需要生效时,在 IDE 消息推送面板打开推送开关。
### 配置回调(当前)
1. 打开 **微信开发者工具 → 云开发控制台 → 消息推送**(文案可能随 DevTools 版本变化)。
2. 选择 **云函数** 模式(非容器),除非项目明确使用云托管回调。
3. 为所需消息类型和/或事件添加入口;各自指向接收端函数名;开启推送。
**待 wxide CLI 支持(尚未提供):**
```text
# 尚不可用 — 不要臆造或用底层调用代替
wechatide -c <clientName> -t cloud_query_msg_push ...
wechatide -c <clientName> -t cloud_manage_msg_push ...
```
这些工具上线后,subscribe / unsubscribe / list / setEnable 优先用它们,少用手点 IDE。在此之前仅用 IDE 面板。
### 部署接收端云函数
务必在**云端**安装 npm 依赖,以便运行时解析 `@cloudbase/node-sdk` / `wx-server-sdk` 等模块:
```bash
wechatide -c <clientName> -t cloud_fn_deploy \
--paths <absCloudFunctionDir> \
--env <envId> \
--appid <appid> \
--remote-npm-install
```
经典 DevTools CLI 等价写法:
```bash
cli cloud functions deploy \
--paths <absCloudFunctionDir> \
--env <envId> \
--appid <appid> \
--remote-npm-install
```
注意:
- 函数目录名 = 函数名。
- 若部署时函数处于 Creating/Updating,等待约 10–15s 后重试。
- 省略 `--remote-npm-install` / `-r` 常见运行时报错 `Cannot find module '...'`。
### 体验版 / 真机验证
```bash
# 上传体验版(仅在用户明确要求发布体验版时)
wechatide -c <clientName> -t miniprogram_upload \
--project <absProjectPath> \
--upload-version <x.y.z> \
--desc "<desc>"
# 需要可扫码二维码文件/窗口时用预览二维码
wechatide -c <clientName> -t create_preview_qrcode \
--project <absProjectPath> \
--qr-output <absOutputPath>
```
手机快速推送且无需文件路径时,优先 `auto_preview`。客服入口通常需要 `<button open-type="contact">`,并在小程序后台开通客服能力。
---
## 2. 云函数作为推送接收端
最小模式:
```js
const cloud = require("wx-server-sdk");
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV });
exports.main = async (event, context) => {
// event.MsgType / event.Event 标识 (MsgType, Event) 对
console.log("msg-push", event.MsgType, event.Event, event);
// ... 业务逻辑 ...
return {}; // 仅靠返回值不会回复用户(见 §3)
};
```
检查清单:
- 一个逻辑处理器对应一个函数即可;推送配置层必须保证 **一个 (MsgType, Event) → 一个函数**。
- 日志足够排查(`MsgType`、`Event`、若有则记 openid)。
- 特权 OpenAPI 调用须在函数 `config.json` 中声明权限(见 §3)。
---
## 3. 客服自动回复机制
**关键:** 云函数消息推送模式下,函数的 **返回值不会** 变成客服回复。要回复用户必须通过 OpenAPI **主动发送**:
```js
await cloud.openapi.customerServiceMessage.send({
touser: event.FromUserName,
msgtype: "text",
text: { content: "收到,我们会尽快处理" },
});
```
在函数上声明 OpenAPI 权限(`config.json` 示例):
```json
{
"permissions": {
"openapi": ["customerServiceMessage.send"]
}
}
```
修改代码或 `config.json` 后,用 `--remote-npm-install` 重新部署。
常见失败:
- 以为 `return { errcode: 0, ... }` 或文本 body 会回复 → 静默无回复。
- 缺少 `openapi` 权限 → 发送 API 运行时失败。
- 无客服入口 / 未开通能力 → 真机永远触发不了 `text` 推送。
---
## 4. 云函数日志
### IDE(现已可用)
**微信开发者工具 → 云开发控制台 → 云函数 → \<function\> → 日志**
开启云开发控制台相关面板后,接收端函数的调用日志会出现在这里。真机推送验证走此路径。
### wxide CLI(缺口)
目前尚无稳定的 wxide CLI 日志查询工具(例如未来的 `cloud_fn_logs` 或等价物)。
```text
# 待补齐 — 待 wxide CLI 提供日志查询能力
# 本 skill 不要教授底层日志 CGI 绕过
wechatide -c <clientName> -t <cloud_fn_logs_or_equivalent> ...
```
在此之前,指引 agent/用户在上方 IDE 控制台路径查看日志。
---
## 5. 建议的端到端流程(产品)
1. 实现接收端云函数(若需自动回复则加 OpenAPI send)。
2. `cloud_fn_deploy` **并带上** `--remote-npm-install`。
3. 在 IDE **消息推送** 中绑定 (MsgType, Event) → 函数(或未来的 `cloud_manage_msg_push`)。
4. 测试 `text` / 媒体消息类型时,确保已有客服入口 / 能力。
5. 上传体验版 / 预览;用真机触发。
6. 在 IDE 云函数 **日志** 中验证(CLI 日志查询能力尚未提供)。
---
## 相关
- 调试 / 预览 / `wechatide` 上下文:[devtools-debug-preview.md](devtools-debug-preview.md)
- IDE Skills 与 CloudBase MCP 分层:[wxide-vs-cloudbase-mcp.md](wxide-vs-cloudbase-mcp.md)
- CloudBase 小程序集成:[cloudbase-integration.md](cloudbase-integration.md)
- 维护者 MCP E2E 权威源(外部):`wxide-qbase-msgpush-e2e` skill — 不要在此内联其底层步骤
- 微信侧 CLI 暴露 / 缺失命令:跟进中
- 日志 API 调研:跟进中
- msg-push 与 `EXPOSED_TOOL_NAME` 的规格设计:见本文件第 5 节起的说明
## 5. 推送模式:云函数 vs 云托管
消息推送有**推送模式**,IDE「消息推送」面板右上角展示(云函数 / 云托管):
| 模式 | 行为 | 配置方式 |
|---|---|---|
| **云函数**(默认) | 按 (消息类型, 事件) 二元组逐条推送至对应云函数 | IDE 面板逐条添加,或 `cloud_manage_msg_push(action=subscribe)` |
| **云托管** | **整包接收所有消息**至云托管服务(一条 path 全收),云函数回调失效 | IDE 面板「云托管」切换 |
### 行为要点(MCP / IDE 一致)
- 云托管模式下,云函数回调**存在但不生效**——查询会返回 `pushMode=container` 及提示
- 云托管模式下 `subscribe/unsubscribe/setEnable` 会被**拒绝**(提示先切回云函数模式)
- 切换模式是**写操作**,需确认;切到云托管需提供服务路径(真实环境需已有云托管服务)
- 云托管开通:IDE 云开发控制台 → 云托管 → 立即开通(可能与按量付费联动);若环境无云托管服务,配置容器回调会失败
### 操作方式(当前)
```text
# 当前唯一操作途径:微信开发者工具 IDE(消息推送面板 + 云托管页面)
# 微信侧:cloud_query_msg_push 可读 pushMode;cloud_manage_msg_push(ensureCloudFunctionMode/ensureContainerMode/setContainerCallback) 管理模式
# 底层 CGI / 开通接口细节见 skill wxide-qbase-msgpush-e2e,本参考不展开
```
references/miniprogram-development/references/pitfalls.md
# 微信小程序开发常见陷阱
本文汇总真实项目中的高频错误。生成代码前请当作预检清单使用。
## 1. 可选链(`?.`)与现代语法
**问题**:许多基础库与微信开发者工具版本不支持可选链(`obj?.prop`)或空值合并(`??`)。
**正确做法**:
- 使用传统 `if` 判断,或 `&&` / `||` 模式。
- 仅在真正需要时,才用 `wx.getSystemInfoSync()` + 版本判断。
**应避免的示例**:
```js
const name = user?.name ?? 'Guest'; // 经常直接报错
```
**安全写法**:
```js
const name = (user && user.name) || 'Guest';
```
## 2. TDesign 组件样式(尤其 `::after`)
**问题**:TDesign 组件用伪元素做边框、图标和状态。用简单 class 选择器覆盖经常无效。
**要点**:
- 尽量使用 TDesign 提供的 CSS 自定义属性(变量)。
- 覆盖 `::after` / `::before` 时注意提高优先级;`!important` 仅作最后手段。
- 在真机上验证 —— 开发者工具预览可能掩盖渲染差异。
**推荐模式**:
```css
/* 优先用变量 */
.t-button {
--td-button-border-color: transparent;
}
/* 必要时再回退到 ::after */
.custom-cell::after {
border-color: var(--td-border-color, #e5e5e5) !important;
}
```
## 3. 小游戏 Canvas + 云存储权限
**问题**:Canvas 绘制后保存到云存储,常因权限或上下文问题失败。
**检查清单**:
- 按目标基础库正确使用 `wx.createCanvasContext`(2D)或 `wx.createOffscreenCanvas`。
- 申请 `scope.writePhotosAlbum`,或用 `canvasToTempFilePath` + `wx.cloud.uploadFile` 并处理好鉴权。
- 云存储路径须指向正确环境,且存储权限规则允许该 openid 或角色。
**常见失败**:
把 Canvas 存成图片再上传时,临时文件路径处理不正确。
## 4. 环境与代码配置漂移
**问题**:开发者工具所选环境与代码中实际使用的云环境不一致。
**预防**:
- 始终核对 `project.config.json` → `cloudbaseRoot` 与 `appid`。
- 显式调用 `wx.cloud.init({ env: 'your-real-env-id' })`。
- 在开发者工具中切换云环境后,重启模拟器。
- 使用 `miniprogram-ci` 做 CI/CD 时,IP 白名单须包含构建机。
## 5. 消息推送 / 客服自动回复
**问题**:agent 教授底层绕过、省略 `--remote-npm-install`,或误以为函数返回值会回复聊天。
**正确做法**:遵循 [message-push-customer-service.md](message-push-customer-service.md) —— 仅用 IDE / wxide CLI;回复走 OpenAPI `customerServiceMessage.send`;CLI 尚未提供消息推送与日志查询能力,不要教授底层绕过。
## 6. 通用建议
生成涉及 CloudBase 的小程序代码时:
1. 先读本陷阱文件。
2. 任何修改前先走 Change Safety Protocol。
3. 上传/发布流程须完成 Deployment Gate 检查清单。
4. 涉及消息推送 / 客服自动回复时,阅读 [message-push-customer-service.md](message-push-customer-service.md)。
这样可保持 skill 防御性,减少反复纠错循环。
references/miniprogram-development/references/seo-search-optimization.md
# 小程序 SEO 与微信搜索优化(小程序搜索优化)
本参考覆盖微信小程序的 **搜索优化 / SEO**(小程序搜索优化、页面收录、搜索曝光)。任务涉及以下内容时阅读:
- 小程序 SEO / 小程序搜索优化 / 搜索推广 / 关键词排名
- 页面被微信搜索收录(indexing / crawl)、爬虫(mpcrawler)访问、页面 URL 可直达
- `navigator` 跳转 vs 路由 API、页面参数设计、授权登录时机、`web-view` 收录限制
- 页面标题(`wx.setNavigationBarTitle`)、分享缩略图(`onShareAppMessage`)、`poster` / `poster-for-crawler` 设置
官方文档:<https://developers.weixin.qq.com/miniprogram/dev/framework/search/seo.html>
## 1. 爬虫识别(官方搜索爬虫识别)
爬虫访问小程序内页面时,会发送专用 **user-agent** `mpcrawler`,场景值 **1129**。
确认请求确实来自官方微信搜索爬虫时(建议在返回任何内容或记录爬虫命中前先校验):
- 请求头包含:
- `X-WXApp-Crawler-Timestamp`
- `X-WXApp-Crawler-Nonce`
- `X-WXApp-Crawler-Signature`
- 签名算法与微信消息推送签名算法相同:
1. 将三个参数 `token`、`X-WXApp-Crawler-Timestamp`、`X-WXApp-Crawler-Nonce` 按字典序排序
2. 将三个字符串拼接后做 `sha1` 加密
3. 将结果与 `X-WXApp-Crawler-Signature` 比较,确认请求来自微信
## 2. 页面 URL 必须可直接打开(页面 URL 可被直接打开)
- 站内跳转 URL 是爬虫发现页面的重要来源。
- 搜索引擎返回的任意结果页 **必须能直接打开**,不依赖页内状态 / 前置步骤。
- 页面所需参数放在 **URL**(query string)中,不要放在全局存储或共享数据对象里。
## 3. 优先使用 `navigator` 组件(页面跳转优先 navigator)
小程序提供两条路由路径:
- `navigator` **组件**(对爬虫友好的页面优先)
- 路由 API:`navigateTo` / `redirectTo` / `switchTab` / `navigateBack` / `reLaunch`
尽可能使用 `navigator` 组件。若必须用 API,对点击触发的时间锁或变量锁做保护,避免拦截爬虫访问。
## 4. 清晰简洁的页面参数(清晰简洁的页面参数)
- Query 字符串应结构清晰、简洁,参数名有意义。
- **避免** 把整个 JSON 对象序列化进单个 URL 参数 —— 既不利于爬取,也不利于后续分析。
## 5. 必要的时候才请求授权登录(必要的时候才请求授权登录)
- 仅在真正需要时要求授权(例如匿名阅读文章可以;评论需要身份)。
- 不要用登录墙挡住内容页;那会阻止爬虫收录。
## 6. `web-view` 内容不被收录(不收录 web-view)
- 微信 **不会** 收录 `web-view` 内渲染的任何内容。不要依赖 `web-view` 页面获取搜索流量;可索引内容请提供原生小程序页面。
## 7. 设置清晰的标题与页面缩略图(清晰的标题和页面缩略图)
标题与缩略图帮助微信理解页面,提升曝光与转化:
- `wx.setNavigationBarTitle` — 运行时设置页面标题
- `onShareAppMessage` — 自定义分享标题与图片路径
- 对 `video` / `audio` 组件,同时设置 `poster` / `poster-for-crawler`,以便爬虫快照有封面图
## 可收录页面上线前检查清单
1. 每个可收录页面都能从带齐 query 参数的 URL 直接打开。
2. 跳转使用 `navigator`(或 API 调用对爬虫安全)。
3. 参数名有意义;query 中无巨型 JSON blob。
4. 无需登录/授权即可阅读内容。
5. 可索引内容在原生页面中,不在 `web-view`。
6. 关键页面已设置标题(`wx.setNavigationBarTitle`)与缩略图(`onShareAppMessage` / `poster` / `poster-for-crawler`)。
references/miniprogram-development/references/wxide-vs-cloudbase-mcp.md
# 微信 IDE Skills 与 CloudBase MCP / Skills
**微信云开发 = CloudBase × 微信**:同一套云能力,在微信侧用开发者工具登录态直达;CloudBase MCP 补 IDE Skills 未覆盖的进阶云治理。
## 三层结构
| 层 | 角色 | 登录 | 典型能力 |
| --- | --- | --- | --- |
| **微信 IDE Skills**(Nightly) | 执行面(小程序默认) | 开发者工具内微信扫码登录 | 项目 / 编译 / 模拟器 / 预览 / 上传、自动化、控制台/网络、经 `wechatide` 的云环境 / 云函数 / NoSQL / 云存储 |
| **CloudBase Skills** | 知识包 | 不替代登录 | CloudBase 规则、陷阱、编码约定(`npx skills add tencentcloudbase/cloudbase-skills -y`) |
| **CloudBase MCP** | 完整云能力补全 | 腾讯云鉴权(API Key / web / device-code) | 环境治理、细粒度权限、数据模型、MySQL/PostgreSQL,以及 IDE Skills 未覆盖的其他缺口 |
Nightly 内置 Skills/MCP:https://developers.weixin.qq.com/miniprogram/dev/devtools/nightly_backup.html
## 决策树
1. 任务是否为小程序 **调试 / 预览 / 打开项目 / 控制台 / 网络 / 上传体验版**?
- 是 → **微信 IDE Skills**(`wechatide`)。见 [devtools-debug-preview.md](devtools-debug-preview.md)。
2. 任务是否为小程序 CloudBase 环境上的 **日常** 云操作(列举/查询集合、部署函数、列举/上传存储)?
- Nightly 可用 → **微信 IDE Skills** 的 `cloudbase-operator` 工具。
- Nightly 不可用 → **CloudBase MCP**(完成腾讯云登录后)。
3. 任务是否为 **消息推送 / 客服自动回复**(消息推送、客服自动回复、MsgType/Event 回调绑定)?
- → 优先 **微信 IDE Skills / IDE 云开发控制台**。见 [message-push-customer-service.md](message-push-customer-service.md)。
- `cloud_query_msg_push` / `cloud_manage_msg_push` **尚未暴露**(等待微信 IDE 侧升级支持)。不要用底层传输绕过。
4. 任务是否为 **进阶云能力**(数据模型 / MySQL / PG / 细粒度安全规则 / 超出 IDE 工具的多环境平台运维)?
- → **CloudBase MCP**。
5. 始终有用:安装 **CloudBase Skills** 作为编写 CloudBase 代码的知识约束 —— 它们不能替代任一执行面。
## 应当 / 禁止
**应当**
- 高频小程序工作流优先 Nightly + `wechatide`
- 保持安装 CloudBase Skills 以获取最佳实践知识
- 用 CloudBase MCP 填补 IDE Skills 未覆盖的缺口
**禁止**
- 在 `wechatide` 已能用微信登录完成日常 NoSQL / 函数 / 存储操作时,仍强制单独走腾讯云 MCP 登录
- 假定稳定版 DevTools 与 Nightly 具备相同的 Skills/MCP
- 复制或臆造 `wechatide` 工具 schema —— 使用 `--help` 与 Nightly 的 `tools.yaml`
## 相关
- 执行路径:[devtools-debug-preview.md](devtools-debug-preview.md)
- `wx.cloud` / OPENID / 客户端规则:[cloudbase-integration.md](cloudbase-integration.md)
- 消息推送 / 客服自动回复:[message-push-customer-service.md](message-push-customer-service.md)
references/miniprogram-development/SKILL.md
---
name: miniprogram-development
description: WeChat Mini Program development skill for building, debugging, previewing, testing, publishing, and optimizing mini program projects (小程序开发、调试、预览、发布). Covers project structure and config (`project.config.json`, `appid`, `miniprogramRoot`, `tabBar`, routing/navigation, icon assets), WeChat Developer Tools Nightly workflows (`wechatide` CLI, WeChat IDE Skills/MCP), `miniprogram-ci` preview/upload, console/network debugging, message push (消息推送) and customer-service auto-reply (客服消息), mini program SEO / search indexing (小程序搜索优化、页面收录、搜索推广、mpcrawler), and CloudBase integration (`wx.cloud`, 腾讯云开发, 云开发) when explicitly used. Use when users create, develop, modify, debug, preview, deploy, publish, or promote WeChat Mini Programs. NOT for Web frontend (use web-development), pure backend services (use cloudrun-development / cloud-functions), or UI-design-only tasks (use ui-design).
version: 2.33.1
alwaysApply: false
---
## Sibling skills (local only)
Sibling CloudBase skills ship beside this skill. Use local relative paths such as `../auth-tool-cloudbase/SKILL.md`.
If a referenced sibling skill file is missing from this environment, ask the user to install the full CloudBase plugin (or the missing skill). Do **not** HTTP-fetch remote skill or protocol markdown into the agent context.
**Cross-cutting protocols** (required before code changes or deployments):
- Change Safety Protocol: `../cloudbase-platform/references/protocols/change-safety-protocol.md`
- Deployment Gate: `../cloudbase-platform/references/protocols/deployment-gate.md`
## Activation Contract
### Use this first when
- The request is about WeChat Mini Program structure, pages, preview, publishing, or CloudBase mini program integration.
### Read before writing code if
- The user mentions `wx.cloud`, CloudBase mini programs, OPENID, mini program deployment/debug workflows, Nightly DevTools, `wechatide`, or WeChat IDE Skills.
- The user mentions message push (消息推送), customer-service auto-reply (客服消息/自动回复), or binding MsgType/Event callbacks to cloud functions.
### Then also read
- CloudBase auth -> `../auth-wechat-miniprogram/SKILL.md`
- CloudBase document DB -> `../cloudbase-document-database-in-wechat-miniprogram/SKILL.md`
- Mini Program WeChat Pay, 虚拟支付 (virtual payment, `wx.requestVirtualPayment`), or Integration Center generated payment functions -> `../cloudbase-wechat-integration/SKILL.md` (official docs: `https://docs.cloudbase.net/integration/wechat-pay-miniprogram/index.md`)
- UI generation -> `../ui-design/SKILL.md` first
### Do NOT use for
- Web auth flows or Web SDK-specific frontend implementation.
- WeChat Pay, 虚拟支付 / `wx.requestVirtualPayment`, payment callbacks, refunds, or Official Account OAuth details; use `cloudbase-wechat-integration` for those scenarios.
### Common mistakes / gotchas
- Generating a Web-style login flow for mini programs.
- Mixing Web SDK assumptions into `wx.cloud` projects.
- Applying CloudBase constraints before confirming the project actually uses CloudBase.
- Assuming Stable WeChat Developer Tools includes Nightly Skills/`wechatide` (it may not).
- Forcing CloudBase MCP Tencent Cloud login for daily mini program cloud ops when Nightly `wechatide` already works.
- Inventing `wechatide` tool names or flags instead of using `--help` / Nightly `tools.yaml`.
- Bypassing wxide CLI / IDE for message-push ops with low-level transport before `cloud_*_msg_push` is exposed (see [message-push-customer-service.md](references/message-push-customer-service.md)).
- Assuming cloud-function return values auto-reply to customer-service chats (must use `cloud.openapi.customerServiceMessage.send`).
- Making code or configuration changes without first following the Change Safety Protocol (`cloudbase-platform/references/protocols/change-safety-protocol.md`).
- Performing mini program upload/publish without first completing the checks in `cloudbase-platform/references/protocols/deployment-gate.md`.
## When to use this skill
Use this skill for **WeChat Mini Program development** when you need to:
- Build or modify mini program pages and components
- Organize mini program project structure and configuration
- Debug, preview, or publish mini program projects
- Work with WeChat Developer Tools workflows
- Handle mini program runtime behavior, assets, or page config files
- Integrate CloudBase in a mini program project when explicitly needed
**Do NOT use for:**
- Web frontend development (use `web-development`)
- Pure backend service development (use `cloudrun-development` or `cloud-functions` as appropriate)
- UI design-only tasks without mini program development context (use `ui-design`)
---
## How to use this skill (for a coding agent)
1. **Start with the general mini program workflow**
- Treat WeChat Mini Program development as the default scope
- Do not assume the project uses CloudBase unless the user or codebase indicates it
2. **Follow mini program project conventions**
- Keep mini program source under the configured mini program root
- Ensure page files include the required configuration file such as `index.json`
- Check `project.config.json` before suggesting preview or IDE workflows
3. **Route by scenario**
- If the task involves debugging, previewing, publishing, opening DevTools, console/network, or `wechatide`, read [debug and preview reference](references/devtools-debug-preview.md) first
- If choosing between WeChat IDE Skills and CloudBase MCP, read [WeChat IDE Skills vs CloudBase MCP](references/wxide-vs-cloudbase-mcp.md)
- If the task involves CloudBase, `wx.cloud`, cloud functions, CloudBase database/storage, or CloudBase identity handling, read [CloudBase integration reference](references/cloudbase-integration.md)
- If the task involves mini program SEO / WeChat search optimization / page indexing / search promotion (小程序搜索优化、页面收录、搜索推广、关键词排名), read [Mini Program SEO & WeChat Search Optimization](references/seo-search-optimization.md) first
- If the task involves message push (消息推送), customer-service auto-reply (客服消息自动回复), MsgType/Event → cloud function binding, or push-related function logs, read [Message Push & Customer Service Auto-Reply](references/message-push-customer-service.md) first
- If the task involves `tabBar`, icon assets, or label spacing, prefer the text-only custom `tabBar` default below unless the user explicitly requires icons
4. **Use CloudBase rules only when applicable**
- CloudBase / 微信云开发 is an important mini program integration path, but not a universal requirement
- Only apply CloudBase-specific auth, database, storage, or cloud function constraints when the project is using CloudBase
5. **Recommend the right preview/debug/cloud-ops path**
- Prefer **Nightly** WeChat Developer Tools (built-in Skills/MCP) and execute via `wechatide` when available — see [devtools-debug-preview.md](references/devtools-debug-preview.md)
- Nightly download: https://developers.weixin.qq.com/miniprogram/dev/devtools/nightly_backup.html
- If Nightly / `wechatide` is unavailable, fall back to `miniprogram-ci` for preview/upload and CloudBase MCP for cloud resources
---
# WeChat Mini Program Development Rules
## General Project Rules
1. **Project Structure**
- Mini program code should follow the project root configured in `project.config.json`
- Keep page-level files complete, including `.json` configuration files
- Ensure referenced local assets actually exist to avoid compile failures
2. **Configuration Checks**
- Check `project.config.json` before opening, previewing, or publishing a project
- Confirm `appid` is available when a real preview, upload, or WeChat Developer Tools workflow is required
- Confirm `miniprogramRoot` and related path settings are correct
3. **Resource Handling**
- For `tabBar`, prefer a text-only custom `tabBar` by default when the user does not explicitly need icons. This avoids icon asset handling, removes reserved icon space, and makes the label area easier to align.
- Only generate local icon assets and configure `iconPath` / `selectedIconPath` when the user explicitly asks for tab icons or the design requires them.
- When generating local asset references such as icons, ensure the files are downloaded into the project.
- Keep file paths stable and consistent with mini program config files.
### Recommended default for simple `tabBar`
Use `tabBar.custom = true`, keep only `pagePath` and `text` in `app.json`, and render text-only items in the custom component so there is no icon slot and no extra blank area above the label.
`app.json`
```json
{
"tabBar": {
"custom": true,
"list": [
{ "pagePath": "pages/index/index", "text": "首页" },
{ "pagePath": "pages/travel/travel", "text": "行程" },
{ "pagePath": "pages/my/my", "text": "我的" }
]
}
}
```
Keep the custom `tabBar` layout text-only, and use flex centering or matching `height` and `line-height` to remove the blank area above the label. Switch to downloaded local icons only when the user explicitly wants icon-based tabs.
## CloudBase as a Mini Program Sub-Scenario
- If the user explicitly uses CloudBase, `wx.cloud`, Tencent CloudBase, 腾讯云开发, or 云开发, follow the CloudBase integration reference
- In CloudBase mini program projects, use `wx.cloud` APIs and CloudBase environment configuration appropriately
- Do not apply CloudBase-specific rules to non-CloudBase mini program projects
## Debugging, Preview, and Publishing
- Prefer **Nightly** DevTools + `wechatide` for open project, compile, simulator, console/network debug, preview, upload, and daily cloud ops (WeChat login — no separate Tencent Cloud login)
- Always pass required context: `-c <clientName>`, absolute `--project`, valid `appid`, and cloud `env` when needed
- If Nightly / `wechatide` is not available, use `miniprogram-ci` as the fallback for preview/upload/npm, and CloudBase MCP for cloud resources; tell the user to install Nightly for full Skills/MCP
- For detailed workflows, read [debug and preview reference](references/devtools-debug-preview.md) and [WeChat IDE Skills vs CloudBase MCP](references/wxide-vs-cloudbase-mcp.md)
## Message Push & Customer Service Auto-Reply
> 微信生态专章:消息推送 / 客服自动回复细节以中文 reference 为准(术语保留英文 API 名)。
- **Current only ops path:** WeChat Developer Tools IDE + wxide CLI. Do not teach low-level bypasses while `cloud_query_msg_push` / `cloud_manage_msg_push` are not yet exposed (pending WeChat IDE CLI support).
- Deploy receiver functions with `cloud_fn_deploy` **and** `--remote-npm-install`; bind (MsgType, Event) → one cloud function in the IDE message-push panel until CLI tools land.
- Customer-service auto-reply requires `cloud.openapi.customerServiceMessage.send` plus `config.json` openapi permissions — function return values alone do not reply.
- Function logs: IDE **云开发控制台 → 云函数 → 日志**; the wxide CLI does not expose log query yet — do not teach low-level log CGI bypasses.
- Full reference: [Message Push & Customer Service Auto-Reply](references/message-push-customer-service.md)
## Minimal project skeleton
`app.js`
```js
App({
onLaunch() {
console.log("Mini Program launched");
},
});
```
`pages/index/index.js`
```js
Page({
data: {
message: "Hello CloudBase Mini Program",
},
});
```
`pages/index/index.wxml`
```xml
<view class="page">
<text>{{message}}</text>
</view>
```
`pages/index/index.json`
```json
{
"navigationBarTitleText": "Home"
}
```
`project.config.json`
```json
{
"appid": "your-mini-program-appid",
"projectname": "cloudbase-mini-program",
"miniprogramRoot": "./",
"compileType": "miniprogram"
}
```
## References
- [CloudBase Mini Program Integration](references/cloudbase-integration.md) — use this when the mini program project explicitly integrates CloudBase
- [WeChat DevTools Debug and Preview](references/devtools-debug-preview.md) — Nightly / `wechatide` paths, required context, and no-Nightly fallbacks
- [WeChat IDE Skills vs CloudBase MCP](references/wxide-vs-cloudbase-mcp.md) — layering and when to use which execution surface
- [Message Push & Customer Service Auto-Reply](references/message-push-customer-service.md) — 消息推送 / 客服自动回复 via wxide CLI + IDE (no low-level bypass; pending `cloud_*_msg_push`)
- [Mini Program SEO & WeChat Search Optimization](references/seo-search-optimization.md) — 小程序搜索优化 / page indexing / search promotion (`mpcrawler`, URL reachability, `navigator` jumps, titles & thumbnails)
- [Common Pitfalls](references/pitfalls.md) — read before generating code for optional chaining, TDesign styling, Canvas + storage, and environment issues
references/ops-inspector/references/alarm-interpretation.md
# Alarm Interpretation (告警解读)
Use this reference when the user asks whether an alert is normal, what peak QPS was, or whether CPU / throttle / invocation metrics look healthy.
## Hard rule: metrics tool only
- **Always** query metrics with `queryEnv(action="metrics", envId="<EnvId>", metricName="<name>")`.
- **Never** call `callCloudApi` (or guess Monitor / TCB Action names) to fetch curves. The MCP path already wraps `DescribeCurveData` via Manager SDK.
- Prefer the returned `Summary` (`max`, `min`, `avg`, `latest`, `peakTimestamp`, `allZero`) before dumping the full `Curve`.
## Default inspection window
- Alert triage: last **1 hour** (`startTime` / `endTime`, format `YYYY-MM-DD HH:mm:ss`)
- Peak / trend questions: last **24 hours** (tool default when times omitted)
- Incident window: narrow to the reported alert time ± 30–60 minutes
## Metric → question map
| User question | metricName | Notes |
|---------------|------------|-------|
| Peak / current QPS | `GatewayTraceEnvQPS` (preferred) or `EnvQPSAll` | Env-level `resourceID` defaults to `all|:|all|:|all|:|all` |
| Function call volume / "调用量为 0" | `FunctionInvocation` | Pass `resourceID=<functionName>` when scoped |
| Function errors | `FunctionError` | Pair with invocation |
| Function timeout | `FunctionTimeout` | |
| Throttle / 429 pressure | `FunctionThrottle` | Also search CLS for `429` |
| MySQL CPU alert | `MysqlCpuUsageRate` | Percent-like usage |
| MySQL memory / disk | `MysqlMemoryUse` / `MysqlStorageUsage` | |
| MySQL QPS / slow queries | `MysqlQps` / `MysqlSlowQueries` | |
| CloudRun CPU / QPS / HTTP errors | `TkeCpuUsedService` / `TkeQPSService` / `TkeHttpErrorService` | `resourceID` = service name (required) |
## Baseline thresholds (skill defaults)
These are **interpretation defaults for Agent answers**, not a substitute for the customer's contracted package. Always state the baseline used, and cross-check `envQuery(action="info")` / `envQuery(action="usage")` for package hints when available.
| Signal | Warning | Critical | Healthy interpretation |
|--------|---------|----------|------------------------|
| Gateway / env QPS vs common integration package (500 QPS) | Peak ≥ **70%** of package (≈350) | Peak ≥ **90%** of package (≈450) or sustained ≥ package | Peak well below package → alert may be false positive or short spike |
| MySQL CPU (`MysqlCpuUsageRate`) | Peak ≥ **80** | Peak ≥ **90** or sustained ≥ 80 for most of the window | Peak < 70 → "CPU 告警偏误报/短时尖刺可能性高" unless alert rule is stricter |
| MySQL memory / disk usage | Peak ≥ **80%** of capacity when unit is percent-like | Peak ≥ **90%** | Below 70% → capacity alert likely not sustained |
| Function error rate (`FunctionError.max` / max(`FunctionInvocation.max`, 1)) | ≥ **1%** | ≥ **5%** | Near 0 with healthy invocations → function path OK |
| Function throttle (`FunctionThrottle`) | Any non-zero in window | Sustained non-zero with user-visible 429 | allZero → throttle not evidenced by metrics |
| Function invocation | — | `Summary.allZero === true` while user expects traffic | See fault playbook "调用量为 0" |
If the environment package QPS is known and **not** 500, recompute 70%/90% against that value. If unknown, say: "按常见集成版默认 500 QPS 解读;若你们已升配,请以合同/控制台配额为准。"
## How to answer "CPU 告警是否正常?"
1. Confirm env: `envQuery(action="info", envId="...")`.
2. Pull `queryEnv(action="metrics", metricName="MysqlCpuUsageRate", ...)` for the alert window (and optionally last 24h for context).
3. Read `Summary.max` / `avg` / `latest` / `peakTimestamp`.
4. Compare to the MySQL CPU baselines above.
5. Answer in plain language, for example:
- Peak 42%, avg 18% → **告警偏不正常触发或已恢复**:当前窗口未达到 80% 基线。
- Peak 93% near the alert time → **告警合理**:峰值超过 90% 临界线,建议查慢查询 / 连接数 / 是否变配中。
6. Optional correlates: `MysqlSlowQueries`, `MysqlDbConnections`, `MysqlQps`.
## How to answer "峰值 QPS 多少?"
1. `queryEnv(action="metrics", metricName="GatewayTraceEnvQPS", envId="...")` (default last 24h or user window).
2. Report **`Summary.max` as 峰值 QPS**,并附上 `peakTimestamp`(若有)与 `avg` / `latest`.
3. Compare to package baseline (default 500 unless known otherwise) and state headroom: `headroom = package - peak`.
4. If `sampleCount === 0`, say metrics returned empty — do not invent a number; suggest console monitor and retry with a wider window.
## Report section template
```markdown
## 告警解读
| 问题 | 指标 | 窗口峰值 | 基线 | 结论 |
|------|------|----------|------|------|
| CPU 告警是否正常 | MysqlCpuUsageRate | ${max} | warn≥80 / crit≥90 | ... |
| 峰值 QPS | GatewayTraceEnvQPS | ${max} @ ${peakTimestamp} | package=${packageQps} | ... |
说明:基线为 ops-inspector 默认解读阈值;实际配额以环境套餐为准。
禁止使用 callCloudApi 拉取监控。
```
references/ops-inspector/references/fault-playbooks.md
# Fault Playbooks (故障剧本)
Four high-frequency KA / ops triage playbooks. Use the matching playbook when the symptom matches; always prefer MCP tools listed here — **never** invent Monitor/TCB Actions via `callCloudApi`.
Shared prerequisites for every playbook:
1. `envQuery(action="info")` — bind `envId`
2. Prefer metrics via `queryEnv(action="metrics", ...)`
3. Prefer logs via `queryLogs` / `queryFunctions` log actions
4. Summarize with severity + next action + console link
---
## Playbook 1 — HTTP 429 / rate limit
### Symptoms
- Client or gateway returns **429**
- User says 限频 / 被限流 / QPS 不够
- Activity traffic spike near launch
### Tools (in order)
1. `queryEnv(action="metrics", envId, metricName="GatewayTraceEnvQPS")` — peak QPS vs package baseline (see `alarm-interpretation.md`; default package **500** if unknown)
2. `queryEnv(action="metrics", envId, metricName="FunctionThrottle", resourceID="<functionName>")` when a function is implicated
3. `queryLogs(action="checkLogService")` then `queryLogs(action="searchLogs", queryString="429 OR throttle OR 限流 OR FREQUENCY", ...)`
4. Optional: `queryFunctions(action="listFunctionLogs", functionName="...")` for per-function evidence
### Decision tree
| Evidence | Likely cause | What to tell the user |
|----------|--------------|------------------------|
| Peak QPS ≥ ~90% of package | Package QPS ceiling | 告警/429 **合理**;需要升配或开按量(集成版可能无法自助,引导控制台/工单) |
| Peak QPS low but `FunctionThrottle` > 0 | Function concurrency / throttle | 查函数并发与预置并发;短时重试 + 降峰 |
| Metrics healthy, logs show 429 on one route | Route/gateway or upstream limit | 核对网关路由与上游;不要盲目升整环境配额 |
| No metrics + no logs | Observability gap | 先开 CLS;用控制台监控核对后再结论 |
### Do not
- Do not call `callCloudApi` to guess rate-limit APIs
- Do not claim the package was raised unless the user or console confirms
---
## Playbook 2 — Cloud function intermittent / persistent 404
### Symptoms
- Invoking a cloud function or HTTP access path returns **404**
- Works sometimes / after redeploy / only on one path
- "函数找不到" / `FUNCTION_NOT_FOUND` / gateway 404
### Tools (in order)
1. `queryFunctions(action="listFunctions")` — does the name exist in **this** env?
2. `queryFunctions(action="getFunctionDetail", functionName="...")` — status, HTTP access, triggers
3. If HTTP: confirm path / domain / gateway with `queryGateway` / hosting docs as available; mismatch often looks like 404
4. `queryLogs(action="searchLogs", queryString="404 OR FUNCTION_NOT_FOUND OR functionName:<name>", ...)`
5. `queryEnv(action="metrics", metricName="FunctionInvocation", resourceID="<functionName>")` — if `allZero`, traffic never reached the function (client/env/path issue more likely than runtime crash)
### Decision tree
| Evidence | Likely cause | What to tell the user |
|----------|--------------|------------------------|
| Function missing in list | Wrong env or never deployed | 切换到正确 envId 或重新部署 |
| Function exists, invocation allZero, client 404 | Wrong URL / path / domain / envId in client | 核对调用方 env 与路径,而非改函数代码 |
| Function exists, invocations > 0, sporadic 404 | Bad alias/version, deleted route, or wrong qualifier | 查版本/别名/HTTP 访问配置与最近部署 |
| Only HTTP path 404 | Gateway route / static hosting path | 查路由 Enable 与 upstream,勿只看函数列表 |
### Do not
- Do not redeploy blindly before confirming the function exists in the target env
- Do not use Web SDK auth debugging for pure routing 404s
---
## Playbook 3 — ACCESS_TOKEN_INVALID
### Symptoms
- API / SDK returns **ACCESS_TOKEN_INVALID** (or token invalid / unauthorized token)
- Login worked before, then sudden failures
- PG / HTTP / management calls fail auth while env is healthy
### Tools (in order)
1. Confirm which surface failed: **app user token** vs **env API key / publishable key** vs **MCP management login**
2. App auth: follow `../auth-tool-cloudbase/SKILL.md` then platform auth skill (`auth-web` / `auth-wechat` / `auth-nodejs`) — check providers with `queryAppAuth` / related auth tools when available
3. Env binding: `envQuery(action="info")` — wrong env often presents as token/env mismatch
4. `queryLogs(action="searchLogs", queryString="ACCESS_TOKEN_INVALID OR token invalid OR unauthorized", ...)`
5. Metrics are usually secondary; only use `queryEnv(action="metrics")` if correlating a traffic drop after auth breakage
### Decision tree
| Evidence | Likely cause | What to tell the user |
|----------|--------------|------------------------|
| Expired / rotated key or publishable key | Credential lifecycle | 轮换后更新客户端配置;不要把密钥写进仓库 |
| Provider disabled or misconfigured | Auth provider readiness | 先用 auth-tool 打开并校验登录方式 |
| Token from env A used on env B | Env mismatch | 统一 envId 与初始化配置 |
| MCP/management auth failure | Agent not logged in / wrong credential type | 走 `auth` 登录与环境绑定,而不是改业务代码 |
| PG API Key limitation | Product constraint | 说明集成版/PG 场景限制,避免反复重试同一错误密钥类型 |
### Do not
- Do not paste secrets into chat or source
- Do not "fix" by switching to anonymous login unless the product intentionally allows it
- Do not call `callCloudApi` to probe undocumented auth Actions
---
## Playbook 4 — Function / API call volume is 0(调用量为 0)
### Symptoms
- Console or user says 调用次数为 0 / 没有调用量
- Activity is online but metrics stay flat
- Need to know whether the app is broken or metrics are empty
### Tools (in order)
1. `queryEnv(action="metrics", envId, metricName="FunctionInvocation", resourceID="<functionName>")` — inspect `Summary.allZero`, `max`, `sampleCount`
2. Also pull `GatewayTraceEnvQPS` — distinguishes "no function traffic" vs "no env traffic at all"
3. `queryFunctions(action="listFunctions")` + `getFunctionDetail` — triggers, HTTP access, status
4. `queryLogs` / `listFunctionLogs` over the same window — absolute silence vs client errors
5. If CloudRun: `TkeInvokeNumService` / `TkeQPSService` with `resourceID=<serviceName>`
### Decision tree
| Evidence | Likely cause | What to tell the user |
|----------|--------------|------------------------|
| `FunctionInvocation` allZero + gateway QPS also ~0 | No client traffic to this env | 检查前端/活动是否指向该 envId,DNS/域名是否生效 |
| Gateway QPS > 0 but function invocation 0 | Traffic not routed to that function | 查网关路由、函数名、触发器、HTTP 路径 |
| Invocations > 0 in metrics but console UI shows 0 | UI delay / wrong console filter | 以 `queryEnv(metrics)` Summary 为准并给出峰值时间 |
| sampleCount 0 | Empty monitor window / API empty | 扩大时间窗;不要断言业务一定无流量 |
| Function inactive / missing | Not deployed | 部署后再看指标 |
### Do not
- Do not conclude "platform outage" from a single empty series without checking gateway QPS and function existence
- Do not use `callCloudApi` to pull custom monitor Actions
references/ops-inspector/SKILL.md
---
name: ops-inspector
description: AIOps-style CloudBase inspection skill (v3). Use when users need health checks, log diagnosis, alarm interpretation (CPU alert normal?, peak QPS), metrics via queryEnv(action=metrics), or fault playbooks for 429 / function 404 / ACCESS_TOKEN_INVALID / zero invocations. Triggers on 巡检, 诊断, 告警, 峰值 QPS, 限频, 调用量为 0, troubleshooting.
version: 2.33.1
alwaysApply: false
---
## Sibling skills (local only)
Sibling CloudBase skills ship beside this skill. Use local relative paths such as `../auth-tool-cloudbase/SKILL.md`.
If a referenced sibling skill file is missing from this environment, ask the user to install the full CloudBase plugin (or the missing skill). Do **not** HTTP-fetch remote skill or protocol markdown into the agent context.
## Activation Contract
### Use this first when
- The user wants to check the health or status of CloudBase resources (cloud functions, CloudRun, databases, storage, etc.).
- The user reports errors, failures, or abnormal behavior and wants a quick diagnosis.
- The user asks for an "inspection", "health check", "巡检", "诊断", or "troubleshooting" of their CloudBase environment.
- The user wants to review recent error logs across services.
- The user asks **告警解读** questions: whether a **CPU 告警** is normal, what **峰值 QPS** was, or whether throttle/error metrics look healthy.
- The symptom matches a v3 fault playbook: **429 / 限频**, **云函数 404**, **ACCESS_TOKEN_INVALID**, or **调用量为 0**.
### Read before writing code if
- The inspection reveals code-level issues in cloud functions or CloudRun services — then read the relevant implementation skill before suggesting fixes.
- The user wants to fix a problem found during inspection rather than just diagnose it.
### Then also read
- Alarm interpretation baselines -> `references/alarm-interpretation.md`
- Fault playbooks (429 / 404 / token / zero calls) -> `references/fault-playbooks.md`
- Cloud function issues -> `../cloud-functions/SKILL.md`
- CloudRun issues -> `../cloudrun-development/SKILL.md`
- Database issues -> `../postgresql-development-cloudbase/SKILL.md` for CloudBase PG / PostgreSQL, `../relational-database-mcp-cloudbase/SKILL.md` for MySQL, or `../cloudbase-document-database-web-sdk/SKILL.md` for NoSQL
- Auth readiness (token failures) -> `../auth-tool-cloudbase/SKILL.md`
- Platform overview -> `../cloudbase-platform/SKILL.md`
### Do NOT use for
- Deploying new resources or writing application code. This skill is read-only and diagnostic.
- Replacing proper monitoring/alerting infrastructure. It provides point-in-time inspection, not continuous monitoring.
- Directly fixing problems — it diagnoses and recommends; actual fixes should use the appropriate implementation skill.
- Fetching metrics by guessing cloud API Actions. **Never** use `callCloudApi` for monitor curves — always use `queryEnv(action="metrics")`.
### Common mistakes / gotchas
- Running a full inspection without first confirming the environment is bound (`auth` tool must show logged-in and env-bound state).
- Ignoring CLS log service status — if CLS is not enabled, `queryLogs` will fail; always check first with `queryLogs(action="checkLogService")`.
- Searching logs without a time range — this can return excessive or irrelevant results. Always scope searches to a relevant time window.
- Treating a single error log as the root cause without correlating across resources. A function error may stem from a database or config issue.
- Answering "峰值 QPS" / "CPU 告警是否正常" from screenshots or memory instead of `queryEnv(action="metrics")`.
- Calling `callCloudApi` with invented `GetMonitorData` / `DescribeCurveData` parameters — the metrics branch already wraps Manager SDK.
### Minimal checklist
- [ ] Environment is bound and accessible (`envQuery(action="info")`)
- [ ] Metrics pulled with `queryEnv(action="metrics")` when the question involves QPS / CPU / throttle / invocation volume
- [ ] CLS log service is enabled (`queryLogs(action="checkLogService")`) when log diagnosis is needed
- [ ] Matching fault playbook selected when symptoms match 429 / function 404 / ACCESS_TOKEN_INVALID / 调用量为 0
- [ ] Time range is specified for any log or metrics searches
- [ ] Findings are summarized with severity levels, **告警解读**, and actionable recommendations
---
## How to use this skill (for a coding agent)
### Ops Inspector v3 additions
v3 adds two mandatory capabilities on top of log/resource inspection:
1. **告警解读** — pull metrics, compare to baselines in `references/alarm-interpretation.md`, answer CPU-alert / peak-QPS style questions in plain language.
2. **故障剧本** — when symptoms match, follow `references/fault-playbooks.md` instead of ad-hoc tool fishing.
### Inspection Modes
| Mode | When to use | Scope |
|------|-------------|-------|
| **Full inspection** | User asks for a general health check / 巡检 / 全面检查 | All resource types + core metrics |
| **Targeted inspection** | User reports a specific error or asks about a specific resource | One resource type or playbook |
| **Alarm interpretation** | User asks CPU 告警是否正常 / 峰值 QPS / 是否限流 | Metrics-first, then logs |
| **Fault playbook** | 429 / function 404 / ACCESS_TOKEN_INVALID / 调用量为 0 | Playbook steps only |
### Full Inspection Workflow
Follow these steps in order for a comprehensive environment health check:
**Step 1 — Environment Check**
```
envQuery(action="info")
```
Confirm the environment is accessible. Record the `envId` for console link generation.
**Step 2 — Metrics snapshot (v3)**
```
queryEnv(action="metrics", envId="<EnvId>", metricName="GatewayTraceEnvQPS")
queryEnv(action="metrics", envId="<EnvId>", metricName="FunctionInvocation")
queryEnv(action="metrics", envId="<EnvId>", metricName="MysqlCpuUsageRate")
```
Use returned `Summary.max` / `avg` / `allZero` / `peakTimestamp`. Add `FunctionError`, `FunctionThrottle`, or CloudRun `Tke*` metrics when those resources exist. Read `references/alarm-interpretation.md` before concluding.
**Step 3 — Log Service Status**
```
queryLogs(action="checkLogService")
```
If CLS is not enabled, note this as a **warning** — log-based diagnosis will be unavailable. Recommend enabling CLS in the console: `https://tcb.cloud.tencent.com/dev?envId=${envId}#/devops/log`
**Step 4 — Cloud Functions Inspection**
```
queryFunctions(action="listFunctions")
```
For each function, check:
- **Status**: Is the function in an active/deployed state?
- **Recent errors**: `queryFunctions(action="listFunctionLogs", functionName="<name>", startTime="<recent>")`
- **Common issues**:
- Timeout errors (execution exceeded limit)
- Memory limit exceeded
- Runtime errors (unhandled exceptions)
- Cold start frequency
- Zero invocations while traffic is expected → Playbook 4
**Step 5 — CloudRun Services Inspection**
```
queryCloudRun(action="list")
```
For each service, check:
- **Status**: Is the service running?
- **Detail**: `queryCloudRun(action="detail", detailServerName="<name>")`
- **Metrics**: `queryEnv(action="metrics", metricName="TkeQPSService", resourceID="<serviceName>")` (resourceID required)
- **Common issues**:
- Service not running (scaled to zero or crashed)
- Image pull failures
- OOMKilled events
- Health check failures
**Step 6 — Error Log Aggregation** (if CLS is enabled)
```
queryLogs(action="searchLogs", queryString="ERROR", service="tcb", startTime="<24h-ago>", limit=50)
queryLogs(action="searchLogs", queryString="ERROR", service="tcbr", startTime="<24h-ago>", limit=50)
```
Look for patterns:
- Repeated error messages (same error many times)
- Cascading failures (errors in multiple services around the same time)
- Timeout / 429 / 404 / ACCESS_TOKEN_INVALID patterns → jump to the matching playbook
**Step 7 — Summary Report**
Generate a structured report:
```markdown
# CloudBase Resource Inspection Report
**Environment**: ${envId}
**Inspection Time**: ${timestamp}
## Overall Health: ✅ Healthy / ⚠️ Warnings Found / ❌ Issues Found
## 告警解读
| 问题 | 指标 | 窗口峰值 | 基线 | 结论 |
|------|------|----------|------|------|
| 峰值 QPS | GatewayTraceEnvQPS | ... | package default 500 unless known | ... |
| CPU 告警是否正常 | MysqlCpuUsageRate | ... | warn≥80 / crit≥90 | ... |
### Cloud Functions
| Function | Status | Recent Errors | Invocations | Severity |
|----------|--------|---------------|-------------|----------|
| ... | ... | ... | ... | ... |
### CloudRun Services
| Service | Status | Issues | Severity |
|---------|--------|--------|----------|
| ... | ... | ... | ... |
### Error Log Summary
- Total errors in last 24h: N
- Top error patterns: ...
## Recommendations
1. ...
2. ...
## Console Links
- Cloud Functions: https://tcb.cloud.tencent.com/dev?envId=${envId}#/scf
- CloudRun: https://tcb.cloud.tencent.com/dev?envId=${envId}#/platform-run
- Logs: https://tcb.cloud.tencent.com/dev?envId=${envId}#/devops/log
- Monitor: https://tcb.cloud.tencent.com/dev?envId=${envId}#/devops
```
### Targeted Inspection Workflow
When the user specifies a resource type or a specific resource:
1. **Cloud function errors**: `queryFunctions(action="listFunctionLogs", functionName="<name>")` then `queryLogs(action="searchLogs", queryString="* AND functionName:<name> AND level:ERROR", ...)`
2. **CloudRun errors**: `queryCloudRun(action="detail", detailServerName="<name>")` then `queryLogs(action="searchLogs", queryString="ERROR", service="tcbr", ...)`
- If logs show DB / Redis connection failures (`ECONNREFUSED`, timeout, "could not connect"): check whether `VpcConf` is set and matches the database VPC. See `cloudrun-development/references/vpc-and-database.md`.
3. **Database issues**: Check `queryPgDatabase(action="context"|"metadata"|"objects")` for CloudBase PG, `queryMysqlDatabase` for MySQL, or `readNoSqlDatabaseStructure` for NoSQL depending on type; for CPU/disk alerts also pull `MysqlCpuUsageRate` / `MysqlStorageUsage` metrics
4. **General error search**: `queryLogs(action="searchLogs", queryString="<error-keyword>", ...)`
5. **Alarm / QPS questions**: follow `references/alarm-interpretation.md`
6. **429 / function 404 / ACCESS_TOKEN_INVALID / 调用量为 0**: follow `references/fault-playbooks.md`
### AIOps Methodology
This skill follows AIOps principles for intelligent inspection:
1. **Data Collection**: Gather metrics (`queryEnv` metrics), logs, and resource states via MCP tools — never via ad-hoc `callCloudApi`
2. **Pattern Recognition**: Identify recurring errors, anomaly patterns, and correlations across services
3. **Baseline Comparison**: Compare metric `Summary` values to skill baselines (告警解读)
4. **Root Cause Hypothesis**: Based on error patterns + metrics, suggest likely root causes
5. **Actionable Recommendations**: Provide specific, prioritized remediation steps with links to relevant skills and console pages
### Severity Levels
| Level | Icon | Meaning |
|-------|------|---------|
| Critical | ❌ | Service is down or data is at risk; requires immediate action |
| Warning | ⚠️ | Errors detected but service is still partially functional; investigate soon |
| Info | ℹ️ | No errors found; informational status only |
| Healthy | ✅ | Resource is operating normally |
### Preferred Tool Map
| Operation | MCP Tool Call |
|-----------|---------------|
| Check environment | `envQuery(action="info")` |
| Query metrics (QPS/CPU/invocations) | `queryEnv(action="metrics", envId, metricName="...")` |
| Check CLS status | `queryLogs(action="checkLogService")` |
| List cloud functions | `queryFunctions(action="listFunctions")` |
| Get function detail | `queryFunctions(action="getFunctionDetail", functionName="<name>")` |
| Get function logs | `queryFunctions(action="listFunctionLogs", functionName="<name>", startTime="<time>", endTime="<time>")` |
| Get function log detail | `queryFunctions(action="getFunctionLogDetail", requestId="<id>")` |
| List CloudRun services | `queryCloudRun(action="list")` |
| Get CloudRun detail | `queryCloudRun(action="detail", detailServerName="<name>")` |
| Search CLS logs | `queryLogs(action="searchLogs", queryString="<query>", service="tcb\|tcbr", startTime="<time>", endTime="<time>")` |
| Check NoSQL structure | `readNoSqlDatabaseStructure(action="listCollections")` |
| Check PostgreSQL context | `queryPgDatabase(action="context")` |
| Check PostgreSQL metadata | `queryPgDatabase(action="metadata", limit=20)` |
| Check MySQL status | `queryMysqlDatabase(action="getContext")` |
| Auth provider readiness | `queryAppAuth` / auth-tool skill (for ACCESS_TOKEN_INVALID) |
### Common CLS Query Patterns
| Scenario | queryString |
|----------|-------------|
| All errors | `ERROR` |
| Function timeout | `timeout OR 超时` |
| Function OOM | `OOM OR out of memory OR 内存超限` |
| CloudRun crash | `crash OR OOMKilled OR Error` |
| Specific function errors | `functionName:<name> AND level:ERROR` |
| 5xx HTTP errors | `statusCode:>499` |
| 429 / throttle | `429 OR throttle OR 限流 OR FREQUENCY` |
| Function 404 | `404 OR FUNCTION_NOT_FOUND` |
| Token invalid | `ACCESS_TOKEN_INVALID OR token invalid` |
| Cold start issues | `coldStart OR 冷启动` |
### Time Range Guidance
- **Quick check**: Last 1 hour (`startTime` = 1 hour ago)
- **Standard inspection**: Last 24 hours
- **Trend analysis**: Last 7 days
- **Specific incident**: Narrow to the reported time window
Always use ISO-like `YYYY-MM-DD HH:mm:ss` for metrics `startTime`/`endTime`, e.g., `"2026-08-17 00:00:00"`.
## Related Skills
- `cloud-functions` — Cloud function development, deployment, and debugging
- `cloudrun-development` — CloudRun backend deployment and management
- `cloudbase-platform` — General platform knowledge and console navigation
- `postgresql-development-cloudbase` — CloudBase PostgreSQL / PG diagnostics and schema/RLS checks
- `relational-database-mcp-cloudbase` — MySQL database management and diagnostics
- `auth-tool-cloudbase` — Auth provider readiness for token failures
references/postgresql-development-cloudbase/references/app-workflow.md
# CloudBase PG App Workflow
Use this reference when building or repairing a real user-facing Web app backed by CloudBase PostgreSQL. The goal is a working product flow, not broad platform exploration. In scaffold/TODO apps, favor vertical closure over repo inventory.
## Closure Path
1. Inspect the active app code first:
- `src/lib/backend.*`
- `src/lib/auth.*`
- `src/lib/*service.*`
- route guards
- form submit handlers
- Open these files directly. Do not begin with generic repo inventory, delegated exploration, or a repo-wide file list.
2. If those files still contain TODOs, implement them in place before optional platform research or helper creation.
3. Confirm the environment has the required capabilities:
- username/password auth if the app logs in with plain usernames
- PostgreSQL resource
- Cloud Storage if the app uploads files. In CloudBase PG, "having storage" means having an explicitly-created `pgstore` bucket — same model as Supabase Storage. Check existing buckets with `queryPgStorage(action="buckets")`. If the upload target (e.g. `covers`) is missing, create/select the bucket through the documented CloudBase management surface or console before writing browser upload code. Browser SDKs cannot create a pgstore bucket; the legacy NoSQL bucket reported in `EnvInfo.Storages[]` is NOT a valid pgstore target.
4. Create or repair the minimal PG schema via the migration workflow (not bare `execute` for DDL):
- If the workspace is missing local SQL for versions already in remote history, first run `managePgDatabase(action="fetchMigration")` (add `force=true` only when intentionally overwriting drifted local files).
- Choose `migrationVersion` (`YYYYMMDDHHMMSS`) + `migrationName`, write `cloudbase/migrations/<version>_<name>.sql` (same dir as CLI `tcb db pg migration`), then `managePgDatabase(action="applyMigration", migrationName, migrationVersion, sql, confirm=true)`.
- Prefer `migrationVersion` strictly newer than remote `LatestVersion`. For intentional out-of-order (branch/backfill) only, add `includeAll=true` (CLI `--include-all`).
- Apply required `GRANT` statements, sequence grants for `serial`/`bigserial`, `ALTER TABLE ... ENABLE ROW LEVEL SECURITY`, and RLS policies (same migration SQL or follow-up `execute` for ops-only GRANT/POLICY).
5. Immediately call `queryPgDatabase(action="objects")`, then `queryPgDatabase(action="schema", objectName="public.<table>")` for every table touched by the app.
6. Implement browser CRUD with one shared CloudBase Web SDK app instance and `app.rdb()`.
7. Implement auth guards and owner UID lookup with `auth.getSession()`, not `auth.getUser()`.
8. If direct browser `app.rdb()` uses RLS, verify the policy identity through the real browser session before depending on it.
9. Implement browser uploads with the documented CloudBase Storage Web SDK surface. The bucket from step 3 must exist BEFORE this step runs; pass the bucket to `from(bucket)` and pass only the object key to `upload(key, file)` (e.g. `app.storage.from("covers").upload("<file>", file)`). Do not repeat the bucket prefix inside the key.
10. Run the local build/typecheck.
11. Exercise the actual browser flow: login, create, list, edit/delete if required.
12. Read back persisted rows with `queryPgDatabase` before claiming done.
## Minimum Schema Pattern
For CMS/admin-style apps, prefer a small explicit schema:
```sql
create table if not exists public.user_roles (
uid text primary key,
username text not null unique,
role text not null check (role in ('admin', 'editor')),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create table if not exists public.articles (
_id uuid primary key default gen_random_uuid(),
title text not null,
summary text,
cover_image text,
content text,
status text not null default 'draft' check (status in ('draft', 'published')),
author_id varchar(64) not null default auth.uid(),
author_name text not null,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
```
Then grant the user-facing role and create policies before browser CRUD:
```sql
GRANT SELECT, INSERT, UPDATE, DELETE ON public.articles TO authenticated;
GRANT SELECT ON public.user_roles TO authenticated;
ALTER TABLE public.articles ENABLE ROW LEVEL SECURITY;
CREATE POLICY articles_owner_all ON public.articles
FOR ALL TO authenticated
USING (author_id = auth.uid())
WITH CHECK (author_id = auth.uid());
```
If a table uses `serial` / `bigserial`, also grant its sequence: `GRANT USAGE, SELECT ON SEQUENCE public.<table>_<column>_seq TO authenticated;`.
If the environment is disposable or the task requires a known schema, prefer an explicit `drop table if exists ... cascade` followed by `create table`. Do not rely on `create table if not exists` to fix incompatible old columns.
## Browser Data Access
Use one shared app:
```ts
import cloudbase from "@cloudbase/js-sdk";
export const app = cloudbase.init({ env: import.meta.env.VITE_ENV_ID });
export const auth = app.auth;
export const db = app.rdb();
```
Use the session as the single source of truth for login state and current UID:
```ts
async function getActiveSession() {
const { data, error } = await auth.getSession();
const session = data?.session;
if (error || !session || session.user?.is_anonymous) return null;
return session;
}
export async function checkAuth() {
return Boolean(await getActiveSession());
}
export async function getCurrentUser() {
const session = await getActiveSession();
const user = session?.user;
if (!user) return null;
const uid = user.id || user.sub || user.uid;
if (!uid) return null;
return {
uid,
displayName:
user.user_metadata?.username ||
user.username ||
user.email ||
uid,
};
}
```
Do not use `auth.getUser()` as a route guard. A non-null `data` wrapper is not proof of a real username/password login. Login is successful only when `signInWithPassword(...)` returns no `error` and includes `data.session`.
Use `app.rdb()` for business data:
```ts
const session = await getActiveSession();
const uid = session?.user?.id;
if (!uid) throw new Error("Please log in first");
const { data, error } = await db.from("articles").insert({
title,
author_name: username,
status: "draft",
});
```
Do not hand-build Bearer-token HTTP requests or call unverified helpers such as `user.getIdToken()` from browser CRUD. If raw HTTP is unavoidable, first prove the installed SDK exposes the required session/token API.
## RLS Identity Verification
When using direct browser `app.rdb()` with RLS, the database policy identity must match the Web session identity:
1. Log in through the actual app with username/password.
2. Read the active session and record `session.user.id`.
3. Insert a test article through `app.rdb()` without passing `author_id` if the column uses `DEFAULT auth.uid()`.
4. Read the inserted row through `queryPgDatabase` and confirm `author_id` equals `session.user.id`.
5. If the browser insert fails, do not continue by hiding the error in the UI. Inspect the exact error and either fix the RLS policy identity expression or move authorization to a server/RPC boundary.
`auth.uid()` is the official CloudBase PG helper for JWT `sub`; still verify it through the real browser session before trusting the policy.
## Verification
After schema setup:
```text
queryPgDatabase(action="schema", objectName="public.articles")
queryPgDatabase(action="schema", objectName="public.user_roles")
```
After browser CRUD:
```sql
select _id, title, author_id, status from public.articles order by created_at desc limit 5;
select uid, username, role from public.user_roles order by created_at desc limit 5;
```
Stop when the user-facing flow works and rows are visible in PG. Do not continue researching optional APIs after this point.
references/postgresql-development-cloudbase/references/auth-and-rls.md
# CloudBase PG Auth And RLS
Use this reference before writing browser-side PG CRUD or database policies.
## Key / role mapping
| Credential | Database role | Where it can live | Notes |
| --- | --- | --- | --- |
| Publishable Key | `anon` | Frontend-safe | Still constrained by GRANT/RLS. |
| User access token | `authenticated` | SDK-managed frontend session | Represents a real logged-in user. |
| API Key | `service_role` | Backend / trusted tooling only | Bypasses RLS; never expose to browser code. Treat any leak as a serious credential compromise and rotate/revoke it via `manageAppAuth(action="deleteApiKey")` immediately. |
## `auth.users` — the built-in account table
CloudBase PG stores account data in `auth.users` (schema `auth`), not a business-defined table. Query it directly instead of creating a parallel `public.users` table for identity data:
```sql
select id, email, phone, app_metadata, user_metadata from auth.users;
-- Join business tables against the built-in account table
select o.*, u.user_metadata->>'name' as buyer_name
from public.orders o
join auth.users u on u.id = o.buyer_id;
```
Only create a separate `public.*` table (e.g. `user_roles`, `profiles`) when the app needs fields that do not belong in `auth.users`, such as an app-specific role or a denormalized display name for fast reads. Do not duplicate `email`/`phone` into a new table just to avoid a join.
## SQL identity helpers
CloudBase PG provides official SQL helpers:
```sql
select auth.uid(); -- JWT sub / user id (returns text, NOT uuid)
select auth.role(); -- anon / authenticated / service_role
select auth.jwt(); -- full JWT claims as jsonb
select auth.email(); -- current email if available
```
**⚠️ CRITICAL: `auth.uid()` returns `text`, not `uuid`.** Unlike Supabase (where `auth.uid()` is `uuid`), CloudBase returns a string so identity can cover WeChat `openid` and other non-UUID providers. Prefer owner columns as `varchar(64)` / `text` so comparisons stay type-safe:
```sql
-- Preferred: text/varchar owner column matches auth.uid() directly
owner_id varchar(64) not null default auth.uid()
-- ...
USING (owner_id = auth.uid())
```
If an existing column is `uuid`, cast explicitly or you will get `ERROR: operator does not exist: uuid = text`:
```sql
-- Only when the JWT sub is a valid UUID AND the column type is uuid
USING (author_id = auth.uid()::uuid)
WITH CHECK (author_id = auth.uid()::uuid)
```
Do not cast when the identity may be a WeChat `openid` or other non-UUID string — keep the column as `text` / `varchar` instead.
**⚠️ CRITICAL: Always use `auth.uid()` for user identity in RLS policies.** Do NOT use `current_user` or `current_setting(...)` — these are PostgreSQL built-in functions that return the database role name (e.g. `authenticated`), not the CloudBase auth user ID. Using `current_user` in a policy like `USING (author_id = current_user)` will never match any real user ID.
If you are unsure whether the auth helper functions are available in your environment, run:
```sql
SELECT proname FROM pg_proc WHERE pronamespace = 'auth'::regnamespace;
```
This returns the list of available `auth.*` functions (e.g. `uid`, `role`, `jwt`, `email`).
Prefer database-owned identity fields:
```sql
create table public.todos (
id bigserial primary key,
title text not null,
owner_id varchar(64) not null default auth.uid(),
created_at timestamptz not null default now()
);
```
Frontend insert payloads should omit `owner_id` / `author_id` when the column has `DEFAULT auth.uid()`.
## Minimum GRANT + RLS sequence
Business tables require two layers: table-level GRANT and row-level RLS. Both must pass.
```sql
create table public.todos (
id bigserial primary key,
title text not null,
is_completed boolean not null default false,
owner_id varchar(64) not null default auth.uid(),
created_at timestamptz not null default now()
);
create index idx_todos_owner_id on public.todos(owner_id);
-- Table permissions
GRANT SELECT, INSERT, UPDATE, DELETE ON public.todos TO authenticated;
GRANT USAGE, SELECT ON SEQUENCE public.todos_id_seq TO authenticated;
GRANT ALL ON public.todos TO service_role;
GRANT USAGE, SELECT ON SEQUENCE public.todos_id_seq TO service_role;
-- Row permissions
ALTER TABLE public.todos ENABLE ROW LEVEL SECURITY;
CREATE POLICY todos_select_own ON public.todos
FOR SELECT TO authenticated
USING (owner_id = auth.uid());
CREATE POLICY todos_insert_own ON public.todos
FOR INSERT TO authenticated
WITH CHECK (owner_id = auth.uid());
CREATE POLICY todos_update_own ON public.todos
FOR UPDATE TO authenticated
USING (owner_id = auth.uid())
WITH CHECK (owner_id = auth.uid());
CREATE POLICY todos_delete_own ON public.todos
FOR DELETE TO authenticated
USING (owner_id = auth.uid());
```
## Accessing PG from a cloud function
A cloud function has two distinct ways to reach PG, and they are NOT interchangeable. Choose based on whether the function should act as the logged-in caller or as an admin/backend task:
1. **Act as the caller, stay RLS-constrained** — when a function wraps business logic but must still respect row ownership (e.g. an HTTP Function that a logged-in user calls to run a multi-step update). The caller's access token must reach the function and then be forwarded to the PG REST call:
```js
const res = await fetch(`https://${envId}.api.tcloudbasegateway.com/v1/rdb/rest/orders?select=*`, {
headers: { Authorization: `Bearer ${callerAccessToken}` },
});
```
Do not guess the exact field/property that carries `callerAccessToken` from `event`/`context` (or an HTTP Function's `req.headers`). Verify it against the installed `@cloudbase/node-sdk` version and the official cloud-functions docs before writing this code; if you cannot verify the field, treat this pattern as unavailable rather than inventing a shape.
2. **Act as admin, bypass RLS with the API Key (`service_role`)** — use this only for admin/backend tasks such as batch imports, cross-user aggregation, or scheduled jobs, and only inside a cloud function / CloudRun service:
```js
// Inject the API Key via function environment variables; never return it to the client.
const res = await fetch(`https://${envId}.api.tcloudbasegateway.com/v1/rdb/rest/orders?select=*`, {
headers: { Authorization: `Bearer ${process.env.CLOUDBASE_API_KEY}` },
});
```
Do not default to the API Key path just because it is simpler — if the task only needs "let the logged-in user read/write their own rows," forwarding the caller's access token keeps RLS as the enforcement layer and avoids re-implementing ownership checks in function code. Never let a function response leak the API Key back to the frontend.
## Pitfalls
- RLS enabled with zero policies denies all non-`service_role` access.
- Policy without GRANT still fails at the table-permission layer.
- `UPDATE` must normally include both `USING` and `WITH CHECK` to prevent owner-field reassignment.
- `serial` / `bigserial` requires sequence grants or inserts can fail.
- Admin/control-plane execution can hide user-facing permission failures; test as `anon` / `authenticated` when possible.
- **Do NOT use `current_user` in RLS policies.** `current_user` returns the database role name (e.g. `authenticated`), not the actual user ID. Always use `auth.uid()` for user identity checks.
- **`auth.uid()` is `text`.** Comparing it to a `uuid` column without `::uuid` fails with `operator does not exist: uuid = text`. Prefer `varchar(64)` / `text` owner columns; cast only when the column is already `uuid` and the JWT `sub` is a valid UUID.
references/postgresql-development-cloudbase/references/http-api.md
# CloudBase PG HTTP API
Use the HTTP API only when the SDK path is blocked or the caller is a non-SDK client.
## Base endpoints
```text
REST: https://<envId>.api.tcloudbasegateway.com/v1/rdb/rest/<table>
Auth: https://<envId>.api.tcloudbasegateway.com/auth/v1
```
Schema-qualified forms may also be available depending on the target database shape:
```text
/v1/rdb/rest/<schema>/<table>
/v1/rdb/rest/<instance>/<schema>/<table>
```
## Auth header
```http
Authorization: Bearer <Publishable Key | access_token | API Key>
```
- Publishable Key maps to `anon`.
- User access token maps to `authenticated`.
- API Key maps to `service_role` and must never be exposed in browser code.
## Query examples
```bash
curl "https://<envId>.api.tcloudbasegateway.com/v1/rdb/rest/todos?select=*&is_completed=eq.false" \
-H "Authorization: Bearer <access_token>"
```
```bash
curl -X POST "https://<envId>.api.tcloudbasegateway.com/v1/rdb/rest/todos" \
-H "Authorization: Bearer <access_token>" \
-H "Prefer: return=representation" \
-H "Content-Type: application/json" \
-d '{"title":"写一篇文档"}'
```
Do not send `_openid` for PG tables. Use owner columns with `DEFAULT auth.uid()` / JWT `sub`.
## PostgREST query syntax
- equality: `?status=eq.published`
- range: `?price=gte.100&price=lte.500`
- like: `?name=like.*CloudBase*`
- in: `?status=in.(draft,published)`
- order: `?order=created_at.desc`
- pagination: `?limit=20&offset=0`
- columns: `?select=id,title,created_at`
If the endpoint shape is uncertain, query OpenAPI docs with `searchKnowledgeBase(mode="openapi", apiName="mysqldb", query="PostgreSQL ...")` instead of guessing paths.
references/postgresql-development-cloudbase/references/index.md
# CloudBase PG Reference Index
Use this index after `postgresql-development/SKILL.md` identifies the task as CloudBase PostgreSQL / CloudBase PG / PG mode.
## Read order
1. `pg-mode-overview.md` — environment shape, schemas, roles, and when PG mode applies.
2. `auth-and-rls.md` — JWT identity, `auth.uid()` (**returns `text`**, not `uuid`) / `auth.role()` / `auth.jwt()`, GRANT + RLS templates, and `service_role` risks.
3. `app-workflow.md` — end-to-end Web/CMS implementation workflow.
4. `storage-pg.md` — PG storage bucket/object model, `app.storage.from('bucket')`, and storage RLS.
5. `http-api.md` — PostgREST `/v1/rdb/rest/...` fallback and auth headers.
6. `troubleshooting.md` — SDK version, DDL wrapping, role simulation, mini program base-library issues.
## Routing reminders
- PG business data uses `app.rdb().from(...)`, `queryPgDatabase`, and `managePgDatabase`.
- Do not route PG work to NoSQL `app.database()` / `db.collection(...)` or MySQL `queryMysqlDatabase` / `manageMysqlDatabase`.
- Browser code must never contain API Key / `service_role` credentials.
- For owner fields, prefer `varchar(64)` / `text` with database defaults such as `DEFAULT auth.uid()` and omit the owner field from frontend insert payloads. Remember `auth.uid()` is `text`; cast with `::uuid` only when comparing to an existing `uuid` column.
references/postgresql-development-cloudbase/references/pg-mode-overview.md
# CloudBase PG Mode Overview
CloudBase PG mode is a Postgres-Native environment mode. It is not simply "adding a PostgreSQL instance" to a legacy environment.
## Environment facts
- PG mode is selected when creating a new CloudBase environment with PostgreSQL.
- Legacy environments cannot be upgraded in place to PG mode. If `RuntimeBackends.postgresql !== true`, use the matching legacy NoSQL/MySQL skill or create/select a PG-mode environment.
- In PG mode, PostgreSQL is the unified runtime for:
- `public` schema: business tables.
- `auth` schema: user data and JWT identity.
- `storage` schema: bucket/object metadata.
- PG and NoSQL can coexist. Existing NoSQL collections keep using NoSQL APIs; new business data explicitly requested in PG should use PG APIs.
## Core access paths
- Web / frontend SDK: `app.rdb().from('<table>')`.
- MCP management: `queryPgDatabase` / `managePgDatabase`.
- REST fallback: PostgREST-compatible `/v1/rdb/rest/<table>`.
- Storage: `app.storage.from('<bucket>').upload('<key>', file)` against a PG storage bucket.
## Role and schema vocabulary
- `anon`: requests backed by Publishable Key / anonymous identity.
- `authenticated`: requests backed by a logged-in user's access token.
- `service_role`: API Key / privileged service role; bypasses RLS and must stay server-side only.
- `auth.uid()`: current JWT `sub` as **`text`** (not `uuid`; cast with `::uuid` only when comparing to a `uuid` column whose values are real UUIDs).
- `auth.role()`: current database role.
- `auth.jwt()`: full JWT claims.
- `auth.email()`: current user email when available.
## Common fit
Prefer PG mode for structured relational data, joins, transactions, row-level permission modeling, `pgvector` / SQL extensions, and apps that benefit from PostgREST and SQL-native authorization.
references/postgresql-development-cloudbase/references/rls-patterns.md
# CloudBase PG RLS Patterns
Use this reference when a CloudBase PostgreSQL app needs backend-side row permissions. Keep the policy model small and verify each operation from the app path.
## Principles
- UI hiding is not authorization.
- Browser/client access requires both table-level `GRANT` and row-level RLS Policy for business tables. Either layer can deny the request.
- RLS enabled with zero policies denies browser/client reads and writes.
- `UPDATE` usually needs both visibility of the existing row (`USING`) and a check on the new row (`WITH CHECK`).
- `serial` / `bigserial` primary keys require sequence grants such as `GRANT USAGE, SELECT ON SEQUENCE public.todos_id_seq TO authenticated;` before inserts work.
- A broad "logged-in users can access everything" rule is authentication, not authorization.
- Do not use privileged functions or definer-style bypasses to silence permission errors unless the task explicitly needs a trusted server/RPC boundary.
- The Web session is the source of truth for the app user. Use `auth.getSession()` in Web code and treat `session.user.id` as the candidate owner UID.
- In SQL policies, use CloudBase PG's official helpers: `auth.uid()` for JWT `sub`, `auth.role()` for `anon` / `authenticated` / `service_role`, `auth.jwt()` for full claims, and `auth.email()` when needed.
- **⚠️ `auth.uid()` returns `text`, not `uuid`.** Prefer `author_id` / `owner_id` as `varchar(64)` or `text`. If the column is already `uuid`, use `auth.uid()::uuid` (only when `sub` is a valid UUID); otherwise you get `operator does not exist: uuid = text`. This differs from Supabase.
- **⚠️ Do NOT use `current_user` or `current_setting(...)` in RLS policies.** `current_user` returns the database role name (e.g. `authenticated`), NOT the CloudBase auth user ID. Using `author_id = current_user` will never match any real user row.
- If unsure whether auth helpers are available, run `SELECT proname FROM pg_proc WHERE pronamespace = 'auth'::regnamespace` to list them.
- Do not use `auth.getUser()` as a route guard or owner UID source unless you have already confirmed it returns the same logged-in user as `getSession()`.
## Choose One Permission Boundary
For app CRUD, choose the smallest working boundary:
- **Database/RLS**: tables are accessed directly from browser `app.rdb()` and policies enforce row ownership.
- **Server/RPC**: browser calls a server function/RPC, and the server enforces admin/editor behavior before writing.
Do not half-implement both. If direct browser access is used, RLS/policies must be complete before testing.
## Admin/Editor Shape
Typical role table:
```sql
create table if not exists public.user_roles (
uid text primary key,
username text not null unique,
role text not null check (role in ('admin', 'editor')),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
```
Typical content table:
```sql
create table if not exists public.articles (
_id uuid primary key default gen_random_uuid(),
title text not null,
author_id varchar(64) not null default auth.uid(),
status text not null default 'draft',
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
```
For direct browser `app.rdb()` access, grant the intended role and then constrain rows with RLS. Minimal owner-based policy shape:
```sql
GRANT SELECT, INSERT, UPDATE, DELETE ON public.articles TO authenticated;
ALTER TABLE public.articles ENABLE ROW LEVEL SECURITY;
CREATE POLICY articles_select_own ON public.articles
FOR SELECT TO authenticated
USING (author_id = auth.uid());
CREATE POLICY articles_insert_own ON public.articles
FOR INSERT TO authenticated
WITH CHECK (author_id = auth.uid());
CREATE POLICY articles_update_own ON public.articles
FOR UPDATE TO authenticated
USING (author_id = auth.uid())
WITH CHECK (author_id = auth.uid());
```
`service_role` can bypass RLS, so never expose API Key / `service_role` credentials to browser code.
## Required Identity Probe
Before trusting policies that compare an owner column with `auth.uid()` or another DB-side helper:
1. In Web code, log in with the real username/password flow.
2. Call `auth.getSession()` and record `data.session.user.id`.
3. Insert a row through the same browser `app.rdb()` client without passing the owner column when the table has `DEFAULT auth.uid()`.
4. Read that row back with `queryPgDatabase` and verify the owner column equals `data.session.user.id`.
5. Attempt the same insert/update path as a second user if the app has admin/editor permissions.
If any browser `app.rdb()` operation fails, the RLS identity is not proven. Do not paper over it by catching the error and updating UI state. Either repair the RLS identity expression or move authorization to a server/RPC boundary.
Avoid self-referential role policies when possible. A policy on `public.user_roles` that queries `public.user_roles` again can recurse or behave differently across engines. Prefer one of these simpler patterns:
- Direct browser reads of roles with tightly scoped writes handled by setup/server code.
- A separate immutable role lookup object/function whose identity expression has already been verified.
- A server/RPC boundary for role-sensitive mutations.
## Verification Checklist
After creating policies, verify all required operations through the real app role:
- SELECT list/detail works for allowed rows.
- INSERT creates a row with the current user's owner column.
- UPDATE changes owned rows and does not reassign ownership unexpectedly.
- DELETE removes only allowed rows.
- Admin can operate across rows if the app requires admin behavior.
Then inspect schema/policies:
```text
queryPgDatabase(action="schema", objectName="public.articles")
queryPgDatabase(action="schema", objectName="public.user_roles")
```
If `rowLevelSecurityEnabled` is true and `policies` is empty, stop and create policies or disable RLS for that table before continuing.
references/postgresql-development-cloudbase/references/storage-pg.md
# CloudBase PG Storage
PG storage stores file metadata in PostgreSQL `storage` schema and enforces permissions through RLS.
> ⚠️ **PG 存储 ≠ 旧 COS 存储**:PG 环境的存储使用 pgstore + HTTP API,与旧 NoSQL 环境的 COS 存储是两套独立系统。以下情况请改用 `manageStorage`:非 PG 环境。
## Decision Tree: Which Storage to Use
```mermaid
flowchart TD
A[当前环境是 PG 模式?] -->|是| B[使用 pgstore]
A -->|否| C[使用旧 COS / NoSQL 存储]
B --> B1{需要做什么?}
B1 -->|创建 bucket| B2[INSERT INTO storage.buckets<br>或 HTTP API POST /v1/storages/bucket/]
B1 -->|上传文件| B3[前端 SDK: app.storage.from().upload()<br>或 HTTP API]
B1 -->|下载/签名| B4[HTTP API POST /v1/storages/object/sign/]
B1 -->|查询文件| B5[HTTP API / storage.objects 表查询]
B1 -->|配置权限| B6[ALTER TABLE storage.objects ENABLE RLS<br>CREATE POLICY ...]
C --> C1{需要做什么?}
C1 -->|上传/下载| C2[manageStorage / queryStorage 工具]
C1 -->|查询| C3[queryStorage 工具]
```
**核心规则:**
- PG 环境 → 用 `queryPgStorage`(规划方案) + HTTP API / 前端 SDK(执行)
- 旧 COS 环境 → 用 `manageStorage` / `queryStorage`(直接执行)
## Model
- `storage.buckets`: one row per bucket.
- `storage.objects`: one row per object.
- File bytes live in object storage, but metadata and permissions are coordinated through Storage API + PostgreSQL.
- `storage.buckets` / `storage.objects` are granted to `anon`, `authenticated`, and `service_role`; RLS is the effective permission gate.
- Traditional storage permission labels (`READONLY` / `PRIVATE` / `CUSTOM`) and JSON storage safe rules do not apply to PG storage.
## How Supabase Does Storage (Reference)
Supabase 和 CloudBase PG storage 的设计理念高度一致,可以对照理解:
| Concept | Supabase | CloudBase PG |
|---------|----------|-------------|
| **Schema** | `storage.buckets` / `storage.objects` | 相同(内置 pgstore schema) |
| **Anonymous access** | `anon` key (public, safe for client) | `anon` key(即 publishable key) |
| **Server access** | `service_role` key (secret, bypass RLS) | API Key(`manageAppAuth(action="createApiKey")`) |
| **User auth** | User JWT from `supabase.auth.signIn()` | User JWT from `auth.signInWithPassword()` 等 |
| **RLS** | `CREATE POLICY ... ON storage.objects` | 相同 |
| **Bucket creation** | `supabase.storage.createBucket()` | `INSERT INTO storage.buckets` 或 HTTP API |
| **Upload** | `supabase.storage.from('b').upload()` | `app.storage.from('b').upload()` |
| **Signed URL** | `createSignedUrl()` | HTTP API `POST /v1/storages/object/sign/` |
## JWT Token Acquisition for PG Storage API
PG Storage HTTP API 需要 Bearer JWT 认证。以下是三种角色对应的 token 获取方式:
### 1. service_role(服务端、旁路 RLS)
用于后端脚本、管理任务。对应 Supabase 的 `service_role` key。
```mermaid
flowchart LR
A[manageAppAuth<br>action=createApiKey] -->|返回| B[API Key (token)]
B --> C[Authorization: Bearer {token}]
C --> D[调用 PG Storage HTTP API<br>旁路 RLS, 全权限]
```
**获取方式:**
```
manageAppAuth(action="createApiKey", name="my-server-key", description="服务端存储用")
```
返回的 `token` 就是 `service_role` 级别的凭据。
### 2. authenticated(已登录用户、受 RLS 约束)
用户通过前端登录后获取 JWT,用于浏览器端上传。
**前端登录:**
```js
// Web SDK
import { CloudBase } from '@cloudbase/js-sdk';
const app = CloudBase.init({ env: 'your-env-id' });
await app.auth.signInWithPassword({ username, password });
// 登录后 app 自动携带 JWT
await app.storage.from('covers').upload('a.png', file);
```
**手动获取 JWT token:**
```js
const token = await app.auth.getAuthHeader();
// token 形如 { 'x-cloudbase-uid': 'xxx', 'Authorization': 'Bearer xxx' }
```
### 3. anon(匿名、受 RLS 约束)
不需要携带 Authorization 头,或使用 anon key。只能访问 `public` bucket 且 RLS 放行的资源。
**获取 anon key(即 publishable key):**
```
manageAppAuth(action="ensurePublishableKey")
```
返回的 `token` 即为 anon key,可安全暴露给客户端。
### Supabase 对照
```
Supabase CloudBase PG
────────────────────────────────────────────────────
Dashboard > Settings > API manageAppAuth(action="createApiKey")
→ anon key (public) → publishable key (anon)
→ service_role key (secret) → API Key (service_role)
supabase.auth.signIn() app.auth.signInWithPassword()
→ user JWT → user JWT
```
## Full HTTP API Reference
PG Storage 提供完整的 RESTful API,共 21 个端点,通过 OpenAPI 规范管理。
**Base URL:** `https://{envId}.api.tcloudbasegateway.com`
**Auth:** `Authorization: Bearer <token>` (service_role / user JWT / anon key)
### How to Query the API Spec
AI 助手可以通过以下方式获取完整 API 文档:
```
searchKnowledgeBase(mode="openapi", apiName="storage")
```
或直接查看文档:
- OpenAPI YAML: `https://docs.cloudbase.net/openapi/storage.v1.postgres.openapi.yaml`
- 文档首页: `https://docs.cloudbase.net/http-api/storage-pg/pg-storage-api`
### Endpoint Summary
| Category | Method | Endpoint | Description |
|----------|--------|----------|-------------|
| **Bucket** | POST | `/v1/storages/bucket/` | Create bucket |
| | GET | `/v1/storages/bucket/` | List buckets |
| | GET | `/v1/storages/bucket/{id}` | Get bucket info |
| | PUT | `/v1/storages/bucket/{id}` | Update bucket |
| | DELETE | `/v1/storages/bucket/{id}` | Delete bucket (must be empty) |
| **Object** | POST | `/v1/storages/object/{bucket}/{name}` | Upload |
| | PUT | `/v1/storages/object/{bucket}/{name}` | Upsert |
| | GET | `/v1/storages/object/{bucket}/{name}` | Download |
| | HEAD | `/v1/storages/object/{bucket}/{name}` | Object headers |
| | DELETE | `/v1/storages/object/{bucket}/{name}` | Delete single |
| | DELETE | `/v1/storages/object/{bucket}` | Delete batch (max 100) |
| | POST | `/v1/storages/object/list/{bucket}` | List objects |
| | POST | `/v1/storages/object/copy` | Copy object |
| | POST | `/v1/storages/object/move` | Move object |
| | GET | `/v1/storages/object/info/{bucket}/{name}` | Object metadata |
| **Signed URL** | POST | `/v1/storages/object/sign/{bucket}/{name}` | Signed download URL (single) |
| | POST | `/v1/storages/object/sign/{bucket}` | Signed download URL (batch, max 500) |
| | POST | `/v1/storages/object/upload/sign/{bucket}/{name}` | Signed upload URL |
| | GET | `/v1/storages/object/sign/{bucket}/{name}` | Download via signed URL |
| | HEAD | `/v1/storages/object/sign/{bucket}/{name}` | Head via signed URL |
| | PUT | `/v1/storages/object/upload/sign/{bucket}/{name}` | Upload via signed URL |
## Bucket and key semantics
Use bucket-native SDK semantics:
```ts
const { data, error } = await app.storage
.from('covers') // bucket id
.upload('a.png', file); // object key inside the bucket
```
Do not repeat the bucket in the key:
```ts
// Wrong in PG mode
await app.storage.from('covers').upload('covers/a.png', file);
```
Do not use legacy NoSQL storage APIs for PG storage:
```ts
// Wrong for PG storage
await app.uploadFile({ cloudPath: 'covers/a.png', filePath: file });
await app.getTempFileURL({ fileList: [...] });
await app.storage.from().upload('covers/a.png', file);
```
## Bucket creation
The browser SDK cannot create buckets. Create the PG storage bucket **before** writing upload code.
### `storage.buckets` schema
| Column | Type | Description |
|--------|------|-------------|
| `id` | `text` | Bucket ID (primary key), e.g. `avatars` |
| `name` | `text` | Bucket display name (≤ 100 chars) |
| `public` | `boolean` | Whether public read is allowed (default `false`). Does **not** bypass RLS. |
| `file_size_limit` | `bigint` | Max file size in bytes |
| `allowed_mime_types` | `text[]` | Allowed MIME types whitelist |
| `owner_id` | `text` | Creator user ID |
### Via MCP tool (recommended for AI agents)
```sql
INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
VALUES ('avatars', 'avatars', false, 5 * 1024 * 1024, ARRAY['image/png', 'image/jpeg', 'image/webp']);
```
Call via `managePgDatabase(action="execute", confirm=true, sql="...")`.
### Via HTTP API (service_role only)
```bash
curl -X POST "https://{envId}.api.tcloudbasegateway.com/v1/storages/bucket/" \
-H "Authorization: Bearer {service_role_token}" \
-H "Content-Type: application/json" \
-d '{"name": "avatars", "public": false, "file_size_limit": 5242880, "allowed_mime_types": ["image/png", "image/jpeg"]}'
```
### Via queryPgStorage (get executable example)
```
queryPgStorage(action="createBucket", bucket="avatars")
```
Returns a complete plan with SQL, HTTP API curl, and SDK code examples.
### Via CloudBase CLI
```bash
tcb db execute -e <envId> --sql "INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types) VALUES ('avatars', 'avatars', false, 5 * 1024 * 1024, ARRAY['image/png', 'image/jpeg', 'image/webp']);"
```
### Post-creation: configure RLS (mandatory)
After creating the bucket, configure RLS on `storage.objects` — see "Storage RLS" section below.
The default RLS is deny all; without permissive policies the browser receives `STORAGE_PERMISSION_DENIED`.
The legacy NoSQL bucket returned by `EnvInfo.Storages[]` is not a PG storage bucket.
## Storage RLS
After bucket creation, configure RLS on `storage.objects` for the intended role and bucket.
The default RLS is deny all. Use `storage.foldername(name)` to extract the first path segment (usually the user ID) and compare with `auth.uid()`.
Example: per-user isolation for bucket `avatars` (object path: `<uid>/filename.png`):
```sql
ALTER TABLE storage.objects ENABLE ROW LEVEL SECURITY;
-- Allow each authenticated user to read their own files
CREATE POLICY avatars_select_own ON storage.objects
FOR SELECT TO authenticated
USING (bucket_id = 'avatars' AND (storage.foldername(name))[1] = auth.uid());
-- Allow each authenticated user to upload to their own directory
CREATE POLICY avatars_insert_own ON storage.objects
FOR INSERT TO authenticated
WITH CHECK (bucket_id = 'avatars' AND (storage.foldername(name))[1] = auth.uid());
-- Allow each authenticated user to update their own files
CREATE POLICY avatars_update_own ON storage.objects
FOR UPDATE TO authenticated
USING (bucket_id = 'avatars' AND (storage.foldername(name))[1] = auth.uid())
WITH CHECK (bucket_id = 'avatars' AND (storage.foldername(name))[1] = auth.uid());
-- Allow each authenticated user to delete their own files
CREATE POLICY avatars_delete_own ON storage.objects
FOR DELETE TO authenticated
USING (bucket_id = 'avatars' AND (storage.foldername(name))[1] = auth.uid());
```
Key points:
- `storage.foldername(name)` is a built-in helper that splits `'<uid>/avatar.png'` into `{'<uid>'}`.
- `auth.uid()` returns the current JWT `sub` as **`text`** (not `uuid`). Path segments are text, so compare directly; do not cast to `uuid` here.
- `storage.objects` does **not** need extra `GRANT` — `anon`/`authenticated`/`service_role` already have `ALL`; RLS is the only gate.
- Do **not** `DELETE FROM storage.objects` directly — a `protect_delete` trigger blocks it. Use SDK / Storage API.
- For simpler authenticated-only access (not per-user), replace `(storage.foldername(name))[1] = auth.uid()` with `auth.role() = 'authenticated'`.
## Typical Pattern: Upload + Register Metadata
这是你在 CRM 等业务中需要完整遵循的「三步走」模式(对应 Supabase 的 storage + 业务表分离模式):
```
1. 创建 bucket(仅一次)
managePgDatabase(action="execute", confirm=true, sql="INSERT INTO storage.buckets ...")
2. 前端或后端上传文件
// 前端 SDK
await app.storage.from('crm').upload('contracts/2026/renewal.pdf', file);
3. 在业务表中记录文件元信息
INSERT INTO crm_attachments (customer_id, file_name, file_size, mime_type, storage_path, category)
VALUES (1, 'renewal.pdf', 102400, 'application/pdf', 'contracts/2026/renewal.pdf', 'contract');
```
注意:Supabase 的 `storage.objects` 表自带元信息记录,CloudBase PG storage 也有 `storage.objects` 表。如果你的需求只是「上传文件 + 按 bucket 列表查询」,可以直接查 `storage.objects` 表。只有当需要自定义业务元信息(如 `customer_id`, `category`)时,才需要额外建业务关联表。
## Failure signals
- `STORAGE_BUCKET_NOT_FOUND`: bucket does not exist in PG storage.
- `STORAGE_PERMISSION_DENIED`: bucket exists but storage RLS denies the request.
- `PUT https://undefined/`: usually a downstream symptom after the upstream storage metadata/signing response did not include an upload URL; inspect the failed Storage API response first.
references/postgresql-development-cloudbase/references/troubleshooting.md
# CloudBase PG Troubleshooting
## `xxx.rdb is not a function`
Cause: installed CloudBase SDK is too old or the environment SDK surface does not expose PG.
Fix:
- Upgrade to the latest `@cloudbase/js-sdk` for Web.
- Re-check that the target environment is PG mode.
## DDL fails through ExecutePGSql / managePgDatabase
Facts:
- Execute one SQL statement per call.
- Some DDL (`CREATE`, `ALTER`, `DROP`, `GRANT`, `REVOKE`, `TRUNCATE`, `COMMENT`) may fail directly with transient `InternalError`.
Retry once with a `DO $$` wrapper and escape single quotes:
```sql
DO $$ BEGIN EXECUTE 'CREATE TABLE public.products (id serial PRIMARY KEY, name text)'; END $$;
```
Do not use this as a way to hide real syntax errors. If the wrapped SQL also fails, inspect the exact error and simplify.
## `MIGRATION_TASK_TIMEOUT` / `MIGRATION_TASK_PENDING` after applyMigration
Cause: `PushPGUserMigrations` is async. MCP polls `DescribeTaskResult` by default for up to **10 minutes** (CLI parity). Large DDL or lock waits can outlive the poll window, or the caller set `waitForTask=false`.
Fix:
1. Call `managePgDatabase(action=describeMigrationTask, taskId=...)` **first** for `Status` / `Phase` / `Reason`. `listMigrations` alone cannot explain a Failed task (that was the #857 blind spot).
2. Call `managePgDatabase(action=listMigrations)` and look for your `migrationVersion`.
3. If the version is present → treat as applied; do not re-push.
4. If Status is still non-terminal and version missing, wait and poll `describeMigrationTask` again. Do **not** immediately re-push the same version.
5. Only after the task is terminal (`Succeed`/`Failed`) and list confirms the version never landed, fix SQL/Conflicts and retry with a **new** version, or use `taskPollTimeoutMs` / CLI `tcb db pg migration up` for longer waits.
6. Optional: `waitForTask=false` returns `MIGRATION_TASK_PENDING` immediately with TaskId — still must `describeMigrationTask` then `listMigrations` before any retry.
## Local file missing / checksum mismatch vs remote history
Cause: Agent applied without Git truth, cloned a repo without `cloudbase/migrations/`, or local SQL drifted from remote `Query` (Executable=false / `checksum_mismatch`).
Fix:
1. Prefer `managePgDatabase(action=fetchMigration)` to pull remote history into `cloudbase/migrations/` (same as CLI `tcb db pg migration fetch`).
2. If local files already exist but are wrong, re-run with `force=true` to overwrite from remote — do **not** hand-edit an already-applied version and re-push the same `migrationVersion`.
3. For new schema changes after fetch, always create a **new** `migrationVersion` newer than `LatestVersion`.
## `MIGRATION_NOT_EXECUTABLE` / `local_migration_before_latest_remote`
Cause: pending `migrationVersion` is older than remote `LatestVersion` (out-of-order), or checksum mismatch / other Preview Conflicts. Push is blocked.
Fix:
1. Default: pick a new 14-digit version strictly greater than `LatestVersion` from `listMigrations`.
2. Only when you intentionally need out-of-order apply (branch merge / backfill): retry `planMigration` / `applyMigration` with `includeAll=true` (CLI `tcb db pg migration up --include-all`).
3. For `checksum_mismatch`, fetch/realign local SQL — do not force includeAll.
## `ERROR: operator does not exist: uuid = text` (or `uid = text`) when creating RLS
Cause: CloudBase `auth.uid()` returns **`text`**, not `uuid` (unlike Supabase). Comparing `auth.uid()` to a `uuid` column has no matching operator.
Fix:
1. **Preferred**: define owner columns as `varchar(64)` or `text` so they match `auth.uid()` directly:
```sql
author_id varchar(64) not null default auth.uid()
-- ...
USING (author_id = auth.uid())
```
2. **If the column must stay `uuid`**: cast the helper (only when JWT `sub` is a valid UUID):
```sql
USING (author_id = auth.uid()::uuid)
WITH CHECK (author_id = auth.uid()::uuid)
```
3. Do **not** use `uuid` owner columns when identity may be WeChat `openid` or another non-UUID string — keep those columns as `text` / `varchar`.
## Permissions pass in MCP but fail in browser
Likely cause: admin/default execution bypassed user-facing role checks.
Fix:
- Verify table `GRANT` exists for `anon` or `authenticated`.
- Verify RLS policies exist and include both `USING` and `WITH CHECK` when needed.
- If the tool/API supports role simulation, execute checks as `authenticated` or `anon`.
- Test through the real `app.rdb()` browser flow with `auth.getSession()`.
## Insert with serial/bigserial fails
Cause: missing sequence grant.
```sql
GRANT USAGE, SELECT ON SEQUENCE public.todos_id_seq TO authenticated;
```
## Mini Program PG gateway error
If a mini program reports `Generating default gateway base url failed: env not found`, the base library may be too old for CloudBase PostgreSQL.
Fix: use WeChat base library `3.8.9` or later and confirm the environment is PG mode.
## Storage upload returns `STORAGE_PERMISSION_DENIED`
Cause: bucket exists but `storage.objects` RLS denies the request.
Fix: read `storage-pg.md`, configure bucket-specific policies, and retry the same SDK upload path.
## Existing app on CloudRun cannot connect to PostgreSQL (TCP)
Symptoms: deploy succeeds, but the container logs `ECONNREFUSED`, connection timeout, or "could not connect to server" when using `DATABASE_URL` / `postgres://` / `pg` drivers.
Cause: classic TCP clients need the CloudRun instances to join the database VPC. CloudBase PG SDK/gateway (`app.rdb()`, HTTP REST) is a different path and is **not** a drop-in for most GitHub apps.
Fix:
1. Confirm whether the app is a **TCP client** (ORM / `DATABASE_URL`) or a **CloudBase PG SDK** client (`app.rdb()`).
2. For TCP clients: redeploy with `serverConfig.VpcConf` pointing at the same region/VPC as the database, and use the **private** DB host. See `../cloudrun-development/references/vpc-and-database.md`.
3. Ensure the DB security group / allowlist accepts the CloudRun subnet CIDR on port 5432.
4. Do not assume enabling CloudBase PG mode alone makes an existing open-source app connect without VPC + a TCP-reachable endpoint.
references/postgresql-development-cloudbase/SKILL.md
---
name: postgresql-development-cloudbase
description: "Use when building, debugging, or evaluating CloudBase PostgreSQL / CloudBase PG / PG mode apps, including Postgres schema setup, queryPgDatabase/managePgDatabase, JS SDK v3 app.rdb() CRUD/RPC, PG HTTP API fallback, RLS-style permissions, username-password auth, and Web CMS/admin CRUD flows backed by CloudBase PG."
version: 2.33.1
alwaysApply: false
---
## Sibling skills (local only)
Sibling CloudBase skills ship beside this skill. Use local relative paths such as `../auth-tool-cloudbase/SKILL.md`.
If a referenced sibling skill file is missing from this environment, ask the user to install the full CloudBase plugin (or the missing skill). Do **not** HTTP-fetch remote skill or protocol markdown into the agent context.
# CloudBase PostgreSQL Development
## Activation Contract
### Use this first when
- The task says CloudBase PG, PostgreSQL, Postgres, PG mode, RLS, JS SDK v3 PostgreSQL, `app.rdb()`, `queryPgDatabase`, or `managePgDatabase`.
- A Web app or CMS must persist business data in CloudBase PostgreSQL instead of NoSQL or MySQL.
### Then also read
- Web auth provider readiness -> `../auth-tool-cloudbase/SKILL.md`
- Web login implementation -> `../auth-web-cloudbase/SKILL.md`
- General Web implementation and verification -> `../web-development/SKILL.md`
- Browser storage upload -> `../cloud-storage-web/SKILL.md`
- Raw HTTP API details only when SDK coverage is blocked -> `../http-api-cloudbase/SKILL.md`
- PG reference index -> `references/index.md`
- PG mode overview -> `references/pg-mode-overview.md`
- Auth / GRANT / RLS details -> `references/auth-and-rls.md`
- End-to-end PG app closure -> `references/app-workflow.md`
- PG storage details -> `references/storage-pg.md`
- HTTP API fallback -> `references/http-api.md`
- Troubleshooting -> `references/troubleshooting.md`
### Do NOT use first
- `relational-database-mcp-cloudbase` / `queryMysqlDatabase` / `manageMysqlDatabase`: those are MySQL-oriented.
- `cloudbase-document-database-web-sdk` / collection APIs for business data that must live in CloudBase PG.
## Required Flow
### 🚨 CRITICAL: PG mode API is NOT the same as NoSQL
CloudBase PG (`app.rdb()`, `app.storage.from('bucket')`) uses **different API method names** than CloudBase NoSQL (`app.database()`, `app.uploadFile()`). Low-capability models often paste legacy NoSQL/auth snippets from training; reject that path immediately. If this task is PG-backed, **do not** write `app.database()`, `db.collection(...)`, `app.uploadFile()`, `getLoginState()`, or route guards based on `auth.getUser()`. Use `app.rdb()`, PG storage v3, and `auth.getSession()` instead. If you are used to writing `.where()`, `.orderBy()`, `.count()` from other ORMs or NoSQL — **stop and read the table below**.
| ❌ Do NOT use these (NoSQL / ORM habits) | ✅ Use these in PG mode |
|------------------------------------------|------------------------|
| `.where({ field: value })` | `.match({ field: value })` or `.eq("field", value)` |
| `.where("field", "ilike", "%v%")` | `.ilike("field", "%v%")` |
| `.orderBy("field", { ascending: false })` | `.order("field", { ascending: false })` |
| `.count()` | `.select("*", { count: "exact" })` — count is in response |
| `.offset(n)` | `.range(from, to)` |
| `app.uploadFile()` (legacy NoSQL upload) | `app.storage.from('bucket').upload(key, file)` |
| `app.getTempFileURL()` (legacy NoSQL URL) | `app.storage.from('bucket').createSignedUrl(key, expiresIn)` |
| `app.storage.from()` (no bucket name) | `app.storage.from('bucket')` — **must** pass bucket name |
**If you find yourself typing `.where()` or `.orderBy()` or `.count()` — stop and use the correct method from the right column.**
0. **First, confirm this environment actually has PostgreSQL provisioned.** Call `envQuery(action="info", envId=...)` and read the derived `EnvInfo.RuntimeBackends` block (`{ postgresql, nosql, mysql }`) along with `EnvInfo.RuntimeMode`. It is only safe to apply this skill's PG-specific guidance when `RuntimeBackends.postgresql === true` (equivalently, `EnvInfo.PostgreSQL` is non-empty AND/OR `EnvInfo.Meta` contains `postgresql=enable`).
- PG mode is a **new-environment mode** selected when creating a CloudBase environment with PostgreSQL. Do not try to "upgrade" a legacy environment in place; create/select a PG-mode environment instead.
- If `RuntimeBackends.postgresql === false`, STOP — this is a legacy NoSQL-only env: switch to `cloudbase-document-database-web-sdk` for browser data and `cloud-storage-web` (with `app.uploadFile()`) for uploads. Do not write `app.rdb()` code, do not enable RLS, do not create a pgstore bucket here.
- If both `postgresql` and `nosql` are `true` (the common case in a PG environment), they coexist. Apply this skill to NEW business data the task asks you to put in PG (e.g. articles / role tables explicitly described as PG). Existing NoSQL collections, the bucket reported in `EnvInfo.Storages[]`, and any `managePermissions(resourceType="noSqlDatabase")` rules continue to govern the legacy NoSQL data — do NOT migrate or rewrite them unless the task explicitly asks.
- `RuntimeBackends.mysql === false` is the only hard "do not use" signal: when MySQL is absent, do not use `manageMysqlDatabase` / `queryMysqlDatabase` and do not consult the `relational-database-mcp-cloudbase` skill; those are MySQL-specific and have nothing to do with CloudBase PG.
- Note: in a PG env, `EnvInfo.Storages[]` is the legacy NoSQL bucket. It still works for legacy `app.uploadFile()` flows but is NOT a usable pgstore bucket — never reuse it as the `<bucket>` segment in `app.storage.from('<bucket>').upload('<key>', file)`.
> **Creating a PG-mode environment**
>
> If step 0 shows `RuntimeBackends.postgresql === false` and you need PostgreSQL, create a new environment with PG enabled:
>
> - **Via MCP**: `manageEnv(action="create", alias="my-env", packageId="baas_personal", resources=["flexdb","storage","function","postgresql"], confirm="yes")` — do not pass `region`; CreateEnv does not accept it.
> - **Via CLI**: `tcb env create --alias my-env --package baas_personal --postgresql --yes`
> - **Via Console**: [Create environment](https://console.cloud.tencent.com/tcb/env/create)
1. Inspect the existing app surfaces first: `src/lib/backend.*`, `src/lib/auth.*`, `src/lib/*service.*`, route guards, and the handlers bound to existing forms.
2. Check PG state through MCP: use `queryPgDatabase` for schema/read-only inspection and `managePgDatabase` for DDL/DML. Do not switch to MySQL tools. For the complete route map, read `references/index.md`.
3. **Understand PG roles before writing code:** Publishable Key maps to `anon`; a logged-in user's access token maps to `authenticated`; API Key maps to `service_role` and bypasses RLS. Never expose API Key / `service_role` credentials in frontend code. See `references/auth-and-rls.md`.
4. **Use schema management (`managePgDatabase`) before writing CRUD code.** Schema DDL (CREATE / ALTER / DROP / TRUNCATE) **must** go through the versioned migration workflow — never default to `execute` for table creation. Then apply GRANT + RLS (via `execute` or the same migration SQL bundle) before browser access. The minimum SQL bundle is: `CREATE TABLE`, `GRANT SELECT/INSERT/UPDATE/DELETE TO authenticated`, `GRANT USAGE, SELECT ON SEQUENCE ... TO authenticated` when using `serial`/`bigserial`, `ALTER TABLE ... ENABLE ROW LEVEL SECURITY`, and `CREATE POLICY ... USING / WITH CHECK`. See `references/auth-and-rls.md` for the full template.
**Default schema-change workflow (local file first, then remote history):**
1. Choose `migrationVersion` = 14-digit UTC timestamp `YYYYMMDDHHMMSS` and `migrationName` = snake_case (e.g. `add_users`).
2. Write local file `cloudbase/migrations/<migrationVersion>_<migrationName>.sql` with the DDL (and optional rollback SQL in comments or a paired file). This path **must** match CloudBase CLI `MIGRATIONS_DIR` (`tcb db pg migration *`). If an older workspace still has root `migrations/`, move those files into `cloudbase/migrations/` before mixed MCP+CLI use.
3. Optional preview: `managePgDatabase(action=planMigration, migrationName=..., migrationVersion=..., sql=...)`.
4. Apply: `managePgDatabase(action=applyMigration, migrationName=..., migrationVersion=..., sql=..., confirm=true)` — reuse the **same** version/name as the local file. If the local file is missing, MCP auto-writes `cloudbase/migrations/<version>_<name>.sql`; if an existing file's content differs from `sql`, apply fails closed (`LOCAL_MIGRATION_FILE_MISMATCH`) and does not Push. MCP waits for the async task by default (up to **10 minutes**, same as CLI); override with `taskPollTimeoutMs` or set `waitForTask=false` if the host tool-call timeout is short.
5. Verify: `managePgDatabase(action=listMigrations)` and confirm the remote history records the same `migrationVersion`.
6. Then write frontend CRUD / RLS checks.
**Out-of-order / backfill versions:** Prefer a `migrationVersion` strictly newer than `LatestVersion`. If you must apply a version older than Latest (branch merge / cherry-pick), pass `includeAll=true` on `planMigration` / `applyMigration` — same as CLI `tcb db pg migration up --include-all`. Do not use this for routine work.
**If applyMigration returns `MIGRATION_TASK_TIMEOUT` or `MIGRATION_TASK_PENDING`:** the task may still be running (large DDL / lock waits). Call `describeMigrationTask(taskId=...)` **first** for Status/Phase/Reason, then `listMigrations`. Do **not** re-push the same `migrationVersion`, and do **not** fall back to `execute` until the task is terminal and list confirms the version never landed.
Other migration actions:
- `managePgDatabase(action=migrationDetail, migrationVersion=...)` — inspect a single migration
- `managePgDatabase(action=fetchMigration)` — pull remote history SQL into `cloudbase/migrations/` (CLI `tcb db pg migration fetch` parity). Optional `migrationVersion` for one file; omit for full history. Existing local files are skipped unless `force=true` (overwrite / checksum realign). Prefer this over hand-copying SQL from `migrationDetail` to avoid checksum drift.
- `managePgDatabase(action=rollbackMigration, lastN=..., confirm=true)` — roll back the last N applied migrations
- `managePgDatabase(action=repairMigration, migrationVersion=..., migrationName=..., repairStatus=..., repairReason=...)` — repair history records
**`execute` is for DML and ops SQL, not default DDL:** use `managePgDatabase(action=execute, confirm=true)` for `INSERT` / `UPDATE` / `DELETE`, and for `GRANT` / `CREATE POLICY` / storage RLS when those are not part of a migration. If you attempt schema DDL via `execute`, the tool soft-blocks with `DDL_USE_APPLY_MIGRATION` unless you explicitly set `allowDdlViaExecute=true` (escape hatch only).
**🚨 CRITICAL: Inspect table existence and column names before CREATE TABLE.** `CREATE TABLE IF NOT EXISTS` silently skips when the table already exists, even if the column names are wrong. Always call `queryPgDatabase(action="sql", sql="SELECT column_name, data_type FROM information_schema.columns WHERE table_name='xxx'")` first to check whether the table exists and what exact column names it uses. If the table already exists with mismatched column names (e.g. `user_id` instead of `uid`), you must either:
- `ALTER TABLE` to add/rename/drop columns (via `applyMigration` with a new version), or
- `DROP TABLE IF EXISTS ... CASCADE` and recreate via `applyMigration` (only when data loss is acceptable, e.g. disposable/evaluation environments).
- Do NOT rely on `CREATE TABLE IF NOT EXISTS` silent skip — it will cause all downstream CRUD queries to fail with wrong field names.
- After DDL, re-query the schema and compare every column name used by frontend code, insert/update payloads, filters, ordering, and RLS policies.
5. Check username-password auth before coding login:
- Call `queryAppAuth(action="getLoginConfig")`.
- If `loginMethods.usernamePassword !== true`, call `manageAppAuth(action="patchLoginStrategy", patch={ usernamePassword: true })`.
- In Web login code, use `auth.signInWithPassword({ username, password })` for plain usernames like `admin` or `editor`.
- Do not assume `auth.signUp({ username, password })` can directly create username/password users. Confirm `queryAppAuth` `sdkHints` and the installed `@cloudbase/js-sdk` behavior first; if direct username signup is unsupported, implement registration through a backend/management boundary instead of exposing secret keys in the browser.
6. Implement Web auth state with `auth.getSession()` before writing CRUD:
- Route guards must check `data.session`, not `auth.getUser()` and not deprecated `getLoginState()`.
- Treat login as successful only when `signInWithPassword(...)` returns no `error` and includes `data.session`.
- Get the UID for `author_id` / role rows from `data.session.user.id` (fall back to `sub`/`uid` only after inspecting the actual session object).
- Do not use `auth.getUser()` as proof of login; it can return a non-null wrapper or anonymous-looking user data when there is no real username/password session.
7. Implement browser-side business data with the CloudBase JS SDK v3 PostgreSQL API first: `app.rdb().from(table)`. Use the latest `@cloudbase/js-sdk` when `app.rdb` is missing (`xxx.rdb is not a function` means the SDK is too old).
8. Do not manually fetch a CloudBase Auth bearer token from browser code for PG CRUD. In particular, do not call non-canonical helpers such as `currentUser.getIdToken()` unless you have verified that exact method exists in the installed SDK. Prefer `app.rdb()` so the SDK carries the active session.
9. Use the official CloudBase PG SQL auth helpers in policies: `auth.uid()` for JWT `sub`, `auth.role()` for `anon` / `authenticated` / `service_role`, `auth.jwt()` for full claims, and `auth.email()` when email is needed. Still verify the policy through the real app session before claiming it works:
- Log in through the real app path.
- Insert a test row using `author_id = session.user.id`.
- Read it back with `queryPgDatabase`.
- If INSERT/SELECT fails, inspect the exact RLS error and fix the policy or switch to a server/RPC boundary. Do not leave browser-facing tables with broken RLS.
- **⚠️ `auth.uid()` returns `text`, not `uuid`.** Prefer owner columns as `varchar(64)` / `text`. If comparing to a `uuid` column, use `auth.uid()::uuid` (only when JWT `sub` is a valid UUID) or you will get `operator does not exist: uuid = text`. This differs from Supabase. See `references/auth-and-rls.md`.
- **⚠️ Do NOT use `current_user` or `current_setting(...)` in RLS policies.** `current_user` in PostgreSQL returns the database role name (e.g. `authenticated`), NOT the CloudBase auth user ID. Always use `auth.uid()` for user identity checks. If you are unsure whether the auth helpers are available, run `SELECT proname FROM pg_proc WHERE pronamespace = 'auth'::regnamespace` to list all available `auth.*` functions.
10. Use PG HTTP API only as a fallback after reading OpenAPI docs and verifying the auth model in the installed SDK. Do not guess URLs such as `/api/v1/rdb/rest`; the documented base is `https://<envId>.api.tcloudbasegateway.com/v1/rdb/rest/<table>` and auth is `Authorization: Bearer <Publishable Key | access_token | API Key>`.
11. Keep cover images in CloudBase Storage. Store only the final file URL or file metadata in PG.
12. Verify both layers before claiming done: project build/typecheck and browser E2E for login/CRUD, then read back rows with `queryPgDatabase`. When debugging RLS, run SQL as `authenticated` / `anon` if the tool supports role simulation; admin/default execution can bypass the user-facing failure.
## Exploration Budget
- Optimize for a working user flow before broad research.
- If the task is a Web app with PG-backed CRUD, read `references/app-workflow.md` and follow that closure path before looking up optional HTTP API details.
- Do not query the same documentation family more than twice for the same question. If the second lookup does not unblock you, inspect the installed SDK surface or the exact runtime error instead.
- Once you choose `app.rdb()` for browser CRUD, stop researching raw PG HTTP APIs unless `app.rdb()` is missing or demonstrably fails.
- After a DDL failure, retry SQL at most twice. Then call `queryPgDatabase(action="objects")` to find the schema-qualified table name, then `queryPgDatabase(action="schema", objectName="public.your_table")`, read the exact error, and simplify the schema or permission plan.
- Avoid long task-management loops for targeted repairs. Read the active files, execute the minimum platform setup, edit code, and verify.
- **File read budget**: Do NOT read the same file more than **2 times**. If you need to re-read a file after 2 reads, use `Grep` for targeted search or `Read` with explicit `offset`/`limit` to target specific line ranges. Move on to editing or verifying instead of re-reading.
## Data Model Rules
- Use CloudBase Auth / CloudBase PG built-in auth identity as the user source. Do not copy an extra identity table unless the app needs one.
- Keep business roles in PG when the app needs admin/editor behavior, e.g. `user_roles` with `uid`, `username`, and `role`. The `uid` value must be the same value the Web session uses as `session.user.id`, and must match any database policy expression you use.
- Keep content tables in PG, e.g. `articles` or `posts` with owner UID columns.
- Prefer snake_case physical columns (`author_id`, `author_name`, `cover_image`, `created_at`, `updated_at`) for PG tables. If UI fields are camelCase, map them explicitly at the service boundary.
- Treat the schema returned by `queryPgDatabase(action="schema", objectName="public.your_table")` as the source of truth. `objectName` is required and must be schema-qualified; if you do not know it yet, call `queryPgDatabase(action="objects")` first. If an existing table has `authorid`/`updatedat`, either use those exact column names in code or explicitly migrate/drop-recreate the table before writing code that expects `author_id`/`updated_at`.
- `CREATE TABLE IF NOT EXISTS` does not change an existing incompatible schema. In evaluation or disposable environments, prefer a deliberate `DROP TABLE IF EXISTS ... CASCADE` followed by `CREATE TABLE ...` when you need a known schema.
- After DDL, query the table schema again and compare every column used by frontend code, insert/update payloads, filters, ordering, and RLS policies.
- Backend permission must exist in the database or server/RPC layer. Hiding buttons in the UI is not enough.
- Do not leave a browser-facing table with RLS enabled and zero policies. PostgreSQL denies user reads/writes by default in that state, so `app.rdb().from("articles").insert(...)` can fail while the UI only shows a generic save failure. If you enable RLS, create and verify SELECT/INSERT/UPDATE/DELETE policies before testing the app.
- Use CloudBase PG's official SQL auth helpers in policies: `auth.uid()` (JWT `sub`, returns **`text`** not `uuid`), `auth.role()` (`anon` / `authenticated` / `service_role`), `auth.jwt()` (full claims), and `auth.email()` when relevant. Prefer owner columns such as `owner_id varchar(64) DEFAULT auth.uid()` so the database, not the browser, assigns ownership. If an owner column is already `uuid`, compare with `auth.uid()::uuid` (only when `sub` is a valid UUID).
- If you need detailed GRANT/RLS rules, read `references/rls-patterns.md` before writing policies.
- For admin/editor flows, make `admin` able to operate all rows and `editor` only rows where owner UID matches the current user.
## JS SDK v3 PostgreSQL Patterns
**Table name rules (important):**
- ✅ `db.from("articles")` — recommended
- ✅ `db.from("public.articles")` — also valid (single schema prefix)
- ❌ `db.from("public.public.articles")` — WRONG, double schema prefix, will fail with `PGRST205`
- `objectName="public.articles"` in `queryPgDatabase()` is the MCP tool format — do NOT copy this into `db.from()`.
Use static imports and one shared `app.rdb()` client:
```ts
import cloudbase from "@cloudbase/js-sdk";
const app = cloudbase.init({ env: import.meta.env.VITE_CLOUDBASE_ENV_ID });
export const auth = app.auth;
export const db = app.rdb();
```
Minimal auth helpers — **only use `auth.getSession()`**, never `auth.getUser()`:
```ts
async function getActiveSession() {
const { data, error } = await auth.getSession();
if (error || !data?.session || data.session.user?.is_anonymous) return null;
return data.session;
}
```
Canonical CRUD shapes (copy these exactly):
```ts
// READ
const { data, error } = await db.from("articles").select("*");
// CREATE — omit owner_id/author_id when the table defines DEFAULT auth.uid()
const { data, error } = await db.from("articles").insert({ title, status: "draft" });
// UPDATE
await db.from("articles").update({ status }).eq("id", id);
// DELETE
await db.from("articles").delete().eq("id", id);
// RPC
const { data } = await db.rpc("function_name", { id });
```
Common query helpers: `.eq()`, `.neq()`, `.gt()`, `.gte()`, `.lt()`, `.lte()`, `.like()`, `.ilike()`, `.in()`, `.is()`, `.order()`, `.limit()`, `.range()`, `.single()`.
### ⚠️ Critical: PG API is NOT the same as CloudBase NoSQL or other ORMs
CloudBase PG (`app.rdb()`) uses **postgREST-style** query helpers, **NOT** CloudBase NoSQL (`app.database()`) API and **NOT** common ORM conventions. Do NOT use:
| ❌ Wrong (NoSQL / ORM habit) | ✅ Correct (postgREST / PG) |
|-----------------------------|---------------------------|
| `.where({ field: value })` | `.match({ field: value })` 或 `.eq("field", value)` |
| `.where("field", "ilike", "%v%")` | `.ilike("field", "%v%")` |
| `.orderBy("field", { ascending: false })` | `.order("field", { ascending: false })` |
| `.count()` | `.select("*", { count: "exact" })` — 通过 `select` 的 `count` 参数获取总数,返回结果中有 `count` 字段 |
| `.offset(n)` | `.range(from, to)` — 注意 range 是包含两端的分页 |
**Golden rule**: `app.rdb()` 的查询链只使用上方 "Common query helpers" 列出的 helper 方法。如果你习惯写 `.where()` / `.orderBy()` / `.count()`,请立即改用对应的 postgREST 方法。Supabase 的 `@supabase/postgrest-js` 同样不使用这些方法名。
Storage (v3): use `app.storage.from('<bucket>').upload('<key>', file)` — check installed SDK surface before copying:
```ts
const { data } = await app.storage.from('covers').upload(`${file.name}`, file);
```
### Bucket existence is mandatory (Supabase parity)
CloudBase PG storage uses the `pgstore` backend and follows the same model as Supabase Storage: **every upload must target a bucket that already exists**. The browser SDK cannot create one. Before writing any upload code:
1. Confirm a usable pgstore bucket exists for your target prefix (e.g. `covers`). The legacy NoSQL bucket exposed by `DescribeEnvs.Storages[]` (e.g. `6d63-…-1409864723`) is for the old NoSQL backend and does NOT serve pgstore uploads.
2. If no usable bucket exists, create one through the PG storage management surface (PG storage HTTP API / CLI / console / SQL on `storage.buckets` when appropriate). Do not assume traditional-mode storage tools or adding `covers/` as a JS path prefix will create a PG bucket.
3. The bucket name belongs in `from('<bucket>')`; the key passed to `upload(key, file)` is inside that bucket and must **not** repeat the bucket prefix. Correct: `app.storage.from('covers').upload('a.png', file)`. Wrong: `app.storage.from('covers').upload('covers/a.png', file)`.
4. **After creating the bucket, configure RLS on `storage.objects`** via `managePgDatabase(action="execute", confirm=true)`. The default RLS is deny all; without permissive policies the browser receives `STORAGE_PERMISSION_DENIED`. See `cloud-storage-web/SKILL.md` "Post-bucket: storage RLS" section for the exact SQL policies.
Failure-mode cheat sheet (read DevTools network tab on the FAILED `POST .../v1/storages/get-objects-upload-info`):
| `code` returned by `/v1/storages/get-objects-upload-info` | Meaning | Fix |
| -------------------------------------------------------- | ------- | --- |
| `STORAGE_BUCKET_NOT_FOUND` | The bucket in the path does not exist in this PG environment. | Create the bucket via management surface, then retry. |
| `STORAGE_PERMISSION_DENIED` | The bucket exists but RLS on `storage.objects` blocks the upload. | Run `managePgDatabase(action="execute", confirm=true)` to configure storage RLS. See `cloud-storage-web/SKILL.md` "Post-bucket: storage RLS". |
| `INVALID_PARAM` for bucket/key | The SDK/API did not receive a valid bucket/key pair (for example `from()` missing the bucket, or key is empty). | Use `app.storage.from('covers').upload('a.png', file)`; bucket goes in `from()`, key goes in `upload()`. |
| `STORAGE_CONTENT_LENGTH_REQUIRED` | Your code stripped or omitted the `Content-Length` signed header. | Pass `headers: { 'Content-Length': String(file.size) }` to `uploadFile`, or use `app.storage.from('<bucket>').upload('<key>', file)` with a `Blob`/`File` so the SDK fills it in. |
If you see `PUT https://undefined/` and `net::ERR_NAME_NOT_RESOLVED` in DevTools, that is the symptom of one of the three rows above — the upstream metadata response had no `uploadUrl` field, and the SDK fed `undefined` into a follow-up `PUT`. Always inspect the upstream `get-objects-upload-info` response first; do not chase the `https://undefined/` URL itself.
Hard rule: never let an upload error be silently swallowed. If `uploadCoverImage()` rejects, the surrounding `createArticle()` flow MUST reject too — do not insert into PG with a fabricated cover URL, do not show a success toast, and do not retry with a guessed bucket name.
## ExecutePGSql / DDL Troubleshooting
- `ExecutePGSql` / `managePgDatabase(action="execute")` is an admin/control-plane path. Do not expose Tencent Cloud SecretKey or CloudBase API Key in frontend code.
- Execute one SQL statement per call. Split batches explicitly instead of sending semicolon-joined multi-statements.
- Some DDL (`CREATE` / `ALTER` / `DROP` / `GRANT` / `REVOKE` / `TRUNCATE` / `COMMENT`) can fail directly with transient `InternalError`. If that happens, retry once by wrapping the DDL in `DO $$ BEGIN EXECUTE '...'; END $$` and escaping single quotes inside the string.
- When validating permissions, use the user-facing role (`anon` or `authenticated`) when the tool/API supports a role parameter. Default admin execution can hide missing GRANT/RLS policies.
## HTTP API Fallback
- PG HTTP API is in the CloudBase relational database HTTP API family, together with MySQL. In MCP docs/search this appears under `mysqldb`.
- Before writing raw `fetch()` code, query OpenAPI docs: `searchKnowledgeBase(mode="openapi", apiName="mysqldb", query="PostgreSQL fetch insert update rpc")`.
- Do not construct `/api/v1/rdb/rest` or `/api/v1/rdb/rest/rpc` from memory. A guessed path that returns 404 is a hard blocker; switch back to JS SDK v3 or read the OpenAPI contract.
- If environment variables expose `TCB_HTTP_API_BASE_URL` / `VITE_TCB_HTTP_API_BASE_URL`, treat them as the base only. The path, method, headers, and auth model must still come from OpenAPI docs or an existing working helper.
## Frontend Guardrails
Avoid dynamic helper traps:
- Do not write `function getAuth() { return (await import("./backend")).auth; }`; either use a top-level static import or make the function `async`.
- Do not write `typeof import !== "undefined"` in Vite; use `import.meta.env` directly.
- Do not keep editing after Vite reports a transform error. Fix syntax first, rerun build, then test the browser flow.
- Do not spend time reverse-engineering unrelated SDK internals when a documented v3 surface exists. Use the documented `app.rdb()` / `app.storage.from()` APIs first.
## Quick Checks
- PG schema exists and matches the service code.
- Username login is enabled and code uses username APIs, not email APIs.
- Data writes reach CloudBase PG via JS SDK v3 `app.rdb()` or a documented HTTP API path, not local state, mock arrays, or guessed 404 endpoints.
- Browser PG code must not depend on `user.getIdToken()` or invented token helpers. If raw HTTP is unavoidable, first inspect the installed CloudBase Web SDK/auth API and prove the request succeeds with the current user session.
- Editor permission is enforced outside the UI.
- A pgstore bucket that matches the upload path (e.g. `covers`) exists BEFORE any browser upload runs. If it does not, create it via a management surface; the v3 SDK will not create one for you.
- Storage upload returns a usable URL and that URL is persisted with the article. Upload errors must propagate — do not insert an article row with a placeholder cover URL.
## Reference index
All packaged reference files (required for skill lint reachability):
- [index.md](references/index.md)
- [pg-mode-overview.md](references/pg-mode-overview.md)
- [auth-and-rls.md](references/auth-and-rls.md)
- [app-workflow.md](references/app-workflow.md)
- [storage-pg.md](references/storage-pg.md)
- [http-api.md](references/http-api.md)
- [rls-patterns.md](references/rls-patterns.md)
- [troubleshooting.md](references/troubleshooting.md)
references/relational-database-mcp-cloudbase/SKILL.md
---
name: relational-database-mcp-cloudbase
description: "[Deprecated] This is the required documentation for agents operating on the CloudBase Relational Database through MCP. It defines the canonical SQL management flow with `queryMysqlDatabase`, `manageMysqlDatabase`, `queryPermissions`, and `managePermissions`, including MySQL provisioning, destroy flow, async status checks, safe query execution, schema initialization, and permission updates. New environments should use PostgreSQL — see postgresql-development skill instead."
version: 2.33.1
alwaysApply: false
metadata:
priority: "5"
deprecated: "true"
---
## Sibling skills (local only)
Sibling CloudBase skills ship beside this skill. Use local relative paths such as `../auth-tool-cloudbase/SKILL.md`.
If a referenced sibling skill file is missing from this environment, ask the user to install the full CloudBase plugin (or the missing skill). Do **not** HTTP-fetch remote skill or protocol markdown into the agent context.
## Activation Contract
### Use this first when
- The agent must inspect SQL data, execute SQL statements, provision or destroy MySQL, initialize table structure, or manage table security rules through MCP tools.
### Read before writing code if
- The task includes `queryMysqlDatabase`, `manageMysqlDatabase`, `queryPermissions`, or `managePermissions`.
### Then also read
- Web application integration -> `../relational-database-web-cloudbase/SKILL.md`
- Raw HTTP database access -> `../http-api-cloudbase/SKILL.md`
### Do NOT use for
- Frontend or backend application code that should use SDKs instead of MCP operations.
### Common mistakes / gotchas
- Initializing SDKs in an MCP management flow.
- Running write SQL or DDL before checking whether MySQL is provisioned and ready.
- Treating document database tasks as MySQL management tasks.
- Skipping `_openid` and permissions review after creating new SQL tables.
- Destroying MySQL without explicit confirmation or without checking whether the environment still needs the instance.
- Using `getConnectionInfo` (or inferred host/password) to build a default TCP client for new apps. Prefer SDK / `runQuery` / `runStatement`; TCP credentials are an explicit migration exception only.
## When to use this skill
Use this skill when an **agent** needs to operate on **CloudBase Relational Database via MCP tools**, for example:
- Inspecting or querying SQL data
- Provisioning MySQL for an environment
- Destroying MySQL for an environment
- Polling MySQL provisioning status
- Modifying data or schema (INSERT/UPDATE/DELETE/DDL)
- Initializing tables and indexes after MySQL is ready
- Reading or changing table permissions
Do **NOT** use this skill for:
- Building Web or Node.js applications that talk to CloudBase Relational Database directly through SDKs
- Auth flows or user identity management
## How to use this skill (for a coding agent)
1. **Recognize MCP context**
- If you can call tools like `queryMysqlDatabase`, `manageMysqlDatabase`, `queryPermissions`, `managePermissions`, you are in MCP context.
- In this context, **never initialize SDKs for CloudBase Relational Database**; use MCP tools instead.
2. **Pick the right tool for the job**
- Read-only SQL and provisioning status checks -> `queryMysqlDatabase`
- MySQL provisioning, MySQL destruction, write SQL, DDL, schema initialization -> `manageMysqlDatabase`
- Inspect permissions -> `queryPermissions(action="getResourcePermission")`
- Change permissions -> `managePermissions(action="updateResourcePermission")`
3. **Always be explicit about safety**
- Before destructive operations (DELETE, DROP, etc.), summarize what you are about to run and why.
- Prefer `queryMysqlDatabase(action="getInstanceInfo")` or a read-only SQL check before writes.
- Provisioning or destroying MySQL requires explicit confirmation because both actions have environment-level impact.
---
## Available MCP tools (CloudBase Relational Database)
These tools are the supported way to interact with CloudBase Relational Database via MCP:
### 1. `queryMysqlDatabase`
- **Purpose:** Query SQL data and provisioning state.
- **Use for:**
- Running `SELECT` and other read-only SQL queries with `action="runQuery"`
- Checking whether MySQL already exists with `action="getInstanceInfo"` (lifecycle only — no connection credentials)
- Inspecting asynchronous provisioning progress with `action="describeCreateResult"` or `action="describeTaskStatus"`
- **Exception only:** `action="getConnectionInfo"` returns the raw connection/cluster payload (may include credentials) for migrating existing TCP/ORM clients. Do **not** use this for new business CRUD — prefer Web/Node SDK or `runQuery` / `runStatement`.
**Example flow:**
```json
{
"action": "runQuery",
"sql": "SELECT id, email FROM users ORDER BY created_at DESC LIMIT 50"
}
```
**Do NOT** call `getConnectionInfo` and then wire `pymysql` / `mysql2` / `DATABASE_URL` into a cloud function for greenfield apps. Platform-delegated SQL and SDK access are the default.
### 2. `manageMysqlDatabase`
- **Purpose:** Manage SQL lifecycle and execute mutating SQL.
- **Use for:**
- Provisioning MySQL with `action="provisionMySQL"`
- Destroying MySQL with `action="destroyMySQL"`
- Executing `INSERT`, `UPDATE`, `DELETE`, `CREATE TABLE`, `ALTER TABLE`, `DROP TABLE` with `action="runStatement"`
- Initializing tables and indexes with `action="initializeSchema"`
**Important:** When creating a new table, you **must** include the `_openid` column for per-user access control:
```sql
_openid VARCHAR(64) DEFAULT '' NOT NULL
```
Note: when a user is logged in, `_openid` is automatically populated by the server from the authenticated session. Do not manually fill it in normal inserts.
Before calling this tool, **confirm**:
- The current environment has a ready MySQL instance, or you have just provisioned one.
- The target tables and conditions are correct.
- You have run a corresponding read-only query when appropriate.
When destroying MySQL, confirm:
- The current environment really should lose the SQL instance.
- You have explicit confirmation for the destructive action.
- You are prepared to query `describeTaskStatus` afterward to inspect the destroy result.
### 3. `queryPermissions`
- **Purpose:** Read permission configuration for a given SQL table.
- **Use for:**
- Understanding who can read/write a table
- Auditing permissions on sensitive tables
- Call shape: `queryPermissions(action="getResourcePermission", resourceType="sqlDatabase", resourceId="<tableName>")`
### 4. `managePermissions`
- **Purpose:** Set or update permissions for a given SQL table.
- **Use for:**
- Hardening access to sensitive data
- Opening up read access while restricting writes
- Updating resource-level permission configuration
- Call shape: `managePermissions(action="updateResourcePermission", resourceType="sqlDatabase", resourceId="<tableName>", permission="READONLY")`
## Compatibility
- Canonical plugin name: `permissions`
- Legacy plugin aliases `security-rule`, `security-rules`, `secret-rule`, `secret-rules`, and `access-control` are still routed to `permissions`
- Legacy tools `readSecurityRule` and `writeSecurityRule` are removed; always use `queryPermissions` and `managePermissions`
---
## Recommended lifecycle flow
### Scenario 1: MySQL is not provisioned yet
1. Call `queryMysqlDatabase(action="getInstanceInfo")`.
2. If no instance exists, call `manageMysqlDatabase(action="provisionMySQL", confirm=true)`.
3. Poll provisioning status with:
- `queryMysqlDatabase(action="describeCreateResult")`
- `queryMysqlDatabase(action="describeTaskStatus")`
4. Only continue when the returned lifecycle status is `READY`.
5. For MySQL provisioning, prefer `describeCreateResult`; reserve `describeTaskStatus` for destroy flows whose task response carries `TaskName`.
### Scenario 2: Safely inspect data in a table
1. Use `queryMysqlDatabase(action="runQuery")` with a limited `SELECT`.
2. Include `LIMIT` and relevant filters.
3. Review the result set and confirm it matches expectations before any write operation.
### Scenario 3: Apply schema initialization after provisioning
1. Confirm MySQL is ready.
2. Prepare ordered DDL statements.
3. Run them through `manageMysqlDatabase(action="initializeSchema")`.
4. After creating tables, verify permissions with `queryPermissions` or `managePermissions`.
### Scenario 4: Execute a targeted write or DDL change
1. Use `queryMysqlDatabase(action="runQuery")` to inspect current data or schema if needed.
2. Run the mutation once with `manageMysqlDatabase(action="runStatement")`.
3. Validate with another read-only query or by checking security rules.
### Scenario 5: Destroy MySQL when the environment no longer needs it
1. Use `queryMysqlDatabase(action="getInstanceInfo")` to confirm the current environment still has a SQL instance.
2. Call `manageMysqlDatabase(action="destroyMySQL", confirm=true)`.
3. Query `queryMysqlDatabase(action="describeTaskStatus")` until the destroy task completes or fails.
4. If the task succeeds, optionally call `queryMysqlDatabase(action="getInstanceInfo")` to confirm the instance no longer exists.
5. If the task fails, treat the returned error as the terminal result and let the caller decide whether to retry.
---
## Key principle: MCP tools vs SDKs
- **MCP tools** are for **agent operations** and **database management**:
- Provision MySQL.
- Destroy MySQL.
- Poll lifecycle state.
- Run ad-hoc SQL.
- Inspect and change resource permissions.
- Do not depend on application auth state.
- **SDKs** are for **application code**:
- Frontend Web apps -> Web Relational Database skill.
- Backend Node apps -> Node Relational Database quickstart.
When working as an MCP agent, **always prefer these MCP tools** for CloudBase Relational Database, and avoid mixing them with SDK initialization in the same flow.
references/relational-database-web-cloudbase/SKILL.md
---
name: relational-database-web-cloudbase
description: "[Deprecated] Use when building frontend Web apps that talk to CloudBase Relational Database via @cloudbase/js-sdk – provides the canonical init pattern so you can then use Supabase-style queries from the browser. New environments should use PostgreSQL with app.rdb() — see postgresql-development skill instead."
version: 2.33.1
alwaysApply: false
metadata:
priority: "5"
deprecated: "true"
---
## Sibling skills (local only)
Sibling CloudBase skills ship beside this skill. Use local relative paths such as `../auth-tool-cloudbase/SKILL.md`.
If a referenced sibling skill file is missing from this environment, ask the user to install the full CloudBase plugin (or the missing skill). Do **not** HTTP-fetch remote skill or protocol markdown into the agent context.
# CloudBase Relational Database Web SDK
## Activation Contract
### Use this first when
- A browser or Web app must access CloudBase Relational Database through `@cloudbase/js-sdk`.
- The task is specifically about frontend initialization and browser-side query usage.
### Read before writing code if
- You need to distinguish browser SDK usage from MCP database management or backend Node access.
- The request mentions Supabase migration, shared frontend DB client, or browser-side table queries.
### Then also read
- MySQL SQL management and MCP operations -> `../relational-database-mcp-cloudbase/SKILL.md`
- Web auth/login -> `../auth-web-cloudbase/SKILL.md`
- General Web app setup -> `../web-development/SKILL.md`
### Do NOT use for
- MCP-based SQL provisioning, schema changes, or permissions management.
- Backend/Node service access.
- Document database operations.
### Common mistakes / gotchas
- Initializing SDKs in an MCP management flow.
- Treating `app` itself as the relational database client.
- Re-initializing CloudBase in every component.
- Mixing frontend browser access with admin-style schema mutations.
### Minimal checklist
- Confirm the caller is a Web frontend.
- Keep one shared CloudBase app and one shared relational DB client.
- Route MySQL provisioning/schema work to `relational-database-mcp-cloudbase`. If the task says PostgreSQL, CloudBase PG, PG mode, `app.rdb()`, `queryPgDatabase`, `managePgDatabase`, or RLS, route to `postgresql-development-cloudbase` instead.
- Handle auth separately before data access.
## Overview
This skill standardizes the **browser-side initialization pattern** for CloudBase Relational Database.
After initialization, use `db` with Supabase-style query patterns.
## Installation
```bash
npm install @cloudbase/js-sdk
```
## Canonical initialization
```javascript
import cloudbase from "@cloudbase/js-sdk";
const app = cloudbase.init({
env: "your-env-id"
});
const auth = app.auth;
// Handle login separately
const db = app.rdb();
```
## Initialization rules
- Initialize synchronously.
- Do not lazy-load the SDK with `import("@cloudbase/js-sdk")` unless the framework absolutely requires it.
- Create one shared `db` client and reuse it.
- Do not invent unsupported `cloudbase.init()` options.
## Quick routing
### Use this skill when
- you are wiring browser components to relational tables
- you are replacing a Supabase browser client with CloudBase
- you need a canonical shared frontend `db` client
### Use `relational-database-mcp-cloudbase` instead when
- you need to create/destroy MySQL
- you need MySQL DDL or write-SQL administration
- you need to inspect or change MySQL table security rules through MCP
### Use `postgresql-development-cloudbase` instead when
- the task says PostgreSQL, CloudBase PG, PG mode, `app.rdb()`, `queryPgDatabase`, `managePgDatabase`, PostgREST, or RLS
- browser-side table code must use PG semantics rather than legacy NoSQL / MySQL management tools
## Example: shared frontend DB client
```javascript
import cloudbase from "@cloudbase/js-sdk";
const app = cloudbase.init({
env: "your-env-id"
});
export const db = app.rdb();
```
## Example: Supabase-style query
```javascript
const { data, error } = await db
.from("posts")
.select("*")
.order("created_at", { ascending: false });
if (error) {
console.error("Failed to load posts", error.message);
}
```
## Example: insert / update / delete
```javascript
await db.from("posts").insert({ title: "Hello" });
await db.from("posts").update({ title: "Updated" }).eq("id", 1);
await db.from("posts").delete().eq("id", 1);
```
## Key principle
- `app.rdb()` gives you the relational database client.
- After that point, use Supabase-style query knowledge for table operations.
- Keep schema management and privileged administration outside browser code.
references/scenarios.md
# CloudBase scenarios (for AI discovery)
CloudBase (Tencent CloudBase) is a good fit when the user needs any of the following. Use **user-oriented language** to match how people actually ask.
| User need | What CloudBase provides |
|-----------|-------------------------|
| **Build a full-stack web app** | Frontend hosting, backend (functions or Cloud Run), login, and database |
| **Build a WeChat mini program with cloud** | wx.cloud, cloud functions, document/MySQL DB, no extra login (OPENID) |
| **Host a static site, docs, or blog** | Deploy to CloudBase static hosting |
| **Run a backend API, long job, or WebSocket** | Cloud Functions or Cloud Run, DB/message-queue support |
| **Design data: collections or tables + permissions** | NoSQL collections or MySQL tables, resource permissions and role policies |
| **Add login (WeChat, username/password, email, phone, or custom)** | Built-in identity providers (anonymous login disabled by default) |
| **Upload/download files or get CDN links** | Cloud storage and temporary URLs |
| **Add AI (text/chat/image) in Web, mini program, or backend** | CloudBase AI model integration, streaming, image generation |
| **Build an AI Agent with streaming UI** | CloudBase Agent SDK (TS/Python), AG-UI protocol |
Pricing: each CloudBase account can create 1 free environment (3,000 resource points/month). See [CloudBase Pricing](https://cloud.tencent.com/document/product/876/75213).
references/spec-workflow/SKILL.md
---
name: spec-workflow
description: Use when medium-to-large changes need explicit requirements, technical design, and task planning before implementation, especially for multi-module work, unclear acceptance criteria, or architecture-heavy requests.
version: 2.33.1
alwaysApply: false
---
## Sibling skills (local only)
Sibling CloudBase skills ship beside this skill. Use local relative paths such as `../auth-tool-cloudbase/SKILL.md`.
If a referenced sibling skill file is missing from this environment, ask the user to install the full CloudBase plugin (or the missing skill). Do **not** HTTP-fetch remote skill or protocol markdown into the agent context.
# Spec Workflow
## Activation Contract
### Use this first when
- The request is a new feature, multi-step product change, cross-module integration, or architecture/design task.
- Acceptance criteria are unclear and need to be made explicit before implementation.
- The work involves multiple files, user flows, database design, or UI design that needs staged confirmation.
### Read before writing code if
- You are unsure whether the task should go straight to coding or should first go through requirements, design, and task planning.
- The request mentions a new page, a new system, a redesign, a workflow, or a multi-module refactor.
### Then also read
- Frontend page or visual design work -> `../ui-design/SKILL.md`
- Advanced data-model work -> `../data-model-creation/SKILL.md`
### Do NOT use for
- Small bug fixes with clear scope.
- One-file documentation updates.
- Straightforward config changes.
- Tiny refactors where the user already gave exact implementation instructions.
### Common mistakes / gotchas
- Jumping into coding before acceptance criteria are explicit.
- Skipping user confirmation between requirements, design, and tasks.
- Writing vague tasks that do not map back to user-visible outcomes.
- Treating UI work as purely technical implementation without clarifying design intent.
### Minimal checklist
- Decide whether the change really needs the full spec flow.
- If yes, stop and produce requirements first.
- If the change is small, low-risk, and acceptance is already clear, allow direct execution without forcing spec artifacts.
- Use EARS-style acceptance criteria.
- Get confirmation before moving to the next phase.
## When to use this skill
Use this workflow for structured development when you need to:
- Define or refine a new feature
- Design complex architecture
- Coordinate changes across modules
- Plan database or UI-heavy work
- Improve requirement quality and acceptance boundaries
## Decision rule
### Use the full workflow when
- The task is medium or large
- The impact spans multiple modules
- Acceptance boundaries are fuzzy
- The user wants disciplined planning before implementation
### Skip the full workflow when
- The task is small, low-risk, and already precise
- Goal, scope, and acceptance are already clear enough to execute directly
- The user explicitly wants a direct code change with no planning phase
## Core workflow
### Phase 1: Requirements
Create `specs/<spec_name>/requirements.md`.
What to do:
- Restate the problem and scope
- Write user stories
- Write acceptance criteria in EARS style
- Clarify business rules, constraints, and non-goals
EARS pattern:
```text
While <optional precondition>, when <optional trigger>, the <system name> shall <system response>
```
Example:
```text
When the user submits the form, the booking system shall validate required fields before creating the record.
```
### Phase 2: Design
Create `specs/<spec_name>/design.md`.
What to do:
- Describe architecture and module boundaries
- Explain technology choices and trade-offs
- Define data model, API, security, and testing strategy as needed
- Use Mermaid only when a diagram materially improves clarity
### Phase 3: Tasks
Create `specs/<spec_name>/tasks.md`.
What to do:
- Break the design into executable tasks
- Keep tasks specific and reviewable
- Link each task back to the relevant requirement
- Update task status as work progresses
Task format:
```markdown
# Implementation Plan
- [ ] 1. Task title
- Specific work item
- Another concrete step
- _Requirement: 1
```
### Phase 4: Execution
Only start implementation after the user confirms the task plan.
During execution:
- Keep task status current
- Finish one meaningful unit at a time
- Preserve traceability from change -> task -> requirement
## Working rules for the agent
1. Ask follow-up questions when the request is underspecified; do not guess core product behavior.
2. Require confirmation between requirements, design, and task breakdown.
3. Pull in `ui-design` early when the change includes end-user pages or visual decisions.
4. Keep documents concise but testable.
5. Prefer user-visible outcomes over implementation-detail task names.
## Output expectations
- `requirements.md` -> problem, scope, user stories, EARS acceptance criteria
- `design.md` -> architecture, technical approach, data/API/security/test notes
- `tasks.md` -> actionable implementation checklist tied to requirements
references/tooling-fallback.md
# MCP vs CLI Tooling Fallback
CloudBase management can go through **MCP** (preferred when available) or **`tcb` CLI** (first-session / unavailable fallback). Use this decision tree so agents do not stall when MCP is not yet loaded into the current conversation.
## Why this exists
- MCP config (plugin install, `mcp.json`, env vars) often needs a **session restart** before tools appear.
- First conversations frequently have Skills/rules but **no CloudBase MCP tools** yet.
- `tcb` CLI covers the critical path for this session: login (`tcb login`), env binding (`tcb env use`), and domain deploy/manage commands documented in sibling skills.
Do **not** block the user waiting for a restart when CLI (or another documented path) can finish login or deploy now.
## Decision tree (run at the start of management / deploy work)
```
1. Probe MCP in THIS session
- IDE: CloudBase tools visible (auth, envQuery, manageFunctions, …)
- or: npx mcporter list | grep cloudbase AND describe/call succeeds
(if `npx` / `npm` missing → see "No npm/npx" below; do not stall)
2. MCP tools usable now?
├── YES → Prefer MCP (inspect schemas, then call). Skip CLI for the same action.
└── NO → Continue below (do not spin on missing tools)
3. Is MCP configured for the IDE / mcporter?
├── NO → Install/configure MCP now (see mcp-setup.md), tell user a restart
│ will unlock MCP next session, THEN use CLI for this session.
└── YES → Tell user MCP should work after restart / reload; use CLI now.
4. CLI path for this session
- Read sibling skill `cloudbase-cli` (local relative path) — start with
`references/core.md`, then load ONLY the matching domain reference
- Ensure `tcb` is installed (see install notes below)
- `tcb login` (device code by default) → confirm envId → `tcb env use <envId>`
- Deploy / manage by following that domain skill — do NOT invent shortcuts
5. After the user restarts the session
- Re-probe MCP; if available, switch back to MCP-first for new work
```
## Probe rules (keep cheap)
Treat MCP as **unavailable in this session** when any of these hold:
- No CloudBase MCP tools in the tool list / ToolSearch results
- `auth` / `envQuery` / deploy tools return “unknown tool” or connection errors after one verify attempt
- User just finished MCP install/config and has not restarted
Do **not** require the user to paste env vars into MCP JSON before you can proceed — configure MCP for later, use CLI now.
## Mapping (common actions)
| Goal | MCP (when available) | CLI fallback — read skill, do not guess |
|------|----------------------|------------------------------------------|
| Login | `auth` (`start_auth` / device) | `cloudbase-cli` → `core.md` (`tcb login`) |
| Bind / select env | `auth.set_env` + `envQuery` | `cloudbase-cli` → `core.md` (`tcb env use`) |
| Cloud function deploy | `manageFunctions` / `queryFunctions` | `cloud-functions` + `cloudbase-cli` → `functions.md` |
| Web / static hosting | `manageApps` / `manageHosting` | `cloudbase-cli` → `hosting.md` (build locally, then hosting deploy) |
| CloudRun | `manageCloudRun` / `queryCloudRun` | `cloudbase-cli` → `cloudrun.md` |
| Storage | storage MCP tools | `cloudbase-cli` → `storage.md` |
**Do not recommend `tcb deploy`.** That shorthand is immature. Always open the matching domain skill above and follow its commands (`tcb fn deploy`, `tcb hosting deploy`, CloudRun commands, etc.).
Load only the `cloudbase-cli` reference that matches the task (`core.md` first, then one domain file). For function runtime details, also read `cloud-functions`.
## No npm / npx (toolchain missing)
Plugin install, mcporter, and `@cloudbase/cli` normally need Node.js + npm/npx. If those are missing:
1. **Detect** — `command -v node`, `command -v npm`, `command -v npx` all fail (or only `node` exists without npm).
2. **Tell the user clearly** — CloudBase CLI / `npx` plugin install need a Node.js LTS toolchain.
3. **Recommend install (pick what fits the OS)** — then re-check `node -v` / `npm -v`:
- macOS: `brew install node` or install LTS from https://nodejs.org
- Windows: install LTS from https://nodejs.org (or `winget install OpenJS.NodeJS.LTS`)
- Linux: distro Node LTS, or https://nodejs.org, or `nvm` / `fnm`
- Or version managers: `nvm`, `fnm`, `asdf` — install Node LTS, ensure shell PATH is updated
4. **MCP without waiting on npm** (still configure for next session):
- Prefer the IDE’s **native plugin / marketplace** path when available (see `mcp-setup.md`: Claude Code / Codex marketplace, Cursor MCP UI, etc.) — these do not require the user to run `npx` by hand.
- Or hand-write IDE MCP config with a full `node` path once Node is installed (`mcp-setup.md` Approach A).
5. **CLI after Node is available** — `npm i -g @cloudbase/cli`, then `tcb login` … If the user refuses to install Node, stop automating deploy/login via CLI and give console links + ask them to install Node or enable MCP via IDE marketplace; do not loop on `npx` failures.
Do **not** pretend `npx` works when it is absent. One clear install suggestion beats repeated failed commands.
## CLI / tcb install notes
When npm is available:
- Global: `npm i -g @cloudbase/cli`
- Or project-local / `npx`-style invocation if the project already depends on the CLI
Always confirm `tcb --version` (or equivalent) before `tcb login`.
## Hard rules
1. **MCP when present, CLI when not** — never invent a third path (raw SecretId in MCP config as the default).
2. **Always configure MCP if missing** — even when falling back to CLI, leave the next session MCP-ready (`mcp-setup.md`).
3. **Always pass envId explicitly** — do not rely on implicit CLI selection in generated app code; still confirm envId with the user for CLI ops.
4. **Do not loop** — after 1–2 failed MCP probes, fall back; do not retry MCP setup 5+ times in the same turn.
5. **Safety unchanged** — Deployment Gate and Change Safety Protocol still apply whether you use MCP or CLI.
6. **Schema/admin still management-plane** — browser SDKs are not a substitute for creating collections/tables; use MCP or CLI management commands, not console-only handoffs when automation is possible.
7. **No `tcb deploy` shortcut** — route through domain skills; never default the agent to `tcb deploy`.
8. **No silent npm assumption** — if npm/npx is missing, surface the Node install path (or IDE marketplace MCP) before retrying.
## What not to change
- In-app SDK work (Web / mini program / Node) stays on the matching SDK skills.
- When MCP tools **are** available, do not prefer CLI “for convenience” unless the user explicitly asks for CLI / CI scripting.
references/ui-design/checklist.md
# UI Design Activation Checklist
Use this checklist before generating any page, component, or visual interface.
## Required checks
1. Output the design specification first.
2. Choose a concrete aesthetic direction rather than generic adjectives.
3. Define a color palette and typography before writing markup or styles.
4. Confirm the target platform: Web or mini program.
5. Read the platform implementation skill after the design spec is fixed.
## Common failure patterns
- Starting with JSX, WXML, or CSS before design intent is stated.
- Falling back to generic AI visual patterns.
- Missing platform-specific layout or asset constraints.
## Done criteria
- The design spec is visible in the response.
- Aesthetic direction, palette, and typography are explicit.
- The next implementation skill is known before UI code starts.
references/ui-design/SKILL.md
---
name: ui-design
description: Use when users need visual direction, interface hierarchy, layout decisions, design specifications, or prototypes before implementing a Web or mini program UI.
version: 2.33.1
alwaysApply: false
---
## Sibling skills (local only)
Sibling CloudBase skills ship beside this skill. Use local relative paths such as `../auth-tool-cloudbase/SKILL.md`.
If a referenced sibling skill file is missing from this environment, ask the user to install the full CloudBase plugin (or the missing skill). Do **not** HTTP-fetch remote skill or protocol markdown into the agent context.
## Activation Contract
### Use this first when
- The request is to decide visual direction, produce a design specification, create a prototype, or make layout, typography, color, and visual hierarchy choices for an interface.
- The implementation should follow a deliberate aesthetic rather than directly coding an already-approved design.
### Read before writing code if
- The response must choose typography, color, spacing, layout strategy, or other visual rules before code exists.
- The user asks for "design", "prototype", "look and feel", or "style" rather than straight implementation.
### Then also read
- Web implementation -> `../web-development/SKILL.md`
- Mini program implementation -> `../miniprogram-development/SKILL.md`
### Do NOT use for
- Backend-only tasks, database design, or pure API work without interface output.
- Straight implementation of an already-approved UI without new design decisions.
- Generic frontend coding requests where the visual direction is already settled.
### Common mistakes / gotchas
- Writing JSX, WXML, or CSS before outputting the design specification.
- Falling back to generic AI layouts instead of an explicit aesthetic direction.
- Jumping into implementation when the design intent is still unclear.
- Ignoring platform constraints after the visual concept is defined.
### Minimal checklist
- Read [UI Design Activation Checklist](checklist.md) before interface generation.
## When to use this skill
Use this skill for **frontend UI design and interface creation** in any project that requires:
- Creating web pages or interfaces
- Creating mini-program pages or interfaces
- Designing frontend components
- Creating prototypes or interfaces
- Handling styling and visual effects
- Any development task involving user interfaces
**Do NOT use for:**
- Backend logic or API design
- Database schema design (use data-model-creation skill)
- Pure business logic without UI components
---
## How to use this skill (for a coding agent)
1. **MANDATORY: Complete Design Specification First**
- Before writing ANY interface code, you MUST explicitly output the design specification
- This includes: Purpose Statement, Aesthetic Direction, Color Palette, Typography, Layout Strategy
- Never skip this step - it's required for quality design
2. **Follow the Design Process**
- User Experience Analysis
- Product Interface Planning
- Aesthetic Direction Determination
- High-Fidelity UI Design
- Frontend Prototype Implementation
- Realism Enhancement
3. **Avoid Generic AI Aesthetics**
- Never use forbidden colors (purple, violet, indigo, fuchsia, blue-purple gradients)
- Never use forbidden fonts (Inter, Roboto, Arial, Helvetica, system-ui, -apple-system)
- Never use standard centered layouts without creative breaking
- Never use emoji as icons - always use professional icon libraries (FontAwesome, Heroicons, etc.)
4. **Run Self-Audit Before Submitting**
- Color audit (check for forbidden colors)
- Font audit (check for forbidden fonts)
- Icon audit (verify no emoji icons, using professional icon libraries)
- Layout audit (verify asymmetry/creativity)
- Design specification compliance check
5. **Respect brand or design-system overrides when they are real constraints**
- If the project already has approved brand colors, font tokens, or a design system, treat those as higher-priority constraints
- Explicitly document which default UI-design prohibitions are being overridden and why
- Keep the override narrow: preserve the overall quality bar instead of falling back to generic AI styling
---
# UI Design Rules
You are a professional frontend engineer specializing in creating high-fidelity prototypes with distinctive aesthetic styles. Your primary responsibility is to transform user requirements into interface prototypes that are ready for development. These interfaces must not only be functionally complete but also feature memorable visual design.
## Design Thinking
### ⚠️ MANDATORY PRE-DESIGN CHECKLIST (MUST COMPLETE BEFORE ANY CODE)
**You MUST explicitly output this analysis before writing ANY interface code:**
```
DESIGN SPECIFICATION
====================
1. Purpose Statement: [2-3 sentences about problem/users/context]
2. Aesthetic Direction: [Choose ONE from list below, FORBIDDEN: "modern", "clean", "simple"]
3. Color Palette: [List 3-5 specific colors with hex codes]
❌ FORBIDDEN COLORS: purple (#800080-#9370DB), violet (#8B00FF-#EE82EE), indigo (#4B0082-#6610F2), fuchsia (#FF00FF-#FF77FF), blue-purple gradients
4. Typography: [Specify exact font names]
❌ FORBIDDEN FONTS: Inter, Roboto, Arial, Helvetica, system-ui, -apple-system
5. Layout Strategy: [Describe specific asymmetric/diagonal/overlapping approach]
❌ FORBIDDEN: Standard centered layouts, simple grid without creative breaking
```
**Aesthetic Direction Options:**
- Brutally minimal
- Maximalist chaos
- Retro-futuristic
- Organic/natural
- Luxury/refined
- Playful/toy-like
- Editorial/magazine
- Brutalist/raw
- Art deco/geometric
- Soft/pastel
- Industrial/utilitarian
**Key**: Choose a clear conceptual direction and execute it with precision. Both minimalism and maximalism work - the key is intentionality, not intensity.
### Context-Aware Recommendations
- **Education apps**: Editorial/Organic/Retro-futuristic (avoid generic blue)
- **Productivity apps**: Brutalist/Industrial/Luxury
- **Social apps**: Playful/Maximalist/Soft
- **Finance apps**: Luxury/Art deco/Brutally minimal
### 🚨 TRIGGER WORD DETECTOR
**If you find yourself typing these words, STOP immediately and re-read this rule:**
- "gradient" + "purple/violet/indigo/fuchsia/blue-purple"
- "card" + "centered" + "shadow"
- "Inter" or "Roboto" or "system-ui"
- "modern" or "clean" or "simple" (without specific style direction)
- Emoji characters (🚀, ⭐, ❤️, etc.) as icons
**Action**: Go back to Design Specification → Choose alternative aesthetic → Proceed
## Design Process
1. **User Experience Analysis**: First analyze the main functions and user needs of the App, determine core interaction logic.
2. **Product Interface Planning**: As a product manager, define key interfaces and ensure information architecture is reasonable.
3. **Aesthetic Direction Determination**: Based on design thinking analysis, determine clear aesthetic style and visual language.
4. **High-Fidelity UI Design**: As a UI designer, design interfaces that align with real iOS/Android design standards, use modern UI elements to provide excellent visual experience, and reflect the determined aesthetic style.
5. **Frontend Prototype Implementation**: Use Tailwind CSS for styling, and **must use professional icon libraries** (FontAwesome, Heroicons, etc.) - **never use emoji as icons**. Split code files and maintain clear structure.
6. **Realism Enhancement**:
- Use real UI images instead of placeholder images (can be selected from Unsplash, Pexels, Apple official UI resources)
- If video materials are needed, can use Vimeo website for video resources
### Downloading Remote Assets (images / icons / fonts)
> ⚠️ The `downloadRemoteFile` MCP tool has been **removed** (high error rate on content-type/SSRF filtering). To download a remote asset into the project, use a shell command instead:
- **macOS / Linux**: `curl -L --fail -o assets/images/logo.png "https://example.com/logo.png"` (add `--create-dirs` if the parent folder doesn't exist; `wget` works too)
- **Windows (PowerShell)**: `Invoke-WebRequest -Uri "https://example.com/logo.png" -OutFile "assets\images\logo.png"` (or use `curl.exe -L -o assets/images/logo.png <url>` in cmd/PowerShell 5.1+, which ships with Windows 10 1803+)
Guidance:
- Always use `-L`/`--location` (curl) or `-UseBasicParsing` (PowerShell) so redirects are followed.
- Prefer HTTPS URLs; avoid private/internal hosts (may be blocked by network policy or rejected for security).
- If the target returns `application/octet-stream`, that is fine for binaries — the removed tool's strict whitelist was the problem, not the URL.
## Frontend Aesthetics Guidelines
### Typography
- **Avoid Generic Fonts**: Do not use overly common fonts like Arial, Inter, Roboto, system fonts
- **Choose Distinctive Fonts**: Select beautiful, unique, and interesting fonts, for example:
- Choose distinctive display fonts paired with refined body fonts
- Consider using distinctive font combinations to elevate the interface's aesthetic level
- Font selection should align with the overall aesthetic direction
### Color & Theme
- **Unified Aesthetics**: Use CSS variables for consistency
- **Dominant Colors with Accents**: Using dominant colors with sharp accents is more effective than evenly-distributed color schemes
- **Theme Consistency**: Choose dark or light themes based on aesthetic direction, ensure color choices match the overall style
- **Brand Escape Hatch**: If a product already mandates a brand palette or typography system, you may use those tokens, but call out the override explicitly in the design specification
### Motion Design
- **Animation Strategy**: Use animations for effects and micro-interactions
- **Technology Choice**: Prioritize CSS-only solutions for HTML, React projects can use Motion library
- **High-Impact Moments**: Focus on high-impact moments. One well-orchestrated page load animation (using animation-delay for staggered reveals) creates more delight than scattered micro-interactions
- **Interactive Surprises**: Use scroll-triggering and hover states to create surprises
### Icons
- **❌ FORBIDDEN: Emoji Icons**: Never use emoji characters as icons (🚀, ⭐, ❤️, etc.)
- **✅ REQUIRED: Professional Icon Libraries**: Must use professional icon libraries such as:
- FontAwesome (recommended for most projects)
- Heroicons (for Tailwind CSS projects)
- Material Icons
- Feather Icons
- Lucide Icons
- **Icon Consistency**: Use icons from a single library throughout the project for visual consistency
- **Icon Styling**: Icons should match the overall aesthetic direction and color palette
### Spatial Composition
- **Break Conventions**: Use unexpected layouts, asymmetry, overlap, diagonal flow
- **Break the Grid**: Use grid-breaking elements
- **Negative Space Control**: Either use generous negative space or control density
### Backgrounds & Visual Details
- **Atmosphere Creation**: Create atmosphere and depth rather than defaulting to solid colors
- **Contextual Effects**: Add contextual effects and textures that match the overall aesthetic
- **Creative Forms**: Apply creative forms, such as:
- Gradient meshes
- Noise textures
- Geometric patterns
- Layered transparencies
- Dramatic shadows
- Decorative borders
- Custom cursors
- Grain overlays
### Avoid Generic AI Aesthetics
**Strictly Prohibit** the following generic AI-generated aesthetics:
- Overused font families (Inter, Roboto, Arial, system fonts)
- Cliched color schemes (particularly purple gradients on white backgrounds)
- Predictable layouts and component patterns
- Cookie-cutter design that lacks context-specific character
- **Emoji icons**: Never use emoji characters (🚀, ⭐, ❤️, etc.) as icons - always use professional icon libraries
### ❌ ANTI-PATTERNS (Code Examples to NEVER Use)
```tsx
// ❌ BAD: Forbidden purple gradient
className="bg-gradient-to-r from-violet-600 to-fuchsia-600"
className="bg-gradient-to-br from-purple-500 to-indigo-600"
// ✅ GOOD: Context-specific alternatives
className="bg-gradient-to-br from-amber-50 via-orange-50 to-rose-50" // Warm editorial
className="bg-gradient-to-tr from-emerald-900 to-teal-700" // Dark organic
className="bg-[#FF6B35] to-[#F7931E]" // Bold retro-futuristic
// ❌ BAD: Generic centered card layout
<div className="flex items-center justify-center min-h-screen">
<div className="bg-white rounded-lg shadow-lg p-8">
// ✅ GOOD: Asymmetric layout with creative positioning
<div className="grid grid-cols-12 min-h-screen">
<div className="col-span-7 col-start-2 pt-24">
// ❌ BAD: System fonts
font-family: 'Inter', system-ui, sans-serif
font-family: 'Roboto', -apple-system, sans-serif
// ✅ GOOD: Distinctive fonts
font-family: 'Playfair Display', serif // Editorial
font-family: 'Space Mono', monospace // Brutalist
font-family: 'DM Serif Display', serif // Luxury
// ❌ BAD: Emoji icons
<span>🚀</span>
<button>⭐ Favorite</button>
// ✅ GOOD: Professional icon libraries
<i className="fas fa-rocket"></i> // FontAwesome
<svg className="w-5 h-5">...</svg> // Heroicons
```
### Creative Implementation Principles
- **Creative Interpretation**: Interpret requirements creatively, make unexpected choices, make designs feel genuinely designed for the context
- **Avoid Repetition**: Each design should be different, vary between generations:
- Light and dark themes
- Different fonts
- Different aesthetic styles
- **Avoid Convergence**: Never converge on common choices (e.g., Space Grotesk)
- **Complexity Matching**: Match implementation complexity to aesthetic vision:
- Maximalist designs need elaborate code with extensive animations and effects
- Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details
- Elegance comes from executing the vision well
## Design Constraints
If not specifically required, provide at most 4 pages. Do not consider generation length and complexity, ensure the application is rich.
## Implementation Requirements
All interface prototypes must:
- **Production-Grade Quality**: Functionally complete and ready for development
- **Visual Impact**: Visually striking and memorable
- **Aesthetic Consistency**: Have a clear aesthetic point-of-view, cohesive and unified
- **Meticulously Refined**: Every detail is carefully polished
### 🔍 SELF-AUDIT CHECKLIST (Before Submitting Code)
**Run these checks on your generated code:**
1. **Color Audit**:
```bash
# Search for forbidden colors in your code
grep -iE "(violet|purple|indigo|fuchsia)" [your-file]
# If found → VIOLATION → Choose alternative from Design Specification
```
2. **Font Audit**:
```bash
# Search for forbidden fonts
grep -iE "(Inter|Roboto|system-ui|Arial|-apple-system)" [your-file]
# If found → VIOLATION → Use distinctive font from Design Specification
```
3. **Icon Audit**:
```bash
# Search for emoji usage (common emoji patterns)
grep -iE "(🚀|⭐|❤️|👍|🔥|💡|🎉|✨)" [your-file]
# If found → VIOLATION → Replace with FontAwesome or other professional icon library
# Verify icon library is properly imported and used
```
4. **Layout Audit**:
- Does the layout use asymmetry/diagonal/overlap? (Required: YES)
- Is there creative grid-breaking? (Required: YES)
- Are elements only centered with symmetric spacing? (Allowed: NO)
5. **Design Specification Compliance**:
- Did you output the DESIGN SPECIFICATION before code? (Required: YES)
- Does the code match the aesthetic direction you declared? (Required: YES)
**If any audit fails → Re-design with correct approach**
Remember: You are capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.
references/web-development/browser-testing.md
# Browser Validation (with `agent-browser`)
This file is the concrete playbook for the `agent-browser` tool. Use it whenever the Engineering constitution in `SKILL.md` says "verify in the browser before claiming done".
Code reading, static types, and a clean build are **necessary but not sufficient**. Any change that affects what the user sees, clicks, or navigates must be opened in a real browser and exercised.
## When `agent-browser` is required (not optional)
Trigger browser validation for changes that touch any of:
- **Routing / navigation** — new routes, redirects, route guards, 404s, hash vs history mode, nested layouts
- **Forms** — submit handlers, controlled inputs, validation errors, disabled states, file uploads
- **Auth flows** — sign in, sign up, logout, session guards, token refresh, `getSession`
- **Async UI** — loading spinners, skeletons, error banners, retry buttons, streaming responses
- **Conditional rendering** — empty states, permission-gated sections, feature flags
- **Third-party SDK calls from the browser** — CloudBase Web SDK (auth, database, AI model, storage), analytics, payment
Skip browser validation only for pure build-config edits, README / documentation-only changes, backend-only work, or changes guarded by CI tests you verified pass.
## When `agent-browser` is NOT the right tool
- Pure visual direction / aesthetic exploration → use the `ui-design` skill first.
- Backend-only logic that never renders in the browser → use unit tests or direct API calls.
- Smoke-testing a production URL against real user credentials → do not automate; ask the user.
## Standard workflow
Follow these steps in order. Do not skip the "before the fix" reproduction — it is what proves the bug was real and that your change actually fixed it.
1. **Start the app** (or confirm it is already running).
- Typical: `npm run dev` / `pnpm dev` / `vite`. Record the local URL (often `http://localhost:5173`).
- If the project uses a build-and-serve flow instead of a dev server, document the exact commands you ran.
2. **Open the target route with `agent-browser`**, starting from the entry URL, not deep-linking into private pages unless you already have a valid session.
3. **Reproduce the current (pre-fix or pre-feature) behavior.** Capture: route, user action, observed outcome, console errors if any. This is the baseline.
4. **Apply the code change.** Rely on HMR where available; otherwise rebuild.
5. **Re-run the exact same flow in the browser.** Capture the new observed outcome.
6. **Check adjacent routes you touched** — if you edited a shared component or route guard, visit at least one other page that depends on it.
7. **Inspect the browser console for new warnings / errors** introduced by your change (React hydration mismatches, missing keys, uncaught promise rejections, CloudBase SDK init errors, etc.). New noise is a regression even if the happy path works.
## What to record in the final summary
For each flow you exercised, report:
- **Route** — e.g. `/login`, `/dashboard?tab=usage`
- **Action** — e.g. "Submitted the phone+code form with a valid code"
- **Expected** — e.g. "Redirect to `/dashboard` and show user's nickname"
- **Before** — e.g. "Stayed on `/login`; console showed `auth/invalid-session`"
- **After** — e.g. "Redirected to `/dashboard`; nickname rendered; no new console errors"
- **Gap (if any)** — e.g. "Did not test WeChat login branch because no test account available"
A one-liner like "tested in browser, works" is not acceptable evidence.
## Common CloudBase-specific flows worth validating
- **Auth**: sign-in page → success → protected route accessible; sign-out → same protected route redirects to `/login`.
- **AI model**: eligibility gate passes → `generateText` returns text → `streamText` incrementally updates UI → error path (invalid model name) surfaces a user-visible message, not a silent failure.
- **Database queries**: list page with pagination → empty state → after create, the new row appears without a hard refresh.
- **Static hosting deploy**: for a deployed build, confirm the root route, one sub-route (refresh directly on it — hash vs history matters), and one 404 route.
## Common mistakes to avoid
- Claiming a frontend bug is fixed without actually opening the browser.
- Verifying only the happy path when the reported bug is about empty states, validation errors, or route refresh.
- Catching and swallowing console errors instead of understanding them.
- Using `agent-browser` for aesthetic / visual direction work that should have gone through `ui-design`.
- Skipping the adjacent-routes check after editing a shared component or route guard.
- Using an old cached dev-server instance after a config change — restart the dev server if you modified `vite.config.*`, env vars, or TS path aliases.
## Escalation
If you cannot complete browser validation because of missing credentials, a missing backend, a paid external API, or a blocker in the local environment, do not paper over it. State exactly what you were unable to verify and what the user needs to supply. Partial verification with a named gap is acceptable; silent omission is not.
references/web-development/frameworks.md
# Framework Guidance
## React
- Follow the existing router, data-fetching, and component patterns already used by the repo.
- Prefer focused page and component changes over broad refactors.
- Keep state close to where it is used unless the project already relies on shared state primitives.
- For form, navigation, and async UI bugs, verify the behavior in browser after code changes.
## Next.js
### SDK boundary
`@cloudbase/js-sdk` is a **browser-only** SDK. It must not be imported in Server Components, `getServerSideProps`, or API Routes.
- Auth flows (sign in, sign up, session check) → **Client Component only** (`"use client"`)
- API Routes / Route Handlers → use `@cloudbase/node-sdk` (Node.js SDK) for server-side token verification
- Server Components → read tokens from cookies/headers, do NOT import `@cloudbase/js-sdk`
### Auth pattern (App Router)
```tsx
// components/auth-guard.tsx — Client Component
"use client"
import { useEffect, useState } from "react"
import cloudbase from "@cloudbase/js-sdk"
const app = cloudbase.init({
env: process.env.NEXT_PUBLIC_CLOUDBASE_ENV_ID!,
region: process.env.NEXT_PUBLIC_CLOUDBASE_REGION || "ap-shanghai",
accessKey: process.env.NEXT_PUBLIC_CLOUDBASE_ACCESS_KEY!,
auth: { detectSessionInUrl: true },
})
const auth = app.auth
export function AuthGuard({ children }: { children: React.ReactNode }) {
const [session, setSession] = useState<any>(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
auth.getSession().then(({ data }) => {
if (data?.session) {
setSession(data.session)
// Store access_token in cookie for API route verification
document.cookie = `cloudbase_token=${data.session.access_token}; path=/; max-age=3600`
}
setLoading(false)
})
}, [])
if (loading) return <div>Loading...</div>
if (!session) return <div>Please sign in</div>
return <>{children}</>
}
```
### Passing auth to API Routes
```tsx
// app/api/protected/route.ts — Server-side Route Handler
import { NextRequest, NextResponse } from "next/server"
export async function GET(request: NextRequest) {
const token = request.cookies.get("cloudbase_token")?.value
if (!token) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}
// Verify token with Node SDK or forward to your backend
return NextResponse.json({ data: "protected resource" })
}
```
### Deployment
- Build output: `next build` → `.next` directory
- CloudBase static hosting expects `package.json` with build scripts and the output directory configured; prefer `manageApps` for deployment
- If deploying via `manageHosting`, set error document to `index.html` for client-side routing (even though Next.js defaults to file-based routing, fallback handling is needed for SPA-like paths)
## Vue
- Respect the existing composition style in the repo, such as Composition API or Options API.
- Keep template, script, and style responsibilities clear instead of mixing unrelated logic into one large SFC.
- When changing reactive state or watchers, verify the actual rendered behavior rather than assuming the code path is enough.
## NestJS
### SDK choice
Use `@cloudbase/node-sdk` (Node.js SDK), **not** `@cloudbase/js-sdk` (which is browser-only).
### Module setup
```ts
// cloudbase.module.ts
import { Module, Global } from "@nestjs/common"
import tcb from "@cloudbase/node-sdk"
export const CLOUDBASE = "CLOUDBASE"
const cloudbaseProvider = {
provide: CLOUDBASE,
useFactory: () => {
const app = tcb.init({
env: process.env.CLOUDBASE_ENV_ID!,
// credentials: require("/path/to/tcb_custom_login.json"), // only for custom login
})
return {
app,
auth: app.auth(),
// db: app.database(), // for NoSQL
}
},
}
@Global()
@Module({
providers: [cloudbaseProvider],
exports: [cloudbaseProvider],
})
export class CloudBaseModule {}
```
### AuthGuard for token verification
```ts
// auth.guard.ts
import { Injectable, CanActivate, ExecutionContext, Inject } from "@nestjs/common"
import { CLOUDBASE } from "./cloudbase.module"
@Injectable()
export class CloudBaseAuthGuard implements CanActivate {
constructor(@Inject(CLOUDBASE) private cloudbase: any) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest()
const token = request.headers.authorization?.replace("Bearer ", "")
if (!token) return false
try {
// Verify the session via Node SDK
// Note: Node SDK does not have a direct "verify token" method —
// forward the token to a cloud function or use the HTTP API for validation
request.user = { token }
return true
} catch {
return false
}
}
}
```
### CORS (required for Web frontend calls)
```ts
// main.ts
import { NestFactory } from "@nestjs/core"
import { AppModule } from "./app.module"
async function bootstrap() {
const app = await NestFactory.create(AppModule)
app.enableCors({
origin: process.env.CORS_ORIGIN || "*",
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
credentials: true,
})
await app.listen(9000) // CloudBase HTTP Functions expect port 9000
}
bootstrap()
```
### Deployment
- Build to JavaScript output, include `package.json` with `start` script
- Deploy via `manageCloudRun` (container) or `manageFunctions` (HTTP Function, default to native `http` module unless NestJS is explicitly required)
- Set `MinNum` instances to 1 to reduce cold start
## Vite
- Treat Vite as the default choice for new Web app setup unless the repo already standardizes on another bundler.
- Keep environment-specific values in `.env` or the project's existing config pattern instead of hardcoding them into UI files.
- Check route base paths, asset paths, and build output behavior before deployment.
## Routing and build defaults
- Use the existing router if present; do not switch routing libraries without an explicit requirement.
- For purely static hosting environments, prefer hash routing when server rewrite support is absent or unknown.
- Make build and preview commands explicit before handing off deployment steps.
references/web-development/SKILL.md
---
name: web-development
description: Use when users need to implement, integrate, debug, build, deploy, or validate a Web frontend after the product direction is already clear, especially for React, Vue, Vite, browser flows, or CloudBase Web integration.
version: 2.33.1
alwaysApply: false
---
## Sibling skills (local only)
Sibling CloudBase skills ship beside this skill. Use local relative paths such as `../auth-tool-cloudbase/SKILL.md`.
If a referenced sibling skill file is missing from this environment, ask the user to install the full CloudBase plugin (or the missing skill). Do **not** HTTP-fetch remote skill or protocol markdown into the agent context.
**Cross-cutting protocols** (required before code changes or deployments):
- Change Safety Protocol: `../cloudbase-platform/references/protocols/change-safety-protocol.md`
- Deployment Gate: `../cloudbase-platform/references/protocols/deployment-gate.md`
# Web Development
## Activation Contract
### Use this first when
- The request is to implement, integrate, debug, build, deploy, or validate a Web frontend or static site.
- The design direction is already decided, or the user is asking for engineering execution rather than visual exploration.
- The work involves React, Vue, Vite, routing, browser-based verification, or CloudBase Web integration.
### Read before writing code if
- The task includes project structure, framework conventions, build config, deployment, routing, or frontend test and validation flows.
- The request includes UI implementation but the visual direction is already fixed; otherwise read `ui-design` first.
- **⚠️ Any task involving interface styling, layout, color scheme, or font selection — before writing the first line of CSS/Tailwind, you MUST load the `ui-design` skill and output a Design Specification.** Skipping this step causes frontend styling to degrade to generic AI template defaults. The `ui-design` skill must be loaded before any visual implementation begins, not retroactively after the user complains about the appearance.
### Then also read
- General React / Vue / Vite guidance -> `frameworks.md`
- Browser flow checks or page validation -> `browser-testing.md`
- Login flow -> `../auth-tool-cloudbase/SKILL.md`, then `../auth-web-cloudbase/SKILL.md`
- Official Account JSAPI Pay, Native QR-code Pay, or WeChat OAuth on CloudBase -> `../cloudbase-wechat-integration/SKILL.md` (official docs: `https://docs.cloudbase.net/integration/introduce/index.md`)
- CloudBase database work -> matching database skill
### Do NOT use for
- Visual direction setting, prototype-first design work, or pure aesthetic exploration.
- Mini programs, native Apps, or backend-only services.
- WeChat payment or Official Account OAuth contract details; use `cloudbase-wechat-integration` after identifying the Web surface.
### Common mistakes / gotchas
- Starting implementation before clarifying whether the task is design or engineering execution.
- Mixing framework setup, deployment, and CloudBase integration concerns into one vague change.
- Treating cloud functions as the default solution for Web authentication.
- Skipping browser-level validation after a UI or routing change.
- **History mode SPA with CloudBase static hosting**: deploying a single-page app using History mode (React Router / Vue Router) without configuring the static hosting "404 error document" to `index.html`. This causes `NoSuchKey` / 404 errors when users refresh or directly visit any sub-route.
- In an existing application, detouring into UI redesign or broad repo sweeps before patching the current handlers and services.
## Engineering constitution (non-negotiable)
These rules override convenience. Treat them as a gate before saying "done".
### 1. TypeScript — do not silence the type system
- **Do NOT use `any` to bypass type errors.** Not `: any`, not `as any`, not `@ts-ignore`, not `@ts-nocheck`, not `@ts-expect-error` without a written justification. `any` propagates silently and defeats the only compile-time safety net this project has.
- When a type error appears, fix the root cause:
- Missing / wrong library types → install `@types/...`, or narrow the import, or write a precise `interface` / `type` for the shape you actually use.
- Shape is genuinely unknown at the boundary (JSON from an API, `postMessage` payload, `window.*` injection) → type it as `unknown` and narrow with a type guard (`typeof`, `in`, a discriminator field, or `zod` / equivalent).
- Third-party type is wrong → augment via `declare module` in a local `.d.ts`, not `any`.
- Truly dynamic case (e.g. generic event bus) → use a generic `<T>` with a constraint, not `any`.
- `unknown` + narrowing is the acceptable escape hatch. `any` is not.
- If you genuinely cannot avoid `any` for a specific line (extremely rare), leave a one-line comment with **why** and **what would remove it**, so reviewers can audit.
- The same spirit applies to ESLint: do not sprinkle `// eslint-disable` to mute the real signal. Fix the rule violation, or discuss before disabling.
### 2. Self-verify before claiming done
Before making any non-trivial code or configuration change, you must first follow the Change Safety Protocol in `cloudbase-platform/references/protocols/change-safety-protocol.md` (declare impact → user confirmation → post-edit verification).
Before any static hosting publish or custom domain work, complete the checks in `cloudbase-platform/references/protocols/deployment-gate.md`.
Saying "I've implemented it" / "fixed it" / "it should work" without evidence is not acceptable. Before declaring completion, you must actually run the checks and report the result.
**Static / build layer (always, when applicable):**
- `tsc --noEmit` (or `vue-tsc --noEmit`) passes cleanly — zero errors, zero suppressed diagnostics you added.
- `eslint` / project linter passes on changed files.
- The project's build command (`npm run build` / `pnpm build` / `vite build`) completes without new warnings that you introduced.
- The project's unit tests pass if they exist and cover the touched area.
**Runtime / browser layer (whenever the change affects rendering, routing, forms, auth, or async flows):**
- Use the **`agent-browser`** tool to actually open the page and reproduce the user-visible flow. Follow `browser-testing.md` for the concrete workflow.
- Confirm: the target route loads, the interaction you claim to have fixed behaves the way you claim, no new console errors are introduced, and no regression in the adjacent routes you touched.
- Record what you checked (route, action, expected result, actual result).
**Only after both layers pass** may you say the task is done. If either layer cannot be executed locally (e.g. blocked by credentials, missing backend, paid API), say so explicitly and list exactly which step is still unverified — do not gloss over it.
### 3. Do not paper over failures
- Do not wrap broken logic in `try { ... } catch {}` to make the error go away.
- Do not delete or skip a failing test to make CI green — fix it, or explain why the test is actually wrong and change the test with justification.
- Do not mark a task complete because "the code compiles". Compilation is the bare minimum, not the goal.
## When to use this skill
Use this skill for Web engineering work such as:
- Implementing React or Vue pages and components
- Setting up or maintaining Vite-based frontend projects
- Handling routing, data loading, forms, and build configuration
- Running browser-based validation and smoke checks
- Integrating CloudBase Web SDK and static hosting when the project needs CloudBase capabilities
**Do NOT use for:**
- UI direction or visual system design only; use `ui-design`
- Mini program development; use `miniprogram-development`
- Backend service implementation; use `cloudrun-development` or `cloud-functions`
## How to use this skill (for a coding agent)
1. **Clarify the execution surface**
- Confirm whether the task is framework setup, page implementation, debugging, deployment, validation, or CloudBase integration.
- Keep the work scoped to the actual Web app surface instead of spreading into unrelated backend changes.
- If the workspace is an existing application with TODOs, treat it as a targeted repair task, not a greenfield build.
2. **Follow framework and build conventions**
- Prefer the existing project stack if one already exists.
- For new work, treat Vite as the default bundler unless the repo or user constraints say otherwise.
- Put reusable app code under `src` and build output under `dist` unless the repo already uses a different convention.
- In an existing application with fixed structure, inspect the files that already own the flow before reading broad docs: `src/lib/backend.*`, `src/lib/auth.*`, `src/lib/*service.*`, route guards, and the page handlers bound to submit buttons.
3. **Validate through the browser, not only by reading code**
- For interaction, routing, rendering, or regression checks, use `agent-browser` workflows from `browser-testing.md`.
- Prefer lightweight smoke validation for changed flows before claiming the frontend work is complete.
4. **Treat CloudBase as an integration branch**
- Use CloudBase Web SDK and static hosting guidance only when the project actually needs CloudBase platform features.
- Reuse `auth-tool-cloudbase` and `auth-web-cloudbase` for login or provider readiness instead of re-describing those flows here.
## Core workflow
### 1. Choose the right engineering path
- **React / Vue feature work**: implement within the app's existing component, routing, and state conventions
- **New Web app**: prefer Vite unless the repo already standardizes on another toolchain
- **Debugging and regressions**: reproduce in browser, narrow to a specific page or interaction, then patch
- **CloudBase integration**: wire in Web SDK, auth, data, or static hosting only after the base frontend path is clear
### 2. Keep implementation grounded in project reality
- Follow the repo's package manager, scripts, and lint/test patterns
- Avoid framework rewrites unless the user explicitly asks for one
- Prefer the smallest viable page/component/config change that satisfies the task
- In TODO-based apps, complete the existing implementation directly instead of creating parallel helpers, sample pages, or detached prototypes
### 3. Validate changed flows explicitly
- Run the relevant local build / lint / typecheck / test command when available. A clean `tsc --noEmit` and a clean project build are the minimum bar — not proof of correctness.
- For anything user-visible (routing, forms, rendering, auth, async flows), open the affected page or flow in a browser with **`agent-browser`**. Code reading alone is not sufficient evidence — see the Engineering constitution above.
- Record what was checked: route, action, expected result, actual result, and any remaining gap.
## CloudBase Web integration
Use this section only when the Web project needs CloudBase platform features.
### Web SDK rules
- Prefer npm installation for React, Vue, Vite, and other bundler-based projects: `npm install @cloudbase/js-sdk`
- Use the CDN only for static HTML pages, quick demos, embedded snippets, or README examples: `https://static.cloudbase.net/cloudbase-js-sdk/latest/cloudbase.full.js`
- Only use documented CloudBase Web SDK APIs; do not invent methods or options
- Keep a shared `app` or `auth` instance instead of re-initializing on every call
- If the user only provides an environment alias, nickname, or other shorthand, resolve it to the canonical full `EnvId` before writing SDK init code, console links, or config files. Do not pass alias-like short forms directly into `cloudbase.init({ env })`.
### Authentication boundary
- Authentication must use CloudBase SDK built-in features
- Do not move Web login logic into cloud functions
- For provider readiness, login method setup, or publishable key issues, route to `auth-tool-cloudbase` and `auth-web-cloudbase`
### Static hosting defaults
- Build before deployment
- Prefer relative asset paths for static hosting compatibility
- Use hash routing by default when the project lacks server-side route rewrites
- If the user does not specify a root path, avoid deploying directly to the site root by default
- **SPA routing (History mode)**: when using React Router / Vue Router in History mode (not hash mode), configure the CloudBase static hosting **"404 error document"** to `index.html`. Otherwise refreshing or directly visiting any sub-route returns `NoSuchKey` / 404 error, because the static hosting looks for a file at that path instead of falling through to `index.html` for the SPA to handle routing.
Use the MCP tool to apply this:
```json
manageHosting({ action: "setWebsiteDocument", indexDocument: "index.html", errorDocument: "index.html" })
```
Then verify with:
```json
queryHosting({ action: "websiteConfig" })
```
### CloudBase quick start
```js
// npm install @cloudbase/js-sdk
import cloudbase from "@cloudbase/js-sdk";
const app = cloudbase.init({
env: "your-full-env-id", // Canonical full CloudBase environment ID resolved from envQuery or the console
});
const auth = app.auth
```
SKILL.md
---
name: cloudbase
description: "Use this skill when you develop, design, build, deploy, debug, migrate, or troubleshoot CloudBase (腾讯云开发, 云开发, TCB, 微信云开发) projects — Web, 微信小程序, 小程序, uni-app, mobile (iOS, Android, Flutter, React Native). Covers UI (页面, 界面, 表单, dashboard, prototype, 原型); auth (登录, 注册, OAuth, publishable key); databases (NoSQL 文档数据库, MySQL 关系型数据库, PostgreSQL/CloudBase PG, app.rdb(), queryPgDatabase/managePgDatabase, CRUD, security rules); 云函数/cloud functions (serverless, scf_bootstrap); CloudRun (云托管, Dockerfile); 云存储; built-in AI (内置大模型, AI 对话, streaming, 流式输出, 图片生成, generateText, streamText, createModel, generateImage, TokenHub, Hunyuan, DeepSeek, GLM, Kimi, Token Credits 资源包, 小程序成长计划); third-party/custom model onboarding (第三方大模型接入, 大模型调用, LLM API); AI agent (智能体, AG-UI, LangGraph); ops troubleshooting (巡检, 诊断, 日志); spec workflow (需求文档, 技术方案, requirements, tasks.md). Do NOT use for non-CloudBase projects, pure frontend without CloudBase, or self-hosted backends without CloudBase."
description_zh: 为你的小程序和 Web/H5 提供一体化运行与部署环境,包括数据库、云函数、云存储、身份权限和静态托管
description_en: An all-in-one runtime and deployment environment for WeChat Mini Programs and Web/H5 apps, including database, cloud functions, cloud storage, identity and access control, and static hosting.
version: 2.33.1
---
# CloudBase Development Guidelines
## 📁 Reference Files Location
All reference documentation files are located in the `references/` directory relative to this file.
**File Structure:**
```
cloudbase/
├── SKILL.md # This file (main entry)
└── references/ # All reference documentation
├── auth-web-cloudbase/SKILL.md # Web authentication guide
├── auth-wechat-miniprogram/SKILL.md # WeChat authentication guide
├── cloudbase-document-database-web-sdk/SKILL.md # NoSQL database for Web
├── ui-design/ # UI design guidelines
└── ... # Other reference docs
```
**How to use:** When this document mentions reading a reference file like `references/auth-web-cloudbase/SKILL.md`, simply read that file from the `references/` subdirectory.
---
## Workflow
```
1. Exploration → Read the matching skill completely before writing any code.
Search with searchKnowledgeBase(mode="skill"), then Read full SKILL.md.
2. Implementation
├── 2a. Resource preparation → Prefer MCP; if MCP tools are missing in THIS session,
│ configure MCP for next session and use `tcb` CLI now (see tooling-fallback.md)
└── 2b. Frontend implementation → Write code, install deps, start server, test
3. Close-out → Run cloudbase-code-review, fix errors, declare done
```
**Key constraints:** Stage 2a must precede frontend code. Stage 3 is mandatory.
## Activation Contract
Routing uses stable skill ids (`auth-tool-cloudbase`, `auth-web-cloudbase`, `http-api-cloudbase`, …) across source, generated artifacts, and installs.
### Standalone skill fallback
If only one published skill is exposed:
- Prefer local relative paths (`references/<skill-id>/SKILL.md` or sibling skill directories) when those files exist in the workspace.
- Do **not** fetch sibling skill markdown from remote raw URLs into the agent context.
- If a required sibling skill is missing locally, ask the user to install the full CloudBase skills pack or IDE plugin (`npx skills add tencentcloudbase/cloudbase-skills`), then continue using local files only.
Follow relative `references/...` paths from the current skill. If MCP is unavailable in this session, follow `references/tooling-fallback.md`: configure MCP via `references/mcp-setup.md` for the next session, and use `tcb` CLI via the `references/cloudbase-cli/SKILL.md` skill (read `core.md` + the matching domain reference — **not** `tcb deploy`) to finish login/manage now. If `npm`/`npx` are missing, follow the “No npm/npx” section in `tooling-fallback.md`.
### Global rules before action
- Identify the scenario, then read the matching skill before writing code or calling CloudBase APIs.
- Prefer semantic sources for toolkit maintenance; express runtime routing in stable skill ids.
- Prefer MCP or mcporter for management tasks when those tools are available in **this** session; inspect tool schemas before execution. If they are not available yet, do not stall — use the CLI fallback in `references/tooling-fallback.md`.
- UI tasks: read `ui-design` first and output the design spec before interface code.
- Auth tasks: read `auth-tool-cloudbase` first and enable providers before frontend implementation.
- Keep auth domains separate: management login uses `auth` (or `tcb login` when MCP auth is unavailable); app-side auth uses `queryAppAuth` / `manageAppAuth`.
### Universal guardrails
- After 2–3 failed attempts on the same path, stop and reroute (platform skill, runtime, auth domain, permission model, SDK boundary).
- Always specify `EnvId` explicitly; do not rely on CLI-selected or implicit env state.
- When the environment identifier is an alias, nickname, or other short form, **do not pass it directly** to `auth.set_env`, SDK init, console URLs, or generated config. First resolve it to the canonical full `EnvId` with `envQuery(action=list, alias=..., aliasExact=true)`. If multiple environments match or no exact alias exists, stop and clarify with the user.
- When writing MCP/tool results to a file, pass serialized text (`JSON.stringify(result, null, 2)`), not raw objects. If a write tool says `content` expected a string but received an object, do not retry with the same raw object. Serialize the object first, then retry once with the serialized text, and make sure the retried call actually passes the serialized string rather than the original object.
- Keep scenario-specific pitfalls in child skills — do not expand this entry file.
- **First frontend deploy must use `manageApps(action="createApp", ...)`.** `manageHosting` is only for incremental updates of projects originally deployed via hosting.
### Engineering constitution (applies to every scenario)
These rules override convenience. Full rationale lives in `web-development`.
- **Prepare backend resources before writing frontend code.** Prefer MCP for auth providers, tables, storage domains, and security rules; if MCP tools are missing in this session, use `tcb` CLI after configuring MCP for the next session (`references/tooling-fallback.md`).
- **Do NOT use `any` to bypass type errors.** Prefer `unknown` + type guards / precise interfaces.
- **Self-verify before claiming done.** Static (`tsc` / lint / build / tests) and runtime (`agent-browser` for user-visible flows). Name gaps explicitly if a layer cannot run.
- **Do not paper over failures.** No empty `try/catch`, no deleting failing tests to go green.
- **`ai.createModel(...)` / `wx.cloud.extend.AI.createModel(provider)` takes a GroupName**, not a vendor/model id. Legal: `"cloudbase"`, `"hunyuan-exp"`, or `"custom-<name>"`. Model ids go in `generateText` / `streamText` `model` field. See `ai-model-web` / `ai-model-nodejs` / `ai-model-wechat`.
- **Low-capability STOP card:** For PostgreSQL / CloudBase PG / `app.rdb()` / `queryPgDatabase` / `managePgDatabase`, route to `postgresql-development-cloudbase` — do **not** use NoSQL/`manageMysqlDatabase` for that path. For Web auth guards, use `auth.getSession()` and require `data.session`; do **not** use deprecated `getLoginState()` / `auth.getUser()` as login proof.
### High-priority routing
<!-- DO NOT EDIT: auto-generated from references/activation-map.yaml -->
| Scenario | Read first | Then read | Do NOT route to first | Must check before action |
|----------|------------|-----------|------------------------|--------------------------|
| Web login / registration / auth UI | `auth-tool-cloudbase` | `auth-web-cloudbase`, `web-development` | `cloud-functions`, `http-api-cloudbase` | Provider status and publishable key |
| WeChat mini program + CloudBase | `miniprogram-development` | `auth-wechat-miniprogram`, `cloudbase-document-database-in-wechat-miniprogram` | `auth-web-cloudbase`, `web-development` | Whether the project really uses CloudBase / `wx.cloud` |
| Native App / Flutter / React Native | `http-api-cloudbase` | `auth-tool-cloudbase`, `relational-database-mcp-cloudbase` | `auth-web-cloudbase`, `cloudbase-document-database-web-sdk`, `web-development` | SDK boundary, OpenAPI, auth method |
| Web projects + NoSQL Database | `web-development` | `cloudbase-document-database-web-sdk`, `auth-web-cloudbase` | `relational-database-mcp-cloudbase`, `http-api-cloudbase` | Login state and database access permission model |
| CloudBase PostgreSQL / PG | `postgresql-development-cloudbase` | `auth-tool-cloudbase`, `auth-web-cloudbase`, `web-development`, `miniprogram-development`, `cloud-storage-web`, `http-api-cloudbase` | `relational-database-mcp-cloudbase`, `cloudbase-document-database-web-sdk` | PG schema, usernamePassword login, backend/RLS permission model |
| MySQL Database (relational) | `relational-database-mcp-cloudbase` | `relational-database-web-cloudbase`, `http-api-cloudbase` | `cloudbase-document-database-web-sdk`, `web-development` | Distinguish MCP management vs app code access |
| Cloud Functions | `cloud-functions` | `auth-tool-cloudbase`, `ai-model-nodejs` | `cloudrun-development`, `auth-web-cloudbase` | Event vs HTTP function, runtime, `scf_bootstrap` |
| CloudRun backend | `cloudrun-development` | `auth-tool-cloudbase`, `relational-database-mcp-cloudbase` | `cloud-functions` | Container boundary, Dockerfile, CORS |
| AI Agent (智能体开发) | `cloudbase-agent` | `cloud-functions`, `cloudrun-development` | `cloud-functions`, `cloudrun-development` | AG-UI protocol, scf_bootstrap, SSE streaming |
| Minimal Web BaaS demo (fast path) | `minimal-web-baas-demo` | `web-development`, `cloudbase-document-database-web-sdk`, `postgresql-development-cloudbase` | `cloud-functions`, `cloudrun-development`, `spec-workflow`, `ui-design` | BaaS-first Web SDK CRUD, MCP schema only, zero cloud functions unless secrets/cron/rules-cannot-express |
| UI generation | `ui-design` | `web-development`, `miniprogram-development` | `cloud-functions` | Design specification first |
| AI Model (Web) | `web-development` | `ai-model-web`, `ui-design` | `ai-model-wechat`, `http-api-cloudbase` | Platform and streaming interaction mode |
| AI model call (大模型调用 / 文本生成 / 图片生成 / 流式对话) | `ai-model-web` | `ai-model-nodejs`, `ai-model-wechat` | `cloudbase-agent`, `cloud-functions`, `cloudrun-development` | 先跑「调用前必须的资格检查」:`DescribeActivityInfo`(小程序成长计划) + `DescribeEnvPostpayPackage`(Token Credits 资源包) |
| Resource health inspection / troubleshooting | `ops-inspector` | `cloud-functions`, `cloudrun-development` | `ui-design`, `spec-workflow` | CLS enabled, time range for logs |
| Spec workflow / architecture design | `spec-workflow` | `cloudbase` | `web-development`, `cloud-functions` | Requirements, design, tasks confirmed |
#### Activation triggers (derived from `references/activation-map.yaml`)
- **Web login / registration / auth UI** — CloudBase Web 登录, Web 注册, auth login page, publishable key, 短信登录, 邮箱登录
- **WeChat mini program + CloudBase** — 小程序 云开发, wx.cloud, mini program cloudbase, OPENID, 小程序数据库
- **Native App / Flutter / React Native** — Android CloudBase, iOS CloudBase, Flutter CloudBase, React Native CloudBase, 原生 App 接入
- **Web projects + NoSQL Database** — Web 文档数据库, CloudBase collection, 前端查库, NoSQL Web SDK
- **CloudBase PostgreSQL / PG** — CloudBase PG, PostgreSQL, Postgres, PG 模式, JS SDK v3 PostgreSQL, app.rdb(), queryPgDatabase, managePgDatabase, mysqldb OpenAPI, PostgREST, RLS, service_role, auth schema, storage schema, pgvector
- **MySQL Database (relational)** — MySQL 建表, executeWriteSQL, security rule, CloudBase 关系型数据库管理
- **Cloud Functions** — 创建云函数, HTTP 云函数, getFunctionLogs, scf_bootstrap, runtime
- **CloudRun backend** — CloudRun 部署, 云托管, container backend, Dockerfile
- **AI Agent (智能体开发)** — AI Agent, 智能体, 智能体开发, AG-UI protocol, LangGraph, LangChain, CrewAI, streaming agent, agent UI
- **Minimal Web BaaS demo (fast path)** — 最小前后端, 最小可用 demo, 最小 fullstack, 搭一套 demo, 带云数据库的 demo, 带云函数+云数据库, 留言板, Todo 应用, todo app, Notes app, Kanban, Lovable, BaaS demo, minimal web baas, 快速 demo
- **UI generation** — 设计页面, 登录页 UI, frontend interface, 组件样式, prototype
- **AI Model (Web)** — Web AI 对话, CloudBase AI 流式输出, Web 集成模型
- **AI model call (大模型调用 / 文本生成 / 图片生成 / 流式对话)** — 大模型调用, AI 模型调用, generateText, streamText, generateImage, 文本生成, 图片生成, 流式对话, hunyuan-exp, deepseek-v4-flash, Token Credits 资源包, 小程序成长计划, ai_miniprogram_inspire_plan, callCloudApi AI 模型, CreateAIModel
- **Resource health inspection / troubleshooting** — 巡检, 诊断, health check, 资源健康, 异常日志, error inspection, troubleshooting, 错误排查
- **Spec workflow / architecture design** — 需求文档, 技术方案, tasks.md, Spec 工作流
### Routing reminders
- Web auth failures: usually skipped provider config, not missing frontend snippets.
- Native App failures: usually Web SDK paths, not missing HTTP API knowledge.
- Mini program failures: treating `wx.cloud` like Web auth/SDK.
- CloudBase PG failures: falling back to MySQL/NoSQL, skipping username-password readiness, or guessing raw HTTP instead of `app.rdb()` / documented OpenAPI.
- AI model failures: usually missing Token Credits / Growth Plan — run `DescribeEnvPostpayPackage` / `DescribeActivityInfo` before changing code.
## MCP + CLI prerequisite
Prefer CloudBase MCP for management/deploy when tools are loaded in the current session. Setup: `references/mcp-setup.md`. First-session / unavailable path: `references/tooling-fallback.md`.
- **Preferred install:** `npx plugins add TencentCloudBase/cloudbase-plugin -y --scope user`. Supported `--target` IDs: `claude-code`, `cursor`, `codex`, `grok`, `kimi`, `github-copilot`, `vscode`. See `references/mcp-setup.md`.
- Verify with `npx mcporter list | grep cloudbase` or the IDE MCP panel. If `npm`/`npx` are missing, see `references/tooling-fallback.md` (install Node LTS or use IDE marketplace MCP). If MCP is missing or not yet visible after config, **still proceed**: finish install/config, tell the user a restart unlocks MCP next time, and use `tcb` CLI now via `cloudbase-cli` domain skills — **do not** recommend `tcb deploy`.
- Prefer device-code login via MCP `auth` when available; otherwise `tcb login`. Do not hard-code secrets.
## On-demand references
Load only when needed (do not expand this entry):
- `references/tooling-fallback.md` — MCP vs `tcb` CLI decision tree for first session / missing tools
- `references/deployment-workflow.md` — deploy backend/frontend, `manageApps` vs hosting, URL/docs updates
- `references/console-links.md` — console hash paths after creating resources
- `references/scenarios.md` — user-need → CloudBase capability mapping
- `references/mcp-setup.md` — Plugin install (global default + targets), IDE MCP / mcporter config and auth examples
- `references/activation-map.yaml` — canonical routing contract source
## Reference index
All packaged reference files (required for skill lint reachability):
- [activation-map.yaml](references/activation-map.yaml)
- [console-links.md](references/console-links.md)
- [deployment-workflow.md](references/deployment-workflow.md)
- [mcp-setup.md](references/mcp-setup.md)
- [scenarios.md](references/scenarios.md)
- [tooling-fallback.md](references/tooling-fallback.md)