hap-cli 0.6.6__tar.gz → 0.6.8__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.
- {hap_cli-0.6.6/hap_cli.egg-info → hap_cli-0.6.8}/PKG-INFO +17 -10
- hap_cli-0.6.6/PKG-INFO → hap_cli-0.6.8/hap_cli/README.md +14 -35
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/README_CN.md +14 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/__init__.py +1 -1
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/commands/app_cmd.py +51 -5
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/commands/auth_cmd.py +80 -15
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/commands/calendar_cmd.py +3 -3
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/commands/chat_cmd.py +18 -9
- hap_cli-0.6.8/hap_cli/commands/config_cmd.py +293 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/commands/contact_cmd.py +38 -24
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/commands/department_cmd.py +29 -26
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/commands/instance_cmd.py +3 -3
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/commands/node_cmd.py +3 -3
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/commands/optionset_cmd.py +3 -3
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/commands/page_cmd.py +3 -3
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/commands/post_cmd.py +3 -3
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/commands/record_cmd.py +3 -3
- hap_cli-0.6.8/hap_cli/commands/region_cmd.py +19 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/commands/role_cmd.py +3 -3
- hap_cli-0.6.8/hap_cli/commands/v3_registry.py +244 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/commands/workflow_cmd.py +3 -3
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/commands/worksheet_cmd.py +3 -3
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/core/auth.py +267 -36
- hap_cli-0.6.8/hap_cli/core/chat.py +1186 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/core/contact.py +40 -22
- hap_cli-0.6.8/hap_cli/core/global_meta.py +151 -0
- hap_cli-0.6.8/hap_cli/core/logger.py +180 -0
- hap_cli-0.6.8/hap_cli/core/session.py +886 -0
- hap_cli-0.6.8/hap_cli/core/token_crypto.py +80 -0
- hap_cli-0.6.8/hap_cli/core/v3/__init__.py +19 -0
- hap_cli-0.6.8/hap_cli/core/v3/dispatcher.py +410 -0
- hap_cli-0.6.8/hap_cli/core/v3/render.py +116 -0
- hap_cli-0.6.8/hap_cli/core/v3/schema.py +94 -0
- hap_cli-0.6.8/hap_cli/hap_cli.py +174 -0
- hap_cli-0.6.8/hap_cli/i18n.py +245 -0
- hap_cli-0.6.8/hap_cli/locale/__init__.py +1 -0
- hap_cli-0.6.8/hap_cli/locale/messages.json +3280 -0
- hap_cli-0.6.8/hap_cli/locale/messages.schema.json +54 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/tests/conftest.py +20 -0
- hap_cli-0.6.8/hap_cli/tests/test_auth_prerelease.py +410 -0
- hap_cli-0.6.8/hap_cli/tests/test_config_cmd.py +191 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/tests/test_core.py +778 -155
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/tests/test_full_e2e.py +4 -2
- hap_cli-0.6.8/hap_cli/tests/test_global_meta.py +309 -0
- hap_cli-0.6.8/hap_cli/tests/test_i18n.py +306 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/tests/test_integration_approval.py +1 -1
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/tests/test_integration_social.py +14 -7
- hap_cli-0.6.8/hap_cli/tests/test_integration_v3.py +177 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/tests/test_org_id_cli.py +27 -15
- hap_cli-0.6.8/hap_cli/tests/test_v3_dispatcher.py +349 -0
- hap_cli-0.6.8/hap_cli/tests/test_v3_session.py +307 -0
- hap_cli-0.6.6/hap_cli/README.md → hap_cli-0.6.8/hap_cli.egg-info/PKG-INFO +42 -6
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli.egg-info/SOURCES.txt +20 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli.egg-info/requires.txt +1 -2
- hap_cli-0.6.8/setup.py +67 -0
- hap_cli-0.6.6/hap_cli/core/chat.py +0 -672
- hap_cli-0.6.6/hap_cli/core/session.py +0 -493
- hap_cli-0.6.6/hap_cli/core/token_crypto.py +0 -128
- hap_cli-0.6.6/hap_cli/hap_cli.py +0 -230
- hap_cli-0.6.6/setup.py +0 -39
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/commands/__init__.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/context.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/core/__init__.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/core/app.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/core/calendar_mod.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/core/department.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/core/flow_node.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/core/group.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/core/instance.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/core/optionset.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/core/page.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/core/post.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/core/record.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/core/response_crypto.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/core/role.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/core/workflow.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/core/worksheet.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/skills/SKILL.md +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/skills/__init__.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/tests/__init__.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/tests/test_integration.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/tests/test_integration_calendar.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/tests/test_integration_destructive.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/tests/test_integration_misc.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/tests/test_integration_post.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/tests/test_integration_workflow.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/tests/test_integration_worksheet_extra.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/tests/test_org_id_docs.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/tests/test_parameter_conventions.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/utils/__init__.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/utils/formatting.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/utils/options.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli/utils/parameter_mapping.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli.egg-info/dependency_links.txt +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli.egg-info/entry_points.txt +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/hap_cli.egg-info/top_level.txt +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.8}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hap-cli
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.8
|
|
4
4
|
Summary: CLI harness for MingDAO HAP - Enterprise no-code platform
|
|
5
5
|
Author: hap-cli
|
|
6
6
|
License: Apache-2.0
|
|
@@ -15,14 +15,13 @@ Requires-Python: >=3.10
|
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
Requires-Dist: click>=8.0
|
|
17
17
|
Requires-Dist: requests>=2.28
|
|
18
|
-
|
|
19
|
-
Requires-Dist:
|
|
18
|
+
Requires-Dist: pycryptodome>=3.15
|
|
19
|
+
Requires-Dist: python-socketio[client]>=5.10
|
|
20
20
|
Dynamic: author
|
|
21
21
|
Dynamic: classifier
|
|
22
22
|
Dynamic: description
|
|
23
23
|
Dynamic: description-content-type
|
|
24
24
|
Dynamic: license
|
|
25
|
-
Dynamic: provides-extra
|
|
26
25
|
Dynamic: requires-dist
|
|
27
26
|
Dynamic: requires-python
|
|
28
27
|
Dynamic: summary
|
|
@@ -374,6 +373,20 @@ hap --json worksheet record list WORKSHEET_ID
|
|
|
374
373
|
| `chat group-info` | Get information about a chat group |
|
|
375
374
|
| `chat card-detail` | Resolve card references embedded in chat messages |
|
|
376
375
|
|
|
376
|
+
### config — Harness Settings
|
|
377
|
+
|
|
378
|
+
| Command | Description |
|
|
379
|
+
|---|---|
|
|
380
|
+
| `config show` | Show current harness configuration |
|
|
381
|
+
| `config completion [bash\|zsh\|fish] [--install]` | Enable `<Tab>` shell completion (old `hap completion` kept as hidden alias) |
|
|
382
|
+
| `config log on` | Enable API request/response logging |
|
|
383
|
+
| `config log off` | Disable logging |
|
|
384
|
+
| `config log level <LEVEL>` | Set log level (default `WARNING`; choices: `DEBUG/INFO/WARNING/ERROR/CRITICAL`) |
|
|
385
|
+
| `config log status` | Show current logging on/off, level, and file path |
|
|
386
|
+
| `config log view [--lines N] [--no-follow]` | Follow the log file `tail -f` style |
|
|
387
|
+
|
|
388
|
+
Logs are written to `~/.hap-cli/hap-cli.log` (rotated at 10 MB × 3). When enabled, every HAP API call is logged at `INFO` with URL, request body, response body, and a redacted `Authorization` header (first 16 characters only). Sensitive keys like `password` in request bodies are masked automatically. Set `HAP_LOG_LEVEL=DEBUG` to temporarily override the stored level without editing config.
|
|
389
|
+
|
|
377
390
|
## Workflow Management
|
|
378
391
|
|
|
379
392
|
```bash
|
|
@@ -458,12 +471,6 @@ non-interactively (e.g. on a headless server).
|
|
|
458
471
|
|
|
459
472
|
Commands that expose `--org-id` will use the saved current org when you omit the option.
|
|
460
473
|
|
|
461
|
-
For private deployments with encrypted API responses, install crypto support:
|
|
462
|
-
|
|
463
|
-
```bash
|
|
464
|
-
pip install hap-cli[crypto]
|
|
465
|
-
```
|
|
466
|
-
|
|
467
474
|
## More Examples
|
|
468
475
|
|
|
469
476
|
```bash
|
|
@@ -1,32 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: hap-cli
|
|
3
|
-
Version: 0.6.6
|
|
4
|
-
Summary: CLI harness for MingDAO HAP - Enterprise no-code platform
|
|
5
|
-
Author: hap-cli
|
|
6
|
-
License: Apache-2.0
|
|
7
|
-
Classifier: Development Status :: 3 - Alpha
|
|
8
|
-
Classifier: Intended Audience :: Developers
|
|
9
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
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
|
-
Requires-Python: >=3.10
|
|
15
|
-
Description-Content-Type: text/markdown
|
|
16
|
-
Requires-Dist: click>=8.0
|
|
17
|
-
Requires-Dist: requests>=2.28
|
|
18
|
-
Provides-Extra: crypto
|
|
19
|
-
Requires-Dist: pycryptodome>=3.15; extra == "crypto"
|
|
20
|
-
Dynamic: author
|
|
21
|
-
Dynamic: classifier
|
|
22
|
-
Dynamic: description
|
|
23
|
-
Dynamic: description-content-type
|
|
24
|
-
Dynamic: license
|
|
25
|
-
Dynamic: provides-extra
|
|
26
|
-
Dynamic: requires-dist
|
|
27
|
-
Dynamic: requires-python
|
|
28
|
-
Dynamic: summary
|
|
29
|
-
|
|
30
1
|
# hap-cli
|
|
31
2
|
|
|
32
3
|
CLI harness for **MingDAO HAP** (明道云) - an enterprise no-code platform (hap).
|
|
@@ -374,6 +345,20 @@ hap --json worksheet record list WORKSHEET_ID
|
|
|
374
345
|
| `chat group-info` | Get information about a chat group |
|
|
375
346
|
| `chat card-detail` | Resolve card references embedded in chat messages |
|
|
376
347
|
|
|
348
|
+
### config — Harness Settings
|
|
349
|
+
|
|
350
|
+
| Command | Description |
|
|
351
|
+
|---|---|
|
|
352
|
+
| `config show` | Show current harness configuration |
|
|
353
|
+
| `config completion [bash\|zsh\|fish] [--install]` | Enable `<Tab>` shell completion (old `hap completion` kept as hidden alias) |
|
|
354
|
+
| `config log on` | Enable API request/response logging |
|
|
355
|
+
| `config log off` | Disable logging |
|
|
356
|
+
| `config log level <LEVEL>` | Set log level (default `WARNING`; choices: `DEBUG/INFO/WARNING/ERROR/CRITICAL`) |
|
|
357
|
+
| `config log status` | Show current logging on/off, level, and file path |
|
|
358
|
+
| `config log view [--lines N] [--no-follow]` | Follow the log file `tail -f` style |
|
|
359
|
+
|
|
360
|
+
Logs are written to `~/.hap-cli/hap-cli.log` (rotated at 10 MB × 3). When enabled, every HAP API call is logged at `INFO` with URL, request body, response body, and a redacted `Authorization` header (first 16 characters only). Sensitive keys like `password` in request bodies are masked automatically. Set `HAP_LOG_LEVEL=DEBUG` to temporarily override the stored level without editing config.
|
|
361
|
+
|
|
377
362
|
## Workflow Management
|
|
378
363
|
|
|
379
364
|
```bash
|
|
@@ -458,12 +443,6 @@ non-interactively (e.g. on a headless server).
|
|
|
458
443
|
|
|
459
444
|
Commands that expose `--org-id` will use the saved current org when you omit the option.
|
|
460
445
|
|
|
461
|
-
For private deployments with encrypted API responses, install crypto support:
|
|
462
|
-
|
|
463
|
-
```bash
|
|
464
|
-
pip install hap-cli[crypto]
|
|
465
|
-
```
|
|
466
|
-
|
|
467
446
|
## More Examples
|
|
468
447
|
|
|
469
448
|
```bash
|
|
@@ -352,6 +352,20 @@ hap --json worksheet record list 工作表ID
|
|
|
352
352
|
| `chat group-info` | 获取群组信息 |
|
|
353
353
|
| `chat card-detail` | 解析聊天消息中嵌入的卡片引用 |
|
|
354
354
|
|
|
355
|
+
### config — 本地 CLI 配置
|
|
356
|
+
|
|
357
|
+
| 命令 | 说明 |
|
|
358
|
+
|---|---|
|
|
359
|
+
| `config show` | 显示当前 CLI 配置摘要 |
|
|
360
|
+
| `config completion [bash\|zsh\|fish] [--install]` | 启用 `<Tab>` 命令补全(原 `hap completion`,保留隐藏别名) |
|
|
361
|
+
| `config log on` | 打开 API 请求/响应日志记录 |
|
|
362
|
+
| `config log off` | 关闭日志记录 |
|
|
363
|
+
| `config log level <LEVEL>` | 设置日志级别(默认 `WARNING`) |
|
|
364
|
+
| `config log status` | 查看当前日志开关、级别及文件路径 |
|
|
365
|
+
| `config log view [--lines N] [--no-follow]` | 以 `tail -f` 模式跟踪日志文件 |
|
|
366
|
+
|
|
367
|
+
日志默认写入 `~/.hap-cli/hap-cli.log`,自动按 10MB × 3 份滚动。开启后每次 API 调用以 `INFO` 级别记录 URL、请求体、响应体及脱敏后的 `Authorization`(仅保留前 16 字符)。请求体中的 `password` 等敏感字段会被自动打码。临时调试可设 `HAP_LOG_LEVEL=DEBUG` 覆盖本地配置。
|
|
368
|
+
|
|
355
369
|
## 工作流管理
|
|
356
370
|
|
|
357
371
|
### 创建与编辑流程
|
|
@@ -6,14 +6,25 @@ from hap_cli.context import pass_context
|
|
|
6
6
|
from hap_cli.core import app as app_mod
|
|
7
7
|
from hap_cli.utils.formatting import output_json, output_table, output_kv
|
|
8
8
|
from hap_cli.utils.options import org_id_option, require_api_project_id
|
|
9
|
+
from hap_cli.i18n import t
|
|
9
10
|
|
|
10
11
|
|
|
11
|
-
@click.group()
|
|
12
|
+
@click.group(help=t('Application management.'))
|
|
12
13
|
def app():
|
|
13
|
-
"""Application management."""
|
|
14
14
|
pass
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
def _print_apps_with_default(rows, default_app_id, columns, headers):
|
|
18
|
+
marked = []
|
|
19
|
+
for row in rows:
|
|
20
|
+
row = dict(row)
|
|
21
|
+
row["_mark"] = "*" if default_app_id and row.get("appId") == default_app_id else ""
|
|
22
|
+
marked.append(row)
|
|
23
|
+
output_table(marked, ["_mark", *columns], [" ", *headers])
|
|
24
|
+
if default_app_id and any(r.get("appId") == default_app_id for r in rows):
|
|
25
|
+
click.echo(t("* = current default app (set via 'hap app select')."))
|
|
26
|
+
|
|
27
|
+
|
|
17
28
|
@app.command("list")
|
|
18
29
|
@org_id_option()
|
|
19
30
|
@pass_context
|
|
@@ -24,8 +35,9 @@ def app_list(ctx, org_id):
|
|
|
24
35
|
apps = app_mod.get_all_home_apps(session, require_api_project_id(session, org_id))
|
|
25
36
|
ctx.output(
|
|
26
37
|
apps,
|
|
27
|
-
lambda d:
|
|
38
|
+
lambda d: _print_apps_with_default(
|
|
28
39
|
d,
|
|
40
|
+
session.default_app_id,
|
|
29
41
|
["appId", "name", "orgName"],
|
|
30
42
|
["App ID", "Name", "Org Name"],
|
|
31
43
|
),
|
|
@@ -44,8 +56,9 @@ def app_list_managed(ctx, org_id):
|
|
|
44
56
|
apps = app_mod.get_managed_apps(session, require_api_project_id(session, org_id))
|
|
45
57
|
ctx.output(
|
|
46
58
|
apps,
|
|
47
|
-
lambda d:
|
|
59
|
+
lambda d: _print_apps_with_default(
|
|
48
60
|
d,
|
|
61
|
+
session.default_app_id,
|
|
49
62
|
["appId", "name", "worksheetCount"],
|
|
50
63
|
["App ID", "Name", "Worksheets"],
|
|
51
64
|
),
|
|
@@ -93,6 +106,39 @@ def app_worksheets(ctx, app_id):
|
|
|
93
106
|
ctx.handle_error(e)
|
|
94
107
|
|
|
95
108
|
|
|
109
|
+
@app.command("select")
|
|
110
|
+
@click.argument("app_id")
|
|
111
|
+
@pass_context
|
|
112
|
+
def app_select(ctx, app_id):
|
|
113
|
+
"""Set the default application used when --app-id is omitted."""
|
|
114
|
+
try:
|
|
115
|
+
session = ctx.get_session()
|
|
116
|
+
session.default_app_id = app_id
|
|
117
|
+
session.save()
|
|
118
|
+
ctx.output(
|
|
119
|
+
{"status": "ok", "default_app_id": app_id},
|
|
120
|
+
lambda d: click.echo(t("Default app set to: {app_id}").format(app_id=app_id)),
|
|
121
|
+
)
|
|
122
|
+
except Exception as e:
|
|
123
|
+
ctx.handle_error(e)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@app.command("unselect")
|
|
127
|
+
@pass_context
|
|
128
|
+
def app_unselect(ctx):
|
|
129
|
+
"""Clear the saved default application."""
|
|
130
|
+
try:
|
|
131
|
+
session = ctx.get_session()
|
|
132
|
+
session.default_app_id = ""
|
|
133
|
+
session.save()
|
|
134
|
+
ctx.output(
|
|
135
|
+
{"status": "ok", "default_app_id": ""},
|
|
136
|
+
lambda d: click.echo(t("Default app cleared.")),
|
|
137
|
+
)
|
|
138
|
+
except Exception as e:
|
|
139
|
+
ctx.handle_error(e)
|
|
140
|
+
|
|
141
|
+
|
|
96
142
|
# ── App Lifecycle ────────────────────────────────────────────────────────
|
|
97
143
|
|
|
98
144
|
|
|
@@ -360,4 +406,4 @@ def app_logs(ctx, app_id, org_id, page_size, page):
|
|
|
360
406
|
)
|
|
361
407
|
ctx.output(data, lambda d: output_json(d))
|
|
362
408
|
except Exception as e:
|
|
363
|
-
ctx.handle_error(e)
|
|
409
|
+
ctx.handle_error(e)
|
|
@@ -7,7 +7,9 @@ import click
|
|
|
7
7
|
from hap_cli.context import pass_context
|
|
8
8
|
from hap_cli.core.session import Session, SessionError
|
|
9
9
|
from hap_cli.core import auth as auth_mod
|
|
10
|
+
from hap_cli.core import global_meta as gm
|
|
10
11
|
from hap_cli.utils.formatting import output_json, output_kv
|
|
12
|
+
from hap_cli.i18n import t
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
SERVER_HELP = (
|
|
@@ -16,9 +18,8 @@ SERVER_HELP = (
|
|
|
16
18
|
)
|
|
17
19
|
|
|
18
20
|
|
|
19
|
-
@click.group()
|
|
21
|
+
@click.group(help=t('Manage authentication and session identity.'))
|
|
20
22
|
def auth():
|
|
21
|
-
"""Manage authentication and session identity."""
|
|
22
23
|
pass
|
|
23
24
|
|
|
24
25
|
|
|
@@ -46,16 +47,44 @@ def _paste_flow(ctx, server):
|
|
|
46
47
|
return auth_mod.verify_token(server, token)
|
|
47
48
|
|
|
48
49
|
|
|
49
|
-
def
|
|
50
|
+
def _auto_set_language_from_account(user_info: dict) -> None:
|
|
51
|
+
"""Persist a CLI language picked from the user's HAP account.
|
|
52
|
+
|
|
53
|
+
No-op when the user has already pinned a language via
|
|
54
|
+
``hap config language`` (``language_source == "manual"``).
|
|
55
|
+
"""
|
|
56
|
+
from hap_cli.core.session import load_config, save_config
|
|
57
|
+
from hap_cli.i18n import lang_from_hap_account, reset_language_cache
|
|
58
|
+
|
|
59
|
+
cfg = load_config() or {}
|
|
60
|
+
if cfg.get("language_source") == "manual":
|
|
61
|
+
return
|
|
62
|
+
lang = lang_from_hap_account(str(user_info.get("lang") or ""))
|
|
63
|
+
save_config({"language": lang, "language_source": "auto"})
|
|
64
|
+
reset_language_cache()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _save_and_report(ctx, token, webui, user_info, meta):
|
|
50
68
|
session = ctx.get_session()
|
|
51
69
|
session.server_url = webui
|
|
52
70
|
session.auth_token = token
|
|
53
71
|
session.default_app_id = ""
|
|
54
72
|
session.default_project_id = ""
|
|
55
|
-
|
|
73
|
+
session.default_project_name = ""
|
|
74
|
+
# Harvest routing table + org list from the meta we already fetched
|
|
75
|
+
# during login. No extra round-trip. The meta is fetched from the
|
|
76
|
+
# *exact* login host (see _main_api_base_for_url), so meihua
|
|
77
|
+
# sessions get meihua-specific URLs (api2.mingdao.com etc.) without
|
|
78
|
+
# a synthesised override.
|
|
79
|
+
session.service_endpoints = gm.extract_service_endpoints(meta)
|
|
80
|
+
# Auto-detect UI language from the user's HAP account preference,
|
|
81
|
+
# but only if the user has not pinned a language manually.
|
|
82
|
+
_auto_set_language_from_account(user_info)
|
|
83
|
+
orgs = gm.extract_orgs(meta)
|
|
56
84
|
current_org = orgs[0] if orgs else {}
|
|
57
85
|
if current_org.get("projectId"):
|
|
58
86
|
session.default_project_id = current_org["projectId"]
|
|
87
|
+
session.default_project_name = str(current_org.get("companyName") or "")
|
|
59
88
|
session.save()
|
|
60
89
|
|
|
61
90
|
data = {
|
|
@@ -90,19 +119,51 @@ def auth_login(ctx, server, timeout, token_opt):
|
|
|
90
119
|
If the browser flow can't complete (no GUI, callback never fires),
|
|
91
120
|
interrupt the command to fall back to pasting the token manually.
|
|
92
121
|
Use --token to supply a token non-interactively for scripts.
|
|
122
|
+
|
|
123
|
+
Shared-credential shortcut: production (``mingdao`` /
|
|
124
|
+
``https://www.mingdao.com``) and pre-release
|
|
125
|
+
(``https://meihua.mingdao.com``) share user accounts and data —
|
|
126
|
+
only the API routing differs. When you are already logged into one
|
|
127
|
+
and run ``hap auth login`` for the other, the existing token is
|
|
128
|
+
reused and the browser is not opened. If no session exists yet,
|
|
129
|
+
falls back to the regular browser flow.
|
|
93
130
|
"""
|
|
94
131
|
try:
|
|
132
|
+
# Shared-credential warm switch: skip the browser when an
|
|
133
|
+
# existing session on a sibling host can be reused.
|
|
134
|
+
if not token_opt and auth_mod.can_warm_switch_to(server):
|
|
135
|
+
target_host = auth_mod._resolve_target_host(server)
|
|
136
|
+
if not ctx.json_mode:
|
|
137
|
+
click.echo(f"Switching to {target_host} using existing credentials...")
|
|
138
|
+
token, webui, user_info, meta, reused = (
|
|
139
|
+
auth_mod.shortcut_switch_shared_credential(server)
|
|
140
|
+
)
|
|
141
|
+
_save_and_report(ctx, token, webui, user_info, meta)
|
|
142
|
+
if not ctx.json_mode and reused:
|
|
143
|
+
click.echo("(reused existing token — no browser opened)")
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
# Cold pre-release: no existing session, but target is meihua.
|
|
147
|
+
# The shortcut handles browser-to-prod-then-swap so users still
|
|
148
|
+
# land on meihua at the end.
|
|
149
|
+
if not token_opt and auth_mod.is_prerelease_login(server):
|
|
150
|
+
token, webui, user_info, meta, reused = (
|
|
151
|
+
auth_mod.shortcut_switch_shared_credential(server)
|
|
152
|
+
)
|
|
153
|
+
_save_and_report(ctx, token, webui, user_info, meta)
|
|
154
|
+
return
|
|
155
|
+
|
|
95
156
|
if token_opt:
|
|
96
|
-
token, webui, user_info = auth_mod.verify_token(server, token_opt)
|
|
157
|
+
token, webui, user_info, meta = auth_mod.verify_token(server, token_opt)
|
|
97
158
|
else:
|
|
98
159
|
try:
|
|
99
160
|
_print_browser_hint(ctx, server, timeout)
|
|
100
|
-
token, webui, user_info = auth_mod.login(server, timeout=timeout)
|
|
161
|
+
token, webui, user_info, meta = auth_mod.login(server, timeout=timeout)
|
|
101
162
|
except (TimeoutError, KeyboardInterrupt):
|
|
102
163
|
if not ctx.json_mode:
|
|
103
164
|
click.echo("\nSwitching to manual paste mode...")
|
|
104
|
-
token, webui, user_info = _paste_flow(ctx, server)
|
|
105
|
-
_save_and_report(ctx, token, webui, user_info)
|
|
165
|
+
token, webui, user_info, meta = _paste_flow(ctx, server)
|
|
166
|
+
_save_and_report(ctx, token, webui, user_info, meta)
|
|
106
167
|
except ImportError as e:
|
|
107
168
|
ctx.handle_error(SessionError(str(e)))
|
|
108
169
|
except Exception as e:
|
|
@@ -118,6 +179,8 @@ def auth_logout(ctx):
|
|
|
118
179
|
session.auth_token = ""
|
|
119
180
|
session.default_app_id = ""
|
|
120
181
|
session.default_project_id = ""
|
|
182
|
+
session.default_project_name = ""
|
|
183
|
+
session.service_endpoints = {}
|
|
121
184
|
session.save()
|
|
122
185
|
ctx.output(
|
|
123
186
|
{"status": "ok", "server_url": old_server},
|
|
@@ -177,22 +240,24 @@ def auth_whoami(ctx):
|
|
|
177
240
|
|
|
178
241
|
user_info = auth_mod.get_user_info(session.server_url, session.auth_token)
|
|
179
242
|
|
|
180
|
-
|
|
243
|
+
# Prefer the org name cached at login/switch-org time so whoami
|
|
244
|
+
# runs without an extra network round-trip. Fall back to the
|
|
245
|
+
# projects list if the cache is empty (older configs).
|
|
181
246
|
current_org_id = session.default_project_id
|
|
182
|
-
|
|
247
|
+
current_org_name = session.default_project_name
|
|
248
|
+
if current_org_id and not current_org_name:
|
|
183
249
|
try:
|
|
184
250
|
for org in auth_mod.list_my_orgs(session):
|
|
185
251
|
if org.get("projectId") == current_org_id:
|
|
186
|
-
|
|
252
|
+
current_org_name = str(org.get("companyName") or "")
|
|
187
253
|
break
|
|
188
254
|
except Exception:
|
|
189
|
-
|
|
190
|
-
current_org = {"projectId": current_org_id}
|
|
255
|
+
pass
|
|
191
256
|
|
|
192
257
|
data = {
|
|
193
258
|
**user_info,
|
|
194
|
-
"current_org_id":
|
|
195
|
-
"current_org_name":
|
|
259
|
+
"current_org_id": current_org_id,
|
|
260
|
+
"current_org_name": current_org_name,
|
|
196
261
|
}
|
|
197
262
|
ctx.output(
|
|
198
263
|
data,
|
|
@@ -7,15 +7,15 @@ import click
|
|
|
7
7
|
from hap_cli.context import pass_context
|
|
8
8
|
from hap_cli.core import auth as auth_mod
|
|
9
9
|
from hap_cli.core import calendar_mod as cal_mod
|
|
10
|
+
from hap_cli.i18n import t
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
FREQUENCY_CHOICES = ("none", "day", "week", "month", "year")
|
|
13
14
|
REMIND_TYPE_CHOICES = ("none", "minute", "hour", "day")
|
|
14
15
|
|
|
15
16
|
|
|
16
|
-
@click.group()
|
|
17
|
+
@click.group(help=t('Calendar and schedule management.'))
|
|
17
18
|
def calendar():
|
|
18
|
-
"""Calendar and schedule management."""
|
|
19
19
|
pass
|
|
20
20
|
|
|
21
21
|
|
|
@@ -543,4 +543,4 @@ def calendar_categories(ctx):
|
|
|
543
543
|
summary = _simplify_categories_result(raw)
|
|
544
544
|
ctx.output(summary, _render_categories)
|
|
545
545
|
except Exception as e:
|
|
546
|
-
ctx.handle_error(e)
|
|
546
|
+
ctx.handle_error(e)
|
|
@@ -8,6 +8,7 @@ from hap_cli.context import pass_context
|
|
|
8
8
|
from hap_cli.core import chat as chat_mod
|
|
9
9
|
from hap_cli.core import group as group_mod
|
|
10
10
|
from hap_cli.utils.formatting import output_json, output_kv, output_table
|
|
11
|
+
from hap_cli.i18n import t
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
CHAT_SESSION_TYPE_LABELS = {
|
|
@@ -35,9 +36,9 @@ def _chat_message_preview(session_item):
|
|
|
35
36
|
message = ""
|
|
36
37
|
|
|
37
38
|
prefixes = []
|
|
38
|
-
|
|
39
|
-
if isinstance(
|
|
40
|
-
prefixes.append(f"[{
|
|
39
|
+
unread = session_item.get("unread")
|
|
40
|
+
if isinstance(unread, int) and unread > 0:
|
|
41
|
+
prefixes.append(f"[{unread}条未读]")
|
|
41
42
|
|
|
42
43
|
if session_item.get("type") == 2:
|
|
43
44
|
sender_name = ((session_item.get("from") or {}).get("name") or "").strip()
|
|
@@ -72,9 +73,8 @@ def _output_chat_list_table(rows):
|
|
|
72
73
|
)
|
|
73
74
|
|
|
74
75
|
|
|
75
|
-
@click.group()
|
|
76
|
+
@click.group(help=t('Chat and messaging management.'))
|
|
76
77
|
def chat():
|
|
77
|
-
"""Chat and messaging management."""
|
|
78
78
|
pass
|
|
79
79
|
|
|
80
80
|
|
|
@@ -174,7 +174,6 @@ def chat_files(ctx, with_user, group_id, page_size, page, keywords, file_type, s
|
|
|
174
174
|
"--inbox-type", "inbox_type", default=None,
|
|
175
175
|
help="Raw inbox type code (overrides --category for advanced filters)",
|
|
176
176
|
)
|
|
177
|
-
@click.option("--unread", is_flag=True, help="Only unread messages (inbox mode)")
|
|
178
177
|
@click.option("--page", "-p", default=1, help="Page number")
|
|
179
178
|
@click.option("--num", "-n", default=20, help="Messages per page")
|
|
180
179
|
@click.option(
|
|
@@ -187,6 +186,14 @@ def chat_files(ctx, with_user, group_id, page_size, page, keywords, file_type, s
|
|
|
187
186
|
type=click.Choice(["", "up", "down"]),
|
|
188
187
|
help="'up' loads older messages, 'down' loads newer; empty = default",
|
|
189
188
|
)
|
|
189
|
+
@click.option(
|
|
190
|
+
"--mark-read", "mark_read", is_flag=True, default=False,
|
|
191
|
+
help="After fetching, clear the unread badge for this chat. "
|
|
192
|
+
"Opens a brief socket.io session to emit the same event the "
|
|
193
|
+
"web UI uses when the panel is opened. For --inbox-type "
|
|
194
|
+
"(raw code) this only works when the code maps back to a "
|
|
195
|
+
"known category.",
|
|
196
|
+
)
|
|
190
197
|
@pass_context
|
|
191
198
|
def chat_messages(
|
|
192
199
|
ctx,
|
|
@@ -194,12 +201,12 @@ def chat_messages(
|
|
|
194
201
|
group_id,
|
|
195
202
|
category,
|
|
196
203
|
inbox_type,
|
|
197
|
-
unread,
|
|
198
204
|
page,
|
|
199
205
|
num,
|
|
200
206
|
sincetime,
|
|
201
207
|
keyword,
|
|
202
208
|
direction,
|
|
209
|
+
mark_read,
|
|
203
210
|
):
|
|
204
211
|
"""Fetch chat message history.
|
|
205
212
|
|
|
@@ -227,6 +234,7 @@ def chat_messages(
|
|
|
227
234
|
sincetime=sincetime,
|
|
228
235
|
keyword=keyword,
|
|
229
236
|
direction=direction,
|
|
237
|
+
mark_read=mark_read,
|
|
230
238
|
)
|
|
231
239
|
elif group_id:
|
|
232
240
|
data = chat_mod.get_group_messages(
|
|
@@ -237,6 +245,7 @@ def chat_messages(
|
|
|
237
245
|
sincetime=sincetime,
|
|
238
246
|
keyword=keyword,
|
|
239
247
|
direction=direction,
|
|
248
|
+
mark_read=mark_read,
|
|
240
249
|
)
|
|
241
250
|
else:
|
|
242
251
|
type_code = inbox_type or chat_mod.INBOX_CATEGORY_TYPES[category]
|
|
@@ -245,9 +254,10 @@ def chat_messages(
|
|
|
245
254
|
type_=type_code,
|
|
246
255
|
page_index=page,
|
|
247
256
|
page_size=num,
|
|
248
|
-
load_type=1 if unread else 2,
|
|
249
257
|
category=category,
|
|
250
258
|
)
|
|
259
|
+
if mark_read and category:
|
|
260
|
+
chat_mod.mark_inbox_read(session, category)
|
|
251
261
|
ctx.output(data, lambda d: output_json(d))
|
|
252
262
|
except Exception as e:
|
|
253
263
|
ctx.handle_error(e)
|
|
@@ -309,4 +319,3 @@ def chat_card_detail(ctx, task, post, calendar, kcfile, worksheet, worksheetrow)
|
|
|
309
319
|
except Exception as e:
|
|
310
320
|
ctx.handle_error(e)
|
|
311
321
|
|
|
312
|
-
|