e10-data 0.1.15 → 0.1.16-xiaoe

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/README.md CHANGED
@@ -10,10 +10,13 @@ e10-data --version # 验证
10
10
  ```
11
11
 
12
12
  > 依赖:认证复用 `weaver-e10-login` 的登录态。**加解密全部由 `e10-login` 负责,本 CLI 不实现任何加解密**(auth 文件由 e10-login 自己的密钥体系加密:1.5.0 起为「每机随机密钥 + OS keychain」,早期版本为固定 seed,只有 e10-login 解得开)。CLI 通过 e10-login 的命令出口取明文凭据,优先级:**① `e10-login auth --json`**(推荐的正式契约)→ **② `e10-login whoami --json`**(其 JSON 带 `cookies` 时)→ **③ `e10-login whoami`**(文本解析,当前实际路径)→ **④ 明文镜像 `~/.workbuddy/e10-login-cache.md`**(兜底,命中时打印告警)。请先安装并登录 weaver-e10-login(`e10-login oidc`)。
13
+ >
14
+ > 唯一例外:**`e10-data login xiaoe-env`** 在「还没有任何凭据」时也能独立完成登录(用环境变量里的 XiaoE 全局 token 换 ETEAMSID;换 token 本 CLI 自己算,写 profile 仍交 `e10-login set`)。
13
15
 
14
16
  ## 命令总览
15
17
 
16
18
  ```
19
+ e10-data login xiaoe-env [--domain <url>] 登录:环境变量 XiaoE token 换 ETEAMSID(免浏览器/免 python)
17
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>
18
21
  e10-data ds tables|fields|list|chart|stat|count 数据源(业务模块)取数与元数据
19
22
  e10-data doc count|list 文档查询(documentListCount + documentList)
@@ -29,7 +32,7 @@ e10-data attend statis 考勤假期余额统计 getAttendInfoStatis(hr-atte
29
32
  - `--json`:原样输出接口 JSON(供脚本/Agent 消费);默认人读输出。
30
33
  - 复杂 JSON/中文参数:推荐 `@文件` 承载(如 `--dsl @q.json`、`--where @cond.json`)。
31
34
  - ⚠️ 大整数精度(自动处理):E10 的 ID 多为 19 位,超过 JS 安全整数(2^53-1≈9e15)。CLI 解析 JSON 时**自动把超范围裸整数转成字符串**再发请求/输出(实测后端接受字符串做 =/IN/BETWEEN 数值比较),无需手动加引号,也不误伤字符串正文。
32
- - 认证:**加解密由 `e10-login` 负责(本 CLI 不参与)**。凭据出口优先级:`e10-login auth --json` → `e10-login whoami --json` → `e10-login whoami`(文本)→ 明文镜像 `~/.workbuddy/e10-login-cache.md`(兜底,命中时告警)。命中的出口会记入 `~/.e10-cli/.e10-data-auth-mode`(只存模式名、不存凭据),下次优先复用。环境变量:`E10_DATA_AUTH_PROBE=1` 强制重新探测、`E10_DATA_AUTH_MODE=<模式名>` 手动指定、`E10_DATA_QUIET=1` 抑制告警(告警只走 stderr)。
35
+ - 认证:**加解密由 `e10-login` 负责(本 CLI 不参与)**。凭据出口优先级:`e10-login auth --json` → `e10-login whoami --json` → `e10-login whoami`(文本)→ 明文镜像 `~/.workbuddy/e10-login-cache.md`(兜底,命中时告警)。命中的出口会记入 `~/.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` 登录(见下节)。
33
36
  - 版本更新:`npm i -g e10-data@latest`;查看源上最新 `npm view e10-data version`。
34
37
 
35
38
  ## 用法示例
@@ -58,8 +61,33 @@ e10-data exp status --expert data-assistant
58
61
  e10-data exp pull --expert data-assistant --dir <本地专家包目录> [--dry-run] [--force]
59
62
  e10-data attend statis # 查当前登录用户当月假期余额(病假/事假/年假)
60
63
  e10-data attend statis --user 6264843388578732287 --begin 2026-09-01 --end 2026-09-30
64
+ # 无凭据时先登录(环境变量里的 XiaoE 全局 token 换 ETEAMSID)
65
+ XIAOE_USER_TOKEN=<XiaoE token> XIAOE_DOMAIN=<erpa域名> e10-data login xiaoe-env --domain https://www.e-cology.com.cn
61
66
  ```
62
67
 
68
+ ## login xiaoe-env 登录(v0.1.16 新增,移植自 e10-login 1.6.4-xiaoe)
69
+
70
+ `e10-data login xiaoe-env [--domain <url>] [--json]`:用环境变量 **`XIAOE_USER_TOKEN`**(XiaoE 全局 token)+ **`XIAOE_DOMAIN`**(erpa 域名)换出 ETEAMSID 并写入 profile,**免浏览器、免手动复制 cookie**。别名:`e10-data xiaoe-env`(与 `e10-login` 的同名命令一致)。
71
+
72
+ 这是唯一**不需要已有凭据**的命令族(其余命令都要求先有登录态),因此它在 `loadAuth()` 之前被拦截执行。
73
+
74
+ | 步骤 | 做什么 | 谁做 |
75
+ |---|---|---|
76
+ | ① 换 token | `POST {$XIAOE_DOMAIN}/spec-apps/api/app/rpachat/skill/extend/eteamId`,请求头 `authorization: <XIAOE_USER_TOKEN>`,body `{"domain":"<--domain 或当前 profile 的 baseUrl>"}`,取回 `data.eteamId`(code 取 `0000`/`200` 为成功) | **本 CLI 原生实现**(无 python 依赖) |
77
+ | ② 落 profile | `e10-login set --eteamsid <id> --base-url <url>`:teamsCheck 解析用户信息 + 写加密 auth/config + 切换 active profile | `e10-login`(auth 文件加密只有它解得开) |
78
+ | ③ 自检 | 立刻用取数链路把凭据读回来(`loadAuth`),读不回就报错退出(**不假成功**) | 本 CLI |
79
+
80
+ ```bash
81
+ # Windows cmd
82
+ set XIAOE_USER_TOKEN=<token>&& set XIAOE_DOMAIN=<erpa域名>&& e10-data login xiaoe-env --domain https://www.e-cology.com.cn
83
+ # bash / git-bash
84
+ XIAOE_USER_TOKEN=<token> XIAOE_DOMAIN=<erpa域名> e10-data login xiaoe-env --domain https://www.e-cology.com.cn
85
+ ```
86
+
87
+ - `--domain`:目标 e-cology 域名;**缺省 = 当前 profile 的 `config.json.baseUrl`**(明文,读取不涉及解密)→ 再缺省为默认平台地址 `https://www.e-cology.com.cn`;
88
+ - `--json`:stdout 只输出结果 JSON(`baseUrl`/`profile`/`credentialSource`/`writtenBy`/`readback`),其余信息走 stderr,便于脚本消费;**ETEAMSID 一律不回显**;
89
+ - 与原版差异:e10-login 的实现是 `spawn python scripts/xiaoe_env.py`(需本机 python 3),本 CLI 直接原生发请求,**不需要 python**;请求形状与 code 判定逐字段对齐。
90
+
63
91
  ## conf 配置中心与 confGroup 分组(v0.1.9 新增)
64
92
 
65
93
  `conf` 系列对接 chatBi config map 接口,`confGroup` 是**可选**的二级分组维度:后端在 `confType=api` 时按 `confType + confGroup + confKey` 定位配置(如 `api/workflow/GET_WORKFLOW_LIST`),用于把同类 API 定义归档到同一分组。
package/bin/e10-data.mjs CHANGED
@@ -4,6 +4,8 @@
4
4
  * e10-data CLI —— data-assistant 专家统一数据接口命令行(2026-09-03 扩展)
5
5
  *
6
6
  * 覆盖(数据查询链路全部家族;登录/HRM 复用 e10-login / hrm-cli,checkEbModulePermission 已废弃不提供):
7
+ * e10-data login xiaoe-env [--domain <url>] **无凭据时**用环境变量 XIAOE_USER_TOKEN + XIAOE_DOMAIN
8
+ * (XiaoE 全局 token)换 ETEAMSID 并落 profile(免浏览器/免 python;2026-09-17 移植)
7
9
  * e10-data query DSL 查询 executeQueryDsl(conn/model/schema/tableId/dslQuery/debug)
8
10
  * e10-data ds tables|fields|list|chart|stat|count 数据源(业务模块)取数与元数据
9
11
  * appId 一律由调用方显式传入(v0.1.14 起 CLI 不再自动反查):
@@ -22,6 +24,7 @@
22
24
  * 认证:加解密全部由 e10-login 负责(本 CLI 不实现任何加解密)。凭据出口优先级:
23
25
  * ① e10-login auth --json → ② e10-login whoami --json → ③ e10-login whoami(文本解析)
24
26
  * → ④ 明文镜像 ~/.workbuddy/e10-login-cache.md(兜底,命中时告警);均不可用时提示先 e10-login oidc 登录
27
+ * 例外:`login xiaoe-env` 在"还没有任何凭据"时可独立完成登录(换 token 自己算,落 profile 仍交 e10-login)
25
28
  *
26
29
  * 用法示例:
27
30
  * node e10-data.mjs query --conn 1144919996804022272 --model dw --schema dw --dsl @q.json
@@ -244,6 +247,135 @@ function loadAuth() {
244
247
  (diag.length ? " 探测诊断:" + diag.join(" | ") + "\n" : "")
245
248
  );
246
249
  }
250
+ // ---------------- login(唯一"不需要已有凭据"的命令族:xiaoe-env,2026-09-17 移植)----------------
251
+ // 来源:e10-login 1.6.4-xiaoe 的 `e10-login xiaoe-env [--domain <url>]`
252
+ // (lib/xiaoe.js 命令定义 + lib/login-flow.js 收尾 + scripts/xiaoe_env.py 换 token)。
253
+ // 原实现是「Node 里 spawn python scripts/xiaoe_env.py 换 ETEAMSID」,而本 CLI 是零依赖 Node,
254
+ // **不需要 python**:换 token 本身就是一次 POST,此处原生复刻(逐字段对齐:URL 拼接、请求头
255
+ // authorization、body {domain}、code 判定 '0000'/200、取 data.eteamId、错误信息取 msg)。
256
+ // 分工(与本 CLI 既有的认证边界一致,不越界):
257
+ // ① 换 token → 本 CLI 原生实现(无 python 依赖);
258
+ // ② 落 profile → 仍交 e10-login `set --eteamsid --base-url`(auth 文件由它的密钥体系加密,
259
+ // 只有它解得开;`set` 与原命令的收尾 finishLoginByEteamsId 是同一条路径:
260
+ // teamsCheck 解析用户信息 + 写 auth/config + 切换 active profile)。
261
+ // ⚠️ 本段必须在 loadAuth() 之前执行 —— 该命令存在的意义就是"还没有凭据时把凭据造出来",
262
+ // 因此它在 main() 之前拦截(见下方拦截块),不走常规 main() 分发。
263
+ const LOGIN_DEFAULT_BASE_URL = "https://www.e-cology.com.cn";
264
+ const XIAOE_ENV_ETEAMID_PATH = "/spec-apps/api/app/rpachat/skill/extend/eteamId";
265
+ const ETEAMSID_SAFE_RE = /^[A-Za-z0-9_.\-]+$/; // 透传给 e10-login 的参数白名单(该调用走 shell,防注入)
266
+
267
+ /** --domain → 当前 profile 的 config.json.baseUrl → 默认平台地址(对齐 e10-login 的 resolveBaseUrl;
268
+ * config.json 是明文,读它不涉及任何解密) */
269
+ function resolveLoginBaseUrl(optDomain) {
270
+ if (optDomain !== undefined && typeof optDomain !== "string") throw new Error("--domain 需要跟一个平台地址,如 --domain https://www.e-cology.com.cn");
271
+ let cfgBase = "";
272
+ try {
273
+ const cfgFile = path.join(path.dirname(authFilePath().file), "config.json");
274
+ if (fs.existsSync(cfgFile)) cfgBase = String(JSON.parse(fs.readFileSync(cfgFile, "utf-8")).baseUrl || "").trim();
275
+ } catch {}
276
+ return String(optDomain || cfgBase || LOGIN_DEFAULT_BASE_URL).trim().replace(/\/+$/, "");
277
+ }
278
+
279
+ /** 复刻 scripts/xiaoe_env.py:POST {XIAOE_DOMAIN}/spec-apps/api/app/rpachat/skill/extend/eteamId
280
+ * 请求头 authorization=<XIAOE_USER_TOKEN>;body {"domain": <目标平台域名>} → 取 data.eteamId */
281
+ async function xiaoeEnvFetchEteamsId(domain) {
282
+ const token = String(process.env.XIAOE_USER_TOKEN || "").trim();
283
+ const erpa = String(process.env.XIAOE_DOMAIN || "").trim();
284
+ if (!token || !erpa) {
285
+ throw new Error("缺少环境变量 XIAOE_USER_TOKEN / XIAOE_DOMAIN(请先设置后重试,如 export XIAOE_USER_TOKEN=<token> XIAOE_DOMAIN=<erpa域名>)");
286
+ }
287
+ const url = erpa.replace(/\/+$/, "") + XIAOE_ENV_ETEAMID_PATH;
288
+ let resp, text;
289
+ try {
290
+ resp = await fetch(url, {
291
+ method: "POST",
292
+ headers: { authorization: token, "content-type": "application/json", accept: "application/json" },
293
+ body: JSON.stringify({ domain }),
294
+ });
295
+ text = await resp.text();
296
+ } catch (e) {
297
+ throw new Error("请求 XiaoE 换 token 接口失败(" + url + "):" + ((e && e.message) || e));
298
+ }
299
+ let j = null;
300
+ try { j = JSON.parse(text); } catch {}
301
+ if (!j) throw new Error("XiaoE 接口返回非 JSON(HTTP " + resp.status + "):" + text.slice(0, 200));
302
+ const code = j.code;
303
+ const eteamId = String((j.data && j.data.eteamId) || "").trim();
304
+ if (!(code === "0000" || code === 200 || code === "200") || !eteamId) {
305
+ throw new Error("XiaoE 换取失败:" + (j.msg || ("HTTP " + resp.status + " code=" + code)));
306
+ }
307
+ return eteamId;
308
+ }
309
+
310
+ /** e10-data login xiaoe-env:环境变量全局 token → ETEAMSID → 写 profile(走 e10-login set 收尾) */
311
+ async function cmdLoginXiaoeEnv(args, json) {
312
+ const say = json ? console.error : console.log; // --json 时人读信息一律走 stderr,保持 stdout 只有 JSON
313
+ const baseUrl = resolveLoginBaseUrl(args.domain);
314
+ say("ℹ️ 目标平台: " + baseUrl + (args.domain ? "(--domain)" : baseUrl === LOGIN_DEFAULT_BASE_URL ? "(默认平台地址)" : "(当前 profile 的 config.json)"));
315
+
316
+ const eteamsId = await xiaoeEnvFetchEteamsId(baseUrl);
317
+ say("✅ 已取得 ETEAMSID(来源: env,不回显)");
318
+
319
+ if (!ETEAMSID_SAFE_RE.test(eteamsId)) throw new Error("换回的 ETEAMSID 含异常字符,已拒绝透传 e10-login(安全防护)");
320
+ const shq = (s) => '"' + String(s).replace(/"/g, "") + '"';
321
+ const r = spawnSync("e10-login " + ["set", "--eteamsid", shq(eteamsId), "--base-url", shq(baseUrl)].join(" "), {
322
+ shell: true, encoding: "utf-8", timeout: 60000, windowsHide: true,
323
+ });
324
+ const out = (r.stdout || "").trim(), err = (r.stderr || "").trim();
325
+ if (r.error || r.status !== 0) {
326
+ throw new Error("换 token 成功,但 e10-login 写 profile 失败(auth 文件的加密写入只有 e10-login 能做):"
327
+ + (err || out || (r.error && r.error.message) || ("exit=" + r.status)));
328
+ }
329
+ for (const line of (out + (err ? "\n" + err : "")).split(/\r?\n/)) if (line.trim()) say(" [e10-login] " + line.trim());
330
+
331
+ const profile = authFilePath().profile;
332
+ // 自检:立刻用取数链路(loadAuth)把凭据读回来,确认"真能读到"而不是"只写了文件"
333
+ // (历史坑:e10-login 旧版 whoami 不带 cookies → 登录成功但 e10-data 取不到凭据;此处必须报出来)
334
+ // 强制重新探测(E10_DATA_AUTH_PROBE),并拒绝把"明文镜像兜底"当成自检通过 —— 镜像可能是旧环境的
335
+ let readBack = "";
336
+ try {
337
+ process.env.E10_DATA_AUTH_PROBE = "1";
338
+ const a = loadAuth();
339
+ const viaMirror = /镜像/.test(String(a.source || ""));
340
+ readBack = (viaMirror ? "⚠️ 仅命中明文镜像:" : "ok(") + (a.source || "?") + ";baseUrl: " + a.baseUrl + (a.userId ? ";userId: " + a.userId : "") + (viaMirror ? "" : ")");
341
+ if (viaMirror) throw new Error("凭据来自明文镜像而非新写入的 profile(镜像可能是旧环境/已过期)");
342
+ say("✅ 凭据自检通过:" + readBack);
343
+ } catch (e) {
344
+ if (!readBack) readBack = "失败: " + String((e && e.message) || e).split(/\r?\n/)[0];
345
+ throw new Error("profile 已写入 ~/.e10-cli/profiles/" + profile + "/auth,但本 CLI 读不回凭据(" + readBack
346
+ + ")—— 取数仍会失败。请确认 `e10-login whoami` 能输出 cookies(缺则升级 e10-login),或改用 `e10-login oidc`。");
347
+ }
348
+ return {
349
+ ok: true,
350
+ baseUrl,
351
+ profile,
352
+ credentialSource: "env(XIAOE_USER_TOKEN + XIAOE_DOMAIN)",
353
+ writtenBy: "e10-login set",
354
+ eteamsId: "(已写入,不回显)",
355
+ readback: readBack,
356
+ };
357
+ }
358
+
359
+ // ---- 拦截(必须早于 loadAuth()):login 族 = 唯一不需要已有凭据的命令族 ----
360
+ {
361
+ const a2 = process.argv[2] || "";
362
+ const bare = a2 === "xiaoe-env"; // 别名:与 e10-login 同名命令保持一致的肌肉记忆
363
+ if (a2 === "login" || bare) {
364
+ const sub = bare ? a2 : process.argv[3] || "";
365
+ const { args } = parseArgs(process.argv.slice(bare ? 3 : 4));
366
+ const json = !!args.json;
367
+ try {
368
+ if (sub !== "xiaoe-env") throw new Error("未知子命令 `login " + (sub || "(空)") + "`(当前仅移植 xiaoe-env)");
369
+ const r = await cmdLoginXiaoeEnv(args, json);
370
+ if (json) console.log(JSON.stringify(r, null, 1));
371
+ process.exit(0);
372
+ } catch (e) {
373
+ console.error("ERROR: " + ((e && e.message) || e));
374
+ process.exit(1);
375
+ }
376
+ }
377
+ }
378
+
247
379
  const auth = loadAuth();
248
380
  const BASE = auth.baseUrl.replace(/\/+$/, "");
249
381
  const USER = { id: auth.userId || "", tenantKey: auth.tenantKey || "" };
@@ -1413,6 +1545,15 @@ function usage() {
1413
1545
  console.log(`e10-data CLI(data-assistant 数据接口统一命令行,2026-09-03 扩展)
1414
1546
  用法: node e10-data.mjs <族> <子命令> [--参数] 通用 --json 原样输出
1415
1547
 
1548
+ login 登录(**唯一不需要已有凭据**的命令族;其余命令都要求先有登录态)
1549
+ login xiaoe-env [--domain <url>] # 用环境变量 XIAOE_USER_TOKEN + XIAOE_DOMAIN(XiaoE 全局 token)
1550
+ # 换 ETEAMSID 并写入 profile(免浏览器;不依赖 python)
1551
+ # --domain 缺省 = 当前 profile 的 config.json.baseUrl → 默认平台地址
1552
+ # 依赖环境变量:XIAOE_USER_TOKEN(XiaoE token)、XIAOE_DOMAIN(erpa 域名)
1553
+ # 别名 e10-data xiaoe-env(与 e10-login 同名命令一致)
1554
+ # 说明:换 token 由本 CLI 原生完成;写 profile(auth 加密)仍交 e10-login set
1555
+ # 成功后自动用取数链路自检读回凭据,读不回即报错(不假成功)
1556
+
1416
1557
  query DSL 数据查询 executeQueryDsl
1417
1558
  query --conn <connId> --model <ebuilder|flow|eteams|attend|hr|document|dw|blog> --schema <数据库> [--table-id <流程ID>] [--debug true|false] --dsl @file.json|'{...}'
1418
1559
  ds 数据源(业务模块)取数与元数据
@@ -1473,7 +1614,8 @@ eb EB 建模表单权限校验(chatBi EB 配置映射分页查询,替
1473
1614
  # obj-id / form-table 必填(本地强校验;缺失后端只回空、不报错)
1474
1615
  # obj-id / app-id 必须字符串传参(19 位大整数,Number 舍入 → 静默 total=0)
1475
1616
  # total>=1 = 有权限,并回带该行 appId;--require 时 total=0 直接报错退出
1476
- 认证: 优先读权威源 ~/.e10-cli/profiles/<active>/auth(自动同步镜像 ~/.workbuddy/e10-login-cache.md);皆无请先 e10-login oidc`);
1617
+ 认证: 加解密由 e10-login 负责(本 CLI 不参与);凭据出口 = e10-login auth/whoami(自动同步镜像);皆无请先 e10-login oidc
1618
+ (无浏览器/无 python 环境:login xiaoe-env 用环境变量 token 直接登录)`);
1477
1619
  }
1478
1620
 
1479
1621
  async function main() {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "e10-data",
3
- "version": "0.1.15",
4
- "description": "泛微 E10 数据接口统一 CLI(data-assistant 专家配套):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-e10-login(本 CLI 不再自行解密,通过 e10-login 命令出口获取凭据)",
3
+ "version": "0.1.16-xiaoe",
4
+ "description": "泛微 E10 数据接口统一 CLI(data-assistant 专家配套):登录(login xiaoe-env,环境变量 XiaoE 全局 token 换 ETEAMSID,v0.1.16 新增,免浏览器/免 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-e10-login(本 CLI 不再自行解密,通过 e10-login 命令出口获取凭据;仅 login xiaoe-env 在无凭据时自行换 token,写 profile 仍交 e10-login set)",
5
5
  "author": "e10-dw",
6
6
  "license": "MIT",
7
7
  "type": "module",