e10-data 0.1.21 → 0.1.23

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,55 @@
1
1
  # 变更记录
2
2
 
3
+ ## 0.1.23(2026-10-13)—— 新增 `refusal get`:拒答话术
4
+
5
+ ### 1. 新命令族 `refusal`(子命令 `get`)
6
+
7
+ - 端点:`/api/dw/newetl/chatBi/getRefusalSpeechTechnique`,**入参仅 1 个 `type`**(场景/类型标识,由后端定义,CLI 原样透传、不做白名单校验);
8
+ 返回对象的 `data` = **需要使用的字符串内容**(话术正文,允许为空串)。
9
+ - 调用方式仿照 `ds fields-dsl`(`/api/dw/newetl/chatBi/ds/fieldsForDSL`)的 `postMultipart` 表单口径,
10
+ 默认通道策略为 **`auto`:先 POST 表单 → 返回不含 `data` 时自动回退 `GET ?type=`**(见 §3 实测确认 POST 表单即命中),
11
+ 任一条通道拿到 `data` 即止;可用 `--http post|get` 强制单一通道。
12
+ - 输出:人读直接打印 `data` 话术正文(空串 → `(空:type=<type> 无话术内容)`,**空串 ≠ 失败**);
13
+ `--json` 原样输出整个响应对象;`E10_DATA_DEBUG=1` 向 stderr 打印实际生效通道(stdout 保持纯净)。
14
+ - 失败语义:两条通道均未拿到 `data` → 抛错并列出各通道 `code/msg`;**凭据失效(401)不换通道重试**,直接报登录态失效。
15
+
16
+ ```bash
17
+ e10-data refusal get --type 1 # 人读:直接打印话术正文
18
+ e10-data refusal get --type 1 --json # 原样输出 {data, actionMsg}
19
+ e10-data refusal get --type 1 --http get # 锁定 GET 单通道
20
+ ```
21
+
22
+ ### 2. 同步改动
23
+
24
+ - `CMD_SUBS` 白名单新增 `refusal: ["get"]`;`main()` 分发表新增 `refusal` 分支;`usage()` 新增 `refusal` 帮助段;
25
+ 头部注释、`README.md`(命令总览 / 用法示例 / 新增 `refusal get` 章节)同步。
26
+
27
+ ### 3. 实测确认(2026-10-13)
28
+
29
+ - `type=Extraterritorial`(泛微网络租户)实测:**POST multipart 表单字段 `type` 即命中**,`auto` 无需回退 GET;
30
+ 返回体 `{"data":"<话术字符串>","actionMsg":{"code":0,"message":"成功"}}`。契约已回填代码注释与 README。
31
+
32
+ ## 0.1.22(2026-10-10)—— 移除 `eb perm-page`;逗号列表支持 `@文件`
33
+
34
+ ### 1. 移除 `eb` 命令族(`eb perm-page`)—— EB 建模表单不再做权限前置校验
35
+
36
+ > ⚠️ **破坏性**:`e10-data eb perm-page` 命令**已删除**;`e10-data eb perm-page …` 现按「未知命令」处理(打印用法、退出码 1)。
37
+
38
+ - 背景:EB 建模表单取数**不再做权限判定**(数据源通道本身不拦截 EB 表权限);`--app-id` 直接取 `ebdf_obj.app_id`
39
+ (`obj_id` = `ebdf_obj.id` = 数据源 tableId;两值均为 19 位大整数,按字符串原样传参)。
40
+ - 移除范围:`CMD_SUBS` 白名单 `eb` 项、`main()` 分发表 `eb` 分支、`cmdEbPermPage()` 实现与
41
+ `EB_PERM_PAGE_PATH` 常量、`usage()` 的 `eb` 帮助段;并同步改写 `ds describe` / `dsRouteHint` / 头部注释等
42
+ 指向该命令的提示为「不做权限前置校验,appId 取 `ebdf_obj.app_id`」。
43
+
44
+ ### 2. `--fields` / `--group-by` / `--order` 支持 `@文件`
45
+
46
+ - 新增 `readListArg()`:逗号列表参数接受 `@文件`,文件内容可为**逗号字符串 `a,b,c`** 或 **JSON 数组 `["a","b","c"]`**,
47
+ 与 `--where` / `--stat` / `--dsl` 一致。
48
+ - 目的:让逗号不出现在命令行上,规避 shell(尤其 Windows 交互式 PowerShell 把未加引号的 `a,b,c` 当数组拆开)
49
+ 拆分导致**静默丢列 / 静默 0 行**。
50
+ - 覆盖:`ds list --fields`、`ds chart --group-by`、`ds list --order`、`module call --fields`;
51
+ `usage()` 与 `README.md` 同步标注为 `a,b|@f`。
52
+
3
53
  ## 0.1.21(2026-09-28)—— 新增 `module call`:模块配置接口调用(配置中心驱动)
4
54
 
5
55
  ### 1. 新命令族 `module`(子命令 `call`)
package/README.md CHANGED
@@ -26,12 +26,14 @@ e10-data conf get|batch|batch-file|group|group-file|resolve|resolve-file|add|
26
26
  e10-data exp status|manifest|commits|file|pull|init|commit|lock|release|rollback 专家包版本管理
27
27
  e10-data attend statis 考勤假期余额统计 getAttendInfoStatis(hr-attendance 病假/事假等,v0.1.5 新增)
28
28
  e10-data module call 模块配置接口调用(配置中心驱动;已确定「哪个模块的哪条具体接口」之后的统一取数入口,v0.1.21 新增)
29
+ e10-data refusal get --type <type> 拒答话术 getRefusalSpeechTechnique(入参 type → data 话术字符串,v0.1.23 新增)
29
30
  ```
30
31
 
31
32
  ## 通用约定
32
33
 
33
34
  - `--json`:原样输出接口 JSON(供脚本/Agent 消费);默认人读输出。
34
35
  - 复杂 JSON/中文参数:推荐 `@文件` 承载(如 `--dsl @q.json`、`--where @cond.json`)。
36
+ - **逗号列表参数**(`--fields` / `--group-by` / `--order`):**一律走 `@文件`** —— 逗号不出现在命令行上,避免被 shell(尤其 Windows 交互式 PowerShell 把未加引号的 `a,b,c` 当数组拆开)拆分后**静默丢列 / 静默 0 行**。文件内容两种形态均可:逗号字符串 `a,b,c` 或 JSON 数组 `["a","b","c"]`。
35
37
  - ⚠️ 大整数精度(自动处理):E10 的 ID 多为 19 位,超过 JS 安全整数(2^53-1≈9e15)。CLI 解析 JSON 时**自动把超范围裸整数转成字符串**再发请求/输出(实测后端接受字符串做 =/IN/BETWEEN 数值比较),无需手动加引号,也不误伤字符串正文。
36
38
  - 认证:**加解密由 `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` 登录(见下节)。
37
39
  - 版本更新:`npm i -g e10-data@latest`;查看源上最新 `npm view e10-data version`。
@@ -89,6 +91,9 @@ e10-data exp status --expert data-assistant
89
91
  e10-data exp pull --expert data-assistant --dir <本地专家包目录> [--dry-run] [--force]
90
92
  e10-data attend statis # 查当前登录用户当月假期余额(病假/事假/年假)
91
93
  e10-data attend statis --user 6264843388578732287 --begin 2026-09-01 --end 2026-09-30
94
+ e10-data refusal get --type <type> # 拒答话术(人读:直接打印话术正文)
95
+ e10-data refusal get --type <type> --json # 原样输出整个响应对象(data + actionMsg)
96
+ e10-data refusal get --type <type> --http get # 强制走 GET ?type=(默认 auto:POST 表单优先、失败回退 GET)
92
97
  # 无凭据时先登录(环境变量里的 XiaoE 全局 token 换 ETEAMSID)
93
98
  XIAOE_USER_TOKEN=<XiaoE token> XIAOE_DOMAIN=<erpa域名> e10-data login xiaoe-env --domain https://www.e-cology.com.cn
94
99
  ```
@@ -276,7 +281,7 @@ e10-data attend statis [--user <人员ID>] [--begin 2026-09-01] [--end 2026-09-3
276
281
  e10-data module call --conf-key <confKey> --group <模块> \
277
282
  [--args '<JSON>'|--args @params.json] [--query "k=v&k2=v2"] \
278
283
  [--method GET|POST|FORM] [--header 'K: V'|--headers '<JSON>'|@file] \
279
- [--limit 50] [--fields a,b] [--raw] [--show-params] [--dry-run] [--timeout 60] [--json]
284
+ [--limit 50] [--fields a,b|@f] [--raw] [--show-params] [--dry-run] [--timeout 60] [--json]
280
285
  ```
281
286
 
282
287
  | 参数 | 说明 |
@@ -314,3 +319,36 @@ e10-data module call --conf-key POST_EHR_GET_REPORT_LIST --group EHR --args @par
314
319
  e10-data module call --conf-key GET_XXX --group XXX --query "pageNo=1&pageSize=20" --limit 2 --fields id,name --json
315
320
  ```
316
321
 
322
+ ## refusal get 拒答话术(v0.1.23 新增)
323
+
324
+ **用途**:查询**拒答话术**(命中拒答场景时对用户说的话术文案)。接口 `/api/dw/newetl/chatBi/getRefusalSpeechTechnique`,
325
+ 入参只有 1 个 `type`,返回对象中的 `data` 即**需要使用的字符串内容**。
326
+
327
+ ```bash
328
+ e10-data refusal get --type <type> [--http auto|post|get] [--json]
329
+ ```
330
+
331
+ | 参数 | 说明 |
332
+ |---|---|
333
+ | `--type` | **必填**;场景/类型标识,取值由后端定义(CLI **原样透传、不做白名单校验**) |
334
+ | `--http` | 通道:`auto`(默认)= 先 POST multipart 表单 → 返回不含 `data` 时**自动回退** `GET ?type=`,任一条拿到 `data` 即止;`post` = 仅 POST 表单(与 `ds fields-dsl` 同口径);`get` = 仅 `GET ?type=` |
335
+
336
+ - **已实测的接口契约**(2026-10-13,泛微网络租户,`type=Extraterritorial`):
337
+ 请求 = `POST /api/dw/newetl/chatBi/getRefusalSpeechTechnique`,**multipart 表单字段 `type`**;
338
+ 返回 = `{"data":"<话术字符串>","actionMsg":{"code":0,"message":"成功"}}` ⇒ 正常路径**只发 1 次请求**(`auto` 无需回退)。
339
+ - **人读输出**:直接打印 `data` 话术正文;`data` 为空串时打印 `(空:type=<type> 无话术内容)`(**空串 ≠ 调用失败**)。
340
+ - **`--json`**:原样输出整个响应对象(`{"data":"…","actionMsg":{"code":0,"message":"成功"}}`;**成功标记看 `actionMsg.code`,无顶层 `code`/`msg`**),供脚本 / Agent 消费。
341
+ - **排障**:`E10_DATA_DEBUG=1` 时向 **stderr** 打印实际生效的通道(`post` / `get`),stdout 保持纯净。
342
+ - **两种通道均未拿到 `data`**:抛错并列出每条通道的 `code/msg`;凭据失效(401)时**不换通道重试**,直接报登录态失效。
343
+
344
+ ```bash
345
+ # 人读:直接拿到话术正文
346
+ e10-data refusal get --type 1
347
+
348
+ # 取结构化结果(Agent / 脚本)
349
+ e10-data refusal get --type 1 --json
350
+
351
+ # 该端点确认走 GET 时,锁定单通道(少一次探测请求)
352
+ e10-data refusal get --type 1 --http get
353
+ ```
354
+
package/bin/e10-data.mjs CHANGED
@@ -11,10 +11,10 @@
11
11
  * 数据源(业务模块)取数与元数据
12
12
  * groups 内置分组清单(仅作 find 的默认扫描集)
13
13
  * find 跨分组找表(进程内并发扇出;产物=候选表清单 + 路由建议,不取数)
14
- * describe 一次并发取回「字段 + 词库」(不做权限校验,EB 权限仍走 eb perm-page)
14
+ * describe 一次并发取回「字段 + 词库」(不做权限校验)
15
15
  * appId 一律由调用方显式传入(v0.1.14 起 CLI 不再自动反查):
16
16
  * EB 建模表单(groupId=weaver-ebuilder-form-service)取数必须带 --app-id <id>,
17
- * 该值只能来自 chat_bi_eb_config_map 的权限校验查询(同一次查询给出权限与 appId);
17
+ * 该值取自 ebdf_obj.app_id(2026-10-09 起不再做 EB 权限前置校验);
18
18
  * 不带 appId 时按原样发送,由后端报错(CLI 不做拦截)
19
19
  * e10-data doc count|list 文档查询 documentListCount / documentList
20
20
  * e10-data noun list 词库 getChatDocsBySkillKey
@@ -28,6 +28,8 @@
28
28
  * 发请求(禁代理 + Cookie/eteamsid/UA 三件套)→ 提取精简结果(2026-09-28)
29
29
  * 与技能自带 script/call_module.py 等价(参数/退出码/提取全对齐),
30
30
  * 复用本 CLI 凭据缓存与 conf 通道、零临时文件
31
+ * e10-data refusal get 拒答话术 getRefusalSpeechTechnique(入参 type → data 话术字符串;
32
+ * auto 通道 = POST 表单优先、GET ?type= 回退,2026-10-13)
31
33
  *
32
34
  * 通用:--json 原样输出;复杂 JSON 参数支持 @文件 或 内联(中文走 @文件 更稳)
33
35
  * 认证:凭据**唯一来源 = weaver-work-cli**(本 CLI 不实现任何加解密,**不设 e10-login 回退**):
@@ -706,8 +708,8 @@ const CMD_SUBS = {
706
708
  conf: ["get", "batch", "batch-file", "group", "group-file", "resolve", "resolve-file", "add", "add-file", "update", "delete"],
707
709
  exp: ["status", "manifest", "commits", "file", "pull", "init", "commit", "lock", "release", "rollback"],
708
710
  attend: ["statis"],
709
- eb: ["perm-page"],
710
711
  module: ["call"],
712
+ refusal: ["get"],
711
713
  };
712
714
  function commandIsKnown(pos) {
713
715
  const family = pos[0], sub = pos[1];
@@ -895,6 +897,21 @@ function readJsonArg(val, label) {
895
897
  if (val.startsWith("@")) raw = fs.readFileSync(val.slice(1), "utf-8");
896
898
  return parseJsonSafe(raw, label);
897
899
  }
900
+ // 逗号列表参数(--fields / --group-by / --order)取值:支持 @文件,与 --where / --stat / --dsl 一致。
901
+ // 目的:让逗号列表不出现在命令行上,避免被 shell(尤其 Windows 交互式 PowerShell 把未加引号的 a,b,c 当数组拆开)
902
+ // 拆成多个独立参数 —— 拆分后不报错,会静默丢列或静默 0 行(2026-10-09 实测)。
903
+ // 文件内容两种形态均可:逗号字符串 "a,b,c" 或 JSON 数组 ["a","b","c"]。
904
+ function readListArg(val) {
905
+ if (val === undefined || val === null) return val;
906
+ let raw = String(val);
907
+ if (raw.startsWith("@")) raw = fs.readFileSync(raw.slice(1), "utf-8");
908
+ raw = raw.trim();
909
+ if (raw.startsWith("[")) {
910
+ const arr = parseJsonSafe(raw, "列表文件");
911
+ if (Array.isArray(arr)) return arr.map((x) => String(x)).join(",");
912
+ }
913
+ return raw;
914
+ }
898
915
  function need(arg, args) { if (!args[arg]) throw new Error("缺少必填参数 --" + arg); return args[arg]; }
899
916
  // 文件/JSON 参数取值:兼容 --flag <v> 与位置参数 <v>(如 conf add-file @items.json / --add-file @items.json)
900
917
  function flagOrPos(args, flag, keys) {
@@ -1001,8 +1018,9 @@ function parseStat(val) {
1001
1018
  return list.map((s) => ({ fieldId: s.fieldId, fieldName: s.fieldId, objId: "", mainField: true, aggregator: s.aggregator || "SUM", ...(s.fieldAlias ? { fieldAlias: s.fieldAlias } : {}) }));
1002
1019
  }
1003
1020
  function parseOrder(val) {
1004
- if (!val) return [];
1005
- return val.split(",").map((s) => {
1021
+ const v = readListArg(val);
1022
+ if (!v) return [];
1023
+ return v.split(",").map((s) => {
1006
1024
  const [f, dir] = s.trim().split(/\s+/);
1007
1025
  return { fieldId: f, fieldName: f, objId: "", isAsc: (dir || "asc").toLowerCase() !== "desc", mainField: true };
1008
1026
  });
@@ -1069,7 +1087,7 @@ async function cmdFieldsForDSL(args, json) {
1069
1087
  }
1070
1088
  }
1071
1089
  async function cmdList(args, json) {
1072
- const gid = need("group", args), tid = need("table", args), fld = need("fields", args);
1090
+ const gid = need("group", args), tid = need("table", args), fld = readListArg(need("fields", args));
1073
1091
  const fields = fld.split(",").map((f) => ({ fieldId: f, fieldName: f, objId: String(tid), mainField: true }));
1074
1092
  const dto = await dsDto(args, tid, gid, parseInt(args.page || "1", 10), parseInt(args.size || "20", 10), "getListData");
1075
1093
  dto.queryFields = fields;
@@ -1083,7 +1101,7 @@ async function cmdList(args, json) {
1083
1101
  printHuman("", data.list || [], fields.map((f) => f.fieldId));
1084
1102
  }
1085
1103
  async function cmdChart(args, json) {
1086
- const gid = need("group", args), tid = need("table", args), gb = need("group-by", args);
1104
+ const gid = need("group", args), tid = need("table", args), gb = readListArg(need("group-by", args));
1087
1105
  const dto = await dsDto(args, tid, gid, parseInt(args.page || "1", 10), parseInt(args.size || "100", 10), "getChartData");
1088
1106
  dto.groupFields = gb.split(",").map((f) => ({ fieldId: f, fieldName: f, objId: String(tid), mainField: true }));
1089
1107
  dto.groupStatFields = parseStat(need("stat", args)).map((s) => ({ ...s, objId: String(tid) }));
@@ -1121,9 +1139,8 @@ async function cmdCount(args, json) {
1121
1139
  // 故把「找表」「字段+词库」各收敛成 1 次 CLI 调用,**在进程内并发发 HTTP**(一次凭据搞定)。
1122
1140
  // 实测同样的全分组扇出:进程内并发 ≈5.3~5.9s(逐进程 47.5s / 逐进程串行 ~313s)。
1123
1141
  //
1124
- // ⚠️ 权限闸门不在本命令范围:`ds/listByPage` 虽然也回带 appId,但**数据源通道不拦权限**
1125
- // (实测:未授权表在数据源上照样能取到数据)。拿 listByPage 的 appId 跳过校验 = 闸门静默失效。
1126
- // ⇒ EB 建模表单取数前**必须单独**执行 `e10-data eb perm-page`,appId 只从那次调用取。
1142
+ // 权限闸门已取消(2026-10-09 用户要求):EB 建模表单取数不再做权限判定。
1143
+ // appId 直接用 ebdf_obj.app_id(obj_id = ebdf_obj.id = 数据源 tableId;均按字符串原样传参)。
1127
1144
 
1128
1145
  // 分组清单(技能文档 data-source-query.md 1.1 的 58 个)。**仅作 ds find 的「默认扫描候选集」**,
1129
1146
  // 不参与任何取数参数推导。分组随环境模块安装情况增减,故支持 --groups 显式覆盖。
@@ -1326,7 +1343,7 @@ function dsRouteHint(cands) {
1326
1343
  return { channel: "WORKFLOW", desc: "WORKFLOW 模块配置通道 —— 流程类问题一律走模块接口,不用 DSL,也不走数据源", reason: "命中流程类分组" };
1327
1344
  }
1328
1345
  if (has((c) => c.groupId === "weaver-ebuilder-form-service" || c.serviceMark === "ebuilder")) {
1329
- return { channel: "DS_EB", desc: "数据源通道(EB 建模表单)—— 取数前必须先过 eb perm-page 权限校验,appId 取自那次校验", reason: "命中 EB 分组" };
1346
+ return { channel: "DS_EB", desc: "数据源通道(EB 建模表单)—— 不做权限前置校验,appId 取自 ebdf_obj.app_id", reason: "命中 EB 分组" };
1330
1347
  }
1331
1348
  if (has((c) => c.serviceMark === "form")) {
1332
1349
  return { channel: "DS_FORM", desc: "数据源通道(业务表单 / 流程业务表单)", reason: "命中业务表单分组" };
@@ -1433,8 +1450,7 @@ async function cmdDsFind(args, json) {
1433
1450
  }
1434
1451
 
1435
1452
  // describe:一次并发取回「字段 + 词库」(两者入参同为 (groupId, tableId),彼此无依赖)
1436
- // ⚠️ 本命令**不做权限校验** —— EB 建模表单取数前的权限闸门仍必须**单独**执行 `e10-data eb perm-page`
1437
- // (appId 只从那次调用取)。原因:数据源通道本身不拦权限,权限判定与取数必须分开。
1453
+ // 本命令**不做权限校验**:EB 建模表单取数不再做权限前置校验(2026-10-09 取消),appId 直接取 ebdf_obj.app_id。
1438
1454
  function collectFieldNodes(list, out, subName) {
1439
1455
  for (const f of list || []) {
1440
1456
  if (!f) continue;
@@ -1520,10 +1536,10 @@ async function cmdDsDescribe(args, json) {
1520
1536
  }
1521
1537
  out.relatedFields = dsRelatedHint(gid, (out.fields && out.fields.detailCount) || 0);
1522
1538
  out.channelHint = dsRouteHint([{ groupId: gid, serviceMark: dsGroupMark(gid) }]);
1523
- // ⚠️ 权限闸门**不在本命令范围**:EB 建模表单取数前必须另行执行 `e10-data eb perm-page`(appId 只从那次校验取)
1539
+ // 权限闸门已取消(2026-10-09 用户要求):EB 建模表单取数不再做权限判定,appId 直接取 ebdf_obj.app_id
1524
1540
  out.permissionNote = gid === "weaver-ebuilder-form-service"
1525
- ? "该分组是 EB 建模表单:取数前必须另行执行 `e10-data eb perm-page --obj-id <tableId> --form-table <物理表名>` 判权限并取 appId(--form-table 用 ds find / ds tables 返回的 physicalTable)"
1526
- : "非 EB 分组,数据源通道无需 eb perm-page 前置校验";
1541
+ ? "该分组是 EB 建模表单:取数不再做权限前置校验,--app-id 直接取 ebdf_obj.app_id(obj_id = ebdf_obj.id = 数据源 tableId)"
1542
+ : "非 EB 分组,数据源通道无需 appId";
1527
1543
  out.elapsedMs = Date.now() - t0;
1528
1544
  if (errors.length && args.require) throw new Error("describe 存在失败分支,--require 下不继续:" + JSON.stringify(errors));
1529
1545
  if (json) return out;
@@ -1537,46 +1553,7 @@ async function cmdDsDescribe(args, json) {
1537
1553
  else console.log("词库: " + (lx.noLexicon ? "该数据集无词库" : "命中 " + lx.count + " 条(skill=" + lx.skillKey + ")"));
1538
1554
  console.log("关联字段: " + (out.relatedFields.likelyApplicable ? "适用(" + out.relatedFields.tableOrigin + ")" : "不适用(" + out.relatedFields.tableOrigin + ")") + " —— " + out.relatedFields.note);
1539
1555
  console.log("通道提示: " + out.channelHint.channel + " —— " + out.channelHint.desc);
1540
- console.log("权限: 不在本命令范围 —— " + out.permissionNote);
1541
- }
1542
-
1543
- // ---------------- eb:EB 建模表单权限校验 ----------------
1544
- // chatBi EB 配置映射分页查询:EB 表权限前置校验的 CLI 通道
1545
- // (替代原「DSL 查 dw.chat_bi_eb_config_map + 客户端拼 (all_scop=1 OR target_id=本人)」写法)
1546
- // 服务端已按登录人过滤:有权限 → total>=1 且行内回带 appId;无权限 / 表不存在 → total=0
1547
- const EB_PERM_PAGE_PATH = "/api/dw/newetl/chatBi/getChatBiEbConfigMapByPage";
1548
- async function cmdEbPermPage(args, json) {
1549
- // ⚠️ obj-id / app-id 为 19 位大整数:必须原样字符串传递。
1550
- // 经 JS Number 会舍入(840360795890851856 → 840360795890851840),实测后端接受字符串,
1551
- // 但吃下舍入后的数字会返回 total=0 —— 把「有权限」静默伪装成「无权限」。
1552
- const objId = String(need("obj-id", args));
1553
- const formTable = String(need("form-table", args));
1554
- // ⚠️ obj-id / form-table 缺失时后端不是报错,而是 HTTP 200 + 无 actionMsg + data 空,
1555
- // 外观与「无权限」完全一致 → 必须用 need() 在本地拦掉(need 对空串同样报错)。
1556
- const body = {
1557
- objId,
1558
- formTable,
1559
- pageNum: parseInt(args.page || "1", 10),
1560
- pageSize: parseInt(args.size || "10", 10),
1561
- };
1562
- if (typeof args["app-id"] === "string" && args["app-id"] !== "") body.app_id = String(args["app-id"]);
1563
- const r = await postJson(EB_PERM_PAGE_PATH, body);
1564
- if (json) return r;
1565
- // ⚠️ 失败判定:非法入参时 actionMsg 缺失(code=undefined),业务失败时 code!==0 → 一并按失败输出
1566
- const am = r.actionMsg || {};
1567
- if (am.code !== 0) { console.log("code=" + am.code + " msg=" + (am.message || "")); return; }
1568
- const d = r.data || {};
1569
- const recs = d.records || [];
1570
- const total = d.total === undefined || d.total === null ? 0 : d.total;
1571
- const first = recs[0] || null;
1572
- console.log("total = " + total);
1573
- printHuman("", recs, ["id", "objId", "formTable", "appId", "allScop", "targetId", "enabled", "deleteType"]);
1574
- console.log(total >= 1
1575
- ? (first
1576
- ? "结论: 有权限(appId=" + first.appId + ")"
1577
- : "结论: 有权限(total>=1;当前页无记录,appId 请用 --page 1 获取)")
1578
- : "结论: 无权限(total=0)");
1579
- if (args.require && total < 1) throw new Error("没有查询该 EB 表的权限(total=0)");
1556
+ console.log("权限: " + out.permissionNote);
1580
1557
  }
1581
1558
 
1582
1559
  // ---------------- doc:文档查询 ----------------
@@ -2142,7 +2119,7 @@ async function cmdModuleCall(args, json) {
2142
2119
  const group = String(args.group);
2143
2120
  const timeoutMs = Math.max(1, parseInt(args.timeout || "60", 10) || 60) * 1000;
2144
2121
  const limit = (args.limit === undefined || args.limit === true) ? 50 : (parseInt(String(args.limit), 10) || 0);
2145
- const fields = (typeof args.fields === "string" && args.fields) ? args.fields.split(",").map((s) => s.trim()).filter(Boolean) : null;
2122
+ const fields = (typeof args.fields === "string" && args.fields) ? readListArg(args.fields).split(",").map((s) => s.trim()).filter(Boolean) : null;
2146
2123
 
2147
2124
  // ---- ① 精取该条接口配置(confType 固定 api;confGroup 二级定位必带)----
2148
2125
  let rec;
@@ -2698,6 +2675,62 @@ async function expRollback(args, json) {
2698
2675
  console.log("== 回滚成功 =="); console.log(JSON.stringify(r.data ?? {}, null, 1));
2699
2676
  }
2700
2677
 
2678
+ // ---------------- refusal:拒答话术 getRefusalSpeechTechnique(2026-10-13 新增)----------------
2679
+ // 端点:/api/dw/newetl/chatBi/getRefusalSpeechTechnique
2680
+ // 入参:仅 1 个 —— type(场景/类型标识,取值由后端定义,原样透传,不做白名单校验)
2681
+ // 返回:对象,其中 data 为需要使用的**话术字符串**(可能为空串 = 该 type 无话术)
2682
+ // 通道选择:仿照 /api/dw/newetl/chatBi/ds/fieldsForDSL 的调用方式(postMultipart 表单)。
2683
+ // 2026-10-13 实测(type=Extraterritorial,泛微网络租户):**POST multipart 表单字段 type 即命中**,
2684
+ // 返回体形如 { data: "<话术字符串>", actionMsg: { code: 0, message: "成功" } }。
2685
+ // 默认仍为 auto「先 POST 表单 → 判定无效才回退 GET ?type=」,任一条拿到 data 即止
2686
+ // (正常路径只发 1 次请求,回退仅在 POST 拿不到 data 时触发);可用 --http post|get 强制单一通道。
2687
+ const REFUSAL_PATH = "/api/dw/newetl/chatBi/getRefusalSpeechTechnique";
2688
+ const REFUSAL_HTTP_MODES = ["auto", "post", "get"];
2689
+ // 有效返回判定:对象且带 data 字段(data 允许为空串 —— 语义是「该 type 无话术」,不等于失败)
2690
+ function refusalHasData(r) {
2691
+ return !!r && typeof r === "object" && r.data !== undefined && r.data !== null;
2692
+ }
2693
+ async function refusalRequest(mode, type) {
2694
+ if (mode === "get") return getUrl(REFUSAL_PATH + "?type=" + encodeURIComponent(type));
2695
+ return postMultipart(REFUSAL_PATH, { type }); // 与 fieldsForDSL 同口径:multipart 表单字段 type
2696
+ }
2697
+ // 返回 { r, mode }:r = 命中的响应体,mode = 实际生效的通道(post/get),供排障展示
2698
+ async function refusalCall(type, httpMode) {
2699
+ const modes = httpMode === "auto" ? ["post", "get"] : [httpMode];
2700
+ const tried = [];
2701
+ for (const mode of modes) {
2702
+ let r;
2703
+ try {
2704
+ r = await refusalRequest(mode, type);
2705
+ } catch (e) {
2706
+ const msg = (e && e.message) || String(e);
2707
+ if (/登录态失效|401/.test(msg)) throw e; // 凭据问题与「通道选哪条」无关,不换通道重试(避免重复报错)
2708
+ tried.push(mode + ": " + msg);
2709
+ continue;
2710
+ }
2711
+ if (refusalHasData(r)) {
2712
+ if (process.env.E10_DATA_DEBUG) console.error("[debug] getRefusalSpeechTechnique 命中通道=" + mode);
2713
+ return { r, mode };
2714
+ }
2715
+ tried.push(mode + ": code=" + (r && r.code) + " msg=" + ((r && r.msg) || "(无 data 字段)"));
2716
+ }
2717
+ throw new Error("getRefusalSpeechTechnique 调用失败(type=" + type + ")已尝试 " + modes.length + " 种通道 → " + tried.join(" | "));
2718
+ }
2719
+ async function cmdRefusalGet(args, json) {
2720
+ const type = need("type", args);
2721
+ const httpMode = args.http === undefined ? "auto" : String(args.http).toLowerCase();
2722
+ if (!REFUSAL_HTTP_MODES.includes(httpMode)) throw new Error("--http 值必须为 " + REFUSAL_HTTP_MODES.join("|") + "(当前: " + args.http + ")");
2723
+ const { r } = await refusalCall(type, httpMode);
2724
+ if (json) return r; // 原样输出整个响应对象(含 data)
2725
+ const d = r.data;
2726
+ if (typeof d === "string") {
2727
+ if (!d.trim()) { console.log("(空:type=" + type + " 无话术内容)"); return; }
2728
+ console.log(d);
2729
+ return;
2730
+ }
2731
+ console.log(typeof d === "object" ? JSON.stringify(d, null, 1) : String(d));
2732
+ }
2733
+
2701
2734
  // ---------------- main ----------------
2702
2735
  function usage() {
2703
2736
  console.log(`e10-data CLI(data-assistant 数据接口统一命令行,2026-09-03 扩展)
@@ -2730,19 +2763,22 @@ ds 数据源(业务模块)取数与元数据
2730
2763
  ds describe --group <gid> --table <tid> [--app-id <id>] [--skill <skillKey,默认 data-source-query>]
2731
2764
  [--require] [--source-type LOGIC]
2732
2765
  # 一次**并发**取回两件套:字段 + 词库(两者入参同为 (groupId, tableId),互不依赖)
2733
- # ⚠️ **不做权限校验**:EB 分组取数前仍必须单独执行 eb perm-page(appId 只从那次调用取)
2766
+ # 不做权限校验:EB 分组取数不再做权限前置校验,appId 直接取 ebdf_obj.app_id
2734
2767
  # --require:任一路失败即报错退出
2735
2768
  ds tables --group <gid> [--name 关键词] [--app-id <id>]
2736
2769
  ds fields --group <gid> --table <tid> [--app-id <id>]
2737
2770
  ds fields-dsl --group <gid> --table <tid> [--app-id <id>] # 同 fields,接口 /api/dw/newetl/chatBi/ds/fieldsForDSL
2738
- ds list --group <gid> --table <tid> --fields a,b [--where @f|json] [--order "f asc"] [--page N] [--size N] [--app-id <id>]
2739
- ds chart --group <gid> --table <tid> --group-by g1,g2 --stat @f.json [--where ...] [--app-id <id>]
2771
+ ds list --group <gid> --table <tid> --fields a,b|@f [--where @f|json] [--order "f asc"|@f] [--page N] [--size N] [--app-id <id>]
2772
+ ds chart --group <gid> --table <tid> --group-by g1,g2|@f --stat @f.json [--where ...] [--app-id <id>]
2740
2773
  ds stat --group <gid> --table <tid> --stat @f.json [--where ...] [--app-id <id>]
2741
2774
  ds count --group <gid> --table <tid> [--where ...] [--app-id <id>]
2775
+ # 逗号列表(--fields / --group-by / --order)建议一律走 @文件:逗号不出现在命令行上,避免被 shell
2776
+ # (尤其 Windows 交互式 PowerShell 把未加引号的 a,b,c 当数组拆开)拆分 → 拆分后不报错,会静默丢列 / 静默 0 行
2777
+ # 文件内容两种形态均可:逗号字符串 "a,b,c" 或 JSON 数组 ["a","b","c"]
2742
2778
  (ds 通用 --source-type LOGIC|ETEAMS,默认 LOGIC)
2743
2779
  (ds 取数的 appId:一律**显式传入** --app-id <id>(v0.1.14 起 CLI 不再自动反查)。
2744
- EB 建模表单(groupId=weaver-ebuilder-form-service)必须带:该值取自 chat_bi_eb_config_map 的权限校验查询
2745
- ——调用方先查权限(无权限即终止、不取数),同一条记录的 app_id 即本次必带的 appId;
2780
+ EB 建模表单(groupId=weaver-ebuilder-form-service)必须带:该值取自 ebdf_obj.app_id
2781
+ (obj_id = ebdf_obj.id = 数据源 tableId;2026-10-09 起不再做 EB 权限前置校验);
2746
2782
  其他数据集可不传,CLI 不拦截、以接口报错为准;--app-id auto / --no-app-id 已废弃,等同不传)
2747
2783
  --where 过滤条件(v0.1.8 起支持树形 OR,递归):
2748
2784
  数组 = AND 根: [{"fieldId":"f1","conditionType":"Date","compareType":"range","conditionValue":"2026-09~2026-10"},{"fieldId":"f2",...}]
@@ -2785,15 +2821,10 @@ exp 专家包版本管理(expert-repository/v1,响应以 data.actionMsg.
2785
2821
  exp rollback --expert <code> --version vX.Y.Z
2786
2822
  attend 考勤 REST 接口(2026-09-07 新增,hr-attendance 病假/事假等假期余额)
2787
2823
  attend statis [--user <人员ID>] [--begin 2026-09-01] [--end 2026-09-30] # getAttendInfoStatis 假期余额统计;--user 默认当前登录用户,--begin/--end 默认当月
2788
- eb EB 建模表单权限校验(chatBi EB 配置映射分页查询,替代 DSL 查 chat_bi_eb_config_map)
2789
- eb perm-page --obj-id <id> --form-table <物理表名> [--page N] [--size N] [--app-id <id>] [--require]
2790
- # obj-id / form-table 必填(本地强校验;缺失后端只回空、不报错)
2791
- # obj-id / app-id 必须字符串传参(19 位大整数,Number 舍入 → 静默 total=0)
2792
- # total>=1 = 有权限,并回带该行 appId;--require 时 total=0 直接报错退出
2793
2824
  module 模块配置接口调用(配置中心驱动;**已确定「哪个模块的哪条具体接口」之后**用它取数,2026-09-28 新增)
2794
2825
  module call --conf-key <confKey> --group <模块> [--args '<JSON>'|--args @params.json]
2795
2826
  [--query "k=v&k2=v2"] [--method GET|POST|FORM] [--header 'K: V'|--headers '<JSON>'|@file]
2796
- [--limit 50] [--fields a,b] [--raw] [--show-params] [--dry-run] [--timeout 60] [--json]
2827
+ [--limit 50] [--fields a,b|@f] [--raw] [--show-params] [--dry-run] [--timeout 60] [--json]
2797
2828
  # 一条命令完成:精取该条 confAttrs → 按 http_method/input_params 组参 → 发请求
2798
2829
  # (自动禁代理 + Cookie/eteamsid/UA 三件套)→ 按外层结构(datajson/data/records/裸数组)提取精简结果
2799
2830
  # 与技能自带 python 调用器 call_module.py 等价(参数名/退出码/提取口径全对齐),但复用本 CLI 凭据缓存与
@@ -2804,6 +2835,12 @@ module 模块配置接口调用(配置中心驱动;**已确定「哪个模
2804
2835
  # 退出码:0 正常(**空结果也算正常**)|2 未登录·凭据失效|3 非 JSON·confAttrs 异常
2805
2836
  # |4 权限受限(hasSearchRight=false,终端态)|6 命中 HRM 路径黑名单|7 请求失败|8 用法错误
2806
2837
  # 示例: module call --conf-key POST_EHR_GET_ATTEND_INFO_STATIS --group EHR --args @params.json --json
2838
+ refusal 拒答话术(chatBi,2026-10-13 新增)
2839
+ refusal get --type <type> [--http auto|post|get] [--json] # 接口 /api/dw/newetl/chatBi/getRefusalSpeechTechnique
2840
+ # 入参仅 type(场景/类型标识,取值由后端定义,原样透传);返回对象的 data = 话术字符串
2841
+ # --http:auto(默认)= 先 POST multipart 表单(同 ds fields-dsl 口径),
2842
+ # 返回不含 data 时自动回退 GET ?type=;post = 仅 POST 表单;get = 仅 GET query
2843
+ # 人读输出直接打印 data 话术正文(空串 → 提示该 type 无话术);--json 原样输出整个响应对象
2807
2844
  认证: 加解密由 e10-login 负责(本 CLI 不参与);凭据出口 = e10-login auth/whoami(自动同步镜像);皆无请先 e10-login oidc
2808
2845
  (无浏览器/无 python 环境:login xiaoe-env 用环境变量 token 直接登录)`);
2809
2846
  }
@@ -2865,15 +2902,12 @@ async function main() {
2865
2902
  case "rollback": result = await expRollback(args, json); break;
2866
2903
  default: usage(); process.exit(1);
2867
2904
  }
2905
+ } else if (family === "refusal") {
2906
+ if (sub === "get") result = await cmdRefusalGet(args, json);
2907
+ else { usage(); process.exit(1); }
2868
2908
  } else if (family === "attend") {
2869
2909
  if (sub === "statis") result = await cmdAttendStatis(args, json);
2870
2910
  else { usage(); process.exit(1); }
2871
- } else if (family === "eb") {
2872
- if (!sub) { usage(); process.exit(1); }
2873
- switch (sub) {
2874
- case "perm-page": result = await cmdEbPermPage(args, json); break;
2875
- default: usage(); process.exit(1);
2876
- }
2877
2911
  } else if (family === "module") {
2878
2912
  if (!sub) { usage(); process.exit(1); }
2879
2913
  switch (sub) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "e10-data",
3
- "version": "0.1.21",
4
- "description": "泛微 E10 数据接口统一 CLI(data-assistant 专家配套):登录(login xiaoe-env,环境变量 XiaoE 全局 token 换 ETEAMSID,免浏览器/免 python)/DSL 查询/数据源/文档/词库/关联字段翻译/环境参数配置中心(conf,支持 confGroup 分组)/EB 建模表单权限校验(eb perm-page)/专家包版本管理;ds 取数(list/chart/stat/count)的 appId 由调用方显式传入 --app-id(v0.1.14 起 CLI 不再自动反查;EB 建模表单 weaver-ebuilder-form-service 必须带,appId 取自 eb perm-page 权限校验返回行);认证凭据**唯一来源 = 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= 推导)。",
3
+ "version": "0.1.23",
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 锁定单通道)。",
5
5
  "author": "e10-dw",
6
6
  "license": "MIT",
7
7
  "type": "module",