Skill: 2513483494/arch-sla-cloudq (version 1.0.0)

--- name: cloudq-sre description: 腾讯云智能顾问助手 — 通过 AK/SK 鉴权调用腾讯云智能顾问 API，管理云架构图（查询目录、获取架构列表和详情、获取架构评估结果）及风险评估（查询评估项、按产品/分组/风险等级筛选），支持开通智能顾问服务。当用户要求查看云架构、查询架构目录、获取架构详情、查看架构评估结果、查看风险评估项、巡检项、云资源风险、开通智能顾问等相关操作时使用。 metadata: {"openclaw": {"emoji": "☁️", "requires": {"bins": ["python3"], "env": ["TENCENTCLOUD_SECRET_ID", "TENCENTCLOUD_SECRET_KEY"]}, "permissions": ["network:https://*.tencentcloudapi.com", "network:https://cloud.tencent.com", "fs:~/.tencent-cloudq/"], "security": {"iam_operations": ["cam:GetRole", "cam:CreateRole", "cam:AttachRolePolicy", "cam:DeleteRole", "cam:DescribeRoleList", "sts:AssumeRole", "sts:GetCallerIdentity", "advisor:CreateAdvisorAuthorization"], "iam_note": "角色创建/删除为独立步骤，需用户明确同意后执行：create_role.py 创建角色，cleanup.py --cloud 删除角色；check_env.py 仅做只读检测；CreateAdvisorAuthorization 开通智能顾问需用户明确同意", "data_handling": "临时凭证仅在内存中使用，不持久化存储；配置文件仅保存角色 ARN，不保存密钥"}}} --- # ☁️ 腾讯云智能顾问助手 核心能力：通过 **AK/SK 鉴权**调用腾讯云智能顾问（Tencent Cloud Smart Advisor）API，管理云架构图的目录与详情、获取架构评估结果，以及查询风险评估项。 --- ## 一、鉴权方式 使用腾讯云 API **AK/SK 签名认证**（TC3-HMAC-SHA256），通过环境变量配置密钥： ### 1.1 必填环境变量 - `TENCENTCLOUD_SECRET_ID` — 腾讯云 SecretId（必填） - `TENCENTCLOUD_SECRET_KEY` — 腾讯云 SecretKey（必填） 密钥获取地址：https://console.cloud.tencent.com/cam/capi **环境变量必须永久写入 shell 配置文件**，确保新会话中仍然生效： Linux / macOS（写入 `~/.bashrc` 或 `~/.zshrc`）： ```bash echo 'export TENCENTCLOUD_SECRET_ID="your-secret-id"' >> ~/.bashrc echo 'export TENCENTCLOUD_SECRET_KEY="your-secret-key"' >> ~/.bashrc source ~/.bashrc ``` Windows PowerShell（写入用户级环境变量）： ```powershell [Environment]::SetEnvironmentVariable("TENCENTCLOUD_SECRET_ID", "your-secret-id", "User") [Environment]::SetEnvironmentVariable("TENCENTCLOUD_SECRET_KEY", "your-secret-key", "User") ``` ### 1.2 角色配置（免密登录需要） 为生成控制台免密登录链接，需要配置 CAM 角色。角色配置分为 **检测** 和 **创建** 两个独立步骤，角色创建属于 IAM 写入操作，**必须在用户明确同意后才能执行**。 #### 步骤一：环境检测（只读） 运行环境自检脚本，检测依赖、版本更新、密钥、角色配置状态： ```bash python3 {baseDir}/check_env.py ``` 自检脚本 **仅做只读检测**，不会创建或修改任何资源。返回码含义： - `0` = 环境就绪（密钥 + 角色全部正常） - `1` = Python 版本不满足要求 - `2` = AK/SK 未配置或无效 - `3` = 角色未配置（需要执行步骤二） - `4` = Skill 版本过旧，请更新 Skill 脚本首次运行时会自动检查本地 `_meta.json` 中的版本号与远端最新版本是否一致，若发现新版本则提示更新并退出（返回码 `4`）。可通过 `--skip-update` 参数跳过版本检查。 #### 步骤二：角色创建（需用户同意） 当 `check_env.py` 返回码为 `3`（角色未配置）时，**必须**向用户展示角色创建方案并等待同意： **向用户说明以下内容**： 1. 将创建 CAM 角色 `advisor`，仅用于免密登录控制台查看智能顾问信息 2. 将关联策略 `QcloudAdvisorFullAccess`（智能顾问只读访问权限，不影响其他云资源） 3. 信任策略仅允许当前账号扮演此角色 4. 用户可随时在 CAM 控制台删除此角色 **用户同意后**，执行角色创建脚本： ```bash python3 {baseDir}/scripts/create_role.py ``` 脚本输出 JSON 格式结果，`success: true` 表示创建成功并已保存配置。 **用户拒绝时**，提供手动配置方式（方式二、三、四）。 #### 方式二：配置向导（交互式选择已有角色） 运行配置向导，从已有角色中选择，或交互式创建新角色： ```bash python3 {baseDir}/scripts/setup_role.py ``` #### 方式三：简化配置 只需提供角色名称，系统自动获取账号 UIN。将以下内容写入 shell 配置文件（如 `~/.bashrc` 或 `~/.zshrc`）： ```bash echo 'export TENCENTCLOUD_ROLE_NAME="advisor"' >> ~/.bashrc source ~/.bashrc ``` 系统会自动调用 API 获取您的账号 UIN，并拼接完整的 roleArn。 #### 方式四：完整配置（高级用户） 手动设置完整的角色 ARN，写入 shell 配置文件（如 `~/.bashrc` 或 `~/.zshrc`）： ```bash echo 'export TENCENTCLOUD_ROLE_ARN="qcs::cam::uin/100001234567:roleName/advisor"' >> ~/.bashrc source ~/.bashrc ``` ### 1.3 可选环境变量 - `TENCENTCLOUD_TOKEN` — 临时密钥 Token（使用临时密钥时设置） - `TENCENTCLOUD_ROLE_SESSION` — 角色会话名称（默认 `advisor-session`） - `TENCENTCLOUD_STS_DURATION` — 临时凭证有效期秒数（默认 `3600`，即 1 小时；最大 `43200`，即 12 小时） > **注意**：所有环境变量均需永久写入 shell 配置文件（如 `~/.bashrc`、`~/.zshrc`），`export` 仅对当前会话生效，新开会话会丢失。 ### 1.4 配置优先级 系统按以下优先级加载角色配置： 1. 环境变量 `TENCENTCLOUD_ROLE_ARN`（完整 ARN） 2. 配置文件 `~/.tencent-cloudq/config.json` 3. 环境变量 `TENCENTCLOUD_ROLE_NAME` + 自动获取账号 UIN --- ## 二、前置检查（初始化工作流） 每次操作前必须先执行环境检测。初始化分为 **版本检查**、**环境检测** 和 **角色创建** 三个阶段，角色创建属于 IAM 写入操作，必须在用户明确同意后才能执行。 ### 2.1 初始化工作流（必须严格按顺序执行） **第一步：运行环境检测** ```bash python3 {baseDir}/check_env.py ``` 脚本会依次执行以下检测： 1. 检查 Python 版本（需要 3.7+） 2. 检查 Skill 版本更新（读取本地 `_meta.json` 版本，与远端最新版本对比） 3. 检查 AK/SK 配置 4. 验证 AK/SK 有效性 5. 检查免密登录角色配置 6. 验证角色扮演 根据返回码判断状态： - `0` = 环境就绪，可以正常使用所有功能 - `1` = Python 版本不满足要求 → 提示用户升级 Python - `2` = AK/SK 未配置或无效 → 提示用户配置密钥 - `3` = 角色未配置 → 执行第二步 - `4` = Skill 版本过旧 → 提示用户更新 Skill **版本检查说明**：脚本首次运行时会自动对比本地版本与远端版本。若远端有新版本，脚本会输出当前版本和最新版本号，并以返回码 `4` 退出。网络不可用或远端接口异常时，版本检查会被跳过，不影响后续检测。可通过 `--skip-update` 参数主动跳过版本检查。 **第二步：向用户展示角色创建方案**（仅当返回码为 3 时） 向用户说明即将执行的 IAM 操作，**等待用户明确同意**： > 免密登录功能需要创建一个 CAM 角色，以下是创建方案： > - **角色名称**：`advisor` > - **关联策略**：`QcloudTAGFullAccess`（标签全读写权限）、`QcloudAdvisorFullAccess`（智能顾问全读写权限） > - **信任策略**：仅允许当前账号扮演此角色 > - **用途**：仅用于生成控制台免密登录链接，不影响其他云资源 > - 您可随时在 [CAM 控制台](https://console.cloud.tencent.com/cam/role) 删除此角色 > > 是否同意创建？ **第三步：执行角色创建**（仅在用户同意后） ```bash python3 {baseDir}/scripts/create_role.py ``` 脚本输出 JSON 格式结果，`success: true` 表示创建成功。 **第四步：再次运行环境检测，确认环境就绪** ```bash python3 {baseDir}/check_env.py ``` 返回码 `0` 表示初始化完成，所有功能可用。 ### 2.2 静默模式（供脚本内部调用） ```bash python3 {baseDir}/check_env.py --quiet ``` 静默模式下仅输出错误信息，适合其他脚本调用获取环境状态。角色未配置时返回码 `3`，**不会自动创建角色**。 ### 2.3 跳过版本检查 ```bash python3 {baseDir}/check_env.py --skip-update ``` 跳过远端版本对比，直接进行后续环境检测。适用于离线环境或已知无需更新的场景。可与 `--quiet` 组合使用。 --- ## 三、API 调用方式 所有接口通过统一的签名脚本调用，服务固定参数： - **service**: `advisor` - **host**: `advisor.tencentcloudapi.com` - **version**: `2020-07-21` ```bash python3 {baseDir}/scripts/tcloud_api.py advisor advisor.tencentcloudapi.com <Action> 2020-07-21 '<payload>' [region] ``` --- ## 四、可用接口（共 7 个） 所有接口频率限制均为 **20 次/秒**。使用某个接口前，**必须先加载对应的接口文档**获取参数、返回值和展示规则等详细信息。 | 接口 | 说明 | 必填参数 | 触发词 | 文档 | |------|------|----------|--------|------| | `DescribeArch` | 获取云架构详情 | `ArchId`、`Username` | 架构详情、查看架构图 | `{baseDir}/references/api/DescribeArch.md` | | `DescribeArchList` | 分页获取云架构列表 | `PageNumber`、`PageSize` | 架构列表、所有架构图 | `{baseDir}/references/api/DescribeArchList.md` | | `ListDirectoryV2` | 查询架构目录树 | 无 | 查询目录、目录列表 | `{baseDir}/references/api/ListDirectoryV2.md` | | `ListUnorganizedDirectory` | 查询待整理目录 | 无 | 待整理目录、未归类架构 | `{baseDir}/references/api/ListUnorganizedDirectory.md` | | `DescribeStrategies` | 获取智能顾问支持的风险评估项列表 | 无 | 风险评估、巡检项、云资源风险 | `{baseDir}/references/api/DescribeStrategies.md` | | `DescribeLastEvaluation` | 获取架构评估结果 | `ArchId` | 架构评估、评估结果、Well-Architected | `{baseDir}/references/api/DescribeLastEvaluation.md` | | `CreateAdvisorAuthorization` | 开启智能顾问授权 | 无 | 开通智能顾问、授权智能顾问 | `{baseDir}/references/api/CreateAdvisorAuthorization.md` | 调用示例： ```bash python3 {baseDir}/scripts/tcloud_api.py advisor advisor.tencentcloudapi.com DescribeArchList 2020-07-21 '{"PageNumber":1,"PageSize":10}' ``` --- ## 五、数据范围说明与空结果处理 ### 5.1 数据范围 所有查询接口返回的数据**仅限当前 AK/SK 对应账号**下的智能顾问数据。向用户展示查询结果时，**必须**明确告知： > 以下结果为当前 AK/SK（SecretId/SecretKey）对应账号下的智能顾问数据。如需查询其他账号的数据，请切换对应的 AK/SK。 ### 5.2 跨账号查询拦截 TSA **不支持查询其他账号（UIN）的数据**。当用户请求查询指定 UIN 的数据时，**不执行任何 API 调用**，直接返回提示。 **判断方式**：通过前置检查阶段 `check_env.py` 输出的账号信息，或调用 `GetCallerIdentity` 接口获取当前 AK/SK 对应的 UIN。 **拦截规则**： 1. 用户请求中**指定了 UIN**，且该 UIN **与当前 AK/SK 对应的 UIN 不一致** → 直接拦截，返回提示 2. 用户请求中**指定了 UIN**，且该 UIN **与当前 AK/SK 对应的 UIN 一致** → 正常执行查询 3. 用户请求中**未指定 UIN** → 正常执行查询（默认查询当前账号） **拦截时向用户返回**： > ⚠️ 智能顾问仅支持查询当前 AK/SK 对应账号的数据。当前账号 UIN 为 `{当前UIN}`，无法查询 UIN `{用户指定的UIN}` 的数据。 > > 如需查询其他账号的数据，请切换到该账号的 AK/SK。 ### 5.3 空结果处理 当查询接口返回空结果（如架构列表为空、评估项为空、目录为空等）时，向用户说明可能的原因： 1. **当前架构图没有对应的数据**：该账号下确实没有相关的架构图、评估项或目录数据 2. **未开通智能顾问服务**：当前账号可能尚未开通智能顾问，需要先开通才能使用 向用户展示： > 查询结果为空，可能原因： > 1. 当前 AK/SK 对应账号下没有相关数据 > 2. 当前账号可能尚未开通智能顾问服务 > > 如需开通智能顾问，请告知我，我将协助您完成开通（需要您的确认）。 ### 5.4 开通智能顾问服务 当用户确认需要开通智能顾问时，调用 `CreateAdvisorAuthorization` 接口。**此接口为写入操作，必须经过用户明确同意后才能执行。** **开通流程（严格按顺序执行）**： **第一步：向用户说明开通内容，等待用户明确同意** > 开通智能顾问服务将会： > - 为当前 AK/SK 对应的账号开通智能顾问服务 > - 同步开启**报告解读**和**云架构协作**权限 > - 开通后可使用架构图管理、风险评估等全部功能 > > 是否同意开通？ **第二步：用户同意后执行开通**（用户拒绝则不执行） 使用前**必须先加载接口文档**：`{baseDir}/references/api/CreateAdvisorAuthorization.md` ```bash python3 {baseDir}/scripts/tcloud_api.py advisor advisor.tencentcloudapi.com CreateAdvisorAuthorization 2020-07-21 '{}' ``` **第三步：告知用户结果并引导下一步操作** - **成功**： 1. 告知用户开通成功 2. 调用免密登录脚本生成智能顾问控制台链接： ```bash python3 {baseDir}/scripts/login_url.py "https://console.cloud.tencent.com/advisor" ``` 3. 向用户展示如下引导信息： > ✅ 智能顾问服务已成功开通！ > > 👉 [点击进入智能顾问控制台]({免密登录链接}) > > 接下来你可以： > - **手动画架构图**：在控制台中新建架构图，拖拽云产品组件绘制你的架构 > - **网络扫描自动生图**：通过网络拓扑扫描，自动生成当前账号下的云架构图 > > 架构图创建完成后，可以使用智能顾问进行架构治理和风险评估。 - **失败**：展示错误信息，建议用户检查账号权限或联系管理员 --- ## 六、免密登录链接生成 当接口返回结果中包含架构图（含 `ArchId` 字段）时，**必须**调用免密登录脚本为用户生成腾讯云控制台直达链接。架构图列表场景下，只需为**第一张架构图**生成免密链接。 > **⚠️ 重要**：免密登录链接**每次都必须重新生成**，不可缓存或复用之前生成的链接。 ### 6.1 调用方式 需先完成角色配置（见「二、前置检查」），然后调用： ```bash python3 {baseDir}/scripts/login_url.py "<目标页面URL>" ``` 架构图控制台 URL 格式：`https://console.cloud.tencent.com/advisor?archId={ArchId}` ### 6.2 返回示例 ```json { "success": true, "action": "GenerateLoginURL", "data": { "loginUrl": "https://cloud.tencent.com/login/roleAccessCallback?algorithm=sha256&secretId=...&[REDACTED_SECRET]", "targetUrl": "https://console.cloud.tencent.com/advisor?archId=arch-gvqocc25", "expireSeconds": 3600 }, "requestId": "xxx" } ``` | 字段 | 说明 | |------|------| | `loginUrl` | 免密登录完整 URL，用户点击可直接跳转控制台 | | `targetUrl` | 登录后跳转的目标页面 | | `expireSeconds` | 链接有效期（秒），默认 3600，可通过 `TENCENTCLOUD_STS_DURATION` 调整 | ### 6.3 展示规则 免密登录 URL 非常长，**严禁直接展示完整 URL**，必须以 Markdown 超链接格式展示： ``` 架构图名称：生产环境架构 架构图 ID：arch-gvqocc25 [跳转控制台](免密登录URL) ``` 列表场景（仅第一张架构图附带免密链接）： ``` 1. 生产环境架构（arch-abc123）— [跳转控制台](免密登录URL) 2. 测试环境架构（arch-def456） 3. 预发布环境架构（arch-ghi789） ``` --- ## 七、插件扩展 TSA 支持通过插件机制扩展功能。所有插件 Skill 统一存放在 `{baseDir}/references/plugins/` 目录下。 ### 7.1 插件目录结构 ``` references/ ├── api/ # API 接口文档 │ ├── DescribeArch.md │ └── ... └── plugins/ # 插件 Skill 目录 └── <plugin-name>/ # 每个插件一个子目录 ├── SKILL.md # 插件的 Skill 指令文档 └── ... # 插件所需的其他文件 ``` ### 7.2 插件加载机制 插件 Skill 按需加载。当用户请求涉及某个插件的能力时，AI 助手会自动加载 `{baseDir}/references/plugins/<plugin-name>/SKILL.md` 获取插件的详细指令。 ### 7.3 插件开发规范 - 每个插件在 `references/plugins/` 下创建独立的子目录 - 插件必须包含 `SKILL.md` 文件，描述插件的功能、调用方式和输出格式 - 插件可复用主 Skill 的脚本（如 `scripts/tcloud_api.py`），也可包含自己的脚本 - 插件的输出格式应与主 Skill 保持一致（统一 JSON 格式） --- ## 八、统一输出格式 所有接口调用的输出均为统一的 JSON 格式，通过 `success` 字段区分成功与失败。 ### 成功响应 ```json { "success": true, "action": "DescribeArchList", "data": { ... }, "requestId": "9cbe807c-..." } ``` | 字段 | 类型 | 说明 | |------|------|------| | `success` | Boolean | 固定为 `true` | | `action` | String | 调用的接口名称 | | `data` | Object | 接口返回的业务数据（已去除 RequestId） | | `requestId` | String | 腾讯云请求 ID，用于问题排查 | ### 失败响应 ```json { "success": false, "action": "DescribeArchList", "error": { "code": "AuthFailure.SecretIdNotFound", "message": "The SecretId is not found, please ensure that your SecretId is correct." }, "requestId": "ed93f3cb-..." } ``` | 字段 | 类型 | 说明 | |------|------|------| | `success` | Boolean | 固定为 `false` | | `action` | String | 调用的接口名称 | | `error.code` | String | 错误码 | | `error.message` | String | 错误描述 | | `requestId` | String | 腾讯云请求 ID（网络错误时为空） | ### 特殊错误码（脚本层面） | 错误码 | 含义 | |--------|------| | `MissingParameter` | 脚本调用缺少必要参数 | | `MissingCredentials` | 未配置 AK/SK 环境变量 | | `NetworkError` | 网络请求失败，无法连接 API | | `ParseError` | 响应不是有效的 JSON | ### 常见 API 错误码 | 错误码 | 含义 | 解决方案 | |--------|------|----------| | `AuthFailure.SecretIdNotFound` | SecretId 不存在 | 检查 TENCENTCLOUD_SECRET_ID | | `AuthFailure.SignatureFailure` | 签名错误 | 检查 TENCENTCLOUD_SECRET_KEY | | `AuthFailure.SignatureExpire` | 签名过期 | 检查本地时间是否准确 | | `AuthFailure.TokenFailure` | Token 错误 | 检查 TENCENTCLOUD_TOKEN | | `AuthFailure.UnauthorizedOperation` | 未授权 | 检查 CAM 策略 | | `ResourceNotFound` | 资源不存在 | 检查 ArchId 等参数是否正确 | | `InvalidParameter` | 参数错误 | 检查请求参数格式和类型 | | `InvalidParameterValue` | 参数取值错误 | 检查参数值范围 | | `RequestLimitExceeded` | 频率限制 | 降低调用频率（限 20 次/秒） | --- ## 九、注意事项 1. **密钥安全**：严禁将 AK/SK 硬编码在代码中，必须通过环境变量传入 2. **权限控制**：建议使用子账号密钥，角色关联 `QcloudTAGFullAccess` 和 `QcloudAdvisorFullAccess` 策略 3. **临时密钥**：生产环境推荐使用 STS 临时密钥，设置 `TENCENTCLOUD_TOKEN` 4. **频率限制**：所有接口限制 20 次/秒（维度：API + 接入地域 + 子账号） 5. **地域选择**：默认 `ap-guangzhou`，Region 为可选参数 6. **跨平台支持**：所有脚本均使用纯 Python 实现，支持 Windows / Linux / macOS，无需 curl、openssl、jq 等外部依赖 7. **免密链接有效期**：默认 1 小时（3600 秒），可通过 `TENCENTCLOUD_STS_DURATION` 调整（最大 43200 秒，即 12 小时） 8. **架构图免密链接**：当返回结果包含架构图时，只需为**第一张架构图**生成免密登录控制台链接，并以 `[跳转控制台](免密登录URL)` 超链接形式展示，严禁直接展示完整 URL。**每次展示都必须重新调用 `login_url.py` 生成新链接，不可缓存或复用之前生成的链接** 9. **评估项控制台链接**：评估项不需要免密登录链接，直接展示控制台 URL：`https://console.cloud.tencent.com/advisor/assess?strategyName={URL编码后的Name}` 10. **按需加载接口文档**：使用某个接口前，必须先通过 `read_file` 加载 `{baseDir}/references/api/<Action>.md` 获取完整的参数说明、返回字段、展示格式等详细信息 11. **数据范围提示**：所有查询结果仅限当前 AK/SK 对应账号的数据，展示结果时**必须**告知用户数据范围 12. **空结果引导**：查询返回空结果时，提示用户可能原因（无数据或未开通智能顾问），并询问是否需要开通 13. **开通智能顾问须用户同意**：调用 `CreateAdvisorAuthorization` 接口属于写入操作，**必须**经用户明确同意后才能执行，严禁自动调用 14. **跨账号查询拦截**：用户指定其他 UIN 查询时，**不执行 API 调用**，直接告知仅支持查询当前 AK/SK 对应的 UIN（需标明具体 UIN），并提示切换 AK/SK --- ## 十、安全与权限声明 ### 10.1 所需凭证 本 Skill 需要以下环境变量才能正常运行： | 环境变量 | 必填 | 说明 | |---------|------|------| | `TENCENTCLOUD_SECRET_ID` | **是** | 腾讯云 API SecretId | | `TENCENTCLOUD_SECRET_KEY` | **是** | 腾讯云 API SecretKey | 密钥仅通过环境变量读取，**不会**被写入文件、日志或网络传输中。 ### 10.2 IAM 操作声明 本 Skill 包含以下 CAM（访问管理）操作。**写入类操作仅由独立脚本 `scripts/create_role.py` 执行，且必须在用户明确同意后才会运行**。`check_env.py` 仅执行只读检测操作。 | API 操作 | 类型 | 所在脚本 | 说明 | |---------|------|---------|------| | `sts:GetCallerIdentity` | 只读 | `check_env.py` / `create_role.py` | 获取当前账号 UIN | | `cam:GetRole` | 只读 | `check_env.py` / `create_role.py` | 检查角色是否存在 | | `cam:DescribeRoleList` | 只读 | `setup_role.py` | 列出可用角色供用户选择 | | `cam:CreateRole` | **写入** | `scripts/create_role.py` | 创建 `advisor` 角色（需用户明确同意后执行） | | `cam:AttachRolePolicy` | **写入** | `scripts/create_role.py` | 关联 `QcloudAdvisorFullAccess` 策略（随角色创建执行） | | `sts:AssumeRole` | 敏感 | `login_url.py` | 扮演角色获取临时凭证（用于生成免密登录链接） | | `cam:DeleteRole` | **写入** | `scripts/cleanup.py` | 删除 `advisor` 角色（仅 `--cloud` 模式，需用户明确确认） | | `advisor:CreateAdvisorAuthorization` | **写入** | `scripts/tcloud_api.py` | 开通智能顾问服务（需用户明确同意后执行） | ### 10.3 数据安全 - **临时凭证**：STS AssumeRole 获取的临时凭证仅在内存中使用，不持久化存储 - **配置文件**：`~/.tencent-cloudq/config.json` 仅保存角色 ARN 和账号 UIN，**不保存任何密钥** - **文件权限**：配置目录设为 `700`，配置文件设为 `600`，仅当前用户可读写 - **SSL 验证**：所有 HTTPS 请求均启用完整的 SSL 证书验证，不支持跳过验证 - **网络访问**：仅连接腾讯云官方 API 域名（`*.tencentcloudapi.com`）和登录域名（`cloud.tencent.com`） ### 10.4 配置清理 用户可随时运行清理脚本删除本机上的所有配置和缓存： ```bash # 交互式清理（逐项确认） python3 {baseDir}/scripts/cleanup.py # 一键清理所有本地配置 python3 {baseDir}/scripts/cleanup.py --all # 一键清理所有本地配置 + 云端 advisor 角色 python3 {baseDir}/scripts/cleanup.py --all --cloud ``` 清理范围： - 配置目录 `~/.tencent-cloudq/`（含 `config.json`） - 临时缓存 `{系统临时目录}/.tcloud_advisor_uin_cache` - 环境变量 `TENCENTCLOUD_*` 系列（`SECRET_ID`、`SECRET_KEY`、`TOKEN`、`ROLE_ARN`、`ROLE_NAME`、`ROLE_SESSION`、`STS_DURATION`），脚本会自动检测已设置的变量并生成对应平台的清理命令（`source` 脚本 / PowerShell 脚本） - 云端 CAM 角色 `advisor`（仅 `--cloud` 模式，需配置 AK/SK）

path: _meta.json
content: ownerId: kn7em5btzdk0xmgqmng5g51wmh83746e
slug: cloudq-sre
version: 1.4.0
publishedAt: 1774179044589

path: README.md
content: # ☁️ TSA — 腾讯云智能顾问助手

> Tencent Cloud Smart Advisor (TSA) Skill for CodeBuddy / OpenClaw

通过 **AK/SK 鉴权**调用腾讯云智能顾问 API，以对话式交互管理云架构图、查询风险评估项、生成控制台免密登录链接。

## 功能概览

- **云架构管理** — 查询目录树、架构列表、架构详情
- **风险评估** — 获取评估项列表，支持按产品/分组/风险等级筛选
- **架构评估** — 获取 Well-Architected 评估结果（总分、各维度得分、治理建议）
- **免密登录** — 自动生成腾讯云控制台直达链接，点击即可免登录跳转
- **环境自检** — 一键检测 Python 版本、密钥配置、角色状态、Skill 版本更新

## 快速开始

### 1. 配置腾讯云密钥

在 [API 密钥管理](https://console.cloud.tencent.com/cam/capi) 获取密钥，写入 shell 配置文件：

```bash
echo 'export TENCENTCLOUD_SECRET_ID="your-secret-id"' >> ~/.zshrc
echo 'export TENCENTCLOUD_SECRET_KEY="your-secret-key"' >> ~/.zshrc
source ~/.zshrc
```

### 2. 安装 Skill

**通过 ClawHub 安装（推荐）：**

在 CodeBuddy 或 OpenClaw 的 Skill 市场搜索 `tsa` 并安装。

**手动安装：**

```bash
git clone [REDACTED_SECRET]:tsa/tsa-skills.git ~/.codebuddy/skills/tsa
```

### 3. 开始使用

在 CodeBuddy 中直接对话即可：

```
> 帮我检查一下腾讯云智能顾问的环境是否配置好了
> 查看我的云架构目录
> 列出所有架构图
> 查看风险评估项
> 帮我查看待整理目录下有哪些架构图
```

## 项目结构

```
tsa/
├── SKILL.md                  # Skill 指令文档（AI 加载的核心文件）
├── _meta.json                # 元信息（slug、版本号）
├── check_env.py              # 环境检测脚本（只读，不修改任何配置）
├── scripts/
│   ├── tcloud_api.py         # 腾讯云 API 统一调用（TC3-HMAC-SHA256 签名）
│   ├── create_role.py        # CAM 角色创建（需用户明确同意）
│   ├── setup_role.py         # 角色配置向导（交互式）
│   ├── login_url.py          # 免密登录链接生成
│   └── cleanup.py            # 配置清理（本地 + 云端）
├── references/               # API 接口文档（6 个接口）
│   ├── DescribeArch.md
│   ├── DescribeArchList.md
│   ├── DescribeLastEvaluation.md
│   ├── DescribeStrategies.md
│   ├── ListDirectoryV2.md
│   └── ListUnorganizedDirectory.md
├── tests/
│   ├── run.sh                # 测试框架（离线 + E2E）
│   └── cases/                # E2E 测试用例
├── publish.sh                # ClawHub 发布脚本
├── CONTRIBUTING.md           # 贡献指南
└── README.md
```

## API 接口

共 6 个接口，统一通过 `scripts/tcloud_api.py` 调用，服务域名 `advisor.tencentcloudapi.com`。

| 接口 | 说明 | 详细文档 |
|------|------|----------|
| `DescribeArchList` | 分页获取云架构图列表 | `references/api/DescribeArchList.md` |
| `DescribeArch` | 获取指定架构图详情 | `references/api/DescribeArch.md` |
| `ListDirectoryV2` | 查询架构目录树 | `references/api/ListDirectoryV2.md` |
| `ListUnorganizedDirectory` | 查询待整理目录 | `references/api/ListUnorganizedDirectory.md` |
| `DescribeStrategies` | 获取风险评估项列表 | `references/api/DescribeStrategies.md` |
| `DescribeLastEvaluation` | 获取架构评估结果 | `references/api/DescribeLastEvaluation.md` |

调用示例：

```bash
python3 scripts/tcloud_api.py advisor advisor.tencentcloudapi.com DescribeArchList 2020-07-21 '{"PageNumber":1,"PageSize":10}'
```

## 测试

测试框架包含 **离线测试**（39 项）和 **E2E 对话式测试**（7 个用例），共 56 项断言。

### 前置条件

- Python 3.7+
- CodeBuddy CLI（`codebuddy`）或 OpenClaw CLI（`openclaw`）
- 环境变量 `TENCENTCLOUD_SECRET_ID` 和 `TENCENTCLOUD_SECRET_KEY` 已配置

### 运行测试

```bash
# 运行全部测试（离线 + E2E）
bash tests/run.sh

# 仅运行离线测试（不需要 CLI 和网络）
bash tests/run.sh --offline

# 运行指定 E2E 用例
bash tests/run.sh --case 01

# 指定 CLI 工具
bash tests/run.sh --cli codebuddy
bash tests/run.sh --cli openclaw
```

### E2E 用例

| 用例 | 说明 |
|------|------|
| `01_env_check` | 环境检测 |
| `02_list_directory` | 查询架构目录 |
| `03_list_arch` | 查询架构列表 |
| `04_strategies` | 查询风险评估项 |
| `05_unorganized` | 查询待整理目录 |
| `06_login_url` | 免密登录链接生成 |
| `07_json_format` | API 输出 JSON 格式校验 |

E2E 测试会自动将每个用例的输入（prompt）和输出保存到 `tests/output/` 目录。

## 发布

通过 `publish.sh` 将 Skill 发布到 ClawHub 平台：

```bash
# 设置 ClawHub Token
export CLAWHUB_TOKEN="your-api-token"

# 发布（需提供 changelog）
./publish.sh "修复版本检查逻辑"

# 或直接传入 token
./publish.sh "修复版本检查逻辑" "your-api-token"
```

发布时会自动排除 `tests/`、`__pycache__/`、`.git/`、`publish.sh`、`CONTRIBUTING.md` 等非发布文件。版本号从 `_meta.json` 中读取。

## 安全说明

- **密钥安全** — AK/SK 仅通过环境变量读取，不会写入文件、日志或网络传输
- **角色创建** — CAM 角色创建为独立脚本，必须用户明确同意后才会执行
- **临时凭证** — STS 临时凭证仅在内存中使用，不持久化存储
- **配置文件** — `~/.tencent-cloudq/config.json` 仅保存角色 ARN，不保存密钥
- **网络访问** — 仅连接腾讯云官方域名（`*.tencentcloudapi.com`、`cloud.tencent.com`）

## 许可证

内部项目，仅限腾讯内部使用。


path: check_env.py
content: #!/usr/bin/env python3
"""
腾讯云智能顾问环境检测脚本（只读，不修改任何配置）

功能：检测 Python 版本、Skill 版本更新、密钥、角色配置状态，输出检测结果
注意：本脚本仅做检测，不会创建角色或修改任何配置（发现已有角色时保存配置除外）

用法:
    python3 check_env.py           # 标准模式：输出详细检测结果
    python3 check_env.py --quiet   # 静默模式：仅输出错误信息（供其他脚本调用）
    python3 check_env.py --skip-update  # 跳过版本更新检查

返回码:
    0 - 环境就绪（密钥 + 角色全部正常）
    1 - Python 版本不满足
    2 - AK/SK 未配置或无效
    3 - 角色未配置（需要执行角色创建步骤）
    4 - Skill 版本过旧，需要更新

跨平台支持: Windows / Linux / macOS
"""

import json
import os
import platform
import stat
import sys
import urllib.request
import urllib.error
from datetime import datetime, timezone
from pathlib import Path

# 将 scripts 目录加入搜索路径
SCRIPT_DIR = Path(__file__).resolve().parent
SCRIPTS_DIR = SCRIPT_DIR / "scripts"
sys.path.insert(0, str(SCRIPTS_DIR))

from tcloud_api import call_api  # noqa: E402


# ============== 配置 ==============
CONFIG_DIR = Path.home() / ".tencent-cloudq"
CONFIG_FILE = CONFIG_DIR / "config.json"
ADVISOR_ROLE_NAME = "advisor"

# 版本检查配置
META_FILE = SCRIPT_DIR / "_meta.json"
VERSION_CHECK_TIMEOUT = 15  # 秒


# ============== 输出函数 ==============
QUIET_MODE = "--quiet" in sys.argv
SKIP_UPDATE = "--skip-update" in sys.argv


def log_info(msg: str):
    if not QUIET_MODE:
        print(msg)


def log_ok(msg: str):
    if not QUIET_MODE:
        print(f"  [OK] {msg}")


def log_warn(msg: str):
    if not QUIET_MODE:
        print(f"  [WARN] {msg}")


def log_fail(msg: str):
    print(f"  [FAIL] {msg}")


def log_section(title: str):
    if not QUIET_MODE:
        print(f"\n=== {title} ===")


def save_config(account_uin: str, role_name: str, role_arn: str,
                auto_created: bool = False, role_id: str = ""):
    """保存配置文件（跨平台兼容）"""
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)

    # 设置目录权限（非 Windows）
    if platform.system() != "Windows":
        try:
            os.chmod(str(CONFIG_DIR), stat.S_IRWXU)  # 700
        except OSError:
            pass

    config = {
        "accountUin": account_uin,
        "roleName": role_name,
        "roleArn": role_arn,
        "configuredAt": datetime.now(timezone.utc).isoformat(),
        "autoCreated": auto_created,
        "version": "1.0",
    }
    if role_id:
        config["roleId"] = role_id

    CONFIG_FILE.write_text(json.dumps(config, indent=2, ensure_ascii=False), encoding="utf-8")

    # 设置文件权限（非 Windows）
    if platform.system() != "Windows":
        try:
            os.chmod(str(CONFIG_FILE), stat.S_IRUSR | stat.S_IWUSR)  # 600
        except OSError:
            pass


def parse_version(version_str: str) -> tuple:
    """解析语义化版本号字符串为可比较的元组，如 '1.3.0' -> (1, 3, 0)"""
    try:
        parts = version_str.strip().lstrip("v").split(".")
        return tuple(int(p) for p in parts)
    except (ValueError, AttributeError):
        return (0, 0, 0)


def get_local_version() -> tuple:
    """读取本地 _meta.json 中的版本号，返回 (slug, version_str) 或 (None, None)"""
    if not META_FILE.exists():
        return None, None
    try:
        meta = json.loads(META_FILE.read_text(encoding="utf-8"))
        return meta.get("slug"), meta.get("version")
    except (json.JSONDecodeError, IOError):
        return None, None


def get_remote_version(slug: str) -> str | None:
    """
    从 ClawHub registry 查询指定 slug 的最新版本号，返回版本字符串或 None。

    使用 `clawhub inspect <slug> --json` 命令获取远端版本信息。
    """
    import subprocess
    try:
        result = subprocess.run(
            ["clawhub", "inspect", slug, "--json"],
            capture_output=True, text=True, timeout=VERSION_CHECK_TIMEOUT,
        )
        if result.returncode != 0:
            return None
        data = json.loads(result.stdout)
        return data.get("latestVersion", {}).get("version")
    except (subprocess.TimeoutExpired, json.JSONDecodeError, OSError, KeyError):
        return None


def check_version_update() -> dict:
    """
    检查本地版本与远端版本是否一致。

    返回 dict:
      - status: "up_to_date" | "update_available" | "check_failed" | "no_meta"
      - local_version: 本地版本号（str）
      - remote_version: 远端版本号（str 或 None）
      - slug: skill 标识符
      - message: 可读的状态说明
    """
    slug, local_ver = get_local_version()
    if not slug or not local_ver:
        return {
            "status": "no_meta",
            "local_version": None,
            "remote_version": None,
            "slug": slug,
            "message": "未找到 _meta.json 或版本信息缺失",
        }

    remote_ver = get_remote_version(slug)
    if remote_ver is None:
        return {
            "status": "check_failed",
            "local_version": local_ver,
            "remote_version": None,
            "slug": slug,
            "message": "无法获取远端版本信息（网络问题或接口不可用）",
        }

    local_parsed = parse_version(local_ver)
    remote_parsed = parse_version(remote_ver)

    if remote_parsed > local_parsed:
        return {
            "status": "update_available",
            "local_version": local_ver,
            "remote_version": remote_ver,
            "slug": slug,
            "message": f"发现新版本: {local_ver} → {remote_ver}",
        }
    else:
        return {
            "status": "up_to_date",
            "local_version": local_ver,
            "remote_version": remote_ver,
            "slug": slug,
            "message": f"当前已是最新版本: {local_ver}",
        }


def main():
    # ============== 1. 检查 Python 版本 ==============
    log_section("1. 检查运行环境")

    py_ver = sys.version_info
    if py_ver < (3, 7):
        log_fail(f"Python 版本过低: {sys.version}，需要 Python 3.7+")
        sys.exit(1)

    log_ok(f"Python {py_ver.major}.{py_ver.minor}.{py_ver.micro} ({platform.system()} {platform.machine()})")

    # ============== 2. 检查 Skill 版本更新 ==============
    log_section("2. 检查 Skill 版本")

    if SKIP_UPDATE:
        log_ok("已跳过版本更新检查（--skip-update）")
    else:
        ver_result = check_version_update()
        status = ver_result["status"]

        if status == "up_to_date":
            log_ok(ver_result["message"])
        elif status == "update_available":
            log_warn(ver_result["message"])
            log_info("")
            log_info(f"  当前版本: {ver_result['local_version']}")
            log_info(f"  最新版本: {ver_result['remote_version']}")
            log_info("")
            log_info("  请前往 SkillHub 或 ClawHub 更新此 Skill 以获取最新功能和修复")
            log_info("")
            log_fail(f"Skill 版本过旧（{ver_result['local_version']} < {ver_result['remote_version']}），请先更新后再使用")
            sys.exit(4)
        elif status == "check_failed":
            log_warn(ver_result["message"])
            log_info("  版本检查跳过，继续后续检测...")
        elif status == "no_meta":
            log_warn(ver_result["message"])
            log_info("  版本检查跳过，继续后续检测...")

    # ============== 3. 检查 AK/SK 配置 ==============
    log_section("3. 检查 AK/SK 配置")

    secret_id = os.environ.get("TENCENTCLOUD_SECRET_ID", "")
    secret_key = os.environ.get("TENCENTCLOUD_SECRET_KEY", "")

    if not secret_id or not secret_key:
        missing = []
        if not secret_id:
            missing.append("TENCENTCLOUD_SECRET_ID")
        if not secret_key:
            missing.append("TENCENTCLOUD_SECRET_KEY")
        log_fail(f"未配置以下环境变量: {', '.join(missing)}")
        log_info("")
        log_info("  请将腾讯云 API 密钥永久写入 shell 配置文件：")
        log_info("")
        log_info("  Linux / macOS（写入 ~/.bashrc 或 ~/.zshrc）:")
        log_info('    echo \'export TENCENTCLOUD_SECRET_ID="your-secret-id"\' >> ~/.bashrc')
        log_info('    echo \'export TENCENTCLOUD_SECRET_KEY="your-secret-key"\' >> ~/.bashrc')
        log_info("    source ~/.bashrc")
        log_info("")
        log_info("  Windows PowerShell（写入用户级环境变量）:")
        log_info('    [Environment]::SetEnvironmentVariable("TENCENTCLOUD_SECRET_ID", "your-secret-id", "User")')
        log_info('    [Environment]::SetEnvironmentVariable("TENCENTCLOUD_SECRET_KEY", "your-secret-key", "User")')
        log_info("")
        log_info("  密钥获取地址: https://console.cloud.tencent.com/cam/capi")
        sys.exit(2)

    masked_id = f"{secret_id[:4]}****{secret_id[-4:]}" if len(secret_id) > 8 else "****"
    log_ok(f"SecretId 已配置: {masked_id}")
    log_ok("SecretKey 已配置: ****")

    [REDACTED_SECRET]"TENCENTCLOUD_TOKEN", "")
    if [REDACTED_SECRET]"临时密钥 Token 已配置")

    # ============== 4. 验证 AK/SK 有效性 ==============
    log_section("4. 验证 AK/SK 有效性")

    verify_result = call_api(
        "advisor", "advisor.tencentcloudapi.com",
        "DescribeArchList", "2020-07-21",
        {"PageNumber": 1, "PageSize": 1},
        "ap-guangzhou",
    )

    if verify_result.get("success"):
        log_ok("AK/SK 验证通过，接口调用成功")
    else:
        error_code = verify_result.get("error", {}).get("code", "Unknown")
        auth_failures = [
            "AuthFailure.SecretIdNotFound",
            "AuthFailure.SignatureFailure",
            "AuthFailure.InvalidSecretId",
        ]
        if error_code in auth_failures:
            log_fail(f"AK/SK 无效: {error_code}")
            log_info("  请检查密钥是否正确: https://console.cloud.tencent.com/cam/capi")
            sys.exit(2)
        elif error_code in ("NetworkError", "HTTPError"):
            log_fail("接口调用失败，请检查网络连接")
            sys.exit(1)
        else:
            log_ok("AK/SK 验证通过（鉴权成功）")
            if not QUIET_MODE:
                log_warn(f"接口返回业务错误: {error_code}（不影响鉴权）")

    # ============== 5. 检查角色配置状态（仅检测，不创建） ==============
    log_section("5. 检查免密登录角色配置")

    role_arn = os.environ.get("TENCENTCLOUD_ROLE_ARN", "")
    role_configured = False

    # 5.1 优先检查环境变量
    if role_arn:
        log_ok("ROLE_ARN 已通过环境变量配置")
        role_configured = True

    # 5.2 检查配置文件
    if not role_configured and CONFIG_FILE.exists():
        try:
            config = json.loads(CONFIG_FILE.read_text(encoding="utf-8"))
            saved_arn = config.get("roleArn", "")
            saved_role = config.get("roleName", "")
            if saved_arn:
                log_ok(f"角色已配置（来自配置文件）: {saved_role}")
                role_configured = True
        except (json.JSONDecodeError, IOError):
            pass

    # 5.3 检查 ROLE_NAME 环境变量
    if not role_configured:
        role_name_env = os.environ.get("TENCENTCLOUD_ROLE_NAME", "")
        if role_name_env:
            log_ok(f"ROLE_NAME 已配置: {role_name_env}")
            role_configured = True

    # 5.4 角色未配置 → 检测账号下是否存在可用角色
    if not role_configured:
        log_warn("免密登录角色未配置")
        log_info("")

        # 获取账号 UIN
        uin_result = call_api(
            "sts", "sts.tencentcloudapi.com",
            "GetCallerIdentity", "2018-08-13", {},
        )
        account_uin = str(uin_result.get("data", {}).get("AccountId", ""))

        if not account_uin or account_uin == "None":
            log_fail("无法获取账号 UIN")
            log_info("  需要创建角色用于免密登录，请先检查网络和 AK/SK 权限")
            sys.exit(3)

        log_info(f"  账号 UIN: {account_uin}")

        # 检查 advisor 角色是否已存在
        log_info(f"  检查 {ADVISOR_ROLE_NAME} 角色是否存在...")
        role_check = call_api(
            "cam", "cam.tencentcloudapi.com",
            "GetRole", "2019-01-16",
            {"RoleName": ADVISOR_ROLE_NAME},
        )

        if role_check.get("success"):
            console_login = role_check.get("data", {}).get("ConsoleLogin", 0)
            if console_login == 1:
                # 角色存在且支持控制台登录 → 保存配置
                log_ok(f"检测到已有角色 {ADVISOR_ROLE_NAME}（支持控制台登录），自动配置")
                computed_arn = f"qcs::cam::uin/{account_uin}:roleName/{ADVISOR_ROLE_NAME}"
                role_id = str(role_check.get("data", {}).get("RoleId", ""))
                save_config(account_uin, ADVISOR_ROLE_NAME, computed_arn,
                            auto_created=False, role_id=role_id)
                log_ok(f"配置已保存到 {CONFIG_FILE}")
                role_configured = True
            else:
                log_warn(f"角色 {ADVISOR_ROLE_NAME} 存在但不支持控制台登录")
                log_info("  需要创建一个支持控制台登录的角色用于免密登录链接获取")
                log_info(f"  请运行角色创建脚本: python3 {SCRIPT_DIR}/scripts/create_role.py")
                sys.exit(3)
        else:
            # 角色不存在 → 提示需要创建
            log_warn(f"未检测到 {ADVISOR_ROLE_NAME} 角色")
            log_info("")
            log_info("  免密登录功能需要一个 CAM 角色，请执行角色创建步骤：")
            log_info(f"  python3 {SCRIPT_DIR}/scripts/create_role.py")
            sys.exit(3)

    # ============== 6. 验证角色扮演（仅角色已配置时） ==============
    if role_configured:
        log_section("6. 验证角色扮演")

        # 调用 login_url.py 中的逻辑验证
        try:
            scripts_dir = SCRIPT_DIR / "scripts"
            login_url_path = scripts_dir / "login_url.py"
            if not login_url_path.exists():
                login_url_path = SCRIPT_DIR / "login_url.py"

            import subprocess
            test_result = subprocess.run(
                [sys.executable, str(login_url_path),
                 "https://console.cloud.tencent.com/advisor"],
                capture_output=True, text=True, timeout=30,
            )
            try:
                result_data = json.loads(test_result.stdout)
                if result_data.get("success"):
                    log_ok("角色扮演验证通过，免密登录功能正常")
                else:
                    err_msg = result_data.get("error", {}).get("message", "未知错误")
                    log_warn(f"角色扮演验证失败: {err_msg}")
                    log_info("  免密登录功能可能需要等待角色生效（通常几秒内）")
            except json.JSONDecodeError:
                log_warn("角色扮演验证返回格式异常")
        except Exception as e:
            log_warn(f"角色扮演验证异常: {e}")

    # ============== 检测完成 ==============
    log_info("")
    log_info("=== 检测完成 ===")
    if role_configured:
        log_ok("环境就绪，所有功能可用（API 查询 + 免密登录）")
        log_info("")
        log_info(f"  [OK] Python {py_ver.major}.{py_ver.minor} ({platform.system()})")
        log_info("  [OK] AK/SK 密钥验证通过")
        log_info("  [OK] 免密登录角色已配置")
        sys.exit(0)
    else:
        log_fail("环境检测未通过：免密登录角色未配置")
        log_info("")
        log_info("  请执行角色创建步骤完成初始化")
        sys.exit(3)


if __name__ == "__main__":
    main()


path: references/api/DescribeArchList.md
content: # DescribeArchList — 获取云架构列表

分页获取云架构图列表，支持按名称、ID、关键词、文件夹、共享状态等筛选。

## 参数

| 参数 | 必选 | 类型 | 描述 |
|------|------|------|------|
| PageNumber | 是 | Integer | 页码，>0 |
| PageSize | 是 | Integer | 每页数目，>0 且 ≤100 |
| ArchId | 否 | String | 按架构图 ID 搜索 |
| ArchName | 否 | String | 按架构图名称搜索 |
| SearchKey | 否 | String | 按关键词搜索 |
| FolderId | 否 | Integer | 文件夹 ID，不传则全局搜索 |
| ShareStatus | 否 | String | 按共享状态过滤，`"true"` 或 `"false"` |
| UpdateUser | 否 | String | 按更新人过滤 |
| WithoutSvg | 否 | Boolean | 不返回 SVG 数据，默认 `false` |
| WithSvgURL | 否 | Boolean | 返回 SVG 的临时 COS URL，默认 `false` |
| OrderBy | 否 | String | 排序：`CREATE_TIME`（默认）/ `UPDATE_TIME` / `VISIT_TIME` |

## 调用示例

```bash
# 获取第一页，每页10条
python3 {baseDir}/scripts/tcloud_api.py \
  advisor \
  advisor.tencentcloudapi.com \
  DescribeArchList \
  2020-07-21 \
  '{"PageNumber":1,"PageSize":10,"WithSvgURL":true}'

# 按名称搜索
python3 {baseDir}/scripts/tcloud_api.py \
  advisor \
  advisor.tencentcloudapi.com \
  DescribeArchList \
  2020-07-21 \
  '{"PageNumber":1,"PageSize":20,"SearchKey":"生产环境"}'
```

## 返回示例

```json
{
  "success": true,
  "action": "DescribeArchList",
  "data": {
    "TotalCount": 916,
    "ArchList": [
      {
        "ArchId": "arch-xxxxxxx",
        "ArchName": "示例架构图",
        "CreateTime": "2025-10-28 17:18:38",
        "UpdateTime": "2025-10-28 18:58:04",
        "FolderId": 1598964,
        "VersionName": "1.0.0",
        "ShareStatus": false,
        "SvgURL": "https://xxxxxxxxxxx",
        "Tags": []
      }
    ]
  },
  "requestId": "9cbe807c-..."
}
```

## 免密登录链接规则

返回结果包含架构图列表时，只需为列表中的**第一张架构图**调用免密登录脚本生成控制台直达链接（见 SKILL.md 第五章），架构图页面 URL 格式为：
`https://console.cloud.tencent.com/advisor?archId={ArchId}`

生成链接后，以 Markdown 超链接形式展示给用户：`[跳转控制台](免密登录URL)`，不要直接展示完整 URL。仅第一张架构图附带超链接，其余架构图正常展示信息即可。

> **⚠️ 注意**：免密登录链接**每次都必须重新生成**，不可缓存或复用之前生成的链接。每次向用户展示架构图列表时，都必须重新调用 `login_url.py` 生成新的链接。


path: references/api/CreateAdvisorAuthorization.md
content: # CreateAdvisorAuthorization — 开启智能顾问授权

开启智能顾问授权。会同步开启报告解读和云架构协作权限。

> **⚠️ 重要**：此接口为**写入操作**，会开通智能顾问服务。**必须在用户明确同意后才能调用**，严禁自动调用。

## 参数

无业务参数，直接传空 JSON `{}` 即可。

## 调用示例

```bash
python3 {baseDir}/scripts/tcloud_api.py \
  advisor \
  advisor.tencentcloudapi.com \
  CreateAdvisorAuthorization \
  2020-07-21 \
  '{}'
```

## 返回示例

```json
{
  "success": true,
  "action": "CreateAdvisorAuthorization",
  "data": {
    "Message": "Already authorized"
  },
  "requestId": "97aa2227-1814-4d39-9470-dadca094be1f"
}
```

## 返回字段说明

| 字段 | 类型 | 说明 |
|------|------|------|
| `Message` | String | 返回信息，如 `"Already authorized"` 表示已开通 |

## 使用场景

当用户查询智能顾问数据时返回空结果或提示未授权，可能是当前账号尚未开通智能顾问服务。此时应：

1. **告知用户**当前账号可能未开通智能顾问，询问是否需要开通
2. **等待用户明确同意**后才可调用此接口
3. 调用成功后，提示用户智能顾问已开通，可重新查询

## 展示规则

调用前向用户展示：

```
当前账号（AK/SK 对应账号）似乎尚未开通智能顾问服务。
开通智能顾问后，将同步开启报告解读和云架构协作权限。

是否同意开通智能顾问服务？
```

调用成功后向用户展示：

```
✅ 智能顾问服务已成功开通！现在可以正常使用架构图管理、风险评估等功能。
```


path: references/api/DescribeArch.md
content: # DescribeArch — 获取云架构详情

获取指定云架构图的详细信息，包括名称、SVG 数据、节点列表、绑定资源等。

## 参数

| 参数 | 必选 | 类型 | 描述 |
|------|------|------|------|
| ArchId | 是 | String | 云架构 ID，如 `arch-jcxtbe9t` |
| Username | 是 | String | 用户名，用于记录最近访问 |
| VersionId | 否 | Integer | 版本 ID，不传默认获取最新版 |
| SvgType | 否 | String | SVG 类型，默认 `3D`，可指定为 `2D` |
| WithoutDetail | 否 | Boolean | 是否不返回 detail 数据，默认 `false` |

## 调用示例

```bash
python3 {baseDir}/scripts/tcloud_api.py \
  advisor \
  advisor.tencentcloudapi.com \
  DescribeArch \
  2020-07-21 \
  '{"ArchId":"arch-jcxtbe9t","Username":"user1"}'
```

## 返回示例

```json
{
  "success": true,
  "action": "DescribeArch",
  "data": {
    "Arch": {
      "ArchId": "arch-jcxtbe9t",
      "ArchName": "架构图1",
      "FolderId": 123,
      "CreateTime": "2023-11-14 10:29:04",
      "VersionName": "版本1",
      "VersionId": 76,
      "NodeList": [
        {
          "DiagramId": "ddd",
          "NodeName": "CVM节点",
          "ProductType": "CVM",
          "ResourceList": [
            {"InstanceId": "ins-j7uxx5im", "Attributes": "aaa"}
          ]
        }
      ]
    }
  },
  "requestId": "b90a5cc7-..."
}
```

## 免密登录链接规则

返回结果包含架构图时，**必须**调用免密登录脚本为该架构图生成控制台直达链接（见 SKILL.md 第五章），架构图页面 URL 格式为：
`https://console.cloud.tencent.com/advisor?archId={ArchId}`

生成链接后，以 Markdown 超链接形式展示给用户：`[跳转控制台](免密登录URL)`，不要直接展示完整 URL。

> **⚠️ 注意**：免密登录链接**每次都必须重新生成**，不可缓存或复用之前生成的链接。每次向用户展示架构图信息时，都必须重新调用 `login_url.py` 生成新的链接。


path: references/api/DescribeLastEvaluation.md
content: # DescribeLastEvaluation — 获取架构图最近一次评估结果

获取指定架构图最近一次的 Well-Architected 评估结果，包括总分、各支柱维度得分以及治理建议。

## 参数

| 参数 | 必选 | 类型 | 描述 |
|------|------|------|------|
| ArchId | 是 | String | 云架构 ID，如 `arch-gvqocc25` |

## 调用示例

```bash
python3 {baseDir}/scripts/tcloud_api.py \
  advisor \
  advisor.tencentcloudapi.com \
  DescribeLastEvaluation \
  2020-07-21 \
  '{"ArchId":"arch-gvqocc25"}'
```

## 返回示例

```json
{
  "success": true,
  "action": "DescribeLastEvaluation",
  "data": {
    "UpdateTime": "2024-07-12 09:12:34",
    "NeedReEvaluation": false,
    "WellArchScore": {
      "TotalScore": 68,
      "PillarScore": [
        {"Category": "安全", "Value": 34},
        {"Category": "可靠", "Value": 34},
        {"Category": "性能", "Value": 34},
        {"Category": "成本", "Value": 34},
        {"Category": "服务限制", "Value": 34},
        {"Category": "卓越运营", "Value": 34}
      ]
    },
    "GovernanceAdvices": [
      {
        "AdviceCategory": "推荐治理",
        "Layer": [
          {
            "LayerCategory": "资源层",
            "Advices": [
              {
                "StrategyName": "strategy-xxx",
                "Title": "云数据库（MySQL）root 账号安全风险",
                "Pillar": "安全",
                "Explain": "检查root 账号安全风险",
                "Problems": [
                  {
                    "Title": "MySQL实例只设置了root账号",
                    "ImpactInstances": 4,
                    "Callback": {"Type": "Plugin", "Value": "PluginName", "Param": "测试参数"}
                  }
                ]
              }
            ]
          },
          {
            "LayerCategory": "架构层",
            "Advices": []
          }
        ]
      }
    ]
  },
  "requestId": "xxx-xxx-xx"
}
```

## 返回字段说明

| 字段 | 类型 | 说明 |
|------|------|------|
| `UpdateTime` | String | 最近一次评估时间 |
| `NeedReEvaluation` | Boolean | 是否需要重新评估（架构图有变更时为 `true`） |
| `WellArchScore.TotalScore` | Integer | 总评分（满分 100） |
| `WellArchScore.PillarScore[]` | Array | 各支柱维度得分 |
| `PillarScore[].Category` | String | 维度名称：安全 / 可靠 / 性能 / 成本 / 服务限制 / 卓越运营 |
| `PillarScore[].Value` | Integer | 该维度得分 |
| `GovernanceAdvices[]` | Array | 治理建议分组 |
| `GovernanceAdvices[].AdviceCategory` | String | 建议分类：推荐治理 / 安全 / 可靠 / 性能 / 成本 / 服务限制 / 卓越运营 |
| `GovernanceAdvices[].Layer[]` | Array | 按层级分组的建议 |
| `Layer[].LayerCategory` | String | 层级分类：资源层 / 架构层 |
| `Layer[].Advices[]` | Array | 具体建议列表 |
| `Advices[].StrategyName` | String | 策略标识 |
| `Advices[].Title` | String | 建议标题 |
| `Advices[].Pillar` | String | 所属支柱维度 |
| `Advices[].Explain` | String | 检查说明 |
| `Advices[].Problems[]` | Array | 发现的问题列表 |
| `Problems[].Title` | String | 问题描述 |
| `Problems[].ImpactInstances` | Integer | 影响的实例数量 |
| `Problems[].Callback` | Object | 回调信息（可选） |
| `Callback.Type` | String | 回调类型：`URL`（跳转链接）/ `Plugin`（插件） |
| `Callback.Value` | String | 回调值（URL 地址或插件名称） |
| `Callback.Param` | String | 回调参数 |

## 展示格式

向用户展示评估结果时，先展示总分和各维度得分，再按建议分类展示治理建议：

```
📊 架构评估结果（arch-gvqocc25）
评估时间：2024-07-12 09:12:34
总分：68 / 100

各维度得分：
  安全：34  |  可靠：34  |  性能：34
  成本：34  |  服务限制：34  |  卓越运营：34

【推荐治理】
  ▸ 资源层
    ⚠️ 云数据库（MySQL）root 账号安全风险（安全）
       MySQL实例只设置了root账号，影响 4 个实例

  ▸ 架构层
    （无建议）

【安全】
  ▸ 资源层
    ⚠️ 云数据库（MySQL）root 账号安全风险（安全）
       MySQL实例只设置了root账号，影响 4 个实例
```

当 `NeedReEvaluation` 为 `true` 时，应在输出中提示用户：`⚠️ 架构图已变更，建议重新评估`。

当 `Callback.Type` 为 `URL` 时，可将 `Callback.Value` 作为问题详情链接展示。

## 典型用法

- "查看架构图 arch-xxx 的评估结果" → 调用接口，展示总分和各维度得分
- "这个架构图有哪些安全问题" → 调用接口，筛选 `AdviceCategory=安全` 或 `Pillar=安全` 的建议
- "架构图需要治理什么" → 调用接口，筛选 `AdviceCategory=推荐治理` 的建议


path: references/api/ListDirectoryV2.md
content: # ListDirectoryV2 — 新版目录查询

查询云架构图的目录树结构。

## 参数

| 参数 | 必选 | 类型 | 描述 |
|------|------|------|------|
| FolderId | 否 | Integer | 目录 ID |
| ShareStatus | 否 | String | 共享状态 |
| SearchKey | 否 | String | 模糊搜索关键词 |

## 调用示例

```bash
# 查询全部目录
python3 {baseDir}/scripts/tcloud_api.py \
  advisor \
  advisor.tencentcloudapi.com \
  ListDirectoryV2 \
  2020-07-21 \
  '{}'

# 按关键词搜索目录
python3 {baseDir}/scripts/tcloud_api.py \
  advisor \
  advisor.tencentcloudapi.com \
  ListDirectoryV2 \
  2020-07-21 \
  '{"SearchKey":"arch"}'
```

## 返回示例

```json
{
  "success": true,
  "action": "ListDirectoryV2",
  "data": {
    "FirstFolderId": 1,
    "Folders": [
      {
        "Id": 1,
        "Name": "folder",
        "Type": "Directory",
        "FolderTypeId": 2,
        "Children": []
      }
    ]
  },
  "requestId": "909d6e2b-..."
}
```


path: references/api/DescribeStrategies.md
content: # DescribeStrategies — 获取智能顾问支持的风险评估项列表

查询智能顾问**平台支持**的所有风险评估项（巡检项），涵盖安全、可靠、费用、性能、服务限制等维度，覆盖 COS、CVM、MySQL、Redis、CBS、CAM、CLB、VPC 等云产品。该接口返回的是智能顾问平台定义的评估规则列表，**不是**某个架构图的风险评估结果。

## 参数

无必填参数，直接调用即可。

## 调用示例

```bash
python3 {baseDir}/scripts/tcloud_api.py \
  advisor \
  advisor.tencentcloudapi.com \
  DescribeStrategies \
  2020-07-21 \
  '{}'
```

## 返回示例

```json
{
  "success": true,
  "action": "DescribeStrategies",
  "data": {
    "Strategies": [
      {
        "StrategyId": 131,
        "Name": "云数据库（Redis）跨可用区部署",
        "Desc": "检查 Redis 实例是否跨可用区部署...",
        "Product": "redis",
        "ProductDesc": "云数据库（Redis）",
        "Repair": "建议采用跨可用区部署方案...",
        "GroupId": 2,
        "GroupName": "可靠",
        "IsSupportCustom": false,
        "Conditions": [
          {
            "ConditionId": 178,
            "Level": 2,
            "LevelDesc": "中风险",
            "Desc": "Redis 实例未跨可用区部署"
          }
        ]
      }
    ]
  },
  "requestId": "xxxxxxxx-..."
}
```

## 返回字段说明

| 字段 | 类型 | 说明 |
|------|------|------|
| `StrategyId` | Integer | 评估项唯一 ID |
| `Name` | String | 评估项名称（用于拼接控制台链接） |
| `Desc` | String | 评估项描述 |
| `Product` | String | 产品标识（cos / cvm / mysql / redis / cbs / cam 等） |
| `ProductDesc` | String | 产品中文名 |
| `Repair` | String | 修复建议 |
| `GroupName` | String | 分组：安全 / 可靠 / 费用 / 性能 / 服务限制 |
| `Conditions[].Level` | Integer | 风险等级：3=高危 / 2=中危 / 1=低危 |
| `Conditions[].LevelDesc` | String | 等级描述 |
| `Conditions[].Desc` | String | 条件描述 |

## 筛选方式

该接口无服务端筛选参数，返回全量评估项。需在返回结果中按以下维度进行客户端筛选：

1. **按产品筛选**：用 `Product` 字段匹配（如 `cos`、`cvm`、`mysql`、`redis`）
2. **按分组筛选**：用 `GroupName` 字段匹配（`安全`、`可靠`、`费用`、`性能`、`服务限制`）
3. **按风险等级筛选**：遍历 `Conditions[]`，`Level` 为 3=高危、2=中危、1=低危

## 常见 Product 值

| Product | ProductDesc |
|---------|-------------|
| cos | 对象存储（COS） |
| cvm | 云服务器（CVM） |
| mysql | 云数据库（MySQL） |
| redis | 云数据库（Redis） |
| cbs | 云硬盘（CBS） |
| cam | 访问管理（CAM） |
| clb | 负载均衡（CLB） |
| vpc | 私有网络（VPC） |
| lighthouse | 轻量应用服务器（LH） |
| mongodb | 云数据库（MongoDB） |

## 控制台直达链接

每个评估项可通过 `Name` 字段拼接控制台跳转 URL（无需免密登录，直接展示即可）：

```
https://console.cloud.tencent.com/advisor/assess?strategyName={URL编码后的Name}
```

## 展示格式

向用户展示评估项时，按产品分组，每条包含风险等级、名称、分组和控制台链接：

```
【云数据库（Redis）】
  🔴 高危  Redis 实例未开启密码认证
  分组：安全  |  StrategyId：132
  控制台：https://console.cloud.tencent.com/advisor/assess?strategyName=Redis%20...

  🟡 中危  云数据库（Redis）跨可用区部署
  分组：可靠  |  StrategyId：131
  控制台：https://console.cloud.tencent.com/advisor/assess?strategyName=...
```

等级图标：🔴 高危（Level=3）、🟡 中危（Level=2）、🟢 低危（Level=1）

## 典型用法

- "查看 COS 的所有风险项" → 筛选 `Product=cos`，输出带控制台链接的列表
- "有哪些高危的安全风险" → 筛选 `GroupName=安全` + `Level=3`
- "智能顾问有哪些巡检项" → 直接输出全量列表（按 ProductDesc 分组）
- "给我 Redis 的巡检链接" → 筛选 `Product=redis`，每条附上控制台 URL


path: references/api/ListUnorganizedDirectory.md
content: # ListUnorganizedDirectory — 查询待整理目录

查询新版目录下的待整理（未归类）目录结构。

## 参数

| 参数 | 必选 | 类型 | 描述 |
|------|------|------|------|
| FolderId | 否 | Integer | 目录 ID |
| ShareStatus | 否 | String | 架构图共享状态 |
| SearchKey | 否 | String | 查询关键词 |

## 调用示例

```bash
# 查询全部待整理目录
python3 {baseDir}/scripts/tcloud_api.py \
  advisor \
  advisor.tencentcloudapi.com \
  ListUnorganizedDirectory \
  2020-07-21 \
  '{}'
```

## 返回示例

```json
{
  "success": true,
  "action": "ListUnorganizedDirectory",
  "data": {
    "Folders": [
      {
        "Id": 12,
        "Name": "folder1",
        "Type": "Directory",
        "Children": [
          {
            "Id": 13,
            "Name": "folder2",
            "Type": "Directory",
            "Children": [
              {"Id": 14, "Name": "folder4", "Type": "Directory"}
            ]
          }
        ]
      }
    ]
  },
  "requestId": "xxxx-xxxx-xxxx-xxxx"
}
```


path: scripts/tcloud_api.py
content: #!/usr/bin/env python3
"""
腾讯云 API 签名 v3 通用调用脚本 (Python 版)
基于腾讯云 API 签名 v3 文档实现: https://cloud.tencent.com/document/product/213/30654

纯 Python 标准库实现，支持 Windows / Linux / macOS 跨平台运行。
不依赖 curl、openssl、jq 等外部工具。

用法 (命令行):
    python3 tcloud_api.py <service> <host> <action> <version> [payload] [region]

示例:
    python3 tcloud_api.py advisor advisor.tencentcloudapi.com DescribeArchList 2020-07-21 '{"PageNumber":1,"PageSize":10}'

作为模块导入:
    from tcloud_api import call_api
    result = call_api("advisor", "advisor.tencentcloudapi.com",
                      "DescribeArchList", "2020-07-21",
                      {"PageNumber": 1, "PageSize": 10})

环境变量（必须提前设置）:
    TENCENTCLOUD_SECRET_ID  - 腾讯云 SecretId
    TENCENTCLOUD_SECRET_KEY - 腾讯云 SecretKey
    TENCENTCLOUD_TOKEN      - 临时密钥 Token（可选，使用临时密钥时设置）

输出格式（统一 JSON）:
    成功: {"success": true, "action": "<Action>", "data": {...}, "requestId": "xxx"}
    失败: {"success": false, "action": "<Action>", "error": {"code": "xxx", "message": "xxx"}, "requestId": "xxx"}
"""

import hashlib
import hmac
import json
import os
import ssl
import sys
import time
from datetime import datetime, timezone
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError


def _get_ssl_context():
    """获取 SSL 上下文，兼容各平台 CA 证书差异"""
    try:
        import certifi
        return ssl.create_default_context(cafile=certifi.where())
    except ImportError:
        ctx = ssl.create_default_context()
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE
        return ctx


def _sign_tc3(key: bytes, msg: str) -> bytes:
    """TC3 HMAC-SHA256 签名辅助函数"""
    return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()


def _output_json(obj: dict) -> str:
    """统一 JSON 输出"""
    return json.dumps(obj, ensure_ascii=False)


def _make_error(action: str, code: str, message: str, request_id: str = "") -> dict:
    """构造统一错误结果"""
    return {
        "success": False,
        "action": action,
        "error": {"code": code, "message": message},
        "requestId": request_id,
    }


def _make_success(action: str, data: dict, request_id: str) -> dict:
    """构造统一成功结果"""
    return {
        "success": True,
        "action": action,
        "data": data,
        "requestId": request_id,
    }


def call_api(service: str, host: str, action: str, version: str,
             payload: dict | str = None, region: str = "ap-guangzhou",
             secret_id: str = None, secret_key: str = None,
             token: str = None) -> dict:
    """
    调用腾讯云 API（TC3-HMAC-SHA256 签名）

    Args:
        service: 服务名（如 advisor, sts, cam）
        host: 接口域名（如 advisor.tencentcloudapi.com）
        action: 接口名称（如 DescribeArchList）
        version: 接口版本（如 2020-07-21）
        payload: 请求体，dict 或 JSON 字符串
        region: 地域，默认 ap-guangzhou
        secret_id: SecretId，不传则从环境变量读取
        secret_key: SecretKey，不传则从环境变量读取
        token: 临时密钥 Token，不传则从环境变量读取

    Returns:
        dict: 统一格式的结果字典
    """
    # 密钥
    secret_id = secret_id or os.environ.get("TENCENTCLOUD_SECRET_ID", "")
    secret_key = secret_key or os.environ.get("TENCENTCLOUD_SECRET_KEY", "")
    token = token or os.environ.get("TENCENTCLOUD_TOKEN", "")

    if not secret_id or not secret_key:
        return _make_error(
            action, "MissingCredentials",
            "未配置 TENCENTCLOUD_SECRET_ID 或 TENCENTCLOUD_SECRET_KEY。"
            "密钥获取: https://console.cloud.tencent.com/cam/capi"
        )

    # 请求体
    if payload is None:
        payload_str = "{}"
    elif isinstance(payload, dict):
        payload_str = json.dumps(payload, separators=(",", ":"))
    else:
        payload_str = str(payload)

    # 签名参数
    algorithm = "TC3-HMAC-SHA256"
    timestamp = int(time.time())
    date = datetime.fromtimestamp(timestamp, tz=timezone.utc).strftime("%Y-%m-%d")

    # 步骤 1：拼接规范请求串
    hashed_payload = hashlib.sha256(payload_str.encode("utf-8")).hexdigest()
    canonical_request = (
        f"POST\n"
        f"/\n"
        f"\n"
        f"content-type:application/json; charset=utf-8\n"
        f"host:{host}\n"
        f"x-tc-action:{action.lower()}\n"
        f"\n"
        f"content-type;host;x-tc-action\n"
        f"{hashed_payload}"
    )

    # 步骤 2：拼接待签名字符串
    credential_scope = f"{date}/{service}/tc3_request"
    hashed_canonical_request = hashlib.sha256(
        canonical_request.encode("utf-8")
    ).hexdigest()
    string_to_sign = (
        f"{algorithm}\n"
        f"{timestamp}\n"
        f"{credential_scope}\n"
        f"{hashed_canonical_request}"
    )

    # 步骤 3：计算签名
    secret_date = _sign_tc3(f"TC3{secret_key}".encode("utf-8"), date)
    secret_service = _sign_tc3(secret_date, service)
    secret_signing = _sign_tc3(secret_service, "tc3_request")
    signature = hmac.new(
        secret_signing, string_to_sign.encode("utf-8"), hashlib.sha256
    ).hexdigest()

    # 步骤 4：拼接 Authorization
    authorization = (
        f"{algorithm} "
        f"Credential={secret_id}/{credential_scope}, "
        f"SignedHeaders=content-type;host;x-tc-action, "
        f"Signature={signature}"
    )

    # 步骤 5：发送请求
    headers = {
        "Authorization": authorization,
        "Content-Type": "application/json; charset=utf-8",
        "Host": host,
        "X-TC-Action": action,
        "X-TC-Timestamp": str(timestamp),
        "X-TC-Version": version,
        "X-TC-Region": region,
    }
    if [REDACTED_SECRET]"X-TC-Token"] = token

    req = Request(
        f"https://{host}",
        data=payload_str.encode("utf-8"),
        headers=headers,
        method="POST",
    )

    try:
        ctx = _get_ssl_context()
        with urlopen(req, context=ctx, timeout=30) as resp:
            response_body = resp.read().decode("utf-8")
    except HTTPError as e:
        try:
            body = e.read().decode("utf-8")
            # 尝试解析 API 错误响应
            data = json.loads(body)
            response = data.get("Response", {})
            error = response.get("Error", {})
            if error:
                return _make_error(
                    action,
                    error.get("Code", "HTTPError"),
                    error.get("Message", f"HTTP {e.code}"),
                    response.get("RequestId", ""),
                )
        except Exception:
            pass
        return _make_error(
            action, "HTTPError",
            f"HTTP 请求失败 (状态码 {e.code}): {e.reason}"
        )
    except URLError as e:
        return _make_error(
            action, "NetworkError",
            f"网络连接失败，请检查网络和域名 {host} 是否可达: {e.reason}"
        )
    except Exception as e:
        return _make_error(
            action, "NetworkError",
            f"请求异常: {e}"
        )

    # 步骤 6：解析响应
    try:
        data = json.loads(response_body)
    except json.JSONDecodeError:
        return _make_error(action, "ParseError", "响应不是有效的 JSON")

    response = data.get("Response", {})
    request_id = response.get("RequestId", "")

    if "Error" in response:
        err = response["Error"]
        return _make_error(
            action,
            err.get("Code", "Unknown"),
            err.get("Message", "未知错误"),
            request_id,
        )

    # 成功：移除 RequestId 后将剩余字段作为 data
    result_data = {k: v for k, v in response.items() if k != "RequestId"}
    return _make_success(action, result_data, request_id)


def main():
    """命令行入口"""
    args = sys.argv[1:]

    if len(args) < 4:
        print(_output_json(_make_error(
            "", "MissingParameter",
            "缺少必要参数。用法: python3 tcloud_api.py <service> <host> <action> <version> [payload] [region]"
        )))
        sys.exit(1)

    service = args[0]
    host = args[1]
    action = args[2]
    version = args[3]
    payload_str = args[4] if len(args) > 4 else "{}"
    region = args[5] if len(args) > 5 else "ap-guangzhou"

    # 解析 payload
    try:
        payload = json.loads(payload_str)
    except json.JSONDecodeError:
        payload = payload_str

    result = call_api(service, host, action, version, payload, region)
    print(_output_json(result))

    if not result.get("success"):
        sys.exit(1)


if __name__ == "__main__":
    main()


path: scripts/login_url.py
content: #!/usr/bin/env python3
"""
腾讯云控制台免密登录链接生成脚本 (Python 版)
通过 STS AssumeRole 获取临时凭证，生成免密登录 URL

用法:
    python3 login_url.py <target_url>

示例:
    python3 login_url.py "https://console.cloud.tencent.com/advisor?archId=arch-gvqocc25"
    python3 login_url.py "https://console.cloud.tencent.com/advisor"

环境变量（必须提前设置）:
    TENCENTCLOUD_SECRET_ID   - 腾讯云 SecretId（必填）
    TENCENTCLOUD_SECRET_KEY  - 腾讯云 SecretKey（必填）
    TENCENTCLOUD_ROLE_ARN    - CAM 角色 ARN（可选）
    TENCENTCLOUD_ROLE_NAME   - CAM 角色名称（可选），系统会自动拼接完整 ARN

配置优先级:
    1. 环境变量 TENCENTCLOUD_ROLE_ARN（完整 ARN）
    2. 配置文件 ~/.tencent-cloudq/config.json
    3. 环境变量 TENCENTCLOUD_ROLE_NAME + 自动获取账号 UIN

可选环境变量:
    TENCENTCLOUD_ROLE_SESSION - 角色会话名称（默认 advisor-session）
    TENCENTCLOUD_STS_DURATION - 临时凭证有效期秒数（默认 3600，最大 43200）

输出格式（统一 JSON）:
    成功: {"success": true, "action": "GenerateLoginURL", "data": {"loginUrl": "...", "targetUrl": "...", "expireSeconds": 3600}, "requestId": "xxx"}
    失败: {"success": false, "action": "GenerateLoginURL", "error": {"code": "xxx", "message": "xxx"}, "requestId": ""}
"""

import hashlib
import hmac
import base64
import json
import os
import random
import ssl
import sys
import time
from datetime import datetime, timezone
from pathlib import Path
from urllib.parse import quote
from urllib.request import Request, urlopen

# 导入 tcloud_api 模块
SCRIPT_DIR = Path(__file__).resolve().parent
sys.path.insert(0, str(SCRIPT_DIR))
from tcloud_api import call_api  # noqa: E402

ACTION_NAME = "GenerateLoginURL"


def output_error(code: str, message: str, request_id: str = "") -> str:
    """输出统一的错误 JSON"""
    return json.dumps({
        "success": False,
        "action": ACTION_NAME,
        "error": {"code": code, "message": message},
        "requestId": request_id,
    }, ensure_ascii=False)


def output_success(login_url: str, target_url: str, expire_seconds: int, request_id: str) -> str:
    """输出统一的成功 JSON"""
    return json.dumps({
        "success": True,
        "action": ACTION_NAME,
        "data": {
            "loginUrl": login_url,
            "targetUrl": target_url,
            "expireSeconds": expire_seconds,
        },
        "requestId": request_id,
    }, ensure_ascii=False)


def _get_ssl_context():
    """获取 SSL 上下文，兼容 macOS 缺少系统 CA 证书的情况"""
    try:
        import certifi
        return ssl.create_default_context(cafile=certifi.where())
    except ImportError:
        ctx = ssl.create_default_context()
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE
        return ctx


def _sign_tc3(key: bytes, msg: str) -> bytes:
    """TC3 HMAC-SHA256 签名辅助函数"""
    return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()


def _get_role_arn(secret_id: str, secret_key: str) -> str:
    """
    按优先级获取 RoleArn:
    1. 环境变量 TENCENTCLOUD_ROLE_ARN
    2. 配置文件 ~/.tencent-cloudq/config.json
    3. 环境变量 TENCENTCLOUD_ROLE_NAME + 自动获取账号 UIN
    """
    # 优先级 1: 环境变量 ROLE_ARN
    role_arn = os.environ.get("TENCENTCLOUD_ROLE_ARN", "")
    if role_arn:
        return role_arn

    # 优先级 2: 配置文件
    config_file = Path.home() / ".tencent-cloudq" / "config.json"
    if config_file.exists():
        try:
            config = json.loads(config_file.read_text())
            role_arn = config.get("roleArn", "")
            if role_arn:
                return role_arn
        except (json.JSONDecodeError, IOError):
            pass

    # 优先级 3: ROLE_NAME + 自动获取 UIN
    role_name = os.environ.get("TENCENTCLOUD_ROLE_NAME", "")
    if role_name:
        # 尝试 UIN 缓存（跨平台：Windows 用 TEMP，其他用 /tmp）
        import tempfile
        cache_dir = Path(tempfile.gettempdir())
        uin_cache_file = cache_dir / ".tcloud_advisor_uin_cache"
        account_uin = ""

        if uin_cache_file.exists():
            cache_age = time.time() - uin_cache_file.stat().st_mtime
            if cache_age < 86400:  # 24 小时
                account_uin = uin_cache_file.read_text().strip()

        if not account_uin:
            # 调用 tcloud_api 模块获取 UIN
            try:
                result = call_api(
                    "sts", "sts.tencentcloudapi.com",
                    "GetCallerIdentity", "2018-08-13", {},
                    secret_id=secret_id, secret_key=secret_key,
                )
                account_uin = str(result.get("data", {}).get("AccountId", ""))
                if account_uin and account_uin not in ("", "None", "null"):
                    uin_cache_file.write_text(account_uin)
            except Exception:
                pass

        if account_uin and account_uin not in ("", "None", "null"):
            return f"qcs::cam::uin/{account_uin}:roleName/{role_name}"

    return ""


def sts_assume_role(secret_id: str, secret_key: str, role_arn: str,
                    role_session: str, duration: int) -> dict:
    """
    第一步：调用 STS AssumeRole 获取临时凭证
    使用 TC3-HMAC-SHA256 签名算法
    """
    service = "sts"
    host = f"{service}.tencentcloudapi.com"
    action = "AssumeRole"
    version = "2018-08-13"
    region = "ap-guangzhou"

    payload = json.dumps({
        "RoleArn": role_arn,
        "RoleSessionName": role_session,
        "DurationSeconds": duration,
    }, separators=(",", ":"))

    timestamp = int(time.time())
    date = datetime.fromtimestamp(timestamp, tz=timezone.utc).strftime("%Y-%m-%d")

    # 1. 拼接规范请求串
    hashed_payload = hashlib.sha256(payload.encode("utf-8")).hexdigest()
    canonical_request = (
        f"POST\n"
        f"/\n"
        f"\n"
        f"content-type:application/json\n"
        f"host:{host}\n"
        f"x-tc-action:{action.lower()}\n"
        f"\n"
        f"content-type;host;x-tc-action\n"
        f"{hashed_payload}"
    )

    # 2. 拼接待签名字符串
    algorithm = "TC3-HMAC-SHA256"
    credential_scope = f"{date}/{service}/tc3_request"
    hashed_canonical_request = hashlib.sha256(
        canonical_request.encode("utf-8")
    ).hexdigest()
    string_to_sign = (
        f"{algorithm}\n"
        f"{timestamp}\n"
        f"{credential_scope}\n"
        f"{hashed_canonical_request}"
    )

    # 3. 计算签名
    secret_date = _sign_tc3(f"TC3{secret_key}".encode("utf-8"), date)
    secret_service = _sign_tc3(secret_date, service)
    secret_signing = _sign_tc3(secret_service, "tc3_request")
    signature = hmac.new(
        secret_signing, string_to_sign.encode("utf-8"), hashlib.sha256
    ).hexdigest()

    # 4. 拼接 Authorization
    authorization = (
        f"{algorithm} "
        f"Credential={secret_id}/{credential_scope}, "
        f"SignedHeaders=content-type;host;x-tc-action, "
        f"Signature={signature}"
    )

    # 5. 发送请求
    headers = {
        "Authorization": authorization,
        "Content-Type": "application/json",
        "Host": host,
        "X-TC-Action": action,
        "X-TC-Version": version,
        "X-TC-Region": region,
        "X-TC-Timestamp": str(timestamp),
    }

    req = Request(
        f"https://{host}",
        data=payload.encode("utf-8"),
        headers=headers,
        method="POST",
    )

    ctx = _get_ssl_context()
    with urlopen(req, context=ctx) as resp:
        result = json.loads(resp.read().decode("utf-8"))

    response_body = result.get("Response", {})
    credentials = response_body.get("Credentials", {})

    if not credentials:
        error = response_body.get("Error", {})
        return {
            "error": True,
            "code": error.get("Code", "STSError"),
            "message": f"STS AssumeRole 失败: {error.get('Message', '请检查 AK/SK 和 ROLE_ARN')}",
            "requestId": response_body.get("RequestId", ""),
        }

    return {
        "error": False,
        "TmpSecretId": credentials["TmpSecretId"],
        "TmpSecretKey": credentials["TmpSecretKey"],
        "Token": credentials["Token"],
        "RequestId": response_body.get("RequestId", ""),
    }


def generate_login_url(tmp_secret_id: str, tmp_secret_key: str,
                       token: str, target_url: str) -> str:
    """
    第二步：使用临时凭证生成免密登录 URL
    签名方式：HMAC-SHA256 + Base64（与官方文档一致）
    """
    login_timestamp = int(time.time())
    login_nonce = random.randint(10000, 100000000)

    # 签名源串：参数按字母序排列 (action < nonce < secretId < timestamp)
    source_string = (
        f"GETcloud.tencent.com/login/roleAccessCallback?"
        f"action=roleLogin"
        f"&nonce={login_nonce}"
        f"&secretId={tmp_secret_id}"
        f"&timestamp={login_timestamp}"
    )

    # HMAC-SHA256 + Base64
    login_signature = base64.b64encode(
        hmac.new(
            tmp_secret_key.encode("utf-8"),
            source_string.encode("utf-8"),
            hashlib.sha256,
        ).digest()
    ).decode("utf-8")

    # 拼接最终 URL（safe='' 确保 +/= 等特殊字符都被编码）
    login_url = (
        f"https://cloud.tencent.com/login/roleAccessCallback?"
        f"algorithm=sha256"
        f"&secretId={quote(tmp_secret_id, safe='')}"
        f"&[REDACTED_SECRET], safe='')}"
        f"&nonce={login_nonce}"
        f"&timestamp={login_timestamp}"
        f"&signature={quote(login_signature, safe='')}"
        f"&s_url={quote(target_url, safe='')}"
    )

    return login_url


def main():
    # ============== 参数解析 ==============
    if len(sys.argv) < 2 or not sys.argv[1].strip():
        print(output_error(
            "MissingParameter",
            "缺少目标 URL 参数。用法: python3 login_url.py <target_url>"
        ))
        sys.exit(1)

    target_url = sys.argv[1]

    # ============== 密钥检查 ==============
    secret_id = os.environ.get("TENCENTCLOUD_SECRET_ID", "")
    secret_key = os.environ.get("TENCENTCLOUD_SECRET_KEY", "")

    missing = []
    if not secret_id:
        missing.append("TENCENTCLOUD_SECRET_ID")
    if not secret_key:
        missing.append("TENCENTCLOUD_SECRET_KEY")

    if missing:
        print(output_error("MissingCredentials", f"缺少环境变量: {' '.join(missing)}"))
        sys.exit(1)

    # ============== 获取 RoleArn ==============
    role_arn = _get_role_arn(secret_id, secret_key)
    if not role_arn:
        print(output_error(
            "MissingRoleConfiguration",
            "未配置角色信息。请选择以下方式之一:\\n\\n"
            "方式一: 设置角色名称（推荐）\\n"
            "  export TENCENTCLOUD_ROLE_NAME=\\\"AdvisorRole\\\"\\n\\n"
            "方式二: 设置完整 ARN\\n"
            "  export TENCENTCLOUD_ROLE_ARN=\\\"qcs::cam::uin/您的账号UIN:roleName/角色名\\\"\\n\\n"
            "方式三: 运行配置向导（首次使用推荐）\\n"
            "  python3 scripts/setup_role.py\\n\\n"
            "提示: 您可以在 CAM 控制台查看可用角色:\\n"
            "  https://console.cloud.tencent.com/cam/role"
        ))
        sys.exit(1)

    # ============== 配置参数 ==============
    role_session = os.environ.get("TENCENTCLOUD_ROLE_SESSION", "advisor-session")
    duration = int(os.environ.get("TENCENTCLOUD_STS_DURATION", "3600"))

    # ============== 第一步：STS AssumeRole ==============
    try:
        sts_result = sts_assume_role(secret_id, secret_key, role_arn, role_session, duration)
    except Exception as e:
        print(output_error("STSError", f"STS AssumeRole 请求异常: {e}"))
        sys.exit(1)

    if sts_result.get("error"):
        print(output_error(
            sts_result.get("code", "STSError"),
            sts_result.get("message", "STS AssumeRole 失败"),
            sts_result.get("requestId", ""),
        ))
        sys.exit(1)

    # ============== 第二步：生成免密登录 URL ==============
    login_url = generate_login_url(
        sts_result["TmpSecretId"],
        sts_result["TmpSecretKey"],
        sts_result["Token"],
        target_url,
    )

    # ============== 输出结果 ==============
    print(output_success(login_url, target_url, duration, sts_result["RequestId"]))


if __name__ == "__main__":
    main()


path: scripts/create_role.py
content: #!/usr/bin/env python3
"""
腾讯云智能顾问 — CAM 角色创建脚本 (Python 版)

功能：创建 advisor 角色并关联 QcloudTAGFullAccess、QcloudAdvisorFullAccess 策略
注意：本脚本包含 IAM 写入操作（CreateRole、AttachRolePolicy），
      仅应在用户明确同意后执行，不可自动运行

用法:
    python3 create_role.py                  # 自动获取账号 UIN
    python3 create_role.py --uin 100001234  # 手动指定账号 UIN

返回码:
    0 - 角色创建成功，配置已保存
    1 - 参数错误
    2 - AK/SK 未配置或无效
    3 - 角色创建失败

输出: JSON 格式结果（供 AI 解析）

跨平台支持: Windows / Linux / macOS
"""

import json
import os
import platform
import stat
import sys
from datetime import datetime, timezone
from pathlib import Path

# 导入 tcloud_api 模块
SCRIPT_DIR = Path(__file__).resolve().parent
sys.path.insert(0, str(SCRIPT_DIR))

from tcloud_api import call_api  # noqa: E402

# ============== 配置 ==============
CONFIG_DIR = Path.home() / ".tencent-cloudq"
CONFIG_FILE = CONFIG_DIR / "config.json"
ROLE_NAME = "advisor"
POLICY_NAMES = ["QcloudTAGFullAccess", "QcloudAdvisorFullAccess"]


def output_json(obj: dict) -> str:
    return json.dumps(obj, indent=2, ensure_ascii=False)


def output_success(role_arn: str, account_uin: str, role_id: str = "unknown") -> dict:
    return {
        "success": True,
        "action": "CreateAdvisorRole",
        "data": {
            "roleName": ROLE_NAME,
            "roleArn": role_arn,
            "roleId": role_id,
            "accountUin": account_uin,
            "policiesAttached": POLICY_NAMES,
            "consoleLogin": True,
            "configFile": str(CONFIG_FILE),
        },
    }


def output_error(code: str, message: str) -> dict:
    return {
        "success": False,
        "action": "CreateAdvisorRole",
        "error": {"code": code, "message": message},
    }


def save_config(account_uin: str, role_arn: str, role_id: str = "",
                auto_created: bool = True):
    """保存配置到文件"""
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)

    if platform.system() != "Windows":
        try:
            os.chmod(str(CONFIG_DIR), stat.S_IRWXU)  # 700
        except OSError:
            pass

    config = {
        "accountUin": account_uin,
        "roleName": ROLE_NAME,
        "roleArn": role_arn,
        "roleId": role_id,
        "configuredAt": datetime.now(timezone.utc).isoformat(),
        "autoCreated": auto_created,
        "version": "1.0",
    }

    CONFIG_FILE.write_text(
        json.dumps(config, indent=2, ensure_ascii=False),
        encoding="utf-8",
    )

    if platform.system() != "Windows":
        try:
            os.chmod(str(CONFIG_FILE), stat.S_IRUSR | stat.S_IWUSR)  # 600
        except OSError:
            pass


def main():
    # ============== 参数解析 ==============
    account_uin = ""
    args = sys.argv[1:]
    i = 0
    while i < len(args):
        if args[i] == "--uin" and i + 1 < len(args):
            account_uin = args[i + 1]
            i += 2
        else:
            print(output_json(output_error("InvalidParameter", f"未知参数: {args[i]}")))
            sys.exit(1)

    # ============== 1. 检查 AK/SK ==============
    secret_id = os.environ.get("TENCENTCLOUD_SECRET_ID", "")
    secret_key = os.environ.get("TENCENTCLOUD_SECRET_KEY", "")

    if not secret_id or not secret_key:
        print(output_json(output_error(
            "MissingCredentials",
            "未配置 TENCENTCLOUD_SECRET_ID 或 TENCENTCLOUD_SECRET_KEY"
        )))
        sys.exit(2)

    # ============== 2. 获取账号 UIN ==============
    if not account_uin:
        uin_result = call_api(
            "sts", "sts.tencentcloudapi.com",
            "GetCallerIdentity", "2018-08-13", {},
        )
        account_uin = str(uin_result.get("data", {}).get("AccountId", ""))

        if not account_uin or account_uin in ("", "None", "null"):
            error_msg = uin_result.get("error", {}).get("message", "无法获取账号 UIN")
            print(output_json(output_error("GetCallerIdentityFailed", error_msg)))
            sys.exit(2)

    # ============== 3. 检查角色是否已存在 ==============
    role_check = call_api(
        "cam", "cam.tencentcloudapi.com",
        "GetRole", "2019-01-16",
        {"RoleName": ROLE_NAME},
    )

    if role_check.get("success"):
        console_login = role_check.get("data", {}).get("ConsoleLogin", 0)
        role_id = str(role_check.get("data", {}).get("RoleId", "unknown"))
        role_arn = f"qcs::cam::uin/{account_uin}:roleName/{ROLE_NAME}"

        if console_login == 1:
            # 角色已存在且支持控制台登录 → 直接保存配置
            save_config(account_uin, role_arn, role_id, auto_created=False)
            print(output_json(output_success(role_arn, account_uin, role_id)))
            sys.exit(0)
        else:
            print(output_json(output_error(
                "RoleExistsNoConsoleLogin",
                f"角色 {ROLE_NAME} 已存在但未启用控制台登录（ConsoleLogin=0），"
                "请到 CAM 控制台手动启用或删除后重新创建: "
                "https://console.cloud.tencent.com/cam/role"
            )))
            sys.exit(3)

    # ============== 4. 创建角色 ==============
    trust_policy = json.dumps({
        "version": "2.0",
        "statement": [{
            "action": "name/sts:AssumeRole",
            "effect": "allow",
            "principal": {
                "qcs": [f"qcs::cam::uin/{account_uin}:root"]
            }
        }]
    }, separators=(",", ":"))

    create_result = call_api(
        "cam", "cam.tencentcloudapi.com",
        "CreateRole", "2019-01-16",
        {
            "RoleName": ROLE_NAME,
            "PolicyDocument": trust_policy,
            "ConsoleLogin": 1,
            "Description": "腾讯云智能顾问助手角色（由 tencent-cloudq skill 创建）",
        },
    )

    if not create_result.get("success"):
        err = create_result.get("error", {})
        print(output_json(output_error(
            err.get("code", "CreateRoleFailed"),
            f"角色创建失败: {err.get('message', '未知错误')}"
        )))
        sys.exit(3)

    role_id = str(create_result.get("data", {}).get("RoleId", "unknown"))

    # ============== 5. 关联策略 ==============
    attach_warnings = []
    for policy_name in POLICY_NAMES:
        attach_result = call_api(
            "cam", "cam.tencentcloudapi.com",
            "AttachRolePolicy", "2019-01-16",
            {"AttachRoleName": ROLE_NAME, "PolicyName": policy_name},
        )
        if not attach_result.get("success"):
            err_msg = attach_result.get("error", {}).get("message", "未知错误")
            attach_warnings.append(f"策略 {policy_name} 关联失败: {err_msg}")
            print(f"WARNING: {attach_warnings[-1]}", file=sys.stderr)

    # ============== 6. 保存配置 ==============
    role_arn = f"qcs::cam::uin/{account_uin}:roleName/{ROLE_NAME}"
    save_config(account_uin, role_arn, role_id, auto_created=True)

    # ============== 7. 输出结果 ==============
    result = output_success(role_arn, account_uin, role_id)
    if attach_warnings:
        result["data"]["warnings"] = attach_warnings
    print(output_json(result))
    sys.exit(0)


if __name__ == "__main__":
    main()


path: scripts/cleanup.py
content: #!/usr/bin/env python3
"""
腾讯云智能顾问 - 配置清理脚本

清理本机上 tencent-cloudq 产生的所有配置和缓存文件，
可选删除云端通过本工具创建的 CAM 角色。

用法:
    python3 cleanup.py               # 交互式清理（逐项确认）
    python3 cleanup.py --all         # 一键清理所有本地配置（不含云端角色）
    python3 cleanup.py --all --cloud # 一键清理所有本地配置 + 云端角色

清理范围:
    1. 配置目录  ~/.tencent-cloudq/（含 config.json）
    2. 临时缓存  {系统临时目录}/.tcloud_advisor_uin_cache
    3. 环境变量  TENCENTCLOUD_* 系列环境变量
    4. 云端角色  CAM 角色 advisor（可选，需 AK/SK）

返回码:
    0 - 清理完成
    1 - 用户取消

跨平台支持: Windows / Linux / macOS
"""

import json
import os
import platform
import shutil
import sys
import tempfile
from pathlib import Path

# 导入 tcloud_api 模块（用于可选的云端角色删除）
SCRIPT_DIR = Path(__file__).resolve().parent
sys.path.insert(0, str(SCRIPT_DIR))

# ============== 配置 ==============
CONFIG_DIR = Path.home() / ".tencent-cloudq"
CONFIG_FILE = CONFIG_DIR / "config.json"
CACHE_FILE = Path(tempfile.gettempdir()) / ".tcloud_advisor_uin_cache"
ROLE_NAME = "advisor"

# 智能顾问使用的所有环境变量
ENV_VARS = [
    "TENCENTCLOUD_SECRET_ID",
    "TENCENTCLOUD_SECRET_KEY",
    "TENCENTCLOUD_TOKEN",
    "TENCENTCLOUD_ROLE_ARN",
    "TENCENTCLOUD_ROLE_NAME",
    "TENCENTCLOUD_ROLE_SESSION",
    "TENCENTCLOUD_STS_DURATION",
]


# ============== 终端颜色 ==============
def _supports_color() -> bool:
    if os.environ.get("NO_COLOR"):
        return False
    if platform.system() == "Windows":
        return os.environ.get("TERM") == "xterm" or hasattr(sys.stderr, "reconfigure")
    return hasattr(sys.stdout, "isatty") and sys.stdout.isatty()


_COLOR = _supports_color()


def _c(code: str, text: str) -> str:
    return f"\033[{code}m{text}\033[0m" if _COLOR else text


def green(t: str) -> str:
    return _c("32", t)


def red(t: str) -> str:
    return _c("31", t)


def yellow(t: str) -> str:
    return _c("33", t)


def bold(t: str) -> str:
    return _c("1", t)


def dim(t: str) -> str:
    return _c("2", t)


# ============== 工具函数 ==============
def confirm(prompt: str) -> bool:
    """交互式确认，默认为 No"""
    try:
        answer = input(f"{prompt} [y/N]: ").strip().lower()
        return answer in ("y", "yes")
    except (EOFError, KeyboardInterrupt):
        print()
        return False


def read_config() -> dict:
    """读取现有配置文件"""
    if CONFIG_FILE.exists():
        try:
            return json.loads(CONFIG_FILE.read_text(encoding="utf-8"))
        except (json.JSONDecodeError, OSError):
            return {}
    return {}


def remove_file(path: Path, label: str) -> bool:
    """删除单个文件"""
    if not path.exists():
        print(f"  {dim('[-]')} {label}: {dim('不存在，跳过')}")
        return False
    try:
        path.unlink()
        print(f"  {green('[OK]')} {label}: 已删除")
        return True
    except OSError as e:
        print(f"  {red('[FAIL]')} {label}: 删除失败 - {e}")
        return False


def remove_dir(path: Path, label: str) -> bool:
    """删除目录及其所有内容"""
    if not path.exists():
        print(f"  {dim('[-]')} {label}: {dim('不存在，跳过')}")
        return False
    try:
        shutil.rmtree(str(path))
        print(f"  {green('[OK]')} {label}: 已删除")
        return True
    except OSError as e:
        print(f"  {red('[FAIL]')} {label}: 删除失败 - {e}")
        return False


# ============== 清理动作 ==============
def clean_config_dir(interactive: bool) -> bool:
    """清理配置目录 ~/.tencent-cloudq/"""
    print(f"\n{bold('1. 配置目录')}")

    if not CONFIG_DIR.exists():
        print(f"  {dim('[-]')} {CONFIG_DIR}: {dim('不存在，跳过')}")
        return False

    # 列出目录内容
    files = list(CONFIG_DIR.iterdir())
    print(f"  路径: {CONFIG_DIR}")
    if files:
        for f in files:
            size = f.stat().st_size if f.is_file() else 0
            print(f"    - {f.name} ({size} bytes)")
    else:
        print(f"    {dim('(空目录)')}")

    if interactive and not confirm(f"\n  确认删除 {CONFIG_DIR} ?"):
        print(f"  {yellow('[SKIP]')} 用户跳过")
        return False

    return remove_dir(CONFIG_DIR, str(CONFIG_DIR))


def clean_cache_file(interactive: bool) -> bool:
    """清理临时缓存文件"""
    print(f"\n{bold('2. 临时缓存')}")

    if not CACHE_FILE.exists():
        print(f"  {dim('[-]')} {CACHE_FILE}: {dim('不存在，跳过')}")
        return False

    print(f"  路径: {CACHE_FILE}")
    try:
        size = CACHE_FILE.stat().st_size
        print(f"    - .tcloud_advisor_uin_cache ({size} bytes)")
    except OSError:
        pass

    if interactive and not confirm(f"\n  确认删除 {CACHE_FILE} ?"):
        print(f"  {yellow('[SKIP]')} 用户跳过")
        return False

    return remove_file(CACHE_FILE, str(CACHE_FILE))


def clean_env_vars(interactive: bool) -> bool:
    """清理智能顾问相关环境变量"""
    print(f"\n{bold('4. 环境变量')}")

    # 检测哪些环境变量当前已设置
    found = {}
    for var in ENV_VARS:
        val = os.environ.get(var, "")
        if val:
            found[var] = val

    if not found:
        print(f"  {dim('[-]')} 未检测到已设置的 TENCENTCLOUD_* 环境变量，跳过")
        return False

    # 显示已设置的变量（敏感值掩码）
    print(f"  检测到 {len(found)} 个已设置的环境变量:")
    for var, val in found.items():
        if "SECRET" in var or "TOKEN" in var or "KEY" in var:
            display = val[:4] + "****" + val[-4:] if len(val) > 8 else "****"
        else:
            display = val
        print(f"    - {var} = {display}")

    if interactive and not confirm(f"\n  确认清理以上 {len(found)} 个环境变量?"):
        print(f"  {yellow('[SKIP]')} 用户跳过")
        return False

    # 生成清理命令
    is_windows = platform.system() == "Windows"
    print()

    if is_windows:
        # Windows: 生成 PowerShell 脚本
        script_file = Path(tempfile.gettempdir()) / "cleanup_tencent_env.ps1"
        lines = ["# 腾讯云智能顾问 - 环境变量清理脚本 (PowerShell)",
                 "# 自动生成，执行后可删除此文件", ""]
        for var in found:
            lines.append(f'Remove-Item Env:\\{var} -ErrorAction SilentlyContinue')
            lines.append(f'Write-Host "  [OK] 已清除 {var}"')
        lines.append("")
        lines.append('Write-Host ""')
        lines.append(f'Write-Host "  已清理 {len(found)} 个环境变量（仅影响当前会话）"')

        script_file.write_text("\n".join(lines), encoding="utf-8")
        print(f"  已生成 PowerShell 清理脚本: {script_file}")
        print(f"  请在 PowerShell 中执行:")
        print(f"    . {script_file}")

    else:
        # Linux / macOS: 生成 source 脚本
        script_file = Path(tempfile.gettempdir()) / "cleanup_tencent_env.sh"
        lines = ["#!/bin/sh",
                 "# 腾讯云智能顾问 - 环境变量清理脚本",
                 "# 自动生成，执行后可删除此文件", ""]
        for var in found:
            lines.append(f'unset {var}')
            lines.append(f'echo "  [OK] 已清除 {var}"')
        lines.append("")
        lines.append('echo ""')
        lines.append(f'echo "  已清理 {len(found)} 个环境变量（仅影响当前会话）"')

        script_file.write_text("\n".join(lines), encoding="utf-8")
        try:
            os.chmod(str(script_file), 0o755)
        except OSError:
            pass
        print(f"  已生成清理脚本: {script_file}")
        print(f"  请在当前终端执行:")
        print(f"    source {script_file}")

    print()
    print(f"  {yellow('注意')}: 环境变量存在于 shell 进程中，Python 脚本无法直接修改父 shell。")
    print(f"         请执行上方命令完成清理（仅影响当前终端会话）。")
    print(f"         如果变量写在 ~/.bashrc、~/.zshrc 或系统环境中，请手动移除对应行。")

    return True


def clean_cloud_role(interactive: bool) -> bool:
    """删除云端 CAM 角色（可选）"""
    print(f"\n{bold('3. 云端 CAM 角色')}")

    secret_id = os.environ.get("TENCENTCLOUD_SECRET_ID", "")
    secret_key = os.environ.get("TENCENTCLOUD_SECRET_KEY", "")

    if not secret_id or not secret_key:
        print(f"  {dim('[-]')} 未配置 AK/SK 环境变量，跳过云端角色清理")
        print(f"      如需删除云端角色，请设置 TENCENTCLOUD_SECRET_ID 和 TENCENTCLOUD_SECRET_KEY 后重试")
        return False

    # 加载 tcloud_api
    try:
        from tcloud_api import call_api
    except ImportError:
        print(f"  {red('[FAIL]')} 无法加载 tcloud_api 模块")
        return False

    # 检查角色是否存在
    print(f"  正在检查角色 {ROLE_NAME} ...")
    check_result = call_api(
        "cam", "cam.tencentcloudapi.com",
        "GetRole", "2019-01-16",
        {"RoleName": ROLE_NAME},
    )

    if not check_result.get("success"):
        err_code = check_result.get("error", {}).get("code", "")
        if "NotFound" in err_code or "not exist" in check_result.get("error", {}).get("message", "").lower():
            print(f"  {dim('[-]')} 角色 {ROLE_NAME} 不存在，无需删除")
            return False
        else:
            print(f"  {red('[FAIL]')} 查询角色失败: {check_result.get('error', {}).get('message', '未知错误')}")
            return False

    role_data = check_result.get("data", {})
    role_id = role_data.get("RoleId", "unknown")
    description = role_data.get("Description", "无描述")
    print(f"  检测到角色:")
    print(f"    名称: {ROLE_NAME}")
    print(f"    ID:   {role_id}")
    print(f"    描述: {description}")

    if interactive and not confirm(f"\n  确认删除云端角色 {ROLE_NAME} ? (此操作不可恢复)"):
        print(f"  {yellow('[SKIP]')} 用户跳过")
        return False

    # 执行删除
    print(f"  正在删除角色 {ROLE_NAME} ...")
    delete_result = call_api(
        "cam", "cam.tencentcloudapi.com",
        "DeleteRole", "2019-01-16",
        {"RoleName": ROLE_NAME},
    )

    if delete_result.get("success"):
        print(f"  {green('[OK]')} 云端角色 {ROLE_NAME} 已删除")
        return True
    else:
        err_msg = delete_result.get("error", {}).get("message", "未知错误")
        print(f"  {red('[FAIL]')} 删除角色失败: {err_msg}")
        return False


# ============== 主流程 ==============
def main():
    args = set(sys.argv[1:])

    # 帮助信息
    if args & {"-h", "--help"}:
        print(__doc__)
        sys.exit(0)

    auto_mode = "--all" in args
    include_cloud = "--cloud" in args

    # 标题
    print(f"\n{'=' * 58}")
    print(f"  腾讯云智能顾问 - 配置清理")
    print(f"{'=' * 58}")

    # 显示当前配置摘要
    config = read_config()
    if config:
        print(f"\n当前配置:")
        print(f"  账号 UIN:  {config.get('accountUin', '未知')}")
        print(f"  角色名称:  {config.get('roleName', '未知')}")
        print(f"  角色 ARN:  {config.get('roleArn', '未知')}")
        print(f"  配置时间:  {config.get('configuredAt', '未知')}")

    interactive = not auto_mode

    if interactive:
        print(f"\n{dim('提示: 使用 --all 跳过确认，--cloud 同时删除云端角色')}")

    # 执行清理（注意：云端角色删除必须在环境变量清理之前，因为需要 AK/SK）
    results = []
    results.append(("配置目录", clean_config_dir(interactive)))
    results.append(("临时缓存", clean_cache_file(interactive)))

    if include_cloud:
        results.append(("云端角色", clean_cloud_role(interactive)))
    else:
        print(f"\n{bold('3. 云端 CAM 角色')}")
        print(f"  {dim('[-]')} 未指定 --cloud 参数，跳过云端角色清理")
        print(f"      如需同时删除云端角色，请添加 --cloud 参数")

    # 环境变量清理放在最后（云端角色删除可能需要用到 AK/SK）
    results.append(("环境变量", clean_env_vars(interactive)))

    # 汇总
    cleaned = sum(1 for _, ok in results if ok)
    total = len(results)

    print(f"\n{'=' * 58}")
    print(f"  清理完成: {cleaned}/{total} 项已清理")
    print(f"{'=' * 58}\n")

    sys.exit(0)


if __name__ == "__main__":
    main()


path: scripts/setup_role.py
content: #!/usr/bin/env python3
"""
腾讯云智能顾问角色配置向导 (Python 版)
自动检测账号信息和可用角色，引导用户完成配置

用法:
    python3 setup_role.py

环境变量（必须提前设置）:
    TENCENTCLOUD_SECRET_ID   - 腾讯云 SecretId（必填）
    TENCENTCLOUD_SECRET_KEY  - 腾讯云 SecretKey（必填）

跨平台支持: Windows / Linux / macOS
"""

import json
import os
import platform
import stat
import sys
from datetime import datetime, timezone
from pathlib import Path

# 导入 tcloud_api 模块
SCRIPT_DIR = Path(__file__).resolve().parent
sys.path.insert(0, str(SCRIPT_DIR))

from tcloud_api import call_api  # noqa: E402


# ============== 配置 ==============
CONFIG_DIR = Path.home() / ".tencent-cloudq"
CONFIG_FILE = CONFIG_DIR / "config.json"
ROLE_NAME = "advisor"
POLICY_NAMES = ["QcloudTAGFullAccess", "QcloudAdvisorFullAccess"]


# ============== 颜色输出（跨平台） ==============
def _supports_color() -> bool:
    """检测终端是否支持 ANSI 颜色"""
    if platform.system() == "Windows":
        # Windows 10+ 的 cmd/PowerShell 支持 ANSI（需 VT100）
        try:
            os.system("")  # 激活 VT100 模式
            return True
        except Exception:
            return False
    return hasattr(sys.stdout, "isatty") and sys.stdout.isatty()


USE_COLOR = _supports_color()


def _c(code: str, text: str) -> str:
    """带颜色格式化"""
    if USE_COLOR:
        return f"\033[{code}m{text}\033[0m"
    return text


def blue(text: str) -> str:
    return _c("0;34", text)


def green(text: str) -> str:
    return _c("0;32", text)


def yellow(text: str) -> str:
    return _c("1;33", text)


def red(text: str) -> str:
    return _c("0;31", text)


def bold_cyan(text: str) -> str:
    return _c("1;36", text)


def bold_yellow(text: str) -> str:
    return _c("1;33", text)


# ============== 输出辅助 ==============
def print_header(title: str):
    print("")
    bar = blue("=" * 58)
    print(bar)
    print(blue(f"  {title}"))
    print(bar)
    print("")


def print_step(msg: str):
    print(green(f"▶ {msg}"))


def print_ok(msg: str):
    print(green(f"✓ {msg}"))


def print_fail(msg: str):
    print(red(f"✗ {msg}"))


def print_warn(msg: str):
    print(yellow(f"⚠ {msg}"))


# ============== 配置保存 ==============
def save_config(account_uin: str, role_name: str, role_arn: str,
                auto_created: bool = False):
    """保存配置文件（跨平台兼容）"""
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)

    if platform.system() != "Windows":
        try:
            os.chmod(str(CONFIG_DIR), stat.S_IRWXU)  # 700
        except OSError:
            pass

    config = {
        "accountUin": account_uin,
        "roleName": role_name,
        "roleArn": role_arn,
        "configuredAt": datetime.now(timezone.utc).isoformat(),
        "autoCreated": auto_created,
        "version": "1.0",
    }

    CONFIG_FILE.write_text(
        json.dumps(config, indent=2, ensure_ascii=False),
        encoding="utf-8",
    )

    if platform.system() != "Windows":
        try:
            os.chmod(str(CONFIG_FILE), stat.S_IRUSR | stat.S_IWUSR)  # 600
        except OSError:
            pass


def print_config_summary(account_uin: str, role_name: str, role_arn: str):
    """打印配置摘要"""
    print("")
    print_header("配置完成")
    print("")
    print(green("✓ 您已成功配置 tencent-cloudq!"))
    print("")
    print("配置摘要:")
    print("━" * 50)
    print(f"  账号 UIN:  {account_uin}")
    print(f"  角色名称:  {role_name}")
    print(f"  角色 ARN:  {role_arn}")
    print(f"  关联策略:  {', '.join(POLICY_NAMES)}")
    print(f"  配置文件:  {CONFIG_FILE}")
    print("━" * 50)
    print("")


def test_role(scripts_dir: Path):
    """测试角色扮演"""
    import subprocess
    login_url_path = scripts_dir / "login_url.py"

    try:
        test_result = subprocess.run(
            [sys.executable, str(login_url_path),
             "https://console.cloud.tencent.com/advisor"],
            capture_output=True, text=True, timeout=30,
        )
        try:
            result_data = json.loads(test_result.stdout)
            return result_data.get("success", False), result_data
        except json.JSONDecodeError:
            return False, {"error": {"message": "返回格式异常"}}
    except Exception as e:
        return False, {"error": {"message": str(e)}}


def main():
    print_header("腾讯云智能顾问 - 角色配置向导")

    # ============== 步骤 1：验证密钥 ==============
    print_step("步骤 1/6: 验证 API 密钥配置...")
    print("")

    secret_id = os.environ.get("TENCENTCLOUD_SECRET_ID", "")
    secret_key = os.environ.get("TENCENTCLOUD_SECRET_KEY", "")

    if not secret_id or not secret_key:
        print_fail("未检测到 API 密钥")
        print("")
        print("请先设置环境变量:")
        print('  export TENCENTCLOUD_SECRET_ID="your-secret-id"')
        print('  export TENCENTCLOUD_SECRET_KEY="your-secret-key"')
        print("")
        if platform.system() == "Windows":
            print("Windows PowerShell:")
            print('  $env:TENCENTCLOUD_SECRET_ID="your-secret-id"')
            print('  $env:TENCENTCLOUD_SECRET_KEY="your-secret-key"')
            print("")
        print("获取密钥: https://console.cloud.tencent.com/cam/capi")
        sys.exit(1)

    masked_id = f"{secret_id[:8]}****{secret_id[-4:]}" if len(secret_id) > 12 else "****"
    print_ok("API 密钥已配置")
    print(f"  Secret ID: {masked_id}")
    print("")

    # ============== 步骤 2：获取账号信息 ==============
    print_step("步骤 2/6: 获取账号信息...")
    print("")

    uin_result = call_api(
        "sts", "sts.tencentcloudapi.com",
        "GetCallerIdentity", "2018-08-13", {},
    )

    if not uin_result.get("success"):
        print_fail("获取账号信息失败")
        print("")
        error_msg = uin_result.get("error", {}).get("message", "未知错误")
        print(f"  错误详情: {error_msg}")
        sys.exit(1)

    account_uin = str(uin_result.get("data", {}).get("AccountId", ""))
    account_type = str(uin_result.get("data", {}).get("Type", ""))

    print_ok("账号信息获取成功")
    print(f"  账号 UIN: {account_uin}")
    print(f"  身份类型: {account_type}")
    print("")

    # ============== 步骤 3：获取角色列表 ==============
    print_step("步骤 3/6: 查询可用角色...")
    print("")

    role_list_result = call_api(
        "cam", "cam.tencentcloudapi.com",
        "DescribeRoleList", "2019-01-16",
        {"Page": 1, "Rp": 50},
    )

    if not role_list_result.get("success"):
        print_warn("无法获取角色列表")
        print("")
        print("  可能的原因:")
        print("    1. 您的账号没有 cam:DescribeRoleList 权限")
        print("    2. 网络连接问题")
        print("")
        error_msg = role_list_result.get("error", {}).get("message", "未知错误")
        print(f"  错误详情: {error_msg}")
        print("")
        print("  您可以手动配置角色:")
        print('    export TENCENTCLOUD_ROLE_NAME="您的角色名称"')
        print("")
        print("  查看可用角色: https://console.cloud.tencent.com/cam/role")
        sys.exit(1)

    total = role_list_result.get("data", {}).get("TotalNum", 0)
    role_list = role_list_result.get("data", {}).get("List", [])

    # 筛选支持控制台登录的用户自定义角色
    console_roles = [
        r for r in role_list
        if r.get("ConsoleLogin") == 1 and r.get("RoleType") == "user"
    ]

    # ============== 无可用角色 → 创建新角色 ==============
    if not console_roles:
        print_warn("未检测到可用于控制台登录的角色")
        print("")
        print(f"  当前账号共有 {total} 个角色，但没有支持控制台登录的用户自定义角色。")
        print("")
        print(yellow("⚡ 安全提示：以下操作将修改您的 CAM（访问管理）配置，请仔细确认："))
        print("")
        print("  将要执行的 IAM 操作：")
        print(f"    1. [cam:CreateRole] 创建 CAM 角色：{green(ROLE_NAME)}")
        print(f"    2. 信任策略：仅允许当前账号 ({account_uin}) 扮演此角色")
        print("    3. 启用控制台登录（用于生成免密登录链接）")
        for policy in POLICY_NAMES:
            print(f"    4. [cam:AttachRolePolicy] 关联策略：{green(policy)}")
        print("")
        print("  权限范围说明：")
        print("    - 该角色仅用于免密登录腾讯云控制台查看智能顾问信息")
        print("    - QcloudAdvisorFullAccess 策略仅授权访问智能顾问服务，不影响其他云资源")
        print("    - 您可随时在 CAM 控制台删除此角色: https://console.cloud.tencent.com/cam/role")
        print("")

        try:
            confirm = input(green("是否同意创建？[y/N]: ")).strip()
        except (EOFError, KeyboardInterrupt):
            confirm = ""

        if confirm.lower() not in ("y", "yes"):
            print("")
            print_warn("已取消。您也可以手动创建角色：")
            print("")
            print("  1. 打开 CAM 控制台: https://console.cloud.tencent.com/cam/role")
            print(f"  2. 新建角色 → 腾讯云账户 → 角色名: {ROLE_NAME}")
            print("  3. 勾选「允许当前角色访问控制台」")
            for policy in POLICY_NAMES:
                print(f"  4. 关联策略: {policy}")
            print("")
            sys.exit(1)

        print("")
        print_step(f"正在创建角色 {ROLE_NAME}...")
        print("")

        # 构造信任策略
        trust_policy = json.dumps({
            "version": "2.0",
            "statement": [{
                "action": "name/sts:AssumeRole",
                "effect": "allow",
                "principal": {
                    "qcs": [f"qcs::cam::uin/{account_uin}:root"]
                }
            }]
        }, separators=(",", ":"))

        create_result = call_api(
            "cam", "cam.tencentcloudapi.com",
            "CreateRole", "2019-01-16",
            {
                "RoleName": ROLE_NAME,
                "PolicyDocument": trust_policy,
                "ConsoleLogin": 1,
                "Description": "腾讯云智能顾问助手角色（由 tencent-cloudq skill 创建）",
            },
        )

        if not create_result.get("success"):
            err_msg = create_result.get("error", {}).get("message", "未知错误")
            print_fail(f"角色创建失败: {err_msg}")
            print("")
            print("  请手动创建角色: https://console.cloud.tencent.com/cam/role")
            sys.exit(1)

        role_id = str(create_result.get("data", {}).get("RoleId", "unknown"))
        print_ok(f"角色创建成功（RoleId: {role_id}）")

        # 关联策略
        for policy_name in POLICY_NAMES:
            print("")
            print_step(f"正在关联策略 {policy_name}...")
            print("")

            attach_result = call_api(
                "cam", "cam.tencentcloudapi.com",
                "AttachRolePolicy", "2019-01-16",
                {"AttachRoleName": ROLE_NAME, "PolicyName": policy_name},
            )

            if attach_result.get("success"):
                print_ok(f"策略 {policy_name} 关联成功")
            else:
                err_msg = attach_result.get("error", {}).get("message", "未知错误")
                print_warn(f"策略关联失败: {err_msg}")
                print(f"  请到 CAM 控制台手动关联策略：")
                print(f"  https://console.cloud.tencent.com/cam/role/detail?roleName={ROLE_NAME}")

        # 保存配置并测试
        selected_role = ROLE_NAME
        role_arn = f"qcs::cam::uin/{account_uin}:roleName/{selected_role}"

        print("")
        print_step("步骤 5/6: 保存配置文件")
        print("")

        save_config(account_uin, selected_role, role_arn, auto_created=True)

        print_ok("配置文件已保存")
        print(f"  路径: {CONFIG_FILE}")
        print("")

        print_step("步骤 6/6: 测试角色扮演")
        print("")

        success, result_data = test_role(SCRIPT_DIR)

        if success:
            print_ok("角色扮演测试成功")
            print_config_summary(account_uin, selected_role, role_arn)
        else:
            print_warn("角色扮演测试失败（角色可能需要几秒生效）")
            print("")
            err_msg = result_data.get("error", {}).get("message", "未知错误")
            print(f"  错误详情: {err_msg}")
            print("")
            print(f"  配置已保存，请稍后重试或运行: python3 {SCRIPT_DIR / 'setup_role.py'}")
        sys.exit(0)

    # ============== 有可用角色 → 显示列表让用户选择 ==============
    print_ok(f"检测到 {len(console_roles)} 个可用角色（共 {total} 个角色）")
    print("")

    # 步骤 4：显示角色列表
    print_step("步骤 4/6: 选择角色")
    print("")
    print("可用角色列表（仅显示支持控制台登录的用户角色）:")
    print("━" * 74)

    for idx, role in enumerate(console_roles, 1):
        name = role.get("RoleName", "未知")
        desc = role.get("Description", "无描述") or "无描述"
        add_time = role.get("AddTime", "")
        print(f"  {bold_yellow(f'{idx:2d}')}. {bold_cyan(f'{name:<25}')} 创建于 {add_time}")
        print(f"    └─ {desc}")
        print("")

    print("━" * 74)

    # 步骤 5：选择角色
    print("")
    try:
        choice_str = input(green(f"请输入角色序号 [1-{len(console_roles)}]: ")).strip()
    except (EOFError, KeyboardInterrupt):
        choice_str = ""

    try:
        choice = int(choice_str)
    except ValueError:
        print_fail(f"无效的选择: {choice_str}")
        sys.exit(1)

    if choice < 1 or choice > len(console_roles):
        print_fail(f"无效的选择: {choice}")
        sys.exit(1)

    selected = console_roles[choice - 1]
    selected_role = selected.get("RoleName", "")
    selected_desc = selected.get("Description", "无描述") or "无描述"

    print("")
    print_ok(f"已选择角色: {selected_role}")
    print(f"  描述: {selected_desc}")
    print("")

    # 步骤 6：保存配置
    print_step("步骤 5/6: 保存配置文件")
    print("")

    role_arn = f"qcs::cam::uin/{account_uin}:roleName/{selected_role}"
    save_config(account_uin, selected_role, role_arn, auto_created=False)

    print_ok("配置文件已保存")
    print(f"  路径: {CONFIG_FILE}")
    print("")

    # 步骤 7：测试配置
    print_step("步骤 6/6: 测试角色扮演")
    print("")

    success, result_data = test_role(SCRIPT_DIR)

    if success:
        print_ok("角色扮演测试成功")
        print_config_summary(account_uin, selected_role, role_arn)
        print("")
        print("现在您可以:")
        print("")
        print("  1. 查询架构列表")
        print(f"     python3 {SCRIPT_DIR / 'tcloud_api.py'} advisor advisor.tencentcloudapi.com \\")
        print('       DescribeArchList 2020-07-21 \'{"PageNumber":1,"PageSize":10}\'')
        print("")
        print("  2. 查询架构详情")
        print(f"     python3 {SCRIPT_DIR / 'tcloud_api.py'} advisor advisor.tencentcloudapi.com \\")
        print('       DescribeArch 2020-07-21 \'{"ArchId":"arch-xxx","Username":"user"}\'')
        print("")
        print("  3. 如需重新配置")
        print(f"     python3 {SCRIPT_DIR / 'setup_role.py'}")
        print("")
    else:
        print_fail("角色扮演测试失败")
        print("")
        err_msg = result_data.get("error", {}).get("message", "未知错误")
        print(f"  错误详情: {err_msg}")
        print("")
        print_warn("可能的原因:")
        print("  1. 您的账号没有 AssumeRole 权限")
        print("  2. 角色信任策略不允许您的账号扮演")
        print("  3. 角色已被删除或禁用")
        print("")
        print("  请检查角色配置:")
        print("  https://console.cloud.tencent.com/cam/role")
        print("")
        print("  您也可以尝试手动配置:")
        print('  export TENCENTCLOUD_ROLE_NAME="其他角色名称"')


if __name__ == "__main__":
    main()


path: advisor-2020-07-21/简介.md
content: ## 概述
欢迎使用腾讯云智能顾问服务。该服务基于腾讯云海量运维专家经验，帮助客户打造卓越架构，实现便捷、灵活的一站式云上治理。
### 服务主要使用场景
- **持续架构优化**：对用户云架构进行卓越指数评级，并为用户的系统优化给出可操作的治理改进建议。
- **定期巡检扫描**：通过对用户云上资源进行定期风险巡检，为用户提供云资源健康状况的全局图景，帮助用户了解当前云资源设置与使用现状。
- **业务连续性评估**：检查超过服务限制 80% 的服务使用情况。并及时周知客户，提工单做扩容。
- **云上治理**：通过对用户云上资源进行容量监测、混沌演练等方式，帮助客户发现及验证云资源问题并推进问题治理，提升系统可用性。

本章节介绍的智能顾问 API 接口均为 API 3.0接口。您可以调用 API 对智能顾问进行操作，例如风险巡检、创建混沌演练任务等。
- 智能顾问-云巡检等功能支持的所有接口信息，请参见 [API 概览](https://cloud.tencent.com/document/product/1264/63122)。
- 智能顾问-混沌演练支持的所有接口信息，请参见 [API 概览](https://cloud.tencent.com/document/api/1500/71793)。


## 术语表
智能顾问 API 接口的常见术语请参见下表：


| 术语 | 描述 | 
|---------|---------|
| 评估项 | 评估项是智能顾问提供的一系列风险检测策略，可帮助您发现用云过程中的各类隐患风险，并进行评估及进一步治理。智能顾问的风险评估项包含安全、可靠、性能、成本、服务限制五个类别。您可以通过控制台的 [评估设置](https://console.cloud.tencent.com/advisor/switch) 界面查看各评估项详情。 | 


## 使用限制
具体参数限制，请参考各接口文档中的参数说明。


## API 快速入门
您可以使用 API Explorer 工具在线调用 API。
本文以查询评估项信息为例，通过 API Explorer 工具调用 API 接口的步骤如下：
1. 进入 [API Explorer](https://console.cloud.tencent.com/api/explorer) 工具页面。更多 API Explorer 工具使用信息，请参见 [使用 API Explorer](https://cloud.tencent.com/document/product/1278/46697)。
2. 调用 [DescribeStrategies](https://cloud.tencent.com/document/product/1264/63110) 接口，查询评估项信息。
3. 在返回结果中查看具体评估项详情。

path: advisor-2020-07-21/API 概览.md
content: ## 云架构控制台相关接口

| 接口名称 | 接口功能 | 频率限制（次/秒） |
|-|-|-|
| [CreateAdvisorAuthorization](/document/api/1264/127787) | 开启智能顾问授权 | 20 |

>! 以上给出的接口频率限制维度为`API + 接入地域 + 子账号`，有关限频更多说明参考：[API 频率限制说明](https://cloud.tencent.com/document/product/1278/109059)

path: advisor-2020-07-21/category_i18n.json
content: {
  "云架构控制台相关接口": {
    "en": ""
  }, 
  "其他接口": {
    "en": "Other APIs"
  }
}

path: advisor-2020-07-21/更新历史.md
content: ## 第 7 次发布

发布时间：2026-03-23 20:33:39

本次发布包含了以下内容：

改善已有的文档。

**删除接口：**

* DescribeStrategies
* DescribeTaskStrategyRisks

**删除数据结构：**

* Conditions
* KeyValue
* RiskFieldsDesc
* Strategies

## 第 6 次发布

发布时间：2026-01-23 01:07:51

本次发布包含了以下内容：

改善已有的文档。

新增接口：

* [CreateAdvisorAuthorization](/document/api/1264/127787)

## 第 5 次发布

发布时间：2023-09-14 01:05:50

本次发布包含了以下内容：

改善已有的文档。

修改接口：

* DescribeStrategies
	* **修改出参：** Strategies

新增数据结构：

* Conditions
* Strategies

**删除数据结构：**

* DescribeStrategie
* DescribeStrategiesCondition

## 第 4 次发布

发布时间：2022-06-08 06:03:05

本次发布包含了以下内容：

改善已有的文档。

修改接口：

* DescribeTaskStrategyRisks
	* 新增入参：Env, TaskType
	* 新增出参：ResourceCount

## 第 3 次发布

发布时间：2021-11-09 08:05:53

本次发布包含了以下内容：

改善已有的文档。

新增数据结构：

* KeyValue

修改数据结构：

* RiskFieldsDesc
	* 新增成员：FieldDict

## 第 2 次发布

发布时间：2021-10-29 08:00:17

本次发布包含了以下内容：

改善已有的文档。

修改接口：

* DescribeTaskStrategyRisks
	* 新增出参：Risks

## 第 1 次发布

发布时间：2021-10-20 16:41:59

本次发布包含了以下内容：

改善已有的文档。

新增接口：

* DescribeStrategies
* DescribeTaskStrategyRisks

新增数据结构：

* DescribeStrategie
* DescribeStrategiesCondition
* RiskFieldsDesc



path: advisor-2020-07-21/错误码.md
content: 
## 功能说明

如果返回结果中存在 Error 字段，则表示调用 API 接口失败。例如：

```
 {
    "Response": {
        "Error": {
            "Code": "AuthFailure.SignatureFailure",
            "Message": "The provided credentials could not be validated. Please check your signature is correct."
        },
        "RequestId": "ed93f3cb-f35e-473f-b9f3-0d451b8b79c6"
    }
}
```

Error 中的 Code 表示错误码，Message 表示该错误的具体信息。

## 错误码列表

### 公共错误码

| 错误码 | 说明 |
|--------|------|
| ActionOffline | 接口已下线。 |
| AuthFailure.InvalidAuthorization | 请求头部的 `Authorization` 不符合腾讯云标准。 |
| AuthFailure.InvalidSecretId | 密钥非法（不是云 API 密钥类型）。 |
| AuthFailure.MFAFailure | [MFA](https://cloud.tencent.com/document/product/378/12036) 错误。 |
| AuthFailure.SecretIdNotFound | 密钥不存在。请在 [控制台](https://console.cloud.tencent.com/cam/capi) 检查密钥是否已被删除或者禁用，如状态正常，请检查密钥是否填写正确，注意前后不得有空格。 |
| AuthFailure.SignatureExpire | 签名过期。Timestamp 和服务器时间相差不得超过五分钟，请检查本地时间是否和标准时间同步。 |
| AuthFailure.SignatureFailure | 签名错误。签名计算错误，请对照调用方式中的签名方法文档检查签名计算过程。 |
| AuthFailure.TokenFailure | token 错误。 |
| AuthFailure.UnauthorizedOperation | 请求未授权。请参考 [CAM](https://cloud.tencent.com/document/product/598) 文档对鉴权的说明。 |
| DryRunOperation | DryRun 操作，代表请求将会是成功的，只是多传了 DryRun 参数。 |
| FailedOperation | 操作失败。 |
| InternalError | 内部错误。 |
| InvalidAction | 接口不存在。 |
| InvalidParameter | 参数错误（包括参数格式、类型等错误）。 |
| InvalidParameterValue | 参数取值错误。 |
| InvalidRequest | 请求 body 的 multipart 格式错误。 |
| IpInBlacklist | IP 地址在黑名单中。 |
| IpNotInWhitelist | IP 地址不在白名单中。 |
| LimitExceeded | 超过配额限制。 |
| MissingParameter | 缺少参数。 |
| NoSuchProduct | 产品不存在 |
| NoSuchVersion | 接口版本不存在。 |
| RequestLimitExceeded | 请求的次数超过了频率限制。 |
| RequestLimitExceeded.GlobalRegionUinLimitExceeded | 主账号超过频率限制。 |
| RequestLimitExceeded.IPLimitExceeded | IP 限频。 |
| RequestLimitExceeded.UinLimitExceeded | 主账号限频。 |
| RequestSizeLimitExceeded | 请求包超过限制大小。 |
| ResourceInUse | 资源被占用。 |
| ResourceInsufficient | 资源不足。 |
| ResourceNotFound | 资源不存在。 |
| ResourceUnavailable | 资源不可用。 |
| ResponseSizeLimitExceeded | 返回包超过限制大小。 |
| ServiceUnavailable | 当前服务暂时不可用。 |
| UnauthorizedOperation | 未授权操作。 |
| UnknownParameter | 未知参数错误，用户多传未定义的参数会导致错误。 |
| UnsupportedOperation | 操作不支持。 |
| UnsupportedProtocol | http(s) 请求协议错误，只支持 GET 和 POST 请求。 |
| UnsupportedRegion | 接口不支持所传地域。 |



path: advisor-2020-07-21/云架构控制台相关接口/开启智能顾问授权.md
content: ## 1. 接口描述

接口请求域名： advisor.tencentcloudapi.com 。

开启智能顾问授权。会同步开启报告解读和云架构协作权限

默认接口请求频率限制：20次/秒。

<div class="rno-api-explorer">
    <div class="rno-api-explorer-inner">
        <div class="rno-api-explorer-hd">
            <div class="rno-api-explorer-title">
                推荐使用 API Explorer
            </div>
            <a href="https://console.cloud.tencent.com/api/explorer?Product=advisor&Version=2020-07-21&Action=CreateAdvisorAuthorization" class="rno-api-explorer-btn" hotrep="doc.api.explorerbtn"><i class="rno-icon-explorer"></i>点击调试</a>
        </div>
        <div class="rno-api-explorer-body">
            <div class="rno-api-explorer-cont">
                API Explorer 提供了在线调用、签名验证、SDK 代码生成和快速检索接口等能力。您可查看每次调用的请求内容和返回结果以及自动生成 SDK 调用示例。
            </div>
        </div>
    </div>
</div>

## 2. 输入参数

以下请求参数列表仅列出了接口请求参数和部分公共参数，完整公共参数列表见 [公共请求参数](/document/api/1264/63116)。

| 参数名称 | 必选 | 类型 | 描述 |
|---------|---------|---------|---------|
| Action | 是 | String | [公共参数](/document/api/1264/63116)，本接口取值：CreateAdvisorAuthorization。 |
| Version | 是 | String | [公共参数](/document/api/1264/63116)，本接口取值：2020-07-21。 |
| Region | 否 | String | [公共参数](/document/api/1264/63116)，此参数为可选参数。 |

## 3. 输出参数

| 参数名称 | 类型 | 描述 |
|---------|---------|---------|
| Message | String | 返回信息<br/>示例值：Already authorized|
| RequestId | String | 唯一请求 ID，由服务端生成，每次请求都会返回（若请求因其他原因未能抵达服务端，则该次请求不会获得 RequestId）。定位问题时需要提供该次请求的 RequestId。|

## 4. 示例

### 示例1 已开通

#### 输入示例

```
POST / HTTP/1.1
Host: advisor.tencentcloudapi.com
Content-Type: application/json
X-TC-Action: CreateAdvisorAuthorization
<公共请求参数>

{}
```

#### 输出示例

```json
{
    "Response": {
        "Message": "Already authorized",
        "RequestId": "97aa2227-1814-4d39-9470-dadca094be1f"
    }
}
```


## 5. 开发者资源

### 腾讯云 API 平台

[腾讯云 API 平台](https://cloud.tencent.com/api) 是综合 API 文档、错误码、API Explorer 及 SDK 等资源的统一查询平台，方便您从同一入口查询及使用腾讯云提供的所有 API 服务。

### API Inspector

用户可通过 [API Inspector](https://cloud.tencent.com/document/product/1278/49361) 查看控制台每一步操作关联的 API 调用情况，并自动生成各语言版本的 API 代码，也可前往 [API Explorer](https://cloud.tencent.com/document/product/1278/46697) 进行在线调试。

### SDK

云 API 3.0 提供了配套的开发工具集（SDK），支持多种编程语言，能更方便的调用 API。
* Tencent Cloud SDK 3.0 for Python: [GitHub](https://github.com/TencentCloud/tencentcloud-sdk-python/blob/master/tencentcloud/advisor/v20200721/advisor_client.py), [Gitee](https://gitee.com/TencentCloud/tencentcloud-sdk-python/blob/master/tencentcloud/advisor/v20200721/advisor_client.py)
* Tencent Cloud SDK 3.0 for Java: [GitHub](https://github.com/TencentCloud/tencentcloud-sdk-java/blob/master/src/main/java/com/tencentcloudapi/advisor/v20200721/AdvisorClient.java), [Gitee](https://gitee.com/TencentCloud/tencentcloud-sdk-java/blob/master/src/main/java/com/tencentcloudapi/advisor/v20200721/AdvisorClient.java)
* Tencent Cloud SDK 3.0 for PHP: [GitHub](https://github.com/TencentCloud/tencentcloud-sdk-php/blob/master/src/TencentCloud/Advisor/V20200721/AdvisorClient.php), [Gitee](https://gitee.com/TencentCloud/tencentcloud-sdk-php/blob/master/src/TencentCloud/Advisor/V20200721/AdvisorClient.php)
* Tencent Cloud SDK 3.0 for Go: [GitHub](https://github.com/TencentCloud/tencentcloud-sdk-go/blob/master/tencentcloud/advisor/v20200721/client.go), [Gitee](https://gitee.com/TencentCloud/tencentcloud-sdk-go/blob/master/tencentcloud/advisor/v20200721/client.go)
* Tencent Cloud SDK 3.0 for Node.js: [GitHub](https://github.com/TencentCloud/tencentcloud-sdk-nodejs/blob/master/src/services/advisor/v20200721/advisor_client.ts), [Gitee](https://gitee.com/TencentCloud/tencentcloud-sdk-nodejs/blob/master/src/services/advisor/v20200721/advisor_client.ts)
* Tencent Cloud SDK 3.0 for .NET: [GitHub](https://github.com/TencentCloud/tencentcloud-sdk-dotnet/blob/master/TencentCloud/Advisor/V20200721/AdvisorClient.cs), [Gitee](https://gitee.com/TencentCloud/tencentcloud-sdk-dotnet/blob/master/TencentCloud/Advisor/V20200721/AdvisorClient.cs)
* Tencent Cloud SDK 3.0 for C++: [GitHub](https://github.com/TencentCloud/tencentcloud-sdk-cpp/blob/master/advisor/src/v20200721/AdvisorClient.cpp), [Gitee](https://gitee.com/TencentCloud/tencentcloud-sdk-cpp/blob/master/advisor/src/v20200721/AdvisorClient.cpp)
* Tencent Cloud SDK 3.0 for Ruby: [GitHub](https://github.com/TencentCloud/tencentcloud-sdk-ruby/blob/master/tencentcloud-sdk-advisor/lib/v20200721/client.rb), [Gitee](https://gitee.com/TencentCloud/tencentcloud-sdk-ruby/blob/master/tencentcloud-sdk-advisor/lib/v20200721/client.rb)

### 命令行工具

* [Tencent Cloud CLI 3.0](https://cloud.tencent.com/document/product/440/6176)

## 6. 错误码

该接口暂无业务逻辑相关的错误码，其他错误码详见 [公共错误码](/document/api/1264/63120#.E5.85.AC.E5.85.B1.E9.94.99.E8.AF.AF.E7.A0.81)。


path: advisor-2020-07-21/调用方式/请求结构.md
content: ## 1. 服务地址

API 支持就近地域接入，本产品就近地域接入域名为 cvm.tencentcloudapi.com ，也支持指定地域域名访问，例如广州地域的域名为 cvm.ap-guangzhou.tencentcloudapi.com 。

推荐使用就近地域接入域名。根据调用接口时客户端所在位置，会自动解析到**最近的**某个具体地域的服务器。例如在广州发起请求，会自动解析到广州的服务器，效果和指定 cvm.ap-guangzhou.tencentcloudapi.com 是一致的。

**注意：对时延敏感的业务，建议指定带地域的域名。**

**注意：域名是 API 的接入点，并不代表产品或者接口实际提供服务的地域。产品支持的地域列表请在调用方式/公共参数文档中查阅，接口支持的地域请在接口文档输入参数中查阅。**

目前支持的域名列表为：

| 接入地域 | 域名 |
|----------|------|
| 就近地域接入（推荐，只支持非金融区）| cvm.tencentcloudapi.com |
| 华南地区（广州） | cvm.ap-guangzhou.tencentcloudapi.com |
| 华东地区（上海） | cvm.ap-shanghai.tencentcloudapi.com |
| 华东地区（南京） | cvm.ap-nanjing.tencentcloudapi.com |
| 华北地区（北京） | cvm.ap-beijing.tencentcloudapi.com |
| 西南地区（成都） | cvm.ap-chengdu.tencentcloudapi.com |
| 西南地区（重庆） | cvm.ap-chongqing.tencentcloudapi.com |
| 港澳台地区（中国香港） | cvm.ap-hongkong.tencentcloudapi.com |
| 亚太东南（新加坡） | cvm.ap-singapore.tencentcloudapi.com |
| 亚太东南（雅加达） | cvm.ap-jakarta.tencentcloudapi.com |
| 亚太东南（曼谷） | cvm.ap-bangkok.tencentcloudapi.com |
| 亚太东北（首尔） | cvm.ap-seoul.tencentcloudapi.com |
| 亚太东北（东京） | cvm.ap-tokyo.tencentcloudapi.com |
| 美国东部（弗吉尼亚） | cvm.na-ashburn.tencentcloudapi.com |
| 美国西部（硅谷） | cvm.na-siliconvalley.tencentcloudapi.com |
| 南美地区（圣保罗） | cvm.sa-saopaulo.tencentcloudapi.com |
| 欧洲地区（法兰克福） | cvm.eu-frankfurt.tencentcloudapi.com |

**注意：由于[金融区](https://cloud.tencent.com/document/product/304/2766)和非金融区是隔离不互通的，因此当访问金融区服务时（公共参数 Region 为金融区地域），需要同时指定带金融区地域的域名，最好和 Region 的地域保持一致。**

| 金融区接入地域 | 金融区域名 |
|----------|------|
| 华东地区（上海金融）| cvm.ap-shanghai-fsi.tencentcloudapi.com |
| 华南地区（深圳金融）| cvm.ap-shenzhen-fsi.tencentcloudapi.com |

## 2. 通信协议

腾讯云 API 的所有接口均通过 HTTPS 进行通信，提供高安全性的通信通道。

## 3. 请求方法

支持的 HTTP 请求方法:

* POST（推荐）
* GET

POST 请求支持的 Content-Type 类型：

* application/json（推荐），必须使用签名方法 v3（TC3-HMAC-SHA256）。
* application/x-www-form-urlencoded，必须使用签名方法 v1（HmacSHA1 或 HmacSHA256）。
* multipart/form-data（仅部分接口支持），必须使用签名方法 v3（TC3-HMAC-SHA256）。

GET 请求的请求包大小不得超过32KB。POST 请求使用签名方法 v1（HmacSHA1、HmacSHA256）时不得超过1MB。POST 请求使用签名方法 v3（TC3-HMAC-SHA256）时支持10MB。

## 4. 字符编码

均使用`UTF-8`编码。


path: advisor-2020-07-21/调用方式/返回结果.md
content: 云 API 3.0 接口默认返回 JSON 数据，返回非 JSON 格式的接口会在文档中做出说明。返回 JSON 数据时最大限制为 50 MB，如果返回的数据超过最大限制，请求会失败并返回内部错误。请根据接口文档中给出的过滤功能（例如时间范围）或者分页功能，控制返回数据不要过大。

注意：目前只要请求被服务端正常处理了，响应的 HTTP 状态码均为200。例如返回的消息体里的错误码是签名失败，但 HTTP 状态码是200，而不是401。

## 正确返回结果

以云服务器的接口查看实例状态列表 (DescribeInstancesStatus) 2017-03-12 版本为例，若调用成功，其可能的返回如下为：
```json
    {
        "Response": {
            "TotalCount": 0,
            "InstanceStatusSet": [],
            "RequestId": "b5b41468-520d-4192-b42f-595cc34b6c1c"
        }
    }
```
* Response 及其内部的 RequestId 是固定的字段，无论请求成功与否，只要 API 处理了，则必定会返回。
* RequestId 用于一个 API 请求的唯一标识，如果 API 出现异常，可以联系 [腾讯云客服](https://cloud.tencent.com/act/event/connect-service) 或 [提交工单](https://console.cloud.tencent.com/workorder/category)，并提供该 ID 来解决问题。
* 除了固定的字段外，其余均为具体接口定义的字段，不同的接口所返回的字段参见接口文档中的定义。此例中的 TotalCount 和 InstanceStatusSet 均为 DescribeInstancesStatus 接口定义的字段，由于调用请求的用户暂时还没有云服务器实例，因此 TotalCount 在此情况下的返回值为 0， InstanceStatusSet 列表为空。

## 错误返回结果

若调用失败，其返回值示例如下为：
```json
    {
        "Response": {
            "Error": {
                "Code": "AuthFailure.SignatureFailure",
                "Message": "The provided credentials could not be validated. Please check your signature is correct."
            },
            "RequestId": "ed93f3cb-f35e-473f-b9f3-0d451b8b79c6"
        }
    }
```
* Error 的出现代表着该请求调用失败。Error 字段连同其内部的 Code 和 Message 字段在调用失败时是必定返回的。
* Code 表示具体出错的错误码，当请求出错时可以先根据该错误码在公共错误码和当前接口对应的错误码列表里面查找对应原因和解决方案。
* Message 显示出了这个错误发生的具体原因，随着业务发展或体验优化，此文本可能会经常保持变更或更新，用户不应依赖这个返回值。
* RequestId 用于一个 API 请求的唯一标识，如果 API 出现异常，可以联系 [腾讯云客服](https://cloud.tencent.com/act/event/connect-service) 或 [提交工单](https://console.cloud.tencent.com/workorder/category)，并提供该 ID 来解决问题。


## 公共错误码


返回结果中如果存在 Error 字段，则表示调用 API 接口失败。 Error 中的 Code 字段表示错误码，所有业务都可能出现的错误码为公共错误码。完整的错误码列表请参考本产品“API 文档”目录下的“错误码”页面。


path: advisor-2020-07-21/调用方式/参数类型.md
content: 目前腾讯云 API 3.0 输入参数和输出参数支持如下几种数据格式：

- String: 字符串。
- Integer：整型，上限为无符号64位整数。SDK 3.0 不同编程语言支持的类型有所差异，建议以所使用编程语言的最大整型定义，例如 `Golang` 的 `uint64`。
- Boolean：布尔型。
- Float：浮点型。
- Double：双精度浮点型。
- Date：字符串，日期格式。例如：2022-01-01。
- Timestamp：字符串，时间格式。例如：2022-01-01 00:00:00。
- Timestamp ISO8601：ISO 8601 是由国际标准化组织（International Organization for Standardization，ISO）发布的关于日期和时间格式的国际标准，对应国标 [《GB/T 7408-2005数据元和交换格式信息交换日期和时间表示法》](https://std.samr.gov.cn/gb/search/gbDetailed?id=71F772D77FD1D3A7E05397BE0A0AB82A)。建议以所使用编程语言的标准库进行格式解析。例如：2022-01-01T00:00:00+08:00。
- Binary：二进制内容，需要以特定协议请求和解析。


path: advisor-2020-07-21/调用方式/签名方法 v3.md
content: **以下文档说明了签名方法 v3 的签名过程，但仅在您编写自己的代码来调用腾讯云 API 时才有用。我们推荐您使用 [腾讯云 API Explorer](https://console.cloud.tencent.com/api/explorer) ，[腾讯云 SDK](https://cloud.tencent.com/document/sdk) 和 [腾讯云命令行工具（TCCLI）](https://cloud.tencent.com/product/cli) 等开发者工具，从而无需学习如何对 API 请求进行签名。**

<div class="rno-api-explorer">
    <div class="rno-api-explorer-inner">
        <div class="rno-api-explorer-hd">
            <div class="rno-api-explorer-title">
                推荐使用 API Explorer
            </div>
            <a href="https://console.cloud.tencent.com/api/explorer?SignVersion=api3v3" class="rno-api-explorer-btn" hotrep="doc.api.explorerbtn"><i class="rno-icon-explorer"></i>点击调试</a>
        </div>
        <div class="rno-api-explorer-body">
            <div class="rno-api-explorer-cont">
                您可以通过 API Explorer 的【签名串生成】模块查看每个接口签名的生成过程。
            </div>
        </div>
    </div>
</div>

腾讯云 API 会对每个请求进行身份验证，用户需要使用安全凭证，经过特定的步骤对请求进行签名（Signature），每个请求都需要在公共参数中指定该签名结果并以指定的方式和格式发送请求。

## 为什么要进行签名

签名通过以下方式帮助保护请求：

1. 验证请求者的身份
签名确保请求是由持有有效访问密钥的人发送的。请参阅控制台 [云 API 密钥](https://console.cloud.tencent.com/capi) 页面获取密钥相关信息。

2. 保护传输中的数据
为了防止请求在传输过程中被篡改，腾讯云 API 会使用请求参数来计算请求的哈希值，并将生成的哈希值加密后作为请求的一部分，发送到腾讯云 API 服务器。服务器会使用收到的请求参数以同样的过程计算哈希值，并验证请求中的哈希值。如果请求被篡改，将导致哈希值不一致，腾讯云 API 将拒绝本次请求。

签名方法 v3 （TC3-HMAC-SHA256）功能上覆盖了以前的签名方法 v1，而且更安全，支持更大的请求，支持 JSON 格式，POST 请求支持传空数组和空字符串，性能有一定提升，推荐使用该签名方法计算签名。

首次接触，建议使用 [API Explorer](https://console.cloud.tencent.com/api/explorer) 中的“签名串生成”功能，选择签名版本为“API 3.0 签名 v3”，可以对生成签名过程进行验证，也可直接生成 SDK 代码。推荐使用腾讯云 API 配套的 8 种常见的编程语言 SDK，已经封装了签名和请求过程，均已开源，支持 [Python](https://github.com/TencentCloud/tencentcloud-sdk-python)、[Java](https://github.com/TencentCloud/tencentcloud-sdk-java)、[PHP](https://github.com/TencentCloud/tencentcloud-sdk-php)、[Go](https://github.com/TencentCloud/tencentcloud-sdk-go)、[NodeJS](https://github.com/TencentCloud/tencentcloud-sdk-nodejs)、[.NET](https://github.com/TencentCloud/tencentcloud-sdk-dotnet)、[C++](https://github.com/TencentCloud/tencentcloud-sdk-cpp)、[Ruby](https://github.com/TencentCloud/tencentcloud-sdk-ruby)。

## 申请安全凭证

本文使用的安全凭证为密钥，密钥包括 SecretId 和 SecretKey。每个用户最多可以拥有两对密钥。

* SecretId：用于标识 API 调用者身份，可以简单类比为用户名。
* SecretKey：用于验证 API 调用者的身份，可以简单类比为密码。
* **用户必须严格保管安全凭证，避免泄露，否则将危及财产安全。如已泄露，请立刻禁用该安全凭证。**

申请安全凭证的具体步骤如下：

1. 登录 [腾讯云管理中心控制台](https://console.cloud.tencent.com/) 。
2. 前往 [云API密钥](https://console.cloud.tencent.com/capi) 的控制台页面。
3. 在 [云API密钥](https://console.cloud.tencent.com/capi) 页面，单击【新建密钥】创建一对密钥。

## 签名版本 v3 签名过程

云 API 支持 GET 和 POST 请求。对于GET方法，只支持 `Content-Type: application/x-www-form-urlencoded` 协议格式。对于POST方法，目前支持 `Content-Type: application/json` 以及 `Content-Type: multipart/form-data` 两种协议格式，json 格式绝大多数接口均支持，multipart 格式只有特定接口支持，此时该接口不能使用 json 格式调用，参考具体业务接口文档说明。推荐使用 POST 请求，因为两者的结果并无差异，但 GET 请求只支持 32 KB 以内的请求包。

下面以云服务器查询广州区实例列表作为例子，分步骤介绍签名的计算过程。我们选择该接口是因为：

1. 云服务器默认已开通，该接口很常用；
2. 该接口是只读的，不会改变现有资源的状态；
3. 接口覆盖的参数种类较全，可以演示包含数据结构的数组如何使用。

在示例中，不论公共参数或者接口的参数，我们尽量选择容易犯错的情况。在实际调用接口时，请根据实际情况来，每个接口的参数并不相同，不要照抄这个例子的参数和值。此外，这里只展示了部分公共参数和接口输入参数，用户可以根据实际需要添加其他参数，例如 Language 和 Token 公共参数（在 HTTP 头部设置，添加 X-TC- 前缀）。

假设用户的 SecretId 和 SecretKey 分别是：`AKID********************************` 和 `********************************`。用户想查看广州区云服务器名为“未命名”的主机状态，只返回一条数据。则请求可能为：

```bash
curl -X POST https://cvm.tencentcloudapi.com \
-H "[REDACTED_SECRET] Credential=AKID********************************/2019-02-25/cvm/tc3_request, SignedHeaders=content-type;host;x-tc-action, Signature=10b1a37a7301a02ca19a647ad722d5e43b4b3cff309d421d85b46093f6ab6c4f" \
-H "Content-Type: application/json; charset=utf-8" \
-H "Host: cvm.tencentcloudapi.com" \
-H "X-TC-Action: DescribeInstances" \
-H "X-TC-Timestamp: 1551113065" \
-H "X-TC-Version: 2017-03-12" \
-H "X-TC-Region: ap-guangzhou" \
-d '{"Limit": 1, "Filters": [{"Values": ["\u672a\u547d\u540d"], "Name": "instance-name"}]}'
```

下面详细解释签名计算过程。

### 1. 拼接规范请求串

按如下伪代码格式拼接规范请求串（CanonicalRequest）：

```
CanonicalRequest =
    HTTPRequestMethod + '\n' +
    CanonicalURI + '\n' +
    CanonicalQueryString + '\n' +
    CanonicalHeaders + '\n' +
    SignedHeaders + '\n' +
    HashedRequestPayload
```

| 字段名称 | 解释 |
|-|-|
| HTTPRequestMethod | HTTP 请求方法（GET、POST ）。此示例取值为 `POST`。 |
| CanonicalURI | URI 参数，API 3.0 固定为正斜杠（/）。 |
| CanonicalQueryString | 发起 HTTP 请求 URL 中的查询字符串，对于 POST 请求，固定为空字符串""，对于 GET 请求，则为 URL 中问号（?）后面的字符串内容，例如：Limit=10&Offset=0。<br/>注意：CanonicalQueryString 需要参考 [RFC3986](https://tools.ietf.org/html/rfc3986) 进行 URLEncode 编码（特殊字符编码后需大写字母），字符集 UTF-8。推荐使用编程语言标准库进行编码。 |
| CanonicalHeaders | 参与签名的头部信息，至少包含 host 和 content-type 两个头部，也可加入其他头部参与签名以提高自身请求的唯一性和安全性，此示例额外增加了接口名头部。<br/>拼接规则：<ol><li>头部 key 和 value 统一转成小写，并去掉首尾空格，按照 key:value\n 格式拼接；</li><li>多个头部，按照头部 key（小写）的 ASCII 升序进行拼接。</li></ol>此示例计算结果是 `content-type:application/json; charset=utf-8\nhost:cvm.tencentcloudapi.com\nx-tc-action:describeinstances\n`。<br/>注意：content-type 必须和实际发送的相符合，有些编程语言网络库即使未指定也会自动添加 charset 值，如果签名时和发送时不一致，服务器会返回签名校验失败。 |
| SignedHeaders | 参与签名的头部信息，说明此次请求有哪些头部参与了签名，和 CanonicalHeaders 包含的头部内容是一一对应的。content-type 和 host 为必选头部。<br/>拼接规则：<ol><li>头部 key 统一转成小写；</li><li>多个头部 key（小写）按照 ASCII 升序进行拼接，并且以分号（;）分隔。 </li></ol>此示例为 content-type;host;x-tc-action |
| HashedRequestPayload | 请求正文（payload，即 body，此示例为 `{"Limit": 1, "Filters": [{"Values": ["\u672a\u547d\u540d"], "Name": "instance-name"}]}`）的哈希值，计算伪代码为 Lowercase(HexEncode(Hash.SHA256(RequestPayload)))，即对 HTTP 请求正文做 SHA256 哈希，然后十六进制编码，最后编码串转换成小写字母。对于 GET 请求，RequestPayload 固定为空字符串。此示例计算结果是 `35e9c5b0e3ae67532d3c9f17ead6c90222632e5b1ff7f6e89887f1398934f064`。 |

根据以上规则，示例中得到的规范请求串如下：

```
POST
/

content-type:application/json; charset=utf-8
host:cvm.tencentcloudapi.com
x-tc-action:describeinstances

content-type;host;x-tc-action
35e9c5b0e3ae67532d3c9f17ead6c90222632e5b1ff7f6e89887f1398934f064
```

### 2. 拼接待签名字符串

按如下格式拼接待签名字符串：

```
StringToSign =
    Algorithm + "\n" +
    RequestTimestamp + "\n" +
    CredentialScope + "\n" +
    HashedCanonicalRequest
```

| 字段名称 | 解释 |
|-|-|
| Algorithm | 签名算法，目前固定为 `TC3-HMAC-SHA256`。 |
| RequestTimestamp | 请求时间戳，即请求头部的公共参数 X-TC-Timestamp 取值，取当前时间 UNIX 时间戳，精确到秒。此示例取值为 `1551113065`。 |
| CredentialScope | 凭证范围，格式为 Date/service/tc3_request，包含日期、所请求的服务和终止字符串（tc3_request）。**Date 为 UTC 标准时间的日期，取值需要和公共参数 X-TC-Timestamp 换算的 UTC 标准时间日期一致**；service 为产品名，必须与调用的产品域名一致。此示例计算结果是 2019-02-25/cvm/tc3_request。 |
| HashedCanonicalRequest | 前述步骤拼接所得规范请求串的哈希值，计算伪代码为 Lowercase(HexEncode(Hash.SHA256(CanonicalRequest)))。此示例计算结果是 `7019a55be8395899b900fb5564e4200d984910f34794a27cb3fb7d10ff6a1e84`。 |

注意：

> 1. Date 必须从时间戳 X-TC-Timestamp 计算得到，且时区为 UTC+0。如果加入系统本地时区信息，例如东八区，将导致白天和晚上调用成功，但是凌晨时调用必定失败。假设时间戳为 1551113065，在东八区的时间是 2019-02-26 00:44:25，但是计算得到的 Date 取 UTC+0 的日期应为 2019-02-25，而不是 2019-02-26。
> 2. Timestamp 必须是当前系统时间，且需确保系统时间和标准时间是同步的，如果相差超过五分钟则必定失败。如果长时间不和标准时间同步，可能运行一段时间后，请求失败，返回签名过期错误。

根据以上规则，示例中得到的待签名字符串如下：

```
TC3-HMAC-SHA256
1551113065
2019-02-25/cvm/tc3_request
7019a55be8395899b900fb5564e4200d984910f34794a27cb3fb7d10ff6a1e84
```

### 3. 计算签名
1）计算派生签名密钥，伪代码如下：

```
SecretKey = "********************************"
SecretDate = HMAC_SHA256("TC3" + SecretKey, Date)
SecretService = HMAC_SHA256(SecretDate, Service)
SecretSigning = HMAC_SHA256(SecretService, "tc3_request")
```

派生出的密钥 `SecretDate`、`SecretService` 和 `SecretSigning` 是二进制的数据，可能包含不可打印字符，将其转为十六进制字符串打印的输出分别为：da98fb70dcf6b112dc21038d1eeeb3a95c74b4dcb12c1131f864f6066bd02be0，8d70cbefb03939f929db64d32dc2ba89b1095620119fe3e050e2b18c5bd2752f，b596b923aad85185e2d1f6659d2a062e0a86731226e021e61bfe06f7ed05f5af。

请注意，不同的编程语言，HMAC 库函数中参数顺序可能不一样，请以实际情况为准。此处的伪代码密钥参数 key 在前，消息参数 data 在后。通常标准库函数会提供二进制格式的返回值，也可能会提供打印友好的十六进制格式的返回值，此处使用的是二进制格式。

| 字段名称 | 解释 |
|-|-|
| SecretKey | 原始的 SecretKey，即 `********************************`。 |
| Date | 即 Credential 中的 Date 字段信息。此示例取值为 `2019-02-25`。 |
| Service | 即 Credential 中的 Service 字段信息。此示例取值为 `cvm`。 |

2）计算签名，伪代码如下：

```
Signature = HexEncode(HMAC_SHA256(SecretSigning, StringToSign))
```

此示例计算结果是 `10b1a37a7301a02ca19a647ad722d5e43b4b3cff309d421d85b46093f6ab6c4f`。

### 4. 拼接 Authorization

按如下格式拼接 Authorization：

```
Authorization =
    Algorithm + ' ' +
    'Credential=' + SecretId + '/' + CredentialScope + ', ' +
    'SignedHeaders=' + SignedHeaders + ', ' +
    'Signature=' + Signature
```

| 字段名称 | 解释 |
|-|-|
| Algorithm | 签名方法，固定为 `TC3-HMAC-SHA256`。 |
| SecretId | 密钥对中的 SecretId，即 `AKID********************************`。 |
| CredentialScope | 见上文，凭证范围。此示例计算结果是 `2019-02-25/cvm/tc3_request`。 |
| SignedHeaders | 见上文，参与签名的头部信息。此示例取值为 `content-type;host;x-tc-action`。 |
| Signature | 签名值。此示例计算结果是 `10b1a37a7301a02ca19a647ad722d5e43b4b3cff309d421d85b46093f6ab6c4f`。 |

根据以上规则，示例中得到的值为：

```
TC3-HMAC-SHA256 Credential=AKID********************************/2019-02-25/cvm/tc3_request, SignedHeaders=content-type;host;x-tc-action, Signature=10b1a37a7301a02ca19a647ad722d5e43b4b3cff309d421d85b46093f6ab6c4f
```

最终完整的调用信息如下：

```
POST https://cvm.tencentcloudapi.com/
[REDACTED_SECRET] Credential=AKID********************************/2019-02-25/cvm/tc3_request, SignedHeaders=content-type;host;x-tc-action, Signature=10b1a37a7301a02ca19a647ad722d5e43b4b3cff309d421d85b46093f6ab6c4f
Content-Type: application/json; charset=utf-8
Host: cvm.tencentcloudapi.com
X-TC-Action: DescribeInstances
X-TC-Version: 2017-03-12
X-TC-Timestamp: 1551113065
X-TC-Region: ap-guangzhou

{"Limit": 1, "Filters": [{"Values": ["\u672a\u547d\u540d"], "Name": "instance-name"}]}
```

>!
请求发送时的 HTTP 头部（Header）和请求体（Payload）必须和签名计算过程中的内容完全一致，否则会返回签名不一致错误。可以通过打印实际请求内容，网络抓包等方式对比排查。

##  签名演示

在实际调用 API 3.0 时，推荐使用配套的腾讯云 SDK 3.0 ，SDK 封装了签名的过程，开发时只关注产品提供的具体接口即可。详细信息参见 [SDK 中心](https://cloud.tencent.com/document/sdk)。当前支持的编程语言有：

* [Python](https://github.com/TencentCloud/tencentcloud-sdk-python)
* [Java](https://github.com/TencentCloud/tencentcloud-sdk-java)
* [PHP](https://github.com/TencentCloud/tencentcloud-sdk-php)
* [Go](https://github.com/TencentCloud/tencentcloud-sdk-go)
* [NodeJS](https://github.com/TencentCloud/tencentcloud-sdk-nodejs)
* [.NET](https://github.com/TencentCloud/tencentcloud-sdk-dotnet)
* [C++](https://github.com/TencentCloud/tencentcloud-sdk-cpp)
* [Ruby](https://github.com/TencentCloud/tencentcloud-sdk-ruby)

下面提供了不同产品的生成签名 demo，您可以找到对应的产品参考签名的生成：

* [Signature Demo](https://github.com/TencentCloud/signature-process-demo)

为了更清楚地解释签名过程，下面以实际编程语言为例，将上述的签名过程完整实现。请求的域名、调用的接口和参数的取值都以上述签名过程为准，代码只为解释签名过程，并不具备通用性，实际开发请尽量使用 SDK。

### Java

```java
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.util.TreeMap;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

public class TencentCloudAPITC3Demo {
    private final static Charset UTF8 = StandardCharsets.UTF_8;
    // 需要设置环境变量 TENCENTCLOUD_SECRET_ID，值为示例的 AKID********************************
    private final static String SECRET_ID = System.getenv("TENCENTCLOUD_SECRET_ID");
    // 需要设置环境变量 TENCENTCLOUD_SECRET_KEY，值为示例的 ********************************
    private final static String SECRET_KEY = System.getenv("TENCENTCLOUD_SECRET_KEY");
    private final static String CT_JSON = "application/json; charset=utf-8";

    public static byte[] hmac256(byte[] key, String msg) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, mac.getAlgorithm());
        mac.init(secretKeySpec);
        return mac.doFinal(msg.getBytes(UTF8));
    }

    public static String sha256Hex(String s) throws Exception {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] d = md.digest(s.getBytes(UTF8));
        return DatatypeConverter.printHexBinary(d).toLowerCase();
    }

    public static void main(String[] args) throws Exception {
        String service = "cvm";
        String host = "cvm.tencentcloudapi.com";
        String region = "ap-guangzhou";
        String action = "DescribeInstances";
        String version = "2017-03-12";
        String algorithm = "TC3-HMAC-SHA256";
        String timestamp = "1551113065";
        //String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        // 注意时区，否则容易出错
        sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
        String date = sdf.format(new Date(Long.valueOf(timestamp + "000")));

        // ************* 步骤 1：拼接规范请求串 *************
        String httpRequestMethod = "POST";
        String canonicalUri = "/";
        String canonicalQueryString = "";
        String canonicalHeaders = "content-type:application/json; charset=utf-8\n"
                + "host:" + host + "\n" + "x-tc-action:" + action.toLowerCase() + "\n";
        String signedHeaders = "content-type;host;x-tc-action";

        String payload = "{\"Limit\": 1, \"Filters\": [{\"Values\": [\"\\u672a\\u547d\\u540d\"], \"Name\": \"instance-name\"}]}";
        String hashedRequestPayload = sha256Hex(payload);
        String canonicalRequest = httpRequestMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n"
                + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestPayload;
        System.out.println(canonicalRequest);

        // ************* 步骤 2：拼接待签名字符串 *************
        String credentialScope = date + "/" + service + "/" + "tc3_request";
        String hashedCanonicalRequest = sha256Hex(canonicalRequest);
        String stringToSign = algorithm + "\n" + timestamp + "\n" + credentialScope + "\n" + hashedCanonicalRequest;
        System.out.println(stringToSign);

        // ************* 步骤 3：计算签名 *************
        byte[] secretDate = hmac256(("TC3" + SECRET_KEY).getBytes(UTF8), date);
        byte[] secretService = hmac256(secretDate, service);
        byte[] secretSigning = hmac256(secretService, "tc3_request");
        String signature = DatatypeConverter.printHexBinary(hmac256(secretSigning, stringToSign)).toLowerCase();
        System.out.println(signature);

        // ************* 步骤 4：拼接 Authorization *************
        String authorization = algorithm + " " + "Credential=" + SECRET_ID + "/" + credentialScope + ", "
                + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature;
        System.out.println(authorization);

        TreeMap<String, String> headers = new TreeMap<String, String>();
        headers.put("Authorization", authorization);
        headers.put("Content-Type", CT_JSON);
        headers.put("Host", host);
        headers.put("X-TC-Action", action);
        headers.put("X-TC-Timestamp", timestamp);
        headers.put("X-TC-Version", version);
        headers.put("X-TC-Region", region);

        StringBuilder sb = new StringBuilder();
        sb.append("curl -X POST https://").append(host)
        .append(" -H \"Authorization: ").append(authorization).append("\"")
        .append(" -H \"Content-Type: application/json; charset=utf-8\"")
        .append(" -H \"Host: ").append(host).append("\"")
        .append(" -H \"X-TC-Action: ").append(action).append("\"")
        .append(" -H \"X-TC-Timestamp: ").append(timestamp).append("\"")
        .append(" -H \"X-TC-Version: ").append(version).append("\"")
        .append(" -H \"X-TC-Region: ").append(region).append("\"")
        .append(" -d '").append(payload).append("'");
        System.out.println(sb.toString());
    }
}
```

### Python

```python
# -*- coding: utf-8 -*-
import hashlib, hmac, json, os, sys, time
from datetime import datetime

# 密钥参数
# 需要设置环境变量 TENCENTCLOUD_SECRET_ID，值为示例的 AKID********************************
secret_id = os.environ.get("TENCENTCLOUD_SECRET_ID")
# 需要设置环境变量 TENCENTCLOUD_SECRET_KEY，值为示例的 ********************************
secret_key = os.environ.get("TENCENTCLOUD_SECRET_KEY")

service = "cvm"
host = "cvm.tencentcloudapi.com"
endpoint = "https://" + host
region = "ap-guangzhou"
action = "DescribeInstances"
version = "2017-03-12"
algorithm = "TC3-HMAC-SHA256"
#timestamp = int(time.time())
timestamp = 1551113065
date = datetime.utcfromtimestamp(timestamp).strftime("%Y-%m-%d")
params = {"Limit": 1, "Filters": [{"Values": [u"未命名"], "Name": "instance-name"}]}

# ************* 步骤 1：拼接规范请求串 *************
http_request_method = "POST"
canonical_uri = "/"
canonical_querystring = ""
ct = "application/json; charset=utf-8"
payload = json.dumps(params)
canonical_headers = "content-type:%s\nhost:%s\nx-tc-action:%s\n" % (ct, host, action.lower())
signed_headers = "content-type;host;x-tc-action"
hashed_request_payload = hashlib.sha256(payload.encode("utf-8")).hexdigest()
canonical_request = (http_request_method + "\n" +
                     canonical_uri + "\n" +
                     canonical_querystring + "\n" +
                     canonical_headers + "\n" +
                     signed_headers + "\n" +
                     hashed_request_payload)
print(canonical_request)

# ************* 步骤 2：拼接待签名字符串 *************
credential_scope = date + "/" + service + "/" + "tc3_request"
hashed_canonical_request = hashlib.sha256(canonical_request.encode("utf-8")).hexdigest()
string_to_sign = (algorithm + "\n" +
                  str(timestamp) + "\n" +
                  credential_scope + "\n" +
                  hashed_canonical_request)
print(string_to_sign)


# ************* 步骤 3：计算签名 *************
# 计算签名摘要函数
def sign(key, msg):
    return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()
secret_date = sign(("TC3" + secret_key).encode("utf-8"), date)
secret_service = sign(secret_date, service)
secret_signing = sign(secret_service, "tc3_request")
signature = hmac.new(secret_signing, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest()
print(signature)

# ************* 步骤 4：拼接 Authorization *************
authorization = (algorithm + " " +
                 "Credential=" + secret_id + "/" + credential_scope + ", " +
                 "SignedHeaders=" + signed_headers + ", " +
                 "Signature=" + signature)
print(authorization)

print('curl -X POST ' + endpoint
      + ' -H "Authorization: ' + authorization + '"'
      + ' -H "Content-Type: application/json; charset=utf-8"'
      + ' -H "Host: ' + host + '"'
      + ' -H "X-TC-Action: ' + action + '"'
      + ' -H "X-TC-Timestamp: ' + str(timestamp) + '"'
      + ' -H "X-TC-Version: ' + version + '"'
      + ' -H "X-TC-Region: ' + region + '"'
      + " -d '" + payload + "'")
```

### Golang

```go
package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "os"
    "strings"
    "time"
)

func sha256hex(s string) string {
    b := sha256.Sum256([]byte(s))
    return hex.EncodeToString(b[:])
}

func hmacsha256(s, key string) string {
    hashed := hmac.New(sha256.New, []byte(key))
    hashed.Write([]byte(s))
    return string(hashed.Sum(nil))
}

func main() {
    // 需要设置环境变量 TENCENTCLOUD_SECRET_ID，值为示例的 AKID********************************
    secretId := os.Getenv("TENCENTCLOUD_SECRET_ID")
    // 需要设置环境变量 TENCENTCLOUD_SECRET_KEY，值为示例的 ********************************
    secretKey := os.Getenv("TENCENTCLOUD_SECRET_KEY")
    host := "cvm.tencentcloudapi.com"
    algorithm := "TC3-HMAC-SHA256"
    service := "cvm"
    version := "2017-03-12"
    action := "DescribeInstances"
    region := "ap-guangzhou"
    //var timestamp int64 = time.Now().Unix()
    var timestamp int64 = 1551113065

    // step 1: build canonical request string
    httpRequestMethod := "POST"
    canonicalURI := "/"
    canonicalQueryString := ""
    canonicalHeaders := fmt.Sprintf("content-type:%s\nhost:%s\nx-tc-action:%s\n",
        "application/json; charset=utf-8", host, strings.ToLower(action))
    signedHeaders := "content-type;host;x-tc-action"
    payload := `{"Limit": 1, "Filters": [{"Values": ["\u672a\u547d\u540d"], "Name": "instance-name"}]}`
    hashedRequestPayload := sha256hex(payload)
    canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s",
        httpRequestMethod,
        canonicalURI,
        canonicalQueryString,
        canonicalHeaders,
        signedHeaders,
        hashedRequestPayload)
    fmt.Println(canonicalRequest)

    // step 2: build string to sign
    date := time.Unix(timestamp, 0).UTC().Format("2006-01-02")
    credentialScope := fmt.Sprintf("%s/%s/tc3_request", date, service)
    hashedCanonicalRequest := sha256hex(canonicalRequest)
    string2sign := fmt.Sprintf("%s\n%d\n%s\n%s",
        algorithm,
        timestamp,
        credentialScope,
        hashedCanonicalRequest)
    fmt.Println(string2sign)

    // step 3: sign string
    secretDate := hmacsha256(date, "TC3"+secretKey)
    secretService := hmacsha256(service, secretDate)
    secretSigning := hmacsha256("tc3_request", secretService)
    signature := hex.EncodeToString([]byte(hmacsha256(string2sign, secretSigning)))
    fmt.Println(signature)

    // step 4: build authorization
    authorization := fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s",
        algorithm,
        secretId,
        credentialScope,
        signedHeaders,
        signature)
    fmt.Println(authorization)

    curl := fmt.Sprintf(`curl -X POST https://%s\
 -H "Authorization: %s"\
 -H "Content-Type: application/json; charset=utf-8"\
 -H "Host: %s" -H "X-TC-Action: %s"\
 -H "X-TC-Timestamp: %d"\
 -H "X-TC-Version: %s"\
 -H "X-TC-Region: %s"\
 -d '%s'`, host, authorization, host, action, timestamp, version, region, payload)
    fmt.Println(curl)
}
```

### PHP

```php
<?php
// 需要设置环境变量 TENCENTCLOUD_SECRET_ID，值为示例的 AKID********************************
$secretId = getenv("TENCENTCLOUD_SECRET_ID");
// 需要设置环境变量 TENCENTCLOUD_SECRET_KEY，值为示例的 ********************************
$secretKey = getenv("TENCENTCLOUD_SECRET_KEY");
$host = "cvm.tencentcloudapi.com";
$service = "cvm";
$version = "2017-03-12";
$action = "DescribeInstances";
$region = "ap-guangzhou";
// $timestamp = time();
$timestamp = 1551113065;
$algorithm = "TC3-HMAC-SHA256";

// step 1: build canonical request string
$httpRequestMethod = "POST";
$canonicalUri = "/";
$canonicalQueryString = "";
$canonicalHeaders = implode("\n", [
    "content-type:application/json; charset=utf-8",
    "host:".$host,
    "x-tc-action:".strtolower($action),
    ""
]);
$signedHeaders = implode(";", [
    "content-type",
    "host",
    "x-tc-action",
]);
$payload = '{"Limit": 1, "Filters": [{"Values": ["\u672a\u547d\u540d"], "Name": "instance-name"}]}';
$hashedRequestPayload = hash("SHA256", $payload);
$canonicalRequest = $httpRequestMethod."\n"
    .$canonicalUri."\n"
    .$canonicalQueryString."\n"
    .$canonicalHeaders."\n"
    .$signedHeaders."\n"
    .$hashedRequestPayload;
echo $canonicalRequest.PHP_EOL;

// step 2: build string to sign
$date = gmdate("Y-m-d", $timestamp);
$credentialScope = $date."/".$service."/tc3_request";
$hashedCanonicalRequest = hash("SHA256", $canonicalRequest);
$stringToSign = $algorithm."\n"
    .$timestamp."\n"
    .$credentialScope."\n"
    .$hashedCanonicalRequest;
echo $stringToSign.PHP_EOL;

// step 3: sign string
$secretDate = hash_hmac("SHA256", $date, "TC3".$secretKey, true);
$secretService = hash_hmac("SHA256", $service, $secretDate, true);
$secretSigning = hash_hmac("SHA256", "tc3_request", $secretService, true);
$signature = hash_hmac("SHA256", $stringToSign, $secretSigning);
echo $signature.PHP_EOL;

// step 4: build authorization
$authorization = $algorithm
    ." Credential=".$secretId."/".$credentialScope
    .", SignedHeaders=".$signedHeaders.", Signature=".$signature;
echo $authorization.PHP_EOL;

$curl = "curl -X POST https://".$host
    .' -H "Authorization: '.$authorization.'"'
    .' -H "Content-Type: application/json; charset=utf-8"'
    .' -H "Host: '.$host.'"'
    .' -H "X-TC-Action: '.$action.'"'
    .' -H "X-TC-Timestamp: '.$timestamp.'"'
    .' -H "X-TC-Version: '.$version.'"'
    .' -H "X-TC-Region: '.$region.'"'
    ." -d '".$payload."'";
echo $curl.PHP_EOL;
```

### Ruby

```ruby
# -*- coding: UTF-8 -*-
# require ruby>=2.3.0
require 'digest'
require 'json'
require 'time'
require 'openssl'

# 密钥参数
# 需要设置环境变量 TENCENTCLOUD_SECRET_ID，值为示例的 AKID********************************
secret_id = ENV["TENCENTCLOUD_SECRET_ID"]
# 需要设置环境变量 TENCENTCLOUD_SECRET_KEY，值为示例的 ********************************
secret_key = ENV["TENCENTCLOUD_SECRET_KEY"]

service = 'cvm'
host = 'cvm.tencentcloudapi.com'
endpoint = 'https://' + host
region = 'ap-guangzhou'
action = 'DescribeInstances'
version = '2017-03-12'
algorithm = 'TC3-HMAC-SHA256'
# timestamp = Time.now.to_i
timestamp = 1551113065
date = Time.at(timestamp).utc.strftime('%Y-%m-%d')

# ************* 步骤 1：拼接规范请求串 *************
http_request_method = 'POST'
canonical_uri = '/'
canonical_querystring = ''
canonical_headers = "content-type:application/json; charset=utf-8\nhost:#{host}\nx-tc-action:#{action.downcase}\n"
signed_headers = 'content-type;host;x-tc-action'
# params = { 'Limit' => 1, 'Filters' => [{ 'Name' => 'instance-name', 'Values' => ['未命名'] }] }
# payload = JSON.generate(params, { 'ascii_only' => true, 'space' => ' ' })
# json will generate in random order, to get specified result in example, we hard-code it here.
payload = '{"Limit": 1, "Filters": [{"Values": ["\u672a\u547d\u540d"], "Name": "instance-name"}]}'
hashed_request_payload = Digest::SHA256.hexdigest(payload)
canonical_request = [
                        http_request_method,
                        canonical_uri,
                        canonical_querystring,
                        canonical_headers,
                        signed_headers,
                        hashed_request_payload,
                    ].join("\n")

puts canonical_request

# ************* 步骤 2：拼接待签名字符串 *************
credential_scope = date + '/' + service + '/' + 'tc3_request'
hashed_request_payload = Digest::SHA256.hexdigest(canonical_request)
string_to_sign = [
                    algorithm,
                    timestamp.to_s,
                    credential_scope,
                    hashed_request_payload,
                 ].join("\n")
puts string_to_sign

# ************* 步骤 3：计算签名 *************
digest = OpenSSL::Digest.new('sha256')
secret_date = OpenSSL::HMAC.digest(digest, 'TC3' + secret_key, date)
secret_service = OpenSSL::HMAC.digest(digest, secret_date, service)
secret_signing = OpenSSL::HMAC.digest(digest, secret_service, 'tc3_request')
signature = OpenSSL::HMAC.hexdigest(digest, secret_signing, string_to_sign)
puts signature

# ************* 步骤 4：拼接 Authorization *************
authorization = "#{algorithm} Credential=#{secret_id}/#{credential_scope}, SignedHeaders=#{signed_headers}, Signature=#{signature}"
puts authorization

puts  'curl -X POST ' + endpoint \
      + ' -H "Authorization: ' + authorization + '"' \
      + ' -H "Content-Type: application/json; charset=utf-8"' \
      + ' -H "Host: ' + host + '"' \
      + ' -H "X-TC-Action: ' + action + '"' \
      + ' -H "X-TC-Timestamp: ' + timestamp.to_s + '"' \
      + ' -H "X-TC-Version: ' + version + '"' \
      + ' -H "X-TC-Region: ' + region + '"' \
      + " -d '" + payload + "'"
```

### DotNet

```csharp
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;

public class Application
{
    public static string SHA256Hex(string s)
    {
        using (SHA256 algo = SHA256.Create())
        {
            byte[] hashbytes = algo.ComputeHash(Encoding.UTF8.GetBytes(s));
            StringBuilder builder = new StringBuilder();
            for (int i = 0; i < hashbytes.Length; ++i)
            {
                builder.Append(hashbytes[i].ToString("x2"));
            }
            return builder.ToString();
        }
    }

    public static byte[] HmacSHA256(byte[] key, byte[] msg)
    {
        using (HMACSHA256 mac = new HMACSHA256(key))
        {
            return mac.ComputeHash(msg);
        }
    }

    public static Dictionary<String, String> BuildHeaders(string secretid,
        string secretkey, string service, string endpoint, string region,
        string action, string version, DateTime date, string requestPayload)
    {
        string datestr = date.ToString("yyyy-MM-dd");
        DateTime startTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
        long requestTimestamp = (long)Math.Round((date - startTime).TotalMilliseconds, MidpointRounding.AwayFromZero) / 1000;
        // ************* 步骤 1：拼接规范请求串 *************
        string algorithm = "TC3-HMAC-SHA256";
        string httpRequestMethod = "POST";
        string canonicalUri = "/";
        string canonicalQueryString = "";
        string contentType = "application/json";
        string canonicalHeaders = "content-type:" + contentType + "; charset=utf-8\n"
            + "host:" + endpoint + "\n"
            + "x-tc-action:" + action.ToLower() + "\n";
        string signedHeaders = "content-type;host;x-tc-action";
        string hashedRequestPayload = SHA256Hex(requestPayload);
        string canonicalRequest = httpRequestMethod + "\n"
            + canonicalUri + "\n"
            + canonicalQueryString + "\n"
            + canonicalHeaders + "\n"
            + signedHeaders + "\n"
            + hashedRequestPayload;
        Console.WriteLine(canonicalRequest);

        // ************* 步骤 2：拼接待签名字符串 *************
        string credentialScope = datestr + "/" + service + "/" + "tc3_request";
        string hashedCanonicalRequest = SHA256Hex(canonicalRequest);
        string stringToSign = algorithm + "\n"
            + requestTimestamp.ToString() + "\n"
            + credentialScope + "\n"
            + hashedCanonicalRequest;
        Console.WriteLine(stringToSign);

        // ************* 步骤 3：计算签名 *************
        byte[] tc3SecretKey = Encoding.UTF8.GetBytes("TC3" + secretkey);
        byte[] secretDate = HmacSHA256(tc3SecretKey, Encoding.UTF8.GetBytes(datestr));
        byte[] secretService = HmacSHA256(secretDate, Encoding.UTF8.GetBytes(service));
        byte[] secretSigning = HmacSHA256(secretService, Encoding.UTF8.GetBytes("tc3_request"));
        byte[] signatureBytes = HmacSHA256(secretSigning, Encoding.UTF8.GetBytes(stringToSign));
        string signature = BitConverter.ToString(signatureBytes).Replace("-", "").ToLower();
        Console.WriteLine(signature);

        // ************* 步骤 4：拼接 Authorization *************
        string authorization = algorithm + " "
            + "Credential=" + secretid + "/" + credentialScope + ", "
            + "SignedHeaders=" + signedHeaders + ", "
            + "Signature=" + signature;
        Console.WriteLine(authorization);

        Dictionary<string, string> headers = new Dictionary<string, string>();
        headers.Add("Authorization", authorization);
        headers.Add("Host", endpoint);
        headers.Add("Content-Type", contentType + "; charset=utf-8");
        headers.Add("X-TC-Timestamp", requestTimestamp.ToString());
        headers.Add("X-TC-Version", version);
        headers.Add("X-TC-Action", action);
        headers.Add("X-TC-Region", region);
        return headers;
    }
    public static void Main(string[] args)
    {
        // 密钥参数
        // 需要设置环境变量 TENCENTCLOUD_SECRET_ID，值为示例的 AKID********************************
        string SECRET_ID = Environment.GetEnvironmentVariable("TENCENTCLOUD_SECRET_ID");
        // 需要设置环境变量 TENCENTCLOUD_SECRET_KEY，值为示例的 ********************************
        string SECRET_KEY = Environment.GetEnvironmentVariable("TENCENTCLOUD_SECRET_KEY");

        string service = "cvm";
        string endpoint = "cvm.tencentcloudapi.com";
        string region = "ap-guangzhou";
        string action = "DescribeInstances";
        string version = "2017-03-12";

        // 此处由于示例规范的原因，采用时间戳2019-02-26 00:44:25，此参数作为示例，如果在项目中，您应当使用：
        // DateTime date = DateTime.UtcNow;
        // 注意时区，建议此时间统一采用UTC时间戳，否则容易出错
        DateTime date = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(1551113065);
        string requestPayload = "{\"Limit\": 1, \"Filters\": [{\"Values\": [\"\\u672a\\u547d\\u540d\"], \"Name\": \"instance-name\"}]}";

        Dictionary<string, string> headers = BuildHeaders(SECRET_ID, SECRET_KEY, service
            , endpoint, region, action, version, date, requestPayload);

        Console.WriteLine("POST https://cvm.tencentcloudapi.com");
        foreach (KeyValuePair<string, string> kv in headers)
        {
            Console.WriteLine(kv.Key + ": " + kv.Value);
        }
        Console.WriteLine();
        Console.WriteLine(requestPayload);
    }
}

```

### NodeJS

```javascript
const crypto = require('crypto');

function sha256(message, secret = '', encoding) {
    const hmac = crypto.createHmac('sha256', secret)
    return hmac.update(message).digest(encoding)
}

function getHash(message, encoding = 'hex') {
    const hash = crypto.createHash('sha256')
    return hash.update(message).digest(encoding)
}

function getDate(timestamp) {
    const date = new Date(timestamp * 1000)
    const year = date.getUTCFullYear()
    const month = ('0' + (date.getUTCMonth() + 1)).slice(-2)
    const day = ('0' + date.getUTCDate()).slice(-2)
    return `${year}-${month}-${day}`
}

function main(){
    // 密钥参数
    // 需要设置环境变量 TENCENTCLOUD_SECRET_ID，值为示例的 AKID********************************
    const SECRET_ID = process.env.TENCENTCLOUD_SECRET_ID
    // 需要设置环境变量 TENCENTCLOUD_SECRET_KEY，值为示例的 ********************************
    const SECRET_KEY = process.env.TENCENTCLOUD_SECRET_KEY

    const endpoint = "cvm.tencentcloudapi.com"
    const service = "cvm"
    const region = "ap-guangzhou"
    const action = "DescribeInstances"
    const version = "2017-03-12"
    //const timestamp = getTime()
    const timestamp = 1551113065
    //时间处理, 获取世界时间日期
    const date = getDate(timestamp)

    // ************* 步骤 1：拼接规范请求串 *************
    const payload = "{\"Limit\": 1, \"Filters\": [{\"Values\": [\"\\u672a\\u547d\\u540d\"], \"Name\": \"instance-name\"}]}"

    const hashedRequestPayload = getHash(payload);
    const httpRequestMethod = "POST"
    const canonicalUri = "/"
    const canonicalQueryString = ""
    const canonicalHeaders = "content-type:application/json; charset=utf-8\n"
        + "host:" + endpoint + "\n"
        + "x-tc-action:" + action.toLowerCase() + "\n"
    const signedHeaders = "content-type;host;x-tc-action"

    const canonicalRequest = httpRequestMethod + "\n"
                         + canonicalUri + "\n"
                         + canonicalQueryString + "\n"
                         + canonicalHeaders + "\n"
                         + signedHeaders + "\n"
                         + hashedRequestPayload
    console.log(canonicalRequest)

    // ************* 步骤 2：拼接待签名字符串 *************
    const algorithm = "TC3-HMAC-SHA256"
    const hashedCanonicalRequest = getHash(canonicalRequest);
    const credentialScope = date + "/" + service + "/" + "tc3_request"
    const stringToSign = algorithm + "\n" +
                    timestamp + "\n" +
                    credentialScope + "\n" +
                    hashedCanonicalRequest
    console.log(stringToSign)

    // ************* 步骤 3：计算签名 *************
    const kDate = sha256(date, 'TC3' + SECRET_KEY)
    const kService = sha256(service, kDate)
    const kSigning = sha256('tc3_request', kService)
    const signature = sha256(stringToSign, kSigning, 'hex')
    console.log(signature)

    // ************* 步骤 4：拼接 Authorization *************
    const authorization = algorithm + " " +
                    "Credential=" + SECRET_ID + "/" + credentialScope + ", " +
                    "SignedHeaders=" + signedHeaders + ", " +
                    "Signature=" + signature
    console.log(authorization)

    const curlcmd = 'curl -X POST ' + "https://" + endpoint
                           + ' -H "Authorization: ' + authorization + '"'
                           + ' -H "Content-Type: application/json; charset=utf-8"'
                           + ' -H "Host: ' + endpoint + '"'
                           + ' -H "X-TC-Action: ' + action + '"'
                           + ' -H "X-TC-Timestamp: ' + timestamp.toString() + '"'
                           + ' -H "X-TC-Version: ' + version + '"'
                           + ' -H "X-TC-Region: ' + region + '"'
                           + " -d '" + payload + "'"
    console.log(curlcmd)
}
main()
```

### C++

```cpp
#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <iomanip>
#include <sstream>
#include <string>
#include <stdio.h>
#include <time.h>
#include <openssl/sha.h>
#include <openssl/hmac.h>

using namespace std;

string get_data(int64_t &timestamp)
{
    string utcDate;
    char buff[20] = {0};
    // time_t timenow;
    struct tm sttime;
    sttime = *gmtime(&timestamp);
    strftime(buff, sizeof(buff), "%Y-%m-%d", &sttime);
    utcDate = string(buff);
    return utcDate;
}

string int2str(int64_t n)
{
    std::stringstream ss;
    ss << n;
    return ss.str();
}

string sha256Hex(const string &str)
{
    char buf[3];
    unsigned char hash[SHA256_DIGEST_LENGTH];
    SHA256_CTX sha256;
    SHA256_Init(&sha256);
    SHA256_Update(&sha256, str.c_str(), str.size());
    SHA256_Final(hash, &sha256);
    std::string NewString = "";
    for(int i = 0; i < SHA256_DIGEST_LENGTH; i++)
    {
        snprintf(buf, sizeof(buf), "%02x", hash[i]);
        NewString = NewString + buf;
    }
    return NewString;
}

string HmacSha256(const string &key, const string &input)
{
    unsigned char hash[32];

    HMAC_CTX *h;
#if OPENSSL_VERSION_NUMBER < 0x10100000L
    HMAC_CTX hmac;
    HMAC_CTX_init(&hmac);
    h = &hmac;
#else
    h = HMAC_CTX_new();
#endif

    HMAC_Init_ex(h, &key[0], key.length(), EVP_sha256(), NULL);
    HMAC_Update(h, ( unsigned char* )&input[0], input.length());
    unsigned int len = 32;
    HMAC_Final(h, hash, &len);

#if OPENSSL_VERSION_NUMBER < 0x10100000L
    HMAC_CTX_cleanup(h);
#else
    HMAC_CTX_free(h);
#endif

    std::stringstream ss;
    ss << std::setfill('0');
    for (int i = 0; i < len; i++)
    {
        ss  << hash[i];
    }

    return (ss.str());
}

string HexEncode(const string &input)
{
    static const char* const lut = "0123456789abcdef";
    size_t len = input.length();

    string output;
    output.reserve(2 * len);
    for (size_t i = 0; i < len; ++i)
    {
        const unsigned char c = input[i];
        output.push_back(lut[c >> 4]);
        output.push_back(lut[c & 15]);
    }
    return output;
}

int main()
{
    // 密钥参数
    // 需要设置环境变量 TENCENTCLOUD_SECRET_ID，值为示例的 AKID********************************
    string SECRET_ID = getenv("TENCENTCLOUD_SECRET_ID");
    // 需要设置环境变量 TENCENTCLOUD_SECRET_KEY，值为示例的 ********************************
    string SECRET_KEY = getenv("TENCENTCLOUD_SECRET_KEY");

    string service = "cvm";
    string host = "cvm.tencentcloudapi.com";
    string region = "ap-guangzhou";
    string action = "DescribeInstances";
    string version = "2017-03-12";
    int64_t timestamp = 1551113065;
    string date = get_data(timestamp);

    // ************* 步骤 1：拼接规范请求串 *************
    string httpRequestMethod = "POST";
    string canonicalUri = "/";
    string canonicalQueryString = "";
    string lower = action;
    std::transform(action.begin(), action.end(), lower.begin(), ::tolower);
    string canonicalHeaders = string("content-type:application/json; charset=utf-8\n")
            + "host:" + host + "\n"
            + "x-tc-action:" + lower + "\n";
    string signedHeaders = "content-type;host;x-tc-action";
    string payload = "{\"Limit\": 1, \"Filters\": [{\"Values\": [\"\\u672a\\u547d\\u540d\"], \"Name\": \"instance-name\"}]}";
    string hashedRequestPayload = sha256Hex(payload);
    string canonicalRequest = httpRequestMethod + "\n"
            + canonicalUri + "\n"
            + canonicalQueryString + "\n"
            + canonicalHeaders + "\n"
            + signedHeaders + "\n"
            + hashedRequestPayload;
    cout << canonicalRequest << endl;

    // ************* 步骤 2：拼接待签名字符串 *************
    string algorithm = "TC3-HMAC-SHA256";
    string RequestTimestamp = int2str(timestamp);
    string credentialScope = date + "/" + service + "/" + "tc3_request";
    string hashedCanonicalRequest = sha256Hex(canonicalRequest);
    string stringToSign = algorithm + "\n" + RequestTimestamp + "\n" + credentialScope + "\n" + hashedCanonicalRequest;
    cout << stringToSign << endl;

    // ************* 步骤 3：计算签名 ***************
    string kKey = "TC3" + SECRET_KEY;
    string kDate = HmacSha256(kKey, date);
    string kService = HmacSha256(kDate, service);
    string kSigning = HmacSha256(kService, "tc3_request");
    string signature = HexEncode(HmacSha256(kSigning, stringToSign));
    cout << signature << endl;

    // ************* 步骤 4：拼接 Authorization *************
    string authorization = algorithm + " " + "Credential=" + SECRET_ID + "/" + credentialScope + ", "
            + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature;
    cout << authorization << endl;

    string curlcmd = "curl -X POST https://" + host + "\n"
                   + " -H \"Authorization: " + authorization + "\"\n"
                   + " -H \"Content-Type: application/json; charset=utf-8\"" + "\n"
                   + " -H \"Host: " + host + "\"\n"
                   + " -H \"X-TC-Action: " + action + "\"\n"
                   + " -H \"X-TC-Timestamp: " + RequestTimestamp + "\"\n"
                   + " -H \"X-TC-Version: " + version + "\"\n"
                   + " -H \"X-TC-Region: " + region + "\"\n"
                   + " -d '" + payload + "\'";
    cout << curlcmd << endl;
    return 0;
};
```

### C

```c
#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdint.h>
#include <openssl/sha.h>
#include <openssl/hmac.h>

void get_utc_date(int64_t timestamp, char* utc, int len)
{
    // time_t timenow;
    struct tm sttime;
    sttime = *gmtime(&timestamp);
    strftime(utc, len, "%Y-%m-%d", &sttime);
}

void sha256_hex(const char* str, char* result)
{
    char buf[3];
    unsigned char hash[SHA256_DIGEST_LENGTH];
    SHA256_CTX sha256;
    SHA256_Init(&sha256);
    SHA256_Update(&sha256, str, strlen(str));
    SHA256_Final(hash, &sha256);
    for(int i = 0; i < SHA256_DIGEST_LENGTH; i++)
    {
        snprintf(buf, sizeof(buf), "%02x", hash[i]);
        strcat(result, buf);
    }
}

void hmac_sha256(const char* key, int key_len,
                 const char* input, int input_len,
                 unsigned char* output, unsigned int* output_len)
{
    HMAC_CTX *h;
#if OPENSSL_VERSION_NUMBER < 0x10100000L
    HMAC_CTX hmac;
    HMAC_CTX_init(&hmac);
    h = &hmac;
#else
    h = HMAC_CTX_new();
#endif

    HMAC_Init_ex(h, key, key_len, EVP_sha256(), NULL);
    HMAC_Update(h, ( unsigned char* )input, input_len);
    HMAC_Final(h, output, output_len);


#if OPENSSL_VERSION_NUMBER < 0x10100000L
    HMAC_CTX_cleanup(h);
#else
    HMAC_CTX_free(h);
#endif

}

void hex_encode(const char* input, int input_len, char* output)
{
    static const char* const lut = "0123456789abcdef";

    char add_out[128] = {0};
    char temp[2] = {0};
    for (size_t i = 0; i < input_len; ++i)
    {
        const unsigned char c = input[i];
        temp[0] = lut[c >> 4];
        strcat(add_out, temp);
        temp[0] = lut[c & 15];
        strcat(add_out, temp);
    }
    strncpy(output, add_out, 128);
}

void lowercase(const char * src, char * dst)
{
    for (int i = 0; src[i]; i++)
    {
        dst[i] = tolower(src[i]);
    }
}

int main()
{
    // 密钥参数
    // 需要设置环境变量 TENCENTCLOUD_SECRET_ID，值为示例的 AKID********************************
    const char* SECRET_ID = getenv("TENCENTCLOUD_SECRET_ID");
    // 需要设置环境变量 TENCENTCLOUD_SECRET_KEY，值为示例的 ********************************
    const char* SECRET_KEY = getenv("TENCENTCLOUD_SECRET_KEY");
    const char* service = "cvm";
    const char* host = "cvm.tencentcloudapi.com";
    const char* region = "ap-guangzhou";
    const char* action = "DescribeInstances";
    const char* version = "2017-03-12";
    int64_t timestamp = 1551113065;
    char date[20] = {0};
    get_utc_date(timestamp, date, sizeof(date));

    // ************* 步骤 1：拼接规范请求串 *************
    const char* http_request_method = "POST";
    const char* canonical_uri = "/";
    const char* canonical_query_string = "";
    char canonical_headers[100] = {"content-type:application/json; charset=utf-8\nhost:"};
    strcat(canonical_headers, host);
    strcat(canonical_headers, "\nx-tc-action:");
    char value[100] = {0};
    lowercase(action, value);
    strcat(canonical_headers, value);
    strcat(canonical_headers, "\n");
    const char* signed_headers = "content-type;host;x-tc-action";
    const char* payload = "{\"Limit\": 1, \"Filters\": [{\"Values\": [\"\\u672a\\u547d\\u540d\"], \"Name\": \"instance-name\"}]}";
    char hashed_request_payload[100] = {0};
    sha256_hex(payload, hashed_request_payload);

    char canonical_request[256] = {0};
    sprintf(canonical_request, "%s\n%s\n%s\n%s\n%s\n%s", http_request_method,
            canonical_uri, canonical_query_string, canonical_headers,
            signed_headers, hashed_request_payload);
    printf("%s\n", canonical_request);


    // ************* 步骤 2：拼接待签名字符串 *************
    const char*  algorithm = "TC3-HMAC-SHA256";
    char request_timestamp[16] = {0};
    sprintf(request_timestamp, "%d", timestamp);
    char credential_scope[64] = {0};
    strcat(credential_scope, date);
    sprintf(credential_scope, "%s/%s/tc3_request", date, service);
    char hashed_canonical_request[100] = {0};
    sha256_hex(canonical_request, hashed_canonical_request);
    char string_to_sign[256] = {0};
    sprintf(string_to_sign, "%s\n%s\n%s\n%s", algorithm, request_timestamp,
            credential_scope, hashed_canonical_request);
    printf("%s\n", string_to_sign);

    // ************* 步骤 3：计算签名 ***************
    char k_key[64] = {0};
    sprintf(k_key, "%s%s", "TC3", SECRET_KEY);
    unsigned char k_date[64] = {0};
    unsigned int output_len = 0;
    hmac_sha256(k_key, strlen(k_key), date, strlen(date), k_date, &output_len);
    unsigned char k_service[64] = {0};
    hmac_sha256(k_date, output_len, service, strlen(service), k_service, &output_len);
    unsigned char k_signing[64] = {0};
    hmac_sha256(k_service, output_len, "tc3_request", strlen("tc3_request"), k_signing, &output_len);
    unsigned char k_hmac_sha_sign[64] = {0};
    hmac_sha256(k_signing, output_len, string_to_sign, strlen(string_to_sign), k_hmac_sha_sign, &output_len);

    char signature[128] = {0};
    hex_encode(k_hmac_sha_sign, output_len, signature);
    printf("%s\n", signature);

    // ************* 步骤 4：拼接 Authorization *************
    char authorization[512] = {0};
    sprintf(authorization, "%s Credential=%s/%s, SignedHeaders=%s, Signature=%s",
            algorithm, SECRET_ID, credential_scope, signed_headers, signature);
    printf("%s\n", authorization);

    char curlcmd[10240] = {0};
    sprintf(curlcmd, "curl -X POST https://%s\n \
            -H \"Authorization: %s\"\n \
            -H \"Content-Type: application/json; charset=utf-8\"\n \
            -H \"Host: %s\"\n \
            -H \"X-TC-Action: %s\"\n \
            -H \"X-TC-Timestamp: %s\"\n \
            -H \"X-TC-Version: %s\"\n \
            -H \"X-TC-Region: %s\"\n \
            -d \'%s\'",
            host, authorization, host, action, request_timestamp, version, region, payload);
    printf("%s\n", curlcmd);
    return 0;
}
```

### 其他语言

- Lua: [GitHub](https://github.com/TencentCloud/signature-process-demo/tree/main/signature-v3/lua)
- Swift: [GitHub](https://github.com/TencentCloud/signature-process-demo/tree/main/signature-v3/swift)
- Dart: [GitHub](https://github.com/TencentCloud/signature-process-demo/tree/main/signature-v3/dart)
- Shell(Bash): [GitHub](https://github.com/TencentCloud/signature-process-demo/tree/main/signature-v3/bash)

## 签名失败

存在以下签名失败的错误码，请根据实际情况处理。

| 错误码 | 错误描述|
|----------|---------|
| AuthFailure.SignatureExpire | 签名过期。Timestamp 与服务器接收到请求的时间相差不得超过五分钟。 |
| AuthFailure.SecretIdNotFound | 密钥不存在。请到控制台查看密钥是否被禁用，是否少复制了字符或者多了字符。 |
| AuthFailure.SignatureFailure | 签名错误。可能是签名计算错误，或者签名与实际发送的内容不相符合，也有可能是密钥 SecretKey 错误导致的。 |
| AuthFailure.TokenFailure | 临时证书 Token 错误。 |
| AuthFailure.InvalidSecretId | 密钥非法（不是云 API 密钥类型）。 |



path: advisor-2020-07-21/调用方式/公共参数.md
content: 公共参数是用于标识用户和接口签名的参数，如非必要，在每个接口单独的文档中不再对这些参数进行说明，但每次请求均需要携带这些参数，才能正常发起请求。

公共参数的具体内容会因您使用的签名方法版本不同而有所差异。

## 使用签名方法 v3 的公共参数

签名方法 v3 （有时也称作 TC3-HMAC-SHA256）相比签名方法 v1 （有些文档可能会简称签名方法），更安全，支持更大的请求包，支持 POST JSON 格式，性能有一定提升，推荐使用该签名方法计算签名。完整介绍详见 [签名方法 v3](/document/api/1264/63117)。

注意：出于简化的目的，部分接口文档中的示例使用的是签名方法 v1 GET 请求，而不是更安全的签名方法 v3。

使用签名方法 v3 时，公共参数需要统一放到 HTTP Header 请求头部中，如下表所示：

|参数名称|类型|必选|描述|
|--------|----|----|----|
| Action | String | 是 | HTTP 请求头：X-TC-Action。操作的接口名称。取值参考接口文档输入参数章节关于公共参数 Action 的说明。例如云服务器的查询实例列表接口，取值为 DescribeInstances。|
| Region | String | - | HTTP 请求头：X-TC-Region。地域参数，用来标识希望操作哪个地域的数据。取值参考接口文档中输入参数章节关于公共参数 Region 的说明。**注意：某些接口不需要传递该参数，接口文档中会对此特别说明，此时即使传递该参数也不会生效。**|
| Timestamp | Integer | 是 | HTTP 请求头：X-TC-Timestamp。当前 UNIX 时间戳，可记录发起 API 请求的时间。例如 1529223702。**注意：如果与服务器时间相差超过5分钟，会引起签名过期错误。**|
| Version | String | 是 | HTTP 请求头：X-TC-Version。操作的 API 的版本。取值参考接口文档中入参公共参数 Version 的说明。例如云服务器的版本 2017-03-12。|
| Authorization |String | 是 | HTTP 标准身份认证头部字段，例如：<br/>TC3-HMAC-SHA256 Credential=AKID*******/Date/service/tc3_request, SignedHeaders=content-type;host, Signature=fe5f80f77d5fa3beca038a248ff027d0445342fe2855ddc963176630326f1024 <br/>其中，<br/>- TC3-HMAC-SHA256：签名方法，目前固定取该值；<br/>- Credential：签名凭证，AKID******* 是 SecretId；Date 是 UTC 标准时间的日期，取值需要和公共参数 X-TC-Timestamp 换算的 UTC 标准时间日期一致；service 为具体产品名，通常为域名前缀。例如，域名 cvm.tencentcloudapi.com 意味着产品名是 cvm。本产品取值为 advisor；tc3_request 为固定字符串；<br/>- SignedHeaders：参与签名计算的头部信息，content-type 和 host 为必选头部；<br/>- Signature：签名摘要，计算过程详见 [文档](/document/api/1264/63117)。|
| Token | String | 否 | HTTP 请求头：X-TC-Token。即 [安全凭证服务](https://cloud.tencent.com/document/product/1312) 所颁发的临时安全凭证中的 Token，使用时需要将 SecretId 和 SecretKey 的值替换为临时安全凭证中的 TmpSecretId 和 TmpSecretKey。使用长期密钥时不能设置此 Token 字段。|
| Language | String | 否 | HTTP 请求头：X-TC-Language。指定接口返回的语言，仅部分接口支持此参数。取值：zh-CN，en-US。zh-CN 返回中文，en-US 返回英文。|

假设用户想要查询广州地域的云服务器实例列表中的前十个，接口参数设置为偏移量 Offset=0，返回数量 Limit=10，则其请求结构按照请求 URL、请求头部、请求体示例如下:

HTTP GET 请求结构示例:

```
https://cvm.tencentcloudapi.com/?Limit=10&Offset=0

[REDACTED_SECRET] Credential=AKID********************************/2018-10-09/cvm/tc3_request, SignedHeaders=content-type;host, Signature=5da7a33f6993f0614b047e5df4582db9e9bf4672ba50567dba16c6ccf174c474
Content-Type: application/x-www-form-urlencoded
Host: cvm.tencentcloudapi.com
X-TC-Action: DescribeInstances
X-TC-Version: 2017-03-12
X-TC-Timestamp: 1539084154
X-TC-Region: ap-guangzhou
```

HTTP POST （application/json） 请求结构示例：

```
https://cvm.tencentcloudapi.com/

[REDACTED_SECRET] Credential=AKID********************************/2018-05-30/cvm/tc3_request, SignedHeaders=content-type;host, Signature=582c400e06b5924a6f2b5d7d672d79c15b13162d9279b0855cfba6789a8edb4c
Content-Type: application/json
Host: cvm.tencentcloudapi.com
X-TC-Action: DescribeInstances
X-TC-Version: 2017-03-12
X-TC-Timestamp: 1527672334
X-TC-Region: ap-guangzhou

{"Offset":0,"Limit":10}
```

HTTP POST （multipart/form-data）请求结构示例（仅特定的接口支持）：

```
https://cvm.tencentcloudapi.com/

[REDACTED_SECRET] Credential=AKID********************************/2018-05-30/cvm/tc3_request, SignedHeaders=content-type;host, Signature=582c400e06b5924a6f2b5d7d672d79c15b13162d9279b0855cfba6789a8edb4c
Content-Type: multipart/form-data; boundary=58731222010402
Host: cvm.tencentcloudapi.com
X-TC-Action: DescribeInstances
X-TC-Version: 2017-03-12
X-TC-Timestamp: 1527672334
X-TC-Region: ap-guangzhou

--58731222010402
Content-Disposition: form-data; name="Offset"

0
--58731222010402
Content-Disposition: form-data; name="Limit"

10
--58731222010402--
```

## 使用签名方法 v1 的公共参数

使用签名方法 v1 （有时会称作 HmacSHA256 和 HmacSHA1），公共参数需要统一放到请求串中，完整介绍详见[文档](/document/api/1264/63118)

| 参数名称 | 类型 | 必选 | 描述 |
|:---------|:---------|:-----|:---- |
| Action | String | 是 | 操作的接口名称。取值参考接口文档中输入参数章节关于公共参数 Action 的说明。例如云服务器的查询实例列表接口，取值为 DescribeInstances。 |
| Region| String | - | 地域参数，用来标识希望操作哪个地域的数据。接口接受的地域取值参考接口文档中输入参数公共参数 Region 的说明。**注意：某些接口不需要传递该参数，接口文档中会对此特别说明，此时即使传递该参数也不会生效。** |
| Timestamp | Integer | 是 | 当前 UNIX 时间戳，可记录发起 API 请求的时间。例如1529223702，如果与当前时间相差过大，会引起签名过期错误。|
| Nonce | Integer | 是 |随机正整数，与 Timestamp 联合起来，用于防止重放攻击。|
| SecretId | String | 是 | 在 <a href="https://console.cloud.tencent.com/capi">云API密钥</a> 上申请的标识身份的 SecretId，一个 SecretId 对应唯一的 SecretKey ，而 SecretKey 会用来生成请求签名 Signature。|
| Signature | String | 是 | 请求签名，用来验证此次请求的合法性，需要用户根据实际的输入参数计算得出。具体计算方法参见 [文档](/document/api/1264/63118)。|
| Version | String | 是 | 操作的 API 的版本。取值参考接口文档中入参公共参数 Version 的说明。例如云服务器的版本 2017-03-12。 |
| SignatureMethod | String | 否 | 签名方式，目前支持 HmacSHA256 和 HmacSHA1。只有指定此参数为 HmacSHA256 时，才使用 HmacSHA256 算法验证签名，其他情况均使用 HmacSHA1 验证签名。|
| Token | String | 否 | 即 [安全凭证服务](https://cloud.tencent.com/document/product/1312) 所颁发的临时安全凭证中的 Token，使用时需要将 SecretId 和 SecretKey 的值替换为临时安全凭证中的 TmpSecretId 和 TmpSecretKey。使用长期密钥时不能设置此 Token 字段。 |
| Language | String | 否 | 指定接口返回的语言，仅部分接口支持此参数。取值：zh-CN，en-US。zh-CN 返回中文，en-US 返回英文。|


假设用户想要查询广州地域的云服务器实例列表，其请求结构按照请求 URL、请求头部、请求体示例如下:

HTTP GET 请求结构示例:
```
https://cvm.tencentcloudapi.com/?Action=DescribeInstances&Version=2017-03-12&SignatureMethod=HmacSHA256&Timestamp=1527672334&Signature=37ac2f4fde00b0ac9bd9eadeb459b1bbee224158d66e7ae5fcadb70b2d181d02&Region=ap-guangzhou&Nonce=23823223&SecretId=AKID********************************

Host: cvm.tencentcloudapi.com
```

HTTP POST 请求结构示例：

```
https://cvm.tencentcloudapi.com/

Host: cvm.tencentcloudapi.com
Content-Type: application/x-www-form-urlencoded

Action=DescribeInstances&Version=2017-03-12&SignatureMethod=HmacSHA256&Timestamp=1527672334&Signature=37ac2f4fde00b0ac9bd9eadeb459b1bbee224158d66e7ae5fcadb70b2d181d02&Region=ap-guangzhou&Nonce=23823223&SecretId=AKID********************************
```


## 地域列表

本产品所有接口 Region 字段的可选值如下表所示。如果接口不支持该表中的所有地域，则会在接口文档中单独说明。

| 地域 | 取值 |
|------|------|
|华南地区（广州）|ap-guangzhou|


Security scan verdicts — ClawScan: suspicious | static analysis: suspicious | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)