e10-data 0.1.29 → 0.1.30

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,89 @@
1
1
  # 变更记录
2
2
 
3
+ ## 0.1.30(2026-10-19)—— `query` 适配 executeQueryDsl 新签名(4 个 URL query 参数 + 显式 Content-Type)
4
+
5
+ ### 1. 背景
6
+
7
+ 后端 `executeQueryDsl` 签名调整为:
8
+
9
+ ```java
10
+ executeQueryDsl(@RequestBody DslQueryVo dslQueryVo,
11
+ @RequestParam("confKey") String confKey,
12
+ @RequestParam("confGroup") String confGroup,
13
+ @RequestParam("username") String username,
14
+ @RequestParam("startDate") String startDate,
15
+ Employee employee)
16
+ ```
17
+
18
+ `@RequestParam` 的 required 校验看的是「键是否存在」,不是「值是否非空」;此前 CLI 的 `query` 是
19
+ 「传了才拼、空串不带」,于是「问题里没有人名 / 没有日期」这种最常见情况会直接缺键失败。
20
+ 另外 `confKey` 在客户端侧的参数名就是 `dimension`(`dimension` 的取值即后端 `confKey`)。
21
+
22
+ ### 2. 改动
23
+
24
+ - `query` 的 `buildQueryString()` 改为**四个键恒定发送**:`dimension` / `confGroup` / `username` / `startDate`,
25
+ 未传时以空值发送(`?dimension=&confGroup=&username=&startDate=`)。
26
+ - `query` 调用处显式声明 `Content-Type: application/json;charset=utf-8`(覆盖 `postJson` 默认的 `...;charset=UTF-8`);
27
+ `postJson()` 相应改为「先组 headers 再 fetch」,使 `extraHeaders` 能正确覆盖默认头。
28
+ - 新增调试钩子 `E10_DEBUG_URL=1`:在 `postJson` 内打印真实请求路径(含 query)与最终生效的 `Content-Type`。
29
+ - 同步 `--help` 与 README 的用法说明(dimension = 后端 confKey,四键恒定发送、值可空)。
30
+
31
+ ### 3. 说明
32
+
33
+ - 这四个参数只能走 URL query,不能放进 JSON body —— body 已被 `@RequestBody DslQueryVo` 整体消费。
34
+ - 实测:请求 URL 与 Content-Type 均正确;`conf get` / `ds count` 返回 200,`executeQueryDsl` 仍返回
35
+ `{"code":500,"msg":"请求失败,请稍后再试"}`,需后端按日志确认 `Employee` 解析方式与 `confKey` 空值是否可接受。
36
+
37
+ ## 0.1.29 本地增强(2026-10-19,第 2 项)—— 平台范围提示四字段统一透出
38
+
39
+ > 未改版本号(仍为 `0.1.29`),属本地补丁;与同版本的第 1 项(`module call --conf-file`)并存。
40
+
41
+ ### 1. 背景
42
+
43
+ 平台在「查询范围被收窄」(`tipTimelimit`) 或「范围过大不予查询」(`tipTimeout`) 时,只把标记放在响应内层
44
+ (`data.tipTimelimit` / `data.data.tipTimeout`,层级随通道不同)。此前 CLI:
45
+
46
+ - 人读模式只打 `count` / `rows`,标记不进 stdout;
47
+ - `--json` 只回原始包体,`module call` 更是把响应投影成 `{ok,api_name,http,via,total,rows,data}`,标记被投影丢掉。
48
+
49
+ 结果只有 `output-format` 的 `compact_result.py` 能感知这两个标记,其余调用路径(模型直接看 CLI 输出、
50
+ 脚本消费 `--json`)都会把**局部数据当全区间**,或把 `tipTimeout` 误读成「0 行」。
51
+
52
+ ### 2. 改动
53
+
54
+ - 新增四个模块级辅助函数:`scanTips`(深度 4、每层前 20 个键/元素的递归扫描)、`tipFields`(只保留四个规范字段)、
55
+ `printTips`(人读 `[!]` 行)、`tipTruthy`(`true` / `1` / `"true"` 等真值判定)。
56
+ - `main()` 的 `--json` 统一出口:命中即把四个字段合并到**输出顶层**(`{...tips, ...result}`,原始值优先)。
57
+ - `module call`:在「⑧ 提取精简结果」阶段对**原始响应**扫描,结果并入 `out`(`--json` 与人读两条路径都带);
58
+ 人读模式额外打 `printTips`。
59
+ - `query` / `ds list` / `ds chart` / `ds stat` / `ds count` / `doc list` / `doc count` / `attend statis` 的人读分支补 `printTips`;
60
+ **`doc list` 的 `total=0` 提前返回之前**必须先打(否则 `tipTimeout` 仍会被读成「0 条」)。
61
+ - `usage()` 末尾补「平台范围提示」说明段;`README.md` 补「平台范围提示四字段统一透出」小节。
62
+
63
+ ### 3. 输出形态
64
+
65
+ ```json
66
+ // tipTimelimit:数据照常返回,只是范围被收窄
67
+ {"tipTimelimit":true,"tipTimelimitMsg":"仅统计最近3个月","code":200,"data":{"count":12,"list":[…]}}
68
+
69
+ // tipTimeout:拒答,不是 0 行
70
+ {"tipTimeout":true,"tipTimeoutMsg":"查询数据范围过大,请缩小查询范围","code":200,"data":{…}}
71
+ ```
72
+
73
+ 人读:
74
+
75
+ ```text
76
+ [!] tipTimeout=true|平台提示(数据范围过大,不是 0 行,count 记 null):查询数据范围过大,请缩小查询范围
77
+ [!] tipTimelimit=true|平台提示(数据已返回,但实际查询范围被收窄):仅统计最近3个月
78
+ ```
79
+
80
+ ### 4. 边界
81
+
82
+ - 只透出、不判定:不改 `count`、不改退出码、不做话术替换,语义处理仍归调用方。
83
+ - 未命中不写键(不输出 `false` / 空串),旧脚本按「键不存在」即可判定。
84
+ - 别名兼容:`tipTimeLimit` / `tipTimeLimitMsg` / `tipTimelimitMessage` / `tipTimeoutMessage` 统一归一为规范名。
85
+ - `compact_result.py` 无需改动:其 `_containers()` 末尾已包含顶层 `rec`,顶层透出后照样识别(且更早命中)。
86
+
3
87
  ## 0.1.29(2026-10-19)—— `module call` 支持 `--conf-file`(配置只取一次)
4
88
 
5
89
  ### 1. `module call` 新增 `--conf-file <文件|@文件>`
package/README.md CHANGED
@@ -18,6 +18,9 @@ e10-data --version # 验证
18
18
  ```
19
19
  e10-data login xiaoe-env [--domain <url>] 登录:环境变量 XiaoE token 换 ETEAMSID(免浏览器/免 python)
20
20
  e10-data query <--conn <connId> --model <ebuilder|flow|eteams|attend|hr|document|dw|blog> --schema <数据库> [--table-id <流程ID>] [--debug true|false] --dsl @file.json>
21
+ [--dimension <维度>] [--conf-group <配置分组>] [--username <人员名称>] [--start-date <YYYY-MM>]
22
+ 后四项 = 后端 4 个 @RequestParam(dimension 即后端 confKey / confGroup / username / startDate):
23
+ 恒定随 URL query 发送,未传则以空值发送(?username=&startDate=),缺键会被后端判为参数缺失
21
24
  e10-data ds tables|fields|list|chart|stat|count 数据源(业务模块)取数与元数据
22
25
  e10-data doc count|list 文档查询(documentListCount + documentList)
23
26
  e10-data noun list|scope 词库查询(list = 按 skill/数据源;scope = 按生效范围批量,2026-10-15 新增)
@@ -40,6 +43,23 @@ e10-data scene groups|items 场景定位元数据 getSceneGroups(①
40
43
  - 认证:**加解密由 `e10-login` 负责(本 CLI 不参与)**。凭据出口:`weaver-work-cli auth export-weaver-env`(解析 `WEAVER_BASE_URL` / `WEAVER_ETEAMSID` / `WEAVER_COOKIE`,另补一次 `auth status` 取 `userId` / `tenantKey`),**已移除 e10-login 回退**。命中的出口会记入 `~/.e10-cli/.e10-data-auth-mode`(只存模式名、不存凭据),下次优先复用。环境变量:`E10_DATA_AUTH_PROBE=1` 强制重新探测、`E10_DATA_AUTH_MODE=<模式名>` 手动指定、`E10_DATA_QUIET=1` 抑制告警(告警只走 stderr)。**没有任何可用凭据时**用 `e10-data login xiaoe-env` 登录(见下节)。
41
44
  - 版本更新:`npm i -g e10-data@latest`;查看源上最新 `npm view e10-data version`。
42
45
 
46
+ ### 平台范围提示四字段统一透出(2026-10-19 本地增强)
47
+
48
+ 平台在「查询范围被收窄」或「范围过大不予查询」时,只把标记塞在响应内层(`data.tipTimelimit` / `data.data.tipTimeout`,层级随通道而异)。此前 CLI 只输出 `count` / `rows`,标记被丢掉 ⇒ 下游会把**局部数据当全区间**,或把 `tipTimeout` 误读成「0 行」。
49
+
50
+ 现在 CLI 在**所有出口**统一把四个字段提到输出顶层,原名原值、不翻译不改写:
51
+
52
+ | 字段 | 含义 | 下游应如何处理 |
53
+ | --- | --- | --- |
54
+ | `tipTimelimit` / `tipTimelimitMsg` | 数据已返回,但平台把**实际查询范围收窄** | 数据照常交付 + 开头转述 `tipTimelimitMsg` 原文 + 标注「实际范围已收窄」 |
55
+ | `tipTimeout` / `tipTimeoutMsg` | 范围过大,平台**拒答** | 原文转述 `tipTimeoutMsg`;`count` 记 `null`;🚫 不得当「0 行 / 无数据」 |
56
+
57
+ - **`--json`**:任意命令的输出顶层都会带命中的(且仅命中的)字段,例如 `{"tipTimelimit":true,"tipTimelimitMsg":"…","code":200,"data":{…}}`;未命中的字段不出现(不写 `false` / 空串)。
58
+ - **人读输出**:额外打一行 `[!] tipTimelimit=true|平台提示(…):<平台原文>`,与 `module call` 的 `[!]` flags 同形态;`query` / `ds list|chart|stat|count` / `doc list|count` / `module call` 均适用。
59
+ - **别名兼容**:`tipTimeLimit` / `tipTimeLimitMsg` / `tipTimelimitMessage` / `tipTimeoutMessage` 等价识别,输出统一规范成名。
60
+ - **扫描范围**:深度 4 层、每层前 20 个键/元素(提示不会藏在明细行里,大列表不白扫;5000 行响应实测 0ms)。
61
+ - **不做判定**:CLI 只透出,不据此改 `count`、不改退出码、不做话术替换 —— 语义处理仍由调用方(Agent / `compact_result.py`)负责。
62
+
43
63
  ## 性能:免凭据命令与凭据缓存(v0.1.17 新增)
44
64
 
45
65
  **① 免凭据命令不再取凭据**:`--version` / `-v`、`--help` / `-h`、以及**未知命令的用法提示**都不需要登录态,现在在取凭据之前直接返回。实测这些路径从 ~2.4s 降到 **~0.2s**。
@@ -72,6 +92,9 @@ e10-data scene groups|items 场景定位元数据 getSceneGroups(①
72
92
  ```bash
73
93
  e10-data query --conn 1144919996804022272 --model dw --schema dw --dsl @q.json
74
94
  e10-data query --conn 1144919996804022272 --model dw --schema dw --debug true --dsl @q.json # debug 模式:额外打印实际执行 SQL
95
+ e10-data query --conn 1144919996804022272 --model dw --schema dw --dsl @q.json \
96
+ --dimension 机构 --conf-group contract --username 张三 --start-date 2026-10 # 4 个 URL query 参数(dimension = 后端 confKey)
97
+ # 不传这四项也会以空值发送:?dimension=&confGroup=&username=&startDate= ← 后端 @RequestParam 缺键即失败
75
98
  e10-data ds count --group weaver-project-servicetask --table 715738355537494016
76
99
  e10-data ds fields --group weaver-formreport-service --table 1307487628041044060
77
100
  e10-data doc count --name 智能问数
package/bin/e10-data.mjs CHANGED
@@ -6,7 +6,9 @@
6
6
  * 覆盖(数据查询链路全部家族;登录/HRM 复用 e10-login / hrm-cli,checkEbModulePermission 已废弃不提供):
7
7
  * e10-data login xiaoe-env [--domain <url>] **无凭据时**用环境变量 XIAOE_USER_TOKEN + XIAOE_DOMAIN
8
8
  * (XiaoE 全局 token)换 ETEAMSID 并落 profile(免浏览器/免 python;2026-09-17 移植)
9
- * e10-data query DSL 查询 executeQueryDsl(conn/model/schema/tableId/dslQuery/debug)
9
+ * e10-data query DSL 查询 executeQueryDsl(conn/model/schema/tableId/dslQuery/debug
10
+ * + URL query: dimension(=后端 confKey)/confGroup/username/startDate,
11
+ * 四个键恒定发送、值允许为空)
10
12
  * e10-data ds groups|find|describe|tables|fields|fields-dsl|list|chart|stat|count
11
13
  * 数据源(业务模块)取数与元数据
12
14
  * groups 内置分组清单(仅作 find 的默认扫描集)
@@ -780,19 +782,25 @@ function checkAuthFail(resp, text, j) {
780
782
  }
781
783
  }
782
784
  async function postJson(endpoint, bodyObj, extraHeaders = {}) {
785
+ const headers = {
786
+ "Content-Type": "application/json;charset=UTF-8",
787
+ "accept": "application/json, text/plain, */*",
788
+ "langType": "zh_CN",
789
+ "cookie": auth.cookie,
790
+ "eteamsid": auth.eteamsid,
791
+ "ebbusinessid": EB_BIZ_ID,
792
+ "user-agent": UA,
793
+ "timezoneoffset": "-480",
794
+ ...extraHeaders,
795
+ };
796
+ // 调试钩子(E10_DEBUG_URL=1):打出真实请求路径(含 query)与实际生效的 Content-Type,
797
+ // 用于排查「参数到底有没有发出去 / 头有没有被 extraHeaders 覆盖」这类问题;默认不输出。
798
+ if (process.env.E10_DEBUG_URL) {
799
+ console.error("[http] POST " + endpoint + " | Content-Type: " + headers["Content-Type"]);
800
+ }
783
801
  const resp = await fetch(BASE + endpoint, {
784
802
  method: "POST",
785
- headers: {
786
- "Content-Type": "application/json;charset=UTF-8",
787
- "accept": "application/json, text/plain, */*",
788
- "langType": "zh_CN",
789
- "cookie": auth.cookie,
790
- "eteamsid": auth.eteamsid,
791
- "ebbusinessid": EB_BIZ_ID,
792
- "user-agent": UA,
793
- "timezoneoffset": "-480",
794
- ...extraHeaders,
795
- },
803
+ headers,
796
804
  body: JSON.stringify(bodyObj),
797
805
  });
798
806
  const text = await resp.text();
@@ -946,7 +954,109 @@ function printHuman(title, rows, cols) {
946
954
  }
947
955
  }
948
956
 
957
+ // ---------------- 平台范围提示:tipTimelimit / tipTimeout 统一透出(2026-10-19 本地增强)----------------
958
+ // 平台在「查询范围被收窄」或「范围过大不予查询」时,只把标记塞在响应内层
959
+ // (常见形态 data.tipTimelimit / data.data.tipTimeout,层级随通道不同而不同)。
960
+ // 此前 CLI 人读输出只打 count/rows、--json 也只回原始包体 ⇒ 下游(模型 / 调用方)看不到
961
+ // 「实际范围已被收窄」,会把局部数据当全区间结论;tipTimeout 还会被误读成「0 行」。
962
+ // 现统一在 CLI 出口把四个字段**原名原值提到输出顶层**:不翻译、不改写、不做任何语义判定。
963
+ const TIP_FLAG_KEYS = {
964
+ tipTimeout: ["tipTimeout"],
965
+ tipTimelimit: ["tipTimelimit", "tipTimeLimit"],
966
+ };
967
+ const TIP_MSG_KEYS = {
968
+ tipTimeoutMsg: ["tipTimeoutMsg", "tipTimeoutMessage"],
969
+ tipTimelimitMsg: ["tipTimelimitMsg", "tipTimeLimitMsg", "tipTimelimitMessage", "tipTimeLimitMessage"],
970
+ };
971
+ const TIP_SCAN_MAX_DEPTH = 4; // 覆盖 data → data.data → data.data.data → data.data.data.data
972
+ const TIP_SCAN_MAX_ITEMS = 20; // 每层只扫前 20 个键/元素(提示不会藏在明细行里,避免大列表白扫)
973
+
974
+ function tipTruthy(v) {
975
+ if (v === true) return true;
976
+ if (typeof v === "number") return v === 1;
977
+ if (typeof v === "string") return ["true", "1", "yes", "y"].includes(v.trim().toLowerCase());
978
+ return false;
979
+ }
980
+
981
+ /** 递归扫描响应体,取出四个平台范围提示字段(先到先得;只收真值 flag 与非空文案) */
982
+ function scanTips(node, depth = 0, acc = {}) {
983
+ if (!node || typeof node !== "object" || depth > TIP_SCAN_MAX_DEPTH) return acc;
984
+ if (Array.isArray(node)) {
985
+ for (const it of node.slice(0, TIP_SCAN_MAX_ITEMS)) scanTips(it, depth + 1, acc);
986
+ return acc;
987
+ }
988
+ for (const [key, aliases] of Object.entries(TIP_FLAG_KEYS)) {
989
+ if (acc[key] !== undefined) continue;
990
+ for (const k of aliases) if (tipTruthy(node[k])) { acc[key] = true; break; }
991
+ }
992
+ for (const [key, aliases] of Object.entries(TIP_MSG_KEYS)) {
993
+ if (acc[key] !== undefined) continue;
994
+ for (const k of aliases) {
995
+ const v = node[k];
996
+ if (v !== undefined && v !== null && String(v).trim() !== "") { acc[key] = String(v).trim(); break; }
997
+ }
998
+ }
999
+ let seen = 0;
1000
+ for (const v of Object.values(node)) {
1001
+ if (seen++ >= TIP_SCAN_MAX_ITEMS) break;
1002
+ if (v && typeof v === "object") scanTips(v, depth + 1, acc);
1003
+ }
1004
+ return acc;
1005
+ }
1006
+
1007
+ /** 只保留四个规范字段(存在才带,顺序固定),供 JSON 输出顶层透出 */
1008
+ function tipFields(tips) {
1009
+ const t = tips || {};
1010
+ const out = {};
1011
+ if (t.tipTimeout) out.tipTimeout = true;
1012
+ if (t.tipTimeoutMsg) out.tipTimeoutMsg = t.tipTimeoutMsg;
1013
+ if (t.tipTimelimit) out.tipTimelimit = true;
1014
+ if (t.tipTimelimitMsg) out.tipTimelimitMsg = t.tipTimelimitMsg;
1015
+ return out;
1016
+ }
1017
+
1018
+ /** 人读模式:命中时打 `[!]` 行(与 module call 的 flags 同形态;文案逐字照抄平台原文) */
1019
+ function printTips(tips) {
1020
+ const t = tips || {};
1021
+ if (t.tipTimeout) {
1022
+ console.log("[!] tipTimeout=true|平台提示(数据范围过大,不是 0 行,count 记 null):" + (t.tipTimeoutMsg || "(平台未给出文案)"));
1023
+ }
1024
+ if (t.tipTimelimit) {
1025
+ console.log("[!] tipTimelimit=true|平台提示(数据已返回,但实际查询范围被收窄):" + (t.tipTimelimitMsg || "(平台未给出文案)"));
1026
+ }
1027
+ }
1028
+
949
1029
  // ---------------- query:DSL 查询 ----------------
1030
+ // ① 查询参数透传(2026-10-19 本地增强):后端 executeQueryDsl 现为
1031
+ // executeQueryDsl(@RequestBody DslQueryVo, @RequestParam confKey, @RequestParam confGroup,
1032
+ // @RequestParam username, @RequestParam startDate, Employee)
1033
+ // ⚠️ 其中 confKey 在客户端侧的参数名就是 dimension(dimension 的取值即后端 confKey 的来源)。
1034
+ // ② 这些参数走 URL query,不能塞进 JSON body —— body 已被 @RequestBody DslQueryVo 整体消费,
1035
+ // POJO 参数不会从 body 取值。
1036
+ // ③ 口径(2026-10-19 修正):四个键**恒定发送**,值允许为空(`?username=&startDate=`)。
1037
+ // @RequestParam 的 required 校验看「键是否存在」而非「值是否非空」,缺键即失败、空值合法;
1038
+ // 早前「空串就不带」会把「问题里没有人名/日期」这种最常见情况直接打成 400/500。
1039
+ // ④ 参数名与 CLI flag 分离:--conf-group / --confGroup 两种写法都认,避免踩 parseArgs 静默吞参。
1040
+ // 裸 flag(只写 --username 不给值)会被 parseArgs 记成 true,argStr 归一为空值后照常发送。
1041
+ const QUERY_PARAM_MAP = [
1042
+ ["dimension", "dimension"], // = 后端 confKey
1043
+ ["confGroup", "conf-group"],
1044
+ ["username", "username"],
1045
+ ["startDate", "start-date"],
1046
+ ];
1047
+ function argStr(args, key) {
1048
+ const v = args[key];
1049
+ if (v === undefined || v === true || v === null) return ""; // 裸开关 / 未传 → 视为空值
1050
+ return String(v).trim();
1051
+ }
1052
+ function buildQueryString(args) {
1053
+ return "?" + QUERY_PARAM_MAP
1054
+ .map(([name, flag]) => {
1055
+ const raw = argStr(args, name) || argStr(args, flag); // 驼峰优先,短横线别名兜底
1056
+ return encodeURIComponent(name) + "=" + encodeURIComponent(raw);
1057
+ })
1058
+ .join("&");
1059
+ }
950
1060
  async function cmdQuery(args, json) {
951
1061
  const conn = need("conn", args), model = need("model", args), schema = need("schema", args);
952
1062
  const tableId = args["table-id"] || "";
@@ -959,7 +1069,13 @@ async function cmdQuery(args, json) {
959
1069
  else if (dbg === "false" || dbg === "0") body.debug = false;
960
1070
  else throw new Error("--debug 值必须为 true 或 false(当前: " + args.debug + ")");
961
1071
  }
962
- const r = await postJson("/api/dw/dsl/executeQueryDsl", body);
1072
+ // 接口路径不变;URL 末尾恒定追加 4 个 query(后端的 @RequestParam 缺键即失败,故不再「没有就不带」)
1073
+ // 请求头显式声明 Content-Type(2026-10-19):executeQueryDsl 的 body 是 @RequestBody DslQueryVo,
1074
+ // 需明确 JSON + charset,避免被后端按表单/编码不匹配处理。大小写与 postJson 默认键一致,
1075
+ // 由 ...extraHeaders 覆盖(不会产生两个同名头)。
1076
+ const r = await postJson("/api/dw/dsl/executeQueryDsl" + buildQueryString(args), body, {
1077
+ "Content-Type": "application/json;charset=utf-8",
1078
+ });
963
1079
  if (json) return r;
964
1080
  if (String(r.code) !== "200" && r.code !== undefined && String(r.code) !== "0") {
965
1081
  console.log("code=" + r.code + " msg=" + (r.msg || ""));
@@ -1106,6 +1222,7 @@ async function cmdList(args, json) {
1106
1222
  const r = await postMultipart("/api/dw/newetl/chatBi/ds/getListDatas", { sourceType: args["source-type"] || "LOGIC", queryDto: dto });
1107
1223
  if (json) return r;
1108
1224
  const data = r.data || {};
1225
+ printTips(scanTips(r)); // 平台范围提示置顶(收窄 / 拒答都必须先于数据出现)
1109
1226
  console.log("== 数据列表 count=" + data.count + " 行=" + (data.list || []).length + " ==");
1110
1227
  printHuman("", data.list || [], fields.map((f) => f.fieldId));
1111
1228
  }
@@ -1117,6 +1234,7 @@ async function cmdChart(args, json) {
1117
1234
  const ct = parseWhere(args.where); if (ct) dto.conditionTrees = ct;
1118
1235
  const r = await postMultipart("/api/dw/newetl/chatBi/ds/getChartDatas", { sourceType: args["source-type"] || "LOGIC", queryDto: dto });
1119
1236
  if (json) return r;
1237
+ printTips(scanTips(r));
1120
1238
  printHuman("分组聚合 rows=" + (r.data || []).length, r.data || []);
1121
1239
  }
1122
1240
  async function cmdStat(args, json) {
@@ -1127,6 +1245,7 @@ async function cmdStat(args, json) {
1127
1245
  const r = await postMultipart("/api/dw/newetl/chatBi/ds/getStatDatas", { sourceType: args["source-type"] || "LOGIC", queryDto: dto });
1128
1246
  if (json) return r;
1129
1247
  const data = r.data || {};
1248
+ printTips(scanTips(r));
1130
1249
  if (data.fields && data.fields.length) { for (const f of data.fields) console.log(JSON.stringify(f)); }
1131
1250
  else console.log("(无统计结果)");
1132
1251
  }
@@ -1137,6 +1256,7 @@ async function cmdCount(args, json) {
1137
1256
  const r = await postMultipart("/api/dw/newetl/chatBi/ds/getCountData", { sourceType: args["source-type"] || "LOGIC", queryDto: dto });
1138
1257
  if (json) return r;
1139
1258
  const data = r.data || {};
1259
+ printTips(scanTips(r));
1140
1260
  console.log("totalCount = " + (data.totalCount ?? "-"));
1141
1261
  }
1142
1262
 
@@ -1594,6 +1714,7 @@ async function cmdDocCount(args, json) {
1594
1714
  const r = await postJson("/api/doc/documents/documentListCount", body);
1595
1715
  if (json) return r;
1596
1716
  const data = r.data || {};
1717
+ printTips(scanTips(r));
1597
1718
  console.log("total = " + (data.total ?? "-"));
1598
1719
  }
1599
1720
  async function cmdDocList(args, json) {
@@ -1602,6 +1723,7 @@ async function cmdDocList(args, json) {
1602
1723
  body.conditions = buildDocConditions(args);
1603
1724
  const r1 = await postJson("/api/doc/documents/documentListCount", body);
1604
1725
  if (json) return r1;
1726
+ printTips(scanTips(r1)); // 必须在 total=0 提前返回之前:tipTimeout 会被误读成「0 条」
1605
1727
  const total = (r1.data || {}).total || 0;
1606
1728
  if (!total) { console.log("total = 0(未查询到符合条件的文档)"); return; }
1607
1729
  const totalPages = Math.ceil(total / pageSize);
@@ -2388,8 +2510,11 @@ async function cmdModuleCall(args, json) {
2388
2510
  // ---- ⑧ 提取精简结果 ----
2389
2511
  const payload = modulePayloadOf(obj);
2390
2512
  const flags = moduleFlagsOf(obj, payload.value);
2513
+ // 平台范围提示(tipTimelimit / tipTimeout)在原始响应任意层级 → 统一提到输出顶层,
2514
+ // 否则精简投影只留 data/rows,下游永远看不到「范围已收窄 / 范围过大」。
2515
+ const tips = scanTips(obj);
2391
2516
  const { rows, total } = moduleRowsOf(obj);
2392
- const out = { ok: true, api_name: apiName, http: status, via: payload.via };
2517
+ const out = { ok: true, api_name: apiName, http: status, via: payload.via, ...tipFields(tips) };
2393
2518
  let projected = null;
2394
2519
  if (rows !== null) {
2395
2520
  const shown = limit > 0 ? rows.slice(0, limit) : rows;
@@ -2423,6 +2548,7 @@ async function cmdModuleCall(args, json) {
2423
2548
  console.log(JSON.stringify(payload.value, null, 1));
2424
2549
  }
2425
2550
  for (const f of flags) console.log("[!] " + f);
2551
+ printTips(tips);
2426
2552
  }
2427
2553
 
2428
2554
  // 权限受限是终端态:结果照常给出(供调用方留证),但退出码必须是非 0(对齐 call_module.py)
@@ -2458,6 +2584,7 @@ async function cmdAttendStatis(args, json) {
2458
2584
  return;
2459
2585
  }
2460
2586
  const d = r.data || {};
2587
+ printTips(scanTips(r)); // 假期余额同样可能被平台收窄范围(如只统计近 N 个月)
2461
2588
  const list = Array.isArray(d.vacationBalanceList) ? d.vacationBalanceList : [];
2462
2589
  if (!list.length) { console.log("(空:该人员无假期余额数据) userId=" + user + " " + body.beginDate + "~" + body.endDate); return; }
2463
2590
  console.log("== 考勤假期余额 userId=" + user + "(" + body.beginDate + " ~ " + body.endDate + ")==");
@@ -3058,6 +3185,10 @@ login 登录(**唯一不需要已有凭据**的命令族;其余命令都
3058
3185
 
3059
3186
  query DSL 数据查询 executeQueryDsl
3060
3187
  query --conn <connId> --model <ebuilder|flow|eteams|attend|hr|document|dw|blog> --schema <数据库> [--table-id <流程ID>] [--debug true|false] --dsl @file.json|'{...}'
3188
+ [--dimension <维度>] [--conf-group <配置分组>|--confGroup <配置分组>] [--username <人员名称>] [--start-date <YYYY-MM>]
3189
+ # 后四项 = 后端 4 个 @RequestParam:dimension(= 后端 confKey)/ confGroup / username / startDate
3190
+ # 恒定随 URL query 发送,未传时以空值发送(?username=&startDate=)—— 缺键会被后端判为参数缺失
3191
+ # 接口路径固定 /api/dw/dsl/executeQueryDsl,这四个不能放进 JSON body
3061
3192
  ds 数据源(业务模块)取数与元数据
3062
3193
  ds groups [--pattern <关键词>] [--json] # 内置分组清单(仅用于 find 的默认扫描集)
3063
3194
  ds find --name <关键词> [--groups g1,g2] [--all-groups] [--max-groups N] [--limit N]
@@ -3179,7 +3310,11 @@ scene 场景定位元数据(chatBi getSceneGroups,2026-10-14 新增;**
3179
3310
  # datasetType=datasource → query(DSL)/ ds(数据源,--source-type/--group/--app-id 见条目字段)
3180
3311
  # 示例: scene groups | scene items --group 1060565684758507520
3181
3312
  认证: 凭据**唯一来源 = weaver-work-cli**(本 CLI 不实现任何加解密,**不设 e10-login 回退**);
3182
- 未登录 / 凭据失效时执行 weaver-work-cli auth login(无浏览器:login xiaoe-env 用环境变量 token)`);
3313
+ 未登录 / 凭据失效时执行 weaver-work-cli auth login(无浏览器:login xiaoe-env 用环境变量 token)
3314
+ 平台范围提示(2026-10-19 本地增强): 响应任意层级出现 tipTimelimit / tipTimelimitMsg /
3315
+ tipTimeout / tipTimeoutMsg 时,一律提到**输出顶层**原名原值透出(--json 顶层带这四个键;
3316
+ 人读模式打 "[!] tipXxx=true|平台提示:<原文>")。tipTimelimit=数据已返回但实际范围被收窄;
3317
+ tipTimeout=范围过大拒答(不是 0 行,count 记 null)`);
3183
3318
  }
3184
3319
 
3185
3320
  async function main() {
@@ -3258,7 +3393,15 @@ async function main() {
3258
3393
  default: usage(); process.exit(1);
3259
3394
  }
3260
3395
  } else { usage(); process.exit(1); }
3261
- if (json && result !== undefined) console.log(JSON.stringify(result, null, 1));
3396
+ // --json 统一出口(2026-10-19 本地增强):任意层级命中平台范围提示(tipTimelimit / tipTimeout)
3397
+ // 都提到输出顶层,四个字段原名原值透出,便于下游(模型 / compact_result.py / 脚本)直接判定。
3398
+ if (json && result !== undefined) {
3399
+ const tips = tipFields(scanTips(result));
3400
+ const merged = (Object.keys(tips).length && result && typeof result === "object" && !Array.isArray(result))
3401
+ ? { ...tips, ...result } // 原始响应已带同名键时以原始值为准
3402
+ : result;
3403
+ console.log(JSON.stringify(merged, null, 1));
3404
+ }
3262
3405
  }
3263
3406
  // 退出码:默认 1(用法/未知错误);错误对象带 exitCode 时按其退出(module call 用到 2/3/4/6/7/8)
3264
3407
  main().catch((e) => {
package/package.json CHANGED
@@ -1,31 +1,31 @@
1
- {
2
- "name": "e10-data",
3
- "version": "0.1.29",
4
- "description": "泛微 E10 数据接口统一 CLI(data-assistant 专家配套):登录(login xiaoe-env,环境变量 XiaoE 全局 token 换 ETEAMSID,免浏览器/免 python)/DSL 查询/数据源/文档/词库/关联字段翻译/环境参数配置中心(conf,支持 confGroup 分组)/专家包版本管理;ds 取数(list/chart/stat/count)的 appId 由调用方显式传入 --app-id(v0.1.14 起 CLI 不再自动反查;EB 建模表单 weaver-ebuilder-form-service 必须带,appId 取自 ebdf_obj.app_id;2026-10-09 起不再做 EB 权限前置校验);认证凭据**唯一来源 = weaver-work-cli**(通过 `auth export-weaver-env` 命令出口取明文凭据,**不设 e10-login 回退**;加解密由该 CLI 自行负责,本 CLI 不实现任何加解密;仅 login xiaoe-env 在无凭据时自行换 token,写 profile 仍交 e10-login set)。v0.1.17 性能改造:免凭据命令(--version/--help/未知命令)提前返回、直连 e10-login JS 入口(去掉 shell:true 的 cmd.exe 开销)、新增凭据进程间 TTL 缓存(不落明文);v0.1.19(2026-09-24)新增 ds 发现层聚合 groups/find/describe(进程内并发,全 58 分组扫表实测 ~5.5s),并修复 ds tables --name 因 listByPage 端点切换导致的关键词过滤静默失效;v0.1.18(2026-09-24)新增 ds fields-dsl(接口 /api/dw/newetl/chatBi/ds/fieldsForDSL),并移除凭据明文镜像同步(不再写 ~/.workbuddy/e10-login-cache.md);v0.1.20(2026-09-24)凭据来源切换为 weaver-work-cli 唯一来源、移除 e10-login 回退,并补齐 userId/tenantKey 传递(修复 attend statis 等命令报「缺少查询人员」的问题)。v0.1.21(2026-09-28)新增 module call —— 模块配置接口调用(配置中心驱动):已确定「哪个模块的哪条具体接口」之后的一条命令取数入口(精取该条 confAttrs → 按 http_method/input_params 组参 → 发请求(自动禁代理 + Cookie/eteamsid/UA 三件套)→ 提取精简结果),与技能自带 script/call_module.py 等价(参数名/退出码/提取口径全对齐),复用本 CLI 凭据缓存与 conf 通道、零临时文件;同时 auth 归一化与凭据缓存补齐 userAgent(供 AgentType UA 使用,缺失时从 cookie 的 agentType= 推导)。v0.1.22(2026-10-10):移除 `eb perm-page` 命令(EB 建模表单取数不再做权限前置校验,appId 直接取 ebdf_obj.app_id);`--fields`/`--group-by`/`--order` 等逗号列表支持 `@文件`(逗号字符串或 JSON 数组,与 --where/--stat/--dsl 一致,规避 shell 拆分导致的静默丢列/0 行)。v0.1.23(2026-10-13)新增 `refusal get` —— 拒答话术 getRefusalSpeechTechnique(入参仅 type,返回对象的 data = 话术字符串;默认 auto 通道 = POST multipart 表单优先、返回不含 data 时自动回退 GET ?type=,可用 --http post|get 锁定单通道)。v0.1.25(2026-10-14)新增 `scene groups|items` —— 场景定位元数据 getSceneGroups(① groups 分组骨架 / ② items --group <groupId> 组内条目),由技能 references/scene-locate/scripts/locate.mjs 整体迁入(属元数据定位接口、不取业务数据):默认输出精简 JSON(groups: groupId/groupName/itemCount/groupDesc;items: id/datasetName/datasetType/sourceId[/moduleName/sourceType/sourceGroupId/appId]/datasetDesc),--json 原样输出接口返回,计数走 stderr;--client agent|all|connector 默认 agent。v0.1.26(2026-10-15)新增 `refusal rules` —— 拒答话术规则 getRefusalSpeechRules(**无入参**,返回对象的 data = 对象数组;调用方式与 refusal get 同口径:默认 auto 通道 = POST 空 multipart 表单优先、返回不含 data 时回退 GET,可用 --http post|get 锁定单通道;人读直接打印 data 数组,--json 原样输出整个响应对象)。v0.1.27(2026-10-15)新增 `noun scope --ids <id1,id2|@ids.json>` —— 词库**按生效范围批量检索**(端点 /api/dw/newetl/chatBi/doc/getChatBiDocByScopeId,POST body = 数据集 id 的 JSON 数组;入参 = 数据集 id 列表 = `scene items --group <分组id>` 里每个条目的 `id`;命中 = allScope=1 全局词库 ∪ scopeEffectId 含任一传入 id;id 按字符串下发,避免 19 位雪花号超 2^53 丢精度)。属**新增通道**,原 `noun list --skill <skillKey>`(getChatDocsBySkillKey)行为**完全不变**。 v0.1.28(2026-10-17):`module call` 支持 `--type <confType>`(自定义 confType / 域级配置分组,默认 `api`,不传时行为与旧版一致)与 `--show-params` 精简模式(默认省略 `request_example`,`--full` / `--with-example` 回显,`--no-example` 显式精简),`--show-params` 输出新增 `conf_type` 字段。v0.1.29(2026-10-19)本地增强:`module call` 新增 `--conf-file <文件|@文件>`——直接读本地 confAttrs 跳过 `conf get`(同一 confKey 一次问数只取一次配置),兼容模板缓存与 `conf get --json` 原样输出,带该参数时 `--group`/`--conf-key` 可从文件回填,stderr 提示「未请求配置中心」;缓存缺失/过期时去掉该参数即回退原路径。",
5
- "author": "e10-dw",
6
- "license": "MIT",
7
- "type": "module",
8
- "bin": {
9
- "e10-data": "bin/e10-data.mjs"
10
- },
11
- "main": "bin/e10-data.mjs",
12
- "files": [
13
- "bin/",
14
- "README.md",
15
- "CHANGELOG.md"
16
- ],
17
- "engines": {
18
- "node": ">=18.17.0"
19
- },
20
- "keywords": [
21
- "weaver",
22
- "e10",
23
- "ecology",
24
- "cli",
25
- "data",
26
- "泛微"
27
- ],
28
- "scripts": {
29
- "pack": "npm pack"
30
- }
31
- }
1
+ {
2
+ "name": "e10-data",
3
+ "version": "0.1.30",
4
+ "description": "泛微 E10 数据接口统一 CLI(data-assistant 专家配套):登录(login xiaoe-env,环境变量 XiaoE 全局 token 换 ETEAMSID,免浏览器/免 python)/DSL 查询/数据源/文档/词库/关联字段翻译/环境参数配置中心(conf,支持 confGroup 分组)/专家包版本管理;ds 取数(list/chart/stat/count)的 appId 由调用方显式传入 --app-id(v0.1.14 起 CLI 不再自动反查;EB 建模表单 weaver-ebuilder-form-service 必须带,appId 取自 ebdf_obj.app_id;2026-10-09 起不再做 EB 权限前置校验);认证凭据**唯一来源 = weaver-work-cli**(通过 `auth export-weaver-env` 命令出口取明文凭据,**不设 e10-login 回退**;加解密由该 CLI 自行负责,本 CLI 不实现任何加解密;仅 login xiaoe-env 在无凭据时自行换 token,写 profile 仍交 e10-login set)。v0.1.17 性能改造:免凭据命令(--version/--help/未知命令)提前返回、直连 e10-login JS 入口(去掉 shell:true 的 cmd.exe 开销)、新增凭据进程间 TTL 缓存(不落明文);v0.1.19(2026-09-24)新增 ds 发现层聚合 groups/find/describe(进程内并发,全 58 分组扫表实测 ~5.5s),并修复 ds tables --name 因 listByPage 端点切换导致的关键词过滤静默失效;v0.1.18(2026-09-24)新增 ds fields-dsl(接口 /api/dw/newetl/chatBi/ds/fieldsForDSL),并移除凭据明文镜像同步(不再写 ~/.workbuddy/e10-login-cache.md);v0.1.20(2026-09-24)凭据来源切换为 weaver-work-cli 唯一来源、移除 e10-login 回退,并补齐 userId/tenantKey 传递(修复 attend statis 等命令报「缺少查询人员」的问题)。v0.1.21(2026-09-28)新增 module call —— 模块配置接口调用(配置中心驱动):已确定「哪个模块的哪条具体接口」之后的一条命令取数入口(精取该条 confAttrs → 按 http_method/input_params 组参 → 发请求(自动禁代理 + Cookie/eteamsid/UA 三件套)→ 提取精简结果),与技能自带 script/call_module.py 等价(参数名/退出码/提取口径全对齐),复用本 CLI 凭据缓存与 conf 通道、零临时文件;同时 auth 归一化与凭据缓存补齐 userAgent(供 AgentType UA 使用,缺失时从 cookie 的 agentType= 推导)。v0.1.22(2026-10-10):移除 `eb perm-page` 命令(EB 建模表单取数不再做权限前置校验,appId 直接取 ebdf_obj.app_id);`--fields`/`--group-by`/`--order` 等逗号列表支持 `@文件`(逗号字符串或 JSON 数组,与 --where/--stat/--dsl 一致,规避 shell 拆分导致的静默丢列/0 行)。v0.1.23(2026-10-13)新增 `refusal get` —— 拒答话术 getRefusalSpeechTechnique(入参仅 type,返回对象的 data = 话术字符串;默认 auto 通道 = POST multipart 表单优先、返回不含 data 时自动回退 GET ?type=,可用 --http post|get 锁定单通道)。v0.1.25(2026-10-14)新增 `scene groups|items` —— 场景定位元数据 getSceneGroups(① groups 分组骨架 / ② items --group <groupId> 组内条目),由技能 references/scene-locate/scripts/locate.mjs 整体迁入(属元数据定位接口、不取业务数据):默认输出精简 JSON(groups: groupId/groupName/itemCount/groupDesc;items: id/datasetName/datasetType/sourceId[/moduleName/sourceType/sourceGroupId/appId]/datasetDesc),--json 原样输出接口返回,计数走 stderr;--client agent|all|connector 默认 agent。v0.1.26(2026-10-15)新增 `refusal rules` —— 拒答话术规则 getRefusalSpeechRules(**无入参**,返回对象的 data = 对象数组;调用方式与 refusal get 同口径:默认 auto 通道 = POST 空 multipart 表单优先、返回不含 data 时回退 GET,可用 --http post|get 锁定单通道;人读直接打印 data 数组,--json 原样输出整个响应对象)。v0.1.27(2026-10-15)新增 `noun scope --ids <id1,id2|@ids.json>` —— 词库**按生效范围批量检索**(端点 /api/dw/newetl/chatBi/doc/getChatBiDocByScopeId,POST body = 数据集 id 的 JSON 数组;入参 = 数据集 id 列表 = `scene items --group <分组id>` 里每个条目的 `id`;命中 = allScope=1 全局词库 ∪ scopeEffectId 含任一传入 id;id 按字符串下发,避免 19 位雪花号超 2^53 丢精度)。属**新增通道**,原 `noun list --skill <skillKey>`(getChatDocsBySkillKey)行为**完全不变**。 v0.1.28(2026-10-17):`module call` 支持 `--type <confType>`(自定义 confType / 域级配置分组,默认 `api`,不传时行为与旧版一致)与 `--show-params` 精简模式(默认省略 `request_example`,`--full` / `--with-example` 回显,`--no-example` 显式精简),`--show-params` 输出新增 `conf_type` 字段。v0.1.29(2026-10-19)本地增强:`module call` 新增 `--conf-file <文件|@文件>`——直接读本地 confAttrs 跳过 `conf get`(同一 confKey 一次问数只取一次配置),兼容模板缓存与 `conf get --json` 原样输出,带该参数时 `--group`/`--conf-key` 可从文件回填,stderr 提示「未请求配置中心」;缓存缺失/过期时去掉该参数即回退原路径。 v0.1.30(2026-10-19):query 透传后端 executeQueryDsl 新增的 4 个 @RequestParam(dimension=后端 confKey / confGroup / username / startDate,恒定发送、值允许为空,缺键即失败),并显式声明 Content-Type=application/json;charset=utf-8。",
5
+ "author": "e10-dw",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "bin": {
9
+ "e10-data": "bin/e10-data.mjs"
10
+ },
11
+ "main": "bin/e10-data.mjs",
12
+ "files": [
13
+ "bin/",
14
+ "README.md",
15
+ "CHANGELOG.md"
16
+ ],
17
+ "engines": {
18
+ "node": ">=18.17.0"
19
+ },
20
+ "keywords": [
21
+ "weaver",
22
+ "e10",
23
+ "ecology",
24
+ "cli",
25
+ "data",
26
+ "泛微"
27
+ ],
28
+ "scripts": {
29
+ "pack": "npm pack"
30
+ }
31
+ }