bocai-cli-core 0.2.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.
Files changed (42) hide show
  1. bocai_cli/__init__.py +3 -0
  2. bocai_cli/api/__init__.py +29 -0
  3. bocai_cli/api/auth_client.py +57 -0
  4. bocai_cli/api/card_client.py +222 -0
  5. bocai_cli/api/center_client.py +55 -0
  6. bocai_cli/api/checkin_client.py +29 -0
  7. bocai_cli/api/course_client.py +85 -0
  8. bocai_cli/api/facade.py +40 -0
  9. bocai_cli/api/guanyuan_client.py +50 -0
  10. bocai_cli/api/member_client.py +97 -0
  11. bocai_cli/api/month_dashboard_client.py +130 -0
  12. bocai_cli/api/qywx_client.py +28 -0
  13. bocai_cli/api/report_client.py +41 -0
  14. bocai_cli/api/reservation_client.py +29 -0
  15. bocai_cli/api/transport.py +166 -0
  16. bocai_cli/app.py +12 -0
  17. bocai_cli/client.py +69 -0
  18. bocai_cli/command_utils.py +28 -0
  19. bocai_cli/commands/__init__.py +8 -0
  20. bocai_cli/commands/auth.py +211 -0
  21. bocai_cli/commands/card.py +653 -0
  22. bocai_cli/commands/center.py +138 -0
  23. bocai_cli/commands/checkin.py +71 -0
  24. bocai_cli/commands/course.py +260 -0
  25. bocai_cli/commands/customreport.py +77 -0
  26. bocai_cli/commands/guanyuan.py +94 -0
  27. bocai_cli/commands/member.py +288 -0
  28. bocai_cli/commands/month_dashboard.py +235 -0
  29. bocai_cli/commands/qywx.py +112 -0
  30. bocai_cli/commands/registry.py +70 -0
  31. bocai_cli/commands/report.py +93 -0
  32. bocai_cli/commands/reservation.py +71 -0
  33. bocai_cli/config.py +121 -0
  34. bocai_cli/date_range_validation.py +65 -0
  35. bocai_cli/errors.py +22 -0
  36. bocai_cli/main.py +8 -0
  37. bocai_cli/output.py +28 -0
  38. bocai_cli/product.py +10 -0
  39. bocai_cli_core-0.2.0.dist-info/METADATA +138 -0
  40. bocai_cli_core-0.2.0.dist-info/RECORD +42 -0
  41. bocai_cli_core-0.2.0.dist-info/WHEEL +4 -0
  42. bocai_cli_core-0.2.0.dist-info/licenses/LICENSE +19 -0
@@ -0,0 +1,93 @@
1
+ from __future__ import annotations
2
+
3
+ import typer
4
+
5
+ from bocai_cli.api import ApiFacade
6
+ from bocai_cli.command_utils import command_handler
7
+ from bocai_cli.config import load_config
8
+ from bocai_cli.date_range_validation import validate_optional_date_range
9
+ from bocai_cli.output import print_json
10
+
11
+ app = typer.Typer(help="report相关命令。", invoke_without_command=True)
12
+
13
+
14
+ @app.callback()
15
+ def report_root(ctx: typer.Context) -> None:
16
+ if ctx.invoked_subcommand is None:
17
+ typer.echo(ctx.get_help())
18
+ raise typer.Exit(code=0)
19
+
20
+
21
+ def _build_api() -> ApiFacade:
22
+ config = load_config()
23
+ return ApiFacade(
24
+ base_url=config.base_url,
25
+ token=config.token,
26
+ timeout_seconds=config.timeout_seconds,
27
+ )
28
+
29
+
30
+ @app.command("revenue", help="获取营收数据")
31
+ @command_handler
32
+ def revenue(
33
+ start_date: str | None = typer.Option(
34
+ None,
35
+ "--start-date",
36
+ help="开始日期,格式 YYYY-MM-DD。",
37
+ ),
38
+ end_date: str | None = typer.Option(
39
+ None,
40
+ "--end-date",
41
+ help="结束日期,格式 YYYY-MM-DD。",
42
+ ),
43
+ center_id: str = typer.Option(
44
+ ...,
45
+ "--center-id",
46
+ help="门店ID。",
47
+ ),
48
+ ) -> None:
49
+ validate_optional_date_range(start_date, end_date)
50
+
51
+ api = _build_api()
52
+ data = api.report.get_revenue(
53
+ start_date=start_date,
54
+ end_date=end_date,
55
+ center_id=center_id,
56
+ )
57
+ print_json({"status": "ok", "data": data})
58
+
59
+
60
+ @app.command("revenue-detail", help="获取营收明细数据")
61
+ @command_handler
62
+ def revenue_detail(
63
+ detail_type: str = typer.Option(
64
+ ...,
65
+ "--type",
66
+ help="营收明细类型。",
67
+ ),
68
+ start_date: str | None = typer.Option(
69
+ None,
70
+ "--start-date",
71
+ help="开始日期,格式 YYYY-MM-DD。",
72
+ ),
73
+ end_date: str | None = typer.Option(
74
+ None,
75
+ "--end-date",
76
+ help="结束日期,格式 YYYY-MM-DD。",
77
+ ),
78
+ center_id: str = typer.Option(
79
+ ...,
80
+ "--center-id",
81
+ help="门店ID。",
82
+ ),
83
+ ) -> None:
84
+ validate_optional_date_range(start_date, end_date)
85
+
86
+ api = _build_api()
87
+ data = api.report.get_revenue_detail(
88
+ type_=detail_type,
89
+ start_date=start_date,
90
+ end_date=end_date,
91
+ center_id=center_id,
92
+ )
93
+ print_json({"status": "ok", "data": data})
@@ -0,0 +1,71 @@
1
+ from __future__ import annotations
2
+
3
+ import typer
4
+
5
+ from bocai_cli.api import ApiFacade
6
+ from bocai_cli.command_utils import command_handler
7
+ from bocai_cli.config import load_config
8
+ from bocai_cli.date_range_validation import validate_date_range
9
+ from bocai_cli.output import print_json
10
+
11
+ app = typer.Typer(help="预约相关命令。", invoke_without_command=True)
12
+
13
+
14
+ @app.callback()
15
+ def reservation_root(ctx: typer.Context) -> None:
16
+ if ctx.invoked_subcommand is None:
17
+ typer.echo(ctx.get_help())
18
+ raise typer.Exit(code=0)
19
+
20
+
21
+ def _build_api() -> ApiFacade:
22
+ config = load_config()
23
+ return ApiFacade(
24
+ base_url=config.base_url,
25
+ token=config.token,
26
+ timeout_seconds=config.timeout_seconds,
27
+ )
28
+
29
+
30
+ @app.command("list", help="获取预约记录数据")
31
+ @command_handler
32
+ def list_reservation(
33
+ start_date: str = typer.Option(
34
+ ...,
35
+ "--start-date",
36
+ help="开始日期,格式 YYYY-MM-DD。",
37
+ ),
38
+ end_date: str = typer.Option(
39
+ ...,
40
+ "--end-date",
41
+ help="结束日期,格式 YYYY-MM-DD。",
42
+ ),
43
+ page_num: int = typer.Option(
44
+ ...,
45
+ "--page-num",
46
+ min=0,
47
+ help="页码,必须大于等于 0。",
48
+ ),
49
+ page_size: int = typer.Option(
50
+ ...,
51
+ "--page-size",
52
+ min=1,
53
+ help="每页条数,必须大于等于 1。",
54
+ ),
55
+ center_id: str = typer.Option(
56
+ ...,
57
+ "--center-id",
58
+ help="门店ID。",
59
+ ),
60
+ ) -> None:
61
+ validate_date_range(start_date, end_date)
62
+
63
+ api = _build_api()
64
+ data = api.reservation.list(
65
+ start_date=start_date,
66
+ end_date=end_date,
67
+ page_num=page_num,
68
+ page_size=page_size,
69
+ center_id=center_id,
70
+ )
71
+ print_json({"status": "ok", "data": data})
bocai_cli/config.py ADDED
@@ -0,0 +1,121 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import sys
5
+ from dataclasses import asdict, dataclass
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ import tomli_w
10
+
11
+ from bocai_cli.errors import ConfigValidationError
12
+
13
+ try:
14
+ import tomllib
15
+ except ModuleNotFoundError: # pragma: no cover
16
+ import tomli as tomllib # type: ignore[no-redef]
17
+
18
+ DEFAULT_CONFIG_PATH = Path.home() / ".config" / "bocai-cli" / "config.toml"
19
+ DEFAULT_BASE_URL = "https://openapi.forzadata.cn/"
20
+
21
+
22
+ @dataclass
23
+ class AppConfig:
24
+ base_url: str | None = DEFAULT_BASE_URL
25
+ token: str | None = None
26
+ timeout_seconds: float = 30.0
27
+ app_id: str | None = None
28
+ secret: str | None = None
29
+ token_url: str | None = None
30
+ token_backup: str | None = None
31
+ token_updated_at: str | None = None
32
+ refresh_token: str | None = None
33
+ expires_at: str | None = None
34
+
35
+
36
+ def get_config_path() -> Path:
37
+ raw_path = os.getenv("BOCAI_CLI_CONFIG_PATH")
38
+ if raw_path:
39
+ return Path(raw_path).expanduser()
40
+ return DEFAULT_CONFIG_PATH
41
+
42
+
43
+ def load_config(path: Path | None = None) -> AppConfig:
44
+ config_path = path or get_config_path()
45
+ if not config_path.exists():
46
+ return AppConfig()
47
+
48
+ try:
49
+ with config_path.open("rb") as f:
50
+ data = tomllib.load(f)
51
+ except tomllib.TOMLDecodeError as exc:
52
+ raise ConfigValidationError(
53
+ "配置文件格式错误,请检查 TOML 语法。",
54
+ details={"config_path": str(config_path)},
55
+ ) from exc
56
+ except OSError as exc:
57
+ raise ConfigValidationError(
58
+ "配置文件读取失败,请检查文件权限或路径。",
59
+ details={"config_path": str(config_path)},
60
+ ) from exc
61
+
62
+ return AppConfig(
63
+ base_url=_optional_string(data.get("base_url")) or DEFAULT_BASE_URL,
64
+ token=_optional_string(data.get("token")),
65
+ timeout_seconds=_optional_float(data.get("timeout_seconds"), default=30.0),
66
+ app_id=_optional_string(data.get("app_id")),
67
+ secret=_optional_string(data.get("secret")),
68
+ token_url=_optional_string(data.get("token_url")),
69
+ token_backup=_optional_string(data.get("token_backup")),
70
+ token_updated_at=_optional_string(data.get("token_updated_at")),
71
+ refresh_token=_optional_string(data.get("refresh_token")),
72
+ expires_at=_optional_string(data.get("expires_at")),
73
+ )
74
+
75
+
76
+ def save_config(config: AppConfig, path: Path | None = None) -> Path:
77
+ config_path = path or get_config_path()
78
+ config_path.parent.mkdir(parents=True, exist_ok=True)
79
+
80
+ payload = _to_toml_payload(config)
81
+ with config_path.open("wb") as f:
82
+ f.write(tomli_w.dumps(payload).encode("utf-8"))
83
+
84
+ _enforce_permissions_best_effort(config_path)
85
+ return config_path
86
+
87
+
88
+ def _to_toml_payload(config: AppConfig) -> dict[str, Any]:
89
+ values = asdict(config)
90
+ payload: dict[str, Any] = {}
91
+ for key, value in values.items():
92
+ if value is None:
93
+ continue
94
+ payload[key] = value
95
+ return payload
96
+
97
+
98
+ def _optional_string(raw: Any) -> str | None:
99
+ if raw is None:
100
+ return None
101
+ value = str(raw).strip()
102
+ return value or None
103
+
104
+
105
+ def _optional_float(raw: Any, default: float) -> float:
106
+ if raw is None:
107
+ return default
108
+ try:
109
+ return float(raw)
110
+ except (TypeError, ValueError):
111
+ return default
112
+
113
+
114
+ def _enforce_permissions_best_effort(path: Path) -> None:
115
+ if sys.platform.startswith("win"):
116
+ return
117
+
118
+ try:
119
+ path.chmod(0o600)
120
+ except OSError:
121
+ return
@@ -0,0 +1,65 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import date, datetime
4
+
5
+ from bocai_cli.errors import ConfigValidationError
6
+
7
+
8
+ def _parse_date(value: str, *, option_name: str) -> date:
9
+ try:
10
+ return datetime.strptime(value, "%Y-%m-%d").date()
11
+ except ValueError as exc:
12
+ raise ConfigValidationError(
13
+ f"--{option_name} 格式必须为 YYYY-MM-DD。",
14
+ details={"invalid": [option_name]},
15
+ ) from exc
16
+
17
+
18
+ def validate_date_range(start_date: str, end_date: str) -> None:
19
+ start = _parse_date(start_date, option_name="start-date")
20
+ end = _parse_date(end_date, option_name="end-date")
21
+ if start > end:
22
+ raise ConfigValidationError(
23
+ "--start-date 不能晚于 --end-date。",
24
+ details={"start_date": start_date, "end_date": end_date},
25
+ )
26
+
27
+ range_days = (end - start).days + 1
28
+ if range_days > 31:
29
+ raise ConfigValidationError(
30
+ "--start-date 到 --end-date 的时间范围不能超过 31 天。",
31
+ details={
32
+ "start_date": start_date,
33
+ "end_date": end_date,
34
+ "range_days": range_days,
35
+ },
36
+ )
37
+
38
+
39
+ def validate_optional_date_order(
40
+ start_date: str | None,
41
+ end_date: str | None,
42
+ *,
43
+ start_option_name: str = "start-date",
44
+ end_option_name: str = "end-date",
45
+ ) -> None:
46
+ start = _parse_date(start_date, option_name=start_option_name) if start_date else None
47
+ end = _parse_date(end_date, option_name=end_option_name) if end_date else None
48
+
49
+ if start is not None and end is not None and start > end:
50
+ raise ConfigValidationError(
51
+ f"--{start_option_name} 不能晚于 --{end_option_name}。",
52
+ details={"start_date": start_date, "end_date": end_date},
53
+ )
54
+
55
+
56
+ def validate_optional_date_range(start_date: str | None, end_date: str | None) -> None:
57
+ if (start_date is None) != (end_date is None):
58
+ raise ConfigValidationError("--start-date 和 --end-date 必须同时提供。")
59
+
60
+ if start_date is None and end_date is None:
61
+ return
62
+
63
+ assert start_date is not None
64
+ assert end_date is not None
65
+ validate_date_range(start_date, end_date)
bocai_cli/errors.py ADDED
@@ -0,0 +1,22 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Any
5
+
6
+
7
+ @dataclass
8
+ class BocaiCliError(Exception):
9
+ message: str
10
+ status_code: int | None = None
11
+ details: dict[str, Any] = field(default_factory=dict)
12
+
13
+ def __str__(self) -> str:
14
+ return self.message
15
+
16
+
17
+ class ConfigValidationError(BocaiCliError):
18
+ pass
19
+
20
+
21
+ class ApiRequestError(BocaiCliError):
22
+ pass
bocai_cli/main.py ADDED
@@ -0,0 +1,8 @@
1
+ from __future__ import annotations
2
+
3
+ from bocai_cli.app import create_app
4
+
5
+ app = create_app("gym")
6
+
7
+ if __name__ == "__main__":
8
+ app()
bocai_cli/output.py ADDED
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from typing import Any
5
+
6
+ import typer
7
+
8
+
9
+ def print_json(payload: dict[str, Any]) -> None:
10
+ typer.echo(json.dumps(payload, ensure_ascii=False))
11
+
12
+
13
+ def print_error(
14
+ message: str,
15
+ *,
16
+ status_code: int | None = None,
17
+ details: dict[str, Any] | None = None,
18
+ ) -> None:
19
+ error_payload: dict[str, Any] = {
20
+ "status": "error",
21
+ "message": message,
22
+ }
23
+ if status_code is not None:
24
+ error_payload["status_code"] = status_code
25
+ if details:
26
+ error_payload["details"] = details
27
+
28
+ print_json(error_payload)
bocai_cli/product.py ADDED
@@ -0,0 +1,10 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Literal
4
+
5
+ Product = Literal["gym", "studio"]
6
+
7
+ PRODUCT_HELP: dict[Product, str] = {
8
+ "gym": "菠菜健身房CLI",
9
+ "studio": "菠菜工作室CLI",
10
+ }
@@ -0,0 +1,138 @@
1
+ Metadata-Version: 2.4
2
+ Name: bocai-cli-core
3
+ Version: 0.2.0
4
+ Summary: Shared core for Bocai CLI products
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.11
8
+ Requires-Dist: httpx>=0.27.0
9
+ Requires-Dist: tenacity>=9.0.0
10
+ Requires-Dist: tomli-w>=1.0.0
11
+ Requires-Dist: typer>=0.12.3
12
+ Provides-Extra: dev
13
+ Requires-Dist: pytest>=8.2.0; extra == 'dev'
14
+ Requires-Dist: respx>=0.21.1; extra == 'dev'
15
+ Description-Content-Type: text/markdown
16
+
17
+ # bocai-cli
18
+
19
+ 使用 Python + Typer 封装 SaaS API 的命令行工具。当前提供健身房和工作室两个产品入口,共享同一套核心代码和配置。
20
+
21
+ | 产品 | 安装包 | 命令 |
22
+ | --- | --- | --- |
23
+ | 健身房 | `bocai-cli` | `bocai-cli` |
24
+ | 工作室 | `bocai-studio-cli` | `bocai-studio-cli` |
25
+
26
+ ## 开发安装
27
+
28
+ 先安装共享核心,再安装两个产品入口:
29
+
30
+ ```bash
31
+ python -m pip install -e .
32
+ python -m pip install --no-deps -e packages/bocai-cli -e packages/bocai-studio-cli
33
+ ```
34
+
35
+ 也可以只安装某一个产品入口:
36
+
37
+ ```bash
38
+ python -m pip install --no-deps -e packages/bocai-cli
39
+ ```
40
+
41
+ ## 构建离线 wheel
42
+
43
+ 构建机需要先安装 hatchling:
44
+
45
+ ```bash
46
+ python -m pip install "hatchling>=1.24.2"
47
+ ```
48
+
49
+ 构建三个同版本 wheel,并同时收集运行依赖:
50
+
51
+ ```bash
52
+ python scripts/build_wheels.py
53
+ ```
54
+
55
+ 如果依赖已经由内网镜像提供,只构建产品 wheel:
56
+
57
+ ```bash
58
+ python scripts/build_wheels.py --no-deps
59
+ ```
60
+
61
+ 构建产物位于 `dist/`,包括 `bocai-cli-core`、`bocai-cli` 和 `bocai-studio-cli`。
62
+
63
+ ## 离线安装
64
+
65
+ 健身房机器:
66
+
67
+ ```bash
68
+ python -m pip install --no-index --find-links dist bocai-cli==0.2.0
69
+ ```
70
+
71
+ 工作室机器:
72
+
73
+ ```bash
74
+ python -m pip install --no-index --find-links dist bocai-studio-cli==0.2.0
75
+ ```
76
+
77
+ 如果机器上已有旧版 `bocai-cli`,仅安装工作室版前先执行:
78
+
79
+ ```bash
80
+ python -m pip uninstall -y bocai-cli
81
+ ```
82
+
83
+ 如果机器上安装过旧包名 `bocai-cli-studio`,先卸载旧包再安装 `bocai-studio-cli`:
84
+
85
+ ```bash
86
+ python -m pip uninstall -y bocai-cli-studio
87
+ ```
88
+
89
+ ## 安装后验证
90
+
91
+ ```bash
92
+ bocai-cli --help
93
+ bocai-studio-cli --help
94
+ bocai-cli report --help
95
+ bocai-studio-cli card --help
96
+ ```
97
+
98
+ ## 工作室命令迁移
99
+
100
+ 健身房版只包含健身房命令,不包含 `studio-*` 命令。工作室能力统一在 `bocai-studio-cli` 中提供,并去掉 `studio-` 前缀。旧版命令迁移如下:
101
+
102
+ | 旧版 `bocai-cli` 命令 | 新工作室命令 |
103
+ | --- | --- |
104
+ | `center studio-seller-list` | `bocai-studio-cli center seller-list` |
105
+ | `center studio-trainer-list` | `bocai-studio-cli center trainer-list` |
106
+ | `member studio-proposed-list` | `bocai-studio-cli member proposed-list` |
107
+ | `member studio-member-list` | `bocai-studio-cli member member-list` |
108
+ | `card studio-contract-list` | `bocai-studio-cli card contract-list` |
109
+ | `card studio-sale-performance` | `bocai-studio-cli card sale-performance` |
110
+ | `card studio-private-consume-performance` | `bocai-studio-cli card private-consume-performance` |
111
+
112
+ `center card-list`、`center source-list`、`center tag-list` 现在只保留在工作室版;健身房 `bocai-cli center` 只提供 `list` 和 `manager-phone`。
113
+
114
+ `bocai-cli` 暂不提供 `member` 命令组,所有会员命令请使用 `bocai-studio-cli member ...`,包括 `detail`、`proposed-list` 和 `member-list`。
115
+
116
+ `bocai-cli card contract-detail` 暂时移除,需要使用 `bocai-studio-cli card contract-detail`。
117
+
118
+ 工作室版不提供 `course` 命令组;课程相关命令仅保留在健身房版 `bocai-cli course ...`。
119
+
120
+ `bocai-studio-cli card sale-performance` 指向工作室销售业绩;`bocai-cli card sale-performance` 仍指向健身房售卡业绩。两组命令不会互相出现在对方的帮助中。
121
+
122
+ `guanyuan absence-statistics` 和 `guanyuan absence-detail` 按产品设置默认业态:健身房默认 `--type 2`,允许 `2/3`;工作室默认 `--type 1`,只允许 `1`。
123
+
124
+ ## 配置文件
125
+
126
+ 两个产品共用现有配置:
127
+
128
+ - 优先读取环境变量 `BOCAI_CLI_CONFIG_PATH`。
129
+ - 未设置时读取 `~/.config/bocai-cli/config.toml`。
130
+ - Windows 默认路径为 `C:\Users\{用户名}\.config\bocai-cli\config.toml`。
131
+
132
+ `config.toml` 示例(`base_url`、`app_id`、`secret` 按实际替换):
133
+
134
+ ```toml
135
+ base_url = "http://10.0.0.227:10086"
136
+ app_id = "test_app_001"
137
+ secret = "test_secret_123456"
138
+ ```
@@ -0,0 +1,42 @@
1
+ bocai_cli/__init__.py,sha256=QwLGZDZhVE7dBKbOwCLRt3tRMXTtg8fYYHHDXi3sZ1c,49
2
+ bocai_cli/app.py,sha256=B-lN54_hDnM0ftMDKWQFACPJNTcFrO7evdonu1flXVI,357
3
+ bocai_cli/client.py,sha256=xjtirUDW9ip3kHAhKaTnaXiqtZaFMqx81Yx5KFQogzs,1754
4
+ bocai_cli/command_utils.py,sha256=JlE_fh_cWRpEnoUamNLrZSEm-GOWy-QdiXHDW3Xgt9c,764
5
+ bocai_cli/config.py,sha256=dwu7X-5wSN-tJTyquBvYlxZu13UXtv08KVKBK1Od2BI,3657
6
+ bocai_cli/date_range_validation.py,sha256=22UqaOtL8q4KrxsAz_TI4qsmq7Aqwtvh06N5_yzWnqw,2202
7
+ bocai_cli/errors.py,sha256=1r9104aIibAmGIIGOBwwggZNPl-PpT-6mZo-wMi5qa0,418
8
+ bocai_cli/main.py,sha256=6_BDJbCwg4L89EntF6DMZ7KmE13vDOMikTU97GsA0rI,137
9
+ bocai_cli/output.py,sha256=c-kvmVpuTxY-CEVuRv93nyEGy5Tu-jPGsrfuHKd3F4o,598
10
+ bocai_cli/product.py,sha256=V_vegkhYX2U5Nc-Z084WlzU3WEncbCSl7z8K8M3Zagw,208
11
+ bocai_cli/api/__init__.py,sha256=3gmi1ICIKAmyBqLPfQS2XdIb-IgdN_fTuXy0aV1RAqk,978
12
+ bocai_cli/api/auth_client.py,sha256=C38FDhrWh1WpK1cpfznFPY35n-OeufgwFzTGexD_s6E,1776
13
+ bocai_cli/api/card_client.py,sha256=9De6Q8h79ixoMWNkyqI3NzStqLdOyLNZ01159zUTORs,6714
14
+ bocai_cli/api/center_client.py,sha256=_3SLniorOkkQh7c730g8GNGCt5WazlNyWK0FgB-yElM,1653
15
+ bocai_cli/api/checkin_client.py,sha256=H620MXbPtc_06ncVuQgj7ksh2ZkiTUEntxWZeb0J_ts,755
16
+ bocai_cli/api/course_client.py,sha256=dDx-a-SBx2ilUcDudqqh4BTmCUa0oFkjlokj0_l3dCo,2300
17
+ bocai_cli/api/facade.py,sha256=mA0PMmxWNjB_a1RH4H6KXjBwwY1EfDjVqL-8_KsMPQY,1593
18
+ bocai_cli/api/guanyuan_client.py,sha256=BY-vuFM1lrAkQ7ogkv92u6h4RnJMb39zypt5M3Z7Tck,1419
19
+ bocai_cli/api/member_client.py,sha256=sQl1NTOzD2h1Z2LwZPyBFq6ZdBrmvM5RlMJuZKL6mEQ,3139
20
+ bocai_cli/api/month_dashboard_client.py,sha256=NjS1GY7Vc2jwOLUH0B3WB52f8pEeFwh3-rNEXZ0Q3A0,3143
21
+ bocai_cli/api/qywx_client.py,sha256=t5QY8l2v5pvBZR4tsgjx0yihUvhV2i7AxQ10vRrQ2Is,873
22
+ bocai_cli/api/report_client.py,sha256=pQQ71_gIowMH3AX9cVnOd3UZWpgf5gpoNKhkx-2qZCY,1343
23
+ bocai_cli/api/reservation_client.py,sha256=4DmgazRQIPLtuvxNR_p-QUyzexk6NMemtuXz3uX4wDw,763
24
+ bocai_cli/api/transport.py,sha256=WoafWXQcVZRb2TaoOGARkbjyUK2ymPyybCz40NTY_Yw,6142
25
+ bocai_cli/commands/__init__.py,sha256=PP8ldg76MZGWkuSZ4ht-61OZX8Fz-E3JvsBKJDtMlm4,444
26
+ bocai_cli/commands/auth.py,sha256=sJ5bJ-4DY2Rs3gAJO6Bcjm0N9naGVwKO_5wtpwJq_SM,5739
27
+ bocai_cli/commands/card.py,sha256=pj_32H4Q2xBQ4X11fYaEs4lPNA-PEoYtEsV_v9WYtC4,19566
28
+ bocai_cli/commands/center.py,sha256=nT42odMuVPRDDC3K0ONpDM4MsH7IM2A8KvitxHIvN-A,3684
29
+ bocai_cli/commands/checkin.py,sha256=xpD80uyHu4SgVCe5pSmsnaeQkl2uaHFs88Ydi38jKho,1781
30
+ bocai_cli/commands/course.py,sha256=VitQQeuBT-VNJVRS0_9wT-Eu5iYupFg4m6FKnl3x3dk,7876
31
+ bocai_cli/commands/customreport.py,sha256=Gq50LtWvVB-yjNXXBbxjs0FhA3Q9aSg2rvgTTon77Ck,1919
32
+ bocai_cli/commands/guanyuan.py,sha256=xJEW1h_gg44F8KFOoo7GCoQEu1SjCiXinr_QxTlnvxU,2567
33
+ bocai_cli/commands/member.py,sha256=5WvhqrLHHhTN1iAKUFYgJOke_SDWMxdOpL8IvkrcCdI,8231
34
+ bocai_cli/commands/month_dashboard.py,sha256=sm4CDZMwv1tN3s3V0W09DVPYaHfYko-CoH4HhI3t0vY,5547
35
+ bocai_cli/commands/qywx.py,sha256=vooHJM9R80Kh8kiBllJKadtjYJiubPWAlTxiXdi9D68,3435
36
+ bocai_cli/commands/registry.py,sha256=Kk9PjPOLdayuqJMET-cVGA2odMslJQUPf68BKjMTmbM,2950
37
+ bocai_cli/commands/report.py,sha256=u7yKBkFJnCys_btkOM5ZrhHxyxq-MYroSgJx2HEUy2k,2365
38
+ bocai_cli/commands/reservation.py,sha256=H7RQoKT2hxI5QRiz9K23MkqM3P-_2ambc5LMOr8Zlys,1793
39
+ bocai_cli_core-0.2.0.dist-info/METADATA,sha256=Mc1QYwCxnX6KYd8f2oD96dntoD4wMucWgKIZ6PU5R6o,4317
40
+ bocai_cli_core-0.2.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
41
+ bocai_cli_core-0.2.0.dist-info/licenses/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
42
+ bocai_cli_core-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2018 The Python Packaging Authority
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.