maxc-cli 0.1.2__tar.gz → 0.1.4__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 (67) hide show
  1. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/MANIFEST.in +1 -0
  2. maxc_cli-0.1.4/PKG-INFO +156 -0
  3. maxc_cli-0.1.4/README.md +135 -0
  4. maxc_cli-0.1.4/scripts/regression_test.py +326 -0
  5. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/setup.py +8 -1
  6. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/src/maxc_cli/__init__.py +1 -1
  7. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/src/maxc_cli/app.py +504 -309
  8. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/src/maxc_cli/auth_providers.py +291 -151
  9. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/src/maxc_cli/backend/auth.py +29 -2
  10. maxc_cli-0.1.4/src/maxc_cli/backend/catalog.py +276 -0
  11. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/src/maxc_cli/backend/data.py +33 -2
  12. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/src/maxc_cli/backend/job.py +80 -10
  13. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/src/maxc_cli/backend/meta.py +124 -9
  14. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/src/maxc_cli/backend/odps.py +13 -16
  15. maxc_cli-0.1.4/src/maxc_cli/backend/query.py +234 -0
  16. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/src/maxc_cli/cache.py +56 -0
  17. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/src/maxc_cli/cli.py +192 -88
  18. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/src/maxc_cli/config.py +50 -0
  19. maxc_cli-0.1.4/src/maxc_cli/exceptions.py +145 -0
  20. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/src/maxc_cli/helpers.py +113 -15
  21. maxc_cli-0.1.4/src/maxc_cli/masking.py +104 -0
  22. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/src/maxc_cli/models.py +48 -55
  23. maxc_cli-0.1.4/src/maxc_cli/setting_parser.py +126 -0
  24. {maxc_cli-0.1.2/skills/use-maxc-cli → maxc_cli-0.1.4/src/maxc_cli/skills}/SKILL.md +96 -9
  25. {maxc_cli-0.1.2/skills/use-maxc-cli → maxc_cli-0.1.4/src/maxc_cli/skills}/references/bootstrap-auth.md +77 -0
  26. {maxc_cli-0.1.2/skills/use-maxc-cli → maxc_cli-0.1.4/src/maxc_cli/skills}/references/command-patterns.md +21 -13
  27. maxc_cli-0.1.4/src/maxc_cli/skills/references/maxcompute-sql-notes.md +136 -0
  28. maxc_cli-0.1.4/src/maxc_cli/skills/references/migrate-from-odpscmd.md +149 -0
  29. maxc_cli-0.1.4/src/maxc_cli/skills/references/partition-guide.md +135 -0
  30. {maxc_cli-0.1.2/skills/use-maxc-cli → maxc_cli-0.1.4/src/maxc_cli/skills}/references/setup-install.md +3 -7
  31. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/src/maxc_cli/utils.py +9 -1
  32. maxc_cli-0.1.4/src/maxc_cli.egg-info/PKG-INFO +156 -0
  33. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/src/maxc_cli.egg-info/SOURCES.txt +19 -8
  34. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/tests/test_agent_hints_and_cli.py +17 -12
  35. maxc_cli-0.1.4/tests/test_agent_skill_commands_context.py +438 -0
  36. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/tests/test_cache.py +2 -0
  37. maxc_cli-0.1.4/tests/test_catalog.py +390 -0
  38. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/tests/test_cli_mock.py +30 -350
  39. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/tests/test_compat.py +2 -0
  40. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/tests/test_e2e_smoke.py +2 -0
  41. maxc_cli-0.1.4/tests/test_error_self_correction.py +75 -0
  42. maxc_cli-0.1.4/tests/test_external_auth.py +664 -0
  43. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/tests/test_integration.py +2 -0
  44. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/tests/test_integration_real.py +2 -36
  45. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/tests/test_job_improvements.py +2 -0
  46. maxc_cli-0.1.4/tests/test_masking.py +134 -0
  47. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/tests/test_query_auto_promote.py +7 -5
  48. maxc_cli-0.1.4/tests/test_setting_parser.py +207 -0
  49. maxc_cli-0.1.2/PKG-INFO +0 -220
  50. maxc_cli-0.1.2/README.md +0 -199
  51. maxc_cli-0.1.2/scripts/sync_codex_skill.py +0 -68
  52. maxc_cli-0.1.2/skills/use-maxc-cli/.DS_Store +0 -0
  53. maxc_cli-0.1.2/src/maxc_cli/backend/query.py +0 -148
  54. maxc_cli-0.1.2/src/maxc_cli/exceptions.py +0 -99
  55. maxc_cli-0.1.2/src/maxc_cli.egg-info/PKG-INFO +0 -220
  56. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/pyproject.toml +0 -0
  57. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/setup.cfg +0 -0
  58. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/src/maxc_cli/__main__.py +0 -0
  59. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/src/maxc_cli/audit.py +0 -0
  60. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/src/maxc_cli/backend/__init__.py +0 -0
  61. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/src/maxc_cli/output.py +0 -0
  62. {maxc_cli-0.1.2/skills/use-maxc-cli → maxc_cli-0.1.4/src/maxc_cli/skills}/agents/openai.yaml +0 -0
  63. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/src/maxc_cli/store.py +0 -0
  64. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/src/maxc_cli.egg-info/dependency_links.txt +0 -0
  65. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/src/maxc_cli.egg-info/entry_points.txt +0 -0
  66. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/src/maxc_cli.egg-info/requires.txt +0 -0
  67. {maxc_cli-0.1.2 → maxc_cli-0.1.4}/src/maxc_cli.egg-info/top_level.txt +0 -0
@@ -1,2 +1,3 @@
1
+ recursive-include src/maxc_cli/skills *
1
2
  recursive-include skills/use-maxc-cli *
2
3
  recursive-include scripts *.py
@@ -0,0 +1,156 @@
1
+ Metadata-Version: 2.4
2
+ Name: maxc-cli
3
+ Version: 0.1.4
4
+ Summary: Agent-native MaxCompute CLI for external coding agents
5
+ Classifier: Programming Language :: Python :: 3
6
+ Classifier: Programming Language :: Python :: 3.8
7
+ Classifier: Programming Language :: Python :: 3.9
8
+ Classifier: Programming Language :: Python :: 3.10
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Requires-Python: >=3.8,<3.13
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: PyYAML>=5.4
14
+ Requires-Dist: pyodps
15
+ Dynamic: classifier
16
+ Dynamic: description
17
+ Dynamic: description-content-type
18
+ Dynamic: requires-dist
19
+ Dynamic: requires-python
20
+ Dynamic: summary
21
+
22
+ # maxc-cli
23
+
24
+ MaxCompute CLI — 不是 Agent,而是给 Agent 调用的结构化工具层。
25
+
26
+ ## 快速开始
27
+
28
+ ```bash
29
+ pip install maxc-cli
30
+
31
+ # 认证
32
+ maxc auth login --from-env --json # 从环境变量
33
+ maxc auth login --access-id ID --secret-access-key KEY --project PROJ --endpoint URL --json
34
+
35
+ # 确认就绪
36
+ maxc auth whoami --json
37
+ maxc agent context --json
38
+
39
+ # 用
40
+ maxc meta search "销售" --json
41
+ maxc meta describe schema.table --json
42
+ maxc query cost "SELECT * FROM schema.table WHERE ds='20260415'" --json
43
+ maxc query "SELECT * FROM schema.table WHERE ds='20260415'" --json
44
+ ```
45
+
46
+ ## 命令一览
47
+
48
+ | 家族 | 命令 | 说明 |
49
+ |------|------|------|
50
+ | **query** | `query [run]`, `query cost`, `query explain` | SQL 执行、成本估算、执行计划 |
51
+ | **job** | `submit`, `status`, `wait`, `result`, `cancel`, `diagnose`, `list` | 异步任务全生命周期 |
52
+ | **meta** | `list-tables`, `describe`, `search`, `search-columns`, `partitions`, `latest-partition`, `freshness`, `list-projects`, `list-schemas`, `semantic set/get/list-missing` | 元数据发现与语义管理 |
53
+ | **data** | `sample`, `profile` | 数据采样与画像 |
54
+ | **auth** | `login`, `login-external`, `whoami`, `can-i` | 认证与权限 |
55
+ | **session** | `set`, `show`, `unset` | 项目/Schema 切换 |
56
+ | **diff** | `schema`, `partition`, `data` | 表结构/分区/数据对比 |
57
+ | **cache** | `build`, `build-status`, `status`, `clear` | 元数据缓存管理 |
58
+ | **agent** | `context`, `skill`, `install-skill` | Agent 集成与 SKILL 安装 |
59
+
60
+ 所有命令支持 `--json` 输出 Envelope v2.0 结构化响应。
61
+
62
+ ## Agent 集成
63
+
64
+ ### 方式 1:SKILL HUB(主路径)
65
+
66
+ SKILL HUB 安装 SKILL → Agent 读 SKILL.md → Agent 自己 `pip install maxc-cli`。
67
+
68
+ ### 方式 2:install-skill(内网兜底)
69
+
70
+ 先 pip install,再一键注册到 Agent 平台:
71
+
72
+ ```bash
73
+ pip install maxc-cli
74
+ maxc agent install-skill claude-code # 或 cursor / windsurf / codex / qwen / qoder / qoderwork
75
+ ```
76
+
77
+ `install-skill` 从独立仓库 [maxcompute-cli-guidance](https://gitlab.alibaba-inc.com/aone-open-skill/maxcompute-cli-guidance) 获取 SKILL.md + references/ 并安装到目标平台目录。
78
+
79
+ ### preflight 检查
80
+
81
+ Agent 启动时应先运行:
82
+
83
+ ```bash
84
+ maxc agent context --json # 版本、认证状态、后端可达性、能力矩阵
85
+ maxc agent skill --json # SKILL.md 路径与 min_cli_version
86
+ ```
87
+
88
+ ## Envelope v2.0
89
+
90
+ 所有 `--json` 输出遵循统一结构:
91
+
92
+ ```json
93
+ {
94
+ "version": "2.0",
95
+ "command": "meta.describe",
96
+ "command_id": "meta.describe",
97
+ "status": "success | failure",
98
+ "data": { ... },
99
+ "metadata": { ... },
100
+ "error": null | { "code": "...", "message": "...", "recovery_steps": [...] },
101
+ "agent_hints": {
102
+ "next_actions": ["maxc meta search --json", ...],
103
+ "action_ids": ["meta.search", ...],
104
+ "insights": [...],
105
+ "warnings": [...]
106
+ }
107
+ }
108
+ ```
109
+
110
+ - `action_ids`:稳定 dot-notation,用于程序化路由(`meta.describe`、`job.wait`)
111
+ - `next_actions`:可直接 copy-paste 的 CLI 命令
112
+ - `error.recovery_steps`:错误码对应的恢复步骤
113
+
114
+ 详见 [`docs/ENVELOPE_SPEC.md`](docs/ENVELOPE_SPEC.md)。
115
+
116
+ ## 项目结构
117
+
118
+ ```
119
+ src/maxc_cli/
120
+ ├── cli.py # argparse 命令注册
121
+ ├── app.py # MaxCApp 业务逻辑
122
+ ├── models.py # Envelope / AgentHints / QueryResult
123
+ ├── exceptions.py # ErrorPayload + 9 个异常子类 + recovery_steps
124
+ ├── config.py # YAML 配置加载
125
+ ├── cache.py # LocalCache (SQLite)
126
+ ├── store.py # JobStore (SQLite)
127
+ ├── output.py # Rich / 纯文本渲染
128
+ ├── auth_providers.py # AK-SK / NCS / 环境变量认证
129
+ ├── backend/ # ODPS 后端(query / meta / catalog / data / diff 五个 mixin)
130
+ └── (skills/ 已迁移至独立仓库 aone-open-skill/maxcompute-cli-guidance)
131
+ ```
132
+
133
+ ## 文档
134
+
135
+ | 文档 | 内容 |
136
+ |------|------|
137
+ | [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) | 三层架构、核心数据流、缓存/认证架构 |
138
+ | [`docs/ENVELOPE_SPEC.md`](docs/ENVELOPE_SPEC.md) | Envelope v2.0 规范、pagination、error codes |
139
+ | [`docs/ODPS_BACKEND.md`](docs/ODPS_BACKEND.md) | ODPS 后端 API 映射、限制与回退行为 |
140
+ | [`docs/design.md`](docs/design.md) | 产品定位与命令体系 |
141
+ | [`docs/implementation.md`](docs/implementation.md) | 当前代码的真实行为和输出契约 |
142
+ | [`docs/roadmap.md`](docs/roadmap.md) | 路线图 |
143
+
144
+ ## 限制
145
+
146
+ - **只读**:CLI 强制 SELECT-only,不支持 DDL/DML
147
+ - **auth login**:AK/SK 明文存储于 `~/.maxc/config.yaml`(文件权限 0600)
148
+ - **list-tables 分页**:CLI 侧 offset token,非服务端游标
149
+ - **diff data**:按主键快照对比,非全量 diff
150
+
151
+ ## 开发
152
+
153
+ ```bash
154
+ pip install -e .
155
+ pytest tests/ -m unit # 142 个单元测试
156
+ ```
@@ -0,0 +1,135 @@
1
+ # maxc-cli
2
+
3
+ MaxCompute CLI — 不是 Agent,而是给 Agent 调用的结构化工具层。
4
+
5
+ ## 快速开始
6
+
7
+ ```bash
8
+ pip install maxc-cli
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
13
+
14
+ # 确认就绪
15
+ maxc auth whoami --json
16
+ maxc agent context --json
17
+
18
+ # 用
19
+ maxc meta search "销售" --json
20
+ maxc meta describe schema.table --json
21
+ maxc query cost "SELECT * FROM schema.table WHERE ds='20260415'" --json
22
+ maxc query "SELECT * FROM schema.table WHERE ds='20260415'" --json
23
+ ```
24
+
25
+ ## 命令一览
26
+
27
+ | 家族 | 命令 | 说明 |
28
+ |------|------|------|
29
+ | **query** | `query [run]`, `query cost`, `query explain` | SQL 执行、成本估算、执行计划 |
30
+ | **job** | `submit`, `status`, `wait`, `result`, `cancel`, `diagnose`, `list` | 异步任务全生命周期 |
31
+ | **meta** | `list-tables`, `describe`, `search`, `search-columns`, `partitions`, `latest-partition`, `freshness`, `list-projects`, `list-schemas`, `semantic set/get/list-missing` | 元数据发现与语义管理 |
32
+ | **data** | `sample`, `profile` | 数据采样与画像 |
33
+ | **auth** | `login`, `login-external`, `whoami`, `can-i` | 认证与权限 |
34
+ | **session** | `set`, `show`, `unset` | 项目/Schema 切换 |
35
+ | **diff** | `schema`, `partition`, `data` | 表结构/分区/数据对比 |
36
+ | **cache** | `build`, `build-status`, `status`, `clear` | 元数据缓存管理 |
37
+ | **agent** | `context`, `skill`, `install-skill` | Agent 集成与 SKILL 安装 |
38
+
39
+ 所有命令支持 `--json` 输出 Envelope v2.0 结构化响应。
40
+
41
+ ## Agent 集成
42
+
43
+ ### 方式 1:SKILL HUB(主路径)
44
+
45
+ SKILL HUB 安装 SKILL → Agent 读 SKILL.md → Agent 自己 `pip install maxc-cli`。
46
+
47
+ ### 方式 2:install-skill(内网兜底)
48
+
49
+ 先 pip install,再一键注册到 Agent 平台:
50
+
51
+ ```bash
52
+ pip install maxc-cli
53
+ maxc agent install-skill claude-code # 或 cursor / windsurf / codex / qwen / qoder / qoderwork
54
+ ```
55
+
56
+ `install-skill` 从独立仓库 [maxcompute-cli-guidance](https://gitlab.alibaba-inc.com/aone-open-skill/maxcompute-cli-guidance) 获取 SKILL.md + references/ 并安装到目标平台目录。
57
+
58
+ ### preflight 检查
59
+
60
+ Agent 启动时应先运行:
61
+
62
+ ```bash
63
+ maxc agent context --json # 版本、认证状态、后端可达性、能力矩阵
64
+ maxc agent skill --json # SKILL.md 路径与 min_cli_version
65
+ ```
66
+
67
+ ## Envelope v2.0
68
+
69
+ 所有 `--json` 输出遵循统一结构:
70
+
71
+ ```json
72
+ {
73
+ "version": "2.0",
74
+ "command": "meta.describe",
75
+ "command_id": "meta.describe",
76
+ "status": "success | failure",
77
+ "data": { ... },
78
+ "metadata": { ... },
79
+ "error": null | { "code": "...", "message": "...", "recovery_steps": [...] },
80
+ "agent_hints": {
81
+ "next_actions": ["maxc meta search --json", ...],
82
+ "action_ids": ["meta.search", ...],
83
+ "insights": [...],
84
+ "warnings": [...]
85
+ }
86
+ }
87
+ ```
88
+
89
+ - `action_ids`:稳定 dot-notation,用于程序化路由(`meta.describe`、`job.wait`)
90
+ - `next_actions`:可直接 copy-paste 的 CLI 命令
91
+ - `error.recovery_steps`:错误码对应的恢复步骤
92
+
93
+ 详见 [`docs/ENVELOPE_SPEC.md`](docs/ENVELOPE_SPEC.md)。
94
+
95
+ ## 项目结构
96
+
97
+ ```
98
+ src/maxc_cli/
99
+ ├── cli.py # argparse 命令注册
100
+ ├── app.py # MaxCApp 业务逻辑
101
+ ├── models.py # Envelope / AgentHints / QueryResult
102
+ ├── exceptions.py # ErrorPayload + 9 个异常子类 + recovery_steps
103
+ ├── config.py # YAML 配置加载
104
+ ├── cache.py # LocalCache (SQLite)
105
+ ├── store.py # JobStore (SQLite)
106
+ ├── output.py # Rich / 纯文本渲染
107
+ ├── auth_providers.py # AK-SK / NCS / 环境变量认证
108
+ ├── backend/ # ODPS 后端(query / meta / catalog / data / diff 五个 mixin)
109
+ └── (skills/ 已迁移至独立仓库 aone-open-skill/maxcompute-cli-guidance)
110
+ ```
111
+
112
+ ## 文档
113
+
114
+ | 文档 | 内容 |
115
+ |------|------|
116
+ | [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) | 三层架构、核心数据流、缓存/认证架构 |
117
+ | [`docs/ENVELOPE_SPEC.md`](docs/ENVELOPE_SPEC.md) | Envelope v2.0 规范、pagination、error codes |
118
+ | [`docs/ODPS_BACKEND.md`](docs/ODPS_BACKEND.md) | ODPS 后端 API 映射、限制与回退行为 |
119
+ | [`docs/design.md`](docs/design.md) | 产品定位与命令体系 |
120
+ | [`docs/implementation.md`](docs/implementation.md) | 当前代码的真实行为和输出契约 |
121
+ | [`docs/roadmap.md`](docs/roadmap.md) | 路线图 |
122
+
123
+ ## 限制
124
+
125
+ - **只读**:CLI 强制 SELECT-only,不支持 DDL/DML
126
+ - **auth login**:AK/SK 明文存储于 `~/.maxc/config.yaml`(文件权限 0600)
127
+ - **list-tables 分页**:CLI 侧 offset token,非服务端游标
128
+ - **diff data**:按主键快照对比,非全量 diff
129
+
130
+ ## 开发
131
+
132
+ ```bash
133
+ pip install -e .
134
+ pytest tests/ -m unit # 142 个单元测试
135
+ ```
@@ -0,0 +1,326 @@
1
+ #!/usr/bin/env python3
2
+ """Pre-release regression test - runs all CLI commands against real MaxCompute backend."""
3
+ import json
4
+ import os
5
+ import sys
6
+ import tempfile
7
+ from io import StringIO
8
+ from pathlib import Path
9
+
10
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src"))
11
+ from maxc_cli.cli import run
12
+
13
+ RESULTS = []
14
+ TABLE = None
15
+ TABLE_COL = None
16
+ JOB_ID = None
17
+
18
+ def assert_eq(a, b): assert a == b, f"expected {b!r}, got {a!r}"
19
+ def assert_true(v): assert v, f"expected truthy, got {v!r}"
20
+ def assert_key(d, k): assert k in d, f"missing key: {k!r} in {list(d.keys())}"
21
+ def assert_type(v, t): assert isinstance(v, t), f"expected {t.__name__}, got {type(v).__name__}"
22
+
23
+ def run_cmd(label, argv, expect_code=0, check=None):
24
+ global JOB_ID
25
+ stdout, stderr = StringIO(), StringIO()
26
+ code = run(argv, cwd=Path.cwd(), stdout=stdout, stderr=stderr)
27
+ out = stdout.getvalue()
28
+ err = stderr.getvalue()
29
+ try:
30
+ data = json.loads(out) if out.strip() else {}
31
+ except json.JSONDecodeError:
32
+ data = {"_raw": out[:300]}
33
+
34
+ ok = (expect_code is None) or (code == expect_code)
35
+ detail = ""
36
+ if not ok:
37
+ detail = f"exit_code={code}, expected={expect_code}"
38
+ if err.strip():
39
+ detail += f" stderr={err[:200]}"
40
+ if ok and check:
41
+ try:
42
+ check(data)
43
+ except Exception as e:
44
+ ok = False
45
+ detail = str(e)
46
+
47
+ icon = "PASS" if ok else "FAIL"
48
+ RESULTS.append((label, icon, detail))
49
+ suffix = f" -- {detail}" if detail else ""
50
+ print(f" [{icon}] {label}{suffix}")
51
+ sys.stdout.flush()
52
+ return code, data
53
+
54
+ def pdata(d):
55
+ return d.get("data", {})
56
+
57
+
58
+ # ===========================================================
59
+ print("\n=== 1. Auth ===")
60
+ # ===========================================================
61
+ run_cmd("1.1 auth whoami", ["auth", "whoami", "--json"],
62
+ check=lambda d: (
63
+ assert_eq(d["status"], "success"),
64
+ assert_true(pdata(d)["identity"]["authenticated"]),
65
+ ))
66
+
67
+ # Get test table
68
+ _, tdata = run_cmd("(preflight) list-tables", ["meta", "list-tables", "--json"])
69
+ tables = pdata(tdata).get("tables", [])
70
+ TABLE = tables[0]["table_name"] if tables else None
71
+ if not TABLE:
72
+ print(" [SKIP] No tables available, aborting")
73
+ sys.exit(1)
74
+ print(f" >> Test table: {TABLE}")
75
+
76
+ # Describe to get column name
77
+ _, ddata = run_cmd("(preflight) describe", ["meta", "describe", TABLE, "--json"])
78
+ cols = pdata(ddata).get("table", {}).get("schema", [])
79
+ TABLE_COL = cols[0]["name"] if cols else "id"
80
+ print(f" >> Test column: {TABLE_COL}")
81
+
82
+ run_cmd("1.2 auth can-i SELECT", ["auth", "can-i", "--table", TABLE, "--operation", "SELECT", "--json"],
83
+ expect_code=None, # may be 0 or 1 depending on permissions
84
+ check=lambda d: assert_key(pdata(d), "authorization"))
85
+
86
+ run_cmd("1.3 auth can-i --brief", ["auth", "can-i", "--table", TABLE, "--operation", "SELECT", "--brief", "--json"],
87
+ expect_code=None, # may be 0 or 1
88
+ check=lambda d: assert_true(d["status"] in ("success", "failure")))
89
+
90
+
91
+ # ===========================================================
92
+ print("\n=== 2. Meta ===")
93
+ # ===========================================================
94
+ run_cmd("2.1 meta list-tables", ["meta", "list-tables", "--json"],
95
+ check=lambda d: assert_true(len(pdata(d).get("tables", [])) > 0))
96
+
97
+ run_cmd("2.2 meta describe", ["meta", "describe", TABLE, "--json"],
98
+ check=lambda d: (
99
+ assert_eq(pdata(d)["table"]["table_name"], TABLE),
100
+ assert_key(pdata(d)["table"], "schema"),
101
+ ))
102
+
103
+ run_cmd("2.3 meta describe --full", ["meta", "describe", TABLE, "--full", "--json"],
104
+ check=lambda d: assert_eq(d["status"], "success"))
105
+
106
+ run_cmd("2.4 meta search", ["meta", "search", "test", "--json"],
107
+ check=lambda d: assert_key(pdata(d)["search"], "matches"))
108
+
109
+ run_cmd("2.5 meta search-columns", ["meta", "search-columns", "id", "--json"],
110
+ check=lambda d: assert_key(pdata(d)["search"], "matches"))
111
+
112
+ run_cmd("2.6 meta partitions", ["meta", "partitions", TABLE, "--json"],
113
+ check=lambda d: assert_key(pdata(d), "partitions"))
114
+
115
+ run_cmd("2.7 meta latest-partition", ["meta", "latest-partition", TABLE, "--json"],
116
+ check=lambda d: assert_key(pdata(d)["partition"], "has_partitions"))
117
+
118
+ run_cmd("2.8 meta freshness", ["meta", "freshness", TABLE, "--json"],
119
+ check=lambda d: assert_key(pdata(d)["freshness"], "freshness_status"))
120
+
121
+ run_cmd("2.9 meta list-projects", ["meta", "list-projects", "--json"],
122
+ check=lambda d: assert_type(pdata(d)["projects"], list))
123
+
124
+ run_cmd("2.10 meta list-schemas", ["meta", "list-schemas", "--json"],
125
+ expect_code=None, # may fail if catalog not available
126
+ check=lambda d: assert_true(d["status"] in ("success", "failure")))
127
+
128
+
129
+ # ===========================================================
130
+ print("\n=== 3. Meta Semantic ===")
131
+ # ===========================================================
132
+ run_cmd("3.1 meta semantic set", ["meta", "semantic", "set", TABLE, "--desc", "regression test desc", "--json"],
133
+ check=lambda d: assert_eq(d["status"], "success"))
134
+
135
+ run_cmd("3.2 meta semantic get", ["meta", "semantic", "get", TABLE, "--json"],
136
+ check=lambda d: assert_eq(d["status"], "success"))
137
+
138
+ run_cmd("3.3 meta semantic list-missing", ["meta", "semantic", "list-missing", "--json"],
139
+ check=lambda d: assert_eq(d["status"], "success"))
140
+
141
+
142
+ # ===========================================================
143
+ print("\n=== 4. Query ===")
144
+ # ===========================================================
145
+ run_cmd("4.1 query simple", ["query", "SELECT 1 AS c1, 'test' AS c2", "--json", "--force"],
146
+ check=lambda d: (
147
+ assert_eq(d["status"], "success"),
148
+ assert_true(len(pdata(d)["result"]["rows"]) == 1),
149
+ ))
150
+
151
+ run_cmd("4.2 query cost", ["query", "cost", "SELECT 1 AS c1", "--json", "--force"],
152
+ check=lambda d: assert_key(pdata(d)["analysis"], "cost_model"))
153
+
154
+ run_cmd("4.3 query explain", ["query", "explain", "SELECT 1 AS c1", "--json", "--force"],
155
+ check=lambda d: assert_eq(pdata(d)["analysis"]["analysis_mode"], "explain"))
156
+
157
+ run_cmd("4.4 query pagination",
158
+ ["query", "SELECT 1 AS id UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5",
159
+ "--page-size", "2", "--json", "--force"],
160
+ check=lambda d: (
161
+ assert_true(pdata(d)["pagination"]["has_more"]),
162
+ assert_true(pdata(d)["pagination"]["next_cursor"] is not None),
163
+ ))
164
+
165
+ run_cmd("4.5 query dry-run", ["query", "SELECT 1", "--dry-run", "--json", "--force"],
166
+ check=lambda d: assert_eq(pdata(d)["result"]["returned_rows"], 0))
167
+
168
+ run_cmd("4.6 query wait=0", ["query", "SELECT 1 AS async_test", "--wait", "0", "--json", "--force"],
169
+ check=lambda d: (
170
+ assert_eq(d["status"], "pending"),
171
+ assert_key(pdata(d), "job"),
172
+ ))
173
+
174
+ tmpfile = Path(tempfile.mktemp(suffix=".csv"))
175
+ run_cmd("4.7 query --output csv",
176
+ ["query", "SELECT 1 AS a, 2 AS b", "--json", "--force", "--output", str(tmpfile), "--output-format", "csv"],
177
+ check=lambda d: assert_eq(d["status"], "success"))
178
+ if tmpfile.exists():
179
+ print(f" CSV file created: {tmpfile.stat().st_size} bytes")
180
+ tmpfile.unlink()
181
+ else:
182
+ RESULTS.append(("4.7b csv file exists", "FAIL", "file not created"))
183
+
184
+
185
+ # ===========================================================
186
+ print("\n=== 5. Query: New Features ===")
187
+ # ===========================================================
188
+ run_cmd("5.1 masking (phone/email/password)",
189
+ ["query", "SELECT 1 as id, '13812345678' as phone, 'alice@example.com' as email, 'secret' as password",
190
+ "--force", "--json"],
191
+ check=lambda d: (
192
+ assert_eq(pdata(d)["result"]["rows"][0]["phone"], "138****5678"),
193
+ assert_eq(pdata(d)["result"]["rows"][0]["email"], "a***@example.com"),
194
+ assert_eq(pdata(d)["result"]["rows"][0]["password"], "******"),
195
+ assert_true(any("masked" in w.lower() for w in d.get("agent_hints", {}).get("warnings", []))),
196
+ ))
197
+
198
+ run_cmd("5.2 LIMIT truncation warning",
199
+ ["query", "SELECT t.* FROM (SELECT 1 as id UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5) t",
200
+ "--max-rows", "2", "--force", "--json"],
201
+ check=lambda d: (
202
+ assert_true(pdata(d)["pagination"]["has_more"]),
203
+ assert_true(any("truncated" in w.lower() for w in d.get("agent_hints", {}).get("warnings", []))),
204
+ ))
205
+
206
+ run_cmd("5.3 error self-correction (wrong column)",
207
+ ["query", f"SELECT nonexistent_col FROM {TABLE} LIMIT 5", "--force", "--json"],
208
+ expect_code=None, # SQL_ERROR(1) or other
209
+ check=lambda d: (
210
+ assert_true(d["status"] == "failure"),
211
+ assert_key(d.get("data", {}), "schema_context"),
212
+ ))
213
+
214
+
215
+ # ===========================================================
216
+ print("\n=== 6. Job ===")
217
+ # ===========================================================
218
+ _, jsubmit = run_cmd("6.1 job submit", ["job", "submit", "SELECT 1 AS job_test", "--json", "--force"],
219
+ check=lambda d: assert_key(pdata(d), "job_id"))
220
+ JOB_ID = pdata(jsubmit).get("job_id")
221
+ if JOB_ID:
222
+ print(f" job_id: {JOB_ID}")
223
+
224
+ run_cmd("6.2 job status", ["job", "status", JOB_ID, "--json"],
225
+ check=lambda d: assert_key(pdata(d).get("job", {}), "stage"))
226
+
227
+ run_cmd("6.3 job wait", ["job", "wait", JOB_ID, "--json"],
228
+ check=lambda d: assert_true(d["status"] in ("success", "failure")))
229
+
230
+ run_cmd("6.4 job result", ["job", "result", JOB_ID, "--json"],
231
+ check=lambda d: assert_key(pdata(d).get("result", {}), "rows"))
232
+
233
+ run_cmd("6.5 job diagnose", ["job", "diagnose", JOB_ID, "--json"],
234
+ check=lambda d: assert_key(pdata(d).get("diagnosis", {}), "diagnosis_category"))
235
+ else:
236
+ for label in ["6.2 job status", "6.3 job wait", "6.4 job result", "6.5 job diagnose"]:
237
+ RESULTS.append((label, "SKIP", "no job_id"))
238
+
239
+ run_cmd("6.6 job list", ["job", "list", "--json"],
240
+ check=lambda d: assert_type(pdata(d).get("jobs", None), list))
241
+
242
+
243
+ # ===========================================================
244
+ print("\n=== 7. Data ===")
245
+ # ===========================================================
246
+ run_cmd("7.1 data sample", ["data", "sample", TABLE, "--rows", "3", "--json"],
247
+ expect_code=None, # may fail if table is partitioned
248
+ check=lambda d: assert_true(d["status"] in ("success", "failure")))
249
+
250
+ run_cmd("7.2 data profile", ["data", "profile", TABLE, "--json"],
251
+ expect_code=None, # may fail if table is partitioned
252
+ check=lambda d: assert_true(d["status"] in ("success", "failure")))
253
+
254
+
255
+ # ===========================================================
256
+ print("\n=== 8. Diff ===")
257
+ # ===========================================================
258
+ run_cmd("8.1 diff schema self", ["diff", "schema", TABLE, TABLE, "--json"],
259
+ check=lambda d: assert_eq(pdata(d)["diff"]["compatible"], True))
260
+
261
+ run_cmd("8.2 diff partition self", ["diff", "partition", TABLE, TABLE, "--json"],
262
+ check=lambda d: assert_key(pdata(d)["diff"], "summary"))
263
+
264
+ run_cmd("8.3 diff data self", ["diff", "data", TABLE, TABLE, "--keys", TABLE_COL, "--rows", "5", "--json"],
265
+ expect_code=None, # may fail if table is partitioned
266
+ check=lambda d: assert_true(d["status"] in ("success", "failure")))
267
+
268
+
269
+ # ===========================================================
270
+ print("\n=== 9. Session ===")
271
+ # ===========================================================
272
+ run_cmd("9.1 session show", ["session", "show", "--json"],
273
+ check=lambda d: assert_key(pdata(d), "project"))
274
+
275
+ run_cmd("9.2 session set", ["session", "set", "--project", "meta_dev", "--json"],
276
+ check=lambda d: assert_eq(d["status"], "success"))
277
+
278
+ run_cmd("9.3 session unset", ["session", "unset", "--json"],
279
+ check=lambda d: assert_eq(d["status"], "success"))
280
+
281
+
282
+ # ===========================================================
283
+ print("\n=== 10. Agent ===")
284
+ # ===========================================================
285
+ run_cmd("10.1 agent context", ["agent", "context", "--json"],
286
+ check=lambda d: assert_key(pdata(d).get("context", {}), "project"))
287
+
288
+ run_cmd("10.2 agent skill", ["agent", "skill", "--json"],
289
+ check=lambda d: assert_eq(d["status"], "success"))
290
+
291
+
292
+ # ===========================================================
293
+ print("\n=== 11. Cache ===")
294
+ # ===========================================================
295
+ run_cmd("11.1 cache status", ["cache", "status", "--json"],
296
+ check=lambda d: assert_key(pdata(d), "table_count"))
297
+
298
+ run_cmd("11.2 cache clear", ["cache", "clear", "--json"],
299
+ check=lambda d: assert_key(pdata(d), "deleted_tables"))
300
+
301
+ run_cmd("11.3 cache build", ["cache", "build", "--json"],
302
+ check=lambda d: assert_key(pdata(d), "cached_tables"))
303
+
304
+ run_cmd("11.4 cache build-status", ["cache", "build-status", "--json"],
305
+ check=lambda d: assert_eq(d["status"], "success"))
306
+
307
+
308
+ # ===========================================================
309
+ # SUMMARY
310
+ # ===========================================================
311
+ print("\n" + "=" * 60)
312
+ print("REGRESSION TEST SUMMARY")
313
+ print("=" * 60)
314
+ passed = sum(1 for _, s, _ in RESULTS if s == "PASS")
315
+ failed = sum(1 for _, s, _ in RESULTS if s == "FAIL")
316
+ skipped = sum(1 for _, s, _ in RESULTS if s == "SKIP")
317
+
318
+ if failed:
319
+ print(f"\nFAILED ({failed}):")
320
+ for label, status, detail in RESULTS:
321
+ if status == "FAIL":
322
+ print(f" - {label}: {detail}")
323
+
324
+ print(f"\nTotal: {passed} passed, {failed} failed, {skipped} skipped / {len(RESULTS)} tests")
325
+ print("=" * 60)
326
+ sys.exit(1 if failed else 0)
@@ -9,7 +9,7 @@ README = ROOT / "README.md"
9
9
 
10
10
  setup(
11
11
  name="maxc-cli",
12
- version="0.1.2",
12
+ version="0.1.4",
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",
@@ -17,6 +17,13 @@ setup(
17
17
  package_dir={"": "src"},
18
18
  packages=find_packages(where="src"),
19
19
  include_package_data=True,
20
+ package_data={
21
+ "maxc_cli": [
22
+ "skills/SKILL.md",
23
+ "skills/references/*.md",
24
+ "skills/agents/*.yaml",
25
+ ],
26
+ },
20
27
  classifiers=[
21
28
  "Programming Language :: Python :: 3",
22
29
  "Programming Language :: Python :: 3.8",
@@ -2,4 +2,4 @@
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "0.1.2"
5
+ __version__ = "0.1.4"