@investoday/investoday-api 1.9.53 → 1.9.55
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 +2 -10
- package/data/openapi.json +28 -17
- package/lib/call-api.js +9 -179
- package/lib/metadata.js +0 -163
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -77,9 +77,6 @@ investoday-api skill install investoday-finance-data --target "<SKILLS_DIR>"
|
|
|
77
77
|
investoday-api skill install investoday-finance-data --target-code "<TARGET_CODE>"
|
|
78
78
|
investoday-api list
|
|
79
79
|
investoday-api list 沪深京数据
|
|
80
|
-
investoday-api search-api query=违规处罚 tool_ids=list_stock_violation_penalt
|
|
81
|
-
investoday-api search-api query=股票,基本面分析
|
|
82
|
-
investoday-api search-api query=股票 --text
|
|
83
80
|
investoday-api <endpoint> [key=value ...]
|
|
84
81
|
investoday-api <endpoint> --method POST [key=value ...]
|
|
85
82
|
investoday-api <endpoint> --method POST [queryKey=value ...] --body-json '{"bodyKey":[]}'
|
|
@@ -89,10 +86,6 @@ Examples:
|
|
|
89
86
|
|
|
90
87
|
```bash
|
|
91
88
|
investoday-api list 沪深京数据/股票行情
|
|
92
|
-
investoday-api search-api query=stockCodes
|
|
93
|
-
investoday-api search-api query=股票,基本面分析
|
|
94
|
-
investoday-api search-api tool_ids=list_stock_violation_penalt,list_stock_report_schema
|
|
95
|
-
investoday-api search-api query=股票 --text
|
|
96
89
|
investoday-api skill list --page 1 --page-size 20
|
|
97
90
|
investoday-api skill list --api-key "<API_KEY>"
|
|
98
91
|
investoday-api skill search 股票 --json
|
|
@@ -158,9 +151,8 @@ investoday-api config remove
|
|
|
158
151
|
|
|
159
152
|
- Uses the local JSON config created by `investoday-api init`
|
|
160
153
|
- Only calls `https://data-api.investoday.net/data`
|
|
161
|
-
- Bundles endpoint metadata for `list` and
|
|
162
|
-
-
|
|
163
|
-
- `search-api` only accepts structured inputs such as `query=` and `tool_ids=`; `query=` accepts one value and supports comma-separated keywords
|
|
154
|
+
- Bundles endpoint metadata for browsing groups with `list` and resolving known endpoint paths
|
|
155
|
+
- API search is not supported; use `list` and the bundled references to locate endpoints
|
|
164
156
|
- For POST endpoints with JSON body parameters, pass query parameters as `key=value` and pass body parameters with `--body-json`
|
|
165
157
|
- Background update settings are stored in the local JSON config and can be inspected with `investoday-api update status`
|
|
166
158
|
- Prints the API response `data` field as formatted JSON
|
package/data/openapi.json
CHANGED
|
@@ -6648,83 +6648,94 @@
|
|
|
6648
6648
|
"industryCode": {
|
|
6649
6649
|
"type": "string",
|
|
6650
6650
|
"description": "行业代码",
|
|
6651
|
-
"example": ""
|
|
6651
|
+
"example": "740000"
|
|
6652
6652
|
},
|
|
6653
6653
|
"industryName": {
|
|
6654
6654
|
"type": "string",
|
|
6655
6655
|
"description": "行业名称",
|
|
6656
|
-
"example": ""
|
|
6656
|
+
"example": "煤炭"
|
|
6657
6657
|
},
|
|
6658
6658
|
"reportYear": {
|
|
6659
6659
|
"type": "string",
|
|
6660
6660
|
"description": "报告年度",
|
|
6661
|
-
"example": ""
|
|
6661
|
+
"example": "2026"
|
|
6662
6662
|
},
|
|
6663
6663
|
"reportPeriod": {
|
|
6664
6664
|
"type": "string",
|
|
6665
6665
|
"description": "报告时段",
|
|
6666
|
-
"example": ""
|
|
6666
|
+
"example": "0106"
|
|
6667
6667
|
},
|
|
6668
6668
|
"roeTtmQoqGrowth": {
|
|
6669
6669
|
"type": "number",
|
|
6670
|
-
"description": "ROE(TTM)环比增速"
|
|
6670
|
+
"description": "ROE(TTM)环比增速",
|
|
6671
|
+
"example": 0.1563
|
|
6671
6672
|
},
|
|
6672
6673
|
"roeTtmQoqGrowthLabel": {
|
|
6673
6674
|
"type": "integer",
|
|
6674
6675
|
"format": "int32",
|
|
6675
|
-
"description": "ROE(TTM)环比增速的标签"
|
|
6676
|
+
"description": "ROE(TTM)环比增速的标签",
|
|
6677
|
+
"example": 1
|
|
6676
6678
|
},
|
|
6677
6679
|
"grossMarginTTMMoMGrowth": {
|
|
6678
6680
|
"type": "number",
|
|
6679
|
-
"description": "毛利率(TTM)环比增速"
|
|
6681
|
+
"description": "毛利率(TTM)环比增速",
|
|
6682
|
+
"example": 0.0414
|
|
6680
6683
|
},
|
|
6681
6684
|
"grossMarginTTMMoMGrowthLabel": {
|
|
6682
6685
|
"type": "integer",
|
|
6683
6686
|
"format": "int32",
|
|
6684
|
-
"description": "毛利率(TTM)环比增速的标签"
|
|
6687
|
+
"description": "毛利率(TTM)环比增速的标签",
|
|
6688
|
+
"example": 1
|
|
6685
6689
|
},
|
|
6686
6690
|
"ttmNetProfitMarginQoq": {
|
|
6687
6691
|
"type": "string",
|
|
6688
6692
|
"description": "净利率(TTM)环比增速",
|
|
6689
|
-
"example": ""
|
|
6693
|
+
"example": "0.08"
|
|
6690
6694
|
},
|
|
6691
6695
|
"ttmNetProfitMarginQoqLabel": {
|
|
6692
6696
|
"type": "integer",
|
|
6693
6697
|
"format": "int32",
|
|
6694
|
-
"description": "净利率(TTM)环比增速的标签"
|
|
6698
|
+
"description": "净利率(TTM)环比增速的标签",
|
|
6699
|
+
"example": 1
|
|
6695
6700
|
},
|
|
6696
6701
|
"latestQtrNetProfitYoyQoq": {
|
|
6697
6702
|
"type": "number",
|
|
6698
|
-
"description": "最近一季度归母净利润同比增速的环比增速"
|
|
6703
|
+
"description": "最近一季度归母净利润同比增速的环比增速",
|
|
6704
|
+
"example": 14.5252
|
|
6699
6705
|
},
|
|
6700
6706
|
"latestQtrNetProfitYoyQoqLabel": {
|
|
6701
6707
|
"type": "integer",
|
|
6702
6708
|
"format": "int32",
|
|
6703
|
-
"description": "最近一季度归母净利润同比增速的环比增速的标签"
|
|
6709
|
+
"description": "最近一季度归母净利润同比增速的环比增速的标签",
|
|
6710
|
+
"example": 1
|
|
6704
6711
|
},
|
|
6705
6712
|
"latestQtrOpIncomeYoyQoq": {
|
|
6706
6713
|
"type": "number",
|
|
6707
|
-
"description": "最近一季度营业总收入同比增速的环比增速"
|
|
6714
|
+
"description": "最近一季度营业总收入同比增速的环比增速",
|
|
6715
|
+
"example": 211.331
|
|
6708
6716
|
},
|
|
6709
6717
|
"latestQtrOpIncomeYoyQoqLabel": {
|
|
6710
6718
|
"type": "integer",
|
|
6711
6719
|
"format": "int32",
|
|
6712
|
-
"description": "最近一季度营业总收入同比增速的环比增速的标签"
|
|
6720
|
+
"description": "最近一季度营业总收入同比增速的环比增速的标签",
|
|
6721
|
+
"example": 1
|
|
6713
6722
|
},
|
|
6714
6723
|
"latestQtrAdjNetProfitYoyMom": {
|
|
6715
6724
|
"type": "string",
|
|
6716
6725
|
"description": "最近一季度扣非净利润同比增速的环比增速",
|
|
6717
|
-
"example": ""
|
|
6726
|
+
"example": "5.8477"
|
|
6718
6727
|
},
|
|
6719
6728
|
"latestQtrAdjNetProfitYoyMomLabel": {
|
|
6720
6729
|
"type": "integer",
|
|
6721
6730
|
"format": "int32",
|
|
6722
|
-
"description": "最近一季度扣非净利润同比增速的环比增速的标签"
|
|
6731
|
+
"description": "最近一季度扣非净利润同比增速的环比增速的标签",
|
|
6732
|
+
"example": 1
|
|
6723
6733
|
},
|
|
6724
6734
|
"totalTag": {
|
|
6725
6735
|
"type": "integer",
|
|
6726
6736
|
"format": "int32",
|
|
6727
|
-
"description": "总的标签"
|
|
6737
|
+
"description": "总的标签",
|
|
6738
|
+
"example": 6
|
|
6728
6739
|
},
|
|
6729
6740
|
"pageNum": {
|
|
6730
6741
|
"type": "integer",
|
package/lib/call-api.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { getMetadata
|
|
1
|
+
const { getMetadata } = require("./metadata");
|
|
2
2
|
const {
|
|
3
3
|
BASE_SKILL_NAME,
|
|
4
4
|
DEFAULT_SKILL_PACKAGE_BASE_URL,
|
|
@@ -82,7 +82,6 @@ function printHelp() {
|
|
|
82
82
|
" investoday-api skill install <skill-name> [--target <skills-dir>] [--target-code <agent>] [--force] [--json]\n" +
|
|
83
83
|
" investoday-api <endpoint> [key=value ...] [--method GET|POST] [--body-json '<json>']\n" +
|
|
84
84
|
" investoday-api list [group-or-subgroup]\n" +
|
|
85
|
-
" investoday-api search-api query=<query> [tool_ids=<tool_id,...>] [--text]\n" +
|
|
86
85
|
" investoday-api --version\n" +
|
|
87
86
|
" investoday-api --help\n\n" +
|
|
88
87
|
"命令:\n" +
|
|
@@ -90,8 +89,7 @@ function printHelp() {
|
|
|
90
89
|
" config 查看、定位或删除本地配置\n" +
|
|
91
90
|
" update 管理 investoday-api 和 skill 的后台自动更新\n" +
|
|
92
91
|
" skill 浏览、搜索或安装 Skill Store 中的 skill\n" +
|
|
93
|
-
" list 浏览接口分组、子分组或接口\n" +
|
|
94
|
-
" search-api 搜索接口并返回请求参数、响应字段和示例命令\n\n" +
|
|
92
|
+
" list 浏览接口分组、子分组或接口\n\n" +
|
|
95
93
|
"示例:\n" +
|
|
96
94
|
" investoday-api init\n" +
|
|
97
95
|
" investoday-api config status\n" +
|
|
@@ -102,9 +100,6 @@ function printHelp() {
|
|
|
102
100
|
" investoday-api list\n" +
|
|
103
101
|
" investoday-api list 沪深京数据\n" +
|
|
104
102
|
" investoday-api list 沪深京数据/公司行为/基本信息\n" +
|
|
105
|
-
" investoday-api search-api query=股票,基本面分析\n" +
|
|
106
|
-
" investoday-api search-api tool_ids=list_stock_violation_penalt,list_stock_report_schema\n" +
|
|
107
|
-
" investoday-api search-api query=股票 --text\n" +
|
|
108
103
|
" investoday-api search key=贵州茅台 type=11\n" +
|
|
109
104
|
" investoday-api stock/basic-info stockCode=600519\n" +
|
|
110
105
|
" investoday-api fund/daily-quotes --method POST fundCode=000001 beginDate=2024-01-01 endDate=2024-12-31\n" +
|
|
@@ -666,7 +661,7 @@ function listTopGroups() {
|
|
|
666
661
|
|
|
667
662
|
function listGroup(query) {
|
|
668
663
|
const normalizedQuery = query.trim();
|
|
669
|
-
const { groupTree
|
|
664
|
+
const { groupTree } = getMetadata();
|
|
670
665
|
const directPath = normalizedQuery.split("/").map((part) => part.trim()).filter(Boolean);
|
|
671
666
|
|
|
672
667
|
const directNode = getGroupNode(groupTree, directPath);
|
|
@@ -686,23 +681,7 @@ function listGroup(query) {
|
|
|
686
681
|
return;
|
|
687
682
|
}
|
|
688
683
|
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
if (!fuzzyMatches.length) {
|
|
692
|
-
exitWithError(`错误:未找到匹配的分组或接口 '${normalizedQuery}'`);
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
const lines = [`Matches for '${normalizedQuery}':`];
|
|
696
|
-
for (const match of fuzzyMatches.slice(0, 20)) {
|
|
697
|
-
lines.push(`- ${match.apiName} | ${match.path} | ${match.method} | ${match.groupPath.join(" / ")}`);
|
|
698
|
-
if (match.description || match.summary) {
|
|
699
|
-
lines.push(` desc: ${compactText(match.description || match.summary)}`);
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
if (fuzzyMatches.length > 20) {
|
|
703
|
-
lines.push(`... and ${fuzzyMatches.length - 20} more`);
|
|
704
|
-
}
|
|
705
|
-
process.stdout.write(`${lines.join("\n")}\n`);
|
|
684
|
+
exitWithError(`错误:未找到分组 '${normalizedQuery}'`);
|
|
706
685
|
}
|
|
707
686
|
|
|
708
687
|
function runListCommand(args) {
|
|
@@ -714,155 +693,6 @@ function runListCommand(args) {
|
|
|
714
693
|
listGroup(args.join(" "));
|
|
715
694
|
}
|
|
716
695
|
|
|
717
|
-
function summarizeNames(items, emptyLabel) {
|
|
718
|
-
if (!items || !items.length) {
|
|
719
|
-
return emptyLabel;
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
const names = items
|
|
723
|
-
.map((item) => {
|
|
724
|
-
const name = item.name ? String(item.name).trim() : "";
|
|
725
|
-
const desc = item.desc ? String(item.desc).replace(/\s+/g, " ").trim() : "";
|
|
726
|
-
if (!name) {
|
|
727
|
-
return "";
|
|
728
|
-
}
|
|
729
|
-
if (!desc) {
|
|
730
|
-
return name;
|
|
731
|
-
}
|
|
732
|
-
return `${name}(${desc})`;
|
|
733
|
-
})
|
|
734
|
-
.filter(Boolean);
|
|
735
|
-
|
|
736
|
-
if (!names.length) {
|
|
737
|
-
return emptyLabel;
|
|
738
|
-
}
|
|
739
|
-
if (names.length <= 6) {
|
|
740
|
-
return names.join(", ");
|
|
741
|
-
}
|
|
742
|
-
return `${names.slice(0, 6).join(", ")} ... (+${names.length - 6})`;
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
function parseStructuredArgs(args) {
|
|
746
|
-
const params = {};
|
|
747
|
-
|
|
748
|
-
for (const arg of args) {
|
|
749
|
-
const equalIndex = arg.indexOf("=");
|
|
750
|
-
if (equalIndex <= 0) {
|
|
751
|
-
continue;
|
|
752
|
-
}
|
|
753
|
-
const key = arg.slice(0, equalIndex);
|
|
754
|
-
const value = arg.slice(equalIndex + 1);
|
|
755
|
-
|
|
756
|
-
if (Object.prototype.hasOwnProperty.call(params, key)) {
|
|
757
|
-
const existing = params[key];
|
|
758
|
-
params[key] = Array.isArray(existing) ? [...existing, value] : [existing, value];
|
|
759
|
-
} else {
|
|
760
|
-
params[key] = value;
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
return params;
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
function normalizeSearchCriteria(args) {
|
|
768
|
-
const textMode = args.includes("--text");
|
|
769
|
-
const filteredArgs = args.filter((arg) => arg !== "--text");
|
|
770
|
-
const structuredArgs = filteredArgs.filter((arg) => arg.includes("="));
|
|
771
|
-
const positionalArgs = filteredArgs.filter((arg) => !arg.includes("="));
|
|
772
|
-
if (positionalArgs.length) {
|
|
773
|
-
exitWithError("错误:search-api 只接受结构化入参,例如 query=... 和 tool_ids=...。");
|
|
774
|
-
}
|
|
775
|
-
const params = parseStructuredArgs(structuredArgs);
|
|
776
|
-
|
|
777
|
-
const queryInputs = ["query", "q"]
|
|
778
|
-
.filter((key) => params[key] !== undefined)
|
|
779
|
-
.flatMap((key) => (Array.isArray(params[key]) ? params[key] : [params[key]]));
|
|
780
|
-
if (queryInputs.length > 1) {
|
|
781
|
-
exitWithError("错误:search-api 只允许一个 query=...,多个关键词请在 query=... 内用英文逗号分隔。");
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
const rawQueryValues = [];
|
|
785
|
-
for (const key of ["query", "q"]) {
|
|
786
|
-
if (params[key] !== undefined) {
|
|
787
|
-
const value = params[key];
|
|
788
|
-
rawQueryValues.push(...(Array.isArray(value) ? value : [value]));
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
const rawToolIdValues = [];
|
|
793
|
-
for (const key of ["tool_ids", "tool_id", "toolIds"]) {
|
|
794
|
-
if (params[key] !== undefined) {
|
|
795
|
-
const value = params[key];
|
|
796
|
-
rawToolIdValues.push(...(Array.isArray(value) ? value : [value]));
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
const query = rawQueryValues
|
|
801
|
-
.flatMap((value) => String(value || "").split(","))
|
|
802
|
-
.map((value) => value.trim())
|
|
803
|
-
.filter(Boolean);
|
|
804
|
-
const toolIds = rawToolIdValues
|
|
805
|
-
.flatMap((value) => String(value || "").split(","))
|
|
806
|
-
.map((value) => value.trim())
|
|
807
|
-
.filter(Boolean);
|
|
808
|
-
|
|
809
|
-
return {
|
|
810
|
-
textMode,
|
|
811
|
-
criteria: { query, toolIds },
|
|
812
|
-
};
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
function formatSearchLabel(criteria) {
|
|
816
|
-
const queryLabel = criteria.query.length ? `query='${criteria.query.join(" | ")}'` : null;
|
|
817
|
-
const toolIdLabel = criteria.toolIds.length ? `tool_ids=${criteria.toolIds.join(",")}` : null;
|
|
818
|
-
return [queryLabel, toolIdLabel].filter(Boolean).join(" ");
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
function runSearchApiCommand(args) {
|
|
822
|
-
const { textMode, criteria } = normalizeSearchCriteria(args);
|
|
823
|
-
const { matches, error } = searchApis(criteria, 10);
|
|
824
|
-
if (error) {
|
|
825
|
-
exitWithError(`错误:${error}`);
|
|
826
|
-
}
|
|
827
|
-
if (!matches.length) {
|
|
828
|
-
exitWithError(`错误:未找到匹配接口 ${formatSearchLabel(criteria) || "当前查询条件"}`);
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
if (!textMode) {
|
|
832
|
-
const payload = {
|
|
833
|
-
query: criteria.query.join(" "),
|
|
834
|
-
toolIds: criteria.toolIds,
|
|
835
|
-
matches: matches.map((match) => ({
|
|
836
|
-
apiName: match.apiName,
|
|
837
|
-
path: match.path,
|
|
838
|
-
method: match.method,
|
|
839
|
-
reference: match.reference,
|
|
840
|
-
toolId: match.toolId,
|
|
841
|
-
description: match.description,
|
|
842
|
-
requestParams: match.parameters,
|
|
843
|
-
responseFields: match.responseFields,
|
|
844
|
-
responseSchema: match.responseSchema,
|
|
845
|
-
exampleCommand: formatExample(match.path, match.method, match.parameters),
|
|
846
|
-
})),
|
|
847
|
-
};
|
|
848
|
-
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
849
|
-
return;
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
const lines = [`Matches for ${formatSearchLabel(criteria) || "当前查询条件"}:`];
|
|
853
|
-
for (const match of matches) {
|
|
854
|
-
lines.push(`- ${match.apiName} | ${match.path} | ${match.method} | ${match.groupPath.join(" / ")}`);
|
|
855
|
-
if (match.description || match.summary) {
|
|
856
|
-
lines.push(` desc: ${compactText(match.description || match.summary)}`);
|
|
857
|
-
}
|
|
858
|
-
lines.push(` request params: ${summarizeNames(match.parameters, "none")}`);
|
|
859
|
-
lines.push(` response fields: ${summarizeNames(match.responseFields, "none")}`);
|
|
860
|
-
lines.push(` example: ${formatExample(match.path, match.method, match.parameters)}`);
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
process.stdout.write(`${lines.join("\n")}\n`);
|
|
864
|
-
}
|
|
865
|
-
|
|
866
696
|
function parseArgs(argv) {
|
|
867
697
|
if (!argv.length) {
|
|
868
698
|
exitWithError(
|
|
@@ -959,8 +789,9 @@ function resolveRequestEndpoint(apiPath, method = "") {
|
|
|
959
789
|
}
|
|
960
790
|
}
|
|
961
791
|
|
|
962
|
-
const {
|
|
963
|
-
|
|
792
|
+
const { records } = getMetadata();
|
|
793
|
+
const matches = records.filter((record) => record.path === apiPath);
|
|
794
|
+
if (matches.length === 1) {
|
|
964
795
|
return matches[0];
|
|
965
796
|
}
|
|
966
797
|
|
|
@@ -1679,12 +1510,12 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1679
1510
|
}
|
|
1680
1511
|
|
|
1681
1512
|
if (argv[0] === "schema" || argv[0] === "example") {
|
|
1682
|
-
exitWithError("错误:schema 和 example
|
|
1513
|
+
exitWithError("错误:schema 和 example 命令已移除,请通过接口 references 文档查看参数和示例。");
|
|
1683
1514
|
return;
|
|
1684
1515
|
}
|
|
1685
1516
|
|
|
1686
1517
|
if (argv[0] === "search-api") {
|
|
1687
|
-
|
|
1518
|
+
exitWithError("错误:search-api 命令已移除,不再支持搜索接口。请使用 `investoday-api list` 浏览接口分组。");
|
|
1688
1519
|
return;
|
|
1689
1520
|
}
|
|
1690
1521
|
|
|
@@ -1713,7 +1544,6 @@ module.exports = {
|
|
|
1713
1544
|
runInitCommand,
|
|
1714
1545
|
runSkillInstallCommand,
|
|
1715
1546
|
runSkillCommand,
|
|
1716
|
-
runSearchApiCommand,
|
|
1717
1547
|
runListCommand,
|
|
1718
1548
|
selectRequestMethod,
|
|
1719
1549
|
verifyApiKey,
|
package/lib/metadata.js
CHANGED
|
@@ -393,169 +393,6 @@ function getMetadata() {
|
|
|
393
393
|
return cachedMetadata;
|
|
394
394
|
}
|
|
395
395
|
|
|
396
|
-
function scoreRecord(record, query) {
|
|
397
|
-
const normalizedQuery = query.toLowerCase();
|
|
398
|
-
let score = 0;
|
|
399
|
-
|
|
400
|
-
const exactFields = [record.path, record.toolId, record.toolName, record.apiName];
|
|
401
|
-
if (exactFields.some((value) => String(value || "").toLowerCase() === normalizedQuery)) {
|
|
402
|
-
score += 100;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
const textFields = [
|
|
406
|
-
record.apiName,
|
|
407
|
-
record.path,
|
|
408
|
-
record.toolId,
|
|
409
|
-
record.toolName,
|
|
410
|
-
record.summary,
|
|
411
|
-
record.description,
|
|
412
|
-
record.groupPath.join("/"),
|
|
413
|
-
];
|
|
414
|
-
for (const value of textFields) {
|
|
415
|
-
const normalizedValue = String(value || "").toLowerCase();
|
|
416
|
-
if (!normalizedValue) {
|
|
417
|
-
continue;
|
|
418
|
-
}
|
|
419
|
-
if (normalizedValue.includes(normalizedQuery)) {
|
|
420
|
-
score += normalizedValue.startsWith(normalizedQuery) ? 25 : 15;
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
for (const parameter of record.parameters || []) {
|
|
425
|
-
const haystacks = [parameter.name, parameter.desc, parameter.type];
|
|
426
|
-
for (const value of haystacks) {
|
|
427
|
-
const normalizedValue = String(value || "").toLowerCase();
|
|
428
|
-
if (normalizedValue.includes(normalizedQuery)) {
|
|
429
|
-
score += parameter.name === query ? 12 : 8;
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
for (const field of record.responseFields || []) {
|
|
435
|
-
const haystacks = [field.name, field.desc];
|
|
436
|
-
for (const value of haystacks) {
|
|
437
|
-
const normalizedValue = String(value || "").toLowerCase();
|
|
438
|
-
if (normalizedValue.includes(normalizedQuery)) {
|
|
439
|
-
score += field.name === query ? 10 : 6;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
return score;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
function matchesRecordQuery(record, query) {
|
|
448
|
-
return scoreRecord(record, query) > 0;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
function normalizeSearchInput(input) {
|
|
452
|
-
const rawQueries = Array.isArray(input?.query)
|
|
453
|
-
? input.query
|
|
454
|
-
: input?.query !== undefined && input?.query !== null && input?.query !== ""
|
|
455
|
-
? [input.query]
|
|
456
|
-
: [];
|
|
457
|
-
const rawToolIds = Array.isArray(input?.toolIds)
|
|
458
|
-
? input.toolIds
|
|
459
|
-
: input?.toolIds !== undefined && input?.toolIds !== null && input?.toolIds !== ""
|
|
460
|
-
? [input.toolIds]
|
|
461
|
-
: [];
|
|
462
|
-
|
|
463
|
-
const queries = rawQueries
|
|
464
|
-
.flatMap((value) => String(value || "").split("\n"))
|
|
465
|
-
.map((value) => value.trim())
|
|
466
|
-
.filter(Boolean);
|
|
467
|
-
const toolIds = rawToolIds
|
|
468
|
-
.flatMap((value) => String(value || "").split(","))
|
|
469
|
-
.map((value) => value.trim())
|
|
470
|
-
.filter(Boolean);
|
|
471
|
-
|
|
472
|
-
return { queries, toolIds };
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
function resolveApi(query) {
|
|
476
|
-
const normalizedQuery = (query || "").trim();
|
|
477
|
-
if (!normalizedQuery) {
|
|
478
|
-
return { matches: [], error: "Please provide an endpoint path, tool_id, or API name." };
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
const { records } = getMetadata();
|
|
482
|
-
const lowerQuery = normalizedQuery.toLowerCase();
|
|
483
|
-
const exactMatches = records.filter((record) =>
|
|
484
|
-
record.path === normalizedQuery ||
|
|
485
|
-
record.toolId === normalizedQuery ||
|
|
486
|
-
record.toolName === normalizedQuery ||
|
|
487
|
-
record.apiName === normalizedQuery
|
|
488
|
-
);
|
|
489
|
-
|
|
490
|
-
if (exactMatches.length === 1) {
|
|
491
|
-
return { matches: exactMatches };
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
const fuzzyMatches = records.filter((record) => {
|
|
495
|
-
const haystacks = [
|
|
496
|
-
record.path,
|
|
497
|
-
record.toolId,
|
|
498
|
-
record.toolName,
|
|
499
|
-
record.apiName,
|
|
500
|
-
record.groupPath.join("/"),
|
|
501
|
-
].map((value) => String(value || "").toLowerCase());
|
|
502
|
-
|
|
503
|
-
return haystacks.some((value) => value.includes(lowerQuery));
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
if (exactMatches.length > 1) {
|
|
507
|
-
return { matches: exactMatches };
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
return { matches: fuzzyMatches };
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
function searchApis(input, limit = 10) {
|
|
514
|
-
const { queries, toolIds } = normalizeSearchInput(input);
|
|
515
|
-
if (!queries.length && !toolIds.length) {
|
|
516
|
-
return { matches: [], error: "Please provide query=... and/or tool_ids=... ." };
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
const { records } = getMetadata();
|
|
520
|
-
let candidates = records;
|
|
521
|
-
const toolIdOrder = new Map(toolIds.map((toolId, index) => [toolId, index]));
|
|
522
|
-
|
|
523
|
-
if (toolIds.length) {
|
|
524
|
-
const toolIdSet = new Set(toolIds);
|
|
525
|
-
candidates = candidates.filter((record) => toolIdSet.has(record.toolId));
|
|
526
|
-
if (!queries.length) {
|
|
527
|
-
const matches = candidates
|
|
528
|
-
.sort((left, right) =>
|
|
529
|
-
(toolIdOrder.get(left.toolId) ?? Number.MAX_SAFE_INTEGER) -
|
|
530
|
-
(toolIdOrder.get(right.toolId) ?? Number.MAX_SAFE_INTEGER) ||
|
|
531
|
-
left.path.localeCompare(right.path)
|
|
532
|
-
)
|
|
533
|
-
.slice(0, limit);
|
|
534
|
-
return { matches };
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
const scored = candidates
|
|
539
|
-
.map((record) => ({
|
|
540
|
-
record,
|
|
541
|
-
matchedAllQueries: queries.every((query) => matchesRecordQuery(record, query)),
|
|
542
|
-
score: queries.reduce((sum, query) => sum + scoreRecord(record, query), 0),
|
|
543
|
-
}))
|
|
544
|
-
.filter((item) => item.matchedAllQueries && item.score > 0)
|
|
545
|
-
.sort((left, right) =>
|
|
546
|
-
right.score - left.score ||
|
|
547
|
-
(toolIdOrder.get(left.record.toolId) ?? Number.MAX_SAFE_INTEGER) -
|
|
548
|
-
(toolIdOrder.get(right.record.toolId) ?? Number.MAX_SAFE_INTEGER) ||
|
|
549
|
-
left.record.path.localeCompare(right.record.path)
|
|
550
|
-
)
|
|
551
|
-
.slice(0, limit)
|
|
552
|
-
.map((item) => item.record);
|
|
553
|
-
|
|
554
|
-
return { matches: scored };
|
|
555
|
-
}
|
|
556
|
-
|
|
557
396
|
module.exports = {
|
|
558
397
|
getMetadata,
|
|
559
|
-
resolveApi,
|
|
560
|
-
searchApis,
|
|
561
398
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@investoday/investoday-api",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.55",
|
|
4
4
|
"description": "CLI for accessing InvestToday China market financial data via investoday-api.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/investoday-data/investoday-finance-data",
|