tyc-cli 0.3.8 → 0.3.9

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.
@@ -5,21 +5,14 @@
5
5
  * - 顶层简单字段渲染为 key/value 表格
6
6
  * - 项目元数据(_summary / _empty / _warnings)单独凸显
7
7
  * - items 列表自动渲染为表格
8
- * - 嵌套对象/对象数组优雅折叠
8
+ * - 嵌套对象/对象数组完整保留;显式截断由统一输出层负责
9
9
  */
10
- const META_KEYS = new Set(["_summary", "_empty", "_warnings", "_tip"]);
10
+ import { renderChangeRecords, splitChangeRecords } from "./personnelChanges.js";
11
+ import { renderHistoryGuide, splitHistoricalPersonnel } from "./history.js";
12
+ const META_KEYS = new Set(["_summary", "_empty", "_warnings", "_tip", "_truncated", "_pagination", "_riskOverview", "_dateSemantics", "_fieldSemantics", "_historyGuide", "_source"]);
11
13
  function isPlainObject(v) {
12
14
  return v !== null && typeof v === "object" && !Array.isArray(v);
13
15
  }
14
- function isEmptyValue(v) {
15
- if (v === null || v === undefined || v === "")
16
- return true;
17
- if (Array.isArray(v) && v.length === 0)
18
- return true;
19
- if (isPlainObject(v) && Object.keys(v).length === 0)
20
- return true;
21
- return false;
22
- }
23
16
  function formatPrimitive(v) {
24
17
  if (v === null || v === undefined || v === "")
25
18
  return "-";
@@ -49,21 +42,16 @@ function formatArrayCell(arr) {
49
42
  return arr.map(formatPrimitive).join(", ");
50
43
  }
51
44
  // 对象数组:紧凑 JSON
52
- return JSON.stringify(arr).slice(0, 80) + (JSON.stringify(arr).length > 80 ? "…" : "");
45
+ return JSON.stringify(arr);
53
46
  }
54
47
  function formatObjectInline(obj) {
55
- const parts = [];
56
- for (const [k, v] of Object.entries(obj)) {
57
- if (isEmptyValue(v))
58
- continue;
59
- if (isPlainObject(v) || Array.isArray(v)) {
60
- parts.push(`${k}=…`);
61
- }
62
- else {
63
- parts.push(`${k}=${formatPrimitive(v)}`);
64
- }
65
- }
66
- return parts.join("; ") || "-";
48
+ return JSON.stringify(obj);
49
+ }
50
+ function renderJSONBlock(value) {
51
+ const text = JSON.stringify(value, null, 2);
52
+ const longestFence = Math.max(0, ...(text.match(/`+/g) || []).map((s) => s.length));
53
+ const fence = "`".repeat(Math.max(3, longestFence + 1));
54
+ return `${fence}json\n${text}\n${fence}\n`;
67
55
  }
68
56
  function renderItemsTable(items, title = "items") {
69
57
  if (items.length === 0) {
@@ -75,6 +63,9 @@ function renderItemsTable(items, title = "items") {
75
63
  items.forEach((v, i) => lines.push(`${i + 1}. ${formatPrimitive(v)}`));
76
64
  return lines.join("\n") + "\n";
77
65
  }
66
+ if (!items.every(isPlainObject)) {
67
+ return `\n### ${title}(${items.length} 条)\n\n${renderJSONBlock(items)}`;
68
+ }
78
69
  // 对象数组 → Markdown 表格
79
70
  const cols = getAllKeys(items);
80
71
  if (cols.length === 0)
@@ -119,16 +110,71 @@ function renderTopFields(obj) {
119
110
  return lines.join("\n") + "\n";
120
111
  }
121
112
  function renderMeta(obj) {
122
- const lines = [];
113
+ const lines = renderHistoryGuide(obj._historyGuide);
114
+ const sourceNotes = (payload, prefix = "") => {
115
+ if (!isPlainObject(payload._source))
116
+ return;
117
+ const source = payload._source;
118
+ lines.push(`> ${prefix}数据来源:${escapeCell(formatPrimitive(source.api))};${escapeCell(formatPrimitive(source.meaning))}`);
119
+ lines.push(`> ${prefix}来源时间口径:${escapeCell(formatPrimitive(source.temporalNote))}`);
120
+ };
121
+ sourceNotes(obj);
122
+ if (isPlainObject(obj.sources)) {
123
+ for (const [scope, payload] of Object.entries(obj.sources)) {
124
+ if (isPlainObject(payload))
125
+ sourceNotes(payload, `${scope}:`);
126
+ }
127
+ }
128
+ if (isPlainObject(obj._fieldSemantics)) {
129
+ for (const key of Object.keys(obj._fieldSemantics).sort()) {
130
+ const note = obj._fieldSemantics[key];
131
+ if (typeof note === "string" && note)
132
+ lines.push(`> 字段口径:${escapeCell(note)}`);
133
+ }
134
+ }
135
+ if (isPlainObject(obj._dateSemantics)) {
136
+ for (const key of ["change_time", "ftShareholding", "changeTime", "createTime", "historicalTime", "firstAppointment"]) {
137
+ const note = obj._dateSemantics[key];
138
+ if (typeof note === "string" && note)
139
+ lines.push(`> 时间口径:${escapeCell(note)}`);
140
+ }
141
+ }
123
142
  if (obj._summary) {
124
143
  lines.push(`> 📋 **摘要**:${formatPrimitive(obj._summary)}`);
125
144
  }
126
145
  if (obj._tip) {
127
146
  lines.push(`> 💡 **提示**:${formatPrimitive(obj._tip)}`);
128
147
  }
129
- if (obj._empty === true) {
148
+ if (obj._empty === true && !isPlainObject(obj._riskOverview)) {
130
149
  lines.push(`> ⚪ **空结果**:未发现该维度记录`);
131
150
  }
151
+ if (isPlainObject(obj._pagination)) {
152
+ const p = obj._pagination;
153
+ const parts = [];
154
+ if (p.page !== undefined)
155
+ parts.push(`第${p.page}页`);
156
+ if (p.requested_page_size !== undefined)
157
+ parts.push(`请求每页${p.requested_page_size}条`);
158
+ if (p.effective_page_size !== undefined)
159
+ parts.push(`实际每页${p.effective_page_size}条`);
160
+ if (p.max_page_size !== undefined)
161
+ parts.push(`上游每页上限${p.max_page_size}条`);
162
+ parts.push(`本次返回${p.returned}条`);
163
+ parts.push(p.total === undefined ? "全量总数未知" : `共${p.total}条`);
164
+ if (p.has_more === true) {
165
+ parts.push(`还有下一页;沿用原命令和主体/筛选参数,传 --pageNum ${p.next_page} --pageSize ${p.effective_page_size}`);
166
+ }
167
+ else if (p.has_more === false) {
168
+ parts.push("已到末页");
169
+ }
170
+ else {
171
+ parts.push("尚不能确定是否还有下一页");
172
+ }
173
+ lines.push(`> 分页:${parts.join(";")}。`);
174
+ }
175
+ else if (obj._truncated === true) {
176
+ lines.push("> 当前返回不是全量;请根据工具分页参数继续查询。");
177
+ }
132
178
  if (obj._warnings && isPlainObject(obj._warnings) && Object.keys(obj._warnings).length > 0) {
133
179
  lines.push(`> ⚠️ **警告**:`);
134
180
  for (const [path, msg] of Object.entries(obj._warnings)) {
@@ -137,6 +183,55 @@ function renderMeta(obj) {
137
183
  }
138
184
  return lines.length > 0 ? lines.join("\n") + "\n" : "";
139
185
  }
186
+ function renderRiskOverview(obj) {
187
+ const parts = [];
188
+ const meta = obj._riskOverview;
189
+ if (isPlainObject(meta)) {
190
+ // State and counts come from Shared Core; never derive them from local rows.
191
+ parts.push(`> 数据状态:${formatPrimitive(meta.dataStatus)};摘要及诊断行数:${formatPrimitive(meta.displayRowCount)};风险事件总数:未确认。`);
192
+ parts.push(`> 计数口径:${formatPrimitive(meta.countMeaning)}`);
193
+ if (meta.reportedTotal !== undefined) {
194
+ parts.push(`> 上游报告总数:${formatPrimitive(meta.reportedTotal)}(保留原始口径,未经事件去重核实)。`);
195
+ }
196
+ if (Array.isArray(meta.reportedCounts) && meta.reportedCounts.length > 0) {
197
+ parts.push(renderItemsTable(meta.reportedCounts, "上游计数来源"));
198
+ }
199
+ if (Array.isArray(meta.issues)) {
200
+ parts.push(...meta.issues.map((issue) => `> 数据说明:${escapeCell(formatPrimitive(issue))}`));
201
+ }
202
+ }
203
+ const sections = [
204
+ ["toolRisks", "可用专项工具获取详情"],
205
+ ["detailRisks", "需通过风险 ID 获取详情"],
206
+ ["relationRiskNotes", "周边风险提示"],
207
+ ["warningRisks", "预警提醒"],
208
+ ["unclassifiedRisks", "未分类风险及诊断信息"],
209
+ ];
210
+ const sectionKeys = new Set(sections.map(([key]) => key));
211
+ const otherFields = Object.fromEntries(Object.entries(obj).filter(([key]) => !META_KEYS.has(key) && !sectionKeys.has(key) && key !== "name" && !(key === "total" && isPlainObject(meta))));
212
+ if (Object.keys(otherFields).length > 0)
213
+ parts.push(renderTopFields(otherFields));
214
+ for (const [key, title] of sections) {
215
+ const rows = obj[key];
216
+ if (!Array.isArray(rows) || rows.length === 0)
217
+ continue;
218
+ if (key === "detailRisks") {
219
+ parts.push("使用 get_risk_detail,将 riskId 作为 id 参数、同一行 type 作为 type 参数。");
220
+ }
221
+ parts.push(renderItemsTable(rows.map((row) => {
222
+ if (!isPlainObject(row) || !Array.isArray(row.examples))
223
+ return row;
224
+ const { examples, ...rest } = row;
225
+ return rest;
226
+ }), title));
227
+ for (const row of rows) {
228
+ if (isPlainObject(row) && Array.isArray(row.examples) && row.examples.length > 0) {
229
+ parts.push(renderItemsTable(row.examples, `${title} / ${formatPrimitive(row.riskType)}:关联主体与原始风险示例`));
230
+ }
231
+ }
232
+ }
233
+ return parts.join("\n\n");
234
+ }
140
235
  function renderNestedObjects(obj) {
141
236
  const lines = [];
142
237
  for (const [k, v] of Object.entries(obj)) {
@@ -181,6 +276,15 @@ export function jsonToMarkdown(value, toolName) {
181
276
  return "```\n" + formatPrimitive(value) + "\n```\n";
182
277
  }
183
278
  const obj = value;
279
+ const historical = splitHistoricalPersonnel(obj, toolName);
280
+ if (historical)
281
+ return jsonToMarkdown(historical.rest, toolName) + "\n" + historical.markdown;
282
+ const changes = splitChangeRecords(obj, toolName);
283
+ if (changes) {
284
+ // The recursive call receives a copy without the extracted list. Keep raw
285
+ // code blocks verbatim: the generic newline compaction must not touch them.
286
+ return jsonToMarkdown(changes.rest, toolName) + "\n" + renderChangeRecords(changes.records, changes.title);
287
+ }
184
288
  const parts = [];
185
289
  // 标题
186
290
  if (toolName) {
@@ -193,6 +297,10 @@ export function jsonToMarkdown(value, toolName) {
193
297
  const meta = renderMeta(obj);
194
298
  if (meta)
195
299
  parts.push(meta);
300
+ if (toolName === "get_risk_overview" || isPlainObject(obj._riskOverview)) {
301
+ parts.push(renderRiskOverview(obj));
302
+ return parts.join("\n").replace(/\n{3,}/g, "\n\n");
303
+ }
196
304
  // 顶层字段
197
305
  const topFields = renderTopFields(obj);
198
306
  parts.push(topFields);
@@ -0,0 +1,74 @@
1
+ // Presentation only: all personnel extraction and statuses come from Shared Core.
2
+ import { changeRecordsForDisplay } from "./history.js";
3
+ const object = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
4
+ const text = (v) => typeof v === "string" ? v : JSON.stringify(v) ?? "null";
5
+ const cell = (v) => (v === undefined || v === "" ? "-" : text(v)).replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
6
+ function block(value) {
7
+ const raw = text(value);
8
+ const longest = Math.max(0, ...(raw.match(/`+/g) ?? []).map((s) => s.length));
9
+ const fence = "`".repeat(Math.max(3, longest + 1));
10
+ return `${fence}text\n${raw}\n${fence}\n`;
11
+ }
12
+ export function splitChangeRecords(obj, toolName) {
13
+ if (toolName === "get_change_records" && Array.isArray(obj.items)) {
14
+ const rest = { ...obj };
15
+ delete rest.items;
16
+ return { rest, records: obj.items, title: "变更记录" };
17
+ }
18
+ if (toolName === "get_historical_registration" && object(obj.sources) && object(obj.sources.cb) && Array.isArray(obj.sources.cb.changeList)) {
19
+ const cb = { ...obj.sources.cb };
20
+ delete cb.changeList;
21
+ return { rest: { ...obj, sources: { ...obj.sources, cb } }, records: obj.sources.cb.changeList, title: "工商信息 / 变更记录" };
22
+ }
23
+ return undefined;
24
+ }
25
+ function renderSide(title, side) {
26
+ if (!object(side))
27
+ return "";
28
+ const out = [`#### ${title}\n`, `解析状态:${cell(side.status)}\n`];
29
+ const people = Array.isArray(side.people) ? side.people : [];
30
+ if (people.length) {
31
+ out.push("| 姓名 | 职务原值 | 法定代表人标志原值 | 显式变更标记 | 其他登记字段 |", "|---|---|---|---|---|");
32
+ for (const person of people) {
33
+ if (!object(person)) {
34
+ out.push(block(person));
35
+ continue;
36
+ }
37
+ const fields = Array.isArray(person.fields) ? person.fields : [];
38
+ const other = fields.filter((field) => !object(field) || !["姓名", "职务", "法定代表人标志"].includes(String(field.key)));
39
+ out.push(`| ${[person.name, person.position, person.legalRepresentativeFlag, person.marker, other].map(cell).join(" | ")} |`);
40
+ }
41
+ out.push("");
42
+ }
43
+ if (Array.isArray(side.warnings))
44
+ for (const warning of side.warnings)
45
+ out.push(`> 解析说明:${cell(warning)}\n`);
46
+ if (Array.isArray(side.unparsedFragments))
47
+ for (const fragment of side.unparsedFragments)
48
+ out.push("未解析片段:\n", block(fragment));
49
+ return out.join("\n");
50
+ }
51
+ export function renderChangeRecords(records, title) {
52
+ const out = [`## ${title}(${records.length} 条)\n`, "> 本次返回记录按变更日期倒序展示;同日维持来源顺序,日期未知置后,不代表全量历程。\n"];
53
+ changeRecordsForDisplay(records).forEach((value, index) => {
54
+ if (!object(value)) {
55
+ out.push(block(value));
56
+ return;
57
+ }
58
+ out.push(`### 第 ${index + 1} 条 · ${cell(value.changeTime)} · ${cell(value.changeItem)}\n`);
59
+ const parsed = object(value._parsed) && value._parsed.version === 1 ? value._parsed : undefined;
60
+ for (const [key, v] of Object.entries(value)) {
61
+ if (["changeTime", "changeItem", "contentBefore", "contentAfter"].includes(key) || (key === "_parsed" && parsed))
62
+ continue;
63
+ out.push(`${key === "createTime" ? "记录创建日期" : key}:${cell(v)}\n`);
64
+ }
65
+ if (parsed) {
66
+ out.push(`> 解析版本:${cell(parsed.version)};原文完整性:${cell(parsed.sourceCompleteness)}(未确认)。解析结果不是完整任职快照,标志原值未转换。\n`);
67
+ out.push(renderSide("变更前人员", parsed.before), renderSide("变更后人员", parsed.after));
68
+ }
69
+ for (const [key, name] of [["contentBefore", "变更前原文"], ["contentAfter", "变更后原文"]]) {
70
+ out.push(`#### ${name}\n`, key in value ? block(value[key]) : "_(原字段未返回)_\n");
71
+ }
72
+ });
73
+ return out.join("\n");
74
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tyc-cli",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
4
4
  "description": "天眼查 AI CLI — 162 个业务语义聚合工具 · 默认 Shared Core 调用 · 兼容 MCP transport",
5
5
  "main": "dist/index.js",
6
6
  "bin": {