lansenger-cli 0.1.0__tar.gz

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.
Files changed (27) hide show
  1. lansenger_cli-0.1.0/LICENSE +21 -0
  2. lansenger_cli-0.1.0/PKG-INFO +22 -0
  3. lansenger_cli-0.1.0/README.md +288 -0
  4. lansenger_cli-0.1.0/pyproject.toml +40 -0
  5. lansenger_cli-0.1.0/setup.cfg +4 -0
  6. lansenger_cli-0.1.0/src/lansenger_cli/__init__.py +0 -0
  7. lansenger_cli-0.1.0/src/lansenger_cli/commands/__init__.py +0 -0
  8. lansenger_cli-0.1.0/src/lansenger_cli/commands/calendar.py +154 -0
  9. lansenger_cli-0.1.0/src/lansenger_cli/commands/callback.py +64 -0
  10. lansenger_cli-0.1.0/src/lansenger_cli/commands/config.py +54 -0
  11. lansenger_cli-0.1.0/src/lansenger_cli/commands/department.py +59 -0
  12. lansenger_cli-0.1.0/src/lansenger_cli/commands/group.py +117 -0
  13. lansenger_cli-0.1.0/src/lansenger_cli/commands/health.py +17 -0
  14. lansenger_cli-0.1.0/src/lansenger_cli/commands/media.py +42 -0
  15. lansenger_cli-0.1.0/src/lansenger_cli/commands/message.py +162 -0
  16. lansenger_cli-0.1.0/src/lansenger_cli/commands/oauth.py +73 -0
  17. lansenger_cli-0.1.0/src/lansenger_cli/commands/staff.py +102 -0
  18. lansenger_cli-0.1.0/src/lansenger_cli/commands/streaming.py +27 -0
  19. lansenger_cli-0.1.0/src/lansenger_cli/commands/todo.py +220 -0
  20. lansenger_cli-0.1.0/src/lansenger_cli/main.py +45 -0
  21. lansenger_cli-0.1.0/src/lansenger_cli/utils.py +79 -0
  22. lansenger_cli-0.1.0/src/lansenger_cli.egg-info/PKG-INFO +22 -0
  23. lansenger_cli-0.1.0/src/lansenger_cli.egg-info/SOURCES.txt +25 -0
  24. lansenger_cli-0.1.0/src/lansenger_cli.egg-info/dependency_links.txt +1 -0
  25. lansenger_cli-0.1.0/src/lansenger_cli.egg-info/entry_points.txt +2 -0
  26. lansenger_cli-0.1.0/src/lansenger_cli.egg-info/requires.txt +6 -0
  27. lansenger_cli-0.1.0/src/lansenger_cli.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lansenger PM Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,22 @@
1
+ Metadata-Version: 2.4
2
+ Name: lansenger-cli
3
+ Version: 0.1.0
4
+ Summary: CLI for Lansenger (蓝信) — send messages, manage groups, staff, departments, calendars, todos, and more
5
+ Author: Lansenger PM Team
6
+ License-Expression: MIT
7
+ Keywords: lansenger,蓝信,cli,chatbot,messaging,enterprise-chat
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Topic :: Communications :: Chat
15
+ Requires-Python: >=3.10
16
+ License-File: LICENSE
17
+ Requires-Dist: lansenger-sdk>=1.0.1
18
+ Requires-Dist: typer>=0.9
19
+ Requires-Dist: rich>=13
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest>=7; extra == "dev"
22
+ Dynamic: license-file
@@ -0,0 +1,288 @@
1
+ # Lansenger CLI
2
+
3
+ 蓝信(Lansenger)命令行工具 — 在终端直接调用蓝信开放平台 API,发送消息、管理群组、查询人员/部门、操作日程与待办等。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ pip install lansenger-cli
9
+ ```
10
+
11
+ 或从源码安装:
12
+
13
+ ```bash
14
+ pip install -e .
15
+ ```
16
+
17
+ 需要 Python ≥ 3.10。
18
+
19
+ ## 快速开始
20
+
21
+ ### 1. 配置凭证
22
+
23
+ 通过 `config set` 命令保存 app 凭证(存储在 `~/.lansenger/sdk_state.json`,密钥脱敏显示):
24
+
25
+ ```bash
26
+ lansenger config set app_id YOUR_APP_ID
27
+ lansenger config set app_secret YOUR_APP_SECRET
28
+ ```
29
+
30
+ 也可以通过环境变量配置(适合 CI/CD 或临时使用):
31
+
32
+ ```bash
33
+ export LANSENGER_APP_ID=YOUR_APP_ID
34
+ export LANSENGER_APP_SECRET=YOUR_APP_SECRET
35
+ ```
36
+
37
+ 可选:设置私有部署网关地址和 OAuth2 passport 地址:
38
+
39
+ ```bash
40
+ lansenger config set api_gateway_url https://your-gateway.example.com/open/apigw
41
+ lansenger config set passport_url https://your-passport.example.com
42
+ ```
43
+
44
+ ### 2. 查看配置
45
+
46
+ ```bash
47
+ lansenger config show
48
+ ```
49
+
50
+ ### 3. 健康检查
51
+
52
+ 验证凭证是否正确、能否成功获取 app token:
53
+
54
+ ```bash
55
+ lansenger health check
56
+ ```
57
+
58
+ ## 命令总览
59
+
60
+ | 命令组 | 说明 | 子命令 |
61
+ |--------|------|--------|
62
+ | `config` | 管理凭证配置 | `set`, `show`, `clear` |
63
+ | `message` | 发送与管理消息 | `send-text`, `send-markdown`, `send-file`, `send-image-url`, `send-link-card`, `send-app-articles`, `send-app-card`, `update-dynamic-card`, `revoke`, `query-groups` |
64
+ | `group` | 管理群组 | `create`, `info`, `members`, `list`, `check`, `update`, `update-members` |
65
+ | `staff` | 查询人员信息 | `basic-info`, `detail`, `ancestors`, `id-mapping`, `org-extra-fields`, `search`, `org-info` |
66
+ | `department` | 查询部门信息 | `detail`, `children`, `staffs` |
67
+ | `calendar` | 日程操作 | `primary`, `create-schedule`, `fetch-schedule`, `delete-schedule`, `list-schedules`, `attendees`, `add-attendees`, `delete-attendees` |
68
+ | `todo` | 待办任务管理 | `create`, `update`, `update-status`, `delete`, `list`, `fetch-by-source`, `fetch-by-id`, `status-counts`, `executor-status`, `add-executors`, `delete-executors`, `executor-list` |
69
+ | `oauth` | OAuth2 用户认证 | `authorize-url`, `exchange-code`, `refresh-token`, `user-info`, `parse-callback`, `validate-state` |
70
+ | `callback` | 回调事件解析 | `parse-payload`, `verify-signature`, `event-types` |
71
+ | `media` | 媒体文件操作 | `upload`, `download`, `download-to-file` |
72
+ | `streaming` | 流式消息(AI 场景) | `create`, `fetch` |
73
+ | `health` | 连接健康检查 | `check` |
74
+
75
+ ## 常用示例
76
+
77
+ ### 发送消息
78
+
79
+ ```bash
80
+ # 发送文本消息
81
+ lansenger message send-text chat123 "Hello World"
82
+
83
+ # 发送 Markdown 消息
84
+ lansenger message send-markdown chat123 "**Bold** text"
85
+
86
+ # 发送文件
87
+ lansenger message send-file chat123 /path/to/file.pdf
88
+
89
+ # 发送带图片 URL 的消息
90
+ lansenger message send-image-url chat123 https://example.com/photo.jpg
91
+
92
+ # 发送链接卡片
93
+ lansenger message send-link-card chat123 "公告标题" https://example.com --desc "点击查看详情"
94
+
95
+ # 发送应用卡片
96
+ lansenger message send-app-card chat123 "卡片标题" --content "正文内容" --card-link https://example.com
97
+
98
+ # 发送多条图文(appArticles)
99
+ lansenger message send-app-articles chat123 '{"title":"文章1","url":"https://a.com"}' '{"title":"文章2","url":"https://b.com"}'
100
+
101
+ # 群内发送并 @all
102
+ lansenger message send-text group123 "全员通知" --group --mention-all
103
+
104
+ # 群内 @指定人
105
+ lansenger message send-text group123 "请查看" --group --mention staff001 --mention staff002
106
+
107
+ # 撤回消息
108
+ lansenger message revoke msg001 msg002
109
+ ```
110
+
111
+ ### 群组管理
112
+
113
+ ```bash
114
+ # 创建群组
115
+ lansenger group create "项目群" org001 --staff staff001 --staff staff002
116
+
117
+ # 查看群信息
118
+ lansenger group info group123
119
+
120
+ # 查看群成员
121
+ lansenger group members group123
122
+
123
+ # 查看群列表
124
+ lansenger group list --user-token YOUR_USER_TOKEN
125
+
126
+ # 检查用户是否在群内
127
+ lansenger group check group123 --staff-id staff001
128
+
129
+ # 更新群信息
130
+ lansenger group update group123 --name "新名称" --desc "新描述"
131
+
132
+ # 添加/移除成员
133
+ lansenger group update-members group123 --add staff003 --remove staff001
134
+ ```
135
+
136
+ ### 人员查询
137
+
138
+ ```bash
139
+ # 查看人员基本信息
140
+ lansenger staff basic-info staff001
141
+
142
+ # 查看人员详细信息
143
+ lansenger staff detail staff001
144
+
145
+ # 搜索人员
146
+ lansenger staff search 张三
147
+
148
+ # 手机号/邮箱映射 staff ID
149
+ lansenger staff id-mapping org001 phone 13800138000
150
+
151
+ # 查看组织信息
152
+ lansenger staff org-info org001
153
+ ```
154
+
155
+ ### 部门查询
156
+
157
+ ```bash
158
+ # 查看部门详情
159
+ lansenger department detail dept001
160
+
161
+ # 查看子部门
162
+ lansenger department children dept001
163
+
164
+ # 查看部门内人员
165
+ lansenger department staffs dept001
166
+ ```
167
+
168
+ ### 日程操作
169
+
170
+ ```bash
171
+ # 获取主日历
172
+ lansenger calendar primary --user-token YOUR_USER_TOKEN
173
+
174
+ # 创建日程
175
+ lansenger calendar create-schedule cal001 "周会" \
176
+ '{"dateTime":"2026-01-01T09:00:00","timeZone":"Asia/Shanghai"}' \
177
+ '{"dateTime":"2026-01-01T10:00:00","timeZone":"Asia/Shanghai"}' \
178
+ '[{"staffId":"staff001"}]' \
179
+ --desc "每周例会"
180
+
181
+ # 查看日程列表
182
+ lansenger calendar list-schedules cal001 1735689600 1735776000 --user-token YOUR_TOKEN
183
+ ```
184
+
185
+ ### 待办任务
186
+
187
+ ```bash
188
+ # 创建待办
189
+ lansenger todo create "审批文档" https://app.com/doc https://app.com/doc \
190
+ "staff001,staff002" org001 --desc "请审批" --type 2
191
+
192
+ # 更新待办状态(11=待阅, 12=已阅, 21=待办, 22=已办)
193
+ lansenger todo update-status task001 22 org001
194
+
195
+ # 查看待办列表
196
+ lansenger todo list org001 --status 21,22
197
+
198
+ # 删除待办
199
+ lansenger todo delete task001 org001
200
+ ```
201
+
202
+ ### OAuth2 用户认证
203
+
204
+ ```bash
205
+ # 生成授权 URL
206
+ lansenger oauth authorize-url https://yourapp.com/callback --scope basic_userinfor
207
+
208
+ # 交换 code 获取 user token
209
+ lansenger oauth exchange-code AUTH_CODE --redirect-uri https://yourapp.com/callback
210
+
211
+ # 刷新 user token
212
+ lansenger oauth refresh-token YOUR_REFRESH_TOKEN
213
+
214
+ # 获取用户信息
215
+ lansenger oauth user-info YOUR_USER_TOKEN
216
+ ```
217
+
218
+ ### 回调事件
219
+
220
+ ```bash
221
+ # 查看所有回调事件类型
222
+ lansenger callback event-types
223
+
224
+ # 解析回调数据
225
+ lansenger callback parse-payload ENCRYPTED_DATA --encoding-key YOUR_KEY
226
+
227
+ # 验证签名
228
+ lansenger callback verify-signature TIMESTAMP NONCE SIGNATURE ENCODING_KEY
229
+ ```
230
+
231
+ ### 媒体文件
232
+
233
+ ```bash
234
+ # 上传文件
235
+ lansenger media upload /path/to/file.pdf --media-type 3
236
+
237
+ # 下载媒体文件到本地
238
+ lansenger media download-to-file MEDIA_ID --output /path/to/save.pdf
239
+ ```
240
+
241
+ ### 流式消息
242
+
243
+ ```bash
244
+ # 创建流式消息(用于 AI agent 渐进式输出)
245
+ lansenger streaming create user123 single stream-session-001
246
+
247
+ # 获取流式消息状态
248
+ lansenger streaming fetch MSG_ID
249
+ ```
250
+
251
+ ## 全局选项
252
+
253
+ | 选项 | 说明 |
254
+ |------|------|
255
+ | `--json` / `-j` | 输出原始 JSON 格式而非 rich 表格 |
256
+
257
+ ```bash
258
+ # JSON 格式输出(便于脚本处理)
259
+ lansenger -j staff basic-info staff001
260
+ ```
261
+
262
+ ## Shell 自动补全
263
+
264
+ typer 内置补全支持:
265
+
266
+ ```bash
267
+ # 安装补全
268
+ lansenger --install-completion
269
+
270
+ # 查看补全脚本
271
+ lansenger --show-completion
272
+ ```
273
+
274
+ 支持 bash、zsh、fish 等主流 shell。
275
+
276
+ ## 凭证安全
277
+
278
+ - 凭证存储在 `~/.lansenger/sdk_state.json`,文件权限 0600
279
+ - `config show` 命令对 `app_id` 和 `app_secret` 脱敏显示(`***`)
280
+ - 也支持环境变量 `LANSENGER_APP_ID` / `LANSENGER_APP_SECRET`,适合 CI/CD 场景
281
+
282
+ ## 与 SDK 的关系
283
+
284
+ 本 CLI 基于 [lansenger-sdk](https://github.com/lansenger-pm/lansenger-skills-official) 的 `LansengerSyncClient` 实现,覆盖 SDK 全部同步 API,不修改 SDK 代码。
285
+
286
+ ## 许可证
287
+
288
+ MIT License
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["setuptools>=64", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "lansenger-cli"
7
+ version = "0.1.0"
8
+ description = "CLI for Lansenger (蓝信) — send messages, manage groups, staff, departments, calendars, todos, and more"
9
+ license = "MIT"
10
+ requires-python = ">=3.10"
11
+ authors = [
12
+ { name = "Lansenger PM Team" },
13
+ ]
14
+ keywords = ["lansenger", "蓝信", "cli", "chatbot", "messaging", "enterprise-chat"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Developers",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Topic :: Communications :: Chat",
23
+ ]
24
+
25
+ dependencies = [
26
+ "lansenger-sdk>=1.0.1",
27
+ "typer>=0.9",
28
+ "rich>=13",
29
+ ]
30
+
31
+ [project.optional-dependencies]
32
+ dev = [
33
+ "pytest>=7",
34
+ ]
35
+
36
+ [project.scripts]
37
+ lansenger = "lansenger_cli.main:app"
38
+
39
+ [tool.setuptools.packages.find]
40
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,154 @@
1
+ import typer
2
+ import json
3
+ from typing import List
4
+
5
+ from lansenger_cli.utils import get_client, output_result, output_list
6
+
7
+ app = typer.Typer(help="Calendar and schedule operations")
8
+
9
+
10
+ @app.command("primary")
11
+ def fetch_primary_calendar(
12
+ user_token: str = typer.Option("", "--user-token", help="User token"),
13
+ user_id: str = typer.Option("", "--user-id", help="User ID"),
14
+ ):
15
+ client = get_client()
16
+ result = client.fetch_primary_calendar(user_token=user_token, user_id=user_id)
17
+ output_result(result, fields=[
18
+ "calendar_id", "summary", "description", "permissions", "role",
19
+ ], title="Primary Calendar")
20
+
21
+
22
+ @app.command("create-schedule")
23
+ def create_schedule(
24
+ calendar_id: str = typer.Argument(help="Calendar ID"),
25
+ summary: str = typer.Argument(help="Schedule summary/title"),
26
+ start_time: str = typer.Argument(help="Start time as JSON: '{\"dateTime\":\"2026-01-01T09:00:00\",\"timeZone\":\"Asia/Shanghai\"}'"),
27
+ end_time: str = typer.Argument(help="End time as JSON"),
28
+ attendees: str = typer.Argument(help="Attendees as JSON list: '[{\"staffId\":\"xxx\"}]'"),
29
+ description: str = typer.Option("", "--desc", "-d", help="Schedule description"),
30
+ all_day: str = typer.Option("no", "--all-day", help="yes or no"),
31
+ repeat_type: str = typer.Option("no", "--repeat", help="Repeat type: no, daily, weekly, monthly, yearly"),
32
+ reminder_type: str = typer.Option("yes", "--reminder", help="Reminder type: yes or no"),
33
+ user_token: str = typer.Option("", "--user-token", help="User token"),
34
+ user_id: str = typer.Option("", "--user-id", help="User ID"),
35
+ ):
36
+ client = get_client()
37
+ start_time_dict = json.loads(start_time)
38
+ end_time_dict = json.loads(end_time)
39
+ attendees_list = json.loads(attendees)
40
+ result = client.create_schedule(
41
+ calendar_id=calendar_id, summary=summary,
42
+ start_time=start_time_dict, end_time=end_time_dict,
43
+ attendees=attendees_list, description=description,
44
+ all_day=all_day, repeat_type=repeat_type,
45
+ reminder_type=reminder_type,
46
+ user_token=user_token, user_id=user_id,
47
+ )
48
+ output_result(result, fields=["schedule_id"], title="Create Schedule Result")
49
+
50
+
51
+ @app.command("fetch-schedule")
52
+ def fetch_schedule(
53
+ calendar_id: str = typer.Argument(help="Calendar ID"),
54
+ schedule_id: str = typer.Argument(help="Schedule ID"),
55
+ user_token: str = typer.Option("", "--user-token", help="User token"),
56
+ user_id: str = typer.Option("", "--user-id", help="User ID"),
57
+ ):
58
+ client = get_client()
59
+ result = client.fetch_schedule(
60
+ calendar_id=calendar_id, schedule_id=schedule_id,
61
+ user_token=user_token, user_id=user_id,
62
+ )
63
+ output_result(result, fields=[
64
+ "schedule_id", "summary", "description", "all_day",
65
+ "start_time", "end_time", "creator", "rsvp_status",
66
+ ], title="Schedule Info")
67
+
68
+
69
+ @app.command("delete-schedule")
70
+ def delete_schedule(
71
+ calendar_id: str = typer.Argument(help="Calendar ID"),
72
+ schedule_id: str = typer.Argument(help="Schedule ID"),
73
+ user_token: str = typer.Option("", "--user-token", help="User token"),
74
+ user_id: str = typer.Option("", "--user-id", help="User ID"),
75
+ ):
76
+ client = get_client()
77
+ result = client.delete_schedule(
78
+ calendar_id=calendar_id, schedule_id=schedule_id,
79
+ user_token=user_token, user_id=user_id,
80
+ )
81
+ output_result(result, fields=["schedule_id"], title="Delete Schedule Result")
82
+
83
+
84
+ @app.command("list-schedules")
85
+ def fetch_schedule_list(
86
+ calendar_id: str = typer.Argument(help="Calendar ID"),
87
+ start_time: int = typer.Argument(help="Start time (unix timestamp)"),
88
+ end_time: int = typer.Argument(help="End time (unix timestamp)"),
89
+ user_token: str = typer.Option("", "--user-token", help="User token"),
90
+ user_id: str = typer.Option("", "--user-id", help="User ID"),
91
+ ):
92
+ client = get_client()
93
+ result = client.fetch_schedule_list(
94
+ calendar_id=calendar_id, start_time=start_time, end_time=end_time,
95
+ user_token=user_token, user_id=user_id,
96
+ )
97
+ if result.success and result.schedule_list:
98
+ output_list(result.schedule_list, columns=["Schedule ID", "Summary"], row_mapper=lambda s: [
99
+ getattr(s, "schedule_id", ""), getattr(s, "summary", ""),
100
+ ])
101
+ else:
102
+ output_result(result, title="Schedule List")
103
+
104
+
105
+ @app.command("attendees")
106
+ def fetch_schedule_attendees(
107
+ calendar_id: str = typer.Argument(help="Calendar ID"),
108
+ schedule_id: str = typer.Argument(help="Schedule ID"),
109
+ user_token: str = typer.Option("", "--user-token", help="User token"),
110
+ user_id: str = typer.Option("", "--user-id", help="User ID"),
111
+ page: int = typer.Option(1, "--page", "-p", help="Page number"),
112
+ page_size: int = typer.Option(500, "--size", "-s", help="Page size"),
113
+ ):
114
+ client = get_client()
115
+ result = client.fetch_schedule_attendees(
116
+ calendar_id=calendar_id, schedule_id=schedule_id,
117
+ user_token=user_token, user_id=user_id,
118
+ page=page, page_size=page_size,
119
+ )
120
+ output_result(result, fields=["total"], title="Schedule Attendees")
121
+
122
+
123
+ @app.command("add-attendees")
124
+ def add_schedule_attendees(
125
+ calendar_id: str = typer.Argument(help="Calendar ID"),
126
+ schedule_id: str = typer.Argument(help="Schedule ID"),
127
+ attendees: str = typer.Argument(help="Attendee staff IDs as JSON list: '[\"id1\",\"id2\"]'"),
128
+ user_token: str = typer.Option("", "--user-token", help="User token"),
129
+ user_id: str = typer.Option("", "--user-id", help="User ID"),
130
+ ):
131
+ client = get_client()
132
+ attendees_list = json.loads(attendees)
133
+ result = client.add_schedule_attendees(
134
+ calendar_id=calendar_id, schedule_id=schedule_id,
135
+ attendees=attendees_list, user_token=user_token, user_id=user_id,
136
+ )
137
+ output_result(result, fields=["schedule_id"], title="Add Attendees Result")
138
+
139
+
140
+ @app.command("delete-attendees")
141
+ def delete_schedule_attendees(
142
+ calendar_id: str = typer.Argument(help="Calendar ID"),
143
+ schedule_id: str = typer.Argument(help="Schedule ID"),
144
+ attendees: str = typer.Argument(help="Attendee staff IDs as JSON list"),
145
+ user_token: str = typer.Option("", "--user-token", help="User token"),
146
+ user_id: str = typer.Option("", "--user-id", help="User ID"),
147
+ ):
148
+ client = get_client()
149
+ attendees_list = json.loads(attendees)
150
+ result = client.delete_schedule_attendees(
151
+ calendar_id=calendar_id, schedule_id=schedule_id,
152
+ attendees=attendees_list, user_token=user_token, user_id=user_id,
153
+ )
154
+ output_result(result, fields=["schedule_id"], title="Delete Attendees Result")
@@ -0,0 +1,64 @@
1
+ import typer
2
+ import json
3
+ from rich import print as rprint
4
+
5
+ from lansenger_cli.utils import is_json_output, console
6
+
7
+ app = typer.Typer(help="Parse and verify callback events")
8
+
9
+
10
+ @app.command("parse-payload")
11
+ def parse_callback_payload(
12
+ encrypted_data: str = typer.Argument(help="Encrypted callback data"),
13
+ encoding_key: str = typer.Option("", "--encoding-key", help="Encoding key for decryption"),
14
+ verify_signature: bool = typer.Option(False, "--verify-sig", help="Verify signature"),
15
+ timestamp: str = typer.Option("", "--timestamp", help="Timestamp for signature verification"),
16
+ nonce: str = typer.Option("", "--nonce", help="Nonce for signature verification"),
17
+ signature: str = typer.Option("", "--signature", help="Signature to verify"),
18
+ ):
19
+ from lansenger_sdk import parse_callback_payload
20
+ events = parse_callback_payload(
21
+ encrypted_data,
22
+ encoding_key=encoding_key,
23
+ verify_signature=verify_signature,
24
+ timestamp=timestamp,
25
+ nonce=nonce,
26
+ signature=signature,
27
+ )
28
+ if is_json_output():
29
+ rprint(json.dumps([e.to_dict() if hasattr(e, "to_dict") else str(e) for e in events], indent=2, ensure_ascii=False))
30
+ return
31
+ for event in events:
32
+ rprint(f"[bold cyan]Event #{event.event_id}[/bold cyan] — {event.event_type} ({event.category})")
33
+ if hasattr(event, "data") and hasattr(event.data, "to_dict"):
34
+ rprint(json.dumps(event.data.to_dict(), indent=2, ensure_ascii=False))
35
+ else:
36
+ rprint(event.data)
37
+
38
+
39
+ @app.command("verify-signature")
40
+ def verify_callback_signature(
41
+ timestamp: str = typer.Argument(help="Timestamp"),
42
+ nonce: str = typer.Argument(help="Nonce"),
43
+ signature: str = typer.Argument(help="Signature"),
44
+ encoding_key: str = typer.Argument(help="Encoding key"),
45
+ ):
46
+ from lansenger_sdk import verify_callback_signature
47
+ valid = verify_callback_signature(timestamp, nonce, signature, encoding_key)
48
+ rprint(f"Signature valid: {valid}")
49
+
50
+
51
+ @app.command("event-types")
52
+ def get_callback_event_types():
53
+ from lansenger_sdk import get_callback_event_types
54
+ types = get_callback_event_types()
55
+ if is_json_output():
56
+ rprint(json.dumps(types, indent=2, ensure_ascii=False))
57
+ return
58
+ from rich.table import Table
59
+ table = Table(title="Callback Event Types", show_header=True, header_style="bold cyan")
60
+ table.add_column("Event Type")
61
+ table.add_column("Category")
62
+ for event_type, category in types.items():
63
+ table.add_row(event_type, category)
64
+ console.print(table)
@@ -0,0 +1,54 @@
1
+ import typer
2
+ from rich import print as rprint
3
+
4
+ from lansenger_sdk import CredentialStore
5
+
6
+ from lansenger_cli.utils import get_store, output_result, is_json_output
7
+
8
+ app = typer.Typer(help="Manage CLI configuration (credentials, tokens)")
9
+
10
+ VALID_KEYS = ["app_id", "app_secret", "api_gateway_url", "passport_url"]
11
+
12
+
13
+ @app.command("set")
14
+ def config_set(
15
+ key: str = typer.Argument(help=f"Config key: {', '.join(VALID_KEYS)}"),
16
+ value: str = typer.Argument(help="Config value"),
17
+ ):
18
+ if key not in VALID_KEYS:
19
+ rprint(f"[red]Error:[/red] Invalid key '{key}'. Valid keys: {', '.join(VALID_KEYS)}")
20
+ raise typer.Exit(1)
21
+ store = get_store()
22
+ creds = store.load_credentials()
23
+ creds[key] = value
24
+ store.save_credentials(
25
+ app_id=creds.get("app_id", ""),
26
+ app_secret=creds.get("app_secret", ""),
27
+ api_gateway_url=creds.get("api_gateway_url", ""),
28
+ passport_url=creds.get("passport_url", ""),
29
+ )
30
+ rprint(f"[green]Set[/green] {key} = {value}")
31
+
32
+
33
+ @app.command("show")
34
+ def config_show():
35
+ store = get_store()
36
+ if is_json_output():
37
+ rprint(store.load())
38
+ return
39
+ creds = store.load_credentials()
40
+ has = store.has_credentials()
41
+ full = store.has_full_config()
42
+ rprint(f"Credentials configured: {has}")
43
+ rprint(f"Full config available: {full}")
44
+ rprint(f"Store path: {store.path}")
45
+ for k, v in creds.items():
46
+ display = v if k in ("api_gateway_url", "passport_url") else ("***" if v else "(empty)")
47
+ rprint(f" {k}: {display}")
48
+
49
+
50
+ @app.command("clear")
51
+ def config_clear():
52
+ store = get_store()
53
+ store.clear()
54
+ rprint("[green]Cleared[/green] all stored configuration.")