drinkzen-admin-cli 0.2.4__tar.gz → 0.3.3__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 (19) hide show
  1. {drinkzen_admin_cli-0.2.4 → drinkzen_admin_cli-0.3.3}/PKG-INFO +32 -1
  2. {drinkzen_admin_cli-0.2.4 → drinkzen_admin_cli-0.3.3}/README.md +31 -0
  3. {drinkzen_admin_cli-0.2.4 → drinkzen_admin_cli-0.3.3}/drinkzen_admin/__init__.py +1 -1
  4. {drinkzen_admin_cli-0.2.4 → drinkzen_admin_cli-0.3.3}/drinkzen_admin/cli.py +45 -0
  5. drinkzen_admin_cli-0.3.3/drinkzen_admin/skill.py +37 -0
  6. {drinkzen_admin_cli-0.2.4 → drinkzen_admin_cli-0.3.3}/drinkzen_admin_cli.egg-info/PKG-INFO +32 -1
  7. {drinkzen_admin_cli-0.2.4 → drinkzen_admin_cli-0.3.3}/drinkzen_admin_cli.egg-info/SOURCES.txt +2 -0
  8. {drinkzen_admin_cli-0.2.4 → drinkzen_admin_cli-0.3.3}/pyproject.toml +1 -1
  9. drinkzen_admin_cli-0.3.3/tests/test_skill.py +26 -0
  10. {drinkzen_admin_cli-0.2.4 → drinkzen_admin_cli-0.3.3}/tests/test_standalone_cli.py +28 -0
  11. {drinkzen_admin_cli-0.2.4 → drinkzen_admin_cli-0.3.3}/drinkzen_admin/client.py +0 -0
  12. {drinkzen_admin_cli-0.2.4 → drinkzen_admin_cli-0.3.3}/drinkzen_admin/config.py +0 -0
  13. {drinkzen_admin_cli-0.2.4 → drinkzen_admin_cli-0.3.3}/drinkzen_admin/formatting.py +0 -0
  14. {drinkzen_admin_cli-0.2.4 → drinkzen_admin_cli-0.3.3}/drinkzen_admin/menu_options.py +0 -0
  15. {drinkzen_admin_cli-0.2.4 → drinkzen_admin_cli-0.3.3}/drinkzen_admin_cli.egg-info/dependency_links.txt +0 -0
  16. {drinkzen_admin_cli-0.2.4 → drinkzen_admin_cli-0.3.3}/drinkzen_admin_cli.egg-info/entry_points.txt +0 -0
  17. {drinkzen_admin_cli-0.2.4 → drinkzen_admin_cli-0.3.3}/drinkzen_admin_cli.egg-info/top_level.txt +0 -0
  18. {drinkzen_admin_cli-0.2.4 → drinkzen_admin_cli-0.3.3}/setup.cfg +0 -0
  19. {drinkzen_admin_cli-0.2.4 → drinkzen_admin_cli-0.3.3}/tests/test_config.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drinkzen-admin-cli
3
- Version: 0.2.4
3
+ Version: 0.3.3
4
4
  Summary: Standalone CLI client for DrinkZen Admin operations
5
5
  Author: DrinkZen Team
6
6
  License: MIT
@@ -64,6 +64,34 @@ PYTHONPATH=packages/admin-cli python3 -m drinkzen_admin.cli config show
64
64
 
65
65
  ---
66
66
 
67
+ ## Agent Skill 安装
68
+
69
+ `drinkzen-admin` 提供与具体 AI 工具无关的运营使用手册。默认安装到当前项目的 `.agents/skills`:
70
+
71
+ ```bash
72
+ drinkzen-admin skill install
73
+ ```
74
+
75
+ 如需安装到当前用户的全局 Agent Skill 目录:
76
+
77
+ ```bash
78
+ drinkzen-admin skill install --scope global
79
+ ```
80
+
81
+ 查询目标路径或更新已有 Skill:
82
+
83
+ ```bash
84
+ drinkzen-admin skill path --scope project --json
85
+ drinkzen-admin skill path --scope global --json
86
+ drinkzen-admin skill install --force
87
+ ```
88
+
89
+ 项目级安装默认使用当前目录,也可以指定项目目录:
90
+
91
+ ```bash
92
+ drinkzen-admin skill install --scope project --project-dir /path/to/project
93
+ ```
94
+
67
95
  ## 密钥与持久化配置管理 (`config`)
68
96
 
69
97
  `drinkzen-admin` 提供了内置的密钥持久化管理,避免每次在终端手动输入敏感 Token 或将其留在 Bash 历史记录中。
@@ -128,6 +156,9 @@ drinkzen-admin brand update 霸王茶姬 --name "霸王茶姬 CHAGEE" --alias "C
128
156
 
129
157
  # 上传品牌 Logo 并标注数据置信度
130
158
  drinkzen-admin brand set-logo 霸王茶姬 ./logo.png --confidence verified --source-name "官方小程序"
159
+
160
+ # 只读核对数据库 Logo 引用与 COS 文件,识别缺失引用与孤儿对象
161
+ drinkzen-admin brand audit-logos --json
131
162
  ```
132
163
 
133
164
  ### 2. 菜单模板管理 (`menu-template`)
@@ -42,6 +42,34 @@ PYTHONPATH=packages/admin-cli python3 -m drinkzen_admin.cli config show
42
42
 
43
43
  ---
44
44
 
45
+ ## Agent Skill 安装
46
+
47
+ `drinkzen-admin` 提供与具体 AI 工具无关的运营使用手册。默认安装到当前项目的 `.agents/skills`:
48
+
49
+ ```bash
50
+ drinkzen-admin skill install
51
+ ```
52
+
53
+ 如需安装到当前用户的全局 Agent Skill 目录:
54
+
55
+ ```bash
56
+ drinkzen-admin skill install --scope global
57
+ ```
58
+
59
+ 查询目标路径或更新已有 Skill:
60
+
61
+ ```bash
62
+ drinkzen-admin skill path --scope project --json
63
+ drinkzen-admin skill path --scope global --json
64
+ drinkzen-admin skill install --force
65
+ ```
66
+
67
+ 项目级安装默认使用当前目录,也可以指定项目目录:
68
+
69
+ ```bash
70
+ drinkzen-admin skill install --scope project --project-dir /path/to/project
71
+ ```
72
+
45
73
  ## 密钥与持久化配置管理 (`config`)
46
74
 
47
75
  `drinkzen-admin` 提供了内置的密钥持久化管理,避免每次在终端手动输入敏感 Token 或将其留在 Bash 历史记录中。
@@ -106,6 +134,9 @@ drinkzen-admin brand update 霸王茶姬 --name "霸王茶姬 CHAGEE" --alias "C
106
134
 
107
135
  # 上传品牌 Logo 并标注数据置信度
108
136
  drinkzen-admin brand set-logo 霸王茶姬 ./logo.png --confidence verified --source-name "官方小程序"
137
+
138
+ # 只读核对数据库 Logo 引用与 COS 文件,识别缺失引用与孤儿对象
139
+ drinkzen-admin brand audit-logos --json
109
140
  ```
110
141
 
111
142
  ### 2. 菜单模板管理 (`menu-template`)
@@ -1,3 +1,3 @@
1
1
  """DrinkZen Admin CLI package."""
2
2
 
3
- __version__ = "0.2.4"
3
+ __version__ = "0.3.0"
@@ -12,6 +12,7 @@ from .client import AdminCLIClient
12
12
  from .config import cmd_config_path, cmd_config_set, cmd_config_show
13
13
  from .formatting import format_cli_output
14
14
  from .menu_options import normalize_menu_option_config
15
+ from .skill import install_skill, skill_path
15
16
 
16
17
 
17
18
  # ----------------------------------------------------------------------
@@ -62,6 +63,16 @@ def cmd_brand_show(
62
63
  return 0
63
64
 
64
65
 
66
+ def cmd_brand_audit_logos(client: AdminCLIClient, is_json: bool = False) -> int:
67
+ """Print the API's read-only COS/database logo consistency audit."""
68
+ status, data = client.request("GET", "/api/admin/brands/logo-audit")
69
+ if status != 200:
70
+ print(format_cli_output({"error": data}, is_json))
71
+ return 1
72
+ print(format_cli_output(data, is_json))
73
+ return 0
74
+
75
+
65
76
  def cmd_brand_update(
66
77
  client: AdminCLIClient,
67
78
  brand_id_or_name: str,
@@ -404,6 +415,19 @@ def build_parser() -> argparse.ArgumentParser:
404
415
  c_show = config_sub.add_parser("show", help="Show active configuration with masked secrets", parents=[common_parser])
405
416
  c_path = config_sub.add_parser("path", help="Print configuration file path", parents=[common_parser])
406
417
 
418
+ # skill
419
+ skill_parser = subparsers.add_parser("skill", help="Install the agent-neutral DrinkZen Admin Skill")
420
+ skill_sub = skill_parser.add_subparsers(dest="skill_action", required=True)
421
+ s_install = skill_sub.add_parser("install", help="Download the Skill into .agents/skills")
422
+ s_install.add_argument("--scope", choices=["project", "global"], default="project")
423
+ s_install.add_argument("--project-dir", default=None, help="Project directory for project scope (default: current directory)")
424
+ s_install.add_argument("--force", action="store_true", help="Replace an existing Skill")
425
+ s_install.add_argument("--json", action="store_true", help="Output JSON format")
426
+ s_path = skill_sub.add_parser("path", help="Show the target Skill path")
427
+ s_path.add_argument("--scope", choices=["project", "global"], default="project")
428
+ s_path.add_argument("--project-dir", default=None)
429
+ s_path.add_argument("--json", action="store_true", help="Output JSON format")
430
+
407
431
  # brand
408
432
  brand_parser = subparsers.add_parser("brand", help="Brand operations", parents=[common_parser])
409
433
  brand_sub = brand_parser.add_subparsers(dest="brand_action", required=True)
@@ -415,6 +439,12 @@ def build_parser() -> argparse.ArgumentParser:
415
439
  b_show = brand_sub.add_parser("show", help="Show brand details", parents=[common_parser])
416
440
  b_show.add_argument("brand", help="Brand ID or name")
417
441
 
442
+ brand_sub.add_parser(
443
+ "audit-logos",
444
+ help="Read-only audit of brand Logo references against object storage",
445
+ parents=[common_parser],
446
+ )
447
+
418
448
  b_update = brand_sub.add_parser("update", help="Update brand information", parents=[common_parser])
419
449
  b_update.add_argument("brand", help="Brand ID or name")
420
450
  b_update.add_argument("--name", default=None, help="New brand name")
@@ -470,6 +500,19 @@ def main(args: list[str] | None = None) -> int:
470
500
  is_json = getattr(parsed, "json", False)
471
501
  dry_run = getattr(parsed, "dry_run", False)
472
502
 
503
+ if parsed.subcommand == "skill":
504
+ target = skill_path(parsed.scope, parsed.project_dir)
505
+ if parsed.skill_action == "path":
506
+ print(format_cli_output({"path": str(target), "scope": parsed.scope}, parsed.json))
507
+ return 0
508
+ try:
509
+ installed = install_skill(parsed.scope, parsed.project_dir, parsed.force)
510
+ print(format_cli_output({"installed": True, "path": str(installed), "scope": parsed.scope}, parsed.json))
511
+ return 0
512
+ except Exception as exc:
513
+ print(format_cli_output({"installed": False, "error": str(exc), "path": str(target)}, parsed.json))
514
+ return 1
515
+
473
516
  # Config commands
474
517
  if parsed.subcommand == "config":
475
518
  if parsed.config_action == "set":
@@ -488,6 +531,8 @@ def main(args: list[str] | None = None) -> int:
488
531
  return cmd_brand_list(client, search=parsed.search, active_only=parsed.active_only, is_json=is_json)
489
532
  if parsed.brand_action == "show":
490
533
  return cmd_brand_show(client, brand_id_or_name=parsed.brand, is_json=is_json)
534
+ if parsed.brand_action == "audit-logos":
535
+ return cmd_brand_audit_logos(client, is_json=is_json)
491
536
  if parsed.brand_action == "update":
492
537
  return cmd_brand_update(
493
538
  client,
@@ -0,0 +1,37 @@
1
+ """Install the DrinkZen Admin Agent Skill without binding to a specific agent."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import urllib.request
6
+ from pathlib import Path
7
+
8
+ SKILL_NAME = "drinkzen-admin"
9
+ SKILL_URL = "https://drinkzen.cn/skills/drinkzen-admin/SKILL.md"
10
+ SKILL_USER_AGENT = "DrinkZenAdminCLI/1.0 (+https://drinkzen.cn)"
11
+
12
+
13
+ def skill_path(scope: str = "project", project_dir: str | None = None) -> Path:
14
+ if scope == "global":
15
+ root = Path.home() / ".agents" / "skills"
16
+ else:
17
+ root = Path(project_dir or ".").expanduser().resolve() / ".agents" / "skills"
18
+ return root / SKILL_NAME / "SKILL.md"
19
+
20
+
21
+ def install_skill(
22
+ scope: str = "project",
23
+ project_dir: str | None = None,
24
+ force: bool = False,
25
+ url: str = SKILL_URL,
26
+ ) -> Path:
27
+ destination = skill_path(scope, project_dir)
28
+ if destination.exists() and not force:
29
+ raise FileExistsError(f"Skill already exists: {destination}. Use --force to replace it.")
30
+ destination.parent.mkdir(parents=True, exist_ok=True)
31
+ request = urllib.request.Request(url, headers={"User-Agent": SKILL_USER_AGENT})
32
+ with urllib.request.urlopen(request, timeout=20) as response:
33
+ content = response.read()
34
+ if not content:
35
+ raise RuntimeError("Downloaded Skill is empty")
36
+ destination.write_bytes(content)
37
+ return destination
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drinkzen-admin-cli
3
- Version: 0.2.4
3
+ Version: 0.3.3
4
4
  Summary: Standalone CLI client for DrinkZen Admin operations
5
5
  Author: DrinkZen Team
6
6
  License: MIT
@@ -64,6 +64,34 @@ PYTHONPATH=packages/admin-cli python3 -m drinkzen_admin.cli config show
64
64
 
65
65
  ---
66
66
 
67
+ ## Agent Skill 安装
68
+
69
+ `drinkzen-admin` 提供与具体 AI 工具无关的运营使用手册。默认安装到当前项目的 `.agents/skills`:
70
+
71
+ ```bash
72
+ drinkzen-admin skill install
73
+ ```
74
+
75
+ 如需安装到当前用户的全局 Agent Skill 目录:
76
+
77
+ ```bash
78
+ drinkzen-admin skill install --scope global
79
+ ```
80
+
81
+ 查询目标路径或更新已有 Skill:
82
+
83
+ ```bash
84
+ drinkzen-admin skill path --scope project --json
85
+ drinkzen-admin skill path --scope global --json
86
+ drinkzen-admin skill install --force
87
+ ```
88
+
89
+ 项目级安装默认使用当前目录,也可以指定项目目录:
90
+
91
+ ```bash
92
+ drinkzen-admin skill install --scope project --project-dir /path/to/project
93
+ ```
94
+
67
95
  ## 密钥与持久化配置管理 (`config`)
68
96
 
69
97
  `drinkzen-admin` 提供了内置的密钥持久化管理,避免每次在终端手动输入敏感 Token 或将其留在 Bash 历史记录中。
@@ -128,6 +156,9 @@ drinkzen-admin brand update 霸王茶姬 --name "霸王茶姬 CHAGEE" --alias "C
128
156
 
129
157
  # 上传品牌 Logo 并标注数据置信度
130
158
  drinkzen-admin brand set-logo 霸王茶姬 ./logo.png --confidence verified --source-name "官方小程序"
159
+
160
+ # 只读核对数据库 Logo 引用与 COS 文件,识别缺失引用与孤儿对象
161
+ drinkzen-admin brand audit-logos --json
131
162
  ```
132
163
 
133
164
  ### 2. 菜单模板管理 (`menu-template`)
@@ -6,10 +6,12 @@ drinkzen_admin/client.py
6
6
  drinkzen_admin/config.py
7
7
  drinkzen_admin/formatting.py
8
8
  drinkzen_admin/menu_options.py
9
+ drinkzen_admin/skill.py
9
10
  drinkzen_admin_cli.egg-info/PKG-INFO
10
11
  drinkzen_admin_cli.egg-info/SOURCES.txt
11
12
  drinkzen_admin_cli.egg-info/dependency_links.txt
12
13
  drinkzen_admin_cli.egg-info/entry_points.txt
13
14
  drinkzen_admin_cli.egg-info/top_level.txt
14
15
  tests/test_config.py
16
+ tests/test_skill.py
15
17
  tests/test_standalone_cli.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "drinkzen-admin-cli"
7
- version = "0.2.4"
7
+ version = "0.3.3"
8
8
  description = "Standalone CLI client for DrinkZen Admin operations"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -0,0 +1,26 @@
1
+ from pathlib import Path
2
+ from tempfile import TemporaryDirectory
3
+ from unittest.mock import patch
4
+
5
+ from drinkzen_admin.skill import SKILL_USER_AGENT, install_skill
6
+
7
+
8
+ class SkillResponse:
9
+ def __enter__(self) -> "SkillResponse":
10
+ return self
11
+
12
+ def __exit__(self, exc_type: object, exc_value: object, traceback: object) -> None:
13
+ return None
14
+
15
+ def read(self) -> bytes:
16
+ return b"---\nname: drinkzen-admin\n---\n"
17
+
18
+
19
+ def test_install_skill_identifies_the_cli_request() -> None:
20
+ with TemporaryDirectory() as temp_dir:
21
+ with patch("urllib.request.urlopen", return_value=SkillResponse()) as urlopen:
22
+ destination = install_skill(project_dir=temp_dir)
23
+
24
+ request = urlopen.call_args.args[0]
25
+ assert request.get_header("User-agent") == SKILL_USER_AGENT
26
+ assert Path(destination).read_text(encoding="utf-8").startswith("---")
@@ -71,6 +71,34 @@ class TestStandaloneCLI(unittest.TestCase):
71
71
  self.assertTrue(output.get("dry_run"))
72
72
  self.assertEqual(output["proposed"]["name"], "新名称")
73
73
 
74
+ @patch("drinkzen_admin.cli.AdminCLIClient")
75
+ def test_brand_audit_logos_command(self, mock_client_cls):
76
+ mock_client = MagicMock()
77
+ mock_client.request.return_value = (
78
+ 200,
79
+ {
80
+ "backend": "CosObjectStorage",
81
+ "total_brands": 14,
82
+ "referenced_logo_count": 9,
83
+ "missing_referenced_objects": [],
84
+ "recoverable_unlinked_objects": [],
85
+ "orphan_object_keys": ["brands/old/logo.png"],
86
+ },
87
+ )
88
+ mock_client_cls.return_value = mock_client
89
+
90
+ buf = io.StringIO()
91
+ with patch("sys.argv", ["drinkzen-admin", "brand", "audit-logos", "--json"]):
92
+ with redirect_stdout(buf):
93
+ exit_code = main()
94
+
95
+ self.assertEqual(exit_code, 0)
96
+ output = json.loads(buf.getvalue())
97
+ self.assertEqual(output["referenced_logo_count"], 9)
98
+ mock_client.request.assert_called_once_with(
99
+ "GET", "/api/admin/brands/logo-audit"
100
+ )
101
+
74
102
  def test_menu_template_validate_command(self):
75
103
  valid_template = json.dumps({
76
104
  "optionGroups": [