@mongoui/skills 0.1.4 → 0.1.5

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.
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env python3
2
+ """Generate or send signed requests for the Glink open-platform gateway.
3
+
4
+ Examples:
5
+ python scripts/glink_signed_request.py ^
6
+ --method POST ^
7
+ --path /openapi/v1/glink/queryBusinessList ^
8
+ --body-json "{\"language\":\"zh-CN\",\"cityCode\":\"1\"}" ^
9
+ --app-key open_app_xxx ^
10
+ --app-secret sk_xxx
11
+
12
+ python scripts/glink_signed_request.py ^
13
+ --method POST ^
14
+ --path /openapi/v1/glink/queryCreditBalance ^
15
+ --body-json "{}" ^
16
+ --app-key open_app_xxx ^
17
+ --app-secret sk_xxx ^
18
+ --send
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import argparse
24
+ import hashlib
25
+ import hmac
26
+ import json
27
+ import os
28
+ import sys
29
+ import time
30
+ import urllib.parse
31
+ import urllib.request
32
+ import uuid
33
+ from pathlib import Path
34
+
35
+
36
+ def sha256_hex(data: bytes) -> str:
37
+ return hashlib.sha256(data).hexdigest()
38
+
39
+
40
+ def parse_query_items(items: list[str]) -> list[tuple[str, str]]:
41
+ pairs: list[tuple[str, str]] = []
42
+ for item in items:
43
+ if "=" not in item:
44
+ raise ValueError(f"invalid query item: {item!r}; expected key=value")
45
+ key, value = item.split("=", 1)
46
+ pairs.append((key, value))
47
+ return pairs
48
+
49
+
50
+ def canonical_query(pairs: list[tuple[str, str]]) -> str:
51
+ ordered = sorted(pairs, key=lambda item: (item[0], item[1]))
52
+ return "&".join(
53
+ f"{urllib.parse.quote(key, safe='~-._')}={urllib.parse.quote(value, safe='~-._')}"
54
+ for key, value in ordered
55
+ )
56
+
57
+
58
+ def build_sign_payload(
59
+ method: str,
60
+ path: str,
61
+ query_string: str,
62
+ body_bytes: bytes,
63
+ timestamp: str,
64
+ nonce: str,
65
+ app_key: str,
66
+ ) -> str:
67
+ return "\n".join(
68
+ [
69
+ method.upper(),
70
+ path,
71
+ query_string,
72
+ sha256_hex(body_bytes),
73
+ timestamp,
74
+ nonce,
75
+ app_key,
76
+ ]
77
+ )
78
+
79
+
80
+ def sign(secret: str, payload: str) -> str:
81
+ return hmac.new(secret.encode("utf-8"), payload.encode("utf-8"), hashlib.sha256).hexdigest()
82
+
83
+
84
+ def load_body(args: argparse.Namespace) -> bytes:
85
+ if args.body_file:
86
+ return Path(args.body_file).read_bytes()
87
+ if args.body_json is None:
88
+ return b""
89
+ parsed = json.loads(args.body_json)
90
+ return json.dumps(parsed, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
91
+
92
+
93
+ def build_url(base_url: str, path: str, query_string: str) -> str:
94
+ base = base_url.rstrip("/")
95
+ url = f"{base}{path}"
96
+ if query_string:
97
+ url = f"{url}?{query_string}"
98
+ return url
99
+
100
+
101
+ def build_headers(app_key: str, timestamp: str, nonce: str, signature: str, has_body: bool) -> dict[str, str]:
102
+ headers = {
103
+ "Accept": "application/json",
104
+ "X-OP-App-Key": app_key,
105
+ "X-OP-Timestamp": timestamp,
106
+ "X-OP-Nonce": nonce,
107
+ "X-OP-Sign": signature,
108
+ }
109
+ if has_body:
110
+ headers["Content-Type"] = "application/json"
111
+ return headers
112
+
113
+
114
+ def send_request(method: str, url: str, headers: dict[str, str], body_bytes: bytes, timeout: float) -> tuple[int, str]:
115
+ request = urllib.request.Request(url=url, data=body_bytes or None, headers=headers, method=method.upper())
116
+ try:
117
+ with urllib.request.urlopen(request, timeout=timeout) as response:
118
+ return response.status, response.read().decode("utf-8", errors="replace")
119
+ except urllib.error.HTTPError as exc:
120
+ return exc.code, exc.read().decode("utf-8", errors="replace")
121
+
122
+
123
+ def main() -> int:
124
+ parser = argparse.ArgumentParser(description="Generate or send a signed Glink open-platform request.")
125
+ parser.add_argument("--base-url", default=os.getenv("OPEN_PLATFORM_BASE_URL", "http://127.0.0.1:8671"))
126
+ parser.add_argument("--method", required=True)
127
+ parser.add_argument("--path", required=True)
128
+ parser.add_argument("--query", action="append", default=[], help="Repeatable key=value query parameter")
129
+ parser.add_argument("--body-json", help="JSON string request body")
130
+ parser.add_argument("--body-file", help="Path to a raw request body file")
131
+ parser.add_argument("--app-key", default=os.getenv("OPEN_PLATFORM_APP_KEY") or os.getenv("OPEN_PLATFORM_OPEN_APP_ID"))
132
+ parser.add_argument("--app-secret", default=os.getenv("OPEN_PLATFORM_APP_SECRET") or os.getenv("OPEN_PLATFORM_OPEN_SECRET"))
133
+ parser.add_argument("--timestamp", default=str(int(time.time())))
134
+ parser.add_argument("--nonce", default=uuid.uuid4().hex)
135
+ parser.add_argument("--send", action="store_true")
136
+ parser.add_argument("--timeout", type=float, default=15.0)
137
+ args = parser.parse_args()
138
+
139
+ if not args.app_key or not args.app_secret:
140
+ print("app key and app secret are required", file=sys.stderr)
141
+ return 2
142
+
143
+ try:
144
+ query_pairs = parse_query_items(args.query)
145
+ body_bytes = load_body(args)
146
+ except Exception as exc:
147
+ print(str(exc), file=sys.stderr)
148
+ return 2
149
+
150
+ query_string = canonical_query(query_pairs)
151
+ payload = build_sign_payload(
152
+ method=args.method,
153
+ path=args.path,
154
+ query_string=query_string,
155
+ body_bytes=body_bytes,
156
+ timestamp=args.timestamp,
157
+ nonce=args.nonce,
158
+ app_key=args.app_key,
159
+ )
160
+ signature = sign(args.app_secret, payload)
161
+ headers = build_headers(args.app_key, args.timestamp, args.nonce, signature, bool(body_bytes))
162
+ url = build_url(args.base_url, args.path, query_string)
163
+
164
+ print("URL:")
165
+ print(url)
166
+ print()
167
+ print("Canonical String:")
168
+ print(payload)
169
+ print()
170
+ print("Headers:")
171
+ print(json.dumps(headers, ensure_ascii=False, indent=2))
172
+ if body_bytes:
173
+ print()
174
+ print("Body:")
175
+ print(body_bytes.decode("utf-8", errors="replace"))
176
+
177
+ if not args.send:
178
+ return 0
179
+
180
+ status, response_text = send_request(args.method, url, headers, body_bytes, args.timeout)
181
+ print()
182
+ print(f"HTTP {status}")
183
+ print(response_text)
184
+ return 0
185
+
186
+
187
+ if __name__ == "__main__":
188
+ raise SystemExit(main())
@@ -1,8 +0,0 @@
1
- ---
2
- name: flink-flight-api
3
- description: Placeholder skill for Flink Flight API.
4
- ---
5
-
6
- # Flink Flight API
7
-
8
- 内容待补充。
@@ -1,175 +0,0 @@
1
- ---
2
- name: glink-hotel-api
3
- description: Glink 酒店 API 接入规范。支持鉴权、酒店列表查询、参数封装、错误处理。触发词:Glink、酒店接口、酒店查询、鉴权函数、hotel list。
4
- ---
5
-
6
- # Glink Hotel API
7
-
8
- ## 目标
9
-
10
- 当用户提到 Glink 酒店相关需求时,优先帮助用户完成以下任务:
11
-
12
- - 实现 Glink API 的鉴权逻辑
13
- - 封装酒店列表查询函数
14
- - 生成 Node.js / TypeScript 调用示例
15
- - 根据接口文档补全请求头、请求体、错误处理
16
- - 在业务项目中封装可复用的 service / sdk 方法
17
-
18
- 默认优先输出 TypeScript 示例,其次是 JavaScript。
19
-
20
- ## 核心规则
21
-
22
- - 所有请求默认使用 HTTPS
23
- - 所有受保护接口必须携带 `Authorization: Bearer {access_token}`
24
- - 请求头必须包含 `Content-Type: application/json`
25
- - 日期字段使用 `YYYY-MM-DD`
26
- - 示例代码优先使用 `fetch`
27
- - 如果用户项目明显使用 `axios`,则跟随项目风格
28
- - 如果用户要求“实现函数”,默认补全类型定义、请求函数、错误处理
29
- - 如果用户没有说明运行环境,默认按 Node.js 18+ 处理
30
- - 如果用户没有说明框架,默认给出独立可复用函数,不强绑定 NestJS/Express
31
-
32
- ## 鉴权要求
33
-
34
- 当用户提到“鉴权”“token”“Bearer”“access_token”时:
35
-
36
- - 明确告诉用户需要在请求头中传递 Bearer Token
37
- - 不要把 token 写死在代码里
38
- - 优先从函数参数、环境变量或配置对象中读取 token
39
- - 生成代码时,默认将鉴权逻辑封装为独立函数或统一请求客户端
40
-
41
- 推荐模式:
42
-
43
- 1. 先定义配置项
44
- 2. 再封装请求方法
45
- 3. 再封装业务接口方法
46
-
47
- ## 常见接口示例
48
-
49
- ### 查询酒店列表
50
-
51
- ```http
52
- POST /v1/hotel/list
53
- Authorization: Bearer {access_token}
54
- Content-Type: application/json
55
-
56
- {
57
- "cityCode": "BJS",
58
- "checkIn": "2026-05-01",
59
- "checkOut": "2026-05-03",
60
- "rooms": 1,
61
- "adults": 2,
62
- "starRating": [4, 5],
63
- "sortBy": "price",
64
- "page": 1,
65
- "pageSize": 20
66
- }
67
- ```
68
-
69
- ## TypeScript 示例
70
-
71
- ```ts
72
- type HotelListRequest = {
73
- cityCode: string;
74
- checkIn: string;
75
- checkOut: string;
76
- rooms: number;
77
- adults: number;
78
- starRating?: number[];
79
- sortBy?: string;
80
- page?: number;
81
- pageSize?: number;
82
- };
83
-
84
- type GlinkClientOptions = {
85
- baseUrl: string;
86
- accessToken: string;
87
- };
88
-
89
- async function glinkRequest<T>(
90
- path: string,
91
- options: GlinkClientOptions,
92
- body: unknown,
93
- ): Promise<T> {
94
- const response = await fetch(`${options.baseUrl}${path}`, {
95
- method: "POST",
96
- headers: {
97
- Authorization: `Bearer ${options.accessToken}`,
98
- "Content-Type": "application/json",
99
- },
100
- body: JSON.stringify(body),
101
- });
102
-
103
- if (!response.ok) {
104
- const errorText = await response.text();
105
- throw new Error(`Glink request failed: ${response.status} ${errorText}`);
106
- }
107
-
108
- return response.json() as Promise<T>;
109
- }
110
-
111
- export async function getHotelList<T>(
112
- options: GlinkClientOptions,
113
- payload: HotelListRequest,
114
- ): Promise<T> {
115
- return glinkRequest<T>("/v1/hotel/list", options, payload);
116
- }
117
- ```
118
-
119
- ## 当用户这样提问时
120
-
121
- ### 用户说
122
-
123
- `帮我实现 Glink 鉴权函数`
124
-
125
- 应该优先输出:
126
-
127
- - 一个可复用的鉴权请求封装
128
- - 如何传入 `access_token`
129
- - 如何设置 `Authorization: Bearer ...`
130
- - 一个最小可运行示例
131
-
132
- ### 用户说
133
-
134
- `帮我调用 Glink 酒店列表接口`
135
-
136
- 应该优先输出:
137
-
138
- - 请求函数
139
- - 请求参数类型
140
- - 调用示例
141
- - 错误处理
142
- - 必要请求头
143
-
144
- ### 用户说
145
-
146
- `帮我封装成 service`
147
-
148
- 应该优先输出:
149
-
150
- - 一个类或模块化 service
151
- - 构造函数接收 `baseUrl` 和 `accessToken`
152
- - 业务方法如 `getHotelList`
153
-
154
- ## 输出要求
155
-
156
- - 优先输出完整可运行代码,而不是只解释思路
157
- - 如果用户没有要求框架,优先输出纯 TypeScript 函数
158
- - 如果接口字段不完整,明确标注“以下字段基于当前 Skills 示例推断”
159
- - 如果缺少响应结构,不要编造复杂返回值类型,可先使用泛型或基础类型
160
- - 如果用户要求接入现有项目,尽量贴近项目已有代码风格
161
-
162
- ## 常见注意事项
163
-
164
- - 不要把 `Authorization` 写成普通自定义字段
165
- - 不要漏掉 `Content-Type: application/json`
166
- - 不要把日期写成时间戳,除非用户明确要求
167
- - 不要默认把所有字段都设成可选
168
- - 不要把分页参数写死
169
- - 如果用户要生产代码,提醒 token 不应硬编码
170
-
171
- ## 回答风格
172
-
173
- - 直接给代码
174
- - 再简要说明关键点
175
- - 优先面向接入开发,不做空泛概念解释