sophhub 0.4.10 → 0.4.12

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.
@@ -5,6 +5,14 @@
5
5
  "bot_api_enabled": false,
6
6
  "workspace": "/home/node/.openclaw/workspace-intern",
7
7
  "agent_dependencies": [],
8
+ "post_install": [
9
+ {
10
+ "name": "init-workspace",
11
+ "script": "scripts/init_workspace.sh",
12
+ "args": [],
13
+ "delete_on_success": true
14
+ }
15
+ ],
8
16
  "tools": {
9
17
  "deny": [
10
18
  "message",
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env bash
2
+ # intern-admin workspace:创建 knowledge/ 树与 memory/;INDEX.md、FAQ.md 由 Agent 按需创建。
3
+ # 须先于 intern-qa 完成,以便其 setup_links.sh 将 knowledge/ 链到本工作区上级同名目录。
4
+ #
5
+ # 用法:安装器 post_install 调用;或:
6
+ # WORKSPACE=/path/to/workspace-intern ./scripts/init_workspace.sh
7
+ # OPENCLAW_WORKSPACE=... ./scripts/init_workspace.sh
8
+ # ./scripts/init_workspace.sh /path/to/workspace-intern
9
+
10
+ set -euo pipefail
11
+
12
+ WS="${WORKSPACE:-${OPENCLAW_WORKSPACE:-${1:-}}}"
13
+ if [[ -z "$WS" ]]; then
14
+ echo "init_workspace: set WORKSPACE or OPENCLAW_WORKSPACE, or pass workspace root as first arg." >&2
15
+ exit 1
16
+ fi
17
+
18
+ WS="$(cd "$WS" && pwd)"
19
+
20
+ # 一次 mkdir -p 建齐 BOOTSTRAP 约定路径(父目录会自动创建,不必逐条拆开写)
21
+ mkdir -p \
22
+ "$WS/knowledge/images/"{media,emf_raw} \
23
+ "$WS/knowledge/attachments" \
24
+ "$WS/knowledge/archive" \
25
+ "$WS/memory"
26
+
27
+ echo "init_workspace: knowledge/ + memory/ ready under ${WS}"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sophhub",
3
- "version": "0.4.10",
3
+ "version": "0.4.12",
4
4
  "description": "SophHub CLI - Manage and download AI Agent skills",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,10 +1,21 @@
1
1
  {
2
2
  "name": "claw-agent-get-send",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "types": ["store"],
5
5
  "displayName": "Claw Agent Get/Send",
6
- "description": "Appia claw.agent.groups.get / claw.agent.message.send:脚本封装 + HTTP/curl 参考(JWT、msg/md 负载、错误码)",
6
+ "description": "Appia(IM 即时通讯)侧 claw.agent.groups.get / claw.agent.message.send:查询机器人在哪些群聊、向群发纯文本或 Markdown;脚本封装 + HTTP/curl 参考(JWT、msg/md、错误码)",
7
7
  "changelog": [
8
+ {
9
+ "version": "1.0.1",
10
+ "date": "2026-05-09",
11
+ "changes": [
12
+ "appia_claw.py:--cred-file / -c,JSON 凭证优先于环境变量;groups.get / message.send 必填 userId;默认 Base URL",
13
+ "reference-http.md 迁至技能根目录;仓库 .gitignore 忽略 **/.secrets/",
14
+ "SKILL.md:bot-secret 式分步用法;description 标明 IM 群聊场景与触发词",
15
+ "docs/claw-agent-get-send.md:凭证说明、常用示例、与 skill 一致的 IM 表述",
16
+ "skill.json store description 与 IM 语境对齐"
17
+ ]
18
+ },
8
19
  {
9
20
  "version": "1.0.0",
10
21
  "date": "2026-04-28",
@@ -17,5 +28,5 @@
17
28
  }
18
29
  ],
19
30
  "createdAt": "2026-04-28",
20
- "updatedAt": "2026-04-28"
31
+ "updatedAt": "2026-05-09"
21
32
  }
@@ -1,72 +1,43 @@
1
1
  ---
2
2
  name: claw-agent-get-send
3
- description: >-
4
- Call Appia Claw HTTP APIs (agent.groups.get, agent.message.send) using env vars or curl.
5
- Use when querying bot group memberships, verifying rid+group name, sending plaintext or Markdown AST to Appia rooms,
6
- or when the user needs JWT auth rules, curl examples, or md payload shape for agent.message.send.
3
+ description: On Appia (an IM / team chat platform), list which group chats the bot agent is in (rid + name), or send plaintext or Markdown to a group via Appia Claw. Use when the user asks for group list / 群列表 / Appia 群聊 / rid, or to message a group / 向群发消息 / IM 通知;claw、agent.groups.get、agent.message.send。
7
4
  ---
8
5
 
9
- # Claw Agent(groups.get / message.send)
6
+ # Appia 即时通讯(IM)· Claw 群列表与发消息
10
7
 
11
- 使用 **环境变量** 鉴权,**不向仓库提交** JWT、`APP_AGENT_ID` 等明文。
12
-
13
- ## 环境变量
14
-
15
- | 变量 | 必填 | 说明 |
16
- |------|------|------|
17
- | `APPIA_BASE_URL` | 是 | 根地址,例如 `https://sophgo.appia.cn` |
18
- | `CLAW_JWT` | 是 | `Authorization: Bearer` 的值 |
19
- | `APP_AGENT_ID` | 是 | SophClaw 机器人 Agent ID(与 `AGENT_ID` 二选一) |
20
- | `TARGET_RID` | send / verify | 目标群 `rid` |
21
- | `TARGET_GROUP_NAME` | verify | 群展示名,`verify-target` 双因子校验用 |
22
- | `TARGET_GROUP_MATCH` | 否 | `exact`(默认)或 `contains` |
23
-
24
- | 可选 | 说明 |
25
- |------|------|
26
- | `APPIA_MSG_CHUNK_SIZE` | 单条 `msg` 最大字符数,超出则拆分多次发送(默认 3500) |
8
+ Appia IM(即时通讯)工具中的群聊/会话场景。脚本 `{baseDir}/scripts/appia_claw.py` 通过 Claw HTTP API 拉取机器人所在群并发消息。`{baseDir}` 与本 `SKILL.md` 同级(安装根下一般有 `scripts/`)。
27
9
 
28
10
  ## 用法
29
11
 
30
- `{baseDir}` 为与本 `SKILL.md` 同级的安装根(通常含 `scripts/`)。
31
-
32
- ### 查询群列表
33
-
34
12
  ```bash
35
- uv run {baseDir}/scripts/appia_claw.py groups --json
36
- ```
13
+ # 子命令前可加:--cred-file /path/to.cred.json(或 -c)、--timeout 秒;凭证与路径示例见 docs/claw-agent-get-send.md
37
14
 
38
- ### 校验目标群
15
+ # 1. 列出当前 agent 已加入的群(rid、群名)
16
+ uv run {baseDir}/scripts/appia_claw.py --cred-file /path/to.cred.json groups
39
17
 
40
- ```bash
41
- uv run {baseDir}/scripts/appia_claw.py verify-target
42
- ```
43
-
44
- ### 发送纯文本(支持分段)
18
+ # 2. 同上,输出接口原始 JSON(便于复制 rid)
19
+ uv run {baseDir}/scripts/appia_claw.py --cred-file /path/to.cred.json groups --json
45
20
 
46
- ```bash
47
- uv run {baseDir}/scripts/appia_claw.py send --file /tmp/brief.txt
48
- uv run {baseDir}/scripts/appia_claw.py send --text "hello" --rid <rid>
49
- ```
21
+ # 3. 双因子校验:凭证或环境里已配置 TARGET_RID、TARGET_GROUP_NAME
22
+ uv run {baseDir}/scripts/appia_claw.py --cred-file /path/to.cred.json verify-target
50
23
 
51
- ### 发送 Markdown AST
24
+ # 4. 向指定 rid 发纯文本(--rid 可换成本地凭证里的 target_rid)
25
+ uv run {baseDir}/scripts/appia_claw.py --cred-file /path/to.cred.json send --text "正文" --rid "<rid>"
52
26
 
53
- ```bash
54
- # JSON 字符串
55
- uv run {baseDir}/scripts/appia_claw.py send-md --md-json '[{"type":"PARAGRAPH","value":[{"type":"PLAIN_TEXT","value":"**粗体** 文本"}]}]'
27
+ # 5. 长文本从 UTF-8 文件发送
28
+ uv run {baseDir}/scripts/appia_claw.py --cred-file /path/to.cred.json send --file /tmp/body.txt --rid "<rid>"
56
29
 
57
- # JSON 文件
58
- uv run {baseDir}/scripts/appia_claw.py send-md --md-file /tmp/md.json
30
+ # 6. Markdown AST:一段纯文本自动包成 md
31
+ uv run {baseDir}/scripts/appia_claw.py --cred-file /path/to.cred.json send-md --text "正文" --rid "<rid>"
59
32
 
60
- # 纯文本自动转 md 结构
61
- uv run {baseDir}/scripts/appia_claw.py send-md --text "简单文本"
33
+ # 7. 从 JSON 文件读 md 数组
34
+ uv run {baseDir}/scripts/appia_claw.py --cred-file /path/to.cred.json send-md --md-file /tmp/md.json --rid "<rid>"
62
35
  ```
63
36
 
64
- ## 说明
65
-
66
- - `send` 命令使用 **`msg` 纯文本**;极长正文按 `APPIA_MSG_CHUNK_SIZE` **分多条**发送。
67
- - `send-md` 命令使用 **`md` Markdown AST** 结构,支持 Rocket.Chat 富文本格式。
68
- - 请避免与业务侧「同 Sender 并发冲突」同时使用(串行调用本脚本即可)。
37
+ 不用凭证文件时,可在环境中设置 `CLAW_JWT`、`APP_AGENT_ID`、`CLAW_USER_ID`(及场景需要的 `TARGET_RID` 等),命令相同,省略 `--cred-file …` 即可。
69
38
 
70
- ## HTTP / curl 参考
39
+ ## 注意事项
71
40
 
72
- 仓库根目录的 `claw.agent.groups.get.curl.md` `claw.agent.message.send.curl.md` 已并入本 skill 的 [reference-http.md](reference-http.md)(鉴权说明、完整 curl、`md` 示例、常见错误码)。需要手写请求或排查 401 / 参数错误时优先读该文件。
41
+ - 最小凭证字段(JSON 或环境变量):JWT、机器人 `agentId`、创建者 `userId`;勿将填好真实值的文件提交 Git。
42
+ - 未设置 `APPIA_BASE_URL` 时,脚本默认 `https://sophgo.appia.cn`。
43
+ - HTTP / curl / 错误码:技能根目录 **`../reference-http.md`**(相对本文件)。
@@ -1,5 +1,5 @@
1
1
  [project]
2
2
  name = "claw-agent-get-send"
3
- version = "1.0.0"
3
+ version = "1.0.1"
4
4
  description = "Appia Claw groups.get / message.send helper"
5
5
  requires-python = ">=3.10"
@@ -11,6 +11,56 @@ from typing import Any
11
11
  from urllib import error, parse, request
12
12
 
13
13
  DEFAULT_CHUNK_SIZE = 3500
14
+ # 未配置 APPIA_BASE_URL / APPIA_HOST(凭证与环境均无)时使用。
15
+ DEFAULT_APPIA_BASE_URL = "https://sophgo.appia.cn"
16
+
17
+ # 凭证文件解析后的规范字段(键)→ 值;未使用凭证文件时为 None。
18
+ _CRED: dict[str, str] | None = None
19
+
20
+ # (规范 env 名, JSON / 文件中允许的别名)。文件中同一逻辑字段任一别名非空即可写入规范键。
21
+ _CRED_CANON_ALIASES: tuple[tuple[str, tuple[str, ...]], ...] = (
22
+ ("APPIA_BASE_URL", ("APPIA_BASE_URL", "appia_base_url", "base_url", "APPIA_HOST", "appia_host")),
23
+ ("CLAW_JWT", ("CLAW_JWT", "claw_jwt", "jwt", "APPIA_CLAW_JWT", "appia_claw_jwt")),
24
+ ("APP_AGENT_ID", ("APP_AGENT_ID", "app_agent_id", "agent_id", "AGENT_ID")),
25
+ ("CLAW_USER_ID", ("CLAW_USER_ID", "claw_user_id", "user_id", "USER_ID", "creator_user_id", "APPIA_USER_ID", "appia_user_id")),
26
+ ("TARGET_RID", ("TARGET_RID", "target_rid")),
27
+ ("TARGET_GROUP_NAME", ("TARGET_GROUP_NAME", "target_group_name")),
28
+ ("TARGET_GROUP_MATCH", ("TARGET_GROUP_MATCH", "target_group_match")),
29
+ ("APPIA_MSG_CHUNK_SIZE", ("APPIA_MSG_CHUNK_SIZE", "appia_msg_chunk_size")),
30
+ )
31
+
32
+
33
+ def _scalar_from_raw(raw: dict[str, Any], aliases: tuple[str, ...]) -> str:
34
+ for key in aliases:
35
+ if key not in raw:
36
+ continue
37
+ val = raw[key]
38
+ if val is None:
39
+ continue
40
+ s = str(val).strip()
41
+ if s:
42
+ return s
43
+ return ""
44
+
45
+
46
+ def load_cred_file(path: str) -> dict[str, str]:
47
+ exp = os.path.expanduser(path)
48
+ try:
49
+ with open(exp, encoding="utf-8") as f:
50
+ raw_any: Any = json.load(f)
51
+ except OSError as exc:
52
+ sys.exit(f"无法读取凭证文件 {exp}: {exc}")
53
+ except json.JSONDecodeError as exc:
54
+ sys.exit(f"凭证文件 JSON 无效 {exp}: {exc}")
55
+ if not isinstance(raw_any, dict):
56
+ sys.exit("凭证文件顶层必须是 JSON 对象")
57
+ raw = raw_any
58
+ out: dict[str, str] = {}
59
+ for canon, aliases in _CRED_CANON_ALIASES:
60
+ v = _scalar_from_raw(raw, aliases)
61
+ if v:
62
+ out[canon] = v
63
+ return out
14
64
 
15
65
 
16
66
  def _env(name: str, *fallbacks: str) -> str:
@@ -21,31 +71,51 @@ def _env(name: str, *fallbacks: str) -> str:
21
71
  return ""
22
72
 
23
73
 
74
+ def _cfg(canon: str, *env_fallbacks: str) -> str:
75
+ """凭证文件中非空字段优先,否则回退到环境变量(与原先 _env 链一致)。"""
76
+ if _CRED:
77
+ v = _CRED.get(canon, "").strip()
78
+ if v:
79
+ return v
80
+ return _env(canon, *env_fallbacks)
81
+
82
+
24
83
  def _base_url() -> str:
25
- base = _env("APPIA_BASE_URL", "APPIA_HOST").rstrip("/")
84
+ base = _cfg("APPIA_BASE_URL", "APPIA_HOST").rstrip("/")
26
85
  if not base:
27
- sys.exit("缺少环境变量 APPIA_BASE_URL")
86
+ base = DEFAULT_APPIA_BASE_URL.rstrip("/")
28
87
  return base
29
88
 
30
89
 
31
90
  def _jwt() -> str:
32
- tok = _env("CLAW_JWT", "APPIA_CLAW_JWT")
91
+ tok = _cfg("CLAW_JWT", "APPIA_CLAW_JWT")
33
92
  if not tok:
34
- sys.exit("缺少环境变量 CLAW_JWT")
93
+ sys.exit("缺少配置:凭证文件或环境变量 CLAW_JWT(或 APPIA_CLAW_JWT)")
35
94
  return tok
36
95
 
37
96
 
38
97
  def _agent_id() -> str:
39
- aid = _env("APP_AGENT_ID", "AGENT_ID")
98
+ aid = _cfg("APP_AGENT_ID", "AGENT_ID")
40
99
  if not aid:
41
- sys.exit("缺少环境变量 APP_AGENT_ID(或 AGENT_ID)")
100
+ sys.exit("缺少配置:凭证文件或环境变量 APP_AGENT_ID(或 AGENT_ID)")
42
101
  return aid
43
102
 
44
103
 
104
+ def _user_id() -> str:
105
+ """创建者 userId:agent.groups.get(query)与 agent.message.send(body)必填。"""
106
+ uid = _cfg("CLAW_USER_ID", "USER_ID", "APPIA_USER_ID")
107
+ if not uid:
108
+ sys.exit(
109
+ "缺少配置:凭证文件或环境变量 CLAW_USER_ID(或 USER_ID、APPIA_USER_ID、creator_user_id)"
110
+ )
111
+ return uid
112
+
113
+
45
114
  def _groups_get(timeout: float) -> dict[str, Any]:
46
115
  base = _base_url()
47
- uid = _agent_id()
48
- q = parse.urlencode({"agentId": uid})
116
+ aid = _agent_id()
117
+ user_id = _user_id()
118
+ q = parse.urlencode({"agentId": aid, "userId": user_id})
49
119
  url = f"{base}/api/v1/claw/agent.groups.get?{q}"
50
120
  req = request.Request(
51
121
  url,
@@ -82,12 +152,15 @@ def cmd_groups(args: argparse.Namespace) -> int:
82
152
 
83
153
 
84
154
  def cmd_verify_target(args: argparse.Namespace) -> int:
85
- rid = _env("TARGET_RID")
86
- name = _env("TARGET_GROUP_NAME")
155
+ rid = _cfg("TARGET_RID")
156
+ name = _cfg("TARGET_GROUP_NAME")
87
157
  if not rid or not name:
88
- print("⚠️ verify-target 需要环境变量 TARGET_RID 与 TARGET_GROUP_NAME", file=sys.stderr)
158
+ print(
159
+ "⚠️ verify-target 需要 TARGET_RID 与 TARGET_GROUP_NAME(凭证文件或环境变量)",
160
+ file=sys.stderr,
161
+ )
89
162
  return 1
90
- mode = (_env("TARGET_GROUP_MATCH") or "exact").lower()
163
+ mode = (_cfg("TARGET_GROUP_MATCH") or "exact").lower()
91
164
  if mode not in ("exact", "contains"):
92
165
  mode = "exact"
93
166
  data = _groups_get(args.timeout)
@@ -146,15 +219,25 @@ def _send_request(body_obj: dict[str, Any], timeout: float) -> dict[str, Any]:
146
219
 
147
220
  def _send_plain(rid: str, msg: str, timeout: float) -> dict[str, Any]:
148
221
  """发送纯文本消息。"""
149
- uid = _agent_id()
150
- body_obj: dict[str, Any] = {"rid": rid, "agentId": uid, "msg": msg}
222
+ aid = _agent_id()
223
+ body_obj: dict[str, Any] = {
224
+ "rid": rid,
225
+ "agentId": aid,
226
+ "userId": _user_id(),
227
+ "msg": msg,
228
+ }
151
229
  return _send_request(body_obj, timeout)
152
230
 
153
231
 
154
232
  def _send_md(rid: str, md: list[dict[str, Any]], timeout: float) -> dict[str, Any]:
155
233
  """发送 Markdown AST 消息。"""
156
- uid = _agent_id()
157
- body_obj: dict[str, Any] = {"rid": rid, "agentId": uid, "md": md}
234
+ aid = _agent_id()
235
+ body_obj: dict[str, Any] = {
236
+ "rid": rid,
237
+ "agentId": aid,
238
+ "userId": _user_id(),
239
+ "md": md,
240
+ }
158
241
  return _send_request(body_obj, timeout)
159
242
 
160
243
 
@@ -168,7 +251,7 @@ def _chunk_text(text: str, size: int) -> list[str]:
168
251
 
169
252
  def cmd_send(args: argparse.Namespace) -> int:
170
253
  """发送纯文本消息(支持分段)。"""
171
- rid = args.rid or _env("TARGET_RID")
254
+ rid = args.rid or _cfg("TARGET_RID")
172
255
  if not rid:
173
256
  print("⚠️ send 需要 TARGET_RID 或 --rid", file=sys.stderr)
174
257
  return 1
@@ -182,7 +265,7 @@ def cmd_send(args: argparse.Namespace) -> int:
182
265
  else:
183
266
  print("⚠️ 需要 --file 或 --text", file=sys.stderr)
184
267
  return 1
185
- chunk_raw = _env("APPIA_MSG_CHUNK_SIZE")
268
+ chunk_raw = _cfg("APPIA_MSG_CHUNK_SIZE")
186
269
  chunk_size = int(chunk_raw) if chunk_raw else DEFAULT_CHUNK_SIZE
187
270
  parts = _chunk_text(text, chunk_size)
188
271
  for idx, part in enumerate(parts):
@@ -198,7 +281,7 @@ def cmd_send(args: argparse.Namespace) -> int:
198
281
 
199
282
  def cmd_send_md(args: argparse.Namespace) -> int:
200
283
  """发送 Markdown AST 消息。"""
201
- rid = args.rid or _env("TARGET_RID")
284
+ rid = args.rid or _cfg("TARGET_RID")
202
285
  if not rid:
203
286
  print("⚠️ send-md 需要 TARGET_RID 或 --rid", file=sys.stderr)
204
287
  return 1
@@ -246,8 +329,15 @@ def cmd_send_md(args: argparse.Namespace) -> int:
246
329
 
247
330
 
248
331
  def main() -> int:
332
+ global _CRED
249
333
  p = argparse.ArgumentParser(description="Appia Claw (groups.get / message.send)")
250
334
  p.add_argument("--timeout", type=float, default=60.0)
335
+ p.add_argument(
336
+ "--cred-file",
337
+ "-c",
338
+ metavar="PATH",
339
+ help="JSON 凭证文件(含 Appia 地址、JWT、agentId 等);文件中非空字段优先于环境变量",
340
+ )
251
341
  sub = p.add_subparsers(dest="cmd", required=True)
252
342
 
253
343
  gp = sub.add_parser("groups", help="GET agent.groups.get")
@@ -273,6 +363,8 @@ def main() -> int:
273
363
  smp.set_defaults(_run=cmd_send_md)
274
364
 
275
365
  args = p.parse_args()
366
+ if args.cred_file:
367
+ _CRED = load_cred_file(args.cred_file)
276
368
  runner = getattr(args, "_run", None)
277
369
  if runner is None:
278
370
  return 1
@@ -1,12 +1,21 @@
1
1
  {
2
2
  "name": "flight-booking",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "types": [
5
5
  "builtin"
6
6
  ],
7
7
  "displayName": "国内机票预定",
8
- "description": "",
8
+ "description": "国内机票查询、下单、出票、退票、改签全流程 skill",
9
9
  "changelog": [
10
+ {
11
+ "changes": [
12
+ "search 取消前 15 条截断,返回当日全部航班并在表格末尾新增汇总行",
13
+ "list_flights_simple 默认 top_n 由 15 改为 None(返回全部)",
14
+ "SKILL.md 强化指引:严禁截断或只展示前 N 条航班,须逐条全部列出"
15
+ ],
16
+ "date": "2026-05-09",
17
+ "version": "1.2.0"
18
+ },
10
19
  {
11
20
  "changes": [
12
21
  "修正票务网关基址为 https://www.sophnet.com/api,避免请求落到 /v1/api 导致 405 或非 JSON 响应"
@@ -23,5 +32,5 @@
23
32
  }
24
33
  ],
25
34
  "createdAt": "2026-04-09",
26
- "updatedAt": "2026-04-16"
35
+ "updatedAt": "2026-05-09"
27
36
  }
@@ -16,13 +16,13 @@ description: "中国国内机票查询与预订。当用户需要查航班、买
16
16
  - **出票前务必再次确认**:**不得在创建订单后自动连续执行出票**。第二步创建订单完成后,应先向用户展示订单号、航程与金额,**在用户明确表示「确认出票」或「去支付/出票」后再执行第三步 pay-issue**;未得到用户确认前,不要主动执行出票。
17
17
  - **退票前务必二次确认**:**绝对不得在用户未明确确认的情况下执行退票。** 退票流程必须分两步:第一步用 `--dry-run` 查询并向用户展示退改签规则和手续费,第二步**必须等用户明确说「确认退票」「退吧」等确认性语句后**才执行实际退票命令。即使用户说了「我想退票」「帮我看看退票」,也只能先执行 dry-run 展示规则,**不得直接执行退票**。
18
18
  - **用户必填变量**:仅需通过**环境变量**配置乘机人 `name`、`mobile`、`credentialNo`、`gender`。联系人默认与乘机人一致,无需重复设置。
19
- - **航班查询结果展示须完整**:向用户展示航班列表时,必须展示脚本返回的**全部条数**(最多 15 条),且每条须包含航班号、航空公司、出发/到达机场、**起飞时间**、**到达时间**(两列分开)、**经济舱价格与公务舱价格**、**机建费**、**燃油费**,不得少条数或漏列(无公务舱时显示「—」)。
19
+ - **航班查询结果展示须完整**:向用户展示航班列表时,**必须展示脚本返回的全部航班,不得截断、不得只展示前 N 条、不得"为节省篇幅"省略任何一条**。脚本已不再做条数限制,返回多少条就展示多少条(即使有几十条也必须全部列出)。每条须包含航班号、航空公司、出发/到达机场、**起飞时间**、**到达时间**(两列分开)、**经济舱价格与公务舱价格**、**机建费**、**燃油费**,不得漏列(无公务舱时显示「—」)。
20
20
 
21
21
  ## 流程概览
22
22
 
23
23
  | 步骤 | 说明 | 脚本子命令 |
24
24
  |------|------|------------|
25
- | 第一步 | 航班查询,按经济舱价格排序并展示前 15 条(每航班一行,含经济舱/公务舱最低价) | `search` |
25
+ | 第一步 | 航班查询,按经济舱价格排序并**展示全部**航班(每航班一行,含经济舱/公务舱最低价;不截断) | `search` |
26
26
  | 第二步 | 创建订单:可选舱位报价查询 → 验舱验价 → 创建订单(常规) | `create-order` |
27
27
  | 第三步 | 支付前校验 + 申请出票 | `pay-issue` |
28
28
  | 附加 | 查询订单状态/详情 | `order-status` |
@@ -48,13 +48,13 @@ python3 {baseDir}/scripts/flight_booking.py search --from-city 杭州 --to-city
48
48
  ```
49
49
 
50
50
  - 支持的城市名示例:杭州、北京、上海、广州、深圳、成都、西安、重庆、南京、武汉、青岛、厦门、昆明、海口、三亚等;也可直接填三字码(如 HGH、BJS、PVG)。若用户只说「杭州到北京」,可直接用城市名执行。
51
- - **输出**:表格列为「航班号、航空公司、出发机场(含航站楼)、到达机场(含航站楼)、起飞时间、到达时间、经济舱、公务舱、机建费、燃油费」;起飞时间与到达时间为**两列**分别展示,按经济舱价格排序的前 15 条。航站楼信息拼在机场名后(如"萧山机场T3")。机建费和燃油费为每张票的附加税费。
51
+ - **输出**:表格列为「航班号、航空公司、出发机场(含航站楼)、到达机场(含航站楼)、起飞时间、到达时间、经济舱、公务舱、机建费、燃油费」;起飞时间与到达时间为**两列**分别展示,按经济舱价格排序,**返回当日全部航班,不做条数截断**。航站楼信息拼在机场名后(如"萧山机场T3")。机建费和燃油费为每张票的附加税费。
52
52
  - **状态保存**:查询成功后会将本次结果写入 `~/.openclaw/flight-booking/.last_search.json`,供下一步创建订单时使用;用户只需选择航班号即可下单,无需再次输入出发/到达/日期。
53
53
  - 将表格展示给用户后,由用户选择要预订的**航班号**(及可选舱位:经济舱 Y / 公务舱 C,默认 Y)。
54
54
 
55
55
  **航班查询结果向用户展示时,请务必做到信息完整、条数完整:**
56
56
 
57
- - **条数**:脚本返回多少条就展示多少条,不要自行删减或只展示部分条数。
57
+ - **条数(重点强调)**:脚本返回多少条就展示多少条,**严禁只展示前 15 条 / 前 N 条 / 部分条数**,也不得以"航班较多,仅展示部分"等理由省略。即使返回 30+ 条也必须**逐条全部列出**,由用户自行选择。
58
58
  - **每条航班必须包含的字段**:航班号、航空公司、出发机场(含航站楼)、到达机场(含航站楼)、**起飞时间**、**到达时间**、**经济舱价格**、**公务舱价格**、**机建费**、**燃油费**。起飞时间与到达时间为**两列**,缺一不可;若某航班无公务舱则显示「—」或「无」,不得省略该列。
59
59
  - **时间**:脚本输出为「起飞时间」「到达时间」两列,向用户展示时也须保留这两列,**不得用 ? 或空白代替到达时间**;若数据中有 `toTime` 必须原样展示。
60
60
  - **价格**:经济舱、公务舱两列都要展示;无该舱位时用「—」表示,不得整列不显示。
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- 机票查询与下单脚本:支持航班查询(前15条)、创建订单、支付校验与出票、订单状态查询、取消订单、退票。
3
+ 机票查询与下单脚本:支持航班查询(返回全部航班,不截断)、创建订单、支付校验与出票、订单状态查询、取消订单、退票。
4
4
  乘机人信息从环境变量读取,用户需提前配置。
5
5
  """
6
6
 
@@ -155,8 +155,9 @@ def _extract_hhmm(time_str: str) -> str:
155
155
  return s[:5] if len(s) >= 5 else s
156
156
 
157
157
 
158
- def list_flights_simple(result: dict, top_n: int = 15) -> list[dict]:
159
- """提取航班列表,每航班一行含经济舱/公务舱最低价、机建燃油费,按经济舱价格排序,取前 top_n 条。"""
158
+ def list_flights_simple(result: dict, top_n: int | None = None) -> list[dict]:
159
+ """提取航班列表,每航班一行含经济舱/公务舱最低价、机建燃油费,按经济舱价格排序。
160
+ top_n=None 时返回全部航班(默认),不做条数截断。"""
160
161
  if result.get("code") != "0":
161
162
  return []
162
163
  data = result.get("data", {})
@@ -192,6 +193,8 @@ def list_flights_simple(result: dict, top_n: int = 15) -> list[dict]:
192
193
  "fuelTax": f.get("fuelTax", 0),
193
194
  })
194
195
  out.sort(key=lambda r: (r.get("economyPrice") is None, r.get("economyPrice") or 0))
196
+ if top_n is None:
197
+ return out
195
198
  return out[:top_n]
196
199
 
197
200
 
@@ -726,9 +729,10 @@ def cmd_search(args: argparse.Namespace) -> int:
726
729
  json.dump(state, f, ensure_ascii=False, indent=2)
727
730
  except OSError:
728
731
  pass # 忽略写入失败,仅影响后续 create-order 需传全参
729
- rows = list_flights_simple(res, top_n=30)
732
+ rows = list_flights_simple(res, top_n=None)
730
733
  _print_flights_table(rows)
731
734
  print()
735
+ print(f"共 {len(rows)} 条航班(已按经济舱价格升序展示全部,未截断)。")
732
736
  print("已保存最近一次查询结果。下单请使用: create-order --flight-no <航班号> [--cabin-grade Y|C]")
733
737
  return 0
734
738
 
@@ -1166,7 +1170,7 @@ def main() -> int:
1166
1170
  sub = parser.add_subparsers(dest="command", required=True)
1167
1171
 
1168
1172
  # search:支持三字码或城市名(如 杭州、北京)
1169
- p_search = sub.add_parser("search", help="航班查询,返回前15条(按经济舱价格排序)")
1173
+ p_search = sub.add_parser("search", help="航班查询,返回当日全部航班(按经济舱价格排序,不截断)")
1170
1174
  p_search.add_argument("--from-city", required=True, help="出发城市:三字码(如 HGH)或城市名(如 杭州)")
1171
1175
  p_search.add_argument("--to-city", required=True, help="到达城市:三字码(如 BJS)或城市名(如 北京)")
1172
1176
  p_search.add_argument("--from-date", required=True, help="出发日期 YYYY-MM-DD")
@@ -1,149 +0,0 @@
1
- # Claw HTTP API(curl 参考)
2
-
3
- 与仓库根目录 `claw.agent.groups.get.curl.md`、`claw.agent.message.send.curl.md` 同源;集成在 skill 内便于离线查阅。
4
-
5
- ---
6
-
7
- ## `GET /api/v1/claw/agent.groups.get`
8
-
9
- 用于按 `agentId` 查询该机器人所在的所有群聊,返回 `rid` 和群名称。
10
-
11
- ### 鉴权方式
12
-
13
- 与 `mcpToDos` 一致:
14
-
15
- - `authRequired: false`
16
- - 通过请求头 `Authorization: Bearer <JWT>` 校验
17
- - 校验规则受以下设置控制:
18
- - `Appia_Antagent_JWT_Enable`
19
- - `APPIA_JWT_SECRET`
20
-
21
- ### 请求示例
22
-
23
- ```bash
24
- curl -sS -G 'https://YOUR_ROCKETCHAT_HOST/api/v1/claw/agent.groups.get' \
25
- -H 'Authorization: Bearer YOUR_MCP_JWT_TOKEN' \
26
- --data-urlencode 'agentId=AGENT_BOT_USER_ID'
27
- ```
28
-
29
- ### 参数
30
-
31
- - `agentId`(query,必填):OpenClaw 侧 Agent ID(会映射到机器人用户)
32
-
33
- ### 成功返回示例
34
-
35
- ```json
36
- {
37
- "success": true,
38
- "data": {
39
- "agentId": "AGENT_BOT_USER_ID",
40
- "groups": [
41
- {
42
- "rid": "ROOM_ID_1",
43
- "name": "群聊A"
44
- },
45
- {
46
- "rid": "ROOM_ID_2",
47
- "name": "群聊B"
48
- }
49
- ],
50
- "total": 2
51
- }
52
- }
53
- ```
54
-
55
- ### 常见失败
56
-
57
- - `{"success":false,"message":"401"}`:JWT 无效或过期
58
- - `{"success":false,"message":"agentId is required"}`:缺少 `agentId`
59
- - `{"success":false,"message":"agent not found by agentId"}`:找不到对应 agentId 的机器人
60
-
61
- ---
62
-
63
- ## `POST /api/v1/claw/agent.message.send`
64
-
65
- 用于根据 `rid + agentId` 向指定群聊发送消息。
66
-
67
- 接口会校验:
68
-
69
- 1. `rid` 对应房间存在
70
- 2. `agentId` 对应用户存在、为 bot、且 active
71
- 3. 该 `agentId` 确实在该 `rid` 的订阅列表里
72
-
73
- 满足后,走后端正常发消息逻辑(`executeSendMessage`)。
74
-
75
- ### 鉴权方式
76
-
77
- 与 `mcpToDos` 一致:
78
-
79
- - `authRequired: false`
80
- - 通过请求头 `Authorization: Bearer <JWT>` 校验
81
- - 校验规则受以下设置控制:
82
- - `Appia_Antagent_JWT_Enable`
83
- - `APPIA_JWT_SECRET`
84
-
85
- ### 请求示例
86
-
87
- #### 示例一:发送纯文本
88
-
89
- ```bash
90
- curl -sS -X POST 'https://YOUR_ROCKETCHAT_HOST/api/v1/claw/agent.message.send' \
91
- -H 'Content-Type: application/json' \
92
- -H 'Authorization: Bearer YOUR_MCP_JWT_TOKEN' \
93
- -d '{
94
- "rid": "TARGET_ROOM_RID",
95
- "agentId": "AGENT_BOT_USER_ID",
96
- "msg": "这是一条由 Claw 机器人发送的消息"
97
- }'
98
- ```
99
-
100
- #### 示例二:发送 Markdown(`md`)
101
-
102
- ```bash
103
- curl -sS -X POST 'https://YOUR_ROCKETCHAT_HOST/api/v1/claw/agent.message.send' \
104
- -H 'Content-Type: application/json' \
105
- -H 'Authorization: Bearer YOUR_MCP_JWT_TOKEN' \
106
- -d '{
107
- "rid": "TARGET_ROOM_RID",
108
- "agentId": "OPENCLAW_AGENT_ID",
109
- "md": [
110
- {
111
- "type": "PARAGRAPH",
112
- "value": [
113
- { "type": "PLAIN_TEXT", "value": "这是一条 *Markdown* 消息" }
114
- ]
115
- }
116
- ]
117
- }'
118
- ```
119
-
120
- ### 参数
121
-
122
- - `rid`(body,必填):目标群聊房间 ID
123
- - `agentId`(body,必填):OpenClaw 侧 Agent ID(会映射到机器人用户)
124
- - `msg`(body,可选):消息文本
125
- - `md`(body,可选):Markdown AST(Rocket.Chat `md` 结构)
126
- - 约束:`msg` 和 `md` 至少提供一个
127
-
128
- ### 成功返回示例
129
-
130
- ```json
131
- {
132
- "success": true,
133
- "data": {
134
- "rid": "TARGET_ROOM_RID",
135
- "agentId": "AGENT_BOT_USER_ID",
136
- "status": "sent"
137
- }
138
- }
139
- ```
140
-
141
- ### 常见失败
142
-
143
- - `{"success":false,"message":"401"}`:JWT 无效或过期
144
- - `{"success":false,"message":"rid and agentId are required, and either msg or md must be provided"}`:参数缺失
145
- - `{"success":false,"message":"room not found"}`:房间不存在
146
- - `{"success":false,"message":"agent not found by agentId"}`:找不到对应 agentId 的机器人
147
- - `{"success":false,"message":"agent must be a bot user"}`:agent 不是 bot
148
- - `{"success":false,"message":"agent is inactive"}`:机器人未激活
149
- - `{"success":false,"message":"agent is not in this room"}`:机器人不在该群里