gangtise-openapi-cli 0.14.2 → 0.14.3
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/src/cli.js +170 -311
- package/dist/src/core/client.js +32 -14
- package/dist/src/core/output.js +7 -15
- package/dist/src/core/quoteSharding.js +4 -1
- package/dist/src/core/titleCache.js +64 -0
- package/dist/src/version.js +1 -1
- package/package.json +1 -1
package/dist/src/cli.js
CHANGED
|
@@ -17,6 +17,20 @@ async function createClient() {
|
|
|
17
17
|
const { GangtiseClient } = await import("./core/client.js");
|
|
18
18
|
return new GangtiseClient(loadConfig());
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Acquire a client, run `produce` to fetch data, and render it through the
|
|
22
|
+
* shared pipeline. Collapses the `createClient()` + `printData(await client.call(...),
|
|
23
|
+
* parseOutputFormat(options.format), options.output)` boilerplate that every
|
|
24
|
+
* query command repeated.
|
|
25
|
+
*/
|
|
26
|
+
async function emit(options, produce, cache) {
|
|
27
|
+
const client = await createClient();
|
|
28
|
+
await printData(await produce(client), parseOutputFormat(options.format), options.output, cache);
|
|
29
|
+
}
|
|
30
|
+
/** Acquire a client and run an arbitrary action (downloads, polling, custom shaping). */
|
|
31
|
+
async function withClient(fn) {
|
|
32
|
+
await fn(await createClient());
|
|
33
|
+
}
|
|
20
34
|
/**
|
|
21
35
|
* Run a download. If `output` is set we already know the destination, so the
|
|
22
36
|
* client streams the body straight to disk (no in-memory Uint8Array copy);
|
|
@@ -57,10 +71,7 @@ program
|
|
|
57
71
|
.description("Authentication commands")
|
|
58
72
|
.addCommand(new Command("login")
|
|
59
73
|
.option("--format <format>", "Output format", "json")
|
|
60
|
-
.action(
|
|
61
|
-
const client = await createClient();
|
|
62
|
-
await printData(await client.login(), parseOutputFormat(options.format));
|
|
63
|
-
}))
|
|
74
|
+
.action((options) => emit(options, (client) => client.login())))
|
|
64
75
|
.addCommand(new Command("status")
|
|
65
76
|
.option("--format <format>", "Output format", "json")
|
|
66
77
|
.action(async (options) => {
|
|
@@ -70,38 +81,14 @@ program
|
|
|
70
81
|
}));
|
|
71
82
|
const lookup = new Command("lookup").description("Lookup helper APIs");
|
|
72
83
|
lookup
|
|
73
|
-
.addCommand(new Command("research-area").addCommand(new Command("list").option("--format <format>", "Output format", "table").action(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
.addCommand(new Command("
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
.addCommand(new Command("meeting-org").addCommand(new Command("list").option("--format <format>", "Output format", "table").action(async (options) => {
|
|
82
|
-
const client = await createClient();
|
|
83
|
-
await printData(await client.call("lookup.meeting-orgs.list"), parseOutputFormat(options.format));
|
|
84
|
-
})))
|
|
85
|
-
.addCommand(new Command("industry").addCommand(new Command("list").option("--format <format>", "Output format", "table").action(async (options) => {
|
|
86
|
-
const client = await createClient();
|
|
87
|
-
await printData(await client.call("lookup.industries.list"), parseOutputFormat(options.format));
|
|
88
|
-
})))
|
|
89
|
-
.addCommand(new Command("region").description("Foreign report region codes").addCommand(new Command("list").option("--format <format>", "Output format", "table").action(async (options) => {
|
|
90
|
-
const client = await createClient();
|
|
91
|
-
await printData(await client.call("lookup.regions.list"), parseOutputFormat(options.format));
|
|
92
|
-
})))
|
|
93
|
-
.addCommand(new Command("announcement-category").description("Announcement category codes").addCommand(new Command("list").option("--format <format>", "Output format", "table").action(async (options) => {
|
|
94
|
-
const client = await createClient();
|
|
95
|
-
await printData(await client.call("lookup.announcement-categories.list"), parseOutputFormat(options.format));
|
|
96
|
-
})))
|
|
97
|
-
.addCommand(new Command("industry-code").description("Shenwan industry codes for security-clue --gts-code").addCommand(new Command("list").option("--format <format>", "Output format", "table").action(async (options) => {
|
|
98
|
-
const client = await createClient();
|
|
99
|
-
await printData(await client.call("lookup.industry-codes.list"), parseOutputFormat(options.format));
|
|
100
|
-
})))
|
|
101
|
-
.addCommand(new Command("theme-id").description("Theme IDs for theme-tracking --theme-id").addCommand(new Command("list").option("--format <format>", "Output format", "table").action(async (options) => {
|
|
102
|
-
const client = await createClient();
|
|
103
|
-
await printData(await client.call("lookup.theme-ids.list"), parseOutputFormat(options.format));
|
|
104
|
-
})));
|
|
84
|
+
.addCommand(new Command("research-area").addCommand(new Command("list").option("--format <format>", "Output format", "table").action((options) => emit(options, (client) => client.call("lookup.research-areas.list")))))
|
|
85
|
+
.addCommand(new Command("broker-org").addCommand(new Command("list").option("--format <format>", "Output format", "table").action((options) => emit(options, (client) => client.call("lookup.broker-orgs.list")))))
|
|
86
|
+
.addCommand(new Command("meeting-org").addCommand(new Command("list").option("--format <format>", "Output format", "table").action((options) => emit(options, (client) => client.call("lookup.meeting-orgs.list")))))
|
|
87
|
+
.addCommand(new Command("industry").addCommand(new Command("list").option("--format <format>", "Output format", "table").action((options) => emit(options, (client) => client.call("lookup.industries.list")))))
|
|
88
|
+
.addCommand(new Command("region").description("Foreign report region codes").addCommand(new Command("list").option("--format <format>", "Output format", "table").action((options) => emit(options, (client) => client.call("lookup.regions.list")))))
|
|
89
|
+
.addCommand(new Command("announcement-category").description("Announcement category codes").addCommand(new Command("list").option("--format <format>", "Output format", "table").action((options) => emit(options, (client) => client.call("lookup.announcement-categories.list")))))
|
|
90
|
+
.addCommand(new Command("industry-code").description("Shenwan industry codes for security-clue --gts-code").addCommand(new Command("list").option("--format <format>", "Output format", "table").action((options) => emit(options, (client) => client.call("lookup.industry-codes.list")))))
|
|
91
|
+
.addCommand(new Command("theme-id").description("Theme IDs for theme-tracking --theme-id").addCommand(new Command("list").option("--format <format>", "Output format", "table").action((options) => emit(options, (client) => client.call("lookup.theme-ids.list")))));
|
|
105
92
|
program.addCommand(lookup);
|
|
106
93
|
const insight = new Command("insight").description("Insight APIs");
|
|
107
94
|
const opinion = new Command("opinion");
|
|
@@ -116,26 +103,19 @@ const announcement = new Command("announcement");
|
|
|
116
103
|
const announcementHk = new Command("announcement-hk");
|
|
117
104
|
const foreignOpinion = new Command("foreign-opinion");
|
|
118
105
|
const independentOpinion = new Command("independent-opinion");
|
|
119
|
-
addTimeFilters(opinion.command("list").option("--rank-type <number>", "Rank type", "1").option("--research-area <id>", "Research area ID", collectList, []).option("--chief <id>", "Chief ID", collectList, []).option("--security <code>", "Security code", collectList, []).option("--broker <id>", "Broker ID", collectList, []).option("--industry <id>", "Industry ID", collectList, []).option("--concept <id>", "Concept ID", collectList, []).option("--llm-tag <tag>", "Semantic tag", collectList, []).option("--source <source>", "Source", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>", "Output path")).action(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
searchType: parseNumberOption(options.searchType, "--search-type", { integer: true, min: 1 }), rankType: parseNumberOption(options.rankType, "--rank-type", { integer: true, min: 1 }), keyword: options.keyword, sourceList: options.source.length ? options.source : undefined,
|
|
133
|
-
researchAreaList: maybeArray(options.researchArea), securityList: maybeArray(options.security), institutionList: maybeArray(options.institution),
|
|
134
|
-
categoryList: maybeArray(options.category), marketList: maybeArray(options.market), participantRoleList: maybeArray(options.participantRole),
|
|
135
|
-
}), parseOutputFormat(options.format), options.output, { endpointKey: "insight.summary.list", idField: "summaryId" });
|
|
136
|
-
});
|
|
137
|
-
summary.command("download").requiredOption("--summary-id <id>").option("--file-type <number>", "File type: 1=original(default) 2=HTML; only affects meeting platform summaries").option("--output <path>").action(async (options) => {
|
|
138
|
-
const client = await createClient();
|
|
106
|
+
addTimeFilters(opinion.command("list").option("--rank-type <number>", "Rank type", "1").option("--research-area <id>", "Research area ID", collectList, []).option("--chief <id>", "Chief ID", collectList, []).option("--security <code>", "Security code", collectList, []).option("--broker <id>", "Broker ID", collectList, []).option("--industry <id>", "Industry ID", collectList, []).option("--concept <id>", "Concept ID", collectList, []).option("--llm-tag <tag>", "Semantic tag", collectList, []).option("--source <source>", "Source", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>", "Output path")).action((options) => emit(options, (client) => client.call("insight.opinion.list", {
|
|
107
|
+
from: parseFrom(options.from), size: parseSize(options.size), startTime: options.startTime, endTime: options.endTime,
|
|
108
|
+
rankType: parseNumberOption(options.rankType, "--rank-type", { integer: true, min: 1 }), keyword: options.keyword, researchAreaList: maybeArray(options.researchArea), chiefList: maybeArray(options.chief),
|
|
109
|
+
securityList: maybeArray(options.security), brokerList: maybeArray(options.broker), industryList: maybeArray(options.industry), conceptList: maybeArray(options.concept),
|
|
110
|
+
llmTagList: maybeArray(options.llmTag), sourceList: maybeArray(options.source),
|
|
111
|
+
})));
|
|
112
|
+
addTimeFilters(summary.command("list").option("--search-type <number>", "Search type", "1").option("--rank-type <number>", "Rank type", "1").option("--source <number>", "Source type", collectNumberList, []).option("--research-area <id>", "Research area", collectList, []).option("--security <code>", "Security code", collectList, []).option("--institution <id>", "Institution ID", collectList, []).option("--category <name>", "Category", collectList, []).option("--market <name>", "Market", collectList, []).option("--participant-role <name>", "Participant role", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>", "Output path")).action((options) => emit(options, (client) => client.call("insight.summary.list", {
|
|
113
|
+
from: parseFrom(options.from), size: parseSize(options.size), startTime: options.startTime, endTime: options.endTime,
|
|
114
|
+
searchType: parseNumberOption(options.searchType, "--search-type", { integer: true, min: 1 }), rankType: parseNumberOption(options.rankType, "--rank-type", { integer: true, min: 1 }), keyword: options.keyword, sourceList: options.source.length ? options.source : undefined,
|
|
115
|
+
researchAreaList: maybeArray(options.researchArea), securityList: maybeArray(options.security), institutionList: maybeArray(options.institution),
|
|
116
|
+
categoryList: maybeArray(options.category), marketList: maybeArray(options.market), participantRoleList: maybeArray(options.participantRole),
|
|
117
|
+
}), { endpointKey: "insight.summary.list", idField: "summaryId" }));
|
|
118
|
+
summary.command("download").requiredOption("--summary-id <id>").option("--file-type <number>", "File type: 1=original(default) 2=HTML; only affects meeting platform summaries").option("--output <path>").action((options) => withClient(async (client) => {
|
|
139
119
|
const qp = { summaryId: options.summaryId };
|
|
140
120
|
if (options.fileType)
|
|
141
121
|
qp.fileType = parseNumberOption(options.fileType, "--file-type", { integer: true, min: 1 });
|
|
@@ -144,124 +124,98 @@ summary.command("download").requiredOption("--summary-id <id>").option("--file-t
|
|
|
144
124
|
fallbackName: `summary-${options.summaryId}`,
|
|
145
125
|
resolveOutputPath: (result) => resolveTitle(client, result, "insight.summary.list", "summaryId", options.summaryId),
|
|
146
126
|
});
|
|
147
|
-
});
|
|
148
|
-
const addScheduleList = (command, endpointKey) => addTimeFilters(command.command("list").option("--research-area <id>", "Research area", collectList, []).option("--institution <id>", "Institution ID", collectList, []).option("--security <code>", "Security code", collectList, []).option("--category <name>", "Category", collectList, []).option("--market <name>", "Market", collectList, []).option("--participant-role <name>", "Participant role", collectList, []).option("--broker-type <name>", "Broker type", collectList, []).option("--object <type>", "Object type: company/industry", collectList, []).option("--permission <number>", "Permission", collectNumberList, []).option("--format <format>", "Output format", "table").option("--output <path>", "Output path")).action(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
brokerTypeList: maybeArray(options.brokerType), objectList: maybeArray(options.object), permission: options.permission.length ? options.permission : undefined,
|
|
155
|
-
}), parseOutputFormat(options.format), options.output);
|
|
156
|
-
});
|
|
127
|
+
}));
|
|
128
|
+
const addScheduleList = (command, endpointKey) => addTimeFilters(command.command("list").option("--research-area <id>", "Research area", collectList, []).option("--institution <id>", "Institution ID", collectList, []).option("--security <code>", "Security code", collectList, []).option("--category <name>", "Category", collectList, []).option("--market <name>", "Market", collectList, []).option("--participant-role <name>", "Participant role", collectList, []).option("--broker-type <name>", "Broker type", collectList, []).option("--object <type>", "Object type: company/industry", collectList, []).option("--permission <number>", "Permission", collectNumberList, []).option("--format <format>", "Output format", "table").option("--output <path>", "Output path")).action((options) => emit(options, (client) => client.call(endpointKey, {
|
|
129
|
+
from: parseFrom(options.from), size: parseSize(options.size), startTime: options.startTime, endTime: options.endTime, keyword: options.keyword,
|
|
130
|
+
researchAreaList: maybeArray(options.researchArea), institutionList: maybeArray(options.institution), securityList: maybeArray(options.security),
|
|
131
|
+
categoryList: maybeArray(options.category), marketList: maybeArray(options.market), participantRoleList: maybeArray(options.participantRole),
|
|
132
|
+
brokerTypeList: maybeArray(options.brokerType), objectList: maybeArray(options.object), permission: options.permission.length ? options.permission : undefined,
|
|
133
|
+
})));
|
|
157
134
|
addScheduleList(roadshow, "insight.roadshow.list");
|
|
158
135
|
addScheduleList(siteVisit, "insight.site-visit.list");
|
|
159
136
|
addScheduleList(strategy, "insight.strategy.list");
|
|
160
137
|
addScheduleList(forum, "insight.forum.list");
|
|
161
|
-
addTimeFilters(research.command("list").option("--search-type <number>", "Search type: 1=title 2=fulltext", "1").option("--rank-type <number>", "Rank type: 1=composite 2=time desc", "1").option("--broker <id>", "Broker ID", collectList, []).option("--security <code>", "Security code", collectList, []).option("--industry <id>", "Industry ID", collectList, []).option("--category <name>", "Report category", collectList, []).option("--llm-tag <tag>", "Semantic tag", collectList, []).option("--rating <name>", "Rating", collectList, []).option("--rating-change <name>", "Rating change", collectList, []).option("--min-pages <number>", "Min report pages").option("--max-pages <number>", "Max report pages").option("--source <type>", "Source type", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>", "Output path")).action(
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}), parseOutputFormat(options.format), options.output, { endpointKey: "insight.research.list", idField: "reportId" });
|
|
171
|
-
});
|
|
172
|
-
research.command("download").requiredOption("--report-id <id>").option("--file-type <number>", "File type: 1=PDF 2=Markdown", "1").option("--output <path>").action(async (options) => {
|
|
173
|
-
const client = await createClient();
|
|
138
|
+
addTimeFilters(research.command("list").option("--search-type <number>", "Search type: 1=title 2=fulltext", "1").option("--rank-type <number>", "Rank type: 1=composite 2=time desc", "1").option("--broker <id>", "Broker ID", collectList, []).option("--security <code>", "Security code", collectList, []).option("--industry <id>", "Industry ID", collectList, []).option("--category <name>", "Report category", collectList, []).option("--llm-tag <tag>", "Semantic tag", collectList, []).option("--rating <name>", "Rating", collectList, []).option("--rating-change <name>", "Rating change", collectList, []).option("--min-pages <number>", "Min report pages").option("--max-pages <number>", "Max report pages").option("--source <type>", "Source type", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>", "Output path")).action((options) => emit(options, (client) => client.call("insight.research.list", {
|
|
139
|
+
from: parseFrom(options.from), size: parseSize(options.size), startTime: options.startTime, endTime: options.endTime, keyword: options.keyword,
|
|
140
|
+
searchType: parseNumberOption(options.searchType, "--search-type", { integer: true, min: 1 }), rankType: parseNumberOption(options.rankType, "--rank-type", { integer: true, min: 1 }),
|
|
141
|
+
brokerList: maybeArray(options.broker), securityList: maybeArray(options.security), industryList: maybeArray(options.industry),
|
|
142
|
+
categoryList: maybeArray(options.category), llmTagList: maybeArray(options.llmTag), ratingList: maybeArray(options.rating),
|
|
143
|
+
ratingChangeList: maybeArray(options.ratingChange), minReportPages: parseOptionalNumberOption(options.minPages, "--min-pages", { integer: true, min: 0 }),
|
|
144
|
+
maxReportPages: parseOptionalNumberOption(options.maxPages, "--max-pages", { integer: true, min: 0 }), sourceList: maybeArray(options.source),
|
|
145
|
+
}), { endpointKey: "insight.research.list", idField: "reportId" }));
|
|
146
|
+
research.command("download").requiredOption("--report-id <id>").option("--file-type <number>", "File type: 1=PDF 2=Markdown", "1").option("--output <path>").action((options) => withClient(async (client) => {
|
|
174
147
|
await runDownload(client, "insight.research.download", { reportId: options.reportId, fileType: parseNumberOption(options.fileType, "--file-type", { integer: true, min: 1 }) }, {
|
|
175
148
|
output: options.output,
|
|
176
149
|
fallbackName: `research-${options.reportId}`,
|
|
177
150
|
resolveOutputPath: (result) => resolveTitle(client, result, "insight.research.list", "reportId", options.reportId),
|
|
178
151
|
});
|
|
179
|
-
});
|
|
180
|
-
addTimeFilters(foreignReport.command("list").option("--search-type <number>", "Search type: 1=title 2=fulltext", "1").option("--rank-type <number>", "Rank type: 1=composite 2=time desc", "1").option("--security <code>", "Security code", collectList, []).option("--region <id>", "Region ID", collectList, []).option("--category <name>", "Report category", collectList, []).option("--industry <id>", "Industry ID", collectList, []).option("--broker <id>", "Broker ID", collectList, []).option("--llm-tag <tag>", "Semantic tag", collectList, []).option("--rating <name>", "Rating", collectList, []).option("--rating-change <name>", "Rating change", collectList, []).option("--min-pages <number>", "Min report pages").option("--max-pages <number>", "Max report pages").option("--format <format>", "Output format", "table").option("--output <path>", "Output path")).action(
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}), parseOutputFormat(options.format), options.output, { endpointKey: "insight.foreign-report.list", idField: "reportId" });
|
|
190
|
-
});
|
|
191
|
-
foreignReport.command("download").requiredOption("--report-id <id>").option("--file-type <number>", "File type: 1=PDF 2=Markdown 3=CN-PDF 4=CN-Markdown", "1").option("--output <path>").action(async (options) => {
|
|
192
|
-
const client = await createClient();
|
|
152
|
+
}));
|
|
153
|
+
addTimeFilters(foreignReport.command("list").option("--search-type <number>", "Search type: 1=title 2=fulltext", "1").option("--rank-type <number>", "Rank type: 1=composite 2=time desc", "1").option("--security <code>", "Security code", collectList, []).option("--region <id>", "Region ID", collectList, []).option("--category <name>", "Report category", collectList, []).option("--industry <id>", "Industry ID", collectList, []).option("--broker <id>", "Broker ID", collectList, []).option("--llm-tag <tag>", "Semantic tag", collectList, []).option("--rating <name>", "Rating", collectList, []).option("--rating-change <name>", "Rating change", collectList, []).option("--min-pages <number>", "Min report pages").option("--max-pages <number>", "Max report pages").option("--format <format>", "Output format", "table").option("--output <path>", "Output path")).action((options) => emit(options, (client) => client.call("insight.foreign-report.list", {
|
|
154
|
+
from: parseFrom(options.from), size: parseSize(options.size), startTime: options.startTime, endTime: options.endTime, keyword: options.keyword,
|
|
155
|
+
searchType: parseNumberOption(options.searchType, "--search-type", { integer: true, min: 1 }), rankType: parseNumberOption(options.rankType, "--rank-type", { integer: true, min: 1 }),
|
|
156
|
+
securityList: maybeArray(options.security), regionList: maybeArray(options.region), categoryList: maybeArray(options.category),
|
|
157
|
+
industryList: maybeArray(options.industry), brokerList: maybeArray(options.broker), llmTagList: maybeArray(options.llmTag),
|
|
158
|
+
ratingList: maybeArray(options.rating), ratingChangeList: maybeArray(options.ratingChange),
|
|
159
|
+
minReportPages: parseOptionalNumberOption(options.minPages, "--min-pages", { integer: true, min: 0 }), maxReportPages: parseOptionalNumberOption(options.maxPages, "--max-pages", { integer: true, min: 0 }),
|
|
160
|
+
}), { endpointKey: "insight.foreign-report.list", idField: "reportId" }));
|
|
161
|
+
foreignReport.command("download").requiredOption("--report-id <id>").option("--file-type <number>", "File type: 1=PDF 2=Markdown 3=CN-PDF 4=CN-Markdown", "1").option("--output <path>").action((options) => withClient(async (client) => {
|
|
193
162
|
await runDownload(client, "insight.foreign-report.download", { reportId: options.reportId, fileType: parseNumberOption(options.fileType, "--file-type", { integer: true, min: 1 }) }, {
|
|
194
163
|
output: options.output,
|
|
195
164
|
fallbackName: `foreign-report-${options.reportId}`,
|
|
196
165
|
resolveOutputPath: (result) => resolveTitle(client, result, "insight.foreign-report.list", "reportId", options.reportId),
|
|
197
166
|
});
|
|
198
|
-
});
|
|
199
|
-
addTimeFilters(announcement.command("list").option("--search-type <number>", "Search type: 1=title 2=fulltext", "1").option("--rank-type <number>", "Rank type: 1=composite 2=time desc", "1").option("--security <code>", "Security code", collectList, []).option("--announcement-type <type>", "Announcement type", collectList, []).option("--category <id>", "Category ID", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>", "Output path")).action(
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}), parseOutputFormat(options.format), options.output, { endpointKey: "insight.announcement.list", idField: "announcementId" });
|
|
207
|
-
});
|
|
208
|
-
announcement.command("download").requiredOption("--announcement-id <id>").option("--file-type <number>", "File type: 1=PDF 2=Markdown", "1").option("--output <path>").action(async (options) => {
|
|
209
|
-
const client = await createClient();
|
|
167
|
+
}));
|
|
168
|
+
addTimeFilters(announcement.command("list").option("--search-type <number>", "Search type: 1=title 2=fulltext", "1").option("--rank-type <number>", "Rank type: 1=composite 2=time desc", "1").option("--security <code>", "Security code", collectList, []).option("--announcement-type <type>", "Announcement type", collectList, []).option("--category <id>", "Category ID", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>", "Output path")).action((options) => emit(options, (client) => client.call("insight.announcement.list", {
|
|
169
|
+
from: parseFrom(options.from), size: parseSize(options.size),
|
|
170
|
+
startTime: parseTimestamp13(options.startTime, "--start-time"), endTime: parseTimestamp13(options.endTime, "--end-time"),
|
|
171
|
+
searchType: parseNumberOption(options.searchType, "--search-type", { integer: true, min: 1 }), rankType: parseNumberOption(options.rankType, "--rank-type", { integer: true, min: 1 }), keyword: options.keyword,
|
|
172
|
+
securityList: maybeArray(options.security), announcementTypeList: maybeArray(options.announcementType), categoryList: maybeArray(options.category),
|
|
173
|
+
}), { endpointKey: "insight.announcement.list", idField: "announcementId" }));
|
|
174
|
+
announcement.command("download").requiredOption("--announcement-id <id>").option("--file-type <number>", "File type: 1=PDF 2=Markdown", "1").option("--output <path>").action((options) => withClient(async (client) => {
|
|
210
175
|
await runDownload(client, "insight.announcement.download", { announcementId: options.announcementId, fileType: parseNumberOption(options.fileType, "--file-type", { integer: true, min: 1 }) }, {
|
|
211
176
|
output: options.output,
|
|
212
177
|
fallbackName: `announcement-${options.announcementId}`,
|
|
213
178
|
resolveOutputPath: (result) => resolveTitle(client, result, "insight.announcement.list", "announcementId", options.announcementId),
|
|
214
179
|
});
|
|
215
|
-
});
|
|
216
|
-
addTimeFilters(announcementHk.command("list").option("--search-type <number>", "Search type: 1=title 2=fulltext", "1").option("--rank-type <number>", "Rank type: 1=composite 2=time desc", "1").option("--security <code>", "Security code (e.g. 01913.HK)", collectList, []).option("--category <id>", "Category ID", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>", "Output path")).action(
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}), parseOutputFormat(options.format), options.output, { endpointKey: "insight.announcement-hk.list", idField: "announcementId" });
|
|
226
|
-
});
|
|
227
|
-
announcementHk.command("download").requiredOption("--announcement-id <id>").option("--output <path>").action(async (options) => {
|
|
228
|
-
const client = await createClient();
|
|
180
|
+
}));
|
|
181
|
+
addTimeFilters(announcementHk.command("list").option("--search-type <number>", "Search type: 1=title 2=fulltext", "1").option("--rank-type <number>", "Rank type: 1=composite 2=time desc", "1").option("--security <code>", "Security code (e.g. 01913.HK)", collectList, []).option("--category <id>", "Category ID", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>", "Output path")).action((options) => emit(options, (client) => client.call("insight.announcement-hk.list", {
|
|
182
|
+
from: parseFrom(options.from), size: parseSize(options.size),
|
|
183
|
+
startTime: options.startTime, endTime: options.endTime,
|
|
184
|
+
searchType: parseNumberOption(options.searchType, "--search-type", { integer: true, min: 1 }),
|
|
185
|
+
rankType: parseNumberOption(options.rankType, "--rank-type", { integer: true, min: 1 }),
|
|
186
|
+
keyword: options.keyword,
|
|
187
|
+
securityList: maybeArray(options.security), categoryList: maybeArray(options.category),
|
|
188
|
+
}), { endpointKey: "insight.announcement-hk.list", idField: "announcementId" }));
|
|
189
|
+
announcementHk.command("download").requiredOption("--announcement-id <id>").option("--output <path>").action((options) => withClient(async (client) => {
|
|
229
190
|
await runDownload(client, "insight.announcement-hk.download", { announcementId: options.announcementId }, {
|
|
230
191
|
output: options.output,
|
|
231
192
|
fallbackName: `announcement-hk-${options.announcementId}`,
|
|
232
193
|
resolveOutputPath: (result) => resolveTitle(client, result, "insight.announcement-hk.list", "announcementId", options.announcementId),
|
|
233
194
|
});
|
|
234
|
-
});
|
|
235
|
-
addTimeFilters(foreignOpinion.command("list").option("--rank-type <number>", "Rank type: 1=composite 2=time desc", "1").option("--security <code>", "Security code (e.g. UBER.N)", collectList, []).option("--region <code>", "Region code", collectList, []).option("--industry <id>", "Industry ID", collectList, []).option("--broker <id>", "Broker ID", collectList, []).option("--rating <name>", "Rating", collectList, []).option("--rating-change <name>", "Rating change", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>", "Output path")).action(
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
keyword: options.keyword,
|
|
254
|
-
industryList: maybeArray(options.industry), securityList: maybeArray(options.security),
|
|
255
|
-
ratingList: maybeArray(options.rating), ratingChangeList: maybeArray(options.ratingChange),
|
|
256
|
-
}), parseOutputFormat(options.format), options.output);
|
|
257
|
-
});
|
|
258
|
-
independentOpinion.command("download").requiredOption("--independent-opinion-id <id>").requiredOption("--file-type <number>", "File type: 1=original HTML 2=CN-translated HTML").option("--output <path>").action(async (options) => {
|
|
259
|
-
const client = await createClient();
|
|
195
|
+
}));
|
|
196
|
+
addTimeFilters(foreignOpinion.command("list").option("--rank-type <number>", "Rank type: 1=composite 2=time desc", "1").option("--security <code>", "Security code (e.g. UBER.N)", collectList, []).option("--region <code>", "Region code", collectList, []).option("--industry <id>", "Industry ID", collectList, []).option("--broker <id>", "Broker ID", collectList, []).option("--rating <name>", "Rating", collectList, []).option("--rating-change <name>", "Rating change", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>", "Output path")).action((options) => emit(options, (client) => client.call("insight.foreign-opinion.list", {
|
|
197
|
+
from: parseFrom(options.from), size: parseSize(options.size),
|
|
198
|
+
startTime: options.startTime, endTime: options.endTime,
|
|
199
|
+
rankType: parseNumberOption(options.rankType, "--rank-type", { integer: true, min: 1 }),
|
|
200
|
+
keyword: options.keyword,
|
|
201
|
+
regionList: maybeArray(options.region), industryList: maybeArray(options.industry),
|
|
202
|
+
securityList: maybeArray(options.security), brokerList: maybeArray(options.broker),
|
|
203
|
+
ratingList: maybeArray(options.rating), ratingChangeList: maybeArray(options.ratingChange),
|
|
204
|
+
})));
|
|
205
|
+
addTimeFilters(independentOpinion.command("list").option("--rank-type <number>", "Rank type: 1=composite 2=time desc", "1").option("--security <code>", "Security code (e.g. GSK.N)", collectList, []).option("--industry <id>", "Industry ID", collectList, []).option("--rating <name>", "Rating", collectList, []).option("--rating-change <name>", "Rating change", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>", "Output path")).action((options) => emit(options, (client) => client.call("insight.independent-opinion.list", {
|
|
206
|
+
from: parseFrom(options.from), size: parseSize(options.size),
|
|
207
|
+
startTime: options.startTime, endTime: options.endTime,
|
|
208
|
+
rankType: parseNumberOption(options.rankType, "--rank-type", { integer: true, min: 1 }),
|
|
209
|
+
keyword: options.keyword,
|
|
210
|
+
industryList: maybeArray(options.industry), securityList: maybeArray(options.security),
|
|
211
|
+
ratingList: maybeArray(options.rating), ratingChangeList: maybeArray(options.ratingChange),
|
|
212
|
+
})));
|
|
213
|
+
independentOpinion.command("download").requiredOption("--independent-opinion-id <id>").requiredOption("--file-type <number>", "File type: 1=original HTML 2=CN-translated HTML").option("--output <path>").action((options) => withClient(async (client) => {
|
|
260
214
|
await runDownload(client, "insight.independent-opinion.download", { independentOpinionId: options.independentOpinionId, fileType: parseNumberOption(options.fileType, "--file-type", { integer: true, min: 1 }) }, {
|
|
261
215
|
output: options.output,
|
|
262
216
|
fallbackName: `independent-opinion-${options.independentOpinionId}`,
|
|
263
217
|
});
|
|
264
|
-
});
|
|
218
|
+
}));
|
|
265
219
|
insight.addCommand(opinion);
|
|
266
220
|
insight.addCommand(summary);
|
|
267
221
|
insight.addCommand(roadshow);
|
|
@@ -276,36 +230,15 @@ insight.addCommand(foreignOpinion);
|
|
|
276
230
|
insight.addCommand(independentOpinion);
|
|
277
231
|
program.addCommand(insight);
|
|
278
232
|
const quote = new Command("quote").description("Quote APIs");
|
|
279
|
-
quote.command("day-kline").option("--security <code>", "Security code (A-share: .SH/.SZ/.BJ, or 'all' for full market)", collectList, []).option("--start-date <date>", "Start date (default: 1 year before end-date)").option("--end-date <date>", "End date (default: latest)").option("--limit <number>", "Max rows per request (default: 6000, max: 10000)").option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action(
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
});
|
|
283
|
-
quote.command("
|
|
284
|
-
|
|
285
|
-
await printData(await callKlineWithSharding(client, "quote.day-kline-hk", buildQuoteKlineBody(options), { shardDays: 2 }), parseOutputFormat(options.format), options.output);
|
|
286
|
-
});
|
|
287
|
-
quote.command("day-kline-us").option("--security <code>", "Security code (US stock: e.g. AAPL.O, or 'all' for full market)", collectList, []).option("--start-date <date>", "Start date (default: 1 year before end-date)").option("--end-date <date>", "End date (default: latest)").option("--limit <number>", "Max rows per request (default: 6000, max: 10000)").option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action(async (options) => {
|
|
288
|
-
const client = await createClient();
|
|
289
|
-
await printData(await callKlineWithSharding(client, "quote.day-kline-us", buildQuoteKlineBody(options), { shardDays: 1 }), parseOutputFormat(options.format), options.output);
|
|
290
|
-
});
|
|
291
|
-
quote.command("index-day-kline").option("--security <code>", "Index code (.SH/.SZ/.BJ, or 'all' for full market)", collectList, []).option("--start-date <date>", "Start date (default: 1 year before end-date)").option("--end-date <date>", "End date (default: latest)").option("--limit <number>", "Max rows per request (default: 6000, max: 10000)").option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action(async (options) => {
|
|
292
|
-
const client = await createClient();
|
|
293
|
-
await printData(await callKlineWithSharding(client, "quote.index-day-kline", buildQuoteKlineBody(options), { shardDays: 30 }), parseOutputFormat(options.format), options.output);
|
|
294
|
-
});
|
|
295
|
-
quote.command("minute-kline").option("--security <code>", "Security code (A-share only: .SH/.SZ/.BJ)").option("--start-time <datetime>", "Start time (yyyy-MM-dd HH:mm:ss)").option("--end-time <datetime>", "End time (yyyy-MM-dd HH:mm:ss)").option("--limit <number>", "Max rows per request (default: 5000, max: 10000)").option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action(async (options) => {
|
|
296
|
-
const client = await createClient();
|
|
297
|
-
await printData(await client.call("quote.minute-kline", { securityCode: options.security, startTime: options.startTime, endTime: options.endTime, limit: parseOptionalNumberOption(options.limit, "--limit", { integer: true, min: 1 }), fieldList: maybeArray(options.field) }), parseOutputFormat(options.format), options.output);
|
|
298
|
-
});
|
|
299
|
-
quote.command("realtime").description("Realtime quote snapshot (A-share / HK / US)").option("--security <code>", "Security code (e.g. 600519.SH / 00700.HK / AAPL.O), or market keyword: aShares / hkStocks / usStocks", collectList, []).option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action(async (options) => {
|
|
300
|
-
const client = await createClient();
|
|
301
|
-
await printData(await client.call("quote.realtime", { securityList: maybeArray(options.security), fieldList: maybeArray(options.field) }), parseOutputFormat(options.format), options.output);
|
|
302
|
-
});
|
|
233
|
+
quote.command("day-kline").option("--security <code>", "Security code (A-share: .SH/.SZ/.BJ, or 'all' for full market)", collectList, []).option("--start-date <date>", "Start date (default: 1 year before end-date)").option("--end-date <date>", "End date (default: latest)").option("--limit <number>", "Max rows per request (default: 6000, max: 10000)").option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => callKlineWithSharding(client, "quote.day-kline", buildQuoteKlineBody(options), { shardDays: 1 })));
|
|
234
|
+
quote.command("day-kline-hk").option("--security <code>", "Security code (HK stock: .HK, or 'all' for full market)", collectList, []).option("--start-date <date>", "Start date (default: 1 year before end-date)").option("--end-date <date>", "End date (default: latest)").option("--limit <number>", "Max rows per request (default: 6000, max: 10000)").option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => callKlineWithSharding(client, "quote.day-kline-hk", buildQuoteKlineBody(options), { shardDays: 2 })));
|
|
235
|
+
quote.command("day-kline-us").option("--security <code>", "Security code (US stock: e.g. AAPL.O, or 'all' for full market)", collectList, []).option("--start-date <date>", "Start date (default: 1 year before end-date)").option("--end-date <date>", "End date (default: latest)").option("--limit <number>", "Max rows per request (default: 6000, max: 10000)").option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => callKlineWithSharding(client, "quote.day-kline-us", buildQuoteKlineBody(options), { shardDays: 1 })));
|
|
236
|
+
quote.command("index-day-kline").option("--security <code>", "Index code (.SH/.SZ/.BJ, or 'all' for full market)", collectList, []).option("--start-date <date>", "Start date (default: 1 year before end-date)").option("--end-date <date>", "End date (default: latest)").option("--limit <number>", "Max rows per request (default: 6000, max: 10000)").option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => callKlineWithSharding(client, "quote.index-day-kline", buildQuoteKlineBody(options), { shardDays: 30 })));
|
|
237
|
+
quote.command("minute-kline").option("--security <code>", "Security code (A-share only: .SH/.SZ/.BJ)").option("--start-time <datetime>", "Start time (yyyy-MM-dd HH:mm:ss)").option("--end-time <datetime>", "End time (yyyy-MM-dd HH:mm:ss)").option("--limit <number>", "Max rows per request (default: 5000, max: 10000)").option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => client.call("quote.minute-kline", { securityCode: options.security, startTime: options.startTime, endTime: options.endTime, limit: parseOptionalNumberOption(options.limit, "--limit", { integer: true, min: 1 }), fieldList: maybeArray(options.field) })));
|
|
238
|
+
quote.command("realtime").description("Realtime quote snapshot (A-share / HK / US)").option("--security <code>", "Security code (e.g. 600519.SH / 00700.HK / AAPL.O), or market keyword: aShares / hkStocks / usStocks", collectList, []).option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => client.call("quote.realtime", { securityList: maybeArray(options.security), fieldList: maybeArray(options.field) })));
|
|
303
239
|
program.addCommand(quote);
|
|
304
240
|
const fundamental = new Command("fundamental").description("Fundamental APIs");
|
|
305
|
-
const addFinancialReport = (name, endpointKey, periodHelp = "Period") => fundamental.command(name).requiredOption("--security-code <code>").option("--start-date <date>").option("--end-date <date>").option("--fiscal-year <year>", "Fiscal year", collectList, []).option("--period <period>", periodHelp, collectList, []).option("--report-type <type>", "Report type", collectList, []).option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action(
|
|
306
|
-
const client = await createClient();
|
|
307
|
-
await printData(await client.call(endpointKey, { securityCode: options.securityCode, startDate: options.startDate, endDate: options.endDate, fiscalYear: maybeArray(options.fiscalYear), period: options.period.length ? options.period : undefined, reportType: options.reportType.length ? options.reportType : undefined, fieldList: maybeArray(options.field) }), parseOutputFormat(options.format), options.output);
|
|
308
|
-
});
|
|
241
|
+
const addFinancialReport = (name, endpointKey, periodHelp = "Period") => fundamental.command(name).requiredOption("--security-code <code>").option("--start-date <date>").option("--end-date <date>").option("--fiscal-year <year>", "Fiscal year", collectList, []).option("--period <period>", periodHelp, collectList, []).option("--report-type <type>", "Report type", collectList, []).option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => client.call(endpointKey, { securityCode: options.securityCode, startDate: options.startDate, endDate: options.endDate, fiscalYear: maybeArray(options.fiscalYear), period: options.period.length ? options.period : undefined, reportType: options.reportType.length ? options.reportType : undefined, fieldList: maybeArray(options.field) })));
|
|
309
242
|
addFinancialReport("income-statement", "fundamental.income-statement");
|
|
310
243
|
addFinancialReport("income-statement-quarterly", "fundamental.income-statement-quarterly", "Period: q1/q2/q3/q4/latest");
|
|
311
244
|
addFinancialReport("balance-sheet", "fundamental.balance-sheet");
|
|
@@ -314,15 +247,11 @@ addFinancialReport("cash-flow-quarterly", "fundamental.cash-flow-quarterly", "Pe
|
|
|
314
247
|
addFinancialReport("income-statement-hk", "fundamental.income-statement-hk", "Period: q1/h1/q3/h2/nsd/annual/latest");
|
|
315
248
|
addFinancialReport("balance-sheet-hk", "fundamental.balance-sheet-hk", "Period: q1/h1/q3/h2/nsd/annual/latest");
|
|
316
249
|
addFinancialReport("cash-flow-hk", "fundamental.cash-flow-hk", "Period: q1/h1/q3/h2/nsd/annual/latest");
|
|
317
|
-
fundamental.command("main-business").requiredOption("--security-code <code>").option("--start-date <date>").option("--end-date <date>").addOption(new Option("--breakdown <type>", "Breakdown: product/industry/region").choices(["product", "industry", "region"]).default("product")).option("--period <type>", "Period: interim/annual", collectList, []).option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action(
|
|
318
|
-
|
|
319
|
-
await printData(await client.call("fundamental.main-business", { securityCode: options.securityCode, startDate: options.startDate, endDate: options.endDate, breakdown: options.breakdown, periodList: maybeArray(options.period), fieldList: maybeArray(options.field) }), parseOutputFormat(options.format), options.output);
|
|
320
|
-
});
|
|
321
|
-
fundamental.command("valuation-analysis").requiredOption("--security-code <code>").addOption(new Option("--indicator <name>", "Indicator").choices(["peTtm", "pbMrq", "peg", "psTtm", "pcfTtm", "em"]).makeOptionMandatory()).option("--start-date <date>").option("--end-date <date>").option("--limit <number>").option("--field <field>", "Field", collectList, []).option("--skip-null", "Drop rows where value or percentileRank is null").option("--format <format>", "Output format", "table").option("--output <path>").action(async (options) => {
|
|
322
|
-
const client = await createClient();
|
|
250
|
+
fundamental.command("main-business").requiredOption("--security-code <code>").option("--start-date <date>").option("--end-date <date>").addOption(new Option("--breakdown <type>", "Breakdown: product/industry/region").choices(["product", "industry", "region"]).default("product")).option("--period <type>", "Period: interim/annual", collectList, []).option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => client.call("fundamental.main-business", { securityCode: options.securityCode, startDate: options.startDate, endDate: options.endDate, breakdown: options.breakdown, periodList: maybeArray(options.period), fieldList: maybeArray(options.field) })));
|
|
251
|
+
fundamental.command("valuation-analysis").requiredOption("--security-code <code>").addOption(new Option("--indicator <name>", "Indicator").choices(["peTtm", "pbMrq", "peg", "psTtm", "pcfTtm", "em"]).makeOptionMandatory()).option("--start-date <date>").option("--end-date <date>").option("--limit <number>").option("--field <field>", "Field", collectList, []).option("--skip-null", "Drop rows where value or percentileRank is null").option("--format <format>", "Output format", "table").option("--output <path>").action((options) => withClient(async (client) => {
|
|
323
252
|
let data = await client.call("fundamental.valuation-analysis", { securityCode: options.securityCode, indicator: options.indicator, startDate: options.startDate, endDate: options.endDate, limit: parseOptionalNumberOption(options.limit, "--limit", { integer: true, min: 1 }), fieldList: maybeArray(options.field) });
|
|
324
253
|
if (options.skipNull) {
|
|
325
|
-
const normalized =
|
|
254
|
+
const normalized = normalizeRows(data);
|
|
326
255
|
if (normalized && typeof normalized === "object" && !Array.isArray(normalized)) {
|
|
327
256
|
const rec = normalized;
|
|
328
257
|
if (Array.isArray(rec.list)) {
|
|
@@ -337,48 +266,27 @@ fundamental.command("valuation-analysis").requiredOption("--security-code <code>
|
|
|
337
266
|
}
|
|
338
267
|
}
|
|
339
268
|
await printData(data, parseOutputFormat(options.format), options.output);
|
|
340
|
-
});
|
|
341
|
-
fundamental.command("top-holders").requiredOption("--security-code <code>").addOption(new Option("--holder-type <type>", "Holder type: top10/top10Float").choices(["top10", "top10Float"]).makeOptionMandatory()).option("--start-date <date>").option("--end-date <date>").option("--fiscal-year <year>", "Fiscal year", collectList, []).option("--period <period>", "Period: q1/interim/q3/annual/latest", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action(
|
|
342
|
-
|
|
343
|
-
await printData(await client.call("fundamental.top-holders", { securityCode: options.securityCode, holderType: options.holderType, startDate: options.startDate, endDate: options.endDate, fiscalYear: maybeArray(options.fiscalYear), period: options.period.length ? options.period : undefined }), parseOutputFormat(options.format), options.output);
|
|
344
|
-
});
|
|
345
|
-
fundamental.command("earning-forecast").requiredOption("--security-code <code>").option("--start-date <date>", "Start date (default: 1 year before end-date)").option("--end-date <date>", "End date (default: today)").option("--consensus <name>", "Consensus indicator: netIncome/netIncomeYoy/eps/pe/bps/pb/peg/roe/ps", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action(async (options) => {
|
|
346
|
-
const client = await createClient();
|
|
269
|
+
}));
|
|
270
|
+
fundamental.command("top-holders").requiredOption("--security-code <code>").addOption(new Option("--holder-type <type>", "Holder type: top10/top10Float").choices(["top10", "top10Float"]).makeOptionMandatory()).option("--start-date <date>").option("--end-date <date>").option("--fiscal-year <year>", "Fiscal year", collectList, []).option("--period <period>", "Period: q1/interim/q3/annual/latest", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => client.call("fundamental.top-holders", { securityCode: options.securityCode, holderType: options.holderType, startDate: options.startDate, endDate: options.endDate, fiscalYear: maybeArray(options.fiscalYear), period: options.period.length ? options.period : undefined })));
|
|
271
|
+
fundamental.command("earning-forecast").requiredOption("--security-code <code>").option("--start-date <date>", "Start date (default: 1 year before end-date)").option("--end-date <date>", "End date (default: today)").option("--consensus <name>", "Consensus indicator: netIncome/netIncomeYoy/eps/pe/bps/pb/peg/roe/ps", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => {
|
|
347
272
|
const endDate = options.endDate ?? new Date().toISOString().slice(0, 10);
|
|
348
273
|
const startDate = options.startDate ?? new Date(Date.now() - 365 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10);
|
|
349
|
-
|
|
350
|
-
});
|
|
274
|
+
return client.call("fundamental.earning-forecast", { securityCode: options.securityCode, startDate, endDate, consensusList: maybeArray(options.consensus) });
|
|
275
|
+
}));
|
|
351
276
|
program.addCommand(fundamental);
|
|
352
277
|
const ai = new Command("ai").description("AI APIs");
|
|
353
|
-
ai.command("knowledge-batch").requiredOption("--query <text>", "Query", collectList, []).option("--top <number>", "Top", "10").option("--resource-type <number>", "Resource type", collectNumberList, []).option("--knowledge-name <name>", "Knowledge name", collectList, []).option("--start-time <ms>").option("--end-time <ms>").option("--format <format>", "Output format", "json").option("--output <path>").action(
|
|
354
|
-
|
|
355
|
-
await printData(await client.call("ai.knowledge-batch", { queries: options.query, top: parseNumberOption(options.top, "--top", { integer: true, min: 1 }), resourceTypes: options.resourceType.length ? options.resourceType : undefined, knowledgeNames: maybeArray(options.knowledgeName), startTime: parseOptionalNumberOption(options.startTime, "--start-time", { integer: true, min: 0 }), endTime: parseOptionalNumberOption(options.endTime, "--end-time", { integer: true, min: 0 }) }), parseOutputFormat(options.format), options.output);
|
|
356
|
-
});
|
|
357
|
-
ai.command("knowledge-resource-download").requiredOption("--resource-type <number>").requiredOption("--source-id <id>").option("--output <path>").action(async (options) => {
|
|
358
|
-
const client = await createClient();
|
|
278
|
+
ai.command("knowledge-batch").requiredOption("--query <text>", "Query", collectList, []).option("--top <number>", "Top", "10").option("--resource-type <number>", "Resource type", collectNumberList, []).option("--knowledge-name <name>", "Knowledge name", collectList, []).option("--start-time <ms>").option("--end-time <ms>").option("--format <format>", "Output format", "json").option("--output <path>").action((options) => emit(options, (client) => client.call("ai.knowledge-batch", { queries: options.query, top: parseNumberOption(options.top, "--top", { integer: true, min: 1 }), resourceTypes: options.resourceType.length ? options.resourceType : undefined, knowledgeNames: maybeArray(options.knowledgeName), startTime: parseOptionalNumberOption(options.startTime, "--start-time", { integer: true, min: 0 }), endTime: parseOptionalNumberOption(options.endTime, "--end-time", { integer: true, min: 0 }) })));
|
|
279
|
+
ai.command("knowledge-resource-download").requiredOption("--resource-type <number>").requiredOption("--source-id <id>").option("--output <path>").action((options) => withClient(async (client) => {
|
|
359
280
|
await runDownload(client, "ai.knowledge-resource.download", { resourceType: parseNumberOption(options.resourceType, "--resource-type", { integer: true, min: 0 }), sourceId: options.sourceId }, {
|
|
360
281
|
output: options.output,
|
|
361
282
|
fallbackName: `resource-${options.sourceId}`,
|
|
362
283
|
});
|
|
363
|
-
});
|
|
364
|
-
ai.command("security-clue").option("--from <number>", "Starting offset", "0").option("--size <number>", "Total rows to return; omit to fetch all").requiredOption("--start-time <datetime>").requiredOption("--end-time <datetime>").addOption(new Option("--query-mode <mode>").choices(["bySecurity", "byIndustry"]).makeOptionMandatory()).option("--gts-code <code>", "GTS code", collectList, []).option("--source <name>", "Source", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action(
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
});
|
|
368
|
-
ai.command("
|
|
369
|
-
const client = await createClient();
|
|
370
|
-
await printData(await client.call("ai.one-pager", { securityCode: options.securityCode }), parseOutputFormat(options.format), options.output);
|
|
371
|
-
});
|
|
372
|
-
ai.command("investment-logic").requiredOption("--security-code <code>").option("--format <format>", "Output format", "json").option("--output <path>").action(async (options) => {
|
|
373
|
-
const client = await createClient();
|
|
374
|
-
await printData(await client.call("ai.investment-logic", { securityCode: options.securityCode }), parseOutputFormat(options.format), options.output);
|
|
375
|
-
});
|
|
376
|
-
ai.command("peer-comparison").requiredOption("--security-code <code>").option("--format <format>", "Output format", "json").option("--output <path>").action(async (options) => {
|
|
377
|
-
const client = await createClient();
|
|
378
|
-
await printData(await client.call("ai.peer-comparison", { securityCode: options.securityCode }), parseOutputFormat(options.format), options.output);
|
|
379
|
-
});
|
|
380
|
-
ai.command("earnings-review").requiredOption("--security-code <code>").requiredOption("--period <period>", "Report period (e.g. 2025q3, 2025interim, 2025annual)").option("--wait", "Wait for content generation (blocking, up to 3 min)").option("--format <format>", "Output format", "json").option("--output <path>").action(async (options) => {
|
|
381
|
-
const client = await createClient();
|
|
284
|
+
}));
|
|
285
|
+
ai.command("security-clue").option("--from <number>", "Starting offset", "0").option("--size <number>", "Total rows to return; omit to fetch all").requiredOption("--start-time <datetime>").requiredOption("--end-time <datetime>").addOption(new Option("--query-mode <mode>").choices(["bySecurity", "byIndustry"]).makeOptionMandatory()).option("--gts-code <code>", "GTS code", collectList, []).option("--source <name>", "Source", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => client.call("ai.security-clue.list", { from: parseFrom(options.from), size: parseSize(options.size), startTime: options.startTime, endTime: options.endTime, queryMode: options.queryMode, gtsCodeList: maybeArray(options.gtsCode), source: maybeArray(options.source) })));
|
|
286
|
+
ai.command("one-pager").requiredOption("--security-code <code>").option("--format <format>", "Output format", "json").option("--output <path>").action((options) => emit(options, (client) => client.call("ai.one-pager", { securityCode: options.securityCode })));
|
|
287
|
+
ai.command("investment-logic").requiredOption("--security-code <code>").option("--format <format>", "Output format", "json").option("--output <path>").action((options) => emit(options, (client) => client.call("ai.investment-logic", { securityCode: options.securityCode })));
|
|
288
|
+
ai.command("peer-comparison").requiredOption("--security-code <code>").option("--format <format>", "Output format", "json").option("--output <path>").action((options) => emit(options, (client) => client.call("ai.peer-comparison", { securityCode: options.securityCode })));
|
|
289
|
+
ai.command("earnings-review").requiredOption("--security-code <code>").requiredOption("--period <period>", "Report period (e.g. 2025q3, 2025interim, 2025annual)").option("--wait", "Wait for content generation (blocking, up to 3 min)").option("--format <format>", "Output format", "json").option("--output <path>").action((options) => withClient(async (client) => {
|
|
382
290
|
const idResult = await client.call("ai.earnings-review.get-id", { securityCode: options.securityCode, period: options.period });
|
|
383
291
|
const dataId = idResult?.dataId;
|
|
384
292
|
if (!dataId) {
|
|
@@ -396,24 +304,16 @@ ai.command("earnings-review").requiredOption("--security-code <code>").requiredO
|
|
|
396
304
|
process.stderr.write(`Content not available after ${POLL_MAX_ATTEMPTS} attempts. Try again later with: gangtise ai earnings-review-check --data-id ${dataId}\n`);
|
|
397
305
|
process.exitCode = 1;
|
|
398
306
|
}
|
|
399
|
-
});
|
|
400
|
-
ai.command("earnings-review-check").requiredOption("--data-id <id>", "dataId from earnings-review").option("--format <format>", "Output format", "json").option("--output <path>").action(
|
|
401
|
-
|
|
402
|
-
await checkAsyncContent(client, "ai.earnings-review.get-content", options.dataId, parseOutputFormat(options.format), options.output);
|
|
403
|
-
});
|
|
404
|
-
ai.command("theme-tracking").requiredOption("--theme-id <id>", "Theme ID (use lookup theme-id list)").requiredOption("--date <date>", "Date (yyyy-MM-dd)").option("--type <name>", "Report type: morning/night", collectList, []).option("--format <format>", "Output format", "json").option("--output <path>").action(async (options) => {
|
|
405
|
-
const client = await createClient();
|
|
307
|
+
}));
|
|
308
|
+
ai.command("earnings-review-check").requiredOption("--data-id <id>", "dataId from earnings-review").option("--format <format>", "Output format", "json").option("--output <path>").action((options) => withClient((client) => checkAsyncContent(client, "ai.earnings-review.get-content", options.dataId, parseOutputFormat(options.format), options.output)));
|
|
309
|
+
ai.command("theme-tracking").requiredOption("--theme-id <id>", "Theme ID (use lookup theme-id list)").requiredOption("--date <date>", "Date (yyyy-MM-dd)").option("--type <name>", "Report type: morning/night", collectList, []).option("--format <format>", "Output format", "json").option("--output <path>").action((options) => emit(options, (client) => {
|
|
406
310
|
const typeList = options.type.length ? options.type : undefined;
|
|
407
|
-
|
|
408
|
-
});
|
|
409
|
-
ai.command("research-outline").requiredOption("--security-code <code>").option("--format <format>", "Output format", "json").option("--output <path>").action(
|
|
410
|
-
|
|
411
|
-
await printData(await client.call("ai.research-outline", { securityCode: options.securityCode }), parseOutputFormat(options.format), options.output);
|
|
412
|
-
});
|
|
413
|
-
ai.command("hot-topic").option("--from <number>", "Starting offset", "0").option("--size <number>", "Total rows to return; omit to fetch all").option("--start-date <date>", "Start date (yyyy-MM-dd)").option("--end-date <date>", "End date (yyyy-MM-dd)").option("--category <name>", "Report type: morningBriefing/noonBriefing/afternoonFlash/eveningBriefing", collectList, []).option("--with-related-securities", "Include related securities info").option("--no-with-related-securities", "Exclude related securities info").option("--with-close-reading", "Include close reading content").option("--no-with-close-reading", "Exclude close reading content").option("--format <format>", "Output format", "json").option("--output <path>").action(async (options) => {
|
|
414
|
-
const client = await createClient();
|
|
311
|
+
return client.call("ai.theme-tracking", { themeId: options.themeId, date: options.date, type: typeList });
|
|
312
|
+
}));
|
|
313
|
+
ai.command("research-outline").requiredOption("--security-code <code>").option("--format <format>", "Output format", "json").option("--output <path>").action((options) => emit(options, (client) => client.call("ai.research-outline", { securityCode: options.securityCode })));
|
|
314
|
+
ai.command("hot-topic").option("--from <number>", "Starting offset", "0").option("--size <number>", "Total rows to return; omit to fetch all").option("--start-date <date>", "Start date (yyyy-MM-dd)").option("--end-date <date>", "End date (yyyy-MM-dd)").option("--category <name>", "Report type: morningBriefing/noonBriefing/afternoonFlash/eveningBriefing", collectList, []).option("--with-related-securities", "Include related securities info").option("--no-with-related-securities", "Exclude related securities info").option("--with-close-reading", "Include close reading content").option("--no-with-close-reading", "Exclude close reading content").option("--format <format>", "Output format", "json").option("--output <path>").action((options) => emit(options, (client) => {
|
|
415
315
|
const ALL_CATEGORIES = ["morningBriefing", "noonBriefing", "afternoonFlash", "eveningBriefing"];
|
|
416
|
-
|
|
316
|
+
return client.call("ai.hot-topic", {
|
|
417
317
|
from: parseFrom(options.from),
|
|
418
318
|
size: parseSize(options.size),
|
|
419
319
|
startDate: options.startDate,
|
|
@@ -421,26 +321,19 @@ ai.command("hot-topic").option("--from <number>", "Starting offset", "0").option
|
|
|
421
321
|
categoryList: options.category.length > 0 ? options.category : ALL_CATEGORIES,
|
|
422
322
|
withRelatedSecurities: options.withRelatedSecurities === false ? undefined : true,
|
|
423
323
|
withCloseReading: options.withCloseReading === false ? undefined : true,
|
|
424
|
-
})
|
|
425
|
-
});
|
|
426
|
-
ai.command("management-discuss-announcement").requiredOption("--report-date <date>", "Report date (yyyy-MM-dd, e.g. 2025-06-30)").requiredOption("--security-code <code>", "Security code (e.g. 000001.SZ)").addOption(new Option("--dimension <name>", "Discussion dimension: businessOperation/financialPerformance/developmentAndRisk/all").choices(["businessOperation", "financialPerformance", "developmentAndRisk", "all"]).makeOptionMandatory()).option("--format <format>", "Output format", "json").option("--output <path>").action(
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
reportDate: options.reportDate,
|
|
438
|
-
securityCode: options.securityCode,
|
|
439
|
-
discussionDimension: options.dimension,
|
|
440
|
-
}), parseOutputFormat(options.format), options.output);
|
|
441
|
-
});
|
|
442
|
-
ai.command("viewpoint-debate").requiredOption("--viewpoint <text>", "Viewpoint text (max 1000 chars)").option("--wait", "Wait for content generation (blocking, up to 3 min)").option("--format <format>", "Output format", "json").option("--output <path>").action(async (options) => {
|
|
443
|
-
const client = await createClient();
|
|
324
|
+
});
|
|
325
|
+
}));
|
|
326
|
+
ai.command("management-discuss-announcement").requiredOption("--report-date <date>", "Report date (yyyy-MM-dd, e.g. 2025-06-30)").requiredOption("--security-code <code>", "Security code (e.g. 000001.SZ)").addOption(new Option("--dimension <name>", "Discussion dimension: businessOperation/financialPerformance/developmentAndRisk/all").choices(["businessOperation", "financialPerformance", "developmentAndRisk", "all"]).makeOptionMandatory()).option("--format <format>", "Output format", "json").option("--output <path>").action((options) => emit(options, (client) => client.call("ai.management-discuss-announcement", {
|
|
327
|
+
reportDate: options.reportDate,
|
|
328
|
+
securityCode: options.securityCode,
|
|
329
|
+
discussionDimension: options.dimension,
|
|
330
|
+
})));
|
|
331
|
+
ai.command("management-discuss-earnings-call").requiredOption("--report-date <date>", "Report date (yyyy-MM-dd, e.g. 2025-06-30)").requiredOption("--security-code <code>", "Security code (e.g. 000001.SZ)").addOption(new Option("--dimension <name>", "Discussion dimension").choices(["businessOperation", "financialPerformance", "developmentAndRisk"]).makeOptionMandatory()).option("--format <format>", "Output format", "json").option("--output <path>").action((options) => emit(options, (client) => client.call("ai.management-discuss-earnings-call", {
|
|
332
|
+
reportDate: options.reportDate,
|
|
333
|
+
securityCode: options.securityCode,
|
|
334
|
+
discussionDimension: options.dimension,
|
|
335
|
+
})));
|
|
336
|
+
ai.command("viewpoint-debate").requiredOption("--viewpoint <text>", "Viewpoint text (max 1000 chars)").option("--wait", "Wait for content generation (blocking, up to 3 min)").option("--format <format>", "Output format", "json").option("--output <path>").action((options) => withClient(async (client) => {
|
|
444
337
|
const idResult = await client.call("ai.viewpoint-debate.get-id", { viewpoint: options.viewpoint });
|
|
445
338
|
const dataId = idResult?.dataId;
|
|
446
339
|
if (!dataId) {
|
|
@@ -458,86 +351,52 @@ ai.command("viewpoint-debate").requiredOption("--viewpoint <text>", "Viewpoint t
|
|
|
458
351
|
process.stderr.write(`Content not available after ${POLL_MAX_ATTEMPTS} attempts. Try again later with: gangtise ai viewpoint-debate-check --data-id ${dataId}\n`);
|
|
459
352
|
process.exitCode = 1;
|
|
460
353
|
}
|
|
461
|
-
});
|
|
462
|
-
ai.command("viewpoint-debate-check").requiredOption("--data-id <id>", "dataId from viewpoint-debate").option("--format <format>", "Output format", "json").option("--output <path>").action(
|
|
463
|
-
const client = await createClient();
|
|
464
|
-
await checkAsyncContent(client, "ai.viewpoint-debate.get-content", options.dataId, parseOutputFormat(options.format), options.output);
|
|
465
|
-
});
|
|
354
|
+
}));
|
|
355
|
+
ai.command("viewpoint-debate-check").requiredOption("--data-id <id>", "dataId from viewpoint-debate").option("--format <format>", "Output format", "json").option("--output <path>").action((options) => withClient((client) => checkAsyncContent(client, "ai.viewpoint-debate.get-content", options.dataId, parseOutputFormat(options.format), options.output)));
|
|
466
356
|
const reference = new Command("reference").description("Reference data APIs");
|
|
467
|
-
reference.command("securities-search").requiredOption("--keyword <text>", "Search keyword (name/code/pinyin/English)").option("--category <type>", "Category: stock/dr/index/fund", collectList, []).option("--top <number>", "Max results (default: 10, max: 10)", "10").option("--format <format>", "Output format", "table").option("--output <path>").action(
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
top: parseNumberOption(options.top, "--top", { integer: true, min: 1 }),
|
|
473
|
-
}), parseOutputFormat(options.format), options.output);
|
|
474
|
-
});
|
|
357
|
+
reference.command("securities-search").requiredOption("--keyword <text>", "Search keyword (name/code/pinyin/English)").option("--category <type>", "Category: stock/dr/index/fund", collectList, []).option("--top <number>", "Max results (default: 10, max: 10)", "10").option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => client.call("reference.securities-search", {
|
|
358
|
+
keyword: options.keyword,
|
|
359
|
+
category: options.category.length ? options.category : undefined,
|
|
360
|
+
top: parseNumberOption(options.top, "--top", { integer: true, min: 1 }),
|
|
361
|
+
})));
|
|
475
362
|
program.addCommand(reference);
|
|
476
363
|
const vault = new Command("vault").description("Vault APIs");
|
|
477
|
-
vault.command("drive-list").option("--from <number>", "Starting offset", "0").option("--size <number>", "Total rows to return; omit to fetch all").option("--start-time <datetime>").option("--end-time <datetime>").option("--keyword <text>").option("--file-type <number>", "File type", collectNumberList, []).option("--space-type <number>", "Space type", collectNumberList, []).option("--format <format>", "Output format", "table").option("--output <path>").action(
|
|
478
|
-
|
|
479
|
-
await printData(await client.call("vault.drive.list", { from: parseFrom(options.from), size: parseSize(options.size), startTime: options.startTime, endTime: options.endTime, keyword: options.keyword, fileTypeList: options.fileType.length ? options.fileType : undefined, spaceTypeList: options.spaceType.length ? options.spaceType : undefined }), parseOutputFormat(options.format), options.output, { endpointKey: "vault.drive.list", idField: "fileId" });
|
|
480
|
-
});
|
|
481
|
-
vault.command("drive-download").requiredOption("--file-id <id>").option("--output <path>").action(async (options) => {
|
|
482
|
-
const client = await createClient();
|
|
364
|
+
vault.command("drive-list").option("--from <number>", "Starting offset", "0").option("--size <number>", "Total rows to return; omit to fetch all").option("--start-time <datetime>").option("--end-time <datetime>").option("--keyword <text>").option("--file-type <number>", "File type", collectNumberList, []).option("--space-type <number>", "Space type", collectNumberList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => client.call("vault.drive.list", { from: parseFrom(options.from), size: parseSize(options.size), startTime: options.startTime, endTime: options.endTime, keyword: options.keyword, fileTypeList: options.fileType.length ? options.fileType : undefined, spaceTypeList: options.spaceType.length ? options.spaceType : undefined }), { endpointKey: "vault.drive.list", idField: "fileId" }));
|
|
365
|
+
vault.command("drive-download").requiredOption("--file-id <id>").option("--output <path>").action((options) => withClient(async (client) => {
|
|
483
366
|
await runDownload(client, "vault.drive.download", { fileId: options.fileId }, {
|
|
484
367
|
output: options.output,
|
|
485
368
|
fallbackName: `file-${options.fileId}`,
|
|
486
369
|
resolveOutputPath: (result) => resolveTitle(client, result, "vault.drive.list", "fileId", options.fileId),
|
|
487
370
|
});
|
|
488
|
-
});
|
|
489
|
-
vault.command("record-list").option("--from <number>", "Starting offset", "0").option("--size <number>", "Total rows to return; omit to fetch all").option("--start-time <datetime>").option("--end-time <datetime>").option("--keyword <text>").option("--category <name>", "Recording type: upload/link/mobile/gtNote/pc/share", collectList, []).option("--space-type <number>", "Space type: 1=my records / 2=tenant records", collectNumberList, []).option("--format <format>", "Output format", "table").option("--output <path>").action(
|
|
490
|
-
|
|
491
|
-
await printData(await client.call("vault.record.list", { from: parseFrom(options.from), size: parseSize(options.size), startTime: options.startTime, endTime: options.endTime, keyword: options.keyword, categoryList: maybeArray(options.category), spaceTypeList: options.spaceType.length ? options.spaceType : undefined }), parseOutputFormat(options.format), options.output, { endpointKey: "vault.record.list", idField: "recordId" });
|
|
492
|
-
});
|
|
493
|
-
vault.command("record-download").requiredOption("--record-id <id>").requiredOption("--content-type <type>", "Content type: original/asr/summary").option("--output <path>").action(async (options) => {
|
|
494
|
-
const client = await createClient();
|
|
371
|
+
}));
|
|
372
|
+
vault.command("record-list").option("--from <number>", "Starting offset", "0").option("--size <number>", "Total rows to return; omit to fetch all").option("--start-time <datetime>").option("--end-time <datetime>").option("--keyword <text>").option("--category <name>", "Recording type: upload/link/mobile/gtNote/pc/share", collectList, []).option("--space-type <number>", "Space type: 1=my records / 2=tenant records", collectNumberList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => client.call("vault.record.list", { from: parseFrom(options.from), size: parseSize(options.size), startTime: options.startTime, endTime: options.endTime, keyword: options.keyword, categoryList: maybeArray(options.category), spaceTypeList: options.spaceType.length ? options.spaceType : undefined }), { endpointKey: "vault.record.list", idField: "recordId" }));
|
|
373
|
+
vault.command("record-download").requiredOption("--record-id <id>").requiredOption("--content-type <type>", "Content type: original/asr/summary").option("--output <path>").action((options) => withClient(async (client) => {
|
|
495
374
|
await runDownload(client, "vault.record.download", { recordId: options.recordId, contentType: options.contentType }, {
|
|
496
375
|
output: options.output,
|
|
497
376
|
fallbackName: `record-${options.recordId}`,
|
|
498
377
|
resolveOutputPath: (result) => resolveTitle(client, result, "vault.record.list", "recordId", options.recordId),
|
|
499
378
|
});
|
|
500
|
-
});
|
|
501
|
-
vault.command("my-conference-list").option("--from <number>", "Starting offset", "0").option("--size <number>", "Total rows to return; omit to fetch all").option("--start-time <datetime>").option("--end-time <datetime>").option("--keyword <text>").option("--research-area <id>", "Research area ID", collectList, []).option("--security <code>", "Security code", collectList, []).option("--institution <id>", "Institution ID", collectList, []).option("--category <name>", "Conference category: earningsCall/strategyMeeting/fundRoadshow/shareholdersMeeting/maMeeting/specialMeeting/companyAnalysis/industryAnalysis/other", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action(
|
|
502
|
-
|
|
503
|
-
await printData(await client.call("vault.my-conference.list", { from: parseFrom(options.from), size: parseSize(options.size), startTime: options.startTime, endTime: options.endTime, keyword: options.keyword, researchAreaList: maybeArray(options.researchArea), securityList: maybeArray(options.security), institutionList: maybeArray(options.institution), categoryList: maybeArray(options.category) }), parseOutputFormat(options.format), options.output, { endpointKey: "vault.my-conference.list", idField: "conferenceId" });
|
|
504
|
-
});
|
|
505
|
-
vault.command("my-conference-download").requiredOption("--conference-id <id>").requiredOption("--content-type <type>", "Content type: asr/summary").option("--output <path>").action(async (options) => {
|
|
506
|
-
const client = await createClient();
|
|
379
|
+
}));
|
|
380
|
+
vault.command("my-conference-list").option("--from <number>", "Starting offset", "0").option("--size <number>", "Total rows to return; omit to fetch all").option("--start-time <datetime>").option("--end-time <datetime>").option("--keyword <text>").option("--research-area <id>", "Research area ID", collectList, []).option("--security <code>", "Security code", collectList, []).option("--institution <id>", "Institution ID", collectList, []).option("--category <name>", "Conference category: earningsCall/strategyMeeting/fundRoadshow/shareholdersMeeting/maMeeting/specialMeeting/companyAnalysis/industryAnalysis/other", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => client.call("vault.my-conference.list", { from: parseFrom(options.from), size: parseSize(options.size), startTime: options.startTime, endTime: options.endTime, keyword: options.keyword, researchAreaList: maybeArray(options.researchArea), securityList: maybeArray(options.security), institutionList: maybeArray(options.institution), categoryList: maybeArray(options.category) }), { endpointKey: "vault.my-conference.list", idField: "conferenceId" }));
|
|
381
|
+
vault.command("my-conference-download").requiredOption("--conference-id <id>").requiredOption("--content-type <type>", "Content type: asr/summary").option("--output <path>").action((options) => withClient(async (client) => {
|
|
507
382
|
await runDownload(client, "vault.my-conference.download", { conferenceId: options.conferenceId, contentType: options.contentType }, {
|
|
508
383
|
output: options.output,
|
|
509
384
|
fallbackName: `conference-${options.conferenceId}`,
|
|
510
385
|
resolveOutputPath: (result) => resolveTitle(client, result, "vault.my-conference.list", "conferenceId", options.conferenceId),
|
|
511
386
|
});
|
|
512
|
-
});
|
|
513
|
-
vault.command("wechat-message-list").option("--from <number>", "Starting offset", "0").option("--size <number>", "Total rows to return; omit to fetch all").option("--start-time <datetime>").option("--end-time <datetime>").option("--keyword <text>").option("--security <code>", "Security code (e.g. 000001.SZ)", collectList, []).option("--wechat-group-id <id>", "WeChat group ID", collectList, []).option("--industry <id>", "Industry ID", collectList, []).option("--category <name>", "Message type: text/image/documents/url", collectList, []).option("--tag <name>", "Tag: roadShow/research/strategyMeeting/meetingSummary/industryComment/companyComment/earningsReview", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action(
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
});
|
|
517
|
-
vault.command("wechat-chatroom-list").option("--from <number>", "Starting offset", "0").option("--size <number>", "Rows to return", "20").option("--room-name <name>", "WeChat group name; repeat or comma-separate for multiple names", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action(async (options) => {
|
|
518
|
-
const client = await createClient();
|
|
519
|
-
await printData(await client.call("vault.wechat-chatroom.list", buildWechatChatroomListBody(options)), parseOutputFormat(options.format), options.output);
|
|
520
|
-
});
|
|
521
|
-
vault.command("stock-pool-list").option("--format <format>", "Output format", "table").option("--output <path>").action(async (options) => {
|
|
522
|
-
const client = await createClient();
|
|
523
|
-
await printData(await client.call("vault.stock-pool.list", {}), parseOutputFormat(options.format), options.output);
|
|
524
|
-
});
|
|
525
|
-
vault.command("stock-pool-stocks").option("--pool-id <id>", "Pool ID; repeat for multiple; use 'all' for all pools", collectList, ["all"]).option("--format <format>", "Output format", "table").option("--output <path>").action(async (options) => {
|
|
526
|
-
const client = await createClient();
|
|
527
|
-
await printData(await client.call("vault.stock-pool.stocks", { poolIdList: options.poolId }), parseOutputFormat(options.format), options.output);
|
|
528
|
-
});
|
|
387
|
+
}));
|
|
388
|
+
vault.command("wechat-message-list").option("--from <number>", "Starting offset", "0").option("--size <number>", "Total rows to return; omit to fetch all").option("--start-time <datetime>").option("--end-time <datetime>").option("--keyword <text>").option("--security <code>", "Security code (e.g. 000001.SZ)", collectList, []).option("--wechat-group-id <id>", "WeChat group ID", collectList, []).option("--industry <id>", "Industry ID", collectList, []).option("--category <name>", "Message type: text/image/documents/url", collectList, []).option("--tag <name>", "Tag: roadShow/research/strategyMeeting/meetingSummary/industryComment/companyComment/earningsReview", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => client.call("vault.wechat-message.list", buildWechatMessageListBody(options))));
|
|
389
|
+
vault.command("wechat-chatroom-list").option("--from <number>", "Starting offset", "0").option("--size <number>", "Rows to return", "20").option("--room-name <name>", "WeChat group name; repeat or comma-separate for multiple names", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => client.call("vault.wechat-chatroom.list", buildWechatChatroomListBody(options))));
|
|
390
|
+
vault.command("stock-pool-list").option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => client.call("vault.stock-pool.list", {})));
|
|
391
|
+
vault.command("stock-pool-stocks").option("--pool-id <id>", "Pool ID; repeat for multiple; use 'all' for all pools", collectList, ["all"]).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => client.call("vault.stock-pool.stocks", { poolIdList: options.poolId })));
|
|
529
392
|
program.addCommand(vault);
|
|
530
393
|
program.addCommand(ai);
|
|
531
394
|
const alternative = new Command("alternative").description("Alternative data APIs");
|
|
532
|
-
alternative.command("edb-search").requiredOption("--keyword <text>", "Search keyword (e.g. '空调')").option("--limit <number>", "Max results (default: 100, max: 200)", "100").option("--format <format>", "Output format", "table").option("--output <path>").action(
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
}), parseOutputFormat(options.format), options.output);
|
|
538
|
-
});
|
|
539
|
-
alternative.command("edb-data").option("--indicator-id <id>", "Indicator ID (repeat, max 10)", collectList, []).requiredOption("--start-date <date>", "Start date (yyyy-MM-dd)").requiredOption("--end-date <date>", "End date (yyyy-MM-dd)").option("--format <format>", "Output format", "table").option("--output <path>").action(async (options) => {
|
|
540
|
-
const client = await createClient();
|
|
395
|
+
alternative.command("edb-search").requiredOption("--keyword <text>", "Search keyword (e.g. '空调')").option("--limit <number>", "Max results (default: 100, max: 200)", "100").option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => client.call("alternative.edb-search", {
|
|
396
|
+
keyword: options.keyword,
|
|
397
|
+
limit: parseNumberOption(options.limit, "--limit", { integer: true, min: 1 }),
|
|
398
|
+
})));
|
|
399
|
+
alternative.command("edb-data").option("--indicator-id <id>", "Indicator ID (repeat, max 10)", collectList, []).requiredOption("--start-date <date>", "Start date (yyyy-MM-dd)").requiredOption("--end-date <date>", "End date (yyyy-MM-dd)").option("--format <format>", "Output format", "table").option("--output <path>").action((options) => withClient(async (client) => {
|
|
541
400
|
const raw = await client.call("alternative.edb-data", {
|
|
542
401
|
indicatorIdList: options.indicatorId,
|
|
543
402
|
startDate: options.startDate,
|
|
@@ -552,7 +411,7 @@ alternative.command("edb-data").option("--indicator-id <id>", "Indicator ID (rep
|
|
|
552
411
|
data = { list, total: list.length };
|
|
553
412
|
}
|
|
554
413
|
await printData(data, parseOutputFormat(options.format), options.output);
|
|
555
|
-
});
|
|
414
|
+
}));
|
|
556
415
|
program.addCommand(alternative);
|
|
557
416
|
program.command("raw").description("Raw API calls").addCommand(new Command("call").argument("<endpointKey>").option("--body <json>").option("--query <key=value>", "Query string pair", collectKeyValue, {}).option("--format <format>", "Output format", "json").option("--output <path>").action(async (endpointKey, options) => {
|
|
558
417
|
const endpoint = ENDPOINTS[endpointKey];
|
package/dist/src/core/client.js
CHANGED
|
@@ -49,6 +49,27 @@ export class GangtiseClient {
|
|
|
49
49
|
await writeTokenCache(this.config.tokenCachePath, cache);
|
|
50
50
|
return accessToken;
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* On a recoverable auth error (expired/invalid token codes), force a one-time
|
|
54
|
+
* token refresh and re-throw as retryable so withRetry replays the request.
|
|
55
|
+
* Otherwise — or once we've already retried this request — it's a no-op and
|
|
56
|
+
* the caller re-throws the original error. `authState` persists across the
|
|
57
|
+
* withRetry attempts so we only refresh once per logical request.
|
|
58
|
+
*/
|
|
59
|
+
async refreshAuthIfRecoverable(error, useAuth, authState) {
|
|
60
|
+
if (useAuth
|
|
61
|
+
&& !authState.retried
|
|
62
|
+
&& error instanceof ApiError
|
|
63
|
+
&& error.code
|
|
64
|
+
&& AUTH_RETRY_CODES.has(error.code)
|
|
65
|
+
&& this.config.accessKey
|
|
66
|
+
&& this.config.secretKey) {
|
|
67
|
+
authState.retried = true;
|
|
68
|
+
this.memoCache = null;
|
|
69
|
+
await this.getAuthorizationHeader(true);
|
|
70
|
+
throw markRetryable(new ApiError(error.message, error.code, error.statusCode, error.details));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
52
73
|
isEnvelope(parsed) {
|
|
53
74
|
if (!parsed || typeof parsed !== 'object')
|
|
54
75
|
return false;
|
|
@@ -200,7 +221,7 @@ export class GangtiseClient {
|
|
|
200
221
|
}
|
|
201
222
|
const dispatcher = getDispatcher();
|
|
202
223
|
const url = new URL(endpoint.path, this.config.baseUrl);
|
|
203
|
-
|
|
224
|
+
const authState = { retried: false };
|
|
204
225
|
return withRetry(async () => {
|
|
205
226
|
const headers = {
|
|
206
227
|
'content-type': 'application/json',
|
|
@@ -236,18 +257,7 @@ export class GangtiseClient {
|
|
|
236
257
|
return this.unwrapEnvelope(parsed, response.statusCode);
|
|
237
258
|
}
|
|
238
259
|
catch (error) {
|
|
239
|
-
|
|
240
|
-
if (useAuth
|
|
241
|
-
&& !authRetried
|
|
242
|
-
&& error instanceof ApiError
|
|
243
|
-
&& error.code
|
|
244
|
-
&& AUTH_RETRY_CODES.has(error.code)
|
|
245
|
-
&& (this.config.accessKey && this.config.secretKey)) {
|
|
246
|
-
authRetried = true;
|
|
247
|
-
this.memoCache = null;
|
|
248
|
-
await this.getAuthorizationHeader(true);
|
|
249
|
-
throw markRetryable(new ApiError(error.message, error.code, error.statusCode, error.details));
|
|
250
|
-
}
|
|
260
|
+
await this.refreshAuthIfRecoverable(error, useAuth, authState);
|
|
251
261
|
throw error;
|
|
252
262
|
}
|
|
253
263
|
}, {
|
|
@@ -265,6 +275,7 @@ export class GangtiseClient {
|
|
|
265
275
|
Object.entries(query).forEach(([key, value]) => {
|
|
266
276
|
url.searchParams.set(key, String(value));
|
|
267
277
|
});
|
|
278
|
+
const authState = { retried: false };
|
|
268
279
|
return withRetry(async () => {
|
|
269
280
|
const authorization = await this.getAuthorizationHeader();
|
|
270
281
|
const startedAt = Date.now();
|
|
@@ -292,7 +303,14 @@ export class GangtiseClient {
|
|
|
292
303
|
if (response.statusCode >= 400) {
|
|
293
304
|
this.throwHttpError(parsed, response.statusCode);
|
|
294
305
|
}
|
|
295
|
-
|
|
306
|
+
let data;
|
|
307
|
+
try {
|
|
308
|
+
data = this.unwrapEnvelope(parsed, response.statusCode);
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
await this.refreshAuthIfRecoverable(error, true, authState);
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
296
314
|
if (data && typeof data === 'object' && 'url' in data && typeof data.url === 'string') {
|
|
297
315
|
return { url: String(data.url), contentType };
|
|
298
316
|
}
|
package/dist/src/core/output.js
CHANGED
|
@@ -67,21 +67,13 @@ function renderCsv(rows) {
|
|
|
67
67
|
return "";
|
|
68
68
|
}
|
|
69
69
|
const columns = Array.from(new Set(rows.flatMap((row) => Object.keys(row))));
|
|
70
|
-
const escape = (value) => {
|
|
71
|
-
if (/^[=+\-@\t\r]/.test(value)) {
|
|
72
|
-
value = "'" + value;
|
|
73
|
-
}
|
|
74
|
-
if (/[",\n]/.test(value)) {
|
|
75
|
-
return `"${value.replaceAll("\"", "\"\"")}"`;
|
|
76
|
-
}
|
|
77
|
-
return value;
|
|
78
|
-
};
|
|
79
70
|
const header = columns.join(",");
|
|
80
|
-
const body = rows.map((row) => columns.map((column) =>
|
|
71
|
+
const body = rows.map((row) => columns.map((column) => csvEscape(formatScalar(row[column]))).join(","));
|
|
81
72
|
return [header, ...body].join("\n");
|
|
82
73
|
}
|
|
83
74
|
export function renderOutput(value, format) {
|
|
84
|
-
|
|
75
|
+
// toRows is computed lazily per branch: json never needs it, and jsonl only
|
|
76
|
+
// falls back to it when the value isn't already a {list}/array.
|
|
85
77
|
switch (format) {
|
|
86
78
|
case "json":
|
|
87
79
|
return JSON.stringify(value, null, 2);
|
|
@@ -89,15 +81,15 @@ export function renderOutput(value, format) {
|
|
|
89
81
|
const items = value && typeof value === "object" && !Array.isArray(value) && Array.isArray(value.list)
|
|
90
82
|
? value.list
|
|
91
83
|
: null;
|
|
92
|
-
return (items ??
|
|
84
|
+
return (items ?? toRows(value)).map((item) => JSON.stringify(item)).join("\n");
|
|
93
85
|
}
|
|
94
86
|
case "csv":
|
|
95
|
-
return renderCsv(
|
|
87
|
+
return renderCsv(toRows(value));
|
|
96
88
|
case "markdown":
|
|
97
|
-
return renderMarkdown(
|
|
89
|
+
return renderMarkdown(toRows(value));
|
|
98
90
|
case "table":
|
|
99
91
|
default:
|
|
100
|
-
return renderTable(
|
|
92
|
+
return renderTable(toRows(value));
|
|
101
93
|
}
|
|
102
94
|
}
|
|
103
95
|
/** Stream large jsonl/csv output row-by-row to avoid building a full string in memory. */
|
|
@@ -4,6 +4,9 @@ const DAY_MS = 86_400_000;
|
|
|
4
4
|
* `--security all` queries so a 2-day A-share shard (~11K rows) isn't
|
|
5
5
|
* silently truncated. Single-security queries are untouched. */
|
|
6
6
|
const ALL_MARKET_LIMIT = 10_000;
|
|
7
|
+
/** Shard fan-out concurrency. Shares the GANGTISE_PAGE_CONCURRENCY knob with
|
|
8
|
+
* pagination (see transport/client) so one env var tunes all request fan-out. */
|
|
9
|
+
const SHARD_CONCURRENCY = Number(process.env.GANGTISE_PAGE_CONCURRENCY ?? 5) || 5;
|
|
7
10
|
function parseDate(value) {
|
|
8
11
|
// Accept yyyy-MM-dd; reject anything else so we can fall back to a single request.
|
|
9
12
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(value))
|
|
@@ -62,7 +65,7 @@ export async function callKlineWithSharding(client, endpointKey, body, config) {
|
|
|
62
65
|
if (process.env.GANGTISE_VERBOSE === "1" || process.env.GANGTISE_VERBOSE === "true") {
|
|
63
66
|
process.stderr.write(`[gangtise] sharding ${endpointKey} into ${shards.length} requests (${config.shardDays} day(s) each)\n`);
|
|
64
67
|
}
|
|
65
|
-
const results = await runWithConcurrency(shards, config.concurrency ??
|
|
68
|
+
const results = await runWithConcurrency(shards, config.concurrency ?? SHARD_CONCURRENCY, async (shard) => {
|
|
66
69
|
return client.call(endpointKey, { ...allMarketBody, startDate: shard.startDate, endDate: shard.endDate });
|
|
67
70
|
});
|
|
68
71
|
let fieldList;
|
|
@@ -4,6 +4,15 @@ import path from "node:path";
|
|
|
4
4
|
export const DEFAULT_TITLE_CACHE_PATH = path.join(os.homedir(), ".config", "gangtise", "title-cache.json");
|
|
5
5
|
export const TITLE_LOOKUP_SIZE = 200;
|
|
6
6
|
const TITLE_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
7
|
+
/**
|
|
8
|
+
* Hard cap on titles kept per endpoint. The cache only needs the recent working
|
|
9
|
+
* set so downloads can resolve friendly filenames; without a cap, `writeTitleCache`
|
|
10
|
+
* merges forever and the endpoint's `ts` refreshes on every write, so the TTL
|
|
11
|
+
* never expires it — the file grew to tens of MB in practice, and `resolveTitle`
|
|
12
|
+
* re-parses the whole thing on every download. Bounds the file to roughly
|
|
13
|
+
* (#cached endpoints × this × avg entry size).
|
|
14
|
+
*/
|
|
15
|
+
export const MAX_TITLES_PER_ENDPOINT = 5_000;
|
|
7
16
|
// Per-process in-memory snapshot of the cache. We read the file at most once,
|
|
8
17
|
// merge subsequent writes in memory, and flush atomically. This avoids the
|
|
9
18
|
// "read whole file → modify → write whole file" pattern firing on every list
|
|
@@ -46,10 +55,65 @@ async function flush(filePath) {
|
|
|
46
55
|
throw error;
|
|
47
56
|
}
|
|
48
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Trim `merged` down to `cap` entries. The freshly-written ids (`freshKeys`) are
|
|
60
|
+
* kept first since those are what the user just listed and is most likely to
|
|
61
|
+
* download; remaining capacity is filled from the rest. Returns `merged` as-is
|
|
62
|
+
* when already within the cap.
|
|
63
|
+
*/
|
|
64
|
+
function capTitles(merged, freshKeys, cap) {
|
|
65
|
+
if (Object.keys(merged).length <= cap)
|
|
66
|
+
return merged;
|
|
67
|
+
const out = {};
|
|
68
|
+
let n = 0;
|
|
69
|
+
for (const k of freshKeys) {
|
|
70
|
+
if (n >= cap)
|
|
71
|
+
break;
|
|
72
|
+
if (k in merged && !(k in out)) {
|
|
73
|
+
out[k] = merged[k];
|
|
74
|
+
n++;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (n < cap) {
|
|
78
|
+
for (const k of Object.keys(merged)) {
|
|
79
|
+
if (n >= cap)
|
|
80
|
+
break;
|
|
81
|
+
if (k in out)
|
|
82
|
+
continue;
|
|
83
|
+
out[k] = merged[k];
|
|
84
|
+
n++;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Keep the cache bounded on every write: drop endpoint entries past their TTL
|
|
91
|
+
* (lookups already ignore them) and cap each endpoint's titles. The just-written
|
|
92
|
+
* endpoint keeps its newest ids first; others over the cap are trimmed too, so a
|
|
93
|
+
* pre-existing oversized file shrinks on the next write.
|
|
94
|
+
*/
|
|
95
|
+
function pruneCache(data, freshEndpoint, freshKeys) {
|
|
96
|
+
const now = Date.now();
|
|
97
|
+
for (const key of Object.keys(data)) {
|
|
98
|
+
const entry = data[key];
|
|
99
|
+
if (!entry || typeof entry.ts !== "number" || now - entry.ts > TITLE_CACHE_TTL_MS) {
|
|
100
|
+
delete data[key];
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (!entry.titles || typeof entry.titles !== "object") {
|
|
104
|
+
delete data[key];
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (Object.keys(entry.titles).length > MAX_TITLES_PER_ENDPOINT) {
|
|
108
|
+
entry.titles = capTitles(entry.titles, key === freshEndpoint ? freshKeys : [], MAX_TITLES_PER_ENDPOINT);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
49
112
|
export async function writeTitleCache(endpoint, titles, filePath = DEFAULT_TITLE_CACHE_PATH) {
|
|
50
113
|
const data = await loadInto(filePath);
|
|
51
114
|
const existing = data[endpoint]?.titles ?? {};
|
|
52
115
|
data[endpoint] = { titles: { ...existing, ...titles }, ts: Date.now() };
|
|
116
|
+
pruneCache(data, endpoint, Object.keys(titles));
|
|
53
117
|
dirty = true;
|
|
54
118
|
// Coalesce concurrent writes: the in-flight flush picks up everything dirty.
|
|
55
119
|
if (pendingWrite)
|
package/dist/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Auto-generated — DO NOT EDIT
|
|
2
|
-
export const CLI_VERSION = "0.14.
|
|
2
|
+
export const CLI_VERSION = "0.14.3";
|