@optima-chat/dev-skills 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/logs.md +27 -47
- package/.claude/skills/account/SKILL.md +61 -0
- package/.claude/skills/entitlement/SKILL.md +55 -0
- package/.claude/skills/grant-balance/SKILL.md +3 -3
- package/.claude/skills/grant-subscription/SKILL.md +4 -4
- package/.codex/skills/account/SKILL.md +61 -0
- package/.codex/skills/entitlement/SKILL.md +55 -0
- package/.codex/skills/grant-balance/SKILL.md +3 -3
- package/.codex/skills/grant-subscription/SKILL.md +1 -1
- package/README.md +6 -0
- package/bin/cli.js +1 -1
- package/bin/helpers/account.ts +146 -0
- package/bin/helpers/billing-http.ts +105 -19
- package/bin/helpers/entitlement/grant.ts +18 -12
- package/bin/helpers/entitlement/list.ts +15 -11
- package/bin/helpers/entitlement/revoke.ts +18 -14
- package/bin/helpers/grant-balance.ts +17 -21
- package/bin/helpers/grant-subscription.ts +47 -21
- package/bin/helpers/logs.ts +200 -0
- package/dist/bin/helpers/account.js +141 -0
- package/dist/bin/helpers/billing-http.js +99 -19
- package/dist/bin/helpers/entitlement/grant.js +20 -12
- package/dist/bin/helpers/entitlement/list.js +17 -11
- package/dist/bin/helpers/entitlement/revoke.js +20 -14
- package/dist/bin/helpers/grant-balance.js +15 -20
- package/dist/bin/helpers/grant-subscription.js +37 -20
- package/dist/bin/helpers/logs.js +207 -0
- package/docs/.admin-cli-4env/spec.md +109 -0
- package/package.json +3 -1
package/.claude/commands/logs.md
CHANGED
|
@@ -50,7 +50,8 @@
|
|
|
50
50
|
- `ci` - CI 持续集成环境(开发环境,默认)
|
|
51
51
|
- `stage` - Stage 预发布环境(ECS Fargate)
|
|
52
52
|
- `prod` - 生产环境(ECS Fargate)
|
|
53
|
-
- `cn-prod`(别名 `cn`)- 阿里云 cn-prod 环境(SAE,cn-beijing
|
|
53
|
+
- `cn-prod`(别名 `cn`)- 阿里云 cn-prod 环境(SAE,cn-beijing,`optima-logs` 直连 SLS)
|
|
54
|
+
- `cn-stage` - 阿里云 cn-stage 预发环境(SAE,cn-beijing,`optima-logs` 直连 SLS)
|
|
54
55
|
|
|
55
56
|
## 示例
|
|
56
57
|
|
|
@@ -72,7 +73,7 @@
|
|
|
72
73
|
- `ci` 或未指定 → 使用 SSH + Docker Compose(第 0 节,默认)
|
|
73
74
|
- `stage` → 使用 AWS CloudWatch Logs - ECS(第 1 节)
|
|
74
75
|
- `prod` → 使用 AWS CloudWatch Logs - ECS(第 2 节)
|
|
75
|
-
- `cn-prod` / `cn` → 阿里云
|
|
76
|
+
- `cn-prod` / `cn` / `cn-stage` → 阿里云 SLS,`optima-logs` 直连(第 3 节,免 buildbox)
|
|
76
77
|
|
|
77
78
|
### 0. CI 环境(environment = "ci" 或默认)
|
|
78
79
|
|
|
@@ -211,59 +212,38 @@ aws logs get-log-events --log-group-name /ecs/commerce-backend-prod --log-stream
|
|
|
211
212
|
|
|
212
213
|
**注意**: `optima-store` 仅在 Stage 环境部署
|
|
213
214
|
|
|
214
|
-
### 3. cn-prod 环境(environment = "cn-prod" 或 "cn")
|
|
215
|
+
### 3. cn-prod / cn-stage 环境(environment = "cn-prod"/"cn" 或 "cn-stage")
|
|
215
216
|
|
|
216
|
-
**部署方式**: 阿里云 **SAE**(
|
|
217
|
-
**访问方式**:
|
|
217
|
+
**部署方式**: 阿里云 **SAE**(cn-beijing),不是 ECS/CloudWatch。
|
|
218
|
+
**访问方式**: ✅ **用 `optima-logs` CLI 直连阿里云 SLS**,不再经 buildbox。
|
|
218
219
|
|
|
219
|
-
>
|
|
220
|
+
> ✅ 现状更新(2026-06-16):cn-prod / cn-stage 全部服务**已接 SLS**(含 ECI 的 `agent-runtime`)。
|
|
221
|
+
> SLS `GetLogs` 是公网控制面 API,本机 `aliyun-optima` profile 直连即可,支持**历史检索 + 时间窗 + 关键词**——
|
|
222
|
+
> 旧的「SSH buildbox → SAE `DescribeInstanceLog` 取当前缓冲」流程已弃用(缓冲式、重启即丢、不能检索)。
|
|
220
223
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
-
|
|
225
|
-
-
|
|
226
|
-
|
|
227
|
-
|
|
224
|
+
**推荐用法(首选)**:
|
|
225
|
+
```bash
|
|
226
|
+
optima-logs gateway-core # 默认 cn-prod,最近 1h,100 行
|
|
227
|
+
optima-logs gateway-core --env cn-stage
|
|
228
|
+
optima-logs agent-runtime --env cn-prod --since 2h # ECI 容器也直接可见
|
|
229
|
+
optima-logs user-auth --grep error -n 200
|
|
230
|
+
optima-logs gateway-core --since 30m --json | jq . # 机器可读
|
|
231
|
+
```
|
|
228
232
|
|
|
229
|
-
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
| `agentic-chat` | `6e290c73-a646-43ef-9da5-ad0b2e7eff73` |
|
|
233
|
-
| `gw-admin` | `c6bc5a78-b27f-46e2-825a-eaa338c23645` |
|
|
234
|
-
| `user-auth` | `d6fbf9de-fed6-4165-978f-7b3a0a456acc` |
|
|
235
|
-
| `user-auth-admin` | `54093f99-37bf-4354-abd7-f6e76abdbcb6` |
|
|
236
|
-
| `optima-scout` | `f5bb7e82-e57c-4bd4-83ae-2c25f8d38647` |
|
|
237
|
-
| `optima-skills` | `55a63ee7-fb75-46e4-99f3-20854a699237` |
|
|
238
|
-
| `infisical` | `52b33fc6-b26d-495a-8885-225d38b3d42f` |
|
|
233
|
+
参数:`--env stage|prod|cn-prod|cn-stage`、`--since 30m|2h|1d`、`--grep <kw>`、`--lines/-n <N>`、`--json`。
|
|
234
|
+
`service` == SLS logstore 名(同名)。列全部 logstore:
|
|
235
|
+
`aliyun sls ListLogStores --project optima-cn-prod-1911493506120573 --region cn-beijing --profile aliyun-optima`
|
|
239
236
|
|
|
240
|
-
|
|
237
|
+
**底层(仅参考,正常用 `optima-logs` 即可)**:
|
|
238
|
+
- SLS project:`optima-cn-prod-1911493506120573` / `optima-cn-stage-1911493506120573`(`optima-<env>-<accountId>`)。
|
|
239
|
+
- logstore = service 名;日志正文在 `content` 字段。
|
|
241
240
|
```bash
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
[agentic-chat]=6e290c73-a646-43ef-9da5-ad0b2e7eff73
|
|
247
|
-
[gw-admin]=c6bc5a78-b27f-46e2-825a-eaa338c23645
|
|
248
|
-
[user-auth]=d6fbf9de-fed6-4165-978f-7b3a0a456acc
|
|
249
|
-
[user-auth-admin]=54093f99-37bf-4354-abd7-f6e76abdbcb6
|
|
250
|
-
[optima-scout]=f5bb7e82-e57c-4bd4-83ae-2c25f8d38647
|
|
251
|
-
[optima-skills]=55a63ee7-fb75-46e4-99f3-20854a699237
|
|
252
|
-
[infisical]=52b33fc6-b26d-495a-8885-225d38b3d42f )
|
|
253
|
-
AID=${APPID[$SERVICE]}
|
|
254
|
-
sshpass -p "$BBPW" ssh -o StrictHostKeyChecking=no root@47.94.105.163 "bash -s" <<REMOTE | tail -n $LINES
|
|
255
|
-
P="--region cn-beijing --profile aliyun-optima"
|
|
256
|
-
GID=\$(aliyun sae GET /pop/v1/sam/app/describeApplicationGroups --AppId $AID \$P 2>/dev/null | python3 -c "import sys,json;print(json.load(sys.stdin)['Data'][0]['GroupId'])")
|
|
257
|
-
IID=\$(aliyun sae GET /pop/v1/sam/app/describeApplicationInstances --AppId $AID --GroupId \$GID \$P 2>/dev/null | python3 -c "import sys,json;d=json.load(sys.stdin)['Data'];print((d if isinstance(d,list) else d['Instances'])[0]['InstanceId'])")
|
|
258
|
-
aliyun sae DescribeInstanceLog --InstanceId "\$IID" \$P 2>/dev/null | python3 -c "import sys,json;print(json.load(sys.stdin).get('Data',''))"
|
|
259
|
-
REMOTE
|
|
241
|
+
NOW=$(date +%s); FROM=$((NOW-3600))
|
|
242
|
+
aliyun sls GetLogs --project optima-cn-prod-1911493506120573 --logstore gateway-core \
|
|
243
|
+
--from $FROM --to $NOW --line 100 --reverse true --query error \
|
|
244
|
+
--region cn-beijing --profile aliyun-optima
|
|
260
245
|
```
|
|
261
246
|
|
|
262
|
-
**局限 / 提示**:
|
|
263
|
-
- 只取第 1 个 InstanceId;多副本要遍历 `Data` 里全部 instance。
|
|
264
|
-
- `agent-runtime` 不在 SAE(是 ECI 动态容器)——它的日志看 `gateway-core` 里 `eci-bridge` 的转发/错误行,或单独查对应 ECI。
|
|
265
|
-
- 过滤:把上面输出 `| grep -iE 'error|eci|fail'` 即可(例:排 agentic-chat 链路 ECI 失败就 grep `gateway-core` 日志的 `eci-bridge`)。
|
|
266
|
-
|
|
267
247
|
## 完整示例脚本
|
|
268
248
|
|
|
269
249
|
### Stage 环境
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "account"
|
|
3
|
+
description: "当用户请求查看账号状态、查询某用户的订阅/权益、禁用账号、封禁用户、ban、停用账号、解封、恢复账号、unban 时,使用此技能。支持 stage、prod、cn-prod、cn-stage 四个环境;标识符支持邮箱/手机号/userId。"
|
|
4
|
+
allowed-tools: ["Bash"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 账号运营(status / ban / unban)
|
|
8
|
+
|
|
9
|
+
以用户为中心的运营 admin 操作:查状态、禁用、恢复。CLI:`optima-account`。
|
|
10
|
+
|
|
11
|
+
标识符 `<email|phone|userId>`:**cn-prod / cn-stage 用户多为手机号注册**,三种都支持;AWS stage/prod 仅 email(经 RDS SSH 隧道解析)。执行前都会打印 `🎯 目标账号` 反查回显,防止发错账号。
|
|
12
|
+
|
|
13
|
+
## 执行方式
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
optima-account status <email|phone|userId> --env <env>
|
|
17
|
+
optima-account ban <email|phone|userId> --reason "..." --env <env> [--yes]
|
|
18
|
+
optima-account unban <email|phone|userId> --env <env> [--yes]
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
| 参数 | 说明 | 默认 |
|
|
22
|
+
|------|------|------|
|
|
23
|
+
| `<email\|phone\|userId>` | 用户标识(必填);cn 支持手机号/userId | - |
|
|
24
|
+
| `--reason "..."` | 禁用原因(**ban 必填**) | - |
|
|
25
|
+
| `--env <env>` | stage / prod / cn-prod / cn-stage | stage |
|
|
26
|
+
| `--yes` | 跳过 prod / cn-prod 的确认提示 | - |
|
|
27
|
+
|
|
28
|
+
## status(只读聚合)
|
|
29
|
+
|
|
30
|
+
显示:订阅(membership-status)+ 权益(entitlements)。纯 M2M,无需 admin 凭证。
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
optima-account status 18898654855 --env cn-prod
|
|
34
|
+
```
|
|
35
|
+
> ⚠️ 账号禁用状态(banned_at/原因)与 credits 余额**暂不显示**——无 M2M 读取端点,见 optima-dev-skills#36。
|
|
36
|
+
|
|
37
|
+
## ban / unban(需 admin-用户 token)
|
|
38
|
+
|
|
39
|
+
走 user-auth `/api/v1/admin/users/{id}/{ban,unban}`,鉴权用 **admin 用户 token**(ROPC password grant,凭证读 Infisical `/shared-secrets/credentials`)。
|
|
40
|
+
|
|
41
|
+
- **cn-prod / cn-stage** 需运行时设 `INFISICAL_CN_EMAIL` / `INFISICAL_CN_PASSWORD`(读 cn Infisical)。
|
|
42
|
+
- **prod / cn-prod**(两个生产环境)执行前要求输入 `yes` 确认;`--yes` 可跳过。
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
optima-account ban user@example.com --reason "abuse" --env prod
|
|
46
|
+
optima-account unban 18898654855 --env cn-prod
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
> ⚠️ **ban 不是即时踢会话**:仅置 `is_active=false`,挡新登录/刷新,但**已签发的 access token 仍有效到过期**。需要立即失效在线会话的能力是 user-auth 后续支持项。
|
|
50
|
+
|
|
51
|
+
## 安全提醒
|
|
52
|
+
|
|
53
|
+
1. ban/unban 前看清打印的 `🎯 目标账号`(手机/email/userId)。
|
|
54
|
+
2. prod / cn-prod 是生产,确认无误再 `yes`。
|
|
55
|
+
3. ban 是名义禁用,活跃会话不会立刻断——别误以为已立即封死。
|
|
56
|
+
|
|
57
|
+
## 相关命令
|
|
58
|
+
|
|
59
|
+
- `optima-grant-subscription` / `optima-grant-balance` - 开通会员 / 赠送 credits
|
|
60
|
+
- `optima-entitlement` - 产品权益授予/撤销/查询
|
|
61
|
+
- `optima-query-db` - 直查数据库核对(如 `is_active` / `banned_at`)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "entitlement"
|
|
3
|
+
description: "当用户请求授予产品权益、发放 entitlement、发 scout-gift、给用户开某个 product、撤销权益、退权益、refund entitlement、查看某用户的权益列表时,使用此技能。支持 stage、prod、cn-prod、cn-stage 四个环境;标识符支持邮箱/手机号/userId。"
|
|
4
|
+
allowed-tools: ["Bash"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 产品权益(Entitlement:grant / revoke / list)
|
|
8
|
+
|
|
9
|
+
管理 billing 的产品权益(如 `scout-gift` 这类 `ONE_SHOT_SKILL` product)。CLI:`optima-entitlement`。
|
|
10
|
+
|
|
11
|
+
标识符 `<email|phone|userId>`:**cn-prod / cn-stage 用户多为手机号**,三种都支持;AWS stage/prod 仅 email。授予/撤销前打印 `🎯 目标账号` 反查回显。
|
|
12
|
+
|
|
13
|
+
## 执行方式
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
optima-entitlement grant <email|phone|userId> --product-key <slug> --justification "..." --env <env> [--yes]
|
|
17
|
+
optima-entitlement revoke <email|phone|userId> --product-key <slug> --reason "..." --env <env> [--yes]
|
|
18
|
+
optima-entitlement list <email|phone|userId> --env <env>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
| 参数 | 说明 | 默认 |
|
|
22
|
+
|------|------|------|
|
|
23
|
+
| `<email\|phone\|userId>` | 用户标识(必填;`--email` 为向后兼容别名) | - |
|
|
24
|
+
| `--product-key <slug>` | 产品 key,如 `scout-gift`(grant/revoke 必填) | - |
|
|
25
|
+
| `--justification "..."` | 授予理由(grant 必填,billing 否则 400) | - |
|
|
26
|
+
| `--reason "..."` | 撤销理由(revoke 必填) | - |
|
|
27
|
+
| `--env <env>` | stage / prod / cn-prod / cn-stage | stage |
|
|
28
|
+
| `--yes` | 跳过 prod / cn-prod 确认 | - |
|
|
29
|
+
|
|
30
|
+
服务端硬编码:`source=ADMIN_GRANT, priceCents=0, currency=USD, grantedBy=<dev-skills client>`。revoke 拒绝非 ADMIN_GRANT 来源(PAYMENT/PARTNER)。
|
|
31
|
+
|
|
32
|
+
## 快速操作
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# 给 cn-prod 手机号用户发 scout-gift
|
|
36
|
+
optima-entitlement grant 18898654855 --product-key scout-gift --justification "运营赠送" --env cn-prod
|
|
37
|
+
|
|
38
|
+
# 查权益
|
|
39
|
+
optima-entitlement list 18898654855 --env cn-prod
|
|
40
|
+
|
|
41
|
+
# 撤销
|
|
42
|
+
optima-entitlement revoke user@example.com --product-key scout-gift --reason "误发" --env prod
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 环境说明
|
|
46
|
+
|
|
47
|
+
- **stage / prod(AWS)**:仅 email;经 RDS SSH 隧道解析 userId。
|
|
48
|
+
- **cn-prod / cn-stage(阿里云)**:email/手机号/userId;经 user-auth internal lookup(M2M,无 SSH 隧道)。走 HTTPS billing API(billing-api.yzsgo.com / billing-api.stage.optima.chat)。
|
|
49
|
+
|
|
50
|
+
> prod / cn-prod 为生产环境,grant/revoke 执行前要求 `yes` 确认(`--yes` 跳过)。
|
|
51
|
+
|
|
52
|
+
## 相关命令
|
|
53
|
+
|
|
54
|
+
- `optima-account status` - 查看用户订阅+权益汇总
|
|
55
|
+
- `optima-grant-subscription` / `optima-grant-balance` - 开通会员 / 赠送 credits
|
|
@@ -11,11 +11,11 @@ allowed-tools: ["Bash"]
|
|
|
11
11
|
## 执行方式:使用 CLI 工具
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
optima-grant-balance <email> --amount <usd> [options]
|
|
14
|
+
optima-grant-balance <email|phone|userId> --amount <usd> [options]
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
**为什么使用 CLI 工具**:
|
|
18
|
-
- 自动通过 email 查找 userId
|
|
18
|
+
- 自动通过 email/手机号/userId 查找 userId(cn 用户多为手机号;执行前打印 🎯 目标账号反查)
|
|
19
19
|
- 自动处理 SSH 隧道和数据库连接
|
|
20
20
|
- 不会影响现有订阅和已有余额(发放 bonus 积分(30 天有效期,重复执行会叠加发放))
|
|
21
21
|
- 自动留审计痕迹(credit_lot 行,幂等键前缀 `dev-skills-grant:`)
|
|
@@ -47,7 +47,7 @@ optima-grant-balance user@example.com --amount 20 --description "服务中断补
|
|
|
47
47
|
|
|
48
48
|
| 参数 | 说明 | 默认值 |
|
|
49
49
|
|------|------|--------|
|
|
50
|
-
| `<email>` |
|
|
50
|
+
| `<email|phone|userId>` | 用户标识(必填;cn 支持手机号/userId) | - |
|
|
51
51
|
| `--amount <usd>` | USD 金额(必填,> 0) | - |
|
|
52
52
|
| `--description <text>` | 描述/原因(仅 console 输出) | - |
|
|
53
53
|
| `--env <env>` | 环境:stage, prod, cn-prod, cn-stage | stage |
|
|
@@ -13,11 +13,11 @@ allowed-tools: ["Bash"]
|
|
|
13
13
|
**重要**:无论用户使用 `/grant-subscription` 命令还是直接请求开通会员,都应该使用 `optima-grant-subscription` CLI 工具:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
optima-grant-subscription <email> [options]
|
|
16
|
+
optima-grant-subscription <email|phone|userId> [options]
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
**为什么使用 CLI 工具**:
|
|
20
|
-
- 自动通过 email 查找 userId
|
|
20
|
+
- 自动通过 email/手机号/userId 查找 userId(cn 用户多为手机号;执行前打印 🎯 目标账号反查)
|
|
21
21
|
- 自动处理 SSH 隧道和数据库连接
|
|
22
22
|
- 自动取消旧订阅、经 billing API supersede 旧授予(void 旧 subscription lot + 发新)
|
|
23
23
|
- 自动按 plan 配置发放 subscription 积分(期末过期)和 token quota
|
|
@@ -60,10 +60,10 @@ optima-grant-subscription user@example.com --plan pro-cn --env cn-stage
|
|
|
60
60
|
|
|
61
61
|
| 参数 | 说明 | 默认值 |
|
|
62
62
|
|------|------|--------|
|
|
63
|
-
| `<email>` |
|
|
63
|
+
| `<email|phone|userId>` | 用户标识(必填;cn 支持手机号/userId) | - |
|
|
64
64
|
| `--plan <id>` | 计划:trial, starter, pro, enterprise | pro |
|
|
65
65
|
| `--months <n>` | 时长(月) | 1 |
|
|
66
|
-
| `--env <env>` | 环境:stage, prod, cn-prod | stage |
|
|
66
|
+
| `--env <env>` | 环境:stage, prod, cn-prod, cn-stage | stage |
|
|
67
67
|
|
|
68
68
|
> **cn-prod(国内环境)**:plan 用 CNY 定价的 `-cn` 档(trial / starter-cn / pro-cn / enterprise-cn,默认 pro-cn);裸 USD 档 id 会被客户端拒绝(防止给 CN 用户误赠 USD 档积分量)。email 查找走 user-auth internal lookup API(无 SSH 隧道)。
|
|
69
69
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "account"
|
|
3
|
+
description: "当用户请求查看账号状态、查询某用户的订阅/权益、禁用账号、封禁用户、ban、停用账号、解封、恢复账号、unban 时,使用此技能。支持 stage、prod、cn-prod、cn-stage 四个环境;标识符支持邮箱/手机号/userId。"
|
|
4
|
+
allowed-tools: ["Bash"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 账号运营(status / ban / unban)
|
|
8
|
+
|
|
9
|
+
以用户为中心的运营 admin 操作:查状态、禁用、恢复。CLI:`optima-account`。
|
|
10
|
+
|
|
11
|
+
标识符 `<email|phone|userId>`:**cn-prod / cn-stage 用户多为手机号注册**,三种都支持;AWS stage/prod 仅 email(经 RDS SSH 隧道解析)。执行前都会打印 `🎯 目标账号` 反查回显,防止发错账号。
|
|
12
|
+
|
|
13
|
+
## 执行方式
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
optima-account status <email|phone|userId> --env <env>
|
|
17
|
+
optima-account ban <email|phone|userId> --reason "..." --env <env> [--yes]
|
|
18
|
+
optima-account unban <email|phone|userId> --env <env> [--yes]
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
| 参数 | 说明 | 默认 |
|
|
22
|
+
|------|------|------|
|
|
23
|
+
| `<email\|phone\|userId>` | 用户标识(必填);cn 支持手机号/userId | - |
|
|
24
|
+
| `--reason "..."` | 禁用原因(**ban 必填**) | - |
|
|
25
|
+
| `--env <env>` | stage / prod / cn-prod / cn-stage | stage |
|
|
26
|
+
| `--yes` | 跳过 prod / cn-prod 的确认提示 | - |
|
|
27
|
+
|
|
28
|
+
## status(只读聚合)
|
|
29
|
+
|
|
30
|
+
显示:订阅(membership-status)+ 权益(entitlements)。纯 M2M,无需 admin 凭证。
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
optima-account status 18898654855 --env cn-prod
|
|
34
|
+
```
|
|
35
|
+
> ⚠️ 账号禁用状态(banned_at/原因)与 credits 余额**暂不显示**——无 M2M 读取端点,见 optima-dev-skills#36。
|
|
36
|
+
|
|
37
|
+
## ban / unban(需 admin-用户 token)
|
|
38
|
+
|
|
39
|
+
走 user-auth `/api/v1/admin/users/{id}/{ban,unban}`,鉴权用 **admin 用户 token**(ROPC password grant,凭证读 Infisical `/shared-secrets/credentials`)。
|
|
40
|
+
|
|
41
|
+
- **cn-prod / cn-stage** 需运行时设 `INFISICAL_CN_EMAIL` / `INFISICAL_CN_PASSWORD`(读 cn Infisical)。
|
|
42
|
+
- **prod / cn-prod**(两个生产环境)执行前要求输入 `yes` 确认;`--yes` 可跳过。
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
optima-account ban user@example.com --reason "abuse" --env prod
|
|
46
|
+
optima-account unban 18898654855 --env cn-prod
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
> ⚠️ **ban 不是即时踢会话**:仅置 `is_active=false`,挡新登录/刷新,但**已签发的 access token 仍有效到过期**。需要立即失效在线会话的能力是 user-auth 后续支持项。
|
|
50
|
+
|
|
51
|
+
## 安全提醒
|
|
52
|
+
|
|
53
|
+
1. ban/unban 前看清打印的 `🎯 目标账号`(手机/email/userId)。
|
|
54
|
+
2. prod / cn-prod 是生产,确认无误再 `yes`。
|
|
55
|
+
3. ban 是名义禁用,活跃会话不会立刻断——别误以为已立即封死。
|
|
56
|
+
|
|
57
|
+
## 相关命令
|
|
58
|
+
|
|
59
|
+
- `optima-grant-subscription` / `optima-grant-balance` - 开通会员 / 赠送 credits
|
|
60
|
+
- `optima-entitlement` - 产品权益授予/撤销/查询
|
|
61
|
+
- `optima-query-db` - 直查数据库核对(如 `is_active` / `banned_at`)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "entitlement"
|
|
3
|
+
description: "当用户请求授予产品权益、发放 entitlement、发 scout-gift、给用户开某个 product、撤销权益、退权益、refund entitlement、查看某用户的权益列表时,使用此技能。支持 stage、prod、cn-prod、cn-stage 四个环境;标识符支持邮箱/手机号/userId。"
|
|
4
|
+
allowed-tools: ["Bash"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 产品权益(Entitlement:grant / revoke / list)
|
|
8
|
+
|
|
9
|
+
管理 billing 的产品权益(如 `scout-gift` 这类 `ONE_SHOT_SKILL` product)。CLI:`optima-entitlement`。
|
|
10
|
+
|
|
11
|
+
标识符 `<email|phone|userId>`:**cn-prod / cn-stage 用户多为手机号**,三种都支持;AWS stage/prod 仅 email。授予/撤销前打印 `🎯 目标账号` 反查回显。
|
|
12
|
+
|
|
13
|
+
## 执行方式
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
optima-entitlement grant <email|phone|userId> --product-key <slug> --justification "..." --env <env> [--yes]
|
|
17
|
+
optima-entitlement revoke <email|phone|userId> --product-key <slug> --reason "..." --env <env> [--yes]
|
|
18
|
+
optima-entitlement list <email|phone|userId> --env <env>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
| 参数 | 说明 | 默认 |
|
|
22
|
+
|------|------|------|
|
|
23
|
+
| `<email\|phone\|userId>` | 用户标识(必填;`--email` 为向后兼容别名) | - |
|
|
24
|
+
| `--product-key <slug>` | 产品 key,如 `scout-gift`(grant/revoke 必填) | - |
|
|
25
|
+
| `--justification "..."` | 授予理由(grant 必填,billing 否则 400) | - |
|
|
26
|
+
| `--reason "..."` | 撤销理由(revoke 必填) | - |
|
|
27
|
+
| `--env <env>` | stage / prod / cn-prod / cn-stage | stage |
|
|
28
|
+
| `--yes` | 跳过 prod / cn-prod 确认 | - |
|
|
29
|
+
|
|
30
|
+
服务端硬编码:`source=ADMIN_GRANT, priceCents=0, currency=USD, grantedBy=<dev-skills client>`。revoke 拒绝非 ADMIN_GRANT 来源(PAYMENT/PARTNER)。
|
|
31
|
+
|
|
32
|
+
## 快速操作
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# 给 cn-prod 手机号用户发 scout-gift
|
|
36
|
+
optima-entitlement grant 18898654855 --product-key scout-gift --justification "运营赠送" --env cn-prod
|
|
37
|
+
|
|
38
|
+
# 查权益
|
|
39
|
+
optima-entitlement list 18898654855 --env cn-prod
|
|
40
|
+
|
|
41
|
+
# 撤销
|
|
42
|
+
optima-entitlement revoke user@example.com --product-key scout-gift --reason "误发" --env prod
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 环境说明
|
|
46
|
+
|
|
47
|
+
- **stage / prod(AWS)**:仅 email;经 RDS SSH 隧道解析 userId。
|
|
48
|
+
- **cn-prod / cn-stage(阿里云)**:email/手机号/userId;经 user-auth internal lookup(M2M,无 SSH 隧道)。走 HTTPS billing API(billing-api.yzsgo.com / billing-api.stage.optima.chat)。
|
|
49
|
+
|
|
50
|
+
> prod / cn-prod 为生产环境,grant/revoke 执行前要求 `yes` 确认(`--yes` 跳过)。
|
|
51
|
+
|
|
52
|
+
## 相关命令
|
|
53
|
+
|
|
54
|
+
- `optima-account status` - 查看用户订阅+权益汇总
|
|
55
|
+
- `optima-grant-subscription` / `optima-grant-balance` - 开通会员 / 赠送 credits
|
|
@@ -10,11 +10,11 @@ description: "Use when the user wants to grant credits (bonus, 30-day expiry) to
|
|
|
10
10
|
## 执行方式:使用 CLI 工具
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
optima-grant-balance <email> --amount <usd> [options]
|
|
13
|
+
optima-grant-balance <email|phone|userId> --amount <usd> [options]
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
**为什么使用 CLI 工具**:
|
|
17
|
-
- 自动通过 email 查找 userId
|
|
17
|
+
- 自动通过 email/手机号/userId 查找 userId(cn 用户多为手机号;执行前打印 🎯 目标账号反查)
|
|
18
18
|
- 自动处理 SSH 隧道和数据库连接
|
|
19
19
|
- 不会影响现有订阅和已有余额(发放 bonus 积分(30 天有效期,重复执行会叠加发放))
|
|
20
20
|
- 自动留审计痕迹(credit_lot 行,幂等键前缀 `dev-skills-grant:`)
|
|
@@ -46,7 +46,7 @@ optima-grant-balance user@example.com --amount 20 --description "服务中断补
|
|
|
46
46
|
|
|
47
47
|
| 参数 | 说明 | 默认值 |
|
|
48
48
|
|------|------|--------|
|
|
49
|
-
| `<email>` |
|
|
49
|
+
| `<email|phone|userId>` | 用户标识(必填;cn 支持手机号/userId) | - |
|
|
50
50
|
| `--amount <usd>` | USD 金额(必填,> 0) | - |
|
|
51
51
|
| `--description <text>` | 描述/原因(仅 console 输出) | - |
|
|
52
52
|
| `--env <env>` | 环境:stage, prod, cn-prod, cn-stage | stage |
|
package/README.md
CHANGED
|
@@ -105,6 +105,12 @@ Claude:
|
|
|
105
105
|
| `optima-show-env` | 查看服务环境变量 | `optima-show-env commerce-backend stage --filter DATABASE` |
|
|
106
106
|
| `optima-generate-test-token` | 生成测试 token | `optima-generate-test-token --business-name "测试店铺"` |
|
|
107
107
|
| `optima-discount` | 优惠码管理 | `optima-discount create --code LAUNCH20 --percent 20 --env stage` |
|
|
108
|
+
| `optima-grant-subscription` | 开通/切换订阅 | `optima-grant-subscription 18898654855 --plan pro-cn --env cn-prod` |
|
|
109
|
+
| `optima-grant-balance` | 赠送 credits | `optima-grant-balance user@example.com --amount 5 --env prod` |
|
|
110
|
+
| `optima-entitlement` | 产品权益 grant/revoke/list | `optima-entitlement grant 18898654855 --product-key scout-gift --justification "..." --env cn-prod` |
|
|
111
|
+
| `optima-account` | 账号 status/ban/unban | `optima-account ban user@example.com --reason "abuse" --env prod` |
|
|
112
|
+
|
|
113
|
+
> **4 环境 + 标识符**:`grant-subscription` / `grant-balance` / `entitlement` / `account` 均支持 `stage` / `prod` / `cn-prod` / `cn-stage`。标识符 `<email\|phone\|userId>`——**cn-prod / cn-stage 用户多为手机号注册**,三种均可;AWS stage/prod 仅 email。`ban`/`unban` 及 `account status` 的禁用态读取需 admin-用户凭证(Infisical `/shared-secrets/credentials`;cn 另需 `INFISICAL_CN_EMAIL/PASSWORD`)。
|
|
108
114
|
|
|
109
115
|
**特点**:
|
|
110
116
|
- ✅ 支持 CI、Stage、Prod 三个环境(query-db)
|
package/bin/cli.js
CHANGED
|
@@ -36,7 +36,7 @@ switch (command) {
|
|
|
36
36
|
log(' optima-generate-test-token [--env production] Generate test token', 'cyan');
|
|
37
37
|
log(' optima-grant-balance <email> --amount <usd> [--env] Grant USD wallet balance', 'cyan');
|
|
38
38
|
log(' optima-grant-subscription <email|phone|userId> --plan <p> [--env] Grant subscription', 'cyan');
|
|
39
|
-
log('
|
|
39
|
+
log(' optima-logs <service> [--env] [--since] [--grep] [-n] View logs (cn=SLS 直连/aws=CloudWatch)', 'cyan');
|
|
40
40
|
log(' /restart-ecs <service> [env] Restart ECS service (skill)', 'cyan');
|
|
41
41
|
|
|
42
42
|
log('\nSupported Services:', 'yellow');
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// optima-account:以用户为中心的运营 admin 操作。
|
|
4
|
+
// · status — 只读聚合(订阅 + 权益)。M2M。
|
|
5
|
+
// · ban / unban — user-auth 账号禁用/恢复。需 admin-用户 token(非 M2M)。
|
|
6
|
+
//
|
|
7
|
+
// 标识符 <email|phone|userId>:cn-prod/cn-stage 支持三种,AWS stage/prod 仅 email
|
|
8
|
+
// (见 resolveTargetUser)。两个生产环境(prod/cn-prod)ban/unban 前会要求确认。
|
|
9
|
+
import { resolveTargetUser } from './grant-subscription';
|
|
10
|
+
import { callBilling, callUserAuthAsAdmin, validateEnvCnProd } from './billing-http';
|
|
11
|
+
import { confirmIfProd } from './confirm-prompt';
|
|
12
|
+
|
|
13
|
+
interface CommonArgs {
|
|
14
|
+
identifier: string;
|
|
15
|
+
env: string;
|
|
16
|
+
reason: string | null;
|
|
17
|
+
yes: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function parseArgs(argv: string[], opts: { reason?: boolean } = {}): CommonArgs {
|
|
21
|
+
const out: Partial<CommonArgs> = { env: 'stage', reason: null, yes: false };
|
|
22
|
+
for (let i = 0; i < argv.length; i++) {
|
|
23
|
+
const a = argv[i];
|
|
24
|
+
const next = argv[i + 1];
|
|
25
|
+
if (a === '--env') { out.env = next; i++; }
|
|
26
|
+
else if (a === '--reason') { out.reason = next; i++; }
|
|
27
|
+
else if (a === '--yes') { out.yes = true; }
|
|
28
|
+
else if (a.startsWith('--')) { throw new Error(`Unknown arg: ${a}`); }
|
|
29
|
+
else if (out.identifier) { throw new Error(`Unexpected positional arg: ${a} (identifier already set to ${out.identifier})`); }
|
|
30
|
+
else { out.identifier = a; }
|
|
31
|
+
}
|
|
32
|
+
if (!out.identifier) throw new Error('target user required: <email|phone|userId>');
|
|
33
|
+
validateEnvCnProd(out.env as string);
|
|
34
|
+
if (opts.reason && !out.reason) throw new Error('--reason required for ban');
|
|
35
|
+
return out as CommonArgs;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function runStatus(argv: string[]): Promise<void> {
|
|
39
|
+
const { identifier, env } = parseArgs(argv);
|
|
40
|
+
const { userId, identity } = await resolveTargetUser(env, identifier);
|
|
41
|
+
|
|
42
|
+
// 订阅(membership-status,M2M;路径前缀 /api/internal 经 billing base URL)
|
|
43
|
+
let sub = '(读取失败)';
|
|
44
|
+
try {
|
|
45
|
+
const { body } = await callBilling<{ active: boolean; planId: string; status: string }>(
|
|
46
|
+
env, 'GET', `/api/internal/users/${encodeURIComponent(userId)}/membership-status`,
|
|
47
|
+
);
|
|
48
|
+
sub = `active=${body.active} plan=${body.planId} status=${body.status}`;
|
|
49
|
+
} catch (e) {
|
|
50
|
+
sub = `(读取失败: ${(e as Error).message})`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 权益(admin/entitlements,M2M)
|
|
54
|
+
let ents = '(读取失败)';
|
|
55
|
+
try {
|
|
56
|
+
const { body } = await callBilling<{ entitlements: Array<{ productKey: string; status: string; expiresAt?: string | null }> }>(
|
|
57
|
+
env, 'GET', `/api/billing/admin/entitlements?userId=${encodeURIComponent(userId)}`,
|
|
58
|
+
);
|
|
59
|
+
const rows = body.entitlements ?? [];
|
|
60
|
+
ents = rows.length ? rows.map((r) => `${r.productKey}(${r.status})`).join(', ') : '(无)';
|
|
61
|
+
} catch (e) {
|
|
62
|
+
ents = `(读取失败: ${(e as Error).message})`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log(`\n=== 账号状态 [${env.toUpperCase()}] ===`);
|
|
66
|
+
console.log(`userId : ${userId}`);
|
|
67
|
+
console.log(`手机 : ${identity.phone || '(无)'}`);
|
|
68
|
+
console.log(`email : ${identity.email || '(无)'}`);
|
|
69
|
+
console.log(`订阅 : ${sub}`);
|
|
70
|
+
console.log(`权益 : ${ents}`);
|
|
71
|
+
console.log(`禁用态/credits 余额: 暂未纳入(banned_at 与 credits 无 M2M 读取端点,见 optima-dev-skills#36)\n`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function runBan(argv: string[]): Promise<void> {
|
|
75
|
+
const { identifier, env, reason, yes } = parseArgs(argv, { reason: true });
|
|
76
|
+
const { userId, identity } = await resolveTargetUser(env, identifier);
|
|
77
|
+
|
|
78
|
+
await confirmIfProd(
|
|
79
|
+
env,
|
|
80
|
+
`Action: BAN userId=${userId} (手机=${identity.phone || '(无)'} email=${identity.email || '(无)'}) on ${env.toUpperCase()}\nReason: ${reason}`,
|
|
81
|
+
yes,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
console.log(`\n🚫 Banning ${identifier} (userId=${userId})...`);
|
|
85
|
+
const res = await callUserAuthAsAdmin(env, 'POST', `/api/v1/admin/users/${encodeURIComponent(userId)}/ban`, { reason });
|
|
86
|
+
console.log(`✓ Banned (HTTP ${res.status})`);
|
|
87
|
+
console.log('⚠️ 注意:ban 仅置 is_active=false——挡新登录/刷新,但**不会立即失效已签发的 access token**(活跃会话到 token 过期才失效)。即时踢会话需 user-auth 后续支持。\n');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function runUnban(argv: string[]): Promise<void> {
|
|
91
|
+
const { identifier, env, yes } = parseArgs(argv);
|
|
92
|
+
const { userId, identity } = await resolveTargetUser(env, identifier);
|
|
93
|
+
|
|
94
|
+
await confirmIfProd(
|
|
95
|
+
env,
|
|
96
|
+
`Action: UNBAN userId=${userId} (手机=${identity.phone || '(无)'} email=${identity.email || '(无)'}) on ${env.toUpperCase()}`,
|
|
97
|
+
yes,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
console.log(`\n♻️ Unbanning ${identifier} (userId=${userId})...`);
|
|
101
|
+
const res = await callUserAuthAsAdmin(env, 'POST', `/api/v1/admin/users/${encodeURIComponent(userId)}/unban`);
|
|
102
|
+
console.log(`✓ Unbanned (HTTP ${res.status})\n`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function printHelp(): void {
|
|
106
|
+
console.log(`Usage: optima-account <subcommand> <email|phone|userId> [options]
|
|
107
|
+
|
|
108
|
+
Subcommands:
|
|
109
|
+
status 只读聚合:订阅(membership) + 权益(entitlements)
|
|
110
|
+
ban 禁用账号(user-auth is_active=false)。需 --reason
|
|
111
|
+
unban 恢复账号
|
|
112
|
+
|
|
113
|
+
Target user: <email|phone|userId> (positional). phone/userId only on
|
|
114
|
+
cn-prod / cn-stage; AWS stage/prod resolve email only.
|
|
115
|
+
|
|
116
|
+
Options:
|
|
117
|
+
--reason "..." Ban reason (required for ban; stored on the user)
|
|
118
|
+
--yes Skip prod/cn-prod confirmation prompt
|
|
119
|
+
--env <env> Environment: stage, prod, cn-prod, cn-stage (default: stage)
|
|
120
|
+
|
|
121
|
+
Notes:
|
|
122
|
+
· ban/unban 用 admin-用户 token(Infisical /shared-secrets/credentials)。
|
|
123
|
+
· ban 非即时踢会话:仅挡新登录/刷新,活跃 token 过期后失效。
|
|
124
|
+
· 禁用原因/banned_at 与 credits 余额暂不在 status 显示(见 #36)。
|
|
125
|
+
|
|
126
|
+
Examples:
|
|
127
|
+
optima-account status 18898654855 --env cn-prod
|
|
128
|
+
optima-account ban user@example.com --reason "abuse" --env prod
|
|
129
|
+
optima-account unban 18898654855 --env cn-prod`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function main(): Promise<void> {
|
|
133
|
+
const [, , subcommand, ...rest] = process.argv;
|
|
134
|
+
if (!subcommand || subcommand === '-h' || subcommand === '--help') { printHelp(); process.exit(0); }
|
|
135
|
+
switch (subcommand) {
|
|
136
|
+
case 'status': await runStatus(rest); break;
|
|
137
|
+
case 'ban': await runBan(rest); break;
|
|
138
|
+
case 'unban': await runUnban(rest); break;
|
|
139
|
+
default:
|
|
140
|
+
console.error(`Unknown subcommand: ${subcommand}`);
|
|
141
|
+
printHelp();
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
main().catch((err) => { console.error('\n❌ Error:', err.message); process.exit(1); });
|