aidee-pro 1.0.0__py3-none-any.whl

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.
__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """CLI-Anything harness for AIDEE — AI recording, transcription, and summarization API."""
2
+
3
+ __version__ = "1.0.0"
aidee_cli.py ADDED
@@ -0,0 +1,404 @@
1
+ """AIDEE CLI — Main entry point."""
2
+
3
+ import os
4
+ import click
5
+
6
+ from __init__ import __version__
7
+
8
+ from core.session import get_api_key, get_base_url, get_token
9
+ from core.recording import (
10
+ create as recording_create,
11
+ update as recording_update,
12
+ list_recordings,
13
+ get_summary_templates,
14
+ update_summary_template,
15
+ merged_summary as recording_merged_summary,
16
+ )
17
+ from core.summary import (
18
+ list_by_recording as summary_list,
19
+ get as summary_get,
20
+ )
21
+ from core.group import (
22
+ create as group_create,
23
+ delete as group_delete,
24
+ list_all as group_list,
25
+ )
26
+ from utils.output import print_result as _print_result
27
+ from utils.aidee_backend import upload_file as _upload_file
28
+
29
+
30
+ def _ctx_base(ctx):
31
+ return ctx.obj.get("base_url") or get_base_url()
32
+
33
+
34
+ def _ctx_token(ctx):
35
+ return ctx.obj.get("token") or get_token()
36
+
37
+
38
+ def _output(result, json_mode: bool):
39
+ _print_result(result, json_mode)
40
+
41
+
42
+ @click.group()
43
+ @click.version_option(version=__version__, prog_name="aidee-pro")
44
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON")
45
+ @click.option("--base-url", envvar="AIDEE_BASE_URL", help="AIDEE API base URL")
46
+ @click.option("--token", envvar="AIDEE_TOKEN", help="Auth token")
47
+ @click.option("--api-key", envvar="AIDEE_API_KEY", help="AIDEE API key (sent as X-Api-Key)")
48
+ @click.pass_context
49
+ def cli(ctx, json_mode, base_url, token, api_key):
50
+ """AIDEE CLI — AI recording, transcription, and summarization API client."""
51
+ ctx.ensure_object(dict)
52
+ ctx.obj["json_mode"] = json_mode
53
+ ctx.obj["base_url"] = base_url
54
+ ctx.obj["token"] = token
55
+ ctx.obj["api_key"] = api_key or get_api_key()
56
+ if ctx.obj["api_key"]:
57
+ os.environ["AIDEE_API_KEY"] = ctx.obj["api_key"]
58
+
59
+
60
+ # --- recording ---
61
+ @cli.group("recording")
62
+ def recording_group():
63
+ """Recording management."""
64
+ pass
65
+
66
+
67
+ @recording_group.command("create")
68
+ @click.option("--title", required=True)
69
+ @click.option("--description", required=True)
70
+ @click.option("--file-url")
71
+ @click.option("--file-name", required=True)
72
+ @click.option("--content-type", required=True)
73
+ @click.option("--file-created-at", required=True)
74
+ @click.option("--duration", type=int)
75
+ @click.option("--summary-template-id")
76
+ @click.option("--group-code")
77
+ @click.option("--device-id")
78
+ @click.option("--speaker-count", type=int)
79
+ @click.option("--latitude", type=float)
80
+ @click.option("--longitude", type=float)
81
+ @click.option("--address")
82
+ @click.option("--address-brief")
83
+ @click.option("--json", "json_mode", is_flag=True)
84
+ @click.pass_context
85
+ def recording_create_cmd(ctx, title, description, file_url, file_name, duration,
86
+ content_type, summary_template_id, group_code, device_id,
87
+ speaker_count, file_created_at, latitude, longitude,
88
+ address, address_brief, json_mode):
89
+ """创建录音记录。"""
90
+ base = _ctx_base(ctx)
91
+ tok = _ctx_token(ctx)
92
+ kwargs = {k: v for k, v in {
93
+ "description": description,
94
+ "fileUrl": file_url,
95
+ "fileName": file_name,
96
+ "duration": duration,
97
+ "recordingContentType": content_type,
98
+ "summaryTemplatesId": summary_template_id,
99
+ "recordingsGroupCode": group_code,
100
+ "deviceId": device_id,
101
+ "speakerCount": speaker_count,
102
+ "fileCreatedAt": file_created_at,
103
+ "latitude": latitude,
104
+ "longitude": longitude,
105
+ "address": address,
106
+ "addressBrief": address_brief,
107
+ }.items() if v is not None}
108
+ r = recording_create(base, tok, title=title, **kwargs)
109
+ _output(r, json_mode or ctx.obj.get("json_mode"))
110
+
111
+
112
+ @recording_group.command("update")
113
+ @click.argument("code")
114
+ @click.option("--title")
115
+ @click.option("--description")
116
+ @click.option("--file-url")
117
+ @click.option("--file-name")
118
+ @click.option("--duration", type=int, help="录音时长(毫秒)")
119
+ @click.option("--content-type")
120
+ @click.option("--group-code")
121
+ @click.option("--ai-summary")
122
+ @click.option("--status")
123
+ @click.option("--json", "json_mode", is_flag=True)
124
+ @click.pass_context
125
+ def recording_update_cmd(ctx, code, title, description, file_url, file_name, duration,
126
+ content_type, group_code, ai_summary, status, json_mode):
127
+ """编辑录音记录。"""
128
+ kwargs = {k: v for k, v in {
129
+ "title": title,
130
+ "description": description,
131
+ "fileUrl": file_url,
132
+ "fileName": file_name,
133
+ "duration": duration,
134
+ "recordingContentType": content_type,
135
+ "recordingsGroupCode": group_code,
136
+ "aiSummary": ai_summary,
137
+ "status": status,
138
+ }.items() if v is not None}
139
+ r = recording_update(_ctx_base(ctx), _ctx_token(ctx), code, **kwargs)
140
+ _output(r, json_mode or ctx.obj.get("json_mode"))
141
+
142
+
143
+ @recording_group.command("list")
144
+ @click.option("--page", default=1, type=int, help="页码,从 1 开始")
145
+ @click.option("--limit", default=10, type=int, help="每页条数")
146
+ @click.option("--title", help="录音标题,模糊匹配")
147
+ @click.option("--keyword", help="关键词,匹配标题/简介/description")
148
+ @click.option("--status", help="录音状态,如 COMPLETED")
149
+ @click.option("--group-code", help="录音组 code")
150
+ @click.option("--device-id", help="设备 ID")
151
+ @click.option("--entry-type", help="条目类型:SINGLE / MERGED_SUMMARY")
152
+ @click.option("--content-type", help="内容类型:ENVIRONMENT / IMPORTED / CALL / MERGED_SUMMARY")
153
+ @click.option("--start-time", help="开始时间,如 2026-06-01T00:00:00")
154
+ @click.option("--end-time", help="结束时间,如 2026-06-10T23:59:59")
155
+ @click.option("--order-by", help="排序字段,默认 file_created_at")
156
+ @click.option("--order-direction", help="排序方向:asc / desc")
157
+ @click.option("--sort-type", help="RECORDING_TIME / UPDATED_TIME")
158
+ @click.option("--json", "json_mode", is_flag=True)
159
+ @click.pass_context
160
+ def recording_list_cmd(ctx, page, limit, title, keyword, status, group_code, device_id,
161
+ entry_type, content_type, start_time, end_time,
162
+ order_by, order_direction, sort_type, json_mode):
163
+ """录音列表查询。"""
164
+ kwargs = {k: v for k, v in {
165
+ "title": title,
166
+ "keyword": keyword,
167
+ "status": status,
168
+ "recordingsGroupCode": group_code,
169
+ "deviceId": device_id,
170
+ "recordingEntryType": entry_type,
171
+ "recordingContentType": content_type,
172
+ "startTime": start_time,
173
+ "endTime": end_time,
174
+ "orderBy": order_by,
175
+ "orderDirection": order_direction,
176
+ "sortType": sort_type,
177
+ }.items() if v is not None}
178
+ r = list_recordings(_ctx_base(ctx), _ctx_token(ctx), page=page, limit=limit, **kwargs)
179
+ _output(r, json_mode or ctx.obj.get("json_mode"))
180
+
181
+
182
+ @recording_group.command("summary-templates")
183
+ @click.option("--sort-scheme", help="排序方案:DEFAULT / INDUSTRY")
184
+ @click.option("--json", "json_mode", is_flag=True)
185
+ @click.pass_context
186
+ def recording_templates_cmd(ctx, sort_scheme, json_mode):
187
+ """总结模板列表查询。"""
188
+ kwargs = {"sortScheme": sort_scheme} if sort_scheme else {}
189
+ r = get_summary_templates(_ctx_base(ctx), _ctx_token(ctx), **kwargs)
190
+ _output(r, json_mode or ctx.obj.get("json_mode"))
191
+
192
+
193
+ @recording_group.command("update-summary-template")
194
+ @click.option("--recording-code", required=True, help="目标录音 code")
195
+ @click.option("--summary-template-id", help="总结模板 ID,不传则默认 zhinengzongjie-neice")
196
+ @click.option("--summary-no", type=int, help="指定则重新生成该次历史总结,不传则新建")
197
+ @click.option("--template-selection-mode", help="MANUAL / AUTO_TEMPLATE")
198
+ @click.option("--json", "json_mode", is_flag=True)
199
+ @click.pass_context
200
+ def recording_update_summary_template_cmd(ctx, recording_code, summary_template_id,
201
+ summary_no, template_selection_mode, json_mode):
202
+ """开启 AI 总结,返回预估耗时。"""
203
+ params = {k: v for k, v in {
204
+ "recordingCode": recording_code,
205
+ "summaryTemplatesId": summary_template_id,
206
+ "summaryNo": summary_no,
207
+ "templateSelectionMode": template_selection_mode,
208
+ }.items() if v is not None}
209
+ r = update_summary_template(_ctx_base(ctx), _ctx_token(ctx), **params)
210
+ _output(r, json_mode or ctx.obj.get("json_mode"))
211
+
212
+
213
+ @recording_group.command("upload")
214
+ @click.argument("file_path")
215
+ @click.option("--recording-code", required=True, help="录音 code")
216
+ @click.option("--json", "json_mode", is_flag=True)
217
+ @click.pass_context
218
+ def recording_upload_cmd(ctx, file_path, recording_code, json_mode):
219
+ """上传音频文件到指定录音。"""
220
+ r = _upload_file(
221
+ path=file_path,
222
+ base_url=_ctx_base(ctx),
223
+ recording_code=recording_code,
224
+ token=_ctx_token(ctx),
225
+ api_key=ctx.obj.get("api_key"),
226
+ )
227
+ _output(r, json_mode or ctx.obj.get("json_mode"))
228
+
229
+
230
+ @recording_group.command("merged-summary")
231
+ @click.option("--recording-codes", required=True, help="来源录音 code,多个用逗号分隔,如 rec_a,rec_b")
232
+ @click.option("--merged-recording-code", help="已有合并录音 code,传则追加来源,不传则新建")
233
+ @click.option("--summary-template-id", help="总结模板 ID,新建时不传默认 smart")
234
+ @click.option("--json", "json_mode", is_flag=True)
235
+ @click.pass_context
236
+ def recording_merged_summary_cmd(ctx, recording_codes, merged_recording_code,
237
+ summary_template_id, json_mode):
238
+ """合并多条录音总结为一份汇总总结。"""
239
+ codes = [c.strip() for c in recording_codes.split(",")]
240
+ kwargs = {k: v for k, v in {
241
+ "recordingCodes": codes,
242
+ "mergedRecordingCode": merged_recording_code,
243
+ "summaryTemplatesId": summary_template_id,
244
+ }.items() if v is not None}
245
+ r = recording_merged_summary(_ctx_base(ctx), _ctx_token(ctx), **kwargs)
246
+ _output(r, json_mode or ctx.obj.get("json_mode"))
247
+
248
+
249
+ # --- summary ---
250
+ @cli.group("summary")
251
+ def summary_group():
252
+ """Recording summary management."""
253
+ pass
254
+
255
+
256
+ @summary_group.command("list")
257
+ @click.argument("recording_code")
258
+ @click.option("--json", "json_mode", is_flag=True)
259
+ @click.pass_context
260
+ def summary_list_cmd(ctx, recording_code, json_mode):
261
+ """录音总结列表查询。"""
262
+ r = summary_list(_ctx_base(ctx), _ctx_token(ctx), recording_code)
263
+ _output(r, json_mode or ctx.obj.get("json_mode"))
264
+
265
+
266
+ @summary_group.command("get")
267
+ @click.argument("id", type=int)
268
+ @click.option("--json", "json_mode", is_flag=True)
269
+ @click.pass_context
270
+ def summary_get_cmd(ctx, id, json_mode):
271
+ """总结详情查询。"""
272
+ r = summary_get(_ctx_base(ctx), _ctx_token(ctx), id)
273
+ _output(r, json_mode or ctx.obj.get("json_mode"))
274
+
275
+
276
+ # --- group ---
277
+ @cli.group("group")
278
+ def group_group():
279
+ """Recording group management."""
280
+ pass
281
+
282
+
283
+ @group_group.command("list")
284
+ @click.option("--json", "json_mode", is_flag=True)
285
+ @click.pass_context
286
+ def group_list_cmd(ctx, json_mode):
287
+ """分组列表查询。"""
288
+ r = group_list(_ctx_base(ctx), _ctx_token(ctx))
289
+ _output(r, json_mode or ctx.obj.get("json_mode"))
290
+
291
+
292
+ @group_group.command("create")
293
+ @click.option("--name", "group_name", required=True, help="分组名称,同一用户下唯一,最长 64 字符")
294
+ @click.option("--description", help="分组描述,最长 500 字符")
295
+ @click.option("--json", "json_mode", is_flag=True)
296
+ @click.pass_context
297
+ def group_create_cmd(ctx, group_name, description, json_mode):
298
+ """创建录音分组。"""
299
+ kwargs = {"description": description} if description else {}
300
+ r = group_create(_ctx_base(ctx), _ctx_token(ctx), group_name=group_name, **kwargs)
301
+ _output(r, json_mode or ctx.obj.get("json_mode"))
302
+
303
+
304
+ @group_group.command("delete")
305
+ @click.argument("code")
306
+ @click.option("--json", "json_mode", is_flag=True)
307
+ @click.pass_context
308
+ def group_delete_cmd(ctx, code, json_mode):
309
+ """删除录音分组。"""
310
+ r = group_delete(_ctx_base(ctx), _ctx_token(ctx), code)
311
+ _output(r, json_mode or ctx.obj.get("json_mode"))
312
+
313
+
314
+ # --- transcribe ---
315
+ @cli.command("transcribe")
316
+ @click.option("--title", required=True, help="录音标题")
317
+ @click.option("--description", required=True, help="录音描述")
318
+ @click.option("--file", "file_path", default=None, help="本地音频文件路径")
319
+ @click.option("--url", "file_url", default=None, help="录音网址链接")
320
+ @click.option("--file-name", required=True, help="原始文件名")
321
+ @click.option("--file-created-at", required=True, help="文件创建时间,格式 yyyy-MM-dd HH:mm:ss")
322
+ @click.option("--content-type", default="IMPORTED", show_default=True,
323
+ help="内容类型:ENVIRONMENT / IMPORTED / CALL / MERGED_SUMMARY")
324
+ @click.option("--template-id", required=True, help="AI 总结模板 ID(可通过 recording summary-templates 获取)")
325
+ @click.option("--json", "json_mode", is_flag=True)
326
+ @click.pass_context
327
+ def transcribe_cmd(ctx, title, description, file_path, file_url, file_name,
328
+ file_created_at, content_type, template_id, json_mode):
329
+ """一键完成:创建录音 → 上传/绑定文件 → 触发 AI 总结。
330
+
331
+ \b
332
+ 两种模式:
333
+ --file 本地文件:上传后自动回写文件 URL
334
+ --url 网络链接:直接绑定到录音记录
335
+ """
336
+ if not file_path and not file_url:
337
+ raise click.UsageError("必须提供 --file(本地文件)或 --url(录音链接)之一")
338
+ if file_path and file_url:
339
+ raise click.UsageError("--file 和 --url 不能同时使用")
340
+
341
+ base = _ctx_base(ctx)
342
+ tok = _ctx_token(ctx)
343
+ api_key = ctx.obj.get("api_key")
344
+ jm = json_mode or ctx.obj.get("json_mode")
345
+
346
+ create_kwargs = {
347
+ "description": description,
348
+ "fileName": file_name,
349
+ "recordingContentType": content_type,
350
+ "fileCreatedAt": file_created_at,
351
+ "summaryTemplatesId": template_id,
352
+ }
353
+ if file_url:
354
+ create_kwargs["fileUrl"] = file_url
355
+
356
+ click.echo("▶ 创建录音记录...")
357
+ result = recording_create(base, tok, title=title, **create_kwargs)
358
+ code = (result.get("data") or {}).get("code")
359
+ if not code:
360
+ _output(result, jm)
361
+ return
362
+
363
+ click.echo(f" 录音 code: {code}")
364
+
365
+ if file_path:
366
+ click.echo(f"▶ 上传音频文件 {file_path}...")
367
+ upload_result = _upload_file(
368
+ path=file_path,
369
+ base_url=base,
370
+ recording_code=code,
371
+ token=tok,
372
+ api_key=api_key,
373
+ )
374
+ uploaded_url = (upload_result.get("data") or {}).get("url")
375
+ if not uploaded_url:
376
+ click.echo("✗ 上传失败", err=True)
377
+ _output(upload_result, jm)
378
+ return
379
+
380
+ click.echo(f" 文件 URL: {uploaded_url}")
381
+ click.echo("▶ 回写文件 URL...")
382
+ recording_update(base, tok, code, fileUrl=uploaded_url)
383
+
384
+ click.echo("▶ 触发 AI 总结...")
385
+ summary_result = update_summary_template(
386
+ base, tok,
387
+ recordingCode=code,
388
+ summaryTemplatesId=template_id,
389
+ )
390
+ estimated = (summary_result.get("data") or {}).get("totalEstimatedMinutes", "?")
391
+ click.echo(f" 预计耗时:{estimated} 分钟")
392
+ click.echo(f"✓ 完成!录音 code: {code}")
393
+ click.echo(f" 查询总结:aidee --json summary list {code}")
394
+
395
+ if jm:
396
+ _output({"recordingCode": code, "estimatedMinutes": estimated}, jm)
397
+
398
+
399
+ def main():
400
+ cli()
401
+
402
+
403
+ if __name__ == "__main__":
404
+ main()
@@ -0,0 +1,6 @@
1
+ Metadata-Version: 2.4
2
+ Name: aidee-pro
3
+ Version: 1.0.0
4
+ Requires-Python: >=3.10
5
+ Requires-Dist: click>=8.0
6
+ Requires-Dist: requests>=2.28
@@ -0,0 +1,31 @@
1
+ __init__.py,sha256=6nIuaDicO0jPsc45Fma_eT68fmcQYQ-rIgVt1bfJ5GM,116
2
+ aidee_cli.py,sha256=EVfTpJ-HIL_aFvMiyij8Y31TTZdW4Ulcakn3iRbLlNA,15364
3
+ core/__init__.py,sha256=_ua0pFRZnuDRTCMkzYW4G7103H9tfrvK_HYUe8hkmks,34
4
+ core/config.py,sha256=FnCs7i5c4sX1o9paEWs3xUeCie87-QKCSnAYLw9eMLc,1345
5
+ core/device.py,sha256=cc0WclSWtIesOJMCcif5FgKBdcUqS6jcRdn84BL6T8w,2071
6
+ core/expo.py,sha256=W0xlnJ6B9OPdsqDt60Voeknd5M8sxU8LJKEWnlahmC4,663
7
+ core/feedback.py,sha256=7p0SMQ7_H6l0eiQ2SwHSYf4EUAMIxm8v8wECqv6lkYM,1025
8
+ core/firmware.py,sha256=y40VoY2QE6rzLk5Jv5Jhg4DGpoiQUhSWDLWiEKVRoWs,579
9
+ core/group.py,sha256=g-ZYgepx__JMZmQ2CityZMKWnLiEThKOwfe8bEfxvH4,1378
10
+ core/industry.py,sha256=nJh5VC8ygbCK2LW7UnJLDYGt2o73GfP60xH_50PnP_o,287
11
+ core/membership.py,sha256=lHfV8akSqhAvZln-24rJZUczaGjqnSdEXgPZ-iKtwbo,1316
12
+ core/position.py,sha256=ZZYmmkCQnnio-M4xM5prXdAfpFsjb5AcqUGaAzO1wT8,285
13
+ core/recording.py,sha256=mHmlSiT5EoJ7UXyjk8HLEUAF1LMfWa3DxCdW1t6_ApA,5232
14
+ core/redemption.py,sha256=ZldF0ZQOZam2mqGX6DjsYtA7U18qvAAhK7m9x5bC-o0,899
15
+ core/session.py,sha256=mj1Uf8vBbfGyA3JFBhvrTvrHwQ1QKhV6pRg4zfcdzvI,2505
16
+ core/summary.py,sha256=-fzup6Bb4f6zcPtwaG_a6ByfxrinRHLNt0jmppfbhTc,1450
17
+ core/template.py,sha256=S6UQZ5q-Lx7EQVe3d2DFFcm_21xw3Ms0ViR_gZDsb6M,2000
18
+ core/template_category.py,sha256=E3VqqftrbcK85Kcywvq_faf-mg4PFSvOe7zkqx0qEJo,516
19
+ core/thirdparty.py,sha256=NaYP_RdNkzwPC1BgiyQdh2fo6YZH1wnK_y-ly1ZB8Gw,360
20
+ core/user.py,sha256=6nXdrEZWk_JRCL_AO-C4uew_m0HlgN9nHWiOuswdipM,1270
21
+ core/websocket.py,sha256=1i0ZJdcuC3Z0YguHIgM1US4EV26QHW2w73bC98Da0cI,1583
22
+ core/word_library.py,sha256=h630ehTGgOJ0XJU0QLcwVBVbUsFe_u1xCf205p0k2C8,1499
23
+ utils/__init__.py,sha256=NdoXaJF-utM1KgmGP3glB4E2txP3J4JCz9aejMez0j4,31
24
+ utils/aidee_backend.py,sha256=CbHMOuhN9iRzkbyHiyfRrFBR1AVe0rJkvzegwvTUt00,4271
25
+ utils/output.py,sha256=40zxw_jHSoaNW-N3B7KzMcSdbyvC0wbBmqkOjQV_Uaw,1202
26
+ utils/repl_skin.py,sha256=bU5bXCBBMQTxKxQXixMQM2D418J29qZtItWPHrbCn3U,1862
27
+ aidee_pro-1.0.0.dist-info/METADATA,sha256=2qUMP4Iy4FgryQZL-owXMHCpxroQ91aIjFYkvbD_X8c,133
28
+ aidee_pro-1.0.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
29
+ aidee_pro-1.0.0.dist-info/entry_points.txt,sha256=cvdWu9mkJDkow4RLpf0YgJHPBss2t3wrSeBpx_euE8I,45
30
+ aidee_pro-1.0.0.dist-info/top_level.txt,sha256=dNldcHeibdUilXvhwjBSj2gKTxO25Qn4kZP8tuiOej8,30
31
+ aidee_pro-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ aidee-pro = aidee_cli:main
@@ -0,0 +1,4 @@
1
+ __init__
2
+ aidee_cli
3
+ core
4
+ utils
core/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """Core modules for AIDEE CLI."""
core/config.py ADDED
@@ -0,0 +1,50 @@
1
+ """Config module — set base_url, token/api key, show status."""
2
+
3
+ from core.session import (
4
+ load_session,
5
+ save_session,
6
+ clear_session,
7
+ )
8
+
9
+
10
+ def set_base_url(url: str) -> dict:
11
+ """Set base URL and save to session."""
12
+ save_session(base_url=url)
13
+ return {"base_url": url, "status": "ok"}
14
+
15
+
16
+ def set_token(token: str) -> dict:
17
+ """Set auth token and save to session."""
18
+ save_session(token=token)
19
+ return {"status": "ok", "message": "Token saved"}
20
+
21
+
22
+ def set_api_key(api_key: str) -> dict:
23
+ """Set API key and save to session."""
24
+ save_session(api_key=api_key)
25
+ return {"status": "ok", "message": "API key saved"}
26
+
27
+
28
+ def _mask_secret(value: str | None) -> str:
29
+ if not value:
30
+ return "(not set)"
31
+ return "***" + value[-4:] if len(value) > 4 else "***"
32
+
33
+
34
+ def show() -> dict:
35
+ """Show current config (masked secrets)."""
36
+ from core.session import SESSION_FILE
37
+ sess = load_session()
38
+ base_url = sess.get("base_url") or "https://api.aidee.me/aidee-server (default)"
39
+ return {
40
+ "base_url": base_url,
41
+ "token": _mask_secret(sess.get("token")),
42
+ "api_key": _mask_secret(sess.get("api_key")),
43
+ "session_file": str(SESSION_FILE),
44
+ }
45
+
46
+
47
+ def clear() -> dict:
48
+ """Clear session."""
49
+ clear_session()
50
+ return {"status": "ok", "message": "Session cleared"}
core/device.py ADDED
@@ -0,0 +1,55 @@
1
+ """Device module — bind, unbind, list, primary."""
2
+
3
+ from typing import Any, Optional
4
+
5
+ from utils.aidee_backend import api_request
6
+
7
+
8
+ def bind(base_url: str, token: Optional[str], **kwargs) -> dict[str, Any]:
9
+ """Bind device."""
10
+ return api_request("POST", "/devices/bind", base_url, token, json_body=kwargs)
11
+
12
+
13
+ def unbind(base_url: str, token: Optional[str], device_id: str) -> dict[str, Any]:
14
+ """Unbind device."""
15
+ return api_request("DELETE", f"/devices/unbind/{device_id}", base_url, token)
16
+
17
+
18
+ def list_devices(base_url: str, token: Optional[str]) -> dict[str, Any]:
19
+ """List user devices."""
20
+ return api_request("GET", "/devices/user/devices", base_url, token)
21
+
22
+
23
+ def get_primary(base_url: str, token: Optional[str]) -> dict[str, Any]:
24
+ """Get primary device."""
25
+ return api_request("GET", "/devices/user/primary", base_url, token)
26
+
27
+
28
+ def set_primary(base_url: str, token: Optional[str], device_id: str) -> dict[str, Any]:
29
+ """Set primary device."""
30
+ return api_request("PUT", "/devices/user/primary", base_url, token, json_body={"deviceId": device_id})
31
+
32
+
33
+ def get(base_url: str, token: Optional[str], identifier: str) -> dict[str, Any]:
34
+ """Get device by identifier."""
35
+ return api_request("GET", f"/devices/{identifier}", base_url, token)
36
+
37
+
38
+ def count(base_url: str, token: Optional[str], **params) -> dict[str, Any]:
39
+ """Get device count."""
40
+ return api_request("GET", "/devices/count", base_url, token, params=params)
41
+
42
+
43
+ def update(base_url: str, token: Optional[str], device_id: str, **kwargs) -> dict[str, Any]:
44
+ """Update device."""
45
+ return api_request("PUT", f"/devices/{device_id}", base_url, token, json_body=kwargs)
46
+
47
+
48
+ def usage_logs(base_url: str, token: Optional[str], **params) -> dict[str, Any]:
49
+ """Get device usage logs."""
50
+ return api_request("GET", "/devices/usage-logs", base_url, token, params=params)
51
+
52
+
53
+ def check_bound(base_url: str, token: Optional[str], sn: str) -> dict[str, Any]:
54
+ """Check if device is bound."""
55
+ return api_request("GET", f"/devices/check-bound/{sn}", base_url, token)
core/expo.py ADDED
@@ -0,0 +1,20 @@
1
+ """Expo updates module."""
2
+
3
+ from typing import Any, Optional
4
+
5
+ from utils.aidee_backend import api_request
6
+
7
+
8
+ def check_update(base_url: str, token: Optional[str], **params) -> dict[str, Any]:
9
+ """Check update."""
10
+ return api_request("GET", "/expo/check-update", base_url, token, params=params)
11
+
12
+
13
+ def manifest(base_url: str, token: Optional[str], **params) -> dict[str, Any]:
14
+ """Get manifest."""
15
+ return api_request("GET", "/expo/manifest", base_url, token, params=params)
16
+
17
+
18
+ def asset(base_url: str, token: Optional[str], hash: str) -> dict[str, Any]:
19
+ """Get asset by hash."""
20
+ return api_request("GET", f"/expo/assets/{hash}", base_url, token)
core/feedback.py ADDED
@@ -0,0 +1,30 @@
1
+ """Feedback usage record module."""
2
+
3
+ from typing import Any, Optional
4
+
5
+ from utils.aidee_backend import api_request
6
+
7
+
8
+ def create(base_url: str, token: Optional[str], **kwargs) -> dict[str, Any]:
9
+ """Create feedback."""
10
+ return api_request("POST", "/feedback", base_url, token, json_body=kwargs)
11
+
12
+
13
+ def get(base_url: str, token: Optional[str], id: str) -> dict[str, Any]:
14
+ """Get feedback by ID."""
15
+ return api_request("GET", f"/feedback/{id}", base_url, token)
16
+
17
+
18
+ def update(base_url: str, token: Optional[str], id: str, **kwargs) -> dict[str, Any]:
19
+ """Update feedback."""
20
+ return api_request("PUT", f"/feedback/{id}", base_url, token, json_body=kwargs)
21
+
22
+
23
+ def list_all(base_url: str, token: Optional[str], **params) -> dict[str, Any]:
24
+ """List feedback."""
25
+ return api_request("GET", "/feedback", base_url, token, params=params)
26
+
27
+
28
+ def delete(base_url: str, token: Optional[str], id: str) -> dict[str, Any]:
29
+ """Delete feedback."""
30
+ return api_request("DELETE", f"/feedback/{id}", base_url, token)
core/firmware.py ADDED
@@ -0,0 +1,15 @@
1
+ """Firmware module."""
2
+
3
+ from typing import Any, Optional
4
+
5
+ from utils.aidee_backend import api_request
6
+
7
+
8
+ def upgrade_info(base_url: str, token: Optional[str], **params) -> dict[str, Any]:
9
+ """Get user devices upgrade info."""
10
+ return api_request("GET", "/firmware/user/devices/upgrade-info", base_url, token, params=params)
11
+
12
+
13
+ def update_upgrade_record(base_url: str, token: Optional[str], record_id: str, **kwargs) -> dict[str, Any]:
14
+ """Update upgrade record."""
15
+ return api_request("PUT", f"/firmware/upgrade-records/{record_id}", base_url, token, json_body=kwargs)