@guandata/guancli 1.0.62 → 1.0.65

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## @guandata/guancli 1.0.65 - 2026-10-12
4
+
5
+ - 子表行更新前校验分页总数与行标识;读取不完整时停止操作,避免误删现有行。
6
+ - 认证请求拒绝跨源重定向,防止 BI 凭据被转发到其他站点。
7
+ - 平台包发布校验兼容 npm 12 的元数据格式。
8
+
9
+ ## @guandata/guancli 1.0.64 - 2026-10-12
10
+
11
+ - 补齐请求转发 / API Gateway 的 CLI 支持与使用文档,支持通过观远 BI 调用外部接口。
12
+ - `ds execute-sql -f json` 新增 `--typed` 选项,支持输出 `null` 和 JSON 数字。
13
+ - 支持按主题读取指标目录树。
14
+ - 修复 `etl get` 的上游刷新配置读取与展示,所有上游均开启时显示“全部开启”。
15
+ - 优化 `npm link` 开发场景提示:缺少本地构建时,引导执行 `npm run build`。
16
+
17
+ ## @guandata/guancli 1.0.63 - 2026-09-24
18
+
19
+ - 指标查询新增 `--explain-requests -f json`,只读展示实际请求顺序与请求体,方便检查参数组合;不会执行取数。
20
+ - 完善筛选、排序和高级计算组合校验,避免无效查询计划被静默接受。
21
+
3
22
  ## @guandata/guancli 1.0.62 - 2026-09-19
4
23
 
5
24
  - 安装包按平台拆分:主包只保留启动器、Skill 与文档,原生程序由同版本的 `@guandata/guancli-<平台>-<架构>` 可选依赖提供,安装时只下载当前系统的一份,安装命令与 CLI 行为不变。
package/README.md CHANGED
@@ -45,6 +45,24 @@ guancli chatbi query --theme-name "经营主题" --message "最近30天营业收
45
45
 
46
46
  ## 版本更新
47
47
 
48
+ ### @guandata/guancli 1.0.65
49
+
50
+ - 子表行更新前校验分页总数与行标识;读取不完整时停止操作,避免误删现有行。
51
+ - 认证请求拒绝跨源重定向,防止 BI 凭据被转发到其他站点。
52
+ - 平台包发布校验兼容 npm 12 的元数据格式。
53
+
54
+ ### @guandata/guancli 1.0.64
55
+
56
+ - 指标目录树支持 `-d / --dir-id <topic-id>`,只读取所选主题的目录树,保留原有权限和输出格式。
57
+ - 新增请求转发路由的创建、查找、详情和调用命令;创建前可用 `--dry-run` 查看请求配置与访问范围。
58
+ - `ds execute-sql -f json --typed` 可保留空值及数值类型;ETL 调度详情能正确识别全部上游更新后触发。
59
+ - 从源码目录运行但尚未构建时,启动器会提示先执行 `npm run build`。
60
+
61
+ ### @guandata/guancli 1.0.63
62
+
63
+ - 指标查询新增 `--explain-requests -f json`,只读展示实际请求顺序与请求体,方便检查参数组合;不会执行取数。
64
+ - 完善筛选、排序和高级计算组合校验,避免无效查询计划被静默接受。
65
+
48
66
  ### @guandata/guancli 1.0.61
49
67
 
50
68
  - 指标取数会校验 `--dim`、`--filter` 和排序字段:未命中指标详情字段时返回明确错误,不再静默忽略条件。
package/bin/run.js CHANGED
@@ -60,11 +60,24 @@ function resolvePlatformBinaries(options = {}) {
60
60
  // Local development fallback. The generated directory is excluded from
61
61
  // the published main package, so installed users always use the optional
62
62
  // platform dependency above.
63
- const localBinDir = path.join(__dirname, "..", "platforms", target.suffix, "bin");
63
+ const pkgRoot = path.join(__dirname, "..");
64
+ const localBinDir = path.join(pkgRoot, "platforms", target.suffix, "bin");
64
65
  const binaryPaths = {};
65
66
  for (const binary of target.binaries) {
66
67
  const binaryPath = path.join(localBinDir, binary);
67
68
  if (!exists(binaryPath)) {
69
+ // scripts/ is not published, so this is a source checkout (npm link).
70
+ // npm link never installs the optional platform package; reinstalling
71
+ // from the registry would not help, the local build is missing.
72
+ if (exists(path.join(pkgRoot, "scripts", "build.js"))) {
73
+ throw new Error(
74
+ `Missing local build for ${key}: ${binaryPath}\n` +
75
+ `This ${PACKAGE_PREFIX} is a source checkout (npm link), which runs the ` +
76
+ `binaries built into platforms/ instead of the optional platform package.\n` +
77
+ `Build them first:\n` +
78
+ ` cd "${pkgRoot}" && npm run build`
79
+ );
80
+ }
68
81
  throw new Error(
69
82
  `Missing optional platform package ${target.packageName} for ${key}.\n` +
70
83
  `Reinstall without disabling optional dependencies:\n` +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guandata/guancli",
3
- "version": "1.0.62",
3
+ "version": "1.0.65",
4
4
  "description": "观远 BI 命令行工具 - 通过 API 操作 ETL、数据集、页面、表单等资源,并提供 ChatBI 问数/洞察能力",
5
5
  "bin": {
6
6
  "guancli": "bin/run.js"
@@ -42,11 +42,11 @@
42
42
  "node": ">=14"
43
43
  },
44
44
  "optionalDependencies": {
45
- "@guandata/guancli-darwin-arm64": "1.0.62",
46
- "@guandata/guancli-darwin-x64": "1.0.62",
47
- "@guandata/guancli-linux-x64": "1.0.62",
48
- "@guandata/guancli-linux-arm64": "1.0.62",
49
- "@guandata/guancli-win32-x64": "1.0.62"
45
+ "@guandata/guancli-darwin-arm64": "1.0.65",
46
+ "@guandata/guancli-darwin-x64": "1.0.65",
47
+ "@guandata/guancli-linux-x64": "1.0.65",
48
+ "@guandata/guancli-linux-arm64": "1.0.65",
49
+ "@guandata/guancli-win32-x64": "1.0.65"
50
50
  },
51
51
  "publishConfig": {
52
52
  "registry": "https://registry.npmjs.org/",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: guancli
3
- description: 当用户要分析观远 BI / Guandata 的 ETL、数据集、页面/仪表板、卡片、血缘、节点 SQL、资源树、目录、表单填报数据、指标 metric 查询、指标树归因 metric_attribution、指标泛化查询参数,或要对本地 CSV/JSON 做通用规范化、字段对齐、安全基础计算、分组 TopN/占比时,主动使用这个 skill。用户给出 ETL ID、dsId、page ID、card ID、metric ID、metric tree ID 想看现有内容/查询数据,或要为已有仪表板/智能洞察卡片生成智能洞察结果时也触发。用户提到“创建超级应用”“创建 SuperApp”“新建 SuperApp 项目”“SuperApp 模板项目”“发布 SuperApp”“下载 SuperApp 整包”时,也要使用这个 skill,并引导使用 `guancli app create` / `guancli app publish` / `guancli app download`。用户要删除、重命名、移动页面时,本 skill 只负责把用户路由到 guanvis(`guanvis page delete/rename/move`),这些命令不在 guancli;被删页面没有任何命令行恢复入口,只能人工登录 BI 网页端还原。创建/编辑/删除指标、指标主题、指标目录或公共维度走 guanmetric;修改 ETL 走 guanetl;页面**内容**(卡片、布局、筛选器)的新建与修改、以及页面**本身**的删除/改名/移动都走 guanvis;数据源/数据集 CRUD/增量/调度走 guands;页面 PNG 视觉验证走 guanvis screenshot。
3
+ description: 当用户要分析观远 BI / Guandata 的 ETL、数据集、页面/仪表板、卡片、血缘、节点 SQL、资源树、目录、表单填报数据、指标 metric 查询、指标树归因 metric_attribution、指标泛化查询参数,或要对本地 CSV/JSON 做通用规范化、字段对齐、安全基础计算、分组 TopN/占比时,主动使用这个 skill。用户给出 ETL ID、dsId、page ID、card ID、metric ID、metric tree ID 想看现有内容/查询数据,或要为已有仪表板/智能洞察卡片生成智能洞察结果时也触发。用户提到“创建超级应用”“创建 SuperApp”“新建 SuperApp 项目”“SuperApp 模板项目”“发布 SuperApp”“下载 SuperApp 整包”,或要根据第三方接口文档创建、配置、调用“请求转发 / API Gateway”时,也要使用这个 skill。用户要删除、重命名、移动页面时,本 skill 只负责把用户路由到 guanvis(`guanvis page delete/rename/move`),这些命令不在 guancli;被删页面没有任何命令行恢复入口,只能人工登录 BI 网页端还原。创建/编辑/删除指标、指标主题、指标目录或公共维度走 guanmetric;修改 ETL 走 guanetl;页面**内容**(卡片、布局、筛选器)的新建与修改、以及页面**本身**的删除/改名/移动都走 guanvis;数据源/数据集 CRUD/增量/调度走 guands;页面 PNG 视觉验证走 guanvis screenshot。
4
4
  compatibility: Requires Node.js 14+. Install via npm install -g --foreground-scripts @guandata/guancli so the AI skill refresh result is visible.
5
5
  ---
6
6
 
@@ -19,6 +19,7 @@ guancli 是 BI 资源的查询分析入口,并支持 Form 表单填报 CRUD、
19
19
  - 用户要搜索指标、获取指标详情、查询指标数据、查看指标目录、执行指标树归因,或让你生成/解释指标泛化查询命令。
20
20
  - 用户给出数据集 ID,想查看该数据集创建或影响到的所有指标,包括直接原子指标以及下游复合指标、衍生指标。
21
21
  - 用户要创建、发布或更新超级应用 / SuperApp,或要把平台上的 SuperApp 整包下载成本地 zip。
22
+ - 用户给出第三方接口文档,希望转换成观远 BI 请求转发配置、创建路由或生成调用代码。
22
23
  - 用户要删除、重命名、移动页面(本 skill 只负责把用户路由到 `guanvis page`,命令不在 guancli)。
23
24
  - 用户要为已有仪表板或智能洞察卡片生成智能洞察结果。
24
25
  - 用户已经取得本地 CSV/JSON,需要稳定处理 BOM/null/嵌套字段、对齐两个结果集、执行安全四则运算,或做通用分组 TopN/占比;不再临时生成 Python 拼接脚本。
@@ -66,12 +67,16 @@ guancli 是 BI 资源的**查询分析入口**,并支持 Form 表单填报 CRU
66
67
  | Form CRUD、子表、ChatBI 主题问数/洞察 | `references/form-chatbi.md` |
67
68
  | 指标与指标树全量命令 | `references/metric-commands.md` |
68
69
  | 指标泛化查询参数与示例 | `references/metric-generic-query.md` |
70
+ | 请求转发配置生成、创建、调用与安全边界 | `references/gateway.md` |
71
+ | 指标 query 参数模式、互斥矩阵与真实请求体 | `references/metric-query-recipes.md` |
69
72
  | 本地 CSV/JSON 规范化、对齐、基础计算、TopN | `references/analysis-primitives.md` |
70
73
 
71
74
  ## 常用查询入口
72
75
 
73
76
  **同轮指标查询规则**:本轮已确定、彼此互不依赖,且参数能被 `batch-query` 完整表达的多个基础指标查询,必须写成一个结构化查询列表并执行一次 `guancli metric batch-query --input queries.json`。同比、XTD、recent、占比、排名等高级计算继续使用单条 `metric query`;同轮其余基础查询仍应单独组成 batch。不得生成 Shell `for` 循环逐项执行基础指标查询,也不自行设计 `&`、`xargs -P` 等并发脚本或调整并发参数。CLI 自动调度并逐项返回结果;需要全部成功后再处理结果时加 `--fail-on-error`,可直接用 `&&` 衔接;默认模式仍需用 `id` 对齐每项 `status`。后续查询依赖本轮结果时,读取本轮结果后再生成下一批。搜索并需要详情时使用 `metric search --with-detail`,避免逐项 get。批量输入、分页与支持边界见 [metric-batch-query.md](references/metric-batch-query.md)。复用已确认的输入契约,缺少信息或遇到参数错误时再读取该参考,不固定增加帮助查询。
74
77
 
78
+ 用户明确要求比较两组指标数值是否一致时,不能把两次查询默认返回的前 50 行按数组位置直接比较。新旧指标必须使用相同的显式维度/日期粒度、筛选范围、排序、分页和 `valueFormat=raw`;结果按维度组合键对齐后再比较指标值。任一查询失败、结果可能截断、维度键不唯一或覆盖范围不一致时,结论只能是 `inconclusive`,不能写成 `different`。这属于显式的数值对比任务,不是每次指标创建都要执行的默认验收;完整 recipe 见 [metric-batch-query.md](references/metric-batch-query.md) 的“指标数值等价对比”。
79
+
75
80
  **减少模型往返**:同一授权和查询轮次内,已确认的指标口径、字段与能力直接复用。将确定性的文件读取、状态检查、裁剪和计算合并成一次本地执行,返回需要判断的摘要;仅在结果引入新决策时再规划下一轮。只读查询可与结果保存、检查和摘要合并执行;确认查询成功后再消费,不为补齐运行轨迹拆分命令或重跑查询。
76
81
 
77
82
  ```bash
@@ -156,7 +161,7 @@ guancli 在这条链路上只出只读的那一环:`guancli page search` / `pa
156
161
  - query 的业务列可以是中文,每个中文层级都用 `["字段名"]`:例如 `.data[] | .["组织"]["名称"]`,每一个中文层级都要引用,不能只改顶层。search/get 的 canonical 英文字段与 query 的业务列名要分开理解。文件模式的 `path` 指向行数组,行数组没有 `rowCount`;先按 `resultMode` 分支再消费,示例见 `references/metric-commands.md`。
157
162
  - 必须解析的命令不得用 `2>/dev/null` 隐藏 stderr,也不得用 `2>&1` 将错误文本送入 JSON 解码器。不要用会让末端命令成功掩盖上游失败的管道;允许组合执行时用 `&&` 衔接“输出到文件 → 解析摘要”,不为读取文件另起一轮推理,必须使用管道时先启用 `set -o pipefail`。
158
163
  - 同类语法错误只允许出现一次;第一次失败后先执行对应命令的 `--help` 或读取本 Skill 的参考文档,再按真实契约纠正,不连续尝试相近写法。
159
- - 普通 `--filter`、可重复 `--filter-json`、单个 `--filter-file` 三选一,禁止混用。复杂筛选优先使用 `--filter-json`(可重复)或 `--filter-file` 条件树。参数和口径明确时直接查询;需要检查计划或排查复杂参数时才用 `--dry-run -f json`。dry-run 只读取指标详情做查询维度、筛选字段和类型校验,不执行指标取数,也不支持普通文件或 stderr 输出目标;保存卡片高级计算列不在指标详情中,因此这类排序候选在 dry-run 中不会被猜测为有效,真实执行时才读取既有卡片编辑配置校验。不支持的 `--dim`、普通 `--filter` 的字段、`--sort-asc`/`--sort-desc` 的字段都按指标详情中的字段(适用维度 / 时间维度 / 指标字段名)校验,未命中返回 `METRIC_UNSUPPORTED_DIMENSION`;结构化 `--filter-json`/`--filter-file` 在解析阶段按同一份字段集合校验,未命中返回 `FILTER_UNKNOWN_FIELD`。两类错误都不会静默忽略条件或透传未知字段。普通展示别名只对 `--dim` 与排序生效;若指标详情的 `publicDimensionMap` 把唯一公共维度名映射到一个适用维度,则 `--dim`、排序和筛选也可使用该公共维度名,CLI 会保留物理 `fdId` 并把公共维度名作为 alias;同名映射到多个物理字段时拒绝猜测。仅由 CLI 为排序注入、以指标 ID 充当 `fdId` 的“指标名伪字段”不能用于筛选,若指标详情的 `fdInfos` 本身含同名原始字段和真实 `fdId`,仍可正常筛选。`--sort-asc`/`--sort-desc` 额外接受保存卡片中真实存在的高级计算列,以及泛化查询的结果列名 `<指标名>_<后缀>`(`_yoy`/`_mom`/`_qoq`/`_wow`/`_dod`/`_ytd`/`_qtd`/`_mtd`/`_wtd`/`_dtd`/`_last`/`_recent_*`/`_percentage`/`_rank`)。遇到这些错误时先用 `guancli metric get` 读取适用维度再改字段。条件组使用 `combineType: AND|OR` 和 `conditions` 数组,叶子使用 `field/operator/value|values`;`IN` 至少一个值,`BT` 恰好两个值。当前指标取数接口不能无损表达多分支 OR,真实执行会在取数前返回 `FILTER_OR_EXECUTION_UNSUPPORTED`,不得拆成多次聚合查询后拼接。失败时按稳定错误码修正,不猜错误文本。
164
+ - 普通 `--filter`、可重复 `--filter-json`、单个 `--filter-file` 三选一,禁止混用。复杂筛选优先使用 `--filter-json`(可重复)或 `--filter-file` 条件树。Agent 执行任意单条 `metric query`(包括无参数的基础查询)前都必须先用同一组查询参数追加 `--explain-requests -f json`,核对真实接口顺序和最终 request body;只有 `valid=true`、`executable=true` 且 body 符合业务意图时,才移除 explain 执行真实查询。`batch-query` 是独立批量契约,不支持 explain,不套用这条单条查询门禁;它仍按上文同轮批量规则使用。只需人工快速检查维度/筛选时可用 `--dry-run -f json`,但不能替代 Agent 的 explain-first 门禁。explain 会调用与真实执行相同的 request builder:泛化查询给出最终 `metric-data/v2` body,基础查询只读保存的 card edit meta 后给出完整 session/modify/data 计划;它不创建 edit session、不执行 POST、不取数。完整模式矩阵见 `references/metric-query-recipes.md`。dry-run 只读取指标详情做查询维度、筛选字段和类型校验,不执行指标取数,也不支持普通文件或 stderr 输出目标;保存卡片高级计算列不在指标详情中,因此这类排序候选在 dry-run 中不会被猜测为有效,explain/真实执行会读取既有 card 配置校验。不支持的 `--dim`、普通 `--filter` 的字段、`--sort-asc`/`--sort-desc` 的字段都按指标详情中的字段(适用维度 / 时间维度 / 指标字段名)校验,未命中返回 `METRIC_UNSUPPORTED_DIMENSION`;结构化 `--filter-json`/`--filter-file` 在解析阶段按同一份字段集合校验,未命中返回 `FILTER_UNKNOWN_FIELD`。两类错误都不会静默忽略条件或透传未知字段。普通展示别名只对 `--dim` 与排序生效;若指标详情的 `publicDimensionMap` 把唯一公共维度名映射到一个适用维度,则 `--dim`、排序和筛选也可使用该公共维度名,CLI 会保留物理 `fdId` 并把公共维度名作为 alias;同名映射到多个物理字段时拒绝猜测。仅由 CLI 为排序注入、以指标 ID 充当 `fdId` 的“指标名伪字段”不能用于筛选,若指标详情的 `fdInfos` 本身含同名原始字段和真实 `fdId`,仍可正常筛选。`--sort-asc`/`--sort-desc` 额外接受保存卡片中真实存在的高级计算列,以及泛化查询的结果列名 `<指标名>_<后缀>`(`_yoy`/`_mom`/`_qoq`/`_wow`/`_dod`/`_ytd`/`_qtd`/`_mtd`/`_wtd`/`_dtd`/`_last`/`_recent_*`/`_percentage`/`_rank`)。遇到这些错误时先用 `guancli metric get` 读取适用维度再改字段。条件组使用 `combineType: AND|OR` 和 `conditions` 数组,叶子使用 `field/operator/value|values`;`IN` 至少一个值,`BT` 恰好两个值。当前指标取数接口不能无损表达多分支 OR,真实执行会在取数前返回 `FILTER_OR_EXECUTION_UNSUPPORTED`,不得拆成多次聚合查询后拼接。失败时按稳定错误码修正,不猜错误文本。
160
165
  - 搜索并同时查看业务口径、数据集和适用维度等详情时,优先使用 `guancli metric search --fuzzy <keyword> --limit <N> --with-detail --brief -f json`,不要先 search 再逐个 get,也不要为终端阅读额外拼 jq。只查单个指标详情时优先使用 `guancli metric get <metric_id> --brief`;只裁剪指标查询结果列时使用 `metric query --columns`。可复制模板见 `references/metric-commands.md`。
161
166
  - 业务词不能直接命中指标时,候选发现必须遵守单批预算:一次性生成“原词 + 最多 4 个必要同义词/等价表达”,按 trim、空白归一化和大小写不敏感语义去重,保留原词在首位,然后只执行一次 `metric search --fuzzy`。不得把同义词拆成多次查询,不得通过调换顺序、改写等价词或查询子集重复搜索;如果单批无候选,立即结束本轮候选发现并说明已尝试关键词、搜索范围、无结果原因和 `stop_reason=no_candidate_within_budget`。后端最多接受 10 个模糊关键词,但 Agent 候选发现预算默认最多 5 个;不要缓存或跨请求复用“未找到指标”的结论。完整协议见 `references/metric-commands.md`。
162
167
  - 指标结果加工前,先读取成功 query envelope 的实际 `columns` 和行对象 keys,按业务含义选择列;禁止硬编码或猜测指标展示列名(包括高级计算后缀),也不能按列位置取值。使用 `python3 references/examples/metric-query-rows.py <envelope.json> --list-columns` 发现列,再用 `--column "<已确认的实际列名>"` 精确选择;候选缺失或多义时停止并返回实际列集合,不猜近似名称。该参数属于消费 recipe,不是 `guancli metric query` 的参数。
@@ -214,13 +219,33 @@ GUANCLI_DOWNLOAD_TIMEOUT=1200 guancli app download <appId> # 大包/慢网络
214
219
 
215
220
  ## 请求转发路由
216
221
 
217
- 请求转发能力通过 `gateway invoke` 调用 BI 网关,CLI 不会直接访问配置中的目标服务地址。当前 PAT 仅开放已配置路由的转发调用,路由管理和调试接口未开放,因此不提供 `route`、`debug` 等管理类子命令;路由需预先在 BI 中配置:
222
+ 创建请求转发、查找已有路由或生成 SuperApp 调用代码时,先读 `references/gateway.md`。创建主流程是:整理配置草案 → 用户确认敏感数据引用、权限范围和启用状态 → `--dry-run` → 真实创建。不要把真实 token、密码、AK/SK 或 client secret 写入配置文件或对话;只引用 BI 中已保存的 `${SECRET.xxx}`。
223
+
224
+ ```bash
225
+ guancli gateway create gateway.json --dry-run
226
+ guancli gateway create gateway.json
227
+ ```
228
+
229
+ 创建接口要求当前用户拥有请求转发 `UPDATE` 权限,环境还需开通超级应用商业授权。`--dry-run` 只验证本地 JSON 结构并展示请求,不证明权限、授权或服务端创建一定成功。
230
+
231
+ 用户用自然语言描述要调用的业务接口、但不知道识别符时,先用业务名或系统名搜索,再读取候选路由的稳定调用契约:
232
+
233
+ ```bash
234
+ guancli gateway search CRM --enabled
235
+ guancli gateway get 42
236
+ ```
237
+
238
+ `search` 只按名称或识别符匹配,返回当前分页;无结果时可缩短关键词或省略关键词,不能把单页结果当作全量。多个候选无法可靠区分时,向用户确认,不要猜路由。`get` 默认输出脱敏后的 v1.0 调用契约,包括 BI 网关路径、方法、追加路径、Query/Header/Body 映射、访问约束、文档说明和调用示例;不得根据 `--raw` 中的目标 URL 生成应用代码。
239
+
240
+ 生成代码前检查 `enabled`、`gatewayMethod`、`loginRequired`、允许用户/用户组、`allowedCallerHosts` 和所有 `required` 入参。POST 路由即使 `bodyRequired=false` 也必须保留可选的透传 body 支持;该字段只决定是否强制要求 body。响应解析必须保留上游类型:空的 204/205 返回 `null`,再按 Content-Type 选择 `json()`、`text()` 或 `blob()`,不得固定执行 `response.json()`。代码只能访问 `gatewayPath` / `gatewayUrlTemplate` 指向的 BI 网关。路由未启用、方法不匹配、来源不允许或必填参数缺失时,明确指出修复动作,不能改为直连第三方地址。
241
+
242
+ 需要实际调试已有路由时使用 `gateway invoke`:
218
243
 
219
244
  ```bash
220
245
  guancli gateway invoke orders --method GET --path 2026/detail --query tenant=cn
221
246
  ```
222
247
 
223
- `invoke` 支持重复 `--query key=value`、`--header key=value`,以及互斥的 `--body` / `--body-file`。默认直接输出解析后的响应 JSON body;排查请求时增加 `--verbose`,可额外查看 `statusCode` 和脱敏后的 `headers`。`--raw` 只输出原始响应体。
248
+ `invoke` 支持重复 `--query key=value`、`--header key=value`,以及互斥的 `--body` / `--body-file`。默认直接输出解析后的响应 JSON body;排查请求时增加 `--verbose`,可额外查看 `statusCode` 和脱敏后的 `headers`。`--raw` 只输出原始响应体。非 2xx 响应会先完成上述输出,再返回错误并使进程退出码非零;脚本既要保留输出用于诊断,也必须检查退出码。
224
249
 
225
250
  为避免覆盖 CLI 到 BI 的认证上下文,`Cookie`、`Authorization`、`token`、`X-Auth-Token`、`X-Personal-Token`、`x-dom-id` 和 `user-id` 等保留 Header 不能通过 `--header` 传入;目标服务鉴权应配置在路由模板或 Secret 中。
226
251
 
@@ -104,6 +104,53 @@
104
104
  "missing 没有金额列时停止;ambiguous 存在销售额和销售净额时停止并询问业务口径",
105
105
  "不抛 KeyError,不用缺失字段默认零或第一列掩盖歧义"
106
106
  ]
107
+ },
108
+ {
109
+ "id": 9,
110
+ "prompt": "帮我查销售额指标按订单日期逐月的同比增长率,只看华东。这个 query 参数比较多,先确认最终会请求哪个接口、body 里的日期字段和高级计算是否正确,再执行真实查询。",
111
+ "expected_output": "代理先用 metric get 确认真实时间字段和地区字段,再构造唯一 compare 模式命令,使用 --explain-requests -f json 检查 metric-data/v2 的 dimensions、filters 和 advCalc;确认 valid/executable 后才移除 explain 执行。",
112
+ "files": [],
113
+ "expectations": [
114
+ "先用 metric get 确认时间字段和筛选字段,不默认字段名",
115
+ "查询组合使用 --dim \"订单日期 toMonth\" --compare yoy --compare-value rate,并只选择一个主模式",
116
+ "真实查询前使用 --explain-requests -f json,检查 data.requestPlan.requests 中的 metric-data/v2 body",
117
+ "只有 valid=true、executable=true 且 body 符合意图时才移除 --explain-requests"
118
+ ]
119
+ },
120
+ {
121
+ "id": 10,
122
+ "prompt": "我想把 --compare yoy 和 --adv-calc-json '{\"advType\":\"XTD\",\"advValue\":{\"advType\":\"XTD\",\"granularity\":\"YEAR\"}}' 一起传给 metric query,顺便看看最终请求体。",
123
+ "expected_output": "代理识别 adv-calc-json 是完整主计算定义,不能与 compare 组合;不要执行含歧义的查询,要求二选一,并分别说明可用 explain 查看请求体。",
124
+ "files": [],
125
+ "expectations": [
126
+ "明确指出 --adv-calc-json 与 compare/xtd/last/recent/percentage/rank 主模式互斥",
127
+ "不生成同时包含 --compare 和 --adv-calc-json 的可执行命令",
128
+ "建议选定一种模式后使用 --explain-requests -f json,而不是直接真实取数"
129
+ ]
130
+ },
131
+ {
132
+ "id": 11,
133
+ "prompt": "把 guancli metric query 的所有参数组合和最终 API 请求体都告诉我,我要避免 Agent 以后乱用。",
134
+ "expected_output": "代理不做无限参数笛卡尔积,而是按 base、compare、xtd、last、recent、percentage、rank、adv-calc-json 八类语义模式说明互斥/依赖,并把公共修饰参数单列;真实指标请求体通过 --explain-requests 解析。",
135
+ "files": [],
136
+ "expectations": [
137
+ "解释重复维度、筛选、recent 数值、rank-top 和任意 adv JSON 使字面上的全部组合无法穷举",
138
+ "完整列出八类主模式,并把筛选三选一、排序二选一和客户端输出参数作为公共矩阵",
139
+ "区分 card 链路与 metric-data/v2 链路",
140
+ "给出 --explain-requests -f json 作为真实 body 的权威入口"
141
+ ]
142
+ },
143
+ {
144
+ "id": 12,
145
+ "prompt": "帮我查一下销售额指标现在的值,没有维度和筛选。",
146
+ "expected_output": "即使是无参数的基础单指标查询,代理也先执行同一 metric_id 的 --explain-requests -f json,检查 valid/executable 以及 session/data 请求计划,再移除 explain 执行真实查询。",
147
+ "files": [],
148
+ "expectations": [
149
+ "无参数基础 metric query 也遵守 explain-first,不因参数简单而直接取数",
150
+ "检查 data.requestPlan 中 valid、executable 以及 card session/data 请求",
151
+ "只有计划有效、可执行且请求体符合意图时才执行真实查询",
152
+ "不把 batch-query 的独立批量契约误写成支持 --explain-requests"
153
+ ]
107
154
  }
108
155
  ]
109
156
  }
@@ -152,6 +152,7 @@ guancli form delete <subFmId> <subId>
152
152
  **注意**:
153
153
  - 子表 add 内部通过主表 update API 间接完成(后端限制),CLI 自动保留已有子行
154
154
  - add 会分页读取已有子行并通过主表 update 全量回写以保留已有行;非并发安全
155
+ - 旧子行查询必须包含有效 `data` 数组、`count` 总数和行标识;缺失、分页不完整或总数变化时停止,不提交全量替换。协议依据为 SurveyEngine `d4f37aca` 的 `DataController.get` / `DataService.getSubData`,短页本身不能证明读取完成。
155
156
  - `--stdin` 批量插入部分失败时 CLI 返回非零退出码,summary 中显示成功/失败计数
156
157
 
157
158
  ### 典型工作流
@@ -0,0 +1,148 @@
1
+ # 请求转发创建与调用辅助
2
+
3
+ 用于把第三方或客户内部接口文档转换为观远 BI 请求转发配置,并通过 `guancli gateway create` 创建。产品语义以[请求转发 / API Gateway](https://docs.guandata.com/product/bi/api-gateway)为准。
4
+
5
+ ## 从接口文档生成配置
6
+
7
+ 先提取并向用户展示配置草案,至少标明每项来自文档的哪一段:
8
+
9
+ - 目标方法、URL、超时时间。
10
+ - Header、Query、Body 入参及必填性。
11
+ - 鉴权方式;需要登录或换 token 时转成前置请求。
12
+ - 前置响应 JSONPath 与主请求 `${PRE_REQ.xxx}` 引用。
13
+ - 网关入口方法、路由识别符、登录要求、用户/用户组、调用方 Host 和日志脱敏 Header。
14
+ - 文档地址与调用说明 `usageMarkdown`。
15
+
16
+ 不确定的权限范围、Host 白名单、Secret key 和启用状态必须列为待确认项。不要猜用户 ID、用户组 ID 或把凭据明文放入 JSON。用户确认后再生成配置文件。
17
+
18
+ ## JSON 契约
19
+
20
+ ```json
21
+ {
22
+ "basicInfo": {
23
+ "name": "CRM 查询用户",
24
+ "routes": "crm-query-user",
25
+ "gatewayMethod": "GET",
26
+ "targetMethod": "GET",
27
+ "documentUrl": "https://docs.example.com/crm/users",
28
+ "usageMarkdown": "Query: keyword;返回 CRM 用户列表。",
29
+ "enabled": true,
30
+ "loginRequired": true,
31
+ "allowedUsers": [{"id": "user-id"}],
32
+ "allowedUserGroups": [{"id": "group-id"}],
33
+ "targetUrl": "https://crm.example.com/api/users",
34
+ "allowedCallerHosts": ["app.example.com"],
35
+ "maskHeaders": ["Authorization", "Cookie", "X-Auth-Token"]
36
+ },
37
+ "config": {
38
+ "request": {
39
+ "timeoutSeconds": 120,
40
+ "headers": [
41
+ {
42
+ "paramName": "Authorization",
43
+ "paramValueOrExpr": "Bearer ${PRE_REQ.accessToken}",
44
+ "paramDescription": "前置登录返回的 token"
45
+ }
46
+ ],
47
+ "query": [
48
+ {
49
+ "paramName": "keyword",
50
+ "paramValueOrExpr": "${REQUEST.query.keyword}",
51
+ "paramDescription": "搜索关键词"
52
+ }
53
+ ]
54
+ },
55
+ "preRequestList": [
56
+ {
57
+ "name": "获取 CRM Token",
58
+ "requestMethod": "POST",
59
+ "url": "https://crm.example.com/oauth/token",
60
+ "timeoutSeconds": 30,
61
+ "documentUrl": "https://docs.example.com/crm/auth",
62
+ "headers": [{"paramName": "Content-Type", "paramValueOrExpr": "application/json"}],
63
+ "query": [],
64
+ "body": "{\"clientId\":\"${SECRET.crm_client_id}\",\"clientSecret\":\"${SECRET.crm_client_secret}\"}",
65
+ "responseExtractors": [{"name": "accessToken", "jsonPath": "$.accessToken"}]
66
+ }
67
+ ]
68
+ }
69
+ }
70
+ ```
71
+
72
+ 规则:
73
+
74
+ - `routes` 只能包含字母、数字和中划线;创建后不要修改识别符。
75
+ - `gatewayMethod` 只支持 `GET`、`POST`;`targetMethod` 支持 `GET`、`POST`、`PUT`、`DELETE`、`PATCH`。
76
+ - 主请求及前置请求超时为 1–300 秒。
77
+ - `allowedUsers`、`allowedUserGroups` 传资源 ID,不传名称代替 ID。登录开启但两者为空时,所有已登录用户可访问。
78
+ - Header 和 Query 只有显式配置的参数才转发;Body 由调用方整体透传并执行占位符替换。
79
+ - Header 和 Query 配置通过 `required: true|false` 保留原接口的必填性;省略时按可选处理。`gateway get` 只对必填入参生成缺失检查,可选 Query/Header 未传时不会被序列化为 `undefined`。
80
+ - CLI 会在输出 dry-run 计划前检查 Header、Query、前置请求 Body、目标/前置请求 URL 的 query 与 userinfo。`Authorization`、`token`、`password`、`AK/SK`、`API Key`、`client secret` 等敏感参数只能由 `${SECRET.xxx}` 或 `${PRE_REQ.xxx}` 服务端引用组成;明文及 `${REQUEST.xxx}` 会被拒绝,且不会回显配置。自定义 Header 固定值默认拒绝;只有确认为公开非凭据值时才在该参数上显式设置 `allowPlaintext: true`,例如 `X-API-Version: 2026-09`、`X-Client-Type: superapp`、`Prefer: return=representation`。敏感 Header 名或 Bearer/Basic/API-key/JWT 等凭据形式不允许用 `allowPlaintext` 绕过。`allowPlaintext` 只是 CLI 本地确认字段,dry-run 的 `request` 和真实 POST body 都会在提交投影中剔除它,两者保持一致。
81
+ - JSON Body 任意层级出现重复字段时会在 dry-run 和真实创建前直接拒绝,避免校验器只检查最后一个值、却把未规范化的原始 Body 提交给 BI。
82
+ - `enabled`、`loginRequired` 省略时服务端默认 `true`,配置草案中仍应显式写出,便于用户确认。
83
+
84
+ 常用占位符:`${REQUEST.path.n}`、`${REQUEST.query.xxx}`、`${REQUEST.header.xxx}`、`${REQUEST.body.xxx}`、`${CURRENT_USER.xxx}`、`${PRE_REQ.xxx}`、`${SECRET.xxx}`、`${SYSTEM.xxx}`。
85
+
86
+ ## 创建流程
87
+
88
+ ```bash
89
+ guancli auth status
90
+ guancli gateway create ./gateway.json --dry-run
91
+ guancli gateway create ./gateway.json
92
+ ```
93
+
94
+ 先检查 dry-run 输出中的目标地址、权限、Secret key、参数映射和 `enabled`。真实创建是外部写操作;只有用户确认草案后才执行。dry-run 计划和创建结果均使用 `schemaVersion: "1.0"`;创建成功返回 `id`、`name`、`routeCode`、`gatewayPath` 和 `enabled`。
95
+
96
+ 服务端仍会最终校验权限、商业授权、路由重名、用户/用户组、Secret 与配置合法性。创建端点为 `POST /api/gateway/manage/routes`,权限资源为 `GATEWAY_ROUTE.UPDATE`;自 2026-09-01 起还需要超级应用商业授权。
97
+
98
+ ## 查找路由并读取调用契约
99
+
100
+ 当用户只给出自然语言需求(例如“调用 CRM 查询客户信息接口”),不要要求用户预先提供识别符,也不要从创建文件或历史对话猜测:
101
+
102
+ ```bash
103
+ guancli gateway search CRM --enabled --limit 20
104
+ guancli gateway get 42
105
+ ```
106
+
107
+ - `search [keyword]` 调用 `POST /api/gateway/manage/routes/query`,关键词由服务端匹配名称或识别符;支持 `--enabled=true|false`、`--offset`、`--limit`。输出为 `schemaVersion: "1.0"` 的脱敏摘要,包含路由 ID、名称、识别符、BI 网关路径、方法、启用状态、访问约束、文档地址和业务说明。
108
+ - 查询结果只代表当前分页。`total` 大于 `items` 数量时仍有后续页;无候选时缩短关键词或省略关键词继续查询。多个候选的名称、说明和方法不足以唯一判断时,列出候选并请用户确认。
109
+ - `get <id>` 调用 `GET /api/gateway/manage/routes/{id}`,默认把后端详情投影成 `schemaVersion: "1.0"` 的稳定调用契约:`gatewayUrlTemplate`、网关方法、追加路径占位符、Query/Header/Body 输入映射、是否必填、访问约束、原始文档/业务说明、CLI 与 SuperApp 示例。
110
+ - 默认输出不包含第三方目标 URL、前置请求 URL 或 `${SECRET.xxx}` 引用。`--raw` 只用于管理员诊断,会原样显示后端详情;不要把 raw 输出放进对话、日志或 SuperApp 代码。
111
+
112
+ 管理查询需要 `GATEWAY_ROUTE.READ`。使用 PAT 时,还需 BI 环境已提供“请求转发”权限点,并在创建 PAT 时选择对应的 `read` / `update` 权限;已有 PAT 不会自动增加新权限。CLI 的本地契约测试只能验证请求构造和字段投影;最终权限、资源存在性和当前配置仍由 BI 服务端裁决。
113
+
114
+ ## 生成 SuperApp 调用
115
+
116
+ 必须先以 `gateway get` 的返回值为事实源。生成前逐项检查:
117
+
118
+ - `route.enabled` 必须为 `true`;否则提示管理员启用。
119
+ - 调用方法必须等于 `invocation.method`,不可用目标请求方法代替网关请求方法。
120
+ - 按 `appendPathPlaceholders` 顺序追加并 URL 编码路径;`${REQUEST.path.1}` 对应第一段。若配置引用 `${REQUEST.path.3}`,前两段也必须提供,即使第二段没有被目标模板直接引用。
121
+ - 为所有 `inputMappings[].required=true` 的 Query、Header、Body 输入组装值;缺少时在调用前报出具体参数名。
122
+ - POST 路由的 `bodyMode=passthrough` 表示调用方提供请求体时应原样透传;`bodyRequired=false` 只表示请求体可省略,不得据此从生成代码中删除 body 支持。字符串请求体直接发送,对象请求体再做 JSON 序列化。
123
+ - 响应保持上游语义:204/205 返回 `null`;JSON Content-Type(`application/json` 或 `+json`)调用 `json()`;文本、XML、JavaScript 和表单响应调用 `text()`;其余文件或二进制响应调用 `blob()`。不得无条件执行 `response.json()`。
124
+ - `loginRequired`、`allowedUsers`、`allowedUserGroups` 和 `allowedCallerHosts` 是访问约束。当前用户或来源不满足时提示申请权限或调整白名单。
125
+ - 代码只调用 `/api/gateway/routes/{routeCode}`,不得出现目标服务 URL、前置请求 URL 或敏感凭据。
126
+
127
+ 示例:
128
+
129
+ ```js
130
+ const response = await fetch(
131
+ `/api/gateway/routes/crm-query-user?keyword=${encodeURIComponent(keyword)}`,
132
+ { method: "GET", credentials: "include" },
133
+ );
134
+ if (!response.ok) {
135
+ throw new Error(`请求转发调用失败:${response.status}`);
136
+ }
137
+ const data = await response.json();
138
+ ```
139
+
140
+ 追加路径按顺序映射为 `${REQUEST.path.1}`、`${REQUEST.path.2}`。CLI 调试调用示例:
141
+
142
+ ```bash
143
+ guancli gateway invoke crm-query-user --method GET --query keyword=张三 --verbose
144
+ ```
145
+
146
+ 出现未授权、路由停用、方法不匹配或参数缺失时,结合返回状态和错误体说明具体修复动作,不要改为绕过网关直连目标服务。
147
+
148
+ `gateway invoke` 无论成功或失败都会先输出目标响应体;`--verbose` 还会输出状态码和脱敏响应头。HTTP 4xx/5xx 等非 2xx 响应在完成输出后返回错误,进程退出码非零,脚本必须同时保留输出并检查退出码。
@@ -50,3 +50,33 @@ lite/kit 执行会在 replay 轮汇集独立请求;只读请求可标为 paral
50
50
  复用 `metric query` 的接口链:`GET /api/metric-platform/metrics/{metricId}`、`GET /api/card/{cardId}/edit/session`、`POST /api/card/{cardId}/edit/{sessionId}/data`;显式维度或排序时还会调用 `GET /api/card/{cardId}/edit` 和 `POST /api/oriput/card/{cardId}/edit/{sessionId}/modify`。代理还原后的 Spring 路由均已确认存在。指标详情要求 `METRIC_PLATFORM/read`(PAT pId 90/read);卡片会话、modify 和取数要求 `BASE/read`,读取编辑配置还要求 `PAGE/update`(PAT 仪表板 pId 1/update)。所以仅查询卡片保存配置时 pId 90/read 可用,显式 dimensions 或 sorting 时 PAT 还要包含仪表板 update;权限不足会逐项返回 403,不能扩大用户资源权限。
51
51
 
52
52
  每项读取指标关联卡片并在独立编辑会话中取数,不拼接多个聚合结果。未显式提供 dimensions 时保留卡片默认分组;提供 dimensions 或 sorting 时只覆盖该项会话中的对应配置,filters 作为本次取数条件传入。指标详情、卡片配置与取数之间不保证快照;改名、发布或权限变化由服务端最终裁决。当前 batch 输入不包含同比、XTD、recent、占比和排名等高级计算;这些仍由单条 `metric query` 的 v2 分支处理,后续若加入 batch 必须沿用相同分流。
53
+
54
+ ## 指标数值等价对比
55
+
56
+ 仅当用户明确要求比较新旧指标、迁移前后指标或两组候选指标的数值时使用本节;普通 `guanmetric flow verify` 的“可用”验收只需证明指标能成功查询,不默认承担数值等价证明。
57
+
58
+ 对每一对指标构造参数完全对称的查询:
59
+
60
+ - 使用相同的显式 `dimensions`;日期维度必须使用双方都支持的相同粒度,如 `日期 toMonth`。
61
+ - 对所有分组维度设置确定性排序;单个日期维度通常使用同一表达式作为 `sortAsc`。没有稳定排序时禁止按数组位置比较。
62
+ - 使用相同的 `filters` 固定业务范围和时间范围。涉及“今天”、动态窗口或卡片默认筛选时,优先改成双方相同的固定区间,避免执行时点和默认卡片配置影响结果。
63
+ - 使用 `valueFormat: "raw"`,避免格式、精度、千分位或单位换算制造展示差异。
64
+ - 显式设置足够的 `limit`,但不要把“传了 10000”理解为结果一定完整;后端或关联卡片仍可能存在更小的返回上限。
65
+
66
+ 消费结果时:
67
+
68
+ 1. 逐项检查 `status=success`,任一侧失败则该对为 `query_error`,不能继续比较。
69
+ 2. 从 `columns` 找出维度列和指标列,不能按固定列名或固定位置猜测业务列。
70
+ 3. 用全部维度值组成复合键,把双方行转换为 `dimensionKey -> raw metric value` 后比较;指标展示名称不同不影响比较。维度键重复、缺失或多指标列无法唯一选择时停止并报告实际列。
71
+ 4. 双方键集合相同后再比较相同键的原始值。未获得用户认可时不要擅自增加浮点容差;确需容差时在结论中写明绝对/相对阈值。
72
+ 5. 若行数达到已知/疑似服务端或卡片上限、双方行数不同且较短一侧是较长一侧的精确前缀,或无法证明查询覆盖完整范围,状态必须为 `inconclusive_truncated`,不能判为 `different`。按日期等稳定维度拆成不重叠固定区间,确保每个分片低于返回上限后再合并比较。
73
+
74
+ 禁止使用以下证据直接下“数值不一致”结论:
75
+
76
+ - 未排序的默认前 50 行顺序不同;
77
+ - 仅第一行、前 N 行或数组位置不同;
78
+ - 一侧结果是另一侧的精确前缀;
79
+ - 指标列标题、关联卡片配置或默认展示维度不同;
80
+ - 只观察到指标引用了某个旧 ID,而没有对相同维度键的实际值做比较。
81
+
82
+ 最终结果至少区分 `same`、`different`、`query_error`、`inconclusive_truncated`。汇报 `different` 时给出第一个真正相同维度键下的值差异;汇报 `inconclusive_truncated` 时给出双方行数、限制参数和建议的分片方式。
@@ -245,6 +245,7 @@ guancli metric query <metric_id> [flags]
245
245
  | `--filter-file string` | 无 | 从 JSON 文件读取一棵结构化条件树;与 `--filter`、`--filter-json` 互斥 |
246
246
  | `--combine-type string` | `AND` | 多条件组合方式:`AND` 或 `OR` |
247
247
  | `--dry-run` | `false` | 只返回规范化查询计划、字段类型和稳定错误码,不执行指标取数;必须同时使用 `-f json`,且不支持普通文件或 stderr 输出目标 |
248
+ | `--explain-requests` | `false` | 只读解析真实执行链路及最终 method/path/body;调用真实 request builder,但不创建 edit session、不 POST、不取数;必须同时使用 `-f json` |
248
249
  | `--sort-asc string` | 无 | 按列升序排序;排序目标可以是适用维度 / 时间维度、指标名本身、保存卡片中真实存在的高级计算列,或泛化结果列名 `<指标名>_<后缀>`(`_yoy`/`_mom`/`_qoq`/`_wow`/`_dod`/`_ytd`/`_qtd`/`_mtd`/`_wtd`/`_dtd`/`_last`/`_recent_*`/`_percentage`/`_rank`);日期子类型使用与 `--dim` 相同的粒度表达式;未命中返回 `METRIC_UNSUPPORTED_DIMENSION`;与 `--sort-desc` 互斥 |
249
250
  | `--sort-desc string` | 无 | 按列降序排序;合法排序目标与 `--sort-asc` 相同(适用维度 / 时间维度、指标名、泛化结果列名);日期子类型使用与 `--dim` 相同的粒度表达式;未命中返回 `METRIC_UNSUPPORTED_DIMENSION`;与 `--sort-asc` 互斥 |
250
251
  | `--columns strings` | 无 | 只显示指定列,逗号分隔,在 CLI 端执行 |
@@ -318,6 +319,7 @@ guancli metric query <metric_id> --profile default --dim 省份 --filter "省份
318
319
  guancli metric query <metric_id> --profile default --dim 省份 --filter "省份 EQ 河南省" --filter "省份 EQ 青海省" --combine-type OR --dry-run -f json
319
320
  guancli metric query <metric_id> --profile default --filter-json '{"field":"省份","operator":"IN","values":["河南省","青海省"]}' --dry-run -f json
320
321
  guancli metric query <metric_id> --profile default --filter-file filters.json --dry-run -f json
322
+ guancli metric query <metric_id> --profile default --dim 省份 --sort-desc 订购数量 --explain-requests -f json
321
323
  guancli metric query <metric_id> --profile default --dim 省份 --sort-desc 订购数量
322
324
  guancli metric query <metric_id> --profile default --dim 省份 --columns "省份,订购数量"
323
325
  guancli metric query <metric_id> --profile default --dim 省份 -f csv
@@ -328,6 +330,8 @@ guancli metric query <metric_id> --profile default --dim 省份 --raw
328
330
 
329
331
  多分支 OR 的 dry-run 会返回 `valid=true`、`executable=false`、`requestCount=0` 和错误码 `FILTER_OR_EXECUTION_UNSUPPORTED`。当前指标取数接口不能在单次请求中无损表达这类 OR,真实执行会在取数前拒绝,不会拆成多次聚合查询后拼接。
330
332
 
333
+ 参数模式、互斥/依赖关系、card 与 `metric-data/v2` 的完整请求体结构见 [指标查询参数模式与请求体](metric-query-recipes.md)。Agent 执行任意单条查询前都先使用同参数的 `--explain-requests -f json`,检查 `data.requestPlan.requests[]`;只有计划有效、可执行且 body 符合业务意图时才移除 explain。`batch-query` 使用独立批量契约且不支持 explain。
334
+
331
335
  #### 可复制模板:筛选并裁剪查询结果
332
336
 
333
337
  ```bash
@@ -363,7 +367,7 @@ CLI 会把条件树确定性展开为“组内 AND、组间 OR”的查询计划
363
367
 
364
368
  ### metric tree
365
369
 
366
- 获取指标目录树。默认输出树形结构,最外层展示指标主题。
370
+ 获取指标目录树。默认输出树形结构,最外层展示指标主题。`-d / --dir-id <topic-id>` 仅读取所选主题的目录树:保留原主题列表的权限过滤和原始输出形状,不读取其他主题的目录树;主题不存在或不在当前身份的主题列表中时返回空结果。
367
371
 
368
372
  ```bash
369
373
  guancli metric tree [flags]
@@ -376,6 +380,7 @@ guancli metric tree --profile default
376
380
  guancli metric tree --profile default --brief
377
381
  guancli metric tree --profile default -f json
378
382
  guancli metric tree --profile default --raw
383
+ guancli metric tree -d <topic-id> --profile default --raw
379
384
  ```
380
385
 
381
386
  > 指标创建、编辑、删除、指标主题/目录创建、公共维度管理、Excel 标准化已迁移到 `guanmetric`。
@@ -8,6 +8,8 @@
8
8
  guancli metric query <metric_id> [flags]
9
9
  ```
10
10
 
11
+ 所有主模式的互斥/依赖矩阵以及最终 `POST /api/metric-platform/metric-data/v2` body 见 [指标查询参数模式与请求体](metric-query-recipes.md)。Agent 对任意单条查询都先追加 `--explain-requests -f json`;该模式会运行最终 request builder,但不会 POST 或取数。只有计划有效、可执行且 body 符合业务意图时才移除 explain 执行。
12
+
11
13
  ## 进入泛化模式的参数
12
14
 
13
15
  只要传入以下任一参数,CLI 就会进入泛化查询模式:
@@ -0,0 +1,358 @@
1
+ # 指标查询参数模式与请求体
2
+
3
+ 本文是 `guancli metric query` 的 Agent 选参事实源。目标不是枚举无限的参数笛卡尔积,而是覆盖所有会改变查询语义或 BI 请求体的有限模式。
4
+
5
+ 需要确认某个真实指标最终会发什么请求时,不要手工替换本文占位符,直接执行:
6
+
7
+ ```bash
8
+ guancli metric query <metric_id> --profile <profile> <查询参数> --explain-requests -f json
9
+ ```
10
+
11
+ 该命令会解析真实指标字段和保存的 card 配置,但不会创建 edit session、不会发送 modify、不会取数。读取 `data.requestPlan.requests[]` 中的 `method/path/body`。
12
+
13
+ ## 1. 先选择唯一主模式
14
+
15
+ | 模式 | 进入参数 | 必需/依赖参数 | 可选参数 | 默认值 | 不能同时出现的主模式 |
16
+ | --- | --- | --- | --- | --- | --- |
17
+ | 基础查询 | 不传任何泛化参数 | 无 | 所有公共修饰参数 | card 模式 | 全部泛化模式 |
18
+ | 同比/环比 | `--compare` | `yoy/mom/qoq/wow/dod` | `--compare-value value/rate/rawdata` | `value` | xtd/last/recent/percentage/rank/adv-calc-json |
19
+ | 累计 | `--xtd` | `ytd/qtd/mtd/wtd/dtd` | 无专属附属参数 | 无 | compare/last/recent/percentage/rank/adv-calc-json |
20
+ | 期末值 | `--last` | 指标必须有时间维度 | 无专属附属参数 | 无 | compare/xtd/recent/percentage/rank/adv-calc-json |
21
+ | 最近 N 周期 | `--recent` | `<正整数>d/w/m/q/y` | `--recent-base YYYY-MM-DD` | 基准日由后端当前上下文决定 | compare/xtd/last/percentage/rank/adv-calc-json |
22
+ | 占比 | `--percentage` | 无 | `--percentage-dim <维度>` | 自动选择层级 | compare/xtd/last/recent/rank/adv-calc-json |
23
+ | 排名 | `--rank-top <正整数>` | `rank-top > 0` | `--rank-order asc/desc`、`--rank-dim <维度>` | `desc`,不限定范围维度 | compare/xtd/last/recent/percentage/adv-calc-json |
24
+ | 原始高级计算 | `--adv-calc-json` | 合法 JSON object | JSON 内字段由 BI 协议决定 | 原样使用 | 不再组合其它主模式 |
25
+
26
+ 附属参数不能单独使用:
27
+
28
+ - `--compare-value` 依赖 `--compare`。
29
+ - `--recent-base` 依赖 `--recent`。
30
+ - `--percentage-dim` 依赖 `--percentage`。
31
+ - `--rank-order`、`--rank-dim` 依赖 `--rank-top`。
32
+
33
+ `--adv-calc-json` 是专家入口。它的结构无法由 CLI 穷举,传入的 object 会原样进入 `metricItems[0].advCalc`。普通 Agent 优先使用结构化参数。
34
+
35
+ ## 2. 公共修饰参数矩阵
36
+
37
+ | 参数族 | 合法组合 | 影响位置 | 说明 |
38
+ | --- | --- | --- | --- |
39
+ | 维度 | `--dim` 可重复,也可用逗号/中文逗号/顿号分隔 | card: modify body 的 `zoneData.row`;泛化: body 的 `dimensions` | 日期粒度写作 `<时间字段> toMonth` 等;必须来自指标详情 |
40
+ | 筛选 | `--filter` / `--filter-json` / `--filter-file` 三选一 | data body 的 `filters` | 不得混用;字段必须能解析真实 `fdId` |
41
+ | 条件关系 | `--combine-type AND/OR` | card data body 的 `combineType`;结构化输入先规范化 | 多分支 OR 当前不可执行,只能查看 dry-run 计划 |
42
+ | 排序 | `--sort-asc` / `--sort-desc` 二选一 | card: modify body 的 `zoneData.sorting`;泛化: body 的 `sorting` | 泛化结果列必须是当前模式生成的精确 alias |
43
+ | 行数 | `--limit` | card data body 或 v2 body 的 `limit` | 默认 50 |
44
+ | 结果裁剪 | `--columns` | 仅 CLI 本地 | 不进入 BI 请求体 |
45
+ | 数值展示 | `--precision`、`--value-format` | 仅 CLI 本地渲染 | 不进入 BI 请求体 |
46
+ | 输出 | `-f`、`-o`、`--raw` | 仅 CLI 输出层 | 不改变查询语义;`--raw` 与 explain/dry-run 不兼容 |
47
+ | 计划 | `--dry-run -f json` | 仅查询指标详情 | 快速校验维度/筛选;不构造最终请求体 |
48
+ | 请求解释 | `--explain-requests -f json` | 只读解析详情与 card meta | 调用真实 builder,返回 method/path/body,不执行 POST |
49
+
50
+ `--dry-run` 和 `--explain-requests` 是两个互斥的计划模式,不能同时传。
51
+
52
+ ## 3. 每种模式的最小合法命令
53
+
54
+ ```bash
55
+ # 基础
56
+ guancli metric query <metric_id> --dim 城市 --filter '城市 = 上海' --limit 20
57
+
58
+ # compare:5 × 3 个有意义的值组合
59
+ guancli metric query <metric_id> --dim "订单日期 toMonth" --compare yoy --compare-value rate
60
+
61
+ # XTD:5 个累计粒度
62
+ guancli metric query <metric_id> --dim 日期 --xtd ytd
63
+
64
+ # last
65
+ guancli metric query <metric_id> --dim 城市 --last
66
+
67
+ # recent:5 个单位;可选固定基准日
68
+ guancli metric query <metric_id> --dim "订单日期 toMonth" --recent 3m --recent-base 2026-04-30
69
+
70
+ # percentage:自动层级 / 指定层级
71
+ guancli metric query <metric_id> --dim 城市 --percentage
72
+ guancli metric query <metric_id> --dim 城市 --percentage --percentage-dim 城市
73
+
74
+ # rank:2 个顺序;可选范围维度
75
+ guancli metric query <metric_id> --dim 城市 --rank-top 10 --rank-order desc --rank-dim 城市
76
+
77
+ # 原始高级计算
78
+ guancli metric query <metric_id> --adv-calc-json '{"advType":"XTD","advValue":{"advType":"XTD","granularity":"YEAR","includeCurrentValue":true}}'
79
+ ```
80
+
81
+ 公共维度、筛选、排序、limit 可以叠加到任一模式,但仍受第 2 节约束。不要把同一个公共修饰项复制成新的“主模式”。
82
+
83
+ ## 4. 基础查询的真实请求链
84
+
85
+ 无维度、无排序时:
86
+
87
+ ```text
88
+ GET /api/metric-platform/metrics/{metricId}
89
+ GET /api/card/{cardId}/edit/session
90
+ POST /api/card/{cardId}/edit/{sessionId}/data
91
+ ```
92
+
93
+ 存在维度或排序时增加 card meta 的读取与隔离会话修改:
94
+
95
+ ```text
96
+ GET /api/metric-platform/metrics/{metricId}
97
+ GET /api/card/{cardId}/edit/session
98
+ GET /api/card/{cardId}/edit
99
+ POST /api/oriput/card/{cardId}/edit/{sessionId}/modify
100
+ POST /api/card/{cardId}/edit/{sessionId}/data
101
+ ```
102
+
103
+ 命令:
104
+
105
+ ```bash
106
+ guancli metric query metric_card \
107
+ --dim 城市 --filter '城市 = 上海' --sort-desc 销售额 --limit 10
108
+ ```
109
+
110
+ data 请求体:
111
+
112
+ ```json
113
+ {
114
+ "limit": 10,
115
+ "filters": [
116
+ {
117
+ "name": "城市",
118
+ "fdId": "fd-city",
119
+ "fdType": "STRING",
120
+ "dsId": "ds-1",
121
+ "filterType": "EQ",
122
+ "filterValue": ["上海"]
123
+ }
124
+ ],
125
+ "combineType": "AND"
126
+ }
127
+ ```
128
+
129
+ modify 请求体不是一个固定小 patch,而是保存 card edit meta 的完整读模型,其中以下位置会被真实 builder 改写:
130
+
131
+ ```json
132
+ {
133
+ "content": {
134
+ "meta": {
135
+ "chartMain": {
136
+ "zoneData": {
137
+ "row": [
138
+ {
139
+ "name": "城市",
140
+ "alias": "城市",
141
+ "dsId": "ds-1",
142
+ "fdId": "fd-city",
143
+ "fdType": "STRING",
144
+ "metaType": "DIM",
145
+ "key": "fd-city"
146
+ }
147
+ ],
148
+ "sorting": [
149
+ {
150
+ "name": "销售额",
151
+ "fdId": "fd-sales",
152
+ "fdType": "DOUBLE",
153
+ "metaType": "METRIC",
154
+ "key": "fd-sales",
155
+ "ordering": "desc"
156
+ }
157
+ ]
158
+ }
159
+ }
160
+ }
161
+ }
162
+ }
163
+ ```
164
+
165
+ 真实 body 还会保留 card meta 的其它字段,所以必须以 `--explain-requests` 输出为准,不能把上面的裁剪示例直接 POST。
166
+
167
+ ## 5. 泛化查询的统一请求体
168
+
169
+ 所有泛化模式都使用:
170
+
171
+ ```text
172
+ GET /api/server-version
173
+ GET /api/metric-platform/metrics/{metricId}
174
+ POST /api/metric-platform/metric-data/v2
175
+ ```
176
+
177
+ 统一外层:
178
+
179
+ ```json
180
+ {
181
+ "projectId": "project-1",
182
+ "metricItems": [
183
+ {
184
+ "name": "销售额",
185
+ "alias": "销售额_<模式后缀>",
186
+ "advCalc": {"<见下表>": "<模式请求体>"}
187
+ }
188
+ ],
189
+ "dimensions": [],
190
+ "filters": [],
191
+ "sorting": [],
192
+ "limit": 50
193
+ }
194
+ ```
195
+
196
+ 空的 `dimensions/filters/sorting` 会省略。以下列出所有结构化主模式生成的 `advCalc`。
197
+
198
+ ### compare
199
+
200
+ `--compare` 映射:`yoy → YEAR`、`mom → MONTH`、`qoq → QUARTER`、`wow → WEEK`、`dod → DAY`。`--compare-value` 映射:`value → VALUE`、`rate → RATE`、`rawdata → RAWDATA`。
201
+
202
+ ```json
203
+ {
204
+ "advType": "COMPARATIVE",
205
+ "advValue": {
206
+ "advType": "COMPARATIVE",
207
+ "dateFdId": "fd-date",
208
+ "valueType": "RATE",
209
+ "offset": 1,
210
+ "offsetType": "YEAR",
211
+ "granularity": "YEAR"
212
+ }
213
+ }
214
+ ```
215
+
216
+ alias 为 `<指标名>_yoy/mom/qoq/wow/dod`。
217
+
218
+ ### XTD
219
+
220
+ `ytd/qtd/mtd/wtd/dtd` 分别映射 `YEAR/QUARTER/MONTH/WEEK/DAY`。
221
+
222
+ ```json
223
+ {
224
+ "advType": "XTD",
225
+ "advValue": {
226
+ "advType": "XTD",
227
+ "granularity": "YEAR",
228
+ "includeCurrentValue": true
229
+ }
230
+ }
231
+ ```
232
+
233
+ alias 为 `<指标名>_ytd/qtd/mtd/wtd/dtd`。
234
+
235
+ ### last
236
+
237
+ ```json
238
+ {
239
+ "advType": "LAST",
240
+ "advValue": {
241
+ "advType": "LAST",
242
+ "dateFdId": "fd-date"
243
+ }
244
+ }
245
+ ```
246
+
247
+ alias 为 `<指标名>_last`。
248
+
249
+ ### recent
250
+
251
+ `d/w/m/q/y` 分别映射 `DAY/WEEK/MONTH/QUARTER/YEAR`,`baselineOffset` 为 `[N-1, 0]`。
252
+
253
+ ```json
254
+ {
255
+ "advType": "RECENT_X",
256
+ "advValue": {
257
+ "advType": "RECENT_X",
258
+ "granularity": "MONTH",
259
+ "baselineOffset": [2, 0],
260
+ "includeCurrentValue": true,
261
+ "baseline": "2026-04-30",
262
+ "actualFinestDimGranularityOpt": "DAY"
263
+ }
264
+ }
265
+ ```
266
+
267
+ `baseline` 仅在传 `--recent-base` 时出现;查询维度比 recent 窗口更细时才出现 `actualFinestDimGranularityOpt`。指定 base 时还会自动把对应日期 `BT` 范围加入外层 `filters`,并在没有显式排序时按日期倒序。alias 为 `<指标名>_recent_<原 recent 值>`。
268
+
269
+ ### percentage
270
+
271
+ 自动层级:
272
+
273
+ ```json
274
+ {"advType":"LEVEL_PERCENTAGE","advValue":{"advType":"LEVEL_PERCENTAGE","auto":true}}
275
+ ```
276
+
277
+ 指定 `--percentage-dim 城市`:
278
+
279
+ ```json
280
+ {"advType":"LEVEL_PERCENTAGE","advValue":{"advType":"LEVEL_PERCENTAGE","auto":false,"tfdId":"fd-city"}}
281
+ ```
282
+
283
+ alias 为 `<指标名>_percentage`。
284
+
285
+ ### rank
286
+
287
+ ```json
288
+ {
289
+ "advType": "RANK",
290
+ "advValue": {
291
+ "advType": "RANK",
292
+ "calDimension": "FIELD",
293
+ "rankType": "RANK",
294
+ "sorting": "DESC",
295
+ "tfdId": "fd-city",
296
+ "topN": {
297
+ "topType": "CUSTOM_NUMBER",
298
+ "count": 10,
299
+ "calculationType": "COUNT"
300
+ }
301
+ }
302
+ }
303
+ ```
304
+
305
+ 不传 `--rank-dim` 时省略 `tfdId`;`asc/desc` 映射 `ASC/DESC`。alias 为 `<指标名>_rank`。
306
+
307
+ ## 6. `--explain-requests` 输出契约
308
+
309
+ 输出仍是 metric JSON envelope,`command=metric.query.explain`。关键字段:
310
+
311
+ ```json
312
+ {
313
+ "schemaVersion": "1.0",
314
+ "command": "metric.query.explain",
315
+ "resultMode": "inline",
316
+ "data": {
317
+ "valid": true,
318
+ "executable": true,
319
+ "queryMode": "card",
320
+ "requestPlan": {
321
+ "fullyResolved": false,
322
+ "httpCallCount": 5,
323
+ "requests": [
324
+ {
325
+ "method": "POST",
326
+ "path": "/api/oriput/card/card-1/edit/{sessionId}/modify",
327
+ "purpose": "在隔离会话中应用维度与排序",
328
+ "mutating": true,
329
+ "placeholders": ["sessionId"],
330
+ "body": {}
331
+ }
332
+ ],
333
+ "clientOnlyOptions": {
334
+ "columns": [],
335
+ "precision": 6,
336
+ "valueFormat": "formatted",
337
+ "format": "json"
338
+ }
339
+ }
340
+ }
341
+ }
342
+ ```
343
+
344
+ - `fullyResolved=false` 只表示真实执行时生成的 `sessionId` 仍是占位符;body 已按当前详情/card meta 构造。
345
+ - `mutating=true` 描述真实执行时该接口会改变服务端状态,包括创建临时 edit session 或修改隔离编辑会话;不能按 HTTP method 推断。它不表示 explain 已执行该请求。
346
+ - explain 自身只发 GET。任何 POST 都只出现在计划里。
347
+ - 认证、Cookie、请求头以及为补齐认证上下文产生的辅助读取不属于 query 请求契约,不会出现在 `requests[]`。
348
+ - `clientOnlyOptions` 明确列出不进入 BI 请求体的选项,避免 Agent 把它们伪造进 body。
349
+ - explain 通过不代表权限、实时数据或后端执行一定成功;它只证明当前客户端能构造请求,并完成已执行的只读预检。
350
+
351
+ ## 7. Agent 固定工作流
352
+
353
+ 1. 用 `metric get <metric_id> --brief -f json` 确认真实维度、时间字段和指标名。
354
+ 2. 从第 1 节选择且只选择一个主模式。
355
+ 3. 按第 2 节添加公共修饰参数;筛选输入三选一、排序二选一。
356
+ 4. Agent 对任意单条 `metric query`(包括无参数基础查询)都先用同一组参数执行 `--explain-requests -f json`;`batch-query` 使用独立批量契约且不支持 explain。
357
+ 5. 只在 explain 返回 `valid=true`、`executable=true` 且请求体符合业务意图后,移除 `--explain-requests` 执行真实查询。
358
+ 6. 若 CLI 返回参数错误,只根据帮助、本文矩阵或稳定错误码修正一次;不要连续猜相近 flag。
@@ -13,6 +13,8 @@ guancli ds execute-sql -inputs <ds_id> --sql-file - < query.sql # 从 stdin 读
13
13
  `execute-sql` 的旧版 public-api fallback 不支持 disable-cache;`--limit` 在非 raw 输出渲染前本地截断,`--raw` 保持后端原始响应。
14
14
  ```
15
15
 
16
+ 程序比对结果时用 `-f json --typed`:NULL 输出 `null`,数值列(按后端 `columns[].fdType`)输出 JSON 数字(原样保留后端的数字文本,长整数和小数位不丢)。不加 `--typed` 的 `-f json` 保持旧格式:所有值都是字符串、NULL 为空串 `""`,分不清 NULL、空串与数字。
17
+
16
18
  `--sql` 与 `--sql-file` 二选一。SQL 较长、包含多层引号或由模型生成时,优先写入 `.sql` 文件再传 `--sql-file`;传 `--sql-file -` 时从 stdin 读取,不会把终端中的普通空输入当作 SQL。
17
19
 
18
20
  数据集 ID (dsId) 可通过 `ds tree` 或 `ds search` 获取。