@optima-chat/dev-skills 0.7.14 → 0.7.16
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/query-db.md +4 -4
- package/.claude/skills/query-db/SKILL.md +1 -1
- package/.claude/skills/show-env/SKILL.md +185 -0
- package/bin/helpers/query-db.ts +2 -2
- package/bin/helpers/show-env.ts +183 -0
- package/dist/bin/helpers/query-db.js +2 -2
- package/dist/bin/helpers/show-env.js +145 -0
- package/package.json +3 -2
|
@@ -192,7 +192,7 @@ print(f\"COMMERCE_DB_PASSWORD={secrets['COMMERCE_DB_PASSWORD']}\")
|
|
|
192
192
|
" > /tmp/stage_db_config.sh && source /tmp/stage_db_config.sh
|
|
193
193
|
|
|
194
194
|
# 4. 建立 SSH 隧道到 Shared EC2,通过隧道访问 Stage RDS
|
|
195
|
-
ssh -i ~/.ssh/optima-ec2-key -f -N -L 15432:optima-stage-postgres.ctg866o0ehac.ap-southeast-1.rds.amazonaws.com:5432 ec2-user@
|
|
195
|
+
ssh -i ~/.ssh/optima-ec2-key -f -N -L 15432:optima-stage-postgres.ctg866o0ehac.ap-southeast-1.rds.amazonaws.com:5432 ec2-user@3.0.210.113
|
|
196
196
|
|
|
197
197
|
# 5. 通过本地端口 15432 连接到 RDS
|
|
198
198
|
PGPASSWORD="${COMMERCE_DB_PASSWORD}" psql -h localhost -p 15432 -U "${COMMERCE_DB_USER}" -d optima_commerce -c "SELECT COUNT(*) FROM products"
|
|
@@ -249,7 +249,7 @@ pkill -f "ssh.*15432:${DATABASE_HOST}:5432"
|
|
|
249
249
|
- Infisical 配置从 GitHub Variables 获取
|
|
250
250
|
- 数据库密钥从 Infisical 动态获取(项目: optima-secrets-v2, 环境: staging, 路径: /shared-secrets/database-users)
|
|
251
251
|
- Stage RDS: `optima-stage-postgres.ctg866o0ehac.ap-southeast-1.rds.amazonaws.com`
|
|
252
|
-
- Shared EC2 IP: `
|
|
252
|
+
- Shared EC2 IP: `3.0.210.113`
|
|
253
253
|
- SSH 隧道: 本地端口 `15432` → Shared EC2 → Stage RDS `5432`
|
|
254
254
|
- Stage 和 Prod 有独立的 RDS 实例
|
|
255
255
|
- ⚠️ session-gateway 数据库名: Stage 用 `optima_shell`, Prod 用 `optima_ai_shell`
|
|
@@ -290,7 +290,7 @@ print(f\"COMMERCE_DB_PASSWORD={secrets['COMMERCE_DB_PASSWORD']}\")
|
|
|
290
290
|
" > /tmp/prod_db_config.sh && source /tmp/prod_db_config.sh
|
|
291
291
|
|
|
292
292
|
# 4. 建立 SSH 隧道到 Shared EC2,通过隧道访问 Prod RDS
|
|
293
|
-
ssh -i ~/.ssh/optima-ec2-key -f -N -L 15433:optima-prod-postgres.ctg866o0ehac.ap-southeast-1.rds.amazonaws.com:5432 ec2-user@
|
|
293
|
+
ssh -i ~/.ssh/optima-ec2-key -f -N -L 15433:optima-prod-postgres.ctg866o0ehac.ap-southeast-1.rds.amazonaws.com:5432 ec2-user@3.0.210.113
|
|
294
294
|
|
|
295
295
|
# 5. 通过本地端口 15433 连接到 RDS
|
|
296
296
|
PGPASSWORD="${COMMERCE_DB_PASSWORD}" psql -h localhost -p 15433 -U "${COMMERCE_DB_USER}" -d optima_commerce -c "SELECT COUNT(*) FROM products"
|
|
@@ -347,7 +347,7 @@ pkill -f "ssh.*15433:${DATABASE_HOST}:5432"
|
|
|
347
347
|
- Infisical 配置从 GitHub Variables 获取
|
|
348
348
|
- 数据库密钥从 Infisical 动态获取(项目: optima-secrets-v2, 环境: prod, 路径: /shared-secrets/database-users)
|
|
349
349
|
- Prod RDS: `optima-prod-postgres.ctg866o0ehac.ap-southeast-1.rds.amazonaws.com`
|
|
350
|
-
- Shared EC2 IP: `
|
|
350
|
+
- Shared EC2 IP: `3.0.210.113`
|
|
351
351
|
- SSH 隧道: 本地端口 `15433` → Shared EC2 → Prod RDS `5432`
|
|
352
352
|
- Stage 用端口 `15432`,Prod 用端口 `15433`
|
|
353
353
|
- Stage 和 Prod 有独立的 RDS 实例
|
|
@@ -203,7 +203,7 @@ optima-query-db commerce-backend "SELECT status, COUNT(*) FROM orders GROUP BY s
|
|
|
203
203
|
| Stage | `optima-stage-postgres.ctg866o0ehac.ap-southeast-1.rds.amazonaws.com` | 15432 |
|
|
204
204
|
| Prod | `optima-prod-postgres.ctg866o0ehac.ap-southeast-1.rds.amazonaws.com` | 15433 |
|
|
205
205
|
|
|
206
|
-
**跳板机**: `
|
|
206
|
+
**跳板机**: `3.0.210.113` (Shared EC2)
|
|
207
207
|
|
|
208
208
|
## 🔗 相关命令
|
|
209
209
|
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "show-env"
|
|
3
|
+
description: "当用户请求查看环境变量、查看配置、查看服务配置、环境变量是什么、env 变量、服务环境变量、查看 Infisical 配置时,使用此技能。支持查看当前 shell 环境变量,以及 Stage、Prod 环境的服务配置。"
|
|
4
|
+
allowed-tools: ["Bash", "SlashCommand"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 查看环境变量
|
|
8
|
+
|
|
9
|
+
当你需要查看环境变量或服务配置时,使用这个场景。
|
|
10
|
+
|
|
11
|
+
## 适用情况
|
|
12
|
+
|
|
13
|
+
- 查看当前 shell 的环境变量
|
|
14
|
+
- 查看服务的环境变量配置(从 Infisical)
|
|
15
|
+
- 排查环境配置问题
|
|
16
|
+
- 确认某个环境变量是否设置正确
|
|
17
|
+
- 对比不同环境的配置差异
|
|
18
|
+
|
|
19
|
+
## 快速操作
|
|
20
|
+
|
|
21
|
+
### 1. 查看当前 shell 环境变量
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# 查看所有环境变量
|
|
25
|
+
env
|
|
26
|
+
|
|
27
|
+
# 查看特定环境变量
|
|
28
|
+
echo $OPTIMA_TOKEN
|
|
29
|
+
echo $OPTIMA_ENV
|
|
30
|
+
|
|
31
|
+
# 搜索包含特定关键字的环境变量
|
|
32
|
+
env | grep -i optima
|
|
33
|
+
env | grep -i aws
|
|
34
|
+
env | grep -i database
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. 查看 Infisical 中的服务配置
|
|
38
|
+
|
|
39
|
+
使用 `optima-show-env` CLI 工具查看存储在 Infisical 中的服务配置:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# 查看 Stage 环境的 commerce-backend 配置
|
|
43
|
+
optima-show-env commerce-backend stage
|
|
44
|
+
|
|
45
|
+
# 查看 Prod 环境的 user-auth 配置
|
|
46
|
+
optima-show-env user-auth prod
|
|
47
|
+
|
|
48
|
+
# 查看 Stage 环境的 agentic-chat 配置
|
|
49
|
+
optima-show-env agentic-chat stage
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**支持的服务**:
|
|
53
|
+
- `commerce-backend` - 电商后端 API
|
|
54
|
+
- `user-auth` - 用户认证服务
|
|
55
|
+
- `agentic-chat` - AI 聊天服务
|
|
56
|
+
- `bi` - BI 后端
|
|
57
|
+
- `session-gateway` - AI Shell 网关
|
|
58
|
+
- `optima-store` - 商城前端
|
|
59
|
+
- `optima-scout` - 产品研究工具
|
|
60
|
+
- `mcp-host` - MCP 主机
|
|
61
|
+
|
|
62
|
+
**支持的环境**:
|
|
63
|
+
- `stage` - Stage 预发布环境
|
|
64
|
+
- `prod` - Prod 生产环境
|
|
65
|
+
|
|
66
|
+
### 3. 使用过滤和选项
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# 只查看数据库相关配置
|
|
70
|
+
optima-show-env commerce-backend stage --filter DATABASE
|
|
71
|
+
|
|
72
|
+
# 只查看 STRIPE 相关配置
|
|
73
|
+
optima-show-env commerce-backend stage --filter STRIPE
|
|
74
|
+
|
|
75
|
+
# 只显示变量名(不显示值)
|
|
76
|
+
optima-show-env user-auth prod --keys-only
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## 常见场景
|
|
80
|
+
|
|
81
|
+
### 场景 1:确认环境变量是否设置
|
|
82
|
+
|
|
83
|
+
**用户请求**:"帮我看看 OPTIMA_TOKEN 环境变量设置了没有"
|
|
84
|
+
|
|
85
|
+
**步骤**:
|
|
86
|
+
```bash
|
|
87
|
+
# 检查是否设置
|
|
88
|
+
if [ -n "$OPTIMA_TOKEN" ]; then
|
|
89
|
+
echo "OPTIMA_TOKEN 已设置"
|
|
90
|
+
echo "长度: ${#OPTIMA_TOKEN} 字符"
|
|
91
|
+
else
|
|
92
|
+
echo "OPTIMA_TOKEN 未设置"
|
|
93
|
+
fi
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### 场景 2:排查服务配置问题
|
|
97
|
+
|
|
98
|
+
**用户请求**:"commerce-backend 连接不上数据库,帮我看看配置"
|
|
99
|
+
|
|
100
|
+
**步骤**:
|
|
101
|
+
1. 查看数据库相关配置:
|
|
102
|
+
```bash
|
|
103
|
+
optima-show-env commerce-backend stage --filter DATABASE
|
|
104
|
+
```
|
|
105
|
+
2. 确认连接字符串格式
|
|
106
|
+
3. 检查密码是否包含特殊字符需要转义
|
|
107
|
+
|
|
108
|
+
### 场景 3:对比不同环境配置
|
|
109
|
+
|
|
110
|
+
**用户请求**:"对比一下 Stage 和 Prod 环境的配置差异"
|
|
111
|
+
|
|
112
|
+
**步骤**:
|
|
113
|
+
```bash
|
|
114
|
+
# 导出两个环境的配置(只看变量名)
|
|
115
|
+
optima-show-env commerce-backend stage --keys-only > /tmp/env-stage.txt
|
|
116
|
+
optima-show-env commerce-backend prod --keys-only > /tmp/env-prod.txt
|
|
117
|
+
|
|
118
|
+
# 对比差异
|
|
119
|
+
diff /tmp/env-stage.txt /tmp/env-prod.txt
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 场景 4:查看特定类型配置
|
|
123
|
+
|
|
124
|
+
**用户请求**:"帮我看看 Stripe 相关配置"
|
|
125
|
+
|
|
126
|
+
**步骤**:
|
|
127
|
+
```bash
|
|
128
|
+
optima-show-env commerce-backend stage --filter STRIPE
|
|
129
|
+
optima-show-env commerce-backend prod --filter STRIPE
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## 环境变量分类
|
|
133
|
+
|
|
134
|
+
### 常见环境变量类型
|
|
135
|
+
|
|
136
|
+
| 类型 | 示例变量 | 说明 |
|
|
137
|
+
|------|----------|------|
|
|
138
|
+
| 数据库 | `DATABASE_URL`, `DB_HOST`, `DB_PASSWORD` | 数据库连接配置 |
|
|
139
|
+
| Redis | `REDIS_URL`, `REDIS_HOST`, `REDIS_DB` | 缓存配置 |
|
|
140
|
+
| AWS/S3 | `MINIO_ACCESS_KEY`, `MINIO_SECRET_KEY`, `MINIO_BUCKET` | 对象存储配置 |
|
|
141
|
+
| Auth | `JWT_SECRET_KEY`, `OAUTH_CLIENT_ID`, `SECRET_KEY` | 认证相关配置 |
|
|
142
|
+
| Stripe | `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET` | 支付配置 |
|
|
143
|
+
| 应用 | `DEBUG`, `LOG_LEVEL`, `NODE_ENV` | 应用运行配置 |
|
|
144
|
+
|
|
145
|
+
## CLI 选项说明
|
|
146
|
+
|
|
147
|
+
| 选项 | 说明 |
|
|
148
|
+
|------|------|
|
|
149
|
+
| `--filter <pattern>` | 按变量名过滤(不区分大小写) |
|
|
150
|
+
| `--keys-only` | 只显示变量名,不显示值 |
|
|
151
|
+
| `--help` | 显示帮助信息 |
|
|
152
|
+
|
|
153
|
+
## 故障排查
|
|
154
|
+
|
|
155
|
+
### 问题 1:optima-show-env 命令不存在
|
|
156
|
+
|
|
157
|
+
**解决方案**:
|
|
158
|
+
```bash
|
|
159
|
+
# 安装或更新工具
|
|
160
|
+
npm install -g @optima-chat/dev-skills@latest
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### 问题 2:无法访问 Infisical
|
|
164
|
+
|
|
165
|
+
**可能原因**:
|
|
166
|
+
- GitHub CLI 未登录
|
|
167
|
+
- 无权访问 Optima-Chat/optima-dev-skills 仓库的 Variables
|
|
168
|
+
|
|
169
|
+
**解决方案**:
|
|
170
|
+
- 运行 `gh auth login` 登录 GitHub
|
|
171
|
+
- 确认有仓库访问权限
|
|
172
|
+
|
|
173
|
+
### 问题 3:服务不存在
|
|
174
|
+
|
|
175
|
+
**错误信息**: "Unknown service 'xxx'"
|
|
176
|
+
|
|
177
|
+
**解决方案**:
|
|
178
|
+
- 检查服务名称拼写
|
|
179
|
+
- 运行 `optima-show-env --help` 查看支持的服务列表
|
|
180
|
+
|
|
181
|
+
## 相关命令
|
|
182
|
+
|
|
183
|
+
- `/show-env` - 查看环境变量
|
|
184
|
+
- `/logs` - 查看服务日志
|
|
185
|
+
- `/query-db` - 查询数据库
|
package/bin/helpers/query-db.ts
CHANGED
|
@@ -51,8 +51,8 @@ const RDS_HOSTS = {
|
|
|
51
51
|
prod: 'optima-prod-postgres.ctg866o0ehac.ap-southeast-1.rds.amazonaws.com'
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
-
// 统一使用
|
|
55
|
-
const EC2_HOST = '
|
|
54
|
+
// 统一使用 BI Data ARM Host 作为跳板机
|
|
55
|
+
const EC2_HOST = '3.0.210.113';
|
|
56
56
|
|
|
57
57
|
function getGitHubVariable(name: string): string {
|
|
58
58
|
return execSync(`gh variable get ${name} -R Optima-Chat/optima-dev-skills`, { encoding: 'utf-8' }).trim();
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
|
|
5
|
+
interface InfisicalConfig {
|
|
6
|
+
url: string;
|
|
7
|
+
clientId: string;
|
|
8
|
+
clientSecret: string;
|
|
9
|
+
projectId: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// 支持的服务列表(Infisical 路径为 /services/<service-name>)
|
|
13
|
+
const SUPPORTED_SERVICES = [
|
|
14
|
+
'commerce-backend',
|
|
15
|
+
'user-auth',
|
|
16
|
+
'agentic-chat',
|
|
17
|
+
'bi',
|
|
18
|
+
'session-gateway',
|
|
19
|
+
'optima-store',
|
|
20
|
+
'optima-scout',
|
|
21
|
+
'mcp-host'
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
// 环境到 Infisical environment 的映射
|
|
25
|
+
const ENV_MAP: Record<string, string> = {
|
|
26
|
+
stage: 'staging',
|
|
27
|
+
prod: 'prod'
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function getGitHubVariable(name: string): string {
|
|
31
|
+
return execSync(`gh variable get ${name} -R Optima-Chat/optima-dev-skills`, { encoding: 'utf-8' }).trim();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getInfisicalConfig(): InfisicalConfig {
|
|
35
|
+
return {
|
|
36
|
+
url: getGitHubVariable('INFISICAL_URL'),
|
|
37
|
+
clientId: getGitHubVariable('INFISICAL_CLIENT_ID'),
|
|
38
|
+
clientSecret: getGitHubVariable('INFISICAL_CLIENT_SECRET'),
|
|
39
|
+
projectId: getGitHubVariable('INFISICAL_PROJECT_ID')
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getInfisicalToken(config: InfisicalConfig): string {
|
|
44
|
+
const response = execSync(
|
|
45
|
+
`curl -s -X POST "${config.url}/api/v1/auth/universal-auth/login" -H "Content-Type: application/json" -d '{"clientId": "${config.clientId}", "clientSecret": "${config.clientSecret}"}'`,
|
|
46
|
+
{ encoding: 'utf-8' }
|
|
47
|
+
);
|
|
48
|
+
return JSON.parse(response).accessToken;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getInfisicalSecrets(config: InfisicalConfig, token: string, environment: string, secretPath: string): Record<string, string> {
|
|
52
|
+
const response = execSync(
|
|
53
|
+
`curl -s "${config.url}/api/v3/secrets/raw?workspaceId=${config.projectId}&environment=${environment}&secretPath=${encodeURIComponent(secretPath)}" -H "Authorization: Bearer ${token}"`,
|
|
54
|
+
{ encoding: 'utf-8' }
|
|
55
|
+
);
|
|
56
|
+
const data = JSON.parse(response);
|
|
57
|
+
const secrets: Record<string, string> = {};
|
|
58
|
+
for (const secret of data.secrets || []) {
|
|
59
|
+
secrets[secret.secretKey] = secret.secretValue;
|
|
60
|
+
}
|
|
61
|
+
return secrets;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
function printHelp() {
|
|
66
|
+
console.log(`
|
|
67
|
+
Usage: optima-show-env <service> <environment> [options]
|
|
68
|
+
|
|
69
|
+
Arguments:
|
|
70
|
+
service Service name (${SUPPORTED_SERVICES.join(', ')})
|
|
71
|
+
environment Environment (stage, prod)
|
|
72
|
+
|
|
73
|
+
Options:
|
|
74
|
+
--filter Filter by key pattern (e.g., --filter DATABASE)
|
|
75
|
+
--keys-only Show only key names, not values
|
|
76
|
+
--help Show this help message
|
|
77
|
+
|
|
78
|
+
Examples:
|
|
79
|
+
optima-show-env commerce-backend stage
|
|
80
|
+
optima-show-env user-auth prod --filter DATABASE
|
|
81
|
+
optima-show-env bi stage --keys-only
|
|
82
|
+
`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function main() {
|
|
86
|
+
const args = process.argv.slice(2);
|
|
87
|
+
|
|
88
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
89
|
+
printHelp();
|
|
90
|
+
process.exit(0);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 解析参数
|
|
94
|
+
const positionalArgs: string[] = [];
|
|
95
|
+
let filter = '';
|
|
96
|
+
let keysOnly = false;
|
|
97
|
+
|
|
98
|
+
for (let i = 0; i < args.length; i++) {
|
|
99
|
+
if (args[i] === '--filter' && args[i + 1]) {
|
|
100
|
+
filter = args[i + 1];
|
|
101
|
+
i++;
|
|
102
|
+
} else if (args[i] === '--keys-only') {
|
|
103
|
+
keysOnly = true;
|
|
104
|
+
} else if (!args[i].startsWith('-')) {
|
|
105
|
+
positionalArgs.push(args[i]);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (positionalArgs.length < 2) {
|
|
110
|
+
console.error('Error: Missing required arguments');
|
|
111
|
+
printHelp();
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const [service, environment] = positionalArgs;
|
|
116
|
+
|
|
117
|
+
// 验证服务
|
|
118
|
+
if (!SUPPORTED_SERVICES.includes(service)) {
|
|
119
|
+
console.error(`Error: Unknown service '${service}'`);
|
|
120
|
+
console.error('Available services:', SUPPORTED_SERVICES.join(', '));
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 验证环境
|
|
125
|
+
if (!ENV_MAP[environment]) {
|
|
126
|
+
console.error(`Error: Unknown environment '${environment}'`);
|
|
127
|
+
console.error('Available environments: stage, prod');
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Infisical 路径为 /services/<service-name>
|
|
132
|
+
const secretPath = `/services/${service}`;
|
|
133
|
+
|
|
134
|
+
console.log(`\n🔍 Fetching environment variables for ${service} (${environment.toUpperCase()})...\n`);
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
const infisicalConfig = getInfisicalConfig();
|
|
138
|
+
console.log('✓ Loaded Infisical config from GitHub Variables');
|
|
139
|
+
|
|
140
|
+
const token = getInfisicalToken(infisicalConfig);
|
|
141
|
+
console.log('✓ Obtained Infisical access token');
|
|
142
|
+
|
|
143
|
+
const infisicalEnv = ENV_MAP[environment];
|
|
144
|
+
const secrets = getInfisicalSecrets(infisicalConfig, token, infisicalEnv, secretPath);
|
|
145
|
+
console.log(`✓ Retrieved secrets from Infisical (path: ${secretPath})\n`);
|
|
146
|
+
|
|
147
|
+
const keys = Object.keys(secrets).sort();
|
|
148
|
+
|
|
149
|
+
if (keys.length === 0) {
|
|
150
|
+
console.log('No environment variables found.');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 过滤
|
|
155
|
+
const filteredKeys = filter
|
|
156
|
+
? keys.filter(key => key.toUpperCase().includes(filter.toUpperCase()))
|
|
157
|
+
: keys;
|
|
158
|
+
|
|
159
|
+
if (filteredKeys.length === 0) {
|
|
160
|
+
console.log(`No environment variables matching '${filter}' found.`);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.log(`📋 Environment Variables (${filteredKeys.length} items):\n`);
|
|
165
|
+
console.log('─'.repeat(60));
|
|
166
|
+
|
|
167
|
+
for (const key of filteredKeys) {
|
|
168
|
+
if (keysOnly) {
|
|
169
|
+
console.log(key);
|
|
170
|
+
} else {
|
|
171
|
+
console.log(`${key}=${secrets[key]}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
console.log('─'.repeat(60));
|
|
176
|
+
|
|
177
|
+
} catch (error: any) {
|
|
178
|
+
console.error('\n❌ Error:', error.message);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
main();
|
|
@@ -68,8 +68,8 @@ const RDS_HOSTS = {
|
|
|
68
68
|
stage: 'optima-stage-postgres.ctg866o0ehac.ap-southeast-1.rds.amazonaws.com',
|
|
69
69
|
prod: 'optima-prod-postgres.ctg866o0ehac.ap-southeast-1.rds.amazonaws.com'
|
|
70
70
|
};
|
|
71
|
-
// 统一使用
|
|
72
|
-
const EC2_HOST = '
|
|
71
|
+
// 统一使用 BI Data ARM Host 作为跳板机
|
|
72
|
+
const EC2_HOST = '3.0.210.113';
|
|
73
73
|
function getGitHubVariable(name) {
|
|
74
74
|
return (0, child_process_1.execSync)(`gh variable get ${name} -R Optima-Chat/optima-dev-skills`, { encoding: 'utf-8' }).trim();
|
|
75
75
|
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
// 支持的服务列表(Infisical 路径为 /services/<service-name>)
|
|
6
|
+
const SUPPORTED_SERVICES = [
|
|
7
|
+
'commerce-backend',
|
|
8
|
+
'user-auth',
|
|
9
|
+
'agentic-chat',
|
|
10
|
+
'bi',
|
|
11
|
+
'session-gateway',
|
|
12
|
+
'optima-store',
|
|
13
|
+
'optima-scout',
|
|
14
|
+
'mcp-host'
|
|
15
|
+
];
|
|
16
|
+
// 环境到 Infisical environment 的映射
|
|
17
|
+
const ENV_MAP = {
|
|
18
|
+
stage: 'staging',
|
|
19
|
+
prod: 'prod'
|
|
20
|
+
};
|
|
21
|
+
function getGitHubVariable(name) {
|
|
22
|
+
return (0, child_process_1.execSync)(`gh variable get ${name} -R Optima-Chat/optima-dev-skills`, { encoding: 'utf-8' }).trim();
|
|
23
|
+
}
|
|
24
|
+
function getInfisicalConfig() {
|
|
25
|
+
return {
|
|
26
|
+
url: getGitHubVariable('INFISICAL_URL'),
|
|
27
|
+
clientId: getGitHubVariable('INFISICAL_CLIENT_ID'),
|
|
28
|
+
clientSecret: getGitHubVariable('INFISICAL_CLIENT_SECRET'),
|
|
29
|
+
projectId: getGitHubVariable('INFISICAL_PROJECT_ID')
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function getInfisicalToken(config) {
|
|
33
|
+
const response = (0, child_process_1.execSync)(`curl -s -X POST "${config.url}/api/v1/auth/universal-auth/login" -H "Content-Type: application/json" -d '{"clientId": "${config.clientId}", "clientSecret": "${config.clientSecret}"}'`, { encoding: 'utf-8' });
|
|
34
|
+
return JSON.parse(response).accessToken;
|
|
35
|
+
}
|
|
36
|
+
function getInfisicalSecrets(config, token, environment, secretPath) {
|
|
37
|
+
const response = (0, child_process_1.execSync)(`curl -s "${config.url}/api/v3/secrets/raw?workspaceId=${config.projectId}&environment=${environment}&secretPath=${encodeURIComponent(secretPath)}" -H "Authorization: Bearer ${token}"`, { encoding: 'utf-8' });
|
|
38
|
+
const data = JSON.parse(response);
|
|
39
|
+
const secrets = {};
|
|
40
|
+
for (const secret of data.secrets || []) {
|
|
41
|
+
secrets[secret.secretKey] = secret.secretValue;
|
|
42
|
+
}
|
|
43
|
+
return secrets;
|
|
44
|
+
}
|
|
45
|
+
function printHelp() {
|
|
46
|
+
console.log(`
|
|
47
|
+
Usage: optima-show-env <service> <environment> [options]
|
|
48
|
+
|
|
49
|
+
Arguments:
|
|
50
|
+
service Service name (${SUPPORTED_SERVICES.join(', ')})
|
|
51
|
+
environment Environment (stage, prod)
|
|
52
|
+
|
|
53
|
+
Options:
|
|
54
|
+
--filter Filter by key pattern (e.g., --filter DATABASE)
|
|
55
|
+
--keys-only Show only key names, not values
|
|
56
|
+
--help Show this help message
|
|
57
|
+
|
|
58
|
+
Examples:
|
|
59
|
+
optima-show-env commerce-backend stage
|
|
60
|
+
optima-show-env user-auth prod --filter DATABASE
|
|
61
|
+
optima-show-env bi stage --keys-only
|
|
62
|
+
`);
|
|
63
|
+
}
|
|
64
|
+
async function main() {
|
|
65
|
+
const args = process.argv.slice(2);
|
|
66
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
67
|
+
printHelp();
|
|
68
|
+
process.exit(0);
|
|
69
|
+
}
|
|
70
|
+
// 解析参数
|
|
71
|
+
const positionalArgs = [];
|
|
72
|
+
let filter = '';
|
|
73
|
+
let keysOnly = false;
|
|
74
|
+
for (let i = 0; i < args.length; i++) {
|
|
75
|
+
if (args[i] === '--filter' && args[i + 1]) {
|
|
76
|
+
filter = args[i + 1];
|
|
77
|
+
i++;
|
|
78
|
+
}
|
|
79
|
+
else if (args[i] === '--keys-only') {
|
|
80
|
+
keysOnly = true;
|
|
81
|
+
}
|
|
82
|
+
else if (!args[i].startsWith('-')) {
|
|
83
|
+
positionalArgs.push(args[i]);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (positionalArgs.length < 2) {
|
|
87
|
+
console.error('Error: Missing required arguments');
|
|
88
|
+
printHelp();
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
const [service, environment] = positionalArgs;
|
|
92
|
+
// 验证服务
|
|
93
|
+
if (!SUPPORTED_SERVICES.includes(service)) {
|
|
94
|
+
console.error(`Error: Unknown service '${service}'`);
|
|
95
|
+
console.error('Available services:', SUPPORTED_SERVICES.join(', '));
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
// 验证环境
|
|
99
|
+
if (!ENV_MAP[environment]) {
|
|
100
|
+
console.error(`Error: Unknown environment '${environment}'`);
|
|
101
|
+
console.error('Available environments: stage, prod');
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
// Infisical 路径为 /services/<service-name>
|
|
105
|
+
const secretPath = `/services/${service}`;
|
|
106
|
+
console.log(`\n🔍 Fetching environment variables for ${service} (${environment.toUpperCase()})...\n`);
|
|
107
|
+
try {
|
|
108
|
+
const infisicalConfig = getInfisicalConfig();
|
|
109
|
+
console.log('✓ Loaded Infisical config from GitHub Variables');
|
|
110
|
+
const token = getInfisicalToken(infisicalConfig);
|
|
111
|
+
console.log('✓ Obtained Infisical access token');
|
|
112
|
+
const infisicalEnv = ENV_MAP[environment];
|
|
113
|
+
const secrets = getInfisicalSecrets(infisicalConfig, token, infisicalEnv, secretPath);
|
|
114
|
+
console.log(`✓ Retrieved secrets from Infisical (path: ${secretPath})\n`);
|
|
115
|
+
const keys = Object.keys(secrets).sort();
|
|
116
|
+
if (keys.length === 0) {
|
|
117
|
+
console.log('No environment variables found.');
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
// 过滤
|
|
121
|
+
const filteredKeys = filter
|
|
122
|
+
? keys.filter(key => key.toUpperCase().includes(filter.toUpperCase()))
|
|
123
|
+
: keys;
|
|
124
|
+
if (filteredKeys.length === 0) {
|
|
125
|
+
console.log(`No environment variables matching '${filter}' found.`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
console.log(`📋 Environment Variables (${filteredKeys.length} items):\n`);
|
|
129
|
+
console.log('─'.repeat(60));
|
|
130
|
+
for (const key of filteredKeys) {
|
|
131
|
+
if (keysOnly) {
|
|
132
|
+
console.log(key);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
console.log(`${key}=${secrets[key]}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
console.log('─'.repeat(60));
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
console.error('\n❌ Error:', error.message);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
main();
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@optima-chat/dev-skills",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.16",
|
|
4
4
|
"description": "Claude Code Skills for Optima development team - cross-environment collaboration tools",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"optima-dev-skills": "./bin/cli.js",
|
|
8
8
|
"optima-query-db": "./dist/bin/helpers/query-db.js",
|
|
9
|
-
"optima-generate-test-token": "./dist/bin/helpers/generate-test-token.js"
|
|
9
|
+
"optima-generate-test-token": "./dist/bin/helpers/generate-test-token.js",
|
|
10
|
+
"optima-show-env": "./dist/bin/helpers/show-env.js"
|
|
10
11
|
},
|
|
11
12
|
"scripts": {
|
|
12
13
|
"postinstall": "node scripts/install.js",
|