@optima-chat/dev-skills 0.7.29 → 0.7.31
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/trace-user.md +293 -0
- package/.claude/skills/grant-credits/SKILL.md +20 -18
- package/.claude/skills/grant-subscription/SKILL.md +16 -14
- package/.claude/skills/query-db/SKILL.md +4 -2
- package/.codex/skills/grant-credits/SKILL.md +2 -2
- package/.codex/skills/grant-subscription/SKILL.md +1 -1
- package/bin/helpers/grant-credits.ts +28 -8
- package/bin/helpers/grant-subscription.ts +17 -12
- package/bin/helpers/query-db.ts +6 -1
- package/dist/bin/helpers/grant-credits.js +28 -8
- package/dist/bin/helpers/grant-subscription.js +17 -12
- package/dist/bin/helpers/query-db.js +6 -1
- package/package.json +1 -1
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# /trace-user - 用户链路追踪
|
|
2
|
+
|
|
3
|
+
查看指定用户在 agentic-chat -> optima-gateway -> agent-runtime 全链路的日志,分析耗时、成功/失败、返回值等。
|
|
4
|
+
|
|
5
|
+
**版本**: v0.1.0
|
|
6
|
+
|
|
7
|
+
## 使用场景
|
|
8
|
+
|
|
9
|
+
**前端开发者**: 用户反馈 "聊天没反应",快速定位是前端、网关还是 Agent 的问题
|
|
10
|
+
**后端开发者**: 追踪特定用户的请求链路,分析 LLM 调用耗时和工具执行
|
|
11
|
+
**DevOps**: 排查线上用户问题,查看完整的跨服务日志关联
|
|
12
|
+
|
|
13
|
+
## 用法
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
/trace-user <user-id-or-email> [environment] [time-range] [options]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 参数
|
|
20
|
+
|
|
21
|
+
- `user-id-or-email` (必需): 用户标识
|
|
22
|
+
- 包含 `@` → email(用 `userEmail` 字段过滤)
|
|
23
|
+
- UUID 格式 → userId(用 `userId` 字段过滤)
|
|
24
|
+
- `environment` (可选): `stage` 或 `prod`,默认 `stage`
|
|
25
|
+
- `time-range` (可选): 如 `10m`, `30m`, `1h`, `2h`, `1d`,默认 `30m`
|
|
26
|
+
- 选项:
|
|
27
|
+
- `--errors` — 只看 error/warn 级别
|
|
28
|
+
- `--session SESSION_ID` — 限定某个 session
|
|
29
|
+
- `--trace TRACE_ID` — 限定某个 traceId
|
|
30
|
+
- `--raw` — 显示原始 JSON
|
|
31
|
+
|
|
32
|
+
## 示例
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
/trace-user alice@example.com # Stage,最近 30 分钟
|
|
36
|
+
/trace-user 37c03a9f-0b47-409c-81a3-5634eaab1a6c prod 2h # Prod,最近 2 小时
|
|
37
|
+
/trace-user alice@example.com --errors # 只看错误
|
|
38
|
+
/trace-user alice@example.com --session sess-xxx # 指定 session
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
如果用户输入 `/trace-user` 或 `/trace-user --help`,显示此帮助文档,不执行查询。
|
|
42
|
+
|
|
43
|
+
ARGUMENTS: $ARGUMENTS
|
|
44
|
+
|
|
45
|
+
## Claude Code 执行步骤
|
|
46
|
+
|
|
47
|
+
### 1. 解析参数
|
|
48
|
+
|
|
49
|
+
从 `$ARGUMENTS` 中解析用户标识、环境、时间范围和选项。
|
|
50
|
+
|
|
51
|
+
**用户标识判断**:
|
|
52
|
+
- 包含 `@` → email → 使用 `userEmail` 字段
|
|
53
|
+
- UUID 格式(`xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`)→ 使用 `userId` 字段
|
|
54
|
+
- 其他 → 当作 userId 尝试
|
|
55
|
+
|
|
56
|
+
### 2. 日志组映射
|
|
57
|
+
|
|
58
|
+
三层服务对应的 CloudWatch Log Group:
|
|
59
|
+
|
|
60
|
+
| 层 | 服务 | Stage 日志组 | Prod 日志组 |
|
|
61
|
+
|----|------|-------------|------------|
|
|
62
|
+
| L1 | agentic-chat | `/ecs/agentic-chat-stage` | `/ecs/agentic-chat-prod` |
|
|
63
|
+
| L2 | gateway-core | `/ecs/gateway-core-stage` | `/ecs/gateway-core-prod` |
|
|
64
|
+
| L3 | agent-runtime | `/ecs/gw-agent-runtime-stage` | `/ecs/gw-agent-runtime-prod` |
|
|
65
|
+
|
|
66
|
+
**AWS 区域**: `ap-southeast-1`
|
|
67
|
+
|
|
68
|
+
### 3. 搜索策略
|
|
69
|
+
|
|
70
|
+
#### 第一步:在 gateway-core 找到用户的 session
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# 按 email 搜索
|
|
74
|
+
aws logs filter-log-events \
|
|
75
|
+
--log-group-name /ecs/gateway-core-{ENV} \
|
|
76
|
+
--filter-pattern '{ $.userEmail = "USER_EMAIL" }' \
|
|
77
|
+
--start-time $(date -d 'TIME_RANGE ago' +%s)000 \
|
|
78
|
+
--region ap-southeast-1 \
|
|
79
|
+
--output json | head -100
|
|
80
|
+
|
|
81
|
+
# 按 userId 搜索
|
|
82
|
+
aws logs filter-log-events \
|
|
83
|
+
--log-group-name /ecs/gateway-core-{ENV} \
|
|
84
|
+
--filter-pattern '{ $.userId = "USER_ID" }' \
|
|
85
|
+
--start-time $(date -d 'TIME_RANGE ago' +%s)000 \
|
|
86
|
+
--region ap-southeast-1 \
|
|
87
|
+
--output json | head -100
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
从结果中提取 `sessionId` 和 `traceId`。
|
|
91
|
+
|
|
92
|
+
#### 第二步:跨服务关联查询(Logs Insights)
|
|
93
|
+
|
|
94
|
+
优先使用 CloudWatch Logs Insights 跨多个 log group 查询:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# 按 userId/email 跨服务查询
|
|
98
|
+
aws logs start-query \
|
|
99
|
+
--log-group-names \
|
|
100
|
+
/ecs/gateway-core-{ENV} \
|
|
101
|
+
/ecs/gw-agent-runtime-{ENV} \
|
|
102
|
+
--start-time $(date -d 'TIME_RANGE ago' +%s) \
|
|
103
|
+
--end-time $(date +%s) \
|
|
104
|
+
--query-string '
|
|
105
|
+
fields @timestamp, @logStream, @message
|
|
106
|
+
| filter userId = "USER_ID" or userEmail = "USER_EMAIL"
|
|
107
|
+
| sort @timestamp asc
|
|
108
|
+
| limit 500
|
|
109
|
+
' \
|
|
110
|
+
--region ap-southeast-1
|
|
111
|
+
|
|
112
|
+
# 等几秒后获取结果
|
|
113
|
+
aws logs get-query-results --query-id QUERY_ID --region ap-southeast-1
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
如果已知 sessionId:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
aws logs start-query \
|
|
120
|
+
--log-group-names \
|
|
121
|
+
/ecs/gateway-core-{ENV} \
|
|
122
|
+
/ecs/gw-agent-runtime-{ENV} \
|
|
123
|
+
--start-time $(date -d 'TIME_RANGE ago' +%s) \
|
|
124
|
+
--end-time $(date +%s) \
|
|
125
|
+
--query-string '
|
|
126
|
+
fields @timestamp, @logStream, level, message, sessionId, traceId, duration_ms
|
|
127
|
+
| filter sessionId = "SESSION_ID"
|
|
128
|
+
| sort @timestamp asc
|
|
129
|
+
| limit 500
|
|
130
|
+
' \
|
|
131
|
+
--region ap-southeast-1
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
#### 第三步:agentic-chat 日志(补充)
|
|
135
|
+
|
|
136
|
+
agentic-chat 前端日志可能不含 userId/sessionId 字段,用时间窗口 + 关键词辅助:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
aws logs tail /ecs/agentic-chat-{ENV} --since {TIME_RANGE} \
|
|
140
|
+
--region ap-southeast-1 | grep -i "USER_EMAIL_OR_ID"
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
#### 第四步:只看错误(--errors)
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
aws logs start-query \
|
|
147
|
+
--log-group-names \
|
|
148
|
+
/ecs/gateway-core-{ENV} \
|
|
149
|
+
/ecs/gw-agent-runtime-{ENV} \
|
|
150
|
+
--start-time $(date -d 'TIME_RANGE ago' +%s) \
|
|
151
|
+
--end-time $(date +%s) \
|
|
152
|
+
--query-string '
|
|
153
|
+
fields @timestamp, @logStream, level, message, sessionId, error
|
|
154
|
+
| filter (userId = "USER_ID" or userEmail = "USER_EMAIL")
|
|
155
|
+
and (level = "error" or level = "warn")
|
|
156
|
+
| sort @timestamp asc
|
|
157
|
+
| limit 200
|
|
158
|
+
' \
|
|
159
|
+
--region ap-southeast-1
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### 4. 结构化日志字段参考
|
|
163
|
+
|
|
164
|
+
optima-gateway 的日志通过 AsyncLocalStorage 自动注入:
|
|
165
|
+
|
|
166
|
+
```json
|
|
167
|
+
{
|
|
168
|
+
"timestamp": "2026-04-16T10:30:00.000Z",
|
|
169
|
+
"level": "info",
|
|
170
|
+
"service": "gateway-core",
|
|
171
|
+
"userId": "37c03a9f-...",
|
|
172
|
+
"userEmail": "alice@example.com",
|
|
173
|
+
"sessionId": "sess-xxx",
|
|
174
|
+
"traceId": "gw-abc123",
|
|
175
|
+
"message": "Session created",
|
|
176
|
+
"duration_ms": 123
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**关键字段**:
|
|
181
|
+
- `userId` / `userEmail` — 用户标识
|
|
182
|
+
- `sessionId` — 会话 ID
|
|
183
|
+
- `traceId` — 跨服务链路 ID
|
|
184
|
+
- `duration_ms` — 操作耗时
|
|
185
|
+
- `level` — 日志级别(info/warn/error)
|
|
186
|
+
|
|
187
|
+
### 5. 输出格式
|
|
188
|
+
|
|
189
|
+
#### A. 概览信息
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
用户: alice@example.com (37c03a9f-...)
|
|
193
|
+
环境: Stage
|
|
194
|
+
时间范围: 最近 30 分钟
|
|
195
|
+
找到 session 数: 3
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
#### B. Session 列表
|
|
199
|
+
|
|
200
|
+
| Session ID | 创建时间 | 状态 | 持续时间 | 消息数 | 错误数 |
|
|
201
|
+
|-----------|---------|------|---------|-------|-------|
|
|
202
|
+
| sess-001 | 10:30:00 | running | 5m | 12 | 0 |
|
|
203
|
+
| sess-002 | 10:20:00 | terminated | 8m | 24 | 1 |
|
|
204
|
+
|
|
205
|
+
#### C. 请求链路时间线
|
|
206
|
+
|
|
207
|
+
```
|
|
208
|
+
Session: sess-001
|
|
209
|
+
TraceId: gw-abc123
|
|
210
|
+
|
|
211
|
+
时间线:
|
|
212
|
+
┌──────────────────────────────────────────────────────────────────────┐
|
|
213
|
+
│ 10:30:00.000 [gateway-core] WS connected, authenticating... │
|
|
214
|
+
│ 10:30:00.150 [gateway-core] OIDC verified, userId=37c03a9f │ +150ms
|
|
215
|
+
│ 10:30:00.200 [gateway-core] Session creating │ +50ms
|
|
216
|
+
│ 10:30:00.800 [gateway-core] Agent task started (ECS RunTask) │ +600ms
|
|
217
|
+
│ 10:30:03.200 [agent-runtime] Container ready, WS connected │ +2400ms
|
|
218
|
+
│ 10:30:03.250 [gateway-core] Session running │ +50ms
|
|
219
|
+
│ 10:30:05.000 [gateway-core] Client message received │
|
|
220
|
+
│ 10:30:05.100 [agent-runtime] LLM request started (openai) │ +100ms
|
|
221
|
+
│ 10:30:08.500 [agent-runtime] LLM response complete │ +3400ms
|
|
222
|
+
│ 10:30:08.600 [agent-runtime] Tool call: read_file │
|
|
223
|
+
│ 10:30:08.800 [agent-runtime] Tool result returned │ +200ms
|
|
224
|
+
│ 10:30:09.000 [gateway-core] Message forwarded to client │
|
|
225
|
+
└──────────────────────────────────────────────────────────────────────┘
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
#### D. 耗时分析
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
关键耗时:
|
|
232
|
+
OIDC 认证: 150ms
|
|
233
|
+
Session 创建: 50ms
|
|
234
|
+
ECS 容器启动: 2400ms (>2s 时标记 warning)
|
|
235
|
+
WS 回连: 50ms
|
|
236
|
+
首次 LLM 调用: 3400ms
|
|
237
|
+
工具执行: 200ms
|
|
238
|
+
|
|
239
|
+
统计:
|
|
240
|
+
总消息数: 12 (user: 5, assistant: 7)
|
|
241
|
+
LLM 调用次数: 7
|
|
242
|
+
平均 LLM 延迟: 2800ms
|
|
243
|
+
工具调用次数: 3
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
#### E. 错误/告警
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
发现 1 个错误:
|
|
250
|
+
10:35:12 [agent-runtime] ERROR: LLM request failed
|
|
251
|
+
provider: openai
|
|
252
|
+
error: "rate_limit_exceeded"
|
|
253
|
+
sessionId: sess-002
|
|
254
|
+
→ 已自动重试(CircuitBreaker half-open)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### 6. 常用诊断场景
|
|
258
|
+
|
|
259
|
+
| 用户报告 | 推荐策略 |
|
|
260
|
+
|---------|---------|
|
|
261
|
+
| "连不上" / "打不开" | 查 gateway-core 连接日志 + OIDC 认证 |
|
|
262
|
+
| "没反应" / "卡住了" | 查 agent-runtime 是否收到消息 + LLM 是否响应 |
|
|
263
|
+
| "回复慢" | 查 duration_ms 字段,分析 ECS 启动 + LLM 延迟 |
|
|
264
|
+
| "出错了" / "报错" | `--errors` 模式,重点看 error 级别 |
|
|
265
|
+
| "用了一半断了" | 查 WS 断连事件 + session 状态变化 |
|
|
266
|
+
| "总是失败" | 查 CircuitBreaker 状态 + provider 切换日志 |
|
|
267
|
+
|
|
268
|
+
### 7. 快捷跟进命令
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
# 查看服务健康状态
|
|
272
|
+
curl -s https://gw.stage.optima.onl/health | jq .
|
|
273
|
+
curl -s https://ai.stage.optima.onl/api/health | jq .
|
|
274
|
+
|
|
275
|
+
# 查看服务 debug 信息
|
|
276
|
+
curl -s -H "X-Debug-Key: 7eede5747b6c50f1c8f2358b98462f74696cdef9bfeab85eaf7ea41166788b5c" \
|
|
277
|
+
https://gw.stage.optima.onl/debug/info | jq .
|
|
278
|
+
|
|
279
|
+
# 查看 ECS 服务状态
|
|
280
|
+
aws ecs describe-services --cluster optima-stage-cluster \
|
|
281
|
+
--services gateway-core-stage --region ap-southeast-1 \
|
|
282
|
+
--query 'services[0].{status:status,running:runningCount,desired:desiredCount}'
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## 注意事项
|
|
286
|
+
|
|
287
|
+
1. **CloudWatch 延迟**: 最新日志可能需要等 10-30 秒才出现
|
|
288
|
+
2. **速率限制**: `filter-log-events` 有限流,大范围查询优先用 `start-query` (Logs Insights)
|
|
289
|
+
3. **日志保留**: CloudWatch 保留 14 天
|
|
290
|
+
4. **结构化日志**: JSON 日志输出到 stderr,在 CloudWatch 中显示为普通文本行
|
|
291
|
+
5. **Insights 限制**: 最多返回 10000 条,一般够用
|
|
292
|
+
6. **权限要求**: 需要 AWS CLI 配置了正确的凭证(`ap-southeast-1` 区域)
|
|
293
|
+
7. 如果用户没有任何日志,可能是该用户在指定时间范围内没有活动、标识输入有误、或服务未部署
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: "grant-credits"
|
|
3
|
-
description: "当用户请求赠送 credits
|
|
3
|
+
description: "当用户请求赠送 credits、充值余额、grant credits、加余额、奖励积分、补偿、推荐奖励时,使用此技能。支持 Stage、Prod 两个环境。"
|
|
4
4
|
allowed-tools: ["Bash"]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
#
|
|
7
|
+
# 赠送余额(Grant Credits)
|
|
8
8
|
|
|
9
|
-
当你需要为用户赠送额外
|
|
9
|
+
当你需要为用户赠送额外 wallet 余额时,使用这个场景。赠送的额度会加到 `usd_wallets.granted_balance_micros`。
|
|
10
10
|
|
|
11
11
|
## 执行方式:使用 CLI 工具
|
|
12
12
|
|
|
@@ -19,38 +19,40 @@ optima-grant-credits <email> --amount <n> [options]
|
|
|
19
19
|
**为什么使用 CLI 工具**:
|
|
20
20
|
- 自动通过 email 查找 userId(跨 user-auth 数据库)
|
|
21
21
|
- 自动处理 SSH 隧道和数据库连接
|
|
22
|
-
-
|
|
22
|
+
- 不会影响现有订阅和已有余额(纯追加到 granted balance)
|
|
23
23
|
- 一条命令完成操作
|
|
24
24
|
|
|
25
25
|
## 适用情况
|
|
26
26
|
|
|
27
|
-
-
|
|
28
|
-
- 推荐奖励
|
|
29
|
-
- 运营活动发放
|
|
27
|
+
- 赠送额外余额(奖励、补偿、推广等)
|
|
28
|
+
- 推荐奖励
|
|
29
|
+
- 运营活动发放
|
|
30
30
|
- 客户补偿
|
|
31
31
|
|
|
32
32
|
## 快速操作
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
|
-
# 赠送 100
|
|
35
|
+
# 赠送 100 credits ($1.00),Stage 环境(默认)
|
|
36
36
|
optima-grant-credits user@example.com --amount 100
|
|
37
37
|
|
|
38
|
-
# 赠送 500
|
|
38
|
+
# 赠送 500 credits ($5.00),Prod 环境
|
|
39
39
|
optima-grant-credits user@example.com --amount 500 --env prod
|
|
40
40
|
|
|
41
|
-
# 推荐奖励
|
|
41
|
+
# 推荐奖励
|
|
42
42
|
optima-grant-credits user@example.com --amount 200 --type referral --env prod
|
|
43
43
|
|
|
44
44
|
# 自定义描述
|
|
45
45
|
optima-grant-credits user@example.com --amount 300 --description "客户补偿 - 服务中断" --env prod
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
> 1 credit = $0.01 = 10,000 micros。--amount 500 即赠送 $5.00 到 granted balance。
|
|
49
|
+
|
|
48
50
|
### 参数说明
|
|
49
51
|
|
|
50
52
|
| 参数 | 说明 | 默认值 |
|
|
51
53
|
|------|------|--------|
|
|
52
54
|
| `<email>` | 用户邮箱(必填) | - |
|
|
53
|
-
| `--amount <n>` | Credits 数量(必填,>=1) | - |
|
|
55
|
+
| `--amount <n>` | Credits 数量(必填,>=1,1 credit = $0.01) | - |
|
|
54
56
|
| `--type <type>` | 类型:bonus, referral | bonus |
|
|
55
57
|
| `--description <text>` | 描述(可选) | Auto-generated |
|
|
56
58
|
| `--env <env>` | 环境:stage, prod | stage |
|
|
@@ -59,15 +61,15 @@ optima-grant-credits user@example.com --amount 300 --description "客户补偿 -
|
|
|
59
61
|
|
|
60
62
|
| | grant-credits | grant-subscription |
|
|
61
63
|
|---|---|---|
|
|
62
|
-
| 作用 | 追加
|
|
63
|
-
|
|
|
64
|
+
| 作用 | 追加 granted balance | 开通/切换订阅计划 |
|
|
65
|
+
| 现有余额 | 不影响(纯累加) | 重置 granted balance |
|
|
64
66
|
| 现有订阅 | 不影响 | 取消旧的,创建新的 |
|
|
65
67
|
| Token quota | 不影响 | 按计划更新 |
|
|
66
68
|
| 适用场景 | 奖励、补偿、推广 | 开通会员、升级计划 |
|
|
67
69
|
|
|
68
70
|
## 常见使用场景
|
|
69
71
|
|
|
70
|
-
### 场景 1
|
|
72
|
+
### 场景 1:奖励余额
|
|
71
73
|
|
|
72
74
|
**用户请求**:"给 xxx@gmail.com 加 200 credits"
|
|
73
75
|
|
|
@@ -85,7 +87,7 @@ optima-grant-credits xxx@gmail.com --amount 300 --type referral --env prod
|
|
|
85
87
|
|
|
86
88
|
### 场景 3:客户补偿
|
|
87
89
|
|
|
88
|
-
**用户请求**:"服务出了问题,补偿 xxx
|
|
90
|
+
**用户请求**:"服务出了问题,补偿 xxx $5"
|
|
89
91
|
|
|
90
92
|
```bash
|
|
91
93
|
optima-grant-credits xxx@gmail.com --amount 500 --description "服务中断补偿" --env prod
|
|
@@ -95,11 +97,11 @@ optima-grant-credits xxx@gmail.com --amount 500 --description "服务中断补
|
|
|
95
97
|
|
|
96
98
|
1. **Stage 优先**:默认操作 Stage 环境
|
|
97
99
|
2. **Prod 谨慎**:操作 Prod 前确认邮箱和数量
|
|
98
|
-
3.
|
|
99
|
-
4. **无过期**:
|
|
100
|
+
3. **纯追加**:不会影响现有余额和订阅(累加到 granted balance)
|
|
101
|
+
4. **无过期**:granted balance 不会自动过期
|
|
100
102
|
|
|
101
103
|
## 相关命令
|
|
102
104
|
|
|
103
|
-
- `optima-grant-credits` -
|
|
105
|
+
- `optima-grant-credits` - 赠送余额(主要方式)
|
|
104
106
|
- `optima-grant-subscription` - 开通订阅计划
|
|
105
107
|
- `optima-query-db` - 查询数据库验证结果
|
|
@@ -19,8 +19,8 @@ optima-grant-subscription <email> [options]
|
|
|
19
19
|
**为什么使用 CLI 工具**:
|
|
20
20
|
- 自动通过 email 查找 userId(跨 user-auth 数据库)
|
|
21
21
|
- 自动处理 SSH 隧道和数据库连接
|
|
22
|
-
-
|
|
23
|
-
- 自动按 plan 配置授予
|
|
22
|
+
- 自动取消旧订阅、重置 wallet granted balance
|
|
23
|
+
- 自动按 plan 配置授予 USD wallet 余额和 token quota
|
|
24
24
|
- 一条命令完成所有操作
|
|
25
25
|
|
|
26
26
|
## 适用情况
|
|
@@ -59,12 +59,14 @@ optima-grant-subscription user@example.com --plan enterprise --env prod
|
|
|
59
59
|
|
|
60
60
|
### 计划配置
|
|
61
61
|
|
|
62
|
-
| Plan | Credits
|
|
63
|
-
|
|
64
|
-
| trial | 20 | 400K | 1M |
|
|
65
|
-
| starter | 500 | 2M | 10M |
|
|
66
|
-
| pro | 2,000 | 8M | 40M |
|
|
67
|
-
| enterprise | 10,000 | 16M | 80M |
|
|
62
|
+
| Plan | 月授予额 (USD) | Credits 等价 | Session Token | Weekly Token |
|
|
63
|
+
|------|---------------|-------------|---------------|--------------|
|
|
64
|
+
| trial | $0.20 | 20 | 400K | 1M |
|
|
65
|
+
| starter | $5.00 | 500 | 2M | 10M |
|
|
66
|
+
| pro | $20.00 | 2,000 | 8M | 40M |
|
|
67
|
+
| enterprise | $100.00 | 10,000 | 16M | 80M |
|
|
68
|
+
|
|
69
|
+
> 1 credit = $0.01 = 10,000 micros。授予额存入 `usd_wallets.granted_balance_micros`。
|
|
68
70
|
|
|
69
71
|
## 常见使用场景
|
|
70
72
|
|
|
@@ -97,22 +99,22 @@ optima-grant-subscription user@example.com --plan starter --env prod
|
|
|
97
99
|
工具会自动完成以下步骤:
|
|
98
100
|
|
|
99
101
|
1. 通过 email 在 user-auth 数据库查找 userId
|
|
100
|
-
2. 加载对应 plan
|
|
102
|
+
2. 加载对应 plan 的配置(月授予额、token 限额等)
|
|
101
103
|
3. 取消该用户的所有活跃订阅
|
|
102
|
-
4.
|
|
103
|
-
5.
|
|
104
|
-
6.
|
|
104
|
+
4. 创建新订阅(设置到期时间)
|
|
105
|
+
5. 重置 wallet granted balance 并授予新额度
|
|
106
|
+
6. 记录 topup 审计记录(source: subscription_grant)
|
|
105
107
|
7. 更新 token quota 限额
|
|
106
108
|
|
|
107
109
|
## 安全提醒
|
|
108
110
|
|
|
109
111
|
1. **Stage 优先**:默认操作 Stage 环境
|
|
110
112
|
2. **Prod 谨慎**:操作 Prod 前确认用户邮箱正确
|
|
111
|
-
3.
|
|
113
|
+
3. **不可逆**:旧订阅会被取消,granted balance 会被重置
|
|
112
114
|
4. **确认 email**:执行前务必确认邮箱地址无误
|
|
113
115
|
|
|
114
116
|
## 相关命令
|
|
115
117
|
|
|
116
118
|
- `optima-grant-subscription` - 开通订阅(主要方式)
|
|
117
|
-
- `optima-grant-credits` - 单独赠送
|
|
119
|
+
- `optima-grant-credits` - 单独赠送 wallet 余额
|
|
118
120
|
- `optima-query-db` - 查询数据库验证结果
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: "query-db"
|
|
3
|
-
description: "当用户请求查询数据库、执行SQL、查看数据、统计数据、检查数据库、查询表、数据库查询时,使用此技能。支持 CI、Stage、Prod 三个环境的 commerce-backend、user-auth、agentic-chat、bi-backend、session-gateway、ads-backend、amazon-backend、shopify-backend、optima-sentinel 等服务的数据库查询。优先使用 optima-query-db CLI 工具。"
|
|
3
|
+
description: "当用户请求查询数据库、执行SQL、查看数据、统计数据、检查数据库、查询表、数据库查询时,使用此技能。支持 CI、Stage、Prod 三个环境的 commerce-backend、user-auth、agentic-chat、bi-backend、session-gateway、gateway-core、ads-backend、amazon-backend、shopify-backend、optima-sentinel 等服务的数据库查询。优先使用 optima-query-db CLI 工具。"
|
|
4
4
|
allowed-tools: ["Bash", "SlashCommand"]
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -75,7 +75,8 @@ optima-query-db commerce-backend "SELECT status, COUNT(*) FROM orders GROUP BY s
|
|
|
75
75
|
- `user-auth` - 用户认证数据库
|
|
76
76
|
- `agentic-chat` - AI 聊天数据库
|
|
77
77
|
- `bi-backend` - BI 后端数据库
|
|
78
|
-
- `session-gateway` - AI Shell
|
|
78
|
+
- `session-gateway` - AI Shell 网关数据库(老)
|
|
79
|
+
- `gateway-core` - Optima Gateway (`optima_gateway`) 数据库 — sessions / conversations / messages / transcripts / credits
|
|
79
80
|
- `optima-logistics` - 物流服务数据库
|
|
80
81
|
- `ads-backend` - Ads 数据库
|
|
81
82
|
- `amazon-backend` - Amazon 数据库
|
|
@@ -212,6 +213,7 @@ optima-query-db commerce-backend "SELECT status, COUNT(*) FROM orders GROUP BY s
|
|
|
212
213
|
| shopify-backend | `/services/shopify-backend` |
|
|
213
214
|
| optima-generation | `/services/optima-generation` |
|
|
214
215
|
| optima-sentinel | `/services/optima-sentinel` |
|
|
216
|
+
| gateway-core | `/services/gateway-core` |
|
|
215
217
|
|
|
216
218
|
### RDS 连接
|
|
217
219
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: "grant-credits"
|
|
3
|
-
description: "Use when the user wants to add bonus or referral
|
|
3
|
+
description: "Use when the user wants to add bonus or referral balance to an Optima user's wallet without changing their subscription."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Grant Credits
|
|
@@ -25,4 +25,4 @@ optima-grant-credits user@example.com --amount 300 --type referral --env prod
|
|
|
25
25
|
|
|
26
26
|
- Default to `stage`.
|
|
27
27
|
- Confirm the email and amount before using `prod`.
|
|
28
|
-
- This operation appends
|
|
28
|
+
- This operation appends to the user's wallet granted balance and does not replace their subscription. Amount is in credits (1 credit = $0.01).
|
|
@@ -25,5 +25,5 @@ optima-grant-subscription user@example.com --plan enterprise --env prod
|
|
|
25
25
|
|
|
26
26
|
- Default to `stage`.
|
|
27
27
|
- Confirm the user email before running on `prod`.
|
|
28
|
-
- This operation replaces existing subscription state and resets
|
|
28
|
+
- This operation replaces existing subscription state and resets the wallet granted balance according to the selected plan.
|
|
29
29
|
- Use `optima-query-db` afterward if the user asks for verification.
|
|
@@ -40,7 +40,8 @@ async function main() {
|
|
|
40
40
|
const infisicalConfig = getInfisicalConfig();
|
|
41
41
|
const token = getInfisicalToken(infisicalConfig);
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
const amountMicros = amount * 10000; // 1 credit = $0.01 = 10,000 micros
|
|
44
|
+
console.log(`\n🎁 Granting $${(amountMicros / 1000000).toFixed(2)} (${amount} credits) as ${type} to ${email} [${env.toUpperCase()}]\n`);
|
|
44
45
|
|
|
45
46
|
const userId = await resolveUserId(email, env, infisicalConfig, token);
|
|
46
47
|
const billing = await connectBillingDB(env, infisicalConfig, token);
|
|
@@ -48,15 +49,34 @@ async function main() {
|
|
|
48
49
|
|
|
49
50
|
const now = new Date().toISOString();
|
|
50
51
|
const safeUserId = escapeSQL(userId);
|
|
51
|
-
const
|
|
52
|
-
const safeDesc = escapeSQL(description || `Admin ${type} credit grant`);
|
|
52
|
+
const safeDesc = escapeSQL(description || `Admin ${type} grant`);
|
|
53
53
|
|
|
54
|
-
console.log(`
|
|
55
|
-
const
|
|
56
|
-
|
|
54
|
+
console.log(`Granting to wallet...`);
|
|
55
|
+
const txSQL = `
|
|
56
|
+
BEGIN;
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
-- Ensure wallet exists
|
|
59
|
+
INSERT INTO usd_wallets (id, user_id, balance_micros, reserved_micros, granted_balance_micros, created_at, updated_at)
|
|
60
|
+
VALUES (gen_random_uuid(), '${safeUserId}', 0, 0, 0, '${now}', '${now}')
|
|
61
|
+
ON CONFLICT (user_id) DO NOTHING;
|
|
62
|
+
|
|
63
|
+
-- Add to granted balance
|
|
64
|
+
UPDATE usd_wallets SET granted_balance_micros = granted_balance_micros + ${amountMicros}, updated_at = '${now}'
|
|
65
|
+
WHERE user_id = '${safeUserId}';
|
|
66
|
+
|
|
67
|
+
-- Record topup for audit trail
|
|
68
|
+
INSERT INTO usd_wallet_topups (id, wallet_id, amount_micros, service_fee_micros, net_credit_micros, status, source, service_namespace, created_at, completed_at)
|
|
69
|
+
SELECT gen_random_uuid(), w.id, ${amountMicros}, 0, ${amountMicros}, 'completed', 'admin_grant', 'platform', '${now}', '${now}'
|
|
70
|
+
FROM usd_wallets w WHERE w.user_id = '${safeUserId}';
|
|
71
|
+
|
|
72
|
+
COMMIT;
|
|
73
|
+
`.trim();
|
|
74
|
+
bq(txSQL);
|
|
75
|
+
console.log(`✓ Wallet granted $${(amountMicros / 1000000).toFixed(2)}`);
|
|
76
|
+
|
|
77
|
+
const balanceMicros = bq(`SELECT granted_balance_micros FROM usd_wallets WHERE user_id='${safeUserId}'`);
|
|
78
|
+
const balanceUsd = (parseInt(balanceMicros, 10) / 1000000).toFixed(2);
|
|
79
|
+
console.log(`\n✅ Done! ${email} now has $${balanceUsd} granted balance\n`);
|
|
60
80
|
}
|
|
61
81
|
|
|
62
82
|
main().catch(error => {
|
|
@@ -53,9 +53,10 @@ async function main() {
|
|
|
53
53
|
|
|
54
54
|
const [planName, monthlyCreditsStr, sessionTokenLimitStr, weeklyTokenLimitStr] = planRow.split('|');
|
|
55
55
|
const monthlyCredits = parseInt(monthlyCreditsStr, 10);
|
|
56
|
+
const grantMicros = monthlyCredits * 10000; // 1 credit = $0.01 = 10,000 micros
|
|
56
57
|
const sessionTokenLimit = parseInt(sessionTokenLimitStr, 10);
|
|
57
58
|
const weeklyTokenLimit = parseInt(weeklyTokenLimitStr, 10);
|
|
58
|
-
console.log(`✓ Plan: ${planName} (
|
|
59
|
+
console.log(`✓ Plan: ${planName} (grant: $${(grantMicros / 1000000).toFixed(2)}, session: ${sessionTokenLimit.toLocaleString()}, weekly: ${weeklyTokenLimit.toLocaleString()})`);
|
|
59
60
|
|
|
60
61
|
// Execute all mutations in a single transaction
|
|
61
62
|
const now = new Date().toISOString();
|
|
@@ -77,18 +78,23 @@ BEGIN;
|
|
|
77
78
|
UPDATE subscriptions SET status='canceled', canceled_at='${now}'
|
|
78
79
|
WHERE user_id='${safeUserId}' AND status IN ('active','trialing');
|
|
79
80
|
|
|
80
|
-
-- Zero out old subscription credits
|
|
81
|
-
UPDATE credit_ledger SET remaining=0
|
|
82
|
-
WHERE user_id='${safeUserId}' AND type IN ('monthly_grant','subscription') AND remaining > 0;
|
|
83
|
-
|
|
84
81
|
-- Create new subscription
|
|
85
82
|
INSERT INTO subscriptions (id, user_id, plan_id, status, billing_interval, current_period_start, current_period_end, created_at, updated_at)
|
|
86
83
|
VALUES (concat('sub_gift_', substr(md5(random()::text), 1, 16)), '${safeUserId}', '${safePlan}', 'active', 'monthly', '${now}', '${periodEndISO}', '${now}', '${now}');
|
|
87
84
|
|
|
88
|
-
--
|
|
89
|
-
INSERT INTO
|
|
90
|
-
|
|
91
|
-
|
|
85
|
+
-- Ensure wallet exists (upsert)
|
|
86
|
+
INSERT INTO usd_wallets (id, user_id, balance_micros, reserved_micros, granted_balance_micros, created_at, updated_at)
|
|
87
|
+
VALUES (gen_random_uuid(), '${safeUserId}', 0, 0, 0, '${now}', '${now}')
|
|
88
|
+
ON CONFLICT (user_id) DO NOTHING;
|
|
89
|
+
|
|
90
|
+
-- Reset granted balance and set new grant
|
|
91
|
+
UPDATE usd_wallets SET granted_balance_micros = ${grantMicros}, updated_at = '${now}'
|
|
92
|
+
WHERE user_id = '${safeUserId}';
|
|
93
|
+
|
|
94
|
+
-- Record topup for audit trail
|
|
95
|
+
INSERT INTO usd_wallet_topups (id, wallet_id, amount_micros, service_fee_micros, net_credit_micros, status, source, service_namespace, created_at, completed_at)
|
|
96
|
+
SELECT gen_random_uuid(), w.id, ${grantMicros}, 0, ${grantMicros}, 'completed', 'subscription_grant', 'platform', '${now}', '${now}'
|
|
97
|
+
FROM usd_wallets w WHERE w.user_id = '${safeUserId}';
|
|
92
98
|
|
|
93
99
|
-- Update existing active session quota, or insert new one if none exists
|
|
94
100
|
UPDATE token_quotas SET plan_id='${safePlan}', monthly_limit=${sessionTokenLimit}, updated_at='${now}'
|
|
@@ -112,10 +118,9 @@ COMMIT;
|
|
|
112
118
|
bq(txSQL);
|
|
113
119
|
|
|
114
120
|
console.log('✓ Old subscriptions canceled');
|
|
115
|
-
console.log('✓ Old credits cleared');
|
|
116
121
|
console.log(`✓ ${planName} subscription created (expires: ${periodEnd.toLocaleDateString()})`);
|
|
117
|
-
if (
|
|
118
|
-
console.log(`✓ ${monthlyCredits} credits
|
|
122
|
+
if (grantMicros > 0) {
|
|
123
|
+
console.log(`✓ Wallet granted $${(grantMicros / 1000000).toFixed(2)} (${monthlyCredits} credits equivalent)`);
|
|
119
124
|
}
|
|
120
125
|
console.log(`✓ Token quotas updated (session: ${sessionTokenLimit.toLocaleString()}, weekly: ${weeklyTokenLimit.toLocaleString()})`);
|
|
121
126
|
|
package/bin/helpers/query-db.ts
CHANGED
|
@@ -43,6 +43,11 @@ const SERVICE_DB_MAP = {
|
|
|
43
43
|
stage: { userKey: 'AI_SHELL_DB_USER', passwordKey: 'AI_SHELL_DB_PASSWORD', database: 'optima_shell' },
|
|
44
44
|
prod: { userKey: 'AI_SHELL_DB_USER', passwordKey: 'AI_SHELL_DB_PASSWORD', database: 'optima_ai_shell' }
|
|
45
45
|
},
|
|
46
|
+
'gateway-core': {
|
|
47
|
+
ci: null, // CI 环境 gateway-core 不带 DB(本地 JSONL-only 模式)
|
|
48
|
+
stage: { databaseUrlPath: '/services/gateway-core', databaseUrlKey: 'DATABASE_URL' },
|
|
49
|
+
prod: { databaseUrlPath: '/services/gateway-core', databaseUrlKey: 'DATABASE_URL' }
|
|
50
|
+
},
|
|
46
51
|
'optima-logistics': {
|
|
47
52
|
ci: null,
|
|
48
53
|
stage: { userKey: 'LOGISTICS_DB_USER', passwordKey: 'LOGISTICS_DB_PASSWORD', database: 'optima_stage_logistics' },
|
|
@@ -229,7 +234,7 @@ async function main() {
|
|
|
229
234
|
if (args.length < 2) {
|
|
230
235
|
console.error('Usage: query-db.ts <service> <sql> [environment]');
|
|
231
236
|
console.error('');
|
|
232
|
-
console.error('Services: commerce-backend, user-auth, agentic-chat, bi-backend, session-gateway, optima-logistics, billing, ads-backend, amazon-backend, browser-backend, shopify-backend, optima-generation, optima-sentinel');
|
|
237
|
+
console.error('Services: commerce-backend, user-auth, agentic-chat, bi-backend, session-gateway, gateway-core, optima-logistics, billing, ads-backend, amazon-backend, browser-backend, shopify-backend, optima-generation, optima-sentinel');
|
|
233
238
|
console.error('Environments: ci (default), stage, prod');
|
|
234
239
|
console.error('');
|
|
235
240
|
console.error('Example: query-db.ts user-auth "SELECT COUNT(*) FROM users" prod');
|
|
@@ -51,19 +51,39 @@ async function main() {
|
|
|
51
51
|
const { email, amount, type, description, env } = parseArgs(process.argv.slice(2));
|
|
52
52
|
const infisicalConfig = (0, db_utils_1.getInfisicalConfig)();
|
|
53
53
|
const token = (0, db_utils_1.getInfisicalToken)(infisicalConfig);
|
|
54
|
-
|
|
54
|
+
const amountMicros = amount * 10000; // 1 credit = $0.01 = 10,000 micros
|
|
55
|
+
console.log(`\n🎁 Granting $${(amountMicros / 1000000).toFixed(2)} (${amount} credits) as ${type} to ${email} [${env.toUpperCase()}]\n`);
|
|
55
56
|
const userId = await (0, db_utils_1.resolveUserId)(email, env, infisicalConfig, token);
|
|
56
57
|
const billing = await (0, db_utils_1.connectBillingDB)(env, infisicalConfig, token);
|
|
57
58
|
const bq = billing.query;
|
|
58
59
|
const now = new Date().toISOString();
|
|
59
60
|
const safeUserId = (0, db_utils_1.escapeSQL)(userId);
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
61
|
+
const safeDesc = (0, db_utils_1.escapeSQL)(description || `Admin ${type} grant`);
|
|
62
|
+
console.log(`Granting to wallet...`);
|
|
63
|
+
const txSQL = `
|
|
64
|
+
BEGIN;
|
|
65
|
+
|
|
66
|
+
-- Ensure wallet exists
|
|
67
|
+
INSERT INTO usd_wallets (id, user_id, balance_micros, reserved_micros, granted_balance_micros, created_at, updated_at)
|
|
68
|
+
VALUES (gen_random_uuid(), '${safeUserId}', 0, 0, 0, '${now}', '${now}')
|
|
69
|
+
ON CONFLICT (user_id) DO NOTHING;
|
|
70
|
+
|
|
71
|
+
-- Add to granted balance
|
|
72
|
+
UPDATE usd_wallets SET granted_balance_micros = granted_balance_micros + ${amountMicros}, updated_at = '${now}'
|
|
73
|
+
WHERE user_id = '${safeUserId}';
|
|
74
|
+
|
|
75
|
+
-- Record topup for audit trail
|
|
76
|
+
INSERT INTO usd_wallet_topups (id, wallet_id, amount_micros, service_fee_micros, net_credit_micros, status, source, service_namespace, created_at, completed_at)
|
|
77
|
+
SELECT gen_random_uuid(), w.id, ${amountMicros}, 0, ${amountMicros}, 'completed', 'admin_grant', 'platform', '${now}', '${now}'
|
|
78
|
+
FROM usd_wallets w WHERE w.user_id = '${safeUserId}';
|
|
79
|
+
|
|
80
|
+
COMMIT;
|
|
81
|
+
`.trim();
|
|
82
|
+
bq(txSQL);
|
|
83
|
+
console.log(`✓ Wallet granted $${(amountMicros / 1000000).toFixed(2)}`);
|
|
84
|
+
const balanceMicros = bq(`SELECT granted_balance_micros FROM usd_wallets WHERE user_id='${safeUserId}'`);
|
|
85
|
+
const balanceUsd = (parseInt(balanceMicros, 10) / 1000000).toFixed(2);
|
|
86
|
+
console.log(`\n✅ Done! ${email} now has $${balanceUsd} granted balance\n`);
|
|
67
87
|
}
|
|
68
88
|
main().catch(error => {
|
|
69
89
|
console.error('\n❌ Error:', error.message);
|
|
@@ -59,9 +59,10 @@ async function main() {
|
|
|
59
59
|
}
|
|
60
60
|
const [planName, monthlyCreditsStr, sessionTokenLimitStr, weeklyTokenLimitStr] = planRow.split('|');
|
|
61
61
|
const monthlyCredits = parseInt(monthlyCreditsStr, 10);
|
|
62
|
+
const grantMicros = monthlyCredits * 10000; // 1 credit = $0.01 = 10,000 micros
|
|
62
63
|
const sessionTokenLimit = parseInt(sessionTokenLimitStr, 10);
|
|
63
64
|
const weeklyTokenLimit = parseInt(weeklyTokenLimitStr, 10);
|
|
64
|
-
console.log(`✓ Plan: ${planName} (
|
|
65
|
+
console.log(`✓ Plan: ${planName} (grant: $${(grantMicros / 1000000).toFixed(2)}, session: ${sessionTokenLimit.toLocaleString()}, weekly: ${weeklyTokenLimit.toLocaleString()})`);
|
|
65
66
|
// Execute all mutations in a single transaction
|
|
66
67
|
const now = new Date().toISOString();
|
|
67
68
|
const periodEnd = new Date();
|
|
@@ -80,18 +81,23 @@ BEGIN;
|
|
|
80
81
|
UPDATE subscriptions SET status='canceled', canceled_at='${now}'
|
|
81
82
|
WHERE user_id='${safeUserId}' AND status IN ('active','trialing');
|
|
82
83
|
|
|
83
|
-
-- Zero out old subscription credits
|
|
84
|
-
UPDATE credit_ledger SET remaining=0
|
|
85
|
-
WHERE user_id='${safeUserId}' AND type IN ('monthly_grant','subscription') AND remaining > 0;
|
|
86
|
-
|
|
87
84
|
-- Create new subscription
|
|
88
85
|
INSERT INTO subscriptions (id, user_id, plan_id, status, billing_interval, current_period_start, current_period_end, created_at, updated_at)
|
|
89
86
|
VALUES (concat('sub_gift_', substr(md5(random()::text), 1, 16)), '${safeUserId}', '${safePlan}', 'active', 'monthly', '${now}', '${periodEndISO}', '${now}', '${now}');
|
|
90
87
|
|
|
91
|
-
--
|
|
92
|
-
INSERT INTO
|
|
93
|
-
|
|
94
|
-
|
|
88
|
+
-- Ensure wallet exists (upsert)
|
|
89
|
+
INSERT INTO usd_wallets (id, user_id, balance_micros, reserved_micros, granted_balance_micros, created_at, updated_at)
|
|
90
|
+
VALUES (gen_random_uuid(), '${safeUserId}', 0, 0, 0, '${now}', '${now}')
|
|
91
|
+
ON CONFLICT (user_id) DO NOTHING;
|
|
92
|
+
|
|
93
|
+
-- Reset granted balance and set new grant
|
|
94
|
+
UPDATE usd_wallets SET granted_balance_micros = ${grantMicros}, updated_at = '${now}'
|
|
95
|
+
WHERE user_id = '${safeUserId}';
|
|
96
|
+
|
|
97
|
+
-- Record topup for audit trail
|
|
98
|
+
INSERT INTO usd_wallet_topups (id, wallet_id, amount_micros, service_fee_micros, net_credit_micros, status, source, service_namespace, created_at, completed_at)
|
|
99
|
+
SELECT gen_random_uuid(), w.id, ${grantMicros}, 0, ${grantMicros}, 'completed', 'subscription_grant', 'platform', '${now}', '${now}'
|
|
100
|
+
FROM usd_wallets w WHERE w.user_id = '${safeUserId}';
|
|
95
101
|
|
|
96
102
|
-- Update existing active session quota, or insert new one if none exists
|
|
97
103
|
UPDATE token_quotas SET plan_id='${safePlan}', monthly_limit=${sessionTokenLimit}, updated_at='${now}'
|
|
@@ -113,10 +119,9 @@ COMMIT;
|
|
|
113
119
|
`.trim();
|
|
114
120
|
bq(txSQL);
|
|
115
121
|
console.log('✓ Old subscriptions canceled');
|
|
116
|
-
console.log('✓ Old credits cleared');
|
|
117
122
|
console.log(`✓ ${planName} subscription created (expires: ${periodEnd.toLocaleDateString()})`);
|
|
118
|
-
if (
|
|
119
|
-
console.log(`✓ ${monthlyCredits} credits
|
|
123
|
+
if (grantMicros > 0) {
|
|
124
|
+
console.log(`✓ Wallet granted $${(grantMicros / 1000000).toFixed(2)} (${monthlyCredits} credits equivalent)`);
|
|
120
125
|
}
|
|
121
126
|
console.log(`✓ Token quotas updated (session: ${sessionTokenLimit.toLocaleString()}, weekly: ${weeklyTokenLimit.toLocaleString()})`);
|
|
122
127
|
console.log(`\n✅ Done! ${email} now has ${planName} plan until ${periodEnd.toLocaleDateString()}\n`);
|
|
@@ -62,6 +62,11 @@ const SERVICE_DB_MAP = {
|
|
|
62
62
|
stage: { userKey: 'AI_SHELL_DB_USER', passwordKey: 'AI_SHELL_DB_PASSWORD', database: 'optima_shell' },
|
|
63
63
|
prod: { userKey: 'AI_SHELL_DB_USER', passwordKey: 'AI_SHELL_DB_PASSWORD', database: 'optima_ai_shell' }
|
|
64
64
|
},
|
|
65
|
+
'gateway-core': {
|
|
66
|
+
ci: null, // CI 环境 gateway-core 不带 DB(本地 JSONL-only 模式)
|
|
67
|
+
stage: { databaseUrlPath: '/services/gateway-core', databaseUrlKey: 'DATABASE_URL' },
|
|
68
|
+
prod: { databaseUrlPath: '/services/gateway-core', databaseUrlKey: 'DATABASE_URL' }
|
|
69
|
+
},
|
|
65
70
|
'optima-logistics': {
|
|
66
71
|
ci: null,
|
|
67
72
|
stage: { userKey: 'LOGISTICS_DB_USER', passwordKey: 'LOGISTICS_DB_PASSWORD', database: 'optima_stage_logistics' },
|
|
@@ -219,7 +224,7 @@ async function main() {
|
|
|
219
224
|
if (args.length < 2) {
|
|
220
225
|
console.error('Usage: query-db.ts <service> <sql> [environment]');
|
|
221
226
|
console.error('');
|
|
222
|
-
console.error('Services: commerce-backend, user-auth, agentic-chat, bi-backend, session-gateway, optima-logistics, billing, ads-backend, amazon-backend, browser-backend, shopify-backend, optima-generation, optima-sentinel');
|
|
227
|
+
console.error('Services: commerce-backend, user-auth, agentic-chat, bi-backend, session-gateway, gateway-core, optima-logistics, billing, ads-backend, amazon-backend, browser-backend, shopify-backend, optima-generation, optima-sentinel');
|
|
223
228
|
console.error('Environments: ci (default), stage, prod');
|
|
224
229
|
console.error('');
|
|
225
230
|
console.error('Example: query-db.ts user-auth "SELECT COUNT(*) FROM users" prod');
|