@yejiming/dsh-data-agent 0.0.11 → 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.en.md +70 -15
  2. package/README.md +70 -15
  3. package/conformance/dsh-ecosystem/baseline.json +59 -0
  4. package/conformance/dsh-ecosystem/dependencies.json +41 -0
  5. package/conformance/dsh-ecosystem/fixtures/host-degraded.fixture.json +12 -0
  6. package/conformance/dsh-ecosystem/fixtures/host-eligible.fixture.json +13 -0
  7. package/conformance/dsh-ecosystem/fixtures/host-rejected.fixture.json +12 -0
  8. package/conformance/dsh-ecosystem/fixtures/profiles/native-only/package.json +8 -0
  9. package/conformance/dsh-ecosystem/fixtures/profiles/native-plus-adapter/package.json +9 -0
  10. package/conformance/dsh-ecosystem/inventory.json +77 -0
  11. package/conformance/dsh-ecosystem/restrictions.json +20 -0
  12. package/dsh-plugin.json +67 -0
  13. package/lib/client.js +1352 -130
  14. package/lib/client.js.map +1 -1
  15. package/lib/{command-DuCpwVbl.js → command-utC5MHd9.js} +101 -60
  16. package/lib/command.js +1 -1
  17. package/lib/{connections-5sfdEDsG.js → connections-CHY4uB6z.js} +747 -65
  18. package/lib/defaults-Cngd8Tf8.js +131 -0
  19. package/lib/ecosystem.js +19 -0
  20. package/lib/index.js +40 -33
  21. package/lib/routes.js +5 -6
  22. package/lib/{tool-DVh61An-.js → tool-ZTOS4B33.js} +161 -177
  23. package/lib/tool.js +1 -1
  24. package/lib/types/analysis-html.d.ts +27 -0
  25. package/lib/types/analysis.d.ts +13 -1
  26. package/lib/types/client/DataAgentWorkbench.d.ts +1 -2
  27. package/lib/types/client/QueryResultTable.d.ts +13 -0
  28. package/lib/types/client/locales.d.ts +48 -0
  29. package/lib/types/client/persistence.d.ts +3 -1
  30. package/lib/types/client/query-export.d.ts +11 -0
  31. package/lib/types/client-discovery.d.ts +3 -4
  32. package/lib/types/clients.d.ts +14 -8
  33. package/lib/types/command.d.ts +2 -2
  34. package/lib/types/connections.d.ts +33 -4
  35. package/lib/types/database-types.d.ts +23 -0
  36. package/lib/types/defaults.d.ts +4 -0
  37. package/lib/types/ecosystem.d.ts +13 -0
  38. package/lib/types/index.d.ts +21 -16
  39. package/lib/types/presentation-text.d.ts +3 -0
  40. package/lib/types/query.d.ts +13 -11
  41. package/lib/types/sql.d.ts +9 -1
  42. package/lib/types/storage.d.ts +11 -1
  43. package/lib/types/structured-read.d.ts +2 -2
  44. package/lib/types/structured.d.ts +1 -1
  45. package/lib/types/tool.d.ts +5 -5
  46. package/lib/types/tui-connection-form.d.ts +10 -7
  47. package/package.json +66 -42
  48. package/preset/data-agent/agent.cordis.yml +9 -4
  49. package/lib/defaults-DP4RyRh1.js +0 -21
@@ -1,14 +1,18 @@
1
- import { r as redactSecretText } from "./connections-5sfdEDsG.js";
1
+ import { d as defaultDatabasePort$1, l as DATABASE_TYPES, p as isDatabaseType, u as databaseTypeLabel$1 } from "./defaults-Cngd8Tf8.js";
2
+ import { i as validatePasswordRef, r as redactSecretText } from "./connections-CHY4uB6z.js";
2
3
  import { RUN_CODE_NAME } from "@deepseek-ai/dsh-tools";
3
4
  //#region src/tui-connection-form.ts
4
- const TUI_DATABASE_TYPES = [
5
- "mysql",
6
- "postgres",
7
- "sqlite",
8
- "oracle",
9
- "hive",
10
- "impala"
11
- ];
5
+ /**
6
+ * Short-lived ANSI connection form used by `/database connect` in dsh-tui.
7
+ *
8
+ * dsh-tui 0.6.x exposes commands but no public custom-form/sensitive-input
9
+ * slot. This adapter therefore owns a small terminal form and only activates
10
+ * for an interactive `dsh-tui` profile. It snapshots the host's `readable`
11
+ * listeners, consumes input for the lifetime of the form, then restores the
12
+ * listeners exactly. It never imports dsh-tui, React, or Ink.
13
+ * @module @yejiming/dsh-data-agent/tui-connection-form
14
+ */
15
+ const TUI_DATABASE_TYPES = DATABASE_TYPES;
12
16
  /** Initial form intentionally leaves host/port empty so placeholders are real defaults. */
13
17
  function createTuiConnectionFormState(initialDraft) {
14
18
  return {
@@ -18,6 +22,8 @@ function createTuiConnectionFormState(initialDraft) {
18
22
  user: initialDraft?.user ?? "",
19
23
  database: initialDraft?.database ?? "",
20
24
  password: "",
25
+ passwordRef: initialDraft?.passwordRef ?? "",
26
+ secure: initialDraft?.secure ?? false,
21
27
  readonly: initialDraft?.readonly ?? false,
22
28
  focus: "type",
23
29
  cursor: 0
@@ -31,38 +37,39 @@ function connectionFormDraft(state) {
31
37
  port: state.port,
32
38
  user: state.user,
33
39
  database: state.database,
34
- readonly: state.readonly
40
+ readonly: state.readonly,
41
+ ...state.type === "clickhouse" ? { secure: state.secure } : {}
35
42
  };
36
43
  }
37
44
  /** Relevant focus order for the selected database kind. */
38
45
  function tuiConnectionFields(type) {
39
- return type === "sqlite" ? [
46
+ if (type === "sqlite") return [
40
47
  "type",
41
48
  "database",
42
49
  "readonly",
43
50
  "confirm",
44
51
  "cancel"
45
- ] : [
52
+ ];
53
+ const fields = [
46
54
  "type",
47
55
  "host",
48
56
  "port",
49
57
  "user",
50
58
  "database",
51
59
  "password",
60
+ "passwordRef"
61
+ ];
62
+ if (type === "clickhouse") fields.push("secure");
63
+ return [
64
+ ...fields,
52
65
  "readonly",
53
66
  "confirm",
54
67
  "cancel"
55
68
  ];
56
69
  }
57
70
  /** Default network port shown as a placeholder and applied only at submit time. */
58
- function defaultDatabasePort(type) {
59
- switch (type) {
60
- case "mysql": return 3306;
61
- case "postgres": return 5432;
62
- case "oracle": return 1521;
63
- case "hive": return 1e4;
64
- case "impala": return 21050;
65
- }
71
+ function defaultDatabasePort(type, secure = false) {
72
+ return defaultDatabasePort$1(type, secure);
66
73
  }
67
74
  /** Detect the supported host without coupling to dsh-tui modules. */
68
75
  function isDshTuiTerminal(argv = process.argv, input = process.stdin, output = process.stdout) {
@@ -130,12 +137,12 @@ function updateTuiConnectionForm(current, key) {
130
137
  state
131
138
  };
132
139
  }
133
- if (state.focus === "readonly") {
140
+ if (state.focus === "readonly" || state.focus === "secure") {
134
141
  if (key.name === "enter" || key.name === "space") state = {
135
142
  ...state,
136
143
  selector: {
137
- field: "readonly",
138
- index: state.readonly ? 1 : 0
144
+ field: state.focus,
145
+ index: state[state.focus] ? 1 : 0
139
146
  }
140
147
  };
141
148
  return {
@@ -163,7 +170,7 @@ function renderTuiConnectionForm(state, columns = 80) {
163
170
  ];
164
171
  for (const field of tuiConnectionFields(state.type)) lines.push(...renderField(state, field, width));
165
172
  if (state.error !== void 0) lines.push("", red(`! ${state.error}`));
166
- lines.push("", dim("密码仅在当前进程内使用,不写入命令、会话或持久化配置。"));
173
+ lines.push("", dim(state.type === "sqlite" ? "SQLite 连接不收集数据库凭据。" : "临时密码不持久化;凭据引用可随非敏感连接 profile 恢复。"));
167
174
  return lines.join("\n");
168
175
  }
169
176
  /**
@@ -322,7 +329,7 @@ function validateTuiConnectionForm(state) {
322
329
  readonly: state.readonly
323
330
  } };
324
331
  const portText = state.port.trim();
325
- const port = portText === "" ? defaultDatabasePort(state.type) : Number(portText);
332
+ const port = portText === "" ? defaultDatabasePort(state.type, state.secure) : Number(portText);
326
333
  if (!Number.isInteger(port) || port < 1 || port > 65535) return { error: "端口必须是 1–65535 的整数,或留空使用默认值" };
327
334
  const input = {
328
335
  type: state.type,
@@ -331,9 +338,19 @@ function validateTuiConnectionForm(state) {
331
338
  database,
332
339
  readonly: state.readonly
333
340
  };
341
+ if (state.type === "clickhouse") input.secure = state.secure;
334
342
  const user = state.user.trim();
335
343
  if (user !== "") input.user = user;
336
- if (state.password !== "") input.password = state.password;
344
+ const passwordRef = state.passwordRef.trim();
345
+ if (state.password !== "" && passwordRef !== "") return { error: "临时密码与凭据引用不能同时填写" };
346
+ if (passwordRef !== "") {
347
+ try {
348
+ validatePasswordRef(passwordRef);
349
+ } catch (error) {
350
+ return { error: error instanceof Error ? error.message : String(error) };
351
+ }
352
+ input.passwordRef = passwordRef;
353
+ } else if (state.password !== "") input.password = state.password;
337
354
  return { input };
338
355
  }
339
356
  function editTextField(state, key) {
@@ -370,7 +387,7 @@ function replaceField(state, value, cursor) {
370
387
  };
371
388
  }
372
389
  function isTextField(field) {
373
- return field === "host" || field === "port" || field === "user" || field === "database" || field === "password";
390
+ return field === "host" || field === "port" || field === "user" || field === "database" || field === "password" || field === "passwordRef";
374
391
  }
375
392
  function moveFocus(state, delta) {
376
393
  const fields = tuiConnectionFields(state.type);
@@ -395,16 +412,37 @@ function updateOpenSelector(state, key) {
395
412
  kind: "editing",
396
413
  state: closeSelector(state)
397
414
  };
398
- if (key.name === "enter") return {
399
- kind: "editing",
400
- state: closeSelector(selector.field === "type" ? {
401
- ...state,
402
- type: TUI_DATABASE_TYPES[selector.index]
403
- } : {
415
+ if (key.name === "enter") {
416
+ let selected;
417
+ if (selector.field === "type") {
418
+ const type = TUI_DATABASE_TYPES[selector.index];
419
+ const previousDefault = state.type === "sqlite" ? "" : String(defaultDatabasePort(state.type, state.secure));
420
+ const secure = type === "clickhouse" && state.secure;
421
+ const port = state.port === "" || state.port === previousDefault ? type === "sqlite" ? "" : String(defaultDatabasePort(type, secure)) : state.port;
422
+ selected = {
423
+ ...state,
424
+ type,
425
+ secure,
426
+ port
427
+ };
428
+ } else if (selector.field === "secure") {
429
+ const secure = selector.index === 1;
430
+ const previousDefault = String(defaultDatabasePort("clickhouse", state.secure));
431
+ const port = state.port === "" || state.port === previousDefault ? String(defaultDatabasePort("clickhouse", secure)) : state.port;
432
+ selected = {
433
+ ...state,
434
+ secure,
435
+ port
436
+ };
437
+ } else selected = {
404
438
  ...state,
405
439
  readonly: selector.index === 1
406
- })
407
- };
440
+ };
441
+ return {
442
+ kind: "editing",
443
+ state: closeSelector(selected)
444
+ };
445
+ }
408
446
  let delta = 0;
409
447
  if (key.name === "up" || key.name === "left") delta = -1;
410
448
  if (key.name === "down" || key.name === "right") delta = 1;
@@ -440,19 +478,20 @@ function renderField(state, field, width) {
440
478
  let placeholder = false;
441
479
  if (field === "type") value = databaseTypeLabel(state.type);
442
480
  else if (field === "readonly") value = state.readonly ? "是" : "否";
481
+ else if (field === "secure") value = state.secure ? "是" : "否";
443
482
  else {
444
483
  const raw = state[field];
445
484
  if (field === "password") value = "*".repeat([...raw].length);
446
485
  else value = raw;
447
486
  if (value === "") {
448
487
  placeholder = true;
449
- value = field === "host" ? "127.0.0.1(默认)" : field === "port" ? `${defaultDatabasePort(state.type)}(默认)` : field === "password" ? "可留空" : field === "user" ? "可留空" : "请输入";
488
+ value = field === "host" ? "127.0.0.1(默认)" : field === "port" ? `${defaultDatabasePort(state.type, state.secure)}(默认)` : field === "password" ? "可留空" : field === "passwordRef" ? "例如 DB_PASSWORD,可留空" : field === "user" ? "可留空" : "请输入";
450
489
  }
451
490
  }
452
491
  const maxValue = Math.max(8, width - 20);
453
492
  const shown = truncate(value.replace(/[\r\n\u001B]/g, " "), maxValue);
454
493
  const content = placeholder ? dim(shown) : shown;
455
- const expandable = field === "type" || field === "readonly";
494
+ const expandable = field === "type" || field === "readonly" || field === "secure";
456
495
  const line = `${pointer} ${label.padEnd(8, " ")} [ ${focused ? cyan(content) : content}${expandable ? " ▾" : ""} ]`;
457
496
  if (state.selector?.field !== field) return [line];
458
497
  return [line, ...renderSelectorOptions(state)];
@@ -460,20 +499,13 @@ function renderField(state, field, width) {
460
499
  function renderSelectorOptions(state) {
461
500
  const selector = state.selector;
462
501
  const labels = selector.field === "type" ? TUI_DATABASE_TYPES.map(databaseTypeLabel) : ["否", "是"];
463
- const selectedIndex = selector.field === "type" ? TUI_DATABASE_TYPES.indexOf(state.type) : state.readonly ? 1 : 0;
502
+ const selectedIndex = selector.field === "type" ? TUI_DATABASE_TYPES.indexOf(state.type) : state[selector.field] ? 1 : 0;
464
503
  return labels.map((label, index) => {
465
504
  return ` ${index === selector.index ? cyan("›") : " "} ${index === selectedIndex ? cyan("●") : dim("○")} ${index === selector.index ? cyan(bold(label)) : label}`;
466
505
  });
467
506
  }
468
507
  function databaseTypeLabel(type) {
469
- switch (type) {
470
- case "mysql": return "MySQL";
471
- case "postgres": return "PostgreSQL";
472
- case "sqlite": return "SQLite";
473
- case "oracle": return "Oracle";
474
- case "hive": return "Hive";
475
- case "impala": return "Impala";
476
- }
508
+ return databaseTypeLabel$1(type);
477
509
  }
478
510
  function fieldLabel(field, type) {
479
511
  switch (field) {
@@ -483,6 +515,8 @@ function fieldLabel(field, type) {
483
515
  case "user": return "数据库用户";
484
516
  case "database": return type === "sqlite" ? "文件路径" : "数据库名";
485
517
  case "password": return "密码";
518
+ case "passwordRef": return "凭据引用";
519
+ case "secure": return "HTTPS";
486
520
  case "readonly": return "只读模式";
487
521
  }
488
522
  }
@@ -513,7 +547,7 @@ const inject = [
513
547
  const DATABASE_COMMAND_USAGE = [
514
548
  "用法:",
515
549
  " /database status",
516
- " /database connect --type <mysql|postgres|sqlite|oracle|hive|impala> --database <name|path> [--host <host>] [--port <port>] [--user <user>] [--password-ref <REF>] [--readonly]",
550
+ ` /database connect --type <${DATABASE_TYPES.join("|")}> --database <name|path> [--host <host>] [--port <port>] [--user <user>] [--password-ref <REF>] [--readonly] [--secure]`,
517
551
  " /database test",
518
552
  " /database disconnect",
519
553
  "安全提示:TUI 无参数 connect 可输入掩码临时密码;命令参数不接受 --password,请使用 --password-ref。"
@@ -625,6 +659,7 @@ function parseDatabaseAction(rawInput) {
625
659
  function parseConnectArguments(tokens) {
626
660
  const values = /* @__PURE__ */ new Map();
627
661
  let readonly;
662
+ let secure;
628
663
  for (let index = 0; index < tokens.length; index += 1) {
629
664
  const token = tokens[index];
630
665
  if (token === "--password" || token.startsWith("--password=") || token.startsWith("password=")) throw new Error("安全限制:/database 不接受明文密码参数;请改用 --password-ref <REF>。");
@@ -636,6 +671,14 @@ function parseConnectArguments(tokens) {
636
671
  readonly = false;
637
672
  continue;
638
673
  }
674
+ if (token === "--secure") {
675
+ secure = true;
676
+ continue;
677
+ }
678
+ if (token === "--insecure") {
679
+ secure = false;
680
+ continue;
681
+ }
639
682
  const assignment = token.startsWith("--") ? token.slice(2).split("=", 2) : token.split("=", 2);
640
683
  let key;
641
684
  let value;
@@ -684,6 +727,7 @@ function parseConnectArguments(tokens) {
684
727
  input.port = number;
685
728
  }
686
729
  if (readonly !== void 0) input.readonly = readonly;
730
+ if (type === "clickhouse" && secure !== void 0) input.secure = secure;
687
731
  return input;
688
732
  }
689
733
  /** Render a public summary; no password-bearing field exists in the type. */
@@ -697,6 +741,7 @@ function formatConnectionStatus(summary) {
697
741
  `数据库:${summary.database}`,
698
742
  `只读:${summary.readonly === true ? "是" : "否"}`
699
743
  ];
744
+ if (summary.type === "clickhouse") lines.push(`HTTPS:${summary.secure === true ? "是" : "否"}`);
700
745
  if (summary.user !== void 0) lines.push(`用户:${summary.user}`);
701
746
  if (summary.profileId !== void 0) lines.push(`Profile:${summary.name ?? summary.profileId}`);
702
747
  if (summary.passwordRef !== void 0) lines.push(`凭据引用:${summary.passwordRef}`);
@@ -749,7 +794,7 @@ async function askForConnection(ctx, invocation, interaction) {
749
794
  {
750
795
  id: "port",
751
796
  header: "端口",
752
- question: `数据库端口(留空使用 ${defaultDatabasePort(typeValue)})`
797
+ question: typeValue === "clickhouse" ? `数据库端口(留空使用HTTP ${defaultDatabasePort$1("clickhouse")};HTTPS ${defaultDatabasePort$1("clickhouse", true)})` : `数据库端口(留空使用 ${defaultDatabasePort$1(typeValue)})`
753
798
  },
754
799
  {
755
800
  id: "user",
@@ -766,6 +811,12 @@ async function askForConnection(ctx, invocation, interaction) {
766
811
  header: "凭据引用",
767
812
  question: "DSH credential reference(可留空)"
768
813
  },
814
+ ...typeValue === "clickhouse" ? [{
815
+ id: "secure",
816
+ header: "HTTPS",
817
+ question: "是否使用HTTPS并验证服务器证书?",
818
+ options: [{ label: "是" }, { label: "否" }]
819
+ }] : [],
769
820
  {
770
821
  id: "readonly",
771
822
  header: "只读",
@@ -787,8 +838,9 @@ async function askForConnection(ctx, invocation, interaction) {
787
838
  };
788
839
  if (typeValue !== "sqlite") {
789
840
  input.host = answerValue(details, "host")?.trim() || "127.0.0.1";
841
+ if (typeValue === "clickhouse") input.secure = answerValue(details, "secure") === "是";
790
842
  const portText = answerValue(details, "port")?.trim();
791
- input.port = portText === void 0 || portText === "" ? defaultDatabasePort(typeValue) : Number(portText);
843
+ input.port = portText === void 0 || portText === "" ? defaultDatabasePort$1(typeValue, input.secure === true) : Number(portText);
792
844
  const user = answerValue(details, "user")?.trim();
793
845
  if (user !== void 0 && user !== "") input.user = user;
794
846
  const passwordRef = answerValue(details, "passwordRef")?.trim();
@@ -800,14 +852,6 @@ async function askForConnection(ctx, invocation, interaction) {
800
852
  throw error;
801
853
  }
802
854
  }
803
- const DATABASE_TYPES = [
804
- "mysql",
805
- "postgres",
806
- "sqlite",
807
- "oracle",
808
- "hive",
809
- "impala"
810
- ];
811
855
  const CONNECT_ARGUMENTS = /* @__PURE__ */ new Set([
812
856
  "type",
813
857
  "host",
@@ -818,9 +862,6 @@ const CONNECT_ARGUMENTS = /* @__PURE__ */ new Set([
818
862
  "profileId",
819
863
  "name"
820
864
  ]);
821
- function isDatabaseType(value) {
822
- return typeof value === "string" && DATABASE_TYPES.includes(value);
823
- }
824
865
  function normalizeArgumentName(value) {
825
866
  return value.replace(/-([a-z])/g, (_match, letter) => letter.toUpperCase());
826
867
  }
package/lib/command.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as formatConnectionStatus, c as parseConnectArguments, i as executeDatabaseCommand, l as parseDatabaseAction, n as DATA_AGENT_TOOL_NAMES, o as inject, r as apply, s as name, t as DATABASE_COMMAND_USAGE } from "./command-DuCpwVbl.js";
1
+ import { a as formatConnectionStatus, c as parseConnectArguments, i as executeDatabaseCommand, l as parseDatabaseAction, n as DATA_AGENT_TOOL_NAMES, o as inject, r as apply, s as name, t as DATABASE_COMMAND_USAGE } from "./command-utC5MHd9.js";
2
2
  export { DATABASE_COMMAND_USAGE, DATA_AGENT_TOOL_NAMES, apply, executeDatabaseCommand, formatConnectionStatus, inject, name, parseConnectArguments, parseDatabaseAction };