sophhub 0.4.63 → 0.4.64

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,370 @@
1
+ #!/usr/bin/env python3
2
+ """Appia Claw 扩展能力:scopes + 人员/通知/待办/未读 + 频道 + 会议纪要/总览 + 写操作 + 聚合调用。
3
+
4
+ 鉴权与 appia_claw.py 一致:MCP/CLAW JWT + userId + agentId。共享 helper 见 appia_common。
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import argparse
10
+ import json
11
+ import sys
12
+ from typing import Any
13
+
14
+ import appia_common as ac
15
+
16
+
17
+ def _channel_params(args: argparse.Namespace) -> dict[str, Any]:
18
+ """频道类接口:--rid 优先,否则用 --name / --channel-name。"""
19
+ rid = args.rid or ac.cfg("TARGET_RID")
20
+ if rid:
21
+ return {"rid": rid}
22
+ name = args.name or args.channel_name
23
+ if not name:
24
+ sys.exit("⚠️ 需要 --rid(或 TARGET_RID)或 --name")
25
+ return {"name": name}
26
+
27
+
28
+ def _load_json_arg(json_str: str | None, json_file: str | None) -> Any:
29
+ if json_str is not None:
30
+ try:
31
+ return json.loads(json_str)
32
+ except json.JSONDecodeError as exc:
33
+ sys.exit(f"⚠️ --json 参数解析失败: {exc}")
34
+ if json_file:
35
+ import os
36
+
37
+ with open(os.path.expanduser(json_file), encoding="utf-8") as f:
38
+ return json.load(f)
39
+ return None
40
+
41
+
42
+ def _common_params() -> dict[str, Any]:
43
+ return {"userId": ac.user_id(), "agentId": ac.agent_id()}
44
+
45
+
46
+ # ---- 读:scopes / 人员 / 通知 / 待办 / 未读 ----
47
+
48
+
49
+ def cmd_scopes(args: argparse.Namespace) -> int:
50
+ data = ac.api_get("agent.scopes.get", _common_params(), args.timeout)
51
+ return ac.emit(data, args.json, json.dumps(data.get("data", {}), ensure_ascii=False, indent=2))
52
+
53
+
54
+ def cmd_scopes_request(args: argparse.Namespace) -> int:
55
+ scopes = [s.strip() for s in args.scopes.split(",") if s.strip()]
56
+ body = {**_common_params(), "scopes": scopes, "reason": args.reason or ""}
57
+ data = ac.api_post_json("agent.scopes.request", body, args.timeout)
58
+ return ac.emit(data, args.json, json.dumps(data.get("data", {}), ensure_ascii=False, indent=2))
59
+
60
+
61
+ def cmd_users_search(args: argparse.Namespace) -> int:
62
+ params = {**_common_params(), "q": args.q, "limit": args.limit}
63
+ data = ac.api_get("agent.users.search", params, args.timeout)
64
+ if args.json:
65
+ print(json.dumps(data, ensure_ascii=False, indent=2))
66
+ return 0
67
+ users = (data.get("data") or {}).get("users") or []
68
+ print(f"total={len(users)}")
69
+ for u in users:
70
+ print(f"- _id={u.get('_id')} username={u.get('username')}")
71
+ return 0
72
+
73
+
74
+ def cmd_notifications(args: argparse.Namespace) -> int:
75
+ params = {**_common_params(), "limit": args.limit, "since": args.since}
76
+ data = ac.api_get("agent.notifications.list", params, args.timeout)
77
+ return ac.emit(data, args.json, json.dumps(data.get("data", {}), ensure_ascii=False, indent=2))
78
+
79
+
80
+ def cmd_todos(args: argparse.Namespace) -> int:
81
+ params = {**_common_params(), "limit": args.limit, "status": args.status}
82
+ data = ac.api_get("agent.todos.list", params, args.timeout)
83
+ return ac.emit(data, args.json, json.dumps(data.get("data", {}), ensure_ascii=False, indent=2))
84
+
85
+
86
+ def cmd_unread(args: argparse.Namespace) -> int:
87
+ data = ac.api_get("agent.unread.summary", _common_params(), args.timeout)
88
+ return ac.emit(data, args.json, json.dumps(data.get("data", {}), ensure_ascii=False, indent=2))
89
+
90
+
91
+ # ---- 读:频道 ----
92
+
93
+
94
+ def cmd_channels_search(args: argparse.Namespace) -> int:
95
+ params = {**_common_params(), "name": args.name, "keyword": args.keyword, "limit": args.limit}
96
+ data = ac.api_get("agent.channels.search", params, args.timeout)
97
+ if args.json:
98
+ print(json.dumps(data, ensure_ascii=False, indent=2))
99
+ return 0
100
+ chans = (data.get("data") or {}).get("channels") or (data.get("data") or {}).get("items") or []
101
+ print(f"total={len(chans)}")
102
+ for c in chans:
103
+ print(f"- rid={c.get('rid', c.get('_id'))} name={c.get('name')}")
104
+ return 0
105
+
106
+
107
+ def cmd_channel_info(args: argparse.Namespace) -> int:
108
+ data = ac.api_get("agent.channel.info.get", {**_common_params(), **_channel_params(args)}, args.timeout)
109
+ return ac.emit(data, args.json, json.dumps(data.get("data", {}), ensure_ascii=False, indent=2))
110
+
111
+
112
+ def cmd_channel_messages(args: argparse.Namespace) -> int:
113
+ params = {**_common_params(), **_channel_params(args), "limit": args.limit}
114
+ data = ac.api_get("agent.channel.messages.list", params, args.timeout)
115
+ return ac.emit(data, args.json, json.dumps(data.get("data", {}), ensure_ascii=False, indent=2))
116
+
117
+
118
+ def cmd_channel_messages_search(args: argparse.Namespace) -> int:
119
+ params = {**_common_params(), **_channel_params(args), "keyword": args.keyword, "limit": args.limit}
120
+ data = ac.api_get("agent.channel.messages.search", params, args.timeout)
121
+ return ac.emit(data, args.json, json.dumps(data.get("data", {}), ensure_ascii=False, indent=2))
122
+
123
+
124
+ def cmd_channel_members(args: argparse.Namespace) -> int:
125
+ data = ac.api_get("agent.channel.members.list", {**_common_params(), **_channel_params(args)}, args.timeout)
126
+ return ac.emit(data, args.json, json.dumps(data.get("data", {}), ensure_ascii=False, indent=2))
127
+
128
+
129
+ def cmd_channel_attachments(args: argparse.Namespace) -> int:
130
+ data = ac.api_get("agent.channel.attachments.list", {**_common_params(), **_channel_params(args)}, args.timeout)
131
+ return ac.emit(data, args.json, json.dumps(data.get("data", {}), ensure_ascii=False, indent=2))
132
+
133
+
134
+ def cmd_channel_announcements(args: argparse.Namespace) -> int:
135
+ data = ac.api_get("agent.channel.announcements.list", {**_common_params(), **_channel_params(args)}, args.timeout)
136
+ return ac.emit(data, args.json, json.dumps(data.get("data", {}), ensure_ascii=False, indent=2))
137
+
138
+
139
+ def cmd_file_metadata(args: argparse.Namespace) -> int:
140
+ if not args.file_id:
141
+ sys.exit("⚠️ file-metadata 需要 --file-id")
142
+ params = {**_common_params(), "fileId": args.file_id}
143
+ data = ac.api_get("agent.file.metadata.get", params, args.timeout)
144
+ return ac.emit(data, args.json, json.dumps(data.get("data", {}), ensure_ascii=False, indent=2))
145
+
146
+
147
+ # ---- 读:会议纪要 / 总览 ----
148
+
149
+
150
+ def cmd_meeting_minutes(args: argparse.Namespace) -> int:
151
+ params = {**_common_params(), "limit": args.limit}
152
+ data = ac.api_get("agent.meeting.minutes.list", params, args.timeout)
153
+ return ac.emit(data, args.json, json.dumps(data.get("data", {}), ensure_ascii=False, indent=2))
154
+
155
+
156
+ def cmd_meeting_minutes_ingest(args: argparse.Namespace) -> int:
157
+ params = {**_common_params(), "limit": args.limit}
158
+ data = ac.api_get("agent.meeting.minutes.ingest.list", params, args.timeout)
159
+ return ac.emit(data, args.json, json.dumps(data.get("data", {}), ensure_ascii=False, indent=2))
160
+
161
+
162
+ def cmd_overview_resolve(args: argparse.Namespace) -> int:
163
+ if not args.url:
164
+ sys.exit("⚠️ overview-resolve 需要 --url")
165
+ params = {**_common_params(), "url": args.url}
166
+ data = ac.api_get("agent.overview.resolve", params, args.timeout)
167
+ return ac.emit(data, args.json, json.dumps(data.get("data", {}), ensure_ascii=False, indent=2))
168
+
169
+
170
+ def cmd_overview_links(args: argparse.Namespace) -> int:
171
+ data = ac.api_get("agent.channel.overview.links.list", {**_common_params(), **_channel_params(args)}, args.timeout)
172
+ return ac.emit(data, args.json, json.dumps(data.get("data", {}), ensure_ascii=False, indent=2))
173
+
174
+
175
+ # ---- 写:待办 / 频道 / 会议 ----
176
+
177
+
178
+ def cmd_todos_update(args: argparse.Namespace) -> int:
179
+ if not args.id:
180
+ sys.exit("⚠️ todos-update 需要 --id")
181
+ body: dict[str, Any] = {**_common_params(), "id": args.id, "status": args.status}
182
+ if args.reminder_time:
183
+ body["reminderTime"] = args.reminder_time
184
+ data = ac.api_post_json("agent.todos.update-status", body, args.timeout)
185
+ return ac.emit(data, args.json, json.dumps(data.get("data", {}), ensure_ascii=False, indent=2))
186
+
187
+
188
+ def cmd_channels_create(args: argparse.Namespace) -> int:
189
+ members = _load_json_arg(args.members_json, args.members_file)
190
+ if members is None:
191
+ members = []
192
+ body: dict[str, Any] = {**_common_params(), "name": args.name or "", "members": members}
193
+ if args.dep_ids:
194
+ body["depIds"] = [d.strip() for d in args.dep_ids.split(",") if d.strip()]
195
+ if args.read_only:
196
+ body["readOnly"] = True
197
+ extra = _load_json_arg(args.extra_json, args.extra_file)
198
+ if extra is not None:
199
+ body["extraData"] = extra
200
+ data = ac.api_post_json("agent.channels.create", body, args.timeout)
201
+ if args.json:
202
+ print(json.dumps(data, ensure_ascii=False, indent=2))
203
+ return 0
204
+ d = data.get("data") or {}
205
+ print(f"created: rid={d.get('rid')} name={(d.get('channel') or {}).get('name', args.name or '')}")
206
+ return 0
207
+
208
+
209
+ def cmd_tool_invoke(args: argparse.Namespace) -> int:
210
+ if not args.tool:
211
+ sys.exit("⚠️ tool-invoke 需要 --tool")
212
+ tool_args = _load_json_arg(args.args_json, args.args_file)
213
+ body = {**_common_params(), "tool": args.tool, "args": tool_args or {}}
214
+ data = ac.api_post_json("agent.tool.invoke", body, args.timeout)
215
+ return ac.emit(data, args.json, json.dumps(data.get("data", {}), ensure_ascii=False, indent=2))
216
+
217
+
218
+ # ---- CLI ----
219
+
220
+
221
+ def _channel_args(parser: argparse.ArgumentParser) -> None:
222
+ parser.add_argument("--rid", help="频道 rid(覆盖 TARGET_RID)")
223
+ parser.add_argument("--name", "--channel-name", dest="name", help="频道名(无 rid 时用)")
224
+
225
+
226
+ def main() -> int:
227
+ p = argparse.ArgumentParser(description="Appia Claw 扩展能力(scopes / 人员 / 通知 / 待办 / 频道 / 会议 / 总览 / 写操作)")
228
+ ac.add_cred_arg(p)
229
+ ac.add_timeout_arg(p)
230
+ sub = p.add_subparsers(dest="cmd", required=True)
231
+
232
+ def _json(sp):
233
+ ac.add_json_arg(sp)
234
+
235
+ sp = sub.add_parser("scopes", help="GET agent.scopes.get")
236
+ _json(sp)
237
+ sp.set_defaults(_run=cmd_scopes)
238
+
239
+ sp = sub.add_parser("scopes-request", help="POST agent.scopes.request(发 myAgent 授权卡片)")
240
+ sp.add_argument("--scopes", required=True, help="逗号分隔,如 channels:write")
241
+ sp.add_argument("--reason", help="申请原因")
242
+ _json(sp)
243
+ sp.set_defaults(_run=cmd_scopes_request)
244
+
245
+ sp = sub.add_parser("users-search", help="GET agent.users.search")
246
+ sp.add_argument("--q", required=True, help="姓名/username 关键词")
247
+ sp.add_argument("--limit", type=int, default=20)
248
+ _json(sp)
249
+ sp.set_defaults(_run=cmd_users_search)
250
+
251
+ sp = sub.add_parser("notifications", help="GET agent.notifications.list")
252
+ sp.add_argument("--limit", type=int, default=20)
253
+ sp.add_argument("--since", help="起始时间")
254
+ _json(sp)
255
+ sp.set_defaults(_run=cmd_notifications)
256
+
257
+ sp = sub.add_parser("todos", help="GET agent.todos.list")
258
+ sp.add_argument("--limit", type=int, default=20)
259
+ sp.add_argument("--status", help="状态过滤")
260
+ _json(sp)
261
+ sp.set_defaults(_run=cmd_todos)
262
+
263
+ sp = sub.add_parser("unread", help="GET agent.unread.summary")
264
+ _json(sp)
265
+ sp.set_defaults(_run=cmd_unread)
266
+
267
+ sp = sub.add_parser("channels-search", help="GET agent.channels.search")
268
+ sp.add_argument("--name", help="频道名")
269
+ sp.add_argument("--keyword", help="关键词")
270
+ sp.add_argument("--limit", type=int, default=20)
271
+ _json(sp)
272
+ sp.set_defaults(_run=cmd_channels_search)
273
+
274
+ sp = sub.add_parser("channel-info", help="GET agent.channel.info.get")
275
+ _channel_args(sp)
276
+ _json(sp)
277
+ sp.set_defaults(_run=cmd_channel_info)
278
+
279
+ sp = sub.add_parser("channel-messages", help="GET agent.channel.messages.list")
280
+ _channel_args(sp)
281
+ sp.add_argument("--limit", type=int, default=20)
282
+ _json(sp)
283
+ sp.set_defaults(_run=cmd_channel_messages)
284
+
285
+ sp = sub.add_parser("channel-messages-search", help="GET agent.channel.messages.search")
286
+ _channel_args(sp)
287
+ sp.add_argument("--keyword", required=True)
288
+ sp.add_argument("--limit", type=int, default=20)
289
+ _json(sp)
290
+ sp.set_defaults(_run=cmd_channel_messages_search)
291
+
292
+ sp = sub.add_parser("channel-members", help="GET agent.channel.members.list")
293
+ _channel_args(sp)
294
+ _json(sp)
295
+ sp.set_defaults(_run=cmd_channel_members)
296
+
297
+ sp = sub.add_parser("channel-attachments", help="GET agent.channel.attachments.list")
298
+ _channel_args(sp)
299
+ _json(sp)
300
+ sp.set_defaults(_run=cmd_channel_attachments)
301
+
302
+ sp = sub.add_parser("channel-announcements", help="GET agent.channel.announcements.list")
303
+ _channel_args(sp)
304
+ _json(sp)
305
+ sp.set_defaults(_run=cmd_channel_announcements)
306
+
307
+ sp = sub.add_parser("file-metadata", help="GET agent.file.metadata.get")
308
+ sp.add_argument("--file-id", required=True)
309
+ _json(sp)
310
+ sp.set_defaults(_run=cmd_file_metadata)
311
+
312
+ sp = sub.add_parser("meeting-minutes", help="GET agent.meeting.minutes.list")
313
+ sp.add_argument("--limit", type=int, default=20)
314
+ _json(sp)
315
+ sp.set_defaults(_run=cmd_meeting_minutes)
316
+
317
+ sp = sub.add_parser("meeting-minutes-ingest", help="GET agent.meeting.minutes.ingest.list")
318
+ sp.add_argument("--limit", type=int, default=20)
319
+ _json(sp)
320
+ sp.set_defaults(_run=cmd_meeting_minutes_ingest)
321
+
322
+ sp = sub.add_parser("overview-resolve", help="GET agent.overview.resolve(总览链接→元数据)")
323
+ sp.add_argument("--url", required=True, help="总览链接")
324
+ _json(sp)
325
+ sp.set_defaults(_run=cmd_overview_resolve)
326
+
327
+ sp = sub.add_parser("overview-links", help="GET agent.channel.overview.links.list")
328
+ _channel_args(sp)
329
+ _json(sp)
330
+ sp.set_defaults(_run=cmd_overview_links)
331
+
332
+ sp = sub.add_parser("todos-update", help="POST agent.todos.update-status")
333
+ sp.add_argument("--id", required=True, help="待办 id")
334
+ sp.add_argument("--status", required=True)
335
+ sp.add_argument("--reminder-time", dest="reminder_time")
336
+ _json(sp)
337
+ sp.set_defaults(_run=cmd_todos_update)
338
+
339
+ sp = sub.add_parser("channels-create", help="POST agent.channels.create")
340
+ sp.add_argument("--name", help="频道名")
341
+ sp.add_argument("--members-json", dest="members_json", help='username 数组 JSON,如 ["zhangsan","lisi"]')
342
+ sp.add_argument("--members-file", dest="members_file", help="username 数组 JSON 文件")
343
+ sp.add_argument("--dep-ids", dest="dep_ids", help="部门 id 逗号分隔")
344
+ sp.add_argument("--read-only", dest="read_only", action="store_true")
345
+ sp.add_argument("--extra-json", dest="extra_json", help="extraData JSON")
346
+ sp.add_argument("--extra-file", dest="extra_file", help="extraData JSON 文件")
347
+ _json(sp)
348
+ sp.set_defaults(_run=cmd_channels_create)
349
+
350
+ sp = sub.add_parser("tool-invoke", help="POST agent.tool.invoke(聚合调用)")
351
+ sp.add_argument("--tool", required=True, help="如 appia.todos.list")
352
+ sp.add_argument("--args-json", dest="args_json", help="args JSON")
353
+ sp.add_argument("--args-file", dest="args_file", help="args JSON 文件")
354
+ _json(sp)
355
+ sp.set_defaults(_run=cmd_tool_invoke)
356
+
357
+ args = p.parse_args()
358
+ ac.init_cred(args.cred_file)
359
+ runner = getattr(args, "_run", None)
360
+ if runner is None:
361
+ return 1
362
+ return int(runner(args))
363
+
364
+
365
+ if __name__ == "__main__":
366
+ try:
367
+ raise SystemExit(main())
368
+ except Exception as exc: # pragma: no cover - CLI
369
+ print(f"⚠️ {exc}", file=sys.stderr)
370
+ raise SystemExit(1)