maxc-cli 0.2.5__tar.gz → 0.3.1__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 (96) hide show
  1. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/PKG-INFO +14 -9
  2. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/README.md +13 -8
  3. maxc_cli-0.3.1/pyproject.toml +22 -0
  4. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/setup.py +1 -1
  5. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/__init__.py +1 -1
  6. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/__main__.py +0 -1
  7. maxc_cli-0.3.1/src/maxc_cli/_samples.py +187 -0
  8. maxc_cli-0.3.1/src/maxc_cli/agent_platforms.py +188 -0
  9. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/app.py +735 -221
  10. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/auth_providers.py +11 -13
  11. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/backend/__init__.py +1 -2
  12. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/backend/auth.py +4 -0
  13. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/backend/catalog.py +1 -1
  14. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/backend/data.py +101 -33
  15. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/backend/job.py +28 -8
  16. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/backend/meta.py +64 -22
  17. maxc_cli-0.3.1/src/maxc_cli/backend/odps.py +200 -0
  18. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/backend/query.py +22 -9
  19. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/cache.py +1 -2
  20. maxc_cli-0.3.1/src/maxc_cli/catalog_bootstrap.py +223 -0
  21. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/cli.py +733 -179
  22. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/config.py +6 -6
  23. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/exceptions.py +10 -2
  24. maxc_cli-0.3.1/src/maxc_cli/help_format.py +134 -0
  25. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/helpers.py +190 -139
  26. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/masking.py +7 -8
  27. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/models.py +19 -12
  28. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/output.py +10 -11
  29. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/setting_parser.py +4 -4
  30. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/skills/SKILL.md +49 -32
  31. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/skills/references/bootstrap-auth.md +18 -14
  32. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/skills/references/bootstrap-flow.md +12 -8
  33. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/skills/references/command-patterns.md +106 -103
  34. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/skills/references/json-output-format.md +8 -6
  35. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/skills/references/maxcompute-select-guide.md +2 -0
  36. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/skills/references/maxcompute-sql-notes.md +11 -9
  37. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/skills/references/migrate-from-odpscmd.md +13 -11
  38. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/skills/references/partition-guide.md +13 -11
  39. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/skills/references/red-lines.md +3 -1
  40. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/skills/references/setup-install.md +19 -8
  41. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/skills/references/sql-common-errors.md +5 -3
  42. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/skills/references/sql-query-patterns.md +3 -1
  43. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/skills/references/text2sql-principles.md +2 -0
  44. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/utils.py +24 -3
  45. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli.egg-info/PKG-INFO +14 -9
  46. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli.egg-info/SOURCES.txt +25 -1
  47. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/tests/test_agent_hints_and_cli.py +2 -2
  48. maxc_cli-0.3.1/tests/test_agent_platforms.py +95 -0
  49. maxc_cli-0.3.1/tests/test_agent_skill_commands.py +252 -0
  50. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/tests/test_agent_skill_commands_context.py +136 -21
  51. maxc_cli-0.3.1/tests/test_backend_data.py +29 -0
  52. maxc_cli-0.3.1/tests/test_backend_data_serialization.py +247 -0
  53. maxc_cli-0.3.1/tests/test_backend_meta.py +92 -0
  54. maxc_cli-0.3.1/tests/test_build_release_script.py +59 -0
  55. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/tests/test_cache.py +0 -1
  56. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/tests/test_catalog.py +2 -3
  57. maxc_cli-0.3.1/tests/test_catalog_bootstrap.py +237 -0
  58. maxc_cli-0.3.1/tests/test_cli_arg_validation.py +132 -0
  59. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/tests/test_cli_mock.py +669 -25
  60. maxc_cli-0.3.1/tests/test_cli_query_parse_and_sanitize.py +118 -0
  61. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/tests/test_compat.py +5 -3
  62. maxc_cli-0.3.1/tests/test_envelope_shape.py +211 -0
  63. maxc_cli-0.3.1/tests/test_error_translation.py +101 -0
  64. maxc_cli-0.3.1/tests/test_exit_codes.py +107 -0
  65. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/tests/test_external_auth.py +84 -12
  66. maxc_cli-0.3.1/tests/test_flag_hoist.py +48 -0
  67. maxc_cli-0.3.1/tests/test_help_format.py +166 -0
  68. maxc_cli-0.3.1/tests/test_helpers.py +51 -0
  69. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/tests/test_integration.py +4 -2
  70. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/tests/test_integration_real.py +28 -4
  71. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/tests/test_job_improvements.py +0 -1
  72. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/tests/test_masking.py +1 -1
  73. maxc_cli-0.3.1/tests/test_meta_schema_and_partition_cols.py +232 -0
  74. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/tests/test_phase1_improvements.py +17 -13
  75. maxc_cli-0.3.1/tests/test_pyinstaller_bundle.py +61 -0
  76. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/tests/test_query_auto_promote.py +4 -3
  77. maxc_cli-0.3.1/tests/test_query_result_csv_fallback.py +235 -0
  78. maxc_cli-0.3.1/tests/test_skill_cli_consistency.py +75 -0
  79. maxc_cli-0.3.1/tests/test_skill_renderer.py +201 -0
  80. maxc_cli-0.2.5/pyproject.toml +0 -7
  81. maxc_cli-0.2.5/src/maxc_cli/backend/odps.py +0 -134
  82. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/MANIFEST.in +0 -0
  83. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/scripts/pyinstaller_entry.py +0 -0
  84. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/scripts/regression_test.py +0 -0
  85. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/setup.cfg +0 -0
  86. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/audit.py +0 -0
  87. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/skills/agents/openai.yaml +0 -0
  88. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli/store.py +0 -0
  89. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli.egg-info/dependency_links.txt +0 -0
  90. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli.egg-info/entry_points.txt +0 -0
  91. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli.egg-info/requires.txt +0 -0
  92. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/src/maxc_cli.egg-info/top_level.txt +0 -0
  93. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/tests/test_e2e_smoke.py +0 -0
  94. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/tests/test_error_self_correction.py +0 -0
  95. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/tests/test_helpers_csv.py +1 -1
  96. {maxc_cli-0.2.5 → maxc_cli-0.3.1}/tests/test_setting_parser.py +3 -3
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maxc-cli
3
- Version: 0.2.5
3
+ Version: 0.3.1
4
4
  Summary: Agent-native MaxCompute CLI for external coding agents
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Classifier: Programming Language :: Python :: 3.9
@@ -29,9 +29,12 @@ MaxCompute CLI — 不是 Agent,而是给 Agent 调用的结构化工具层。
29
29
  ```bash
30
30
  pip install maxc-cli
31
31
 
32
- # 认证
33
- maxc auth login --from-env --json # 从环境变量
34
- maxc auth login --access-id ID --secret-access-key KEY --project PROJ --endpoint URL --json
32
+ # 认证(推荐:交互式 Catalog Picker,无需手填 project)
33
+ maxc auth login --access-id ID --access-key-secret KEY --json
34
+
35
+ # 其他方式
36
+ maxc auth login --from-env --json # 从环境变量
37
+ maxc auth login --access-id ID --access-key-secret KEY --project PROJ --endpoint URL --json # CI / 脚本:显式 project
35
38
 
36
39
  # 确认就绪
37
40
  maxc auth whoami --json
@@ -56,7 +59,7 @@ maxc query "SELECT * FROM schema.table WHERE ds='20260415'" --json
56
59
  | **session** | `set`, `show`, `unset` | 项目/Schema 切换 |
57
60
  | **diff** | `schema`, `partition`, `data` | 表结构/分区/数据对比 |
58
61
  | **cache** | `build`, `build-status`, `status`, `clear` | 元数据缓存管理 |
59
- | **agent** | `context`, `skill`, `install-skill` | Agent 集成与 SKILL 安装 |
62
+ | **agent** | `context`, `skill install`, `skill list` | Agent 集成与 SKILL 安装 |
60
63
 
61
64
  所有命令支持 `--json` 输出 Envelope v2.0 结构化响应。支持 `--format json|markdown|brief`(全局标志)。
62
65
 
@@ -66,16 +69,16 @@ maxc query "SELECT * FROM schema.table WHERE ds='20260415'" --json
66
69
 
67
70
  SKILL HUB 安装 SKILL → Agent 读 SKILL.md → Agent 自己 `pip install maxc-cli`。
68
71
 
69
- ### 方式 2:install-skill(内网兜底)
72
+ ### 方式 2:agent skill install(内网兜底)
70
73
 
71
74
  先 pip install,再一键注册到 Agent 平台:
72
75
 
73
76
  ```bash
74
77
  pip install maxc-cli
75
- maxc agent install-skill claude-code # 或 cursor / windsurf / codex / qwen / qoder / qoderwork
78
+ maxc agent skill install claude-code # 或 cursor / windsurf / codex / qwen / qoder / qoderwork
76
79
  ```
77
80
 
78
- `install-skill` 从独立仓库 [maxcompute-cli-guidance](https://gitlab.alibaba-inc.com/aone-open-skill/maxcompute-cli-guidance) 获取 SKILL.md + references/ 并安装到目标平台目录。
81
+ `agent skill install` 从独立仓库 [maxcompute-cli-guidance](https://gitlab.alibaba-inc.com/aone-open-skill/maxcompute-cli-guidance) 获取 SKILL.md + references/ 并安装到目标平台目录。
79
82
 
80
83
  ### preflight 检查
81
84
 
@@ -180,7 +183,9 @@ src/maxc_cli/
180
183
  ## 限制
181
184
 
182
185
  - **只读**:CLI 强制 SELECT-only,不支持 DDL/DML
183
- - **auth login**:AK/SK 明文存储于 `~/.maxc/config.yaml`(文件权限 0600
186
+ - **auth login**:AK/SK 明文存储于 `~/.maxc/config.yaml`(文件权限 0600)。
187
+ 省略 `--project` 时通过 Catalog API 弹交互式 project picker(需 TTY,仅支持中国区 project)。
188
+ CI 用 `--no-picker`;想重选已保存的 project 用 `--reselect`;非中国区用 `--catalog-endpoint` 覆盖。
184
189
  - **list-tables 分页**:CLI 侧 offset token,非服务端游标
185
190
  - **diff data**:按主键快照对比,非全量 diff
186
191
 
@@ -7,9 +7,12 @@ MaxCompute CLI — 不是 Agent,而是给 Agent 调用的结构化工具层。
7
7
  ```bash
8
8
  pip install maxc-cli
9
9
 
10
- # 认证
11
- maxc auth login --from-env --json # 从环境变量
12
- maxc auth login --access-id ID --secret-access-key KEY --project PROJ --endpoint URL --json
10
+ # 认证(推荐:交互式 Catalog Picker,无需手填 project)
11
+ maxc auth login --access-id ID --access-key-secret KEY --json
12
+
13
+ # 其他方式
14
+ maxc auth login --from-env --json # 从环境变量
15
+ maxc auth login --access-id ID --access-key-secret KEY --project PROJ --endpoint URL --json # CI / 脚本:显式 project
13
16
 
14
17
  # 确认就绪
15
18
  maxc auth whoami --json
@@ -34,7 +37,7 @@ maxc query "SELECT * FROM schema.table WHERE ds='20260415'" --json
34
37
  | **session** | `set`, `show`, `unset` | 项目/Schema 切换 |
35
38
  | **diff** | `schema`, `partition`, `data` | 表结构/分区/数据对比 |
36
39
  | **cache** | `build`, `build-status`, `status`, `clear` | 元数据缓存管理 |
37
- | **agent** | `context`, `skill`, `install-skill` | Agent 集成与 SKILL 安装 |
40
+ | **agent** | `context`, `skill install`, `skill list` | Agent 集成与 SKILL 安装 |
38
41
 
39
42
  所有命令支持 `--json` 输出 Envelope v2.0 结构化响应。支持 `--format json|markdown|brief`(全局标志)。
40
43
 
@@ -44,16 +47,16 @@ maxc query "SELECT * FROM schema.table WHERE ds='20260415'" --json
44
47
 
45
48
  SKILL HUB 安装 SKILL → Agent 读 SKILL.md → Agent 自己 `pip install maxc-cli`。
46
49
 
47
- ### 方式 2:install-skill(内网兜底)
50
+ ### 方式 2:agent skill install(内网兜底)
48
51
 
49
52
  先 pip install,再一键注册到 Agent 平台:
50
53
 
51
54
  ```bash
52
55
  pip install maxc-cli
53
- maxc agent install-skill claude-code # 或 cursor / windsurf / codex / qwen / qoder / qoderwork
56
+ maxc agent skill install claude-code # 或 cursor / windsurf / codex / qwen / qoder / qoderwork
54
57
  ```
55
58
 
56
- `install-skill` 从独立仓库 [maxcompute-cli-guidance](https://gitlab.alibaba-inc.com/aone-open-skill/maxcompute-cli-guidance) 获取 SKILL.md + references/ 并安装到目标平台目录。
59
+ `agent skill install` 从独立仓库 [maxcompute-cli-guidance](https://gitlab.alibaba-inc.com/aone-open-skill/maxcompute-cli-guidance) 获取 SKILL.md + references/ 并安装到目标平台目录。
57
60
 
58
61
  ### preflight 检查
59
62
 
@@ -158,7 +161,9 @@ src/maxc_cli/
158
161
  ## 限制
159
162
 
160
163
  - **只读**:CLI 强制 SELECT-only,不支持 DDL/DML
161
- - **auth login**:AK/SK 明文存储于 `~/.maxc/config.yaml`(文件权限 0600
164
+ - **auth login**:AK/SK 明文存储于 `~/.maxc/config.yaml`(文件权限 0600)。
165
+ 省略 `--project` 时通过 Catalog API 弹交互式 project picker(需 TTY,仅支持中国区 project)。
166
+ CI 用 `--no-picker`;想重选已保存的 project 用 `--reselect`;非中国区用 `--catalog-endpoint` 覆盖。
162
167
  - **list-tables 分页**:CLI 侧 offset token,非服务端游标
163
168
  - **diff data**:按主键快照对比,非全量 diff
164
169
 
@@ -0,0 +1,22 @@
1
+ [build-system]
2
+ requires = ["setuptools>=42", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [tool.pytest.ini_options]
6
+ pythonpath = ["src"]
7
+ testpaths = ["tests"]
8
+
9
+ [tool.ruff]
10
+ target-version = "py38"
11
+ line-length = 100
12
+
13
+ [tool.ruff.lint]
14
+ select = ["E", "F", "I", "UP"]
15
+ # E501 ignored for now: legacy codebase has 300+ long lines (mostly long SQL
16
+ # strings, dense parameter lists, and docstrings). Tighten in a later sweep
17
+ # once we've decided whether to wrap at the source or raise line-length.
18
+ ignore = ["E501"]
19
+
20
+ [tool.ruff.lint.per-file-ignores]
21
+ # Tests put `pytestmark = pytest.mark.unit` before imports on purpose.
22
+ "tests/*" = ["E402"]
@@ -9,7 +9,7 @@ README = ROOT / "README.md"
9
9
 
10
10
  setup(
11
11
  name="maxc-cli",
12
- version="0.2.5",
12
+ version="0.3.1",
13
13
  description="Agent-native MaxCompute CLI for external coding agents",
14
14
  long_description=README.read_text(encoding="utf-8"),
15
15
  long_description_content_type="text/markdown",
@@ -2,4 +2,4 @@
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "0.2.5"
5
+ __version__ = "0.3.1"
@@ -1,6 +1,5 @@
1
1
 
2
2
  from .cli import main
3
3
 
4
-
5
4
  if __name__ == "__main__":
6
5
  raise SystemExit(main())
@@ -0,0 +1,187 @@
1
+ """Per-command sample text for ``--help``. Keys are dotted command names.
2
+
3
+ Style: 1-3 lines, concrete and copy-pasteable. Prefer realistic project names
4
+ ('my_proj') over placeholders ('PROJECT'). Wrap user-facing commands in the
5
+ quoting style they'd actually type.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ SAMPLES: dict[str, str] = {
10
+ "__top__": (
11
+ "maxc auth login\n"
12
+ 'maxc query "SELECT 1"\n'
13
+ "maxc meta list-tables --project my_proj"
14
+ ),
15
+ # ── query ──────────────────────────────────────────────────────────────
16
+ "query": (
17
+ 'maxc query "SELECT 1"\n'
18
+ 'maxc query cost "SELECT * FROM big_table"\n'
19
+ "maxc query explain \"SELECT * FROM t WHERE dt='20260101'\""
20
+ ),
21
+ # ── auth ───────────────────────────────────────────────────────────────
22
+ "auth": "maxc auth login\nmaxc auth whoami --json",
23
+ "auth.login": (
24
+ "maxc auth login # interactive picker\n"
25
+ "maxc auth login --access-id AK --secret-access-key SK --no-picker"
26
+ ),
27
+ "auth.login-external": (
28
+ "maxc auth login-external --process-command 'curl -s https://my-sts/token'"
29
+ ),
30
+ "auth.whoami": "maxc auth whoami\nmaxc auth whoami --json",
31
+ "auth.can-i": "maxc auth can-i --table my_proj.my_table --operation SELECT",
32
+ # ── job ────────────────────────────────────────────────────────────────
33
+ "job": (
34
+ 'maxc job submit "SELECT count(*) FROM my_table"\n'
35
+ "maxc job list --limit 20\n"
36
+ "maxc job status <job_id>"
37
+ ),
38
+ "job.submit": (
39
+ 'maxc job submit "SELECT count(*) FROM my_table"\n'
40
+ 'maxc job submit --file query.sql --project my_proj'
41
+ ),
42
+ "job.status": "maxc job status <job_id>\nmaxc job status <job_id> --json",
43
+ "job.wait": (
44
+ "maxc job wait <job_id>\n"
45
+ "maxc job wait <job_id> --timeout 600 --stream"
46
+ ),
47
+ "job.diagnose": "maxc job diagnose <job_id>\nmaxc job diagnose <job_id> --json",
48
+ "job.result": (
49
+ "maxc job result <job_id>\n"
50
+ "maxc job result <job_id> --max-rows 1000 --json"
51
+ ),
52
+ "job.cancel": "maxc job cancel <job_id>",
53
+ "job.list": "maxc job list\nmaxc job list --limit 50 --json",
54
+ # ── meta ───────────────────────────────────────────────────────────────
55
+ "meta": (
56
+ "maxc meta list-tables --project my_proj\n"
57
+ "maxc meta describe my_table\n"
58
+ "maxc meta search orders"
59
+ ),
60
+ "meta.list-tables": (
61
+ "maxc meta list-tables --project my_proj\n"
62
+ "maxc meta list-tables --schema default --limit 50 --json"
63
+ ),
64
+ "meta.describe": (
65
+ "maxc meta describe my_table\n"
66
+ "maxc meta describe my_proj.my_table --full --json"
67
+ ),
68
+ "meta.search": (
69
+ "maxc meta search orders\n"
70
+ "maxc meta search user --project my_proj --json"
71
+ ),
72
+ "meta.search-columns": (
73
+ "maxc meta search-columns user_id\n"
74
+ "maxc meta search-columns dt --project my_proj --json"
75
+ ),
76
+ "meta.latest-partition": (
77
+ "maxc meta latest-partition my_table\n"
78
+ "maxc meta latest-partition my_proj.my_table --json"
79
+ ),
80
+ "meta.freshness": (
81
+ "maxc meta freshness my_table\n"
82
+ "maxc meta freshness my_proj.my_table --json"
83
+ ),
84
+ "meta.partitions": (
85
+ "maxc meta partitions my_table\n"
86
+ "maxc meta partitions my_proj.my_table --limit 50 --json"
87
+ ),
88
+ "meta.list-projects": "maxc meta list-projects\nmaxc meta list-projects --json",
89
+ "meta.list-schemas": (
90
+ "maxc meta list-schemas\n"
91
+ "maxc meta list-schemas --project my_proj --json"
92
+ ),
93
+ # ── meta semantic ──────────────────────────────────────────────────────
94
+ "meta.semantic": (
95
+ "maxc meta semantic set my_table --desc 'Order facts'\n"
96
+ "maxc meta semantic get my_table\n"
97
+ "maxc meta semantic list-missing"
98
+ ),
99
+ "meta.semantic.set": (
100
+ "maxc meta semantic set my_table --desc 'Order facts'\n"
101
+ "maxc meta semantic set my_table --use-cases reporting analytics --sample-questions 'top users by revenue'"
102
+ ),
103
+ "meta.semantic.get": (
104
+ "maxc meta semantic get my_table\n"
105
+ "maxc meta semantic get my_table --json"
106
+ ),
107
+ "meta.semantic.list-missing": (
108
+ "maxc meta semantic list-missing\n"
109
+ "maxc meta semantic list-missing --json"
110
+ ),
111
+ # ── session ────────────────────────────────────────────────────────────
112
+ "session": (
113
+ "maxc session set --project my_proj\n"
114
+ "maxc session show\n"
115
+ "maxc session unset"
116
+ ),
117
+ "session.set": (
118
+ "maxc session set --project my_proj\n"
119
+ "maxc session set --project my_proj --schema default"
120
+ ),
121
+ "session.show": "maxc session show\nmaxc session show --json",
122
+ "session.unset": "maxc session unset",
123
+ # ── data ───────────────────────────────────────────────────────────────
124
+ "data": (
125
+ "maxc data sample my_table --rows 10\n"
126
+ "maxc data profile my_table\n"
127
+ "maxc data download my_table --output rows.csv"
128
+ ),
129
+ "data.sample": (
130
+ "maxc data sample my_table --rows 10\n"
131
+ "maxc data sample my_table --partition \"dt='20260101'\" --columns id,name"
132
+ ),
133
+ "data.profile": (
134
+ "maxc data profile my_table\n"
135
+ "maxc data profile my_table --partition \"dt='20260101'\" --json"
136
+ ),
137
+ "data.upload": (
138
+ "maxc data upload my_table --file rows.csv\n"
139
+ "maxc data upload my_table --file rows.tsv --delimiter $'\\t' --partition \"dt='20260101'\" --overwrite"
140
+ ),
141
+ "data.download": (
142
+ "maxc data download my_table --output rows.csv\n"
143
+ "maxc data download my_table --output rows.csv --columns id,name --limit 1000"
144
+ ),
145
+ # ── agent ──────────────────────────────────────────────────────────────
146
+ "agent": (
147
+ "maxc agent context\n"
148
+ "maxc agent skill\n"
149
+ "maxc agent skill install claude-code"
150
+ ),
151
+ "agent.context": "maxc agent context\nmaxc agent context --json",
152
+ "agent.skill": "maxc agent skill\nmaxc agent skill --json",
153
+ "agent.skill.install": (
154
+ "maxc agent skill install claude-code\n"
155
+ "maxc agent skill install cursor --invocation aliyun-maxc"
156
+ ),
157
+ "agent.skill.update": (
158
+ "maxc agent skill update cursor\n"
159
+ "maxc agent skill update --all"
160
+ ),
161
+ "agent.skill.uninstall": "maxc agent skill uninstall cursor",
162
+ "agent.skill.list": "maxc agent skill list --json",
163
+ "agent.skill.diff": "maxc agent skill diff cursor --unified",
164
+ "agent.skill.path": "maxc agent skill path cursor\nmaxc agent skill path --source",
165
+ # ── cache ──────────────────────────────────────────────────────────────
166
+ "cache": (
167
+ "maxc cache build --project my_proj\n"
168
+ "maxc cache status --project my_proj\n"
169
+ "maxc cache clear --project my_proj"
170
+ ),
171
+ "cache.build": (
172
+ "maxc cache build --project my_proj\n"
173
+ "maxc cache build --project my_proj --schema default --async"
174
+ ),
175
+ "cache.build-status": (
176
+ "maxc cache build-status --project my_proj\n"
177
+ "maxc cache build-status --project my_proj --build-id <id> --json"
178
+ ),
179
+ "cache.status": (
180
+ "maxc cache status --project my_proj\n"
181
+ "maxc cache status --project my_proj --schema default --json"
182
+ ),
183
+ "cache.clear": (
184
+ "maxc cache clear --project my_proj\n"
185
+ "maxc cache clear --project my_proj --schema default"
186
+ ),
187
+ }
@@ -0,0 +1,188 @@
1
+ """Agent platform registry — single source of truth for SKILL install targets.
2
+
3
+ Each Platform declares where SKILL.md (and optional extra files) live for a
4
+ specific agent product. The MaxCApp skill_install/update/uninstall/list/diff/path
5
+ methods all consult REGISTRY here, so adding a new platform = one entry below
6
+ (no code changes elsewhere).
7
+
8
+ CRITICAL: install_root values must stay byte-equivalent to the legacy
9
+ `_SKILL_PLATFORMS` table in `app.py:3375-3411` — see tests/test_agent_platforms.py
10
+ ::test_install_root_matches_legacy_paths.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import os
15
+ from dataclasses import dataclass
16
+ from pathlib import Path
17
+ from typing import Callable
18
+
19
+ RenderFn = Callable[[Path, str, str], None]
20
+ # (effective_install_root, cli, cli_module) -> None
21
+ # Callee mkdir + write_text directly; returns nothing.
22
+ # The first arg is the *effective* install root (after --dir override is applied
23
+ # by the caller), not platform.install_root.
24
+ # Args (cli, cli_module) are currently only consumed by SKILL.md template render;
25
+ # extra_files with fixed-literal output (e.g., plugin.json) may ignore them.
26
+ # Keep them in the signature so future extra_files using {{cli}} placeholders
27
+ # don't need a protocol bump.
28
+
29
+
30
+ @dataclass(frozen=True)
31
+ class ExtraFile:
32
+ """Non-SKILL file co-installed with the skill (e.g., claude-plugin manifest).
33
+
34
+ `relative_path` is the discovery anchor for list/diff/uninstall — the
35
+ render_fn MUST write its content at `effective_install_root / relative_path`,
36
+ otherwise diff will report "file missing". Enforced by
37
+ test_render_claude_plugin_writes_declared_path.
38
+ """
39
+ relative_path: str
40
+ render_fn_name: str # name of a callable in this module matching RenderFn
41
+
42
+
43
+ @dataclass(frozen=True)
44
+ class Platform:
45
+ name: str
46
+ install_root: Path
47
+ skill_subpath: str | None = None # SKILL contents location relative to install_root;
48
+ # None = write into effective_install_root directly
49
+ # (claude-code uses this — SKILL.md and .claude-plugin/
50
+ # are siblings).
51
+ requires_dir: bool = True
52
+ extra_files: tuple[ExtraFile, ...] = ()
53
+ aliases: tuple[str, ...] = ()
54
+ deprecated_aliases: tuple[str, ...] = ()
55
+ next_step_hint: str = ""
56
+
57
+
58
+ # Invocation map: cli front-end name → (cli, cli_module) substituted into
59
+ # SKILL templates. Migrated here from app.py:3363-3372 so the registry is the
60
+ # single source of truth.
61
+ INVOCATIONS: dict[str, dict[str, str]] = {
62
+ "maxc": {
63
+ "cli": "maxc",
64
+ # `python3 -m` (not `python`) matches the legacy _SKILL_INVOCATIONS at
65
+ # app.py:3363-3372 byte-for-byte — changing this re-renders SKILL.md for
66
+ # every existing install and triggers a phantom `agent skill diff`.
67
+ "cli_module": "python3 -m maxc_cli",
68
+ },
69
+ "aliyun-maxc": {
70
+ "cli": "aliyun maxc",
71
+ "cli_module": "aliyun maxc",
72
+ },
73
+ }
74
+
75
+
76
+ def _claude_root() -> Path:
77
+ return Path.home() / ".claude" / "plugins" / "maxc-cli"
78
+
79
+
80
+ def _codex_root() -> Path:
81
+ # Lazy — must re-read CODEX_HOME at call time, not module-import time,
82
+ # so the env var can be overridden in tests/CI.
83
+ return (
84
+ Path(os.environ.get("CODEX_HOME", str(Path.home() / ".codex")))
85
+ / "skills"
86
+ / "maxcompute-cli-guidance"
87
+ )
88
+
89
+
90
+ def _simple_root(dotdir: str) -> Path:
91
+ return Path.home() / dotdir / "skills" / "maxcompute-cli-guidance"
92
+
93
+
94
+ def render_claude_plugin(install_dir: Path, cli: str, cli_module: str) -> None:
95
+ """Write `.claude-plugin/plugin.json` under `install_dir`.
96
+
97
+ Byte-equivalent to legacy literal at `app.py:3493-3499`. Do NOT reformat
98
+ via json.dumps(indent=…) — that re-flows whitespace and causes
99
+ `agent skill diff` to report a phantom delta against pre-existing installs.
100
+ """
101
+ meta_dir = install_dir / ".claude-plugin"
102
+ meta_dir.mkdir(parents=True, exist_ok=True)
103
+ (meta_dir / "plugin.json").write_text(
104
+ '{\n "name": "maxc-cli",\n'
105
+ ' "description": "MaxCompute/ODPS CLI — query tables, view schema, '
106
+ 'search metadata, execute SQL, check partitions, sample data, '
107
+ 'track jobs. Install via: pip install maxc-cli",\n'
108
+ ' "author": { "name": "maxc-cli contributors" }\n}\n',
109
+ encoding="utf-8",
110
+ )
111
+
112
+
113
+ def _build_registry() -> tuple[Platform, ...]:
114
+ """Build REGISTRY lazily so tests can monkeypatch HOME/CODEX_HOME + reload."""
115
+ return (
116
+ Platform(
117
+ name="claude-code",
118
+ install_root=_claude_root(),
119
+ skill_subpath=None,
120
+ extra_files=(ExtraFile(".claude-plugin/plugin.json", "render_claude_plugin"),),
121
+ next_step_hint="Run /reload-plugins in Claude Code to activate",
122
+ ),
123
+ Platform(name="cursor", install_root=_simple_root(".cursor"),
124
+ next_step_hint="Restart Cursor to activate"),
125
+ Platform(name="windsurf", install_root=_simple_root(".windsurf"),
126
+ next_step_hint="Restart Windsurf to activate"),
127
+ Platform(name="codex", install_root=_codex_root(),
128
+ next_step_hint="Restart Codex to activate"),
129
+ Platform(name="qwen", install_root=_simple_root(".qwen"),
130
+ next_step_hint="Restart Qwen to activate"),
131
+ Platform(name="qoder", install_root=_simple_root(".qoder"),
132
+ next_step_hint="Restart Qoder to activate"),
133
+ Platform(name="qoderwork", install_root=_simple_root(".qoderwork"),
134
+ next_step_hint="Restart QoderWork to activate"),
135
+ )
136
+
137
+
138
+ REGISTRY: tuple[Platform, ...] = _build_registry()
139
+
140
+
141
+ def resolve(name_or_alias: str) -> Platform:
142
+ """Return the Platform matching name / alias / deprecated_alias.
143
+
144
+ Raises KeyError when no match. Caller is responsible for emitting a
145
+ stderr warning when the input matches `deprecated_aliases`.
146
+ """
147
+ for p in REGISTRY:
148
+ if name_or_alias == p.name:
149
+ return p
150
+ if name_or_alias in p.aliases:
151
+ return p
152
+ if name_or_alias in p.deprecated_aliases:
153
+ return p
154
+ raise KeyError(f"Unknown agent platform: {name_or_alias!r}")
155
+
156
+
157
+ def all_platforms() -> tuple[Platform, ...]:
158
+ return REGISTRY
159
+
160
+
161
+ def is_deprecated_alias(name: str) -> bool:
162
+ for p in REGISTRY:
163
+ if name in p.deprecated_aliases:
164
+ return True
165
+ return False
166
+
167
+
168
+ def effective_target(platform: Platform, dir_override: Path | None) -> Path:
169
+ """Resolve where SKILL content for `platform` should be written.
170
+
171
+ effective_target = (--dir override OR platform.install_root) / (skill_subpath or "")
172
+ """
173
+ root = dir_override if dir_override is not None else platform.install_root
174
+ if platform.skill_subpath:
175
+ return root / platform.skill_subpath
176
+ return root
177
+
178
+
179
+ def get_render_fn(name: str) -> RenderFn:
180
+ """Resolve a render_fn name (declared in ExtraFile) into the actual callable.
181
+
182
+ Kept here so caller can `getattr(agent_platforms, name)` without exposing the
183
+ module-internal naming convention to MaxCApp.
184
+ """
185
+ fn = globals().get(name)
186
+ if not callable(fn):
187
+ raise KeyError(f"render_fn not found in agent_platforms: {name!r}")
188
+ return fn # type: ignore[return-value]