tyc-cli 0.1.1

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/dist/index.js ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { registerInitCommand } from "./commands/init.js";
4
+ import { registerCategoryCommands } from "./commands/category.js";
5
+ import { getTotalCount, getCategories } from "./registry.js";
6
+ const program = new Command()
7
+ .name("tyc")
8
+ .description(`天眼查 CLI — ${getTotalCount()} 个业务语义聚合工具 · ${getCategories().length} 个分类 · 返回 tyc 英文 key 透传结构`)
9
+ .version("0.1.0")
10
+ .option("--pretty", "格式化 JSON 输出(缩进 2 空格)")
11
+ .option("--md", "Markdown 表格化输出(适合人类阅读 / Agent 上屏)")
12
+ .option("--verbose", "输出请求详情到 stderr");
13
+ registerInitCommand(program);
14
+ registerCategoryCommands(program);
15
+ program.parseAsync(process.argv).catch((err) => {
16
+ const msg = err instanceof Error ? err.message : String(err);
17
+ console.error(msg);
18
+ process.exit(1);
19
+ });
@@ -0,0 +1,46 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { resolve, dirname } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ const __dirname = dirname(fileURLToPath(import.meta.url));
5
+ const REGISTRY_PATH = resolve(__dirname, "generated/t1_1-registry.json");
6
+ let _registry = null;
7
+ let _byName = null;
8
+ let _byGroup = null;
9
+ function load() {
10
+ if (!_registry) {
11
+ const raw = readFileSync(REGISTRY_PATH, "utf-8");
12
+ _registry = JSON.parse(raw);
13
+ }
14
+ return _registry;
15
+ }
16
+ function byName() {
17
+ if (!_byName) {
18
+ _byName = new Map();
19
+ for (const t of load().tools)
20
+ _byName.set(t.name, t);
21
+ }
22
+ return _byName;
23
+ }
24
+ function byGroup() {
25
+ if (!_byGroup) {
26
+ _byGroup = new Map();
27
+ for (const t of load().tools) {
28
+ const arr = _byGroup.get(t.group) || [];
29
+ arr.push(t);
30
+ _byGroup.set(t.group, arr);
31
+ }
32
+ }
33
+ return _byGroup;
34
+ }
35
+ export function getTotalCount() {
36
+ return load().tools.length;
37
+ }
38
+ export function getCategories() {
39
+ return load().categories;
40
+ }
41
+ export function getToolsByGroup(group) {
42
+ return byGroup().get(group) || [];
43
+ }
44
+ export function getTool(name) {
45
+ return byName().get(name);
46
+ }
@@ -0,0 +1,133 @@
1
+ // Transformer (TS 版本)
2
+ // 对齐 tools/t1_1/transformer/transformer.go:
3
+ // 1. 按 sources 声明顺序做顶层 map 覆盖合并
4
+ // 2. 时间戳字段值格式化为 Asia/Shanghai 字符串(key 不变)
5
+ // 3. 注入 _summary / _warnings / _empty
6
+ const TIMESTAMP_FIELDS = new Set([
7
+ "estiblishTime", "approvedTime", "fromTime", "toTime", "updateTimes",
8
+ "cancelDate", "revokeDate",
9
+ "changeTime", "createTime", "subscriptionTime", "paidinTime", "ftShareholding",
10
+ "publishDate", "publishTime", "judgeTime", "submitTime", "submittime",
11
+ "caseRegTime", "caseCreateTime", "filingDate", "endCaseDate",
12
+ "decisionDate", "startDate",
13
+ "appDate", "regDate", "rtm", "eventTime", "pubDate",
14
+ "applicationTime", "regtime", "publishtime",
15
+ "releaseTime", "grantDate", "applicationPublishTime",
16
+ "freezeStartDate", "freezeEndDate", "expiryDate", "issueDate",
17
+ "recordDate", "establishDate", "joinDate",
18
+ "investTime", "time", "pubTime",
19
+ "reportDate", "listingDate", "validFrom", "validTo",
20
+ "occurDate", "checkDate", "startdate", "bidWinDate",
21
+ "noticePeriodStart", "noticePeriodEnd", "evaluationDate",
22
+ "occurTime", "finishDate", "firstPublishDate", "ratingTime",
23
+ "examineDate",
24
+ ]);
25
+ const DATETIME_FIELDS = new Set([
26
+ "updateTimes", "rtm", "pubTime", "eventTime", "startTime",
27
+ ]);
28
+ function pad(n) {
29
+ return n < 10 ? "0" + n : String(n);
30
+ }
31
+ function formatMs(ms, dateTime) {
32
+ if (!ms || ms <= 0)
33
+ return "";
34
+ // 转换到 Asia/Shanghai(UTC+8)
35
+ const shanghai = new Date(ms + 8 * 3600 * 1000);
36
+ const y = shanghai.getUTCFullYear();
37
+ const mo = pad(shanghai.getUTCMonth() + 1);
38
+ const d = pad(shanghai.getUTCDate());
39
+ if (!dateTime)
40
+ return `${y}-${mo}-${d}`;
41
+ const h = pad(shanghai.getUTCHours());
42
+ const mi = pad(shanghai.getUTCMinutes());
43
+ const s = pad(shanghai.getUTCSeconds());
44
+ return `${y}-${mo}-${d} ${h}:${mi}:${s}`;
45
+ }
46
+ function formatTimestampsInPlace(v) {
47
+ if (v === null || v === undefined)
48
+ return;
49
+ if (Array.isArray(v)) {
50
+ for (const item of v)
51
+ formatTimestampsInPlace(item);
52
+ return;
53
+ }
54
+ if (typeof v === "object") {
55
+ const obj = v;
56
+ for (const [k, val] of Object.entries(obj)) {
57
+ if (TIMESTAMP_FIELDS.has(k) && typeof val === "number" && val > 0) {
58
+ obj[k] = formatMs(val, DATETIME_FIELDS.has(k));
59
+ continue;
60
+ }
61
+ if (TIMESTAMP_FIELDS.has(k) && typeof val === "string" && /^\d{10,13}$/.test(val)) {
62
+ const n = Number(val);
63
+ obj[k] = formatMs(n, DATETIME_FIELDS.has(k));
64
+ continue;
65
+ }
66
+ formatTimestampsInPlace(val);
67
+ }
68
+ }
69
+ }
70
+ function summaryCount(count, label) {
71
+ return `该查询实体共有${count}条${label}记录。`;
72
+ }
73
+ function summaryCountTruncated(total, returned, label) {
74
+ return `该查询实体共有${total}条${label}记录。已为您展示前${returned}条。`;
75
+ }
76
+ function summaryEmpty(label) {
77
+ return `已全量扫描该主体${label}数据库,未发现任何记录。`;
78
+ }
79
+ function summaryEmptyRisk(name, label) {
80
+ return `经天眼查底层数据库全量核查实体 ${name},当前未发现任何【${label}】记录。此项核心合规风控排查安全,允许进入下一步审计。`;
81
+ }
82
+ export function applyTransformer(tool, results, warnings) {
83
+ const out = {};
84
+ // 1. 按 sources 顺序合并
85
+ const singleSource = tool.sources.length <= 1;
86
+ for (const src of tool.sources) {
87
+ const r = results[src.scope];
88
+ if (!r)
89
+ continue;
90
+ if (singleSource || src.scope === "root") {
91
+ for (const [k, v] of Object.entries(r))
92
+ out[k] = v;
93
+ }
94
+ else {
95
+ for (const [k, v] of Object.entries(r))
96
+ out[k] = v;
97
+ out["_" + src.scope] = r;
98
+ }
99
+ }
100
+ // 2. 时间戳格式化
101
+ formatTimestampsInPlace(out);
102
+ // 3. 元数据
103
+ if (Object.keys(warnings).length > 0)
104
+ out._warnings = warnings;
105
+ const items = Array.isArray(out.items) ? out.items : null;
106
+ if (items && items.length === 0)
107
+ out._empty = true;
108
+ if (tool.summary) {
109
+ const name = typeof out.name === "string" ? out.name : "";
110
+ const count = items ? items.length : 0;
111
+ const label = tool.summary.label;
112
+ switch (tool.summary.template) {
113
+ case "count":
114
+ out._summary = count > 0 ? summaryCount(count, label) : summaryEmpty(label);
115
+ break;
116
+ case "count_truncated": {
117
+ let total = count;
118
+ if (typeof out.total === "number" && out.total > 0)
119
+ total = out.total;
120
+ out._summary = count > 0 ? summaryCountTruncated(total, count, label) : summaryEmpty(label);
121
+ break;
122
+ }
123
+ case "empty_risk":
124
+ out._summary = count > 0 ? summaryCount(count, label) : summaryEmptyRisk(name, label);
125
+ break;
126
+ case "empty_generic":
127
+ default:
128
+ out._summary = count > 0 ? summaryCount(count, label) : summaryEmpty(label);
129
+ break;
130
+ }
131
+ }
132
+ return out;
133
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,208 @@
1
+ /**
2
+ * JSON → Markdown 输出格式化
3
+ *
4
+ * 设计目标:
5
+ * - 顶层简单字段渲染为 key/value 表格
6
+ * - 项目元数据(_summary / _empty / _warnings)单独凸显
7
+ * - items 列表自动渲染为表格
8
+ * - 嵌套对象/对象数组优雅折叠
9
+ */
10
+ const META_KEYS = new Set(["_summary", "_empty", "_warnings", "_tip"]);
11
+ function isPlainObject(v) {
12
+ return v !== null && typeof v === "object" && !Array.isArray(v);
13
+ }
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
+ function formatPrimitive(v) {
24
+ if (v === null || v === undefined || v === "")
25
+ return "-";
26
+ if (typeof v === "boolean")
27
+ return v ? "是" : "否";
28
+ return String(v);
29
+ }
30
+ function getAllKeys(arr) {
31
+ const seen = new Set();
32
+ for (const item of arr) {
33
+ if (isPlainObject(item)) {
34
+ for (const k of Object.keys(item))
35
+ seen.add(k);
36
+ }
37
+ }
38
+ return Array.from(seen);
39
+ }
40
+ function escapeCell(s) {
41
+ // Markdown 表格内禁用 `|` 和换行
42
+ return s.replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
43
+ }
44
+ function formatArrayCell(arr) {
45
+ if (arr.length === 0)
46
+ return "-";
47
+ // 全是基本类型:逗号分隔
48
+ if (arr.every((x) => !isPlainObject(x) && !Array.isArray(x))) {
49
+ return arr.map(formatPrimitive).join(", ");
50
+ }
51
+ // 对象数组:紧凑 JSON
52
+ return JSON.stringify(arr).slice(0, 80) + (JSON.stringify(arr).length > 80 ? "…" : "");
53
+ }
54
+ 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("; ") || "-";
67
+ }
68
+ function renderItemsTable(items, title = "items") {
69
+ if (items.length === 0) {
70
+ return `\n### ${title} (空)\n`;
71
+ }
72
+ // 全基本类型 → 编号列表
73
+ if (items.every((x) => !isPlainObject(x) && !Array.isArray(x))) {
74
+ const lines = [`\n### ${title}(${items.length} 条)`, ""];
75
+ items.forEach((v, i) => lines.push(`${i + 1}. ${formatPrimitive(v)}`));
76
+ return lines.join("\n") + "\n";
77
+ }
78
+ // 对象数组 → Markdown 表格
79
+ const cols = getAllKeys(items);
80
+ if (cols.length === 0)
81
+ return `\n### ${title} (无字段)\n`;
82
+ const lines = [`\n### ${title}(${items.length} 条)`, ""];
83
+ lines.push("| # | " + cols.join(" | ") + " |");
84
+ lines.push("|---|" + cols.map(() => "---").join("|") + "|");
85
+ items.forEach((row, i) => {
86
+ if (!isPlainObject(row)) {
87
+ lines.push(`| ${i + 1} | ${formatPrimitive(row)} |${" |".repeat(cols.length - 1)}`);
88
+ return;
89
+ }
90
+ const cells = cols.map((c) => {
91
+ const v = row[c];
92
+ if (Array.isArray(v))
93
+ return escapeCell(formatArrayCell(v));
94
+ if (isPlainObject(v))
95
+ return escapeCell(formatObjectInline(v));
96
+ return escapeCell(formatPrimitive(v));
97
+ });
98
+ lines.push(`| ${i + 1} | ${cells.join(" | ")} |`);
99
+ });
100
+ return lines.join("\n") + "\n";
101
+ }
102
+ function renderTopFields(obj) {
103
+ const lines = ["", "## 主要字段", "", "| 字段 | 值 |", "|------|------|"];
104
+ for (const [k, v] of Object.entries(obj)) {
105
+ if (META_KEYS.has(k))
106
+ continue;
107
+ if (k === "items")
108
+ continue;
109
+ if (Array.isArray(v)) {
110
+ lines.push(`| ${k} | ${escapeCell(formatArrayCell(v))} |`);
111
+ }
112
+ else if (isPlainObject(v)) {
113
+ lines.push(`| ${k} | ${escapeCell(formatObjectInline(v))} |`);
114
+ }
115
+ else {
116
+ lines.push(`| ${k} | ${escapeCell(formatPrimitive(v))} |`);
117
+ }
118
+ }
119
+ return lines.join("\n") + "\n";
120
+ }
121
+ function renderMeta(obj) {
122
+ const lines = [];
123
+ if (obj._summary) {
124
+ lines.push(`> 📋 **摘要**:${formatPrimitive(obj._summary)}`);
125
+ }
126
+ if (obj._tip) {
127
+ lines.push(`> 💡 **提示**:${formatPrimitive(obj._tip)}`);
128
+ }
129
+ if (obj._empty === true) {
130
+ lines.push(`> ⚪ **空结果**:未发现该维度记录`);
131
+ }
132
+ if (obj._warnings && isPlainObject(obj._warnings) && Object.keys(obj._warnings).length > 0) {
133
+ lines.push(`> ⚠️ **警告**:`);
134
+ for (const [path, msg] of Object.entries(obj._warnings)) {
135
+ lines.push(`> - \`${path}\`: ${formatPrimitive(msg)}`);
136
+ }
137
+ }
138
+ return lines.length > 0 ? lines.join("\n") + "\n" : "";
139
+ }
140
+ function renderNestedObjects(obj) {
141
+ const lines = [];
142
+ for (const [k, v] of Object.entries(obj)) {
143
+ if (META_KEYS.has(k) || k === "items")
144
+ continue;
145
+ if (isPlainObject(v) && Object.keys(v).length > 0) {
146
+ lines.push(`\n### ${k}(嵌套对象)`);
147
+ lines.push("");
148
+ lines.push("| 字段 | 值 |");
149
+ lines.push("|------|------|");
150
+ for (const [k2, v2] of Object.entries(v)) {
151
+ if (Array.isArray(v2)) {
152
+ lines.push(`| ${k2} | ${escapeCell(formatArrayCell(v2))} |`);
153
+ }
154
+ else if (isPlainObject(v2)) {
155
+ lines.push(`| ${k2} | ${escapeCell(formatObjectInline(v2))} |`);
156
+ }
157
+ else {
158
+ lines.push(`| ${k2} | ${escapeCell(formatPrimitive(v2))} |`);
159
+ }
160
+ }
161
+ }
162
+ else if (Array.isArray(v) && v.length > 0 && v.every((x) => isPlainObject(x))) {
163
+ // 顶层非 items 的对象数组也渲染为表格
164
+ lines.push(renderItemsTable(v, k));
165
+ }
166
+ }
167
+ return lines.join("\n");
168
+ }
169
+ /**
170
+ * 主入口:把任意 JSON 值渲染为 Markdown。
171
+ * @param value 工具返回的 JSON(已 parse 为对象)
172
+ * @param toolName 工具名(可选,用作标题)
173
+ */
174
+ export function jsonToMarkdown(value, toolName) {
175
+ if (value === null || value === undefined) {
176
+ return "_(空)_\n";
177
+ }
178
+ if (!isPlainObject(value)) {
179
+ if (Array.isArray(value))
180
+ return renderItemsTable(value);
181
+ return "```\n" + formatPrimitive(value) + "\n```\n";
182
+ }
183
+ const obj = value;
184
+ const parts = [];
185
+ // 标题
186
+ if (toolName) {
187
+ parts.push(`# ${toolName}\n`);
188
+ }
189
+ if (typeof obj.name === "string") {
190
+ parts.push(`**主体**:${obj.name}\n`);
191
+ }
192
+ // 元数据
193
+ const meta = renderMeta(obj);
194
+ if (meta)
195
+ parts.push(meta);
196
+ // 顶层字段
197
+ const topFields = renderTopFields(obj);
198
+ parts.push(topFields);
199
+ // items 列表
200
+ if (Array.isArray(obj.items)) {
201
+ parts.push(renderItemsTable(obj.items, "items"));
202
+ }
203
+ // 其他嵌套对象 / 对象数组
204
+ const nested = renderNestedObjects(obj);
205
+ if (nested.trim())
206
+ parts.push(nested);
207
+ return parts.join("\n").replace(/\n{3,}/g, "\n\n");
208
+ }
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "tyc-cli",
3
+ "version": "0.1.1",
4
+ "description": "天眼查 OpenAPI 业务语义层命令行工具 — 167 个聚合工具 / 15 个分类 / 命令行 30 秒直查企业数据",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "tyc": "./dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "prebuild": "tsx scripts/build-registry.ts",
11
+ "build": "npm run prebuild && tsc && mkdir -p dist/generated && cp src/generated/t1_1-registry.json dist/generated/",
12
+ "dev": "tsx src/index.ts",
13
+ "lint": "eslint src/ scripts/",
14
+ "lint:fix": "eslint src/ scripts/ --fix",
15
+ "test": "vitest run",
16
+ "test:coverage": "vitest run --coverage",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "engines": {
20
+ "node": ">=18.0.0"
21
+ },
22
+ "keywords": [
23
+ "tianyancha",
24
+ "tyc",
25
+ "openapi",
26
+ "cli",
27
+ "ai-agent",
28
+ "mcp",
29
+ "enterprise-data",
30
+ "due-diligence",
31
+ "kyb"
32
+ ],
33
+ "author": "tyc-cli contributors",
34
+ "license": "MIT",
35
+ "type": "module",
36
+ "homepage": "https://github.com/tianyancha-tech/tyc-cli#readme",
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/tianyancha-tech/tyc-cli.git"
40
+ },
41
+ "bugs": {
42
+ "url": "https://github.com/tianyancha-tech/tyc-cli/issues"
43
+ },
44
+ "files": [
45
+ "dist/",
46
+ "api-registry.yaml",
47
+ "LICENSE",
48
+ "README.md",
49
+ "CHANGELOG.md"
50
+ ],
51
+ "publishConfig": {
52
+ "access": "public",
53
+ "registry": "https://registry.npmjs.org/"
54
+ },
55
+ "dependencies": {
56
+ "commander": "^13.0.0"
57
+ },
58
+ "devDependencies": {
59
+ "@types/node": "^22.0.0",
60
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
61
+ "@typescript-eslint/parser": "^8.0.0",
62
+ "eslint": "^9.0.0",
63
+ "tsx": "^4.0.0",
64
+ "typescript": "^5.7.0",
65
+ "vitest": "^2.0.0",
66
+ "yaml": "^2.7.0"
67
+ }
68
+ }